Removing Cocoa Dead Code Using Code Coverage
July 28, 2008

Motivation

It is not uncommon to have dead code in programs, that is uncalled methods, or code that is never executed.

Dead code makes the code cumbersome and harder to read. With dead code, it is also harder to figure out each class responsabilities. Executables get bigger and need more memory.

So, dead code must be removed. The question is how.

Note 1: we do not consider here dead code stripping by the linker. It only works with C / C++, and although it does reduce the executable size by removing unused symbols and functions, it does not improve code clarity, which is the primary goal here.

Note 2: static analysis can reveal some unreacheable code by adding -Wunreachable-code to GCC warning flags. We're goind to see techniques that also detect reacheable code that is never called. However, this flag is always useful to add in your projects.

Objective-C / Cocoa needs code coverage

Eclipse makes is easy to detect uncalled Java private methods by issuing a warning.

In Objective-C however, it is impossible to know which methods will be called, due to the dynamic runtime, Cocoa key-value coding or bundles loading.

So, dead code must be hunt at runtime. The program must be run in order to go through most if not all methods. This process is known as code coverage.

When we know the methods that are called, we know the ones that are not. It is important to note that an uncalled method might be called in another run scenario. It remains up to the programmer to know what to remove.

I can think of three techniques do code coverage XCode and Objective-C / Cocoa: breakpoints, dtrace and gcov.

Claudio also told me about a Cedric's technique: the GCC -finstrument-functions compilation flag. See below.

I will be glad if you can comment on this or share your own techniques.

Breakpoints

It is a rudimentary but efficient technique.

  • put breakpoints on each method
  • run the program
  • remove each breakpoint you stop on
  • remaining breakpoint signal uncalled methods

Obviously, this technique can be a hassle if you have more than a few methods.

DTrace

The idea here is to compare the list of your class methods with the list of those methods which are called at runtime.

You can do this with two DTrace scripts. DTrace is a powerful tool to dynamicaly examine the behaviour of user programs or the OS itself. Its scripting language is called D. Since DTrace is dynamic, you do not need to recompile your application. You do not even need to have the source code :-)

The first script shows all the methods of a class. Replace 18678 by the pid of your process, and MyClass by the name of your class.

$ sudo dtrace -l -n "objc18678:MyClass::entry"
   ID   PROVIDER            MODULE                          FUNCTION NAME
18580  objc18678           MyClass                   -myCalledMethod entry
18581  objc18678           MyClass                 -myUncalledMethod entry
18582  objc18678           MyClass                     -awakeFromNib entry

The second script objc_msg_calls.d is a bit longer. It counts each method call and displays the result before termination. Here again, you must give the script your class name.

$ sudo dtrace -s objc_msg_calls.d -c /Untitled.app/Contents/MacOS/Untitled 
  MyClass       awakeFromNib                1
  MyClass       myCalledMethod              1

The difference between the two lists reveals the uncalled methods.

Gcov

Gcov is the GCC code coverage tool. It works this way:

  • compile you code with special options, it produces .gcno files beside your .o files
  • run your executable, it produces .gcda files beside your .o files
  • run gcov tool on the .gcda files to produce .gcov files

To enable Gcov on your project, follow these four simple steps (from Apple QA1514):

  • duplicate your build configuration to say "Coverage"
  • check "Generate Test Coverage Files"
  • check "Instrument Program Flow"
  • add "-lgcov" to "Other Linker Flags"

After you've compiled and run your program, you still need to generate gcov files. This can be done by running the coverage.sh script that you will add to your project. Adapt the OBJ_DIR path to your settings.

$ ./coverage.sh

The .gcov files are then generated in the coverage/ directory. Each line of the source code is prefixed by the number of passages. Dead code is marked with #####.

    1:   13:- (void)myCalledMethod {
    1:   14:    NSLog(@"-- myCalledMethod");
    -:   15:}
    -:   16:
#####:   17:- (void)myUncalledMethod {
#####:   18:    NSLog(@"-- myUncalledMethod");
    -:   19:}
    -:   20:
    1:   21:- (void)awakeFromNib {
    1:   22:    NSLog(@"-- awakeFromNib");
    1:   23:    [self myCalledMethod];

GCC flag

According to GCC man page, you can have these two functions called after entering and before exiting any other function by adding the compilation flag -finstrument-functions:

void __cyg_profile_func_enter (void *this_fn, void *call_site);
void __cyg_profile_func_exit  (void *this_fn, void *call_site);

The parameters are the addresses of the callee and the caller. They can be converted into symbols by calling atos with open from within the function, as in profile_functions.c.

So, add the compilation flag and this profile_functions.c to your project, and the output will look like this:

main (in Untitled) (main.m:12)
-[MyClass awakeFromNib] (in Untitled) (MyClass.m:21)
-[MyClass myCalledMethod] (in Untitled) (MyClass.m:13)

Conclusion

Although automatic dead-code stripping doesn't make sense for Objective-C / Cocoa, dead code still reduces code readability and overall quality.

We showed four quick and easy techniques to detect uncalled methods at runtime. The programmer then has to read his code and decide if those methods should be removed or not.

Each technique has its pros and cons, which we could synthesize as follows:

  • breakpoints: very simple, but doesn't scale
  • dtrace: does not require the source code, but applications must be run as root
  • gcov: needs relink, but reveals unreached code inside called methods
  • gcc flag: needs recompilation and calls to atos, but quite simple and customizable