views:

388

answers:

8

My C project uses preprocessor directives to activate / deactive some features. It's not unusual to find some of the less common configurations do not compile anymore due to a change made a few days ago within an #ifdef.

We use a script to compile the most common configurations, but I'm looking for a tool to ensure everything is compiled (testing is not a problem in our case, we just want to detect ASAP nothing stops compiling). Usually ifdefs / ifndefs are independent, so normally each module have to be compiled just twice (all symbols defined, all undefined). But sometimes the ifdefs are nested, so those modules have to be compiled more times.

Do you know of any tool to search all ifdef / ifndef (also nested ones) and gives how many times a module have to be compiled (with the set of preprocessor symbols to be defined in each one) to ensure every single source line of code is analyzed by the compiler?

A: 

May be You need grep?

vitaly.v.ch
`grep` is not going to be able to analyse which combinations of option are required to achieve full coverage without having to try all combinations.
Tom Duckering
+4  A: 

I am not aware of any tool for doing what you want to do. But looking at your problem i think all you need is a script that will compile the source with all possible combinations of the preprocessor symbols.

Like say if you have..

#ifdef A 
    CallFnA();
#ifdef B
    CallFnB();
#endif
    CallFnC();
#endif

You will have to trigger the build with the foll combinations

  1. A and B both defined
  2. A not defined and B defined ( will not make sense here, but required for entire module)
  3. A defined and B not defined
  4. A and B both not defined

Would love to see some script that will grep the source code and produce the combinations. Something like

find ./ -name '*.cpp' -exec egrep -h '^#ifdef' {} \; | awk '{ print $2}' | sort | uniq

With *.cpp replaced with whatever files you want to search.

Gautam Borad
This is exactly the correct way to do this, and I've implemented it in my answer.
James Thompson
I agree. The OP may have some interesting challenges though - the number of possible combinations can grow very quickly, and not all are necessarily valid. It is going to involve a lot of compilation time!
gavinb
Thanks for the script, I just used it to discover all the symbol names used in the code, and worked great. However, it's not practical to compile all combinations because as you say they grow very quickly. And it's neither required except for nested ifdefs, and your shell script cannot filter nested ones.
Santiago
A: 

Hm, I initially thought that unifdef might be helpful, but looking further as to what you're asking for, no, it wouldn't be of any immediate help.

Vatine
A: 

You can use Hudson with a matrix project. (Note that Hudson is not just a Java testing tool, but a very flexible build server which can build just about anything, including C/C++ projects). If you set up a matrix project, you get the option to create one or more axes. For each axis you can specify one ore more values. Hudson will then run your build using all possible combinations of the variable values. For example, if you specify

os=windows,linux,osx
wordsize=32,64

Hudson will build six combinations; 32- and 64-bit versions for each windows, linux, and osx. When building a "free-style project" (i.e. launching an external build script), the configurations are specified using environment variables. in the example above, "os" and "windows" will be specified as environment variables.

Hudson also has support for filtering combinations in order to avoid building certain invalid combinations (for example, windows 64-bit can be removed, but all others kept).

(Edited post to provide more details about matrix projects.)

JesperE
I took a look at Hudson and matrix project and don't see any relevance from a java testing framework to a C #ifdef problem. Please explain why this is relevant to the #ifdef's.
Philip Schlump
Sorry, I wasn't very clear. I'll clarify in the question.
JesperE
Thanks for the tip, but my problem is not the tool used for compiling all configurations (our simple script is good enough), but discovering which combinations of preprocessor symbols I have to build to ensure every single line of code is compiled.
Santiago
A: 

Another solution is to compile in all the features and have run-time configuration testing for the features. This is a cool "trick" since it allows Marketing to sell different configurations and saves Engineering time by simply setting values in a configuration file.

Otherwise, I suggest a scripting language for building all the configurations.

Thomas Matthews
+2  A: 

Here's a Perl script that does a hacky job of parsing #ifdef entries and assembles a list of the symbols used in a particular file. It then prints out the Cartesian Product of all the possible combinations of having that symbol on or off. This works for a C++ project of mine, and might require minor tweaking for your setup.

#!/usr/bin/perl

use strict;
use warnings;

use File::Find;

my $path = $ENV{PWD};

my $symbol_map = {};
find( make_ifdef_processor( $symbol_map ), $path );

