Skip to content

Commit

Permalink
improved handling of init* methods that don't return self.
Browse files Browse the repository at this point in the history
NSXMLDocument is one of many classes whose init methods return
something other than self. Others include NSArray and NSDictionary,
which return a special "placeholder" subclass instance from alloc.
NSXMLDocument behaves similarly but without an explicit placeholder
subclass.

This change should eliminate a crash that occurred when placeholders
were overreleased.

In past versions of Nu, we looked for known explicit placeholders
and handled them specially, but this was removed in Lion when the
NSArray and NSDictionary placeholders subclasses appeared to to
ignore release messages. However, this was not true for
NSXMLDocument, which implicitly uses the placeholder pattern and
does not have a placeholder subclass.

Now we watch methods whose names begin with "init" to see if they
return their original target or not, which allows us to properly
handle the placeholder pattern whether it is used from an explicit
placeholder subclass or not.
  • Loading branch information
timburks committed Aug 27, 2011
1 parent d323678 commit 270fbd0
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 10 deletions.
35 changes: 25 additions & 10 deletions objc/Nu.m
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,10 @@ - (NSArray *) arguments
int NuMain(int argc, const char *argv[])
{
@autoreleasepool {
NuInit();
NuInit();

@try
{
{
// first we try to load main.nu from the application bundle.
NSString *main_path = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"nu"];
if (main_path) {
Expand Down Expand Up @@ -1692,6 +1693,7 @@ static id nu_calling_objc_method_handler(id target, Method m, NSMutableArray *ar
[target class];

//NSLog(@"calling ObjC method %s with target of class %@", sel_getName(method_getName(m)), [target class]);

IMP imp = method_getImplementation(m);

// if the imp has an associated block, this is a nu-to-nu call.
Expand Down Expand Up @@ -1752,9 +1754,7 @@ static id nu_calling_objc_method_handler(id target, Method m, NSMutableArray *ar

raise_argc_exception(s, argument_count-2, [args count]);
}
else {
bool success = false;

else {
char return_type_buffer[BUFSIZE], arg_type_buffer[BUFSIZE];
method_getReturnType(m, return_type_buffer, BUFSIZE);
ffi_type *result_type = ffi_type_for_objc_type(&return_type_buffer[0]);
Expand Down Expand Up @@ -1789,13 +1789,18 @@ static id nu_calling_objc_method_handler(id target, Method m, NSMutableArray *ar
NSLog (@"failed to prepare cif structure");
}
else {
const char *method_name = sel_getName(method_getName(m));
BOOL callingInitializer = !strncmp("init", method_name, 4);
if (callingInitializer) {
[target retain]; // in case an init method releases its target (to return something else), we preemptively retain it
}
// call the method handler
ffi_call(&cif2, FFI_FN(imp), result_value, argument_values);
success = true;
}
if (success) {
// extract the return value
result = get_nu_value_from_objc_value(result_value, &return_type_buffer[0]);
// NSLog(@"result is %@", result);
// NSLog(@"retain count %d", [result retainCount]);

// Return values should not require a release.
// Either they are owned by an existing object or are autoreleased.
// Exceptions to this rule are handled below.
Expand All @@ -1809,6 +1814,16 @@ static id nu_calling_objc_method_handler(id target, Method m, NSMutableArray *ar
if (already_retained) {
[result autorelease];
}

if (callingInitializer) {
if (result == target) {
// NSLog(@"undoing preemptive retain of init target %@", [target className]);
[target release]; // undo our preemptive retain
} else {
// NSLog(@"keeping preemptive retain of init target %@", [target className]);
}
}

for (i = 0; i < [args count]; i++) {
if (argument_needs_retained[i])
[[args objectAtIndex:i] retainReferencedObject];
Expand Down Expand Up @@ -6319,8 +6334,8 @@ - (id) sendMessage:(id)cdr withContext:(NSMutableDictionary *)context
}
// Test if target specifies another object that should receive the message
else if ( (target = [target forwardingTargetForSelector:sel]) ) {
//NSLog(@"found forwarding target: %@ for selector: %@", target, NSStringFromSelector(sel));
result = [target sendMessage:cdr withContext:context];
//NSLog(@"found forwarding target: %@ for selector: %@", target, NSStringFromSelector(sel));
result = [target sendMessage:cdr withContext:context];
}
// Otherwise, call the overridable handler for unknown messages.
else {
Expand Down
13 changes: 13 additions & 0 deletions test/test_xml.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
;; test_xml.nu
;; tests for Nu usage of NSXMLDocument.
;;
;; Copyright (c) 2011 Tim Burks, Radtastical Inc.

(unless (eq (uname) "iOS")

(class TestXML is NuTestCase

(- (id) testNoCrash is
(set xmlText "<sample><one/><two/><three/></sample>")
(set xmlData (xmlText dataUsingEncoding:NSUTF8StringEncoding))
(set doc ((NSXMLDocument alloc) initWithData:xmlData options:0 error:nil)))))

0 comments on commit 270fbd0

Please sign in to comment.