A deep copy of dictionaries gives an analysis error in Xcode 4.2

I have the following method in the NSDictionary category to make a deep copy that works great.

I just upgraded from Xcode 4.1 to 4.2, and the Analyze function gives two analyzer warnings for this code, as indicated:

- (id)deepCopy; { id dict = [[NSMutableDictionary alloc] init]; id copy; for (id key in self) { id object = [self objectForKey:key]; if ([object respondsToSelector:@selector(deepCopy)]) copy = [object deepCopy]; else copy = [object copy]; [dict setObject:copy forKey:key]; // Both -deepCopy and -copy retain the object, and so does -setObject:forKey:, so need to -release: [copy release]; // Xcode 4.2 Analyze says this is an incorrect decrement of the reference count?! } return dict; // Xcode 4.2 Analyze says this is a potential leak } 

Are these errors in the Xcode analyzer or are there any changes I can make to avoid these warnings?

I am not using ARC yet, although I am wondering if there are any additional changes needed to support ARC for this method.

+3
cocoa deep-copy analyzer clang-static-analyzer
Oct. 14 '11 at 16:48
source share
2 answers

Presumably this is because deepCopy does not start with the copy prefix.

So you might want to change something like copyWithDeepCopiedValues (or something like that) and then see if the analyzer indicates this.

Update

As noted by Aleksander, you can use attributes to indicate the purpose of link counting. This should (IMO) be the exception to the rule and be rarely, if ever, used. Personally, I will not use attributes for objc methods, because it is fragile.

The only attribute I have used so far has been consume , and every time I use these attributes, it was in statically typed contexts (e.g. C functions and C ++ functions and methods).

Reasons you should avoid attributes when possible:

1) Stick to conventions for programmers. The code is more understandable, and you do not need to refer to the documentation.

2) This approach is fragile. You can still introduce a link counter imbalance, and attributes can be used to introduce assembly errors due to conflicts in the attributes.

All versions are built with ARC enabled:

Case No. 1

 #import <Foundation/Foundation.h> @interface MONType : NSObject - (NSString *)string __attribute__((objc_method_family(copy))); @end @implementation MONType - (NSString *)string { NSMutableString * ret = [NSMutableString new]; [ret appendString:@"MONType"]; return ret; } @end int main (int argc, const char * argv[]) { @autoreleasepool { id obj = nil; if (random() % 2U) { obj = [[NSAttributedString alloc] initWithString:@"NSAttributedString"]; } else { obj = [MONType new]; } NSLog(@"Result: %@, %@", obj, [obj string]); } /* this tool name is ARC, dump the leaks: */ system("leaks ARC"); return 0; } 

This program throws the following error: error: multiple methods named 'string' found with mismatched result, parameter type or attributes .

Great, the compiler does everything possible to prevent these problems. This means that conflicts in attributes can introduce translation-based errors. This is bad because when non-trivial code bases are combined and attribute conflicts, you will have errors to fix and update programs. It also means that simply including other libraries in translation units can violate existing programs when using attributes.

Case No. 2

header.h

 extern id NewObject(void); 

Header.m

 #import <Foundation/Foundation.h> #import "Header.h" @interface MONType : NSObject - (NSString *)string __attribute__((objc_method_family(copy))); @end @implementation MONType - (NSString *)string { NSMutableString * ret = [NSMutableString new]; [ret appendString:@"-[MONType string]"]; return ret; } @end id NewObject(void) { id obj = nil; if (random() % 2U) { obj = [[NSAttributedString alloc] initWithString:@"NSAttributedString"]; } else { obj = [MONType new]; } return obj; } 

main.m

 #import <Foundation/Foundation.h> #import "Header.h" int main (int argc, const char * argv[]) { @autoreleasepool { for (size_t idx = 0; idx < 8; ++idx) { id obj = NewObject(); NSLog(@"Result: %@, %@", obj, [obj string]); } } /* this tool name is ARC, dump the leaks: */ system("leaks ARC"); return 0; } 

Ok This is just bad . We introduced leaks because the necessary information was not available in the translation block. Leaks reported here:

 leaks Report Version: 2.0 Process 7778: 1230 nodes malloced for 210 KB Process 7778: 4 leaks for 192 total leaked bytes. Leak: 0x1005001f0 size=64 zone: DefaultMallocZone_0x100003000 __NSCFString ObjC CoreFoundation mutable non-inline: "-[MONType string]" Leak: 0x100500320 size=64 zone: DefaultMallocZone_0x100003000 __NSCFString ObjC CoreFoundation mutable non-inline: "-[MONType string]" Leak: 0x100500230 size=32 zone: DefaultMallocZone_0x100003000 has-length-byte: "-[MONType string]" Leak: 0x100500390 size=32 zone: DefaultMallocZone_0x100003000 has-length-byte: "-[MONType string]" 

note: the score may be different because we used random()

This means that since MONType does not appear in main() , the compiler binds the ARC properties to methods that were visible to the current TU (i.e., string from declarations in Foundation, all of which follow the convention). As a result, the compiler made a mistake, and we were able to inject leaks into our program.

Case 3

Using a similar approach, I was also able to introduce negative imbalances in the counts (premature releases or exchange zombies).

note: The code is not specified, because Example 2 already illustrates how to unbalance the reference counter.

Conclusion

You can avoid all these problems and improve readability and support by adhering to the agreement rather than using attributes.

Message return to non-ARC code: using attributes simplifies manual memory management for easier reading by programmers and for tools that can help you (for example, compiler, static analysis). If the program is complex enough so that the tools cannot detect such errors, then you should review your design, because it will be difficult for you or someone else to debug these problems.

+11
Oct 14 '11 at 16:52
source share

By adding @Justin to the answer, you can tell the compiler that -deepCopy returns the saved object by adding the NS_RETURNS_RETAINED attribute to this:

 - (id) deepCopy NS_RETURNED_RETAINED; 

Alternatively, you can use to explicitly control the "family" method using the objc_method_family attribute as follows:

 - (id) deepCopy __attribute__((objc_method_family(copy))); 

If you do this, the compiler finds out that this method is in the copy family and returns the copied value.

+6
Oct 14 '11 at 16:57
source share



All Articles