The -analyzer-config c++-allocator-inlining experimental option allows the analyzer to reason about C++ operator new() similarly to how it reasons about regular functions. In this mode, operator new() is correctly called before the construction of an object, with the help of a special CFG element. However, the subsequent construction of the object was still not performed into the region of memory returned by operator new(). The patch fixes it. Passing the value from operator new() to the constructor and then to the new-expression itself was tricky because operator new() has no call site of its own in the AST. The new expression itself is not a good call site because it has an incorrect type (operator new() returns 'void *', while the new expression is a pointer to the allocated object type). Additionally, lifetime of the new expression in the environment makes it unsuitable for passing the value. For that reason, an additional program state trait is introduced to keep track of the return value. Finally this patch relaxes restrictions on the memory region class that are required for inlining the constructor. This change affects the old mode as well (c++-allocator-inlining=false) and seems safe because these restrictions were an overkill compared to the actual problems observed. Differential Revision: https://reviews.llvm.org/D40560 rdar://problem/12180598 llvm-svn: 322774
119 lines
3.4 KiB
C++
119 lines
3.4 KiB
C++
// RUN: %clang_analyze_cc1 -std=c++11 -analyzer-checker=core,cplusplus.NewDelete,cplusplus.NewDeleteLeaks,debug.ExprInspection -analyzer-config c++-allocator-inlining=true -std=c++11 -verify %s
|
|
|
|
void clang_analyzer_eval(bool);
|
|
void clang_analyzer_dump(int);
|
|
|
|
typedef __typeof__(sizeof(int)) size_t;
|
|
|
|
void *conjure();
|
|
void exit(int);
|
|
|
|
struct S;
|
|
|
|
S *global_s;
|
|
|
|
// Recursive operator kinda placement new.
|
|
void *operator new(size_t size, S *place);
|
|
|
|
enum class ConstructionKind : char {
|
|
Garbage,
|
|
Recursive
|
|
};
|
|
|
|
struct S {
|
|
public:
|
|
int x;
|
|
S(): x(1) {}
|
|
S(int y): x(y) {}
|
|
|
|
S(ConstructionKind k) {
|
|
switch (k) {
|
|
case ConstructionKind::Recursive: { // Call one more operator new 'r'ecursively.
|
|
S *s = new (nullptr) S(5);
|
|
x = s->x + 1;
|
|
global_s = s;
|
|
return;
|
|
}
|
|
case ConstructionKind::Garbage: {
|
|
// Leaves garbage in 'x'.
|
|
}
|
|
}
|
|
}
|
|
~S() {}
|
|
};
|
|
|
|
// Do not try this at home!
|
|
void *operator new(size_t size, S *place) {
|
|
if (!place)
|
|
return new S();
|
|
return place;
|
|
}
|
|
|
|
void testThatCharConstructorIndeedYieldsGarbage() {
|
|
S *s = new S(ConstructionKind::Garbage);
|
|
clang_analyzer_eval(s->x == 0); // expected-warning{{UNKNOWN}}
|
|
clang_analyzer_eval(s->x == 1); // expected-warning{{UNKNOWN}}
|
|
// FIXME: This should warn, but MallocChecker doesn't default-bind regions
|
|
// returned by standard operator new to garbage.
|
|
s->x += 1; // no-warning
|
|
delete s;
|
|
}
|
|
|
|
|
|
void testChainedOperatorNew() {
|
|
S *s;
|
|
// * Evaluate standard new.
|
|
// * Evaluate constructor S(3).
|
|
// * Bind value for standard new.
|
|
// * Evaluate our custom new.
|
|
// * Evaluate constructor S(Garbage).
|
|
// * Bind value for our custom new.
|
|
s = new (new S(3)) S(ConstructionKind::Garbage);
|
|
clang_analyzer_eval(s->x == 3); // expected-warning{{TRUE}}
|
|
// expected-warning@+9{{Potential leak of memory pointed to by 's'}}
|
|
|
|
// * Evaluate standard new.
|
|
// * Evaluate constructor S(Garbage).
|
|
// * Bind value for standard new.
|
|
// * Evaluate our custom new.
|
|
// * Evaluate constructor S(4).
|
|
// * Bind value for our custom new.
|
|
s = new (new S(ConstructionKind::Garbage)) S(4);
|
|
clang_analyzer_eval(s->x == 4); // expected-warning{{TRUE}}
|
|
delete s;
|
|
|
|
// -> Enter our custom new (nullptr).
|
|
// * Evaluate standard new.
|
|
// * Inline constructor S().
|
|
// * Bind value for standard new.
|
|
// <- Exit our custom new (nullptr).
|
|
// * Evaluate constructor S(Garbage).
|
|
// * Bind value for our custom new.
|
|
s = new (nullptr) S(ConstructionKind::Garbage);
|
|
clang_analyzer_eval(s->x == 1); // expected-warning{{TRUE}}
|
|
delete s;
|
|
|
|
// -> Enter our custom new (nullptr).
|
|
// * Evaluate standard new.
|
|
// * Inline constructor S().
|
|
// * Bind value for standard new.
|
|
// <- Exit our custom new (nullptr).
|
|
// -> Enter constructor S(Recursive).
|
|
// -> Enter our custom new (nullptr).
|
|
// * Evaluate standard new.
|
|
// * Inline constructor S().
|
|
// * Bind value for standard new.
|
|
// <- Exit our custom new (nullptr).
|
|
// * Evaluate constructor S(5).
|
|
// * Bind value for our custom new (nullptr).
|
|
// * Assign that value to global_s.
|
|
// <- Exit constructor S(Recursive).
|
|
// * Bind value for our custom new (nullptr).
|
|
global_s = nullptr;
|
|
s = new (nullptr) S(ConstructionKind::Recursive);
|
|
clang_analyzer_eval(global_s); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(global_s->x == 5); // expected-warning{{TRUE}}
|
|
clang_analyzer_eval(s->x == 6); // expected-warning{{TRUE}}
|
|
delete s;
|
|
}
|