NoQ created this revision.
Herald added subscribers: rnkovacs, eraman.
Under the assumption of `-analyzer-config c++-allocator-inlining=true`, which
enables evaluation of `operator new` as a regular function call, this patch
shows what it takes to actually inline the constructor into the return value of
such operator call.
The CFG for a `new C()` expression looks like:
- `CXXNewAllocator` `new C()` (a special kind of `CFGElement` on which operator
new is being evaluated)
- `CXXConstructExpr` `C()` (a regular `CFGStmt` element on which we call the
constructor)
- `CXXNewExpr` `new C()` (a regular `CFGStmt` element on which we should
ideally have nothing to do)
What i did here is:
1. First, i relax the requirements for constructor regions, as discussed on the
mailing list
(http://lists.llvm.org/pipermail/cfe-dev/2017-November/056095.html). We can now
construct into `ElementRegion` unless it actually represents an array element
(we're not dealing with `operator new[]` yet). Also we remove the explicit
bailout for constructions that correspond to operator new parent expressions,
as long as `-analyzer-config c++-allocator-inlining` is enabled. See changes in
`mayInlineCallKind()`.
2. Then, when the constructor is being modeled, we're trying to get back the
value returned by `operator new`. This is done similarly to other construction
cases - by finding the next (!!) element in the CFG, figuring out that it's a
`CXXNewExpr`, and then taking the respective region to construct into from the
Environment. The `CXXNewAllocator` element is not relied upon on this phase -
for now it only triggers the evaluation of `operator new` at the right moment,
so that we had the return value. See changes in
`getRegionForConstructedObject()` and `canHaveDirectConstructor()`.
3. When we reach the actual `CXXNewExpr` CFG element (the third one), we need
to preserve the value we already have for the `CXXNewExpr` in the environment.
The old code that's conjuring a (heap) pointer here would probably still remain
to handle the case when an inlined `operator new` has actually returned an
`UnknownVal`. Still, this needs polishing, as the FIXME at the top of
`VisitCXXNewExpr()` suggests. See the newly added hack in `VisitCXXNewExpr()`.
4. Finally, the `CXXNewExpr` value keeps dying in the Environment. From the
point of view of the current liveness analysis, the new-expression is dead (or
rather not yet born) until the `CXXNewExpr` statement element is reached, so it
immediately gets purged on every step. The really dirty code that prevents
this, //that should never be committed//, is in `shouldRemoveDeadBindings()`:
for the sake of this proof-of-concept, i've crudely disabled garbage collection
on the respective moments of time. I believe that the proper fix would be on
the liveness analysis side: mark the new-expression as live between the
`CXXNewAllocator` element and `CXXNewExpr` statement element.
My todo list before committing this would be:
1. Fix the live expressions hack.
2. See how reliable is `findElementDirectlyInitializedByCurrentConstructor()`
in this case.
3. Understand how my code works for non-object constructors (eg. `new int`).
4. See how much `VisitCXXNewExpr` can be refactored.
Once this lands, i think we should be looking into enabling `-analyzer-config
c++-allocator-inlining` by default.
https://reviews.llvm.org/D40560
Files:
lib/StaticAnalyzer/Core/ExprEngine.cpp
lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
test/Analysis/inline.cpp
test/Analysis/new-ctor.cpp
Index: test/Analysis/new-ctor.cpp
===================================================================
--- /dev/null
+++ test/Analysis/new-ctor.cpp
@@ -0,0 +1,14 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config c++-allocator-inlining=true -std=c++11 -verify %s
+
+void clang_analyzer_eval(bool);
+
+struct S {
+ int x;
+ S() : x(1) {}
+ ~S() {}
+};
+
+void foo() {
+ S *s = new S;
+ clang_analyzer_eval(s->x == 1); // expected-warning{{TRUE}}
+}
Index: test/Analysis/inline.cpp
===================================================================
--- test/Analysis/inline.cpp
+++ test/Analysis/inline.cpp
@@ -315,17 +315,13 @@
int value;
IntWrapper(int input) : value(input) {
- // We don't want this constructor to be inlined unless we can actually
- // use the proper region for operator new.
- // See PR12014 and <rdar://problem/12180598>.
- clang_analyzer_checkInlined(false); // no-warning
+ clang_analyzer_checkInlined(true); // expected-warning{{TRUE}}
}
};
void test() {
IntWrapper *obj = new IntWrapper(42);
- // should be TRUE
- clang_analyzer_eval(obj->value == 42); // expected-warning{{UNKNOWN}}
+ clang_analyzer_eval(obj->value == 42); // expected-warning{{TRUE}}
delete obj;
}
Index: lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
===================================================================
--- lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
+++ lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
@@ -596,21 +596,30 @@
const CXXConstructorCall &Ctor = cast<CXXConstructorCall>(Call);
+ const CXXConstructExpr *CtorExpr = Ctor.getOriginExpr();
+ const Stmt *ParentExpr = CurLC->getParentMap().getParent(CtorExpr);
+
+ if (ParentExpr && isa<CXXNewExpr>(ParentExpr) &&
+ !Opts.mayInlineCXXAllocator())
+ return CIP_DisallowedOnce;
+
// FIXME: We don't handle constructors or destructors for arrays properly.
// Even once we do, we still need to be careful about implicitly-generated
// initializers for array fields in default move/copy constructors.
+ // We still allow construction into ElementRegion targets when they don't
+ // represent array elements.
const MemRegion *Target = Ctor.getCXXThisVal().getAsRegion();
- if (Target && isa<ElementRegion>(Target))
- return CIP_DisallowedOnce;
-
- // FIXME: This is a hack. We don't use the correct region for a new
- // expression, so if we inline the constructor its result will just be
- // thrown away. This short-term hack is tracked in <rdar://problem/12180598>
- // and the longer-term possible fix is discussed in PR12014.
- const CXXConstructExpr *CtorExpr = Ctor.getOriginExpr();
- if (const Stmt *Parent = CurLC->getParentMap().getParent(CtorExpr))
- if (isa<CXXNewExpr>(Parent))
- return CIP_DisallowedOnce;
+ if (Target && isa<ElementRegion>(Target)) {
+ if (ParentExpr)
+ if (const CXXNewExpr *NewExpr = dyn_cast<CXXNewExpr>(ParentExpr))
+ if (NewExpr->isArray())
+ return CIP_DisallowedOnce;
+
+ if (const TypedValueRegion *TR = dyn_cast<TypedValueRegion>(
+ cast<SubRegion>(Target)->getSuperRegion()))
+ if (TR->getValueType()->isArrayType())
+ return CIP_DisallowedOnce;
+ }
// Inlining constructors requires including initializers in the CFG.
const AnalysisDeclContext *ADC = CallerSFC->getAnalysisDeclContext();
@@ -629,7 +638,7 @@
// FIXME: This is a hack. We don't handle temporary destructors
// right now, so we shouldn't inline their constructors.
if (CtorExpr->getConstructionKind() == CXXConstructExpr::CK_Complete)
- if (!Target || !isa<DeclRegion>(Target))
+ if (!Target || isa<CXXTempObjectRegion>(Target))
return CIP_DisallowedOnce;
break;
Index: lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
===================================================================
--- lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
@@ -114,14 +114,21 @@
if (auto Elem = findElementDirectlyInitializedByCurrentConstructor()) {
if (Optional<CFGStmt> StmtElem = Elem->getAs<CFGStmt>()) {
- auto *DS = cast<DeclStmt>(StmtElem->getStmt());
- if (const auto *Var = dyn_cast<VarDecl>(DS->getSingleDecl())) {
- if (Var->getInit() && Var->getInit()->IgnoreImplicit() == CE) {
- SVal LValue = State->getLValue(Var, LCtx);
- QualType Ty = Var->getType();
- LValue = makeZeroElementRegion(State, LValue, Ty);
- return LValue.getAsRegion();
+ if (auto *NE = dyn_cast<CXXNewExpr>(StmtElem->getStmt())) {
+ SVal NewVal = State->getSVal(NE, LCtx);
+ if (const MemRegion *MR = NewVal.getAsRegion())
+ return MR;
+ } else if (auto *DS = dyn_cast<DeclStmt>(StmtElem->getStmt())) {
+ if (const auto *Var = dyn_cast<VarDecl>(DS->getSingleDecl())) {
+ if (Var->getInit() && Var->getInit()->IgnoreImplicit() == CE) {
+ SVal LValue = State->getLValue(Var, LCtx);
+ QualType Ty = Var->getType();
+ LValue = makeZeroElementRegion(State, LValue, Ty);
+ return LValue.getAsRegion();
+ }
}
+ } else {
+ llvm_unreachable("Unexpected directly initialized element!");
}
} else if (Optional<CFGInitializer> InitElem = Elem->getAs<CFGInitializer>()) {
const CXXCtorInitializer *Init = InitElem->getInitializer();
@@ -165,6 +172,9 @@
if (isa<DeclStmt>(StmtElem->getStmt())) {
return true;
}
+ if (isa<CXXNewExpr>(StmtElem->getStmt())) {
+ return true;
+ }
}
if (Elem.getKind() == CFGElement::Initializer) {
@@ -456,7 +466,7 @@
unsigned blockCount = currBldrCtx->blockCount();
const LocationContext *LCtx = Pred->getLocationContext();
- DefinedOrUnknownSVal symVal = UnknownVal();
+ SVal symVal = UnknownVal();
FunctionDecl *FD = CNE->getOperatorNew();
bool IsStandardGlobalOpNewFunction = false;
@@ -475,11 +485,17 @@
// We assume all standard global 'operator new' functions allocate memory in
// heap. We realize this is an approximation that might not correctly model
// a custom global allocator.
- if (IsStandardGlobalOpNewFunction)
- symVal = svalBuilder.getConjuredHeapSymbolVal(CNE, LCtx, blockCount);
- else
- symVal = svalBuilder.conjureSymbolVal(nullptr, CNE, LCtx, CNE->getType(),
- blockCount);
+ if (const Expr *Init = CNE->getInitializer())
+ if (isa<CXXConstructExpr>(Init))
+ symVal = Pred->getState()->getSVal(CNE, LCtx);
+
+ if (symVal.isUnknown()) {
+ if (IsStandardGlobalOpNewFunction)
+ symVal = svalBuilder.getConjuredHeapSymbolVal(CNE, LCtx, blockCount);
+ else
+ symVal = svalBuilder.conjureSymbolVal(nullptr, CNE, LCtx, CNE->getType(),
+ blockCount);
+ }
ProgramStateRef State = Pred->getState();
CallEventManager &CEMgr = getStateManager().getCallEventManager();
@@ -504,7 +520,8 @@
QualType Ty = FD->getType();
if (const FunctionProtoType *ProtoType = Ty->getAs<FunctionProtoType>())
if (!ProtoType->isNothrow(getContext()))
- State = State->assume(symVal, true);
+ if (auto dSymVal = symVal.getAs<DefinedOrUnknownSVal>())
+ State = State->assume(*dSymVal, true);
}
StmtNodeBuilder Bldr(Pred, Dst, *currBldrCtx);
Index: lib/StaticAnalyzer/Core/ExprEngine.cpp
===================================================================
--- lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -389,6 +389,15 @@
if (!isa<Expr>(S.getStmt()))
return true;
+ if (const CXXNewExpr *NE = dyn_cast_or_null<CXXNewExpr>(S.getStmt()))
+ return false;
+
+ if (const CXXConstructExpr *CE =
+ dyn_cast_or_null<CXXConstructExpr>(S.getStmt()))
+ if (const Stmt *Parent = LC->getParentMap().getParent(CE))
+ if (isa<CXXNewExpr>(Parent))
+ return false;
+
// Run before processing a call.
if (CallEvent::isCallStmt(S.getStmt()))
return true;
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits