Apple Developer Connection
Advanced Search
Member Login Log In | Not a Member? Contact ADC

Improving Your Software with Xcode and Static Code Analysis Techniques

Static analysis refers to a method of examining software that allows developers to discover dangerous programming practices, poor use of language features, or potential errors in source code, without actually running the code. If you use Xcode with C or Objective-C, you can gain real quality improvements by understanding the basics of static analysis techniques and by integrating these techniques into your development process. You can catch potential errors “upstream” in the development cycle (during the compilation stage) as opposed to later, or “downstream,” when they are more costly to fix. The result is cleaner code and more stable and maintainable projects.

Several methods are available for adding static analysis to your development cycle, including formal code reviews, software metrics, and mathematical analysis (Formal Methods). A simple and powerful verification technique is to use the GNU Compiler Collection’s (GCC’s) C compiler’s language dialect and warning options to catch as many semantic errors as possible during the compilation phase. And as you’ll see, Xcode makes static analysis particularly powerful through the use of a graphic interface for build configurations.

In this article, we show you how to use GCC to help verify C and Objective-C programs. This article covers the static analysis features of GCC and shows how to add them to your daily development process under Xcode (we are using Xcode 2.3 and GCC 4.0.1).

Introducing Static Analysis

GCC performs two general classes of checks during compilation: syntax and semantic. Syntax checking refers to the rules governing which statements the compiler accepts as valid for the target programming language. In contrast, semantic checking refers to meaning and the relationship between the statements in a language. Listing 1 shows an example of syntax and semantic errors in C.

Listing 1: Syntax and semantic errors in C

int main(void) {
  int a = 14;
  int b = 99 // The missing ; generates a syntax error.
  printf("%f\n", a + b); // The type mismatch is a semantic error.
  return 0;
}

When GCC performs a default build under Xcode, it enables only the most basic semantic checks. For stricter semantic checking, you pass more options to GCC.

When you add static checks to your development process, you are effectively placing another set of eyes on your code or putting a net under the coding process. Static analysis tools look for dangerous programming practices, poor use of language features, or potential errors that you may have missed-in other words, problems that are just waiting to appear at the worst possible time.

A Bit of History

The designers of early UNIX-based C compilers decided to remove extensive semantic checks from the compiler. As Peter van der Linden points out (in his book Expert C Programming), compiler writers removed full-blown semantic analysis from C compilers to improve compilation speed and simplify compiler design and implementation. (See “For More Information.”) When compiler writers removed static analysis from the compiler, they placed it in another program called lint. The problem was that programmers didn't use lint and instead relied on the compiler.

Realizing the error, compiler writers and vendors began to place more static checking in the compiler. Newer versions of GCC provide decent levels of static checking. If you want more complete static checking, you must augment GCC with a lint tool such as Splint. (Splint is not an Apple/Xcode tool. Splint is a third-party lint tool that provides much better static checking than your compiler or traditional lint. It is freely available from the splint.org website.

Here’s an example of what static checking can do for you. The code in Listing 2 doesn’t generate a warning message when compiled using the default build settings for a standard tool C program) or Cocoa application (Objective-C) project.

Listing 2: Code with no built-in warning message

#include <stdio.h>
int addTen(int n);

int main(void) { 
  float x = addTen(11.95f);
  printf("%f\n", x);
  return 0;
}
int addTen(int n) {
  return n + 10;
}

As you can see, compiling with just default settings can give you a false sense of security.

The first step in adding static analysis to your development process is to understand which static checking options GCC supports and how to apply them to common development scenarios. Because Objective-C is a superset of C, start by looking at options for C programs. When you understand these options, you can move on to some Objective-C examples.

Using GCC and Xcode for Static Analysis

