Use Your Loaf

[[brain engage] write]

Running Custom Clang Analyzer Builds

I don’t remember where I first saw this mentioned so I cannot give proper credit but this is an interesting tip to try if you have five minutes.

Open Source Builds of the Clang Analyzer

The Clang Static Analyzer has long been integrated with Xcode and provides powerful source code analysis to detect bugs in C, C++ and Objective-C code. The analyzer is fully open source and part of the larger Clang project. This means that you do not need to wait for Apple to release a new version of Xcode to get the latest updates to the analyzer.

You can find full details on downloading the latest build from the Clang Static Analyzer site. The download is a pre-built binary so installation only requires you expand the package and copy it to your preferred destination.

At time of writing the latest build is checker-276.tar.bz2 (built February 19, 2014). The release notes provide details on what is new and can make interesting reading. The downloaded file expands into a directory named checker-276. You can place this file any where you choose and since it is self-contained deleting the directory is sufficient to uninstall it. For the purposes of this post I am going to place the analyzer files in /usr/local/checker-276.

Using the build with Xcode

The Clang Analyzer site has full details on running the analyzer within Xcode. The build directory contains the set-xcode-analyzer utility that allows you to set which version of the analyzer Xcode should use. It needs to be run under sudo as it modifies the Xcode application bundle. Also make sure you have exited from Xcode before running set-xcode-analyzer.

Set Xcode to use the custom analyzer build

$ cd /usr/local/checker-276
$ sudo ./set-xcode-analyzer --use-checker-build=/usr/local/checker-276
(+) Using Clang bundled with checker build: /usr/local/checker-276
(+) Searching for xcspec file in:  /Applications/Xcode.app/Contents
(+) processing: /Applications/Xcode.app/Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Clang LLVM 1.0.xcplugin/Contents/Resources/Clang LLVM 1.0.xcspec

Reset Xcode back to using the default Clang Analyzer

$ cd /usr/local/checker-276
$ sudo ./set-xcode-analyzer --use-xcode-clang
(+) Using the Clang bundled with Xcode
(+) Searching for xcspec file in:  /Applications/Xcode.app/Contents
(+) processing: /Applications/Xcode.app/Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/Clang LLVM 1.0.xcplugin/Contents/Resources/Clang LLVM 1.0.xcspec

Why Do It?

The open source build is updated frequently with bug fixes and extra checks. This makes it more likely to spot an error in your code that would go undetected with the Xcode bundled version. On the other hand some of the extra checks may be still be very experimental leading to a lot of false warnings.

As an example I tried running the downloaded build against a sample project (Product > Analyze ⇧⌘B) and it produced the following warning:

warning: The 'viewWillAppear:' instance method in UIViewController subclass 'UYLViewController' is missing a [super viewWillAppear:] call

Missing the call to super from one of the view(Did/Will)(Appear/Disappear) methods is a common mistake that is not currently detected by the analyzer bundled with Xcode (I am running Xcode 5.1.1). So given that it only takes two minutes to download and configure Xcode and painless to switch back I would say it is definitely worth trying on your own code.

Data Detection for Links

If you need to determine if a text string contains a valid URL link resist the temptation to start constructing a regular expression and take a look at the NSDataDetector class from the Foundation framework.

NSDataDetector

The NSDataDetector Class Reference was introduced to the Foundation framework with iOS 4.0 so has been around for a while. It is defined in NSRegularExpression.h which also has this useful explanation:

NSDataDetector is a specialized subclass of NSRegularExpression. Instead of finding matches to regular expression patterns, it matches items identified by Data Detectors, such as dates, addresses, and URLs.

There is a single class method to initialise a new data detector named +dataDetectorWithTypes:error: where you must specify one or more data detector types to check. As time of writing (iOS 7.1) the possible options are:

  • NSTextCheckingTypeDate
  • NSTextCheckingTypeAddress
  • NSTextCheckingTypeLink
  • NSTextCheckingTypePhoneNumber
  • NSTextCheckingTypeTransitInformation

You can OR these values if you want to check for more than one type. However in this case if I just want to check for links I would initialise a data detector as follows:

NSError *error = nil;
NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink 
                                                           error:&error];

Once the data detector is created there are several useful NSRegularExpression methods to check an NSString depending on the desired result:

Counting matches

To simply determine if an NSString contains one or more links use numberOfMatchesInString:options:range:

NSString *stringValue = @"A link: http://useyourloaf.com";
NSUInteger matchCount = [detector numberOfMatchesInString:stringValue
                                                  options:0 
                                                    range:NSMakeRange(0, stringValue.length)];

Finding first match

To find the first link in the string and extract the URL value use firstMatchInString:options:range:. This method returns an NSTextCheckingResult object which if successful will contain an NSURL object for the detected link:

NSString *stringValue =  @"Two links: useyourloaf.com and apple.com"; 
NSURL *url = nil;
NSTextCheckingResult *result = [detector firstMatchInString:stringValue
                                                    options:0
                                                      range:NSMakeRange(0, stringValue.length)];
if (result.resultType == NSTextCheckingTypeLink)
{
    url = result.URL;
}
NSLog(@"matched: %@", url);
// matched: http://useyourloaf.com

Note that the resultType value returned in the NSTextCheckingResult indicates the matching data detector type. In this case the data detector is only matching links so it should always contain NSTextCheckingTypeLink and will have an NSURL object. If you are checking for multiple types you should check which one actually matched as the additional information will vary (for example for NSTextCheckingTypePhoneNumber use the phoneNumber property).

Finding all matches

If you want to find all links in the string you can use matchesInString:options:range: and get back an NSArray of NSTextCheckingResult objects (one for each link matched). Alternatively you can use a block method to enumerate all matches:

NSString *stringValue = @"Two links: useyourloaf.com and apple.com";    
[detector enumerateMatchesInString:stringValue
                           options:0
                             range:NSMakeRange(0, stringValue.length)
                        usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop)
{
    if (result.resultType == NSTextCheckingTypeLink)
    {
        NSLog(@"matched: %@",result.URL);
    }         
}];
// matched: http://useyourloaf.com
// matched: http://apple.com

Format String Issue Using NSInteger

The version of Clang supplied with Xcode has over time added a number of additional warnings for potentially unsafe code. This is generally a good thing but I was a little alarmed recently when I opened a project and found the unit test classes had dozens of warnings:

Format String Issue Values of type ‘NSInteger’ should not be used as format arguments; add an explicit cast to ‘long’ instead

The warnings were all for printf format specifiers used in many of the unit test assert statements. A typical example where both expectedCount and count were of type NSInteger:

XCTAssertTrue(expectedCount==count, @"Expected %d got %d", expectedCount, count);

Integer Sizes

The release by Apple of iOS 7 and the A7 processor also introduced a 64-bit runtime to iOS for the first time. The 64-Bit Transition Guide for Cocoa Touch provides guidelines on how to convert your app to a 64-bit binary. Relevant to my problem it also summarises the changes to the sizes of a number of the built-in data types:

  • A long integer changes from 4 to 8 bytes.
  • The size of a pointer increases from 4 to 8 bytes.
  • NSInteger and NSUInteger Cocoa types increase from 4 to 8 bytes.
  • CGFloat increases from a 4 byte float to an 8 byte double.

The cause of the warning in this case comes from the use of a %d in the format specifier which expects a signed 32-bit integer. However the value supplied was an NSInteger which will be a 64-bit integer with the 64-bit runtime. The fix in this case is to explicitly specify a long integer in the format string (%ld) and cast the NSInteger to a long:

XCTAssertTrue(expectedCount==count, @"Expected %ld got %ld", (long)expectedCount, (long)count);

In the case of an NSUInteger you would use %lu and (unsigned long).

Alternatives to Casting (Updated 14 April 2014)

A couple of interesting alternatives for your consideration if you don’t like casting to a long.

Length Modifiers

This Stack Overflow answer was brought to my attention by Johannes Stühler on Twitter. It makes use of the z and t length modifiers described as follows in the String Programming Guide:

z – Length modifier specifying that a following d, o, u, x, or X conversion specifier applies to a size_t or the corresponding signed integer type argument.

t – Length modifier specifying that a following d, o, u, x, or X conversion specifier applies to a ptrdiff_t or the corresponding unsigned integer type argument.

This allows us to write the following and have it be correct for both 32-bit and 64-bit runtimes:

XCTAssertTrue(expectedCount==count, @"Expected %zd got %zd", expectedCount, count);

The equivalent for an NSUInteger would use %tu.

NSNumber Literals

Another alternative, which I think I saw first on the Big Nerd Ranch blog is to use the Objective-C literal syntax to convert to an NSNumber object:

XCTAssertTrue(expectedCount==count, @"Expected %@ got %@", @(expectedCount), @(count));

Fix All In Scope

It may also we be worth remembering that if you have a number of such warnings you can get Xcode to fix them all in one go using the “Fix All In Scope” option from the “Editor” menu (keyboard shortcut is ⌃⌥⌘F).