Shortcomings

The nature of the C language and its preprocessor can result in pathological cases that can confuse the CScout analysis and substitution engine. In all cases the confusion only results in erroneous analysis or substitutions of the particular identifiers and will not affect other parts of the code. In some cases you can even slightly modify your workspace definition or code to ensure CScout works as you intend. The following cases are the most important in recognising and substituting identifiers:
  1. Conditional compilation

    Some programs have parts of them compiled under conditional preprocessor directives. Consider the following example:

    #ifdef unix
    #include <unistd.h>
    #define erase_file(x) unlink(x)
    #endif
    
    #ifdef WIN32
    #include <windows.h>
    #define erase_file(x) DeleteFile(x)
    #endif
    
    main(int argc, char *argv[])
    {
    	erase_file(argv[1]);
    }
    
    As humans we can understand that erase_file occurs three times within the file. However, because CScout preprocesses the file following the C preprocessor semantics, it will typically match only two instances. In some cases you can get around this problem by defining macros that will ensure that all code inside conditional directives gets processed. In other cases this will result in errors (e.g. a duplicate macro definition in the above example). In such cases you can include in your workspace the same project multiple times, each time with a different set of defined macros.
    workspace example {
    	project idtest {
    		define DEBUG 1
    		define TEST 1
    		file idtest.c util.c
    	}
    	project idtest2 {
    		define NDEBUG 1
    		define PRODUCTION
    		file idtest.c util.c
    }
    
  2. Partial coverage of macro use

    Consider the following example:

    struct s1 {
    	int id;
    } a;
    
    struct s2 {
    	char id;
    } b;
    
    struct s3 {
    	double id;
    } c;
    
    #define getid(x) ((x)->id)
    
    main()
    {
    	printf("%d %c", getid(a), getid(b));
    }
    
    In the above example, changing an id instance should also change the other three instances. However, CScout will not associate the member of s3 with the identifier appearing in the getid macro or the s1 or s2 structures, because there is no getid macro invocation to link them together. If e.g. id is replaced with val the program will compile and function correctly, but when one tries to access the c struture's member in the future using getid an error will result.
    struct s1 {
    	int val;
    } a;
    
    struct s2 {
    	char val;
    } b;
    
    struct s3 {
    	double id;
    } c;
    
    #define getid(x) ((x)->val)
    
    main()
    {
    	printf("%d %c", getid(a), getid(b));	/* OK */
    	printf(" %g", getid(c));		/* New statement: error */
    }
    
    To avoid this (rare) problem you can introduce dummy macro invocations of the form:
    #ifdef CSCOUT
    	(void)getid(d)
    #endif
    
  3. Undefined macros

    We employ a heuristic classifying all instances of an undefined macro as being the same identifier. Thus in the following sequence foo will match all three macro instances:

    #undef foo
    
    #ifdef foo
    #endif
    
    #ifdef foo
    #endif
    
    #define foo 1
    
    In most cases this is what you want, but there may be cases where the macro appears in different files and with a different meaning. In such cases the undefined instances of the macro will erroneously match the defined instance.

In addition, the analysis of functions can be confused by the following situations.

  1. Functions getting called through function pointers will not appear in the call graphs. This is a common limitation of static call analysis.
  2. Function-like macros called from inside function bodies that were generated by macro expansion will not be registered as calls.
  3. Non-function like macros that expand into function calls will not appear in the call graph; the corresponding functions will appear to be called by the function containing the macro.

Finally, because function argument refactoring works at a higher level thann simple identifiers, the following limitations hold.

  1. When a function call's arguments macro-expand into unballanced brackets or into multiple function arguments the replacement can misbehave.
  2. When there is not a one-to-one correspondence between a function's name and its associated identifier (i.e. when the function's name is generated through macro-token concatenation) the function argument refactoring is not offered as an option.