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
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to