Yet another situation where we cannot easily guess, by looking at the CFG, the target region for C++ constructor call.
Consider the following brace initialization:
struct A {
A();
};
struct B : public A {
int x;
};
void foo() {
B b = {};
}AST in C++14:
`-VarDecl 0x7ff07884eab8 <col:3, col:10> col:5 b 'struct B' cinit `-CXXConstructExpr 0x7ff07887a940 <col:9, col:10> 'struct B' 'void (void) noexcept(false)' list zeroing
AST in C++17:
`-VarDecl 0x7fb1cf012b18 <col:3, col:10> col:5 b 'struct B' cinit
`-InitListExpr 0x7fb1cf012f38 <col:9, col:10> 'struct B'
|-CXXConstructExpr 0x7fb1cf012fd0 <col:10> 'struct A' 'void (void)' list
`-ImplicitValueInitExpr 0x7fb1cf013000 <<invalid sloc>> 'int'CFG in C++14:
[B1]
1: {} (CXXConstructExpr, struct B)
2: B b = {};CFG in C++17:
[B1]
1: {} (CXXConstructExpr, struct A)
2: /*implicit*/(int)0
3: {}
4: B b = {};So, essentially, in C++17 we don't have the trivial constructor call for B present anywhere at all, neither in AST, nor in CFG.
This causes a crash in the analyzer because he tries to find the target region for CK_NonVirtualBase constructor of A by looking at the parent stack frame, expecting to find the derived class constructor (for B) here, and then taking a base region within its this-object.
This fix is a quick-and-dirty suppression for the crash. It follows the tradition of "when unsure, make up a temporary and construct into that, then discard the result" - which is wrong, but at least it gets some invalidation done.
In our example it would be correct to construct into base{A, b}. There can also be situations when such initializer-list is instead lifetime-extended by a const& or && reference (see the tests), so in this case we'd need to construct into a respective sub-region of the temporary (but not into the temporary itself, of course).
Finally, from the perspective of checker development, i believe it would be useful to actually revive the constructor call, so that PreCall/PostCall event occured.
Could we try to make another lookup to see if we're initializing a variable of non-reference type? If so, we can make MemRegionManager use the region of the variable; otherwise, we can always fallback to temporary (it seems like sometimes this situation can happen out of DeclStmts). Or am I missing something?