Along with the problem you describe, one occasionally sees stuff like this:
#if A
void foo() {
#else
void foo(int x) {
#endif
Here there are two versions of the beginning of a function definition. There's not even any way to represent that in an ordinary AST, even if you could parse it, which you can't with any ordinary kind of parser. And it's not hard to come up with even nastier examples.I remember the day I actually realized what a clusterfuck the whole preprocessor was when trying to figure out all this precompiled header craziness; it's only there to solve all the horrible shortcomings of the POSIX/C/C++ eco-system as designed and imposed on it by how the preprocessor works. Even Strousup at several occasions admitted that he would like to have the preprocessor removed from C++. That's not going to happen anymore and it's obviously Captain Hindsight speaking here.
Actually, signed modulo can return negative numbers... See, for example, this StackOverflow question:
http://stackoverflow.com/questions/7594508/modulo-operator-w...
Also there's just no guarantees on how the software is built; in a lot of cases that's even outside a developer's reach.
But, yeah, since it's next to impossible to actually get to that state then, yes, sadly the only real option left is to analyze your finite set of builds.
I've seen simple cases where a function returned a signed integer value modulo x. Which of course means that this function can only return a value within 0..x-1.
This function was called in another function which then did some allocations based on it and the static analyzer complained about integer overflows which were obviously not possible in this case.
Dependency chains in preprocessors is a big pain in general too.
#ifdef A #define B #endif
#ifdef C #ifdef B blabla #endif #endif
This will yield a dependency chain for B of (A,C) -> B resulting in blabla being compiled. With complex code bases this already becomes quickly unmanageable. As far as I know no code analyzer is able to handle this.
And I haven't even started talking about supporting #if constructs (ex: #if X > 2). You would need to implement SAT solvers for that too.
Some more info on this at the clang mailing list here: http://lists.cs.uiuc.edu/pipermail/cfe-dev/2012-July/022720.....
These are just other reasons why seperating the preprocessor from the rest of the language was such a bad idea; you lose all the static code analysis stuff you can do on C/C++ code because of it and preprocessor directives control a lot of things in modern and big code bases.