Circular Containers in Objective-C

Published on

Some time ago I accidentally wrote this code:

NSMutableArray *environments = [NSMutableArray new];
for (NSString *key in [dictionary allKeys]) {
    XCCEnvironment *environment = [[XCCEnvironment alloc] initWithName:key
                                                            parameters:dictionary[key]];
    [environments addObject:environments];
}
return environments;

Did you notice the problem here? Well, I didn’t.

Problem

When I run the program I got a crash:

-[__NSArrayM someSelector]: unrecognized selector sent to instance 0x100211d80

Consumer of environments expected to get XCCEnvironment, but got NSMutableArray.

At the beginning it wasn’t clear why it actually happened, but I took a closer look at the code and found that I put array into itself:

// ...
NSMutableArray *environments = [NSMutableArray new];
// ...
[environments addObject:environments];
// ...

Documentation says nothing about collection’s behaviour in such situation, the only valuable (imo) reading I’ve found is Mike Ash’s blog-post Let’s break Cocoa.

The post says that mutable arrays, dictionaries and sets are going really crazy if you make so-called circular containers. Another problem is that they cause a memory leak when ARC is enabled: collection retains itself.

Solution

I believe that normally developers do not put collection inside the collection. Though, it is the same kind of belief as ‘programmers do not dereference null pointers’ - it is still happens and probably it’s kinda unexpected behaviour.

I was pretty sure that clang is able to prevent me and other people from doing this mistake, but I didn’t find any warning/flag/setting that does this check.

Eventually I decided to implement it. Implementation took a couple of evenings but now it’s in trunk.

Actual patch checks the following mutable collections:

  • NSMutableArray
  • NSMutableDictionary
  • NSMutableSet
  • NSMutableOrderedSet
  • NSCountedSet

And shows warning if you trying to put collection inside itself

The warning could be enabled/disabled with -wobjc-circular-container/-wno-objc-circular-container respectively, though it’s enabled ‘by default’.

Conclusion

Recent clang version contains this feature, but it’s not yet available within Xcode, and I guess it’ll appear with the next major release - in a year or so.

But, anyway, having open-source tools is really amazing: you can tweak it, extend it and make your life and, probably, lives of other people a bit better.

Happy hacking!

UPD

This feature got into WWDC 2016, What’s new in LLVM


Drop me a line or ping me on twitter or Mastodon if you have questions!