foreach my $fn ( keys %$symbol_map ) {
   my @symbols = @{ $symbol_map->{$fn} };

   my @options;
   foreach my $symbol (@symbols) {
      push @options, [
         "-D$symbol=0",
         "-D$symbol=1"
      ];
   }

   my @combinations = @{ cartesian( @options ) };
   foreach my $combination (@combinations) {
      print "compile $fn with these symbols defined:\n";
      print "\t", join ' ', ( @$combination );
      print "\n";
   }
}

sub make_ifdef_processor {
   my $map_symbols = shift;

   return sub {
      my $fn = $_;

      if ( $fn =~ /svn-base/ ) {
         return;
      }

      open FILE, "<$fn" or die "Error opening file $fn ($!)";
      while ( my $line = <FILE> ) {
         if ( $line =~ /^\/\// ) { # skip C-style comments
            next;
         }

         if ( $line =~ /#ifdef\s+(.*)$/ ) {
            print "matched line $line\n";
            my $symbol = $1;
            push @{ $map_symbols->{$fn} }, $symbol;
         }
      }
   }
}

sub cartesian {
   my $first_set = shift @_;
   my @product = map { [ $_ ] } @$first_set;

   foreach my $set (@_) {
      my @new_product;
      foreach my $s (@$set) {
         foreach my $list (@product) {
            push @new_product, [ @$list, $s ];
         }
      }

      @product = @new_product;
   }

   return \@product;
}

This will definitely fail with C-style /* */ comments, as I didn't bother to parse those effectively. The other thing to think about is that it might not make sense for all of the symbol combinations to be tested, and you might build that into the script or your testing server. For example, you might have mutually exclusive symbols for specifying a platform:

-DMAC
-DLINUX
-DWINDOWS

Testing the combinations of having these on and off doesn't really make sense. One quick solution is just to compile all combinations, and be comfortable that some will fail. Your test for correctness can then be that the compilation always fails and succeeds with the same combinations.

The other thing to remember is not all combinations are valid because many of them aren't nested. I think that compilation is relatively cheap, but the number of combinations can grow very quickly if you're not careful. You could make the script parse out which symbols are in the same control structure (nested #ifdefs for example), but that's much harder to implement and I've not done that here.

James Thompson
Very nice tool!
gavinb
Thanks! You make an excellent point about the number of combinations in another comment, and I added a short mention of that idea to the answer.
James Thompson
I was looking for an script like this, thanks! However, as noted in another comment, we're interested mainly in all the combinations of nested ifdefs. Our project defines more than 50 symbol names (and probably in the future some more will be added, while other will be removed), so the brute force approach of compiling all combinations of ifdefs is not possible in our case. I'll tweak the script to find all independent ifdefs, and then to generate all combination for just nested ifdefs. Thanks!
Santiago
A: 

Sorry, I don't know any tools to help you, but if I had to do this I would go with a simple script that does this: - Copy all source files to another place, - Add a running number (in a comment, obviously) at the start of each line (first code line of first file = 1, do not reset between files), - Pre-process using all the pre-defined configurations and check which lines were included and which weren't, - Check which lines have been included and which are missing.

Shouldn't take more than a couple of days to get that running using e.g. Perl or Python. What is required is a file that has a line including the configurations. This should be quick enough to do with the help of this script. Just check which lines are not included with the configurations already and edit the file until every line is included. Then just run this occasionally to make sure that there are no new paths.

Analyzing the source like you want would be a much more complex script and I'm not sure if it would be worth the trouble.

Makis
+1  A: 

You can use unifdef -s to get a list of all preprocessor symbols used in preprocessor conditionals. Based on the discussion around the other answers this is clearly not quite the information you need, but if you use the -d option as well, the debugging output includes the nesting level. It should be fairly simple to filter the output to produce the symbol combinations you want.

Tony Finch
Tony, thanks a lot, this great tool will certainly find which are the nested ifdef in our sources. So this info plus the script provided in another answer is what I need.Another good thing you forgot to mention is that the unifdef tool is currently packaged in some linux distributions (and you can use the -d option even if the man page doesn't mention anything about it).
Santiago
I've tweaked unifdef to add a -S option so you can get this information without turning on debugging mode. Version 352 is available from http://dotat.at/prog/unifdef/
Tony Finch