Yup, Static Analyzer has a checker for this, and its current status is “opt-in” (i.e., the checker is optin.cplusplus.VirtualCall
). So we it’s stable and more or less full-featured and we encourage you to try it out, but it’s off by default because it finds non-bugs as well (like the case B). We should definitely enable-by-default the part of it that finds pure virtual calls. I wonder why didn’t we do that already. There’s even an option to enable such behavior.
Yes, indeed, cases like “C” are the reason why this checker was made.
And that’s also the reason why this check requires something as powerful as a full-featured “symbolic execution” to be useful, which is something that’s too slow to be a compiler warning. The previous attempt on this checker was simply scanning the AST for virtual function calls and went through the call graph to see if some of these virtual calls are reachable from the constructor. However, this approach was having false positives when there was no actual execution path that would result in going through the call graph in that order during construction. Eg.,
struct D {
bool Flag;
void setFlag() { Flag = true; } // The flag can be set later.
D() : Flag(false) { foo(); } // But its initial value is “clear”.
void foo() { if (Flag) bar(); } // Flag is still clear when called from D().
virtual void bar() = 0;
}
In this case if you look at the AST you’ll see that D() calls foo(), foo() calls bar(), bar() is pure virtual. But there’s no bug in this code, because foo() never actually calls bar() when called from the constructor. The VirtualCall checker, in its current state, is able to understand this sort of stuff as well (up to overall modeling bugs in the Static Analyzer).
A warning could still be used to catch some easier patterns, eg., when all paths through the constructor from a certain point result in a pure virtual function call. Eg., if you simplify the problem down to finding the bug when D::foo() is defined as something like if (Flag) bar(); else bar();
, it may be possible to come up with an efficient data flow analysis that will catch it and have no false positives. But it still needs to be inter-procedural, so it’s actually still pretty hard and we will still probably have to bail out at some stack depth. This would most likely become the most sophisticated compiler warning we have in Clang: we have a few data flow warnings, such as “variable may be used uninitialized in this function”, but none of them are inter-procedural.
So, yeah, i believe that coming up with a compiler warning is indeed relatively hard () and implementing it as a Static Analyzer warning is most likely the right approach.