The GCC compiler supports two types of static analysis options: language dialect options and warnings options. Language dialect options are not technically static or semantic checks. Instead, they test your source code for compliance with a particular language dialect. (Because most Mac OS X Objective-C programs are Cocoa applications, this article does not cover the Objective-C dialect options. For more information, see section 3.6, Options Controlling Objective-C and Objective-C++ Dialects, of the GCC manual.

Warning options enable the compiler to detect potential programming problems that may cause your program to fail unexpectedly. Together, language dialect and warning options let you write code to a particular language dialect and ensure that it does not contain potential errors.

Setting the Language Dialect and Warning Flags in Xcode

You enable static checking by passing specific compiler options to the GCC build system. In UNIX-style development, you set compiler flags and manage builds with makefiles. Xcode simplifies build management by providing a graphical user interface (GUI) for enabling and disabling compiler options. When you combine this interface with custom build settings, you can perform all kinds of static checks with little effort. (Custom build configurations are covered later in the article.)

To access the build settings for your application, create or open a standard tools or Cocoa application project in Xcode. Next, click the Targets disclosure triangle (under the Groups & Files list), Control-click a target, and then click Get Info (see Figure 1).

Get information about your target

Figure 1. Get information about your target

To pass an option to the compiler, select the checkbox to the right of the options text (see Figure 2). To disable an option, deselect its checkbox. If the GUI does not display an option, you can pass it to the build system using the Other C Flags or Other Warning Flags item.

Enable compiler options

Figure 2. Enable compiler options

A good practice is to enable the warning options you will use for the project before you write the first line of code. Your goal for all projects (new and legacy) is to produce clean builds in which the compiler does not report any warnings. Doing so can be challenging for legacy code, but it is well worth the effort.

Using the Language Dialect Settings

Start by looking at the language dialect options that GCC supports. (See section 3.4, “Options Controlling C Dialect,” of the GCC manual for more information.)

Imagine that you are working on a C program under Xcode. During development, you learn that the program must be portable to systems that support only pre-C99 language compliance, both GNU and non-GNU compilers. As a result, your code cannot contain C99-specific language features.

Because the default standard tool build uses the GNU89 language dialect, you are confident that your code will work well under these compilers. Unfortunately, this is not the case. For example, imagine your code base contains variable-length automatic arrays, as shown in Listing 3.

Listing 3: Variable-length arrays

void processStr(const char *p) {
  char str[strlen(p) + 1];
  (void)strcpy(str, p);
  /*...*/
}

The question is, why does your code compile under GNU89 with a C99-specific feature? The answer is that GNU89 uses the C89 language dialect and enables GNU C language extensions, one of which is the variable-length automatic array.

To ensure that your code is pre-C99 ISO compliant, you must compile it under C89 with GNU extensions turned off. To do so, set the C language dialect to C89 [-std=c89], select the Pedantic Warnings checkbox, and add -ansi to the Other Warning Flags list (see Figures 3a and 3b). (The Pedantic and -ansi options are covered later in this article.)

Set the C language dialect to C89 and add –ansi to the Other Warning Flags list

Figure 3a. Set the C language dialect to C89 and add –ansi to the Other Warning Flags list

Set the C language dialect to C89 and add –ansi to the Other Warning Flags list

Figure 3b. Set the C language dialect to C89 and add –ansi to the Other Warning Flags list

Remember, GCC does not guarantee complete compliance with a particular language dialect. (See the GCC manual for more information.)

Using the Warning-Level Flags

To really get the benefits of static checking in your C and Objective-C programs, your first line of defense is GCC’s warning flags. By passing specific warning flags to the GCC build system, you can control the level and type of static checking that GCC performs. This checking can be anything from basic checking to stricter verification.

Before digging into the specific flags and how to use them, let’s return to Listing 2. This example showed that many coding problems would slip past the compiler unless you used the proper static checking options. When you add the right options, GCC warns you of potential problems (see Figures 4a and 4b).

Listing 2 code with GCC options unset and set

Figure 4a. Listing 2 code with GCC options unset and set

Listing 2 code with GCC options unset and set

Figure 4b. Listing 2 code with GCC options unset and set

Warning Stages

A good way to visualize the static checks that GCC provides is to organize them into stages (see Table 1). As you progress through the stages, you get more checking and more assurance that your code does not have potential problems. For some projects, such as safety-critical applications, you should progress through all stages. (You should also add Splint to your verification process.) For other projects, you may choose only the first few stages.

Note: Because Objective-C is a superset of C, you can use the language-independent C warning options to verify your Objective-C code. For clarity, start with the C options; you can look at the Objective-C options later.

Stage Compiler Options Description
1 -std= Verify the language dialect to which you are writing.
2 -ansi -pedantic Enforce extra language-level checks and compliance.
3 -Wall Report the most common programming errors.
4 -Wall, -Wmost or -Wextra Report the most common programming errors and less-serious but potential problems.
5 -Wno-div-by-zero
-Wsystem-headers
-Wfloat-equal
-Wtraditional (C only)
-Wdeclaration-after-statement (C only)
-Wundef
-Wno-endif-labels
-Wshadow
-Wlarger-than-len
-Wpointer-arith
-Wbad-function-cast (C only)
-Wcast-qual
-Wcast-align
-Wwrite-strings
-Wconversion
-Wsign-compare
-Waggregate-return
-Wstrict-prototypes (C only)
-Wold-style-definition (C only)
-Wmissing-prototypes (C only)
-Wmissing-declarations (C only)
-Wmissing-field-initializers
-Wmissing-noreturn
-Wmissing-format-attribute
-Wno-multichar
-Wno-deprecated-declarations
-Wpacked
-Wpadded
-Wredundant-decls
-Wnested-externs (C only)
-Wunreachable-code
-Winline
-Wno-invalid-offsetof (C++ only)
-Winvalid-pch
-Wlong-long
-Wvariadic-macros
-Wdisabled-optimization
-Wno-pointer-sign
Provide additional checks not covered by -Wall  -Wextra or -Wmost

Table 1: Stages of static checks

Stage 1: Language Dialect Options

Stage 1 options check your code for adherence to the particular language dialect. Too often, we write code and compile it with the default language dialect. In some cases, this is fine. But it’s always better to tell the compiler up front the language dialect you are targeting.

Note: For C applications, always set the dialect when you create the project. For Objective-C (Cocoa-based) applications, the default dialect is fine.

The benefit of this stage is that the compiler catches anything outside the targeted language dialect. Remember, GCC does not guarantee complete compliance with C language standards. But it will give you a good start. (See the GCC manual for more information on the language dialects and the exceptions for the different standards.)

Stage 2: Language Dialect Options Plus Compliance Checks

Stage 2 provides stricter language dialect checking. By using the -ansi and -pedantic options (you enable the Pedantic warning through the GUI), GCC enforces more complete language dialect compliance.

As you know, some of the GCC language dialects enable GNU language extensions (GNU89 and GNU99). This is acceptable as long as you do not need strict ISO C language compliance. If you do, you can use the -ansi and -pedantic options in combination with the dialect setting.

The -ansi option disables all GNU extensions that conflict with the selected language dialect. As a result, you can write programs to a particular ISO C dialect without unwanted effects from GNU extensions. The -pedantic option extends the coverage. You use the -pedantic option together with -ansi to reject all GNU extensions—not just extensions that are incompatible with the language dialect.

By joining the dialect option with -ansi and -pedantic, you can write programs that conform (with some exceptions) to the language dialect of your choice. The benefit of this stage is much stricter checking of language conformance.

Stages 3 and 4: Report Common and Less-Common Programming Errors

The two most commonly used warning options are -Wall and -Wextra/-Wmost. These options are mnemonics that aggregate a set of options under a single option. Xcode does not enable these options by default, however. You must enter them yourself under Other Warning Flags.

The -Wall option enables warning options for the most common programming errors. It reports constructs that are always wrong and therefore need your attention. If a build produces a warning message with this flag on, consider it a serious problem and fix it right away. The conventional wisdom is to enable this option for all builds.

The -Wextra option detects less serious problems. (This option was called -W in earlier versions of GCC. GCC still supports this syntax, but you should consider it deprecated and use -Wextra, instead.). This option detects many classes of constructs that are technically valid but may cause problems if left unchecked.

For example, compiling the code in Listing 4 with just the -Wall option does not produce a warning. However, if you add -Wextra, the problems are detected.

Listing 4: Code compiled with the -Wall option

void checkVal(unsigned int n) {
  if (n < 0) {
    /* Do something... */
  }
  else if (n >= 0) {
    /* Do something else... */
  }
}

Both -Wextra and -Wall are standard GCC options that you can use across compiler implementations. The -Wmost option is an Apple-specific flag equivalent to the -Wall option but disabling -Wparentheses (-Wno-parentheses).

Many developers have mixed feelings about enabling -Wextra for all builds because it detects some false positives. That said, a good recommendation is always to use it, or at least to enable it before you check in your code. It is better to know where you stand upstream in the development process than to let even one error slip past that can hurt you later.

Stage 5: Options That Provide More Rigorous Checking

Turning on -Wall or -Wextra/-Wmost gives you moderate checking for your applications. However, GCC supports other options that increase your level of confidence in your code.

Listing 5 shows an Objective-C application that does not generate any warning messages under Stage 4.

Listing 5: Objective-C application with Stage-4 options enabled

// main.m
#import <Cocoa/Cocoa.h>
#import "ClassA.h"

int main(void) {
  ClassA *obj = [[ClassA alloc] init];
	
  double x = 10.0;
  double y = 11.0;
  double z = 0.0;
	
  z = [obj process:x :y];
  (void)printf("%lf\n", z);
  
  [obj release];	
  return 0;
}

// ClassA.h
#import <Cocoa/Cocoa.h>

@interface ClassA : NSObject {
}
-(double) process:(double)x: (double)y;
@end

// ClassA.m
#import "ClassA.h"

@implementation ClassA
- (id) init {
  self = [super init];
  return self;
}
- (void) dealloc {
  [super dealloc];
}
-(double) process:(double)x: (double)y {
  double z = 0.0;
  if (x == y) {
    z = x * y;
    return z;
  }
  else {
    z = x * y;
    return z;
  }
  z = x - y;
  return z;
}
@end

This is where adding some extra static checking can really help. To add more checking, add -Wfloat-equal and -Wunreachable-code to the Other Warning Flags list. The checks you enabled in Stage 5 provide the most complete static checking that GCC offers (see Figure 5).

Stage-5 static analysis results on Listing 5

Figure 5. Stage-5 static analysis results on Listing 5

More Options

In addition to language dialect and warning options, GCC offers other options that give you control over how warning and error information is displayed. Table 2 lists these options.

Compiler Options Description
-fsyntax-only Check for syntax errors only and do not compile or build code.
-pedantic-errors Report all pedantic warnings (-pedantic) as errors.
-w Disable all warning messages.
-Wfatal-errors Abort the compilation on the first error.
-Wsystem-headers Report warning messages found in system header files.
-Werror Report all warnings as errors.

Table 2: Display options

Objective-C Options

You can use the C language-independent options for checking your Objective-C code. However, GCC also supports several warning options specific to the Objective-C language.

In object-oriented programming, it is common to define an interface for behavior that decedents will implement. The interface does not contain an implementation, just a placeholder that others fill in with an implementation specific to their needs.

To get this functionally in Java code, you use interfaces; in C++, abstract classes. Objective-C provides it through protocols. There are two types of protocols: formal and informal. When a class conforms to another class’s formal protocol, that class must implement all methods in the protocol. An informal protocol, on the other hand, defines a set of methods that a class should implement but are not required.

Listing 6 shows an example of a protocol and a class that implements the protocol.

Listing 6: A protocol and its implementation

// MyProto.h
@protocol MyProto
  -(void) doThis;
  -(void) doThat;
@end

// ClassA.h
#import <Cocoa/Cocoa.h>
#import "MyProto.h"

@interface ClassA: NSObject  {
}
-(void) doThis;
-(void) doThat;

@end

// ClassA.m
#import "ClassA.h"

@implementation ClassA

-(void) doThis {
  (void)printf("ClassA:doThis\n");
}
-(void) doThat {
  (void)printf("ClassA:doThat\n");
}
@end 

MyProto is a formal protocol that defines a list of methods. Any class that wants to conform to MyProto must implement these methods, as ClassA does. GCC will not report any warnings when compiling this code under the default Cocoa application build.

Now, imagine that you extend the implementation as shown in Listing 7 and Figure 6.

Listing 7: Code to extend the implementation of ClassB

// ClassB.h
#import <Cocoa/Cocoa.h>
#import "ClassA.h"

@interface ClassB : ClassA  {
}
@end

// ClassB.m
#import "ClassB.h"

@implementation ClassB
@end

The MyProto protocol and its implementation

Figure 6. The MyProto protocol and its implementation

Building this code produces four related compiler warnings (see Figure 7).

Compiler warnings on the ClassB build

Figure 7. Compiler warnings on the ClassB build

GCC reports these warning because the default build enables the Incomplete Objective-C Protocols Warn option. By setting this option, you are telling GCC to report a warning if methods that a protocol requires are not implemented in the class adopting it.

The problem is that ClassB inherits the implementation of the protocol methods from its superclass (ClassA) instead of implementing them itself. One way to address this behavior is to disable the option. Unfortunately, the compiler will not warn you about legitimate protocol problems in your code. (See section 3.6, “Options Controlling Objective-C and Objective-C++ Dialects,” of the GCC manual for more information.)

Using Static Analysis on a Project

Now that you understand the basics of using GCC and Xcode for statically verifying your C and Objective-C code, it’s time to put your knowledge into action. You need a simple way to build a code base with Stage 1–5 checks. Happily, Xcode supports such a configuration with Targets and Build Configuration.

A target is a high-level container, or blueprint, that aggregates files, source and header files, libraries, build settings, compiler flags, and rules that state how files are processed. A build configuration is a way to collect specific build settings under a name and apply them to a target. Through build configurations, you can customize your builds for a single code base without having to create separate targets for each product. You can have one target for your project and different build configurations, each customized to a different build level.

To create a custom build configuration, click the Targets disclosure triangle (under the Groups & Files list), Control-click a target, and then click Get Info. In the Target Info dialog, click the Build tab, and then choose Edit Configurations from the Configurations menu (Figures 8a and 8b).

Create a custom build configuration

Figure 8a. Create a custom build configuration

Create a custom build configuration

Figure 8b. Create a custom build configuration

To create a new build setting, select an existing setting (Debug, for example), and then click Duplicate. Rename the setting to your custom build configuration. Then, choose the new build configuration from the Target Info window’s Configuration menu and customize it to your needs (see Figures 9a and 9b).

Create a new build setting

Figure 9a. Create a new build setting

Create a new build setting

Figure 9b. Create a new build setting

To use your new build setting, open the Build Results window (choose Window > Tools > Build Results), choose the configuration you want to apply from the Active Build Configuration menu, and then build the project (see Figure 10). (If the Active Build Configuration menu does not appear in the toolbar, you must add it.)

Apply your new build setting

Figure 10. Apply your new build setting

Xcode uses these settings to build your project.

Conclusion

This article has only touched the surface of what you can do with static analysis techniques. Hopefully, it has gotten you excited about using GCC’s static analysis options and integrating static analysis techniques into your development cycle. By combining GCC’s language dialect and warning options with Xcode’s intuitive interface, you can drastically improve the quality of your code with little effort. If you want even more extensive checking of your C applications, try Splint.

For More Information

Posted: 2006-07-10