NoQ created this revision.
NoQ added reviewers: dcoughlin, xazax.hun, a.sidorin, george.karpenkov, szepet.
Herald added subscribers: cfe-commits, rnkovacs.

Just wanted to play safer in a couple of places.

In https://reviews.llvm.org/D44597 i said:

> If the object has trivial destructor then CXXBindTemporaryExpr is not there, 
> and the AST is pretty much indistinguishable from the simple case so we 
> re-use SimpleVariableConstructionContext and 
> SimpleReturnedValueConstructionContext. I'm not entirely sure that this is 
> indeed the same case, but for the purposes of the analyzer, which is the 
> primary user of construction contexts, this seems fine because when the 
> object has trivial destructor it is not scary to inline the constructor.

Well, this is actually an easy question. There certainly are cases where the 
AST is quite distinguishable, eg. ternary conditional operator. However, 
because construction contexts are implemented as a whitelist of possible ASTs 
that we support, it's easy to see that the ternary operator is in fact the only 
case. The patch suppresses construction contexts in this case for now, because 
the example from http://lists.llvm.org/pipermail/cfe-dev/2018-March/057357.html 
made me nervous (even though this particular example works well when there are 
no destructors involved, so this is a speculative play-safe thingy without an 
actual analyzer-based test).

Also raise the improperly-constructed flag for the return value construction 
context into a non-temporary. This also has no effect on the analyzer because 
we'd inline the constructor anyway. Additionally i added a test case to 
demonstrate how this is broken.


Repository:
  rC Clang

https://reviews.llvm.org/D44854

Files:
  lib/Analysis/CFG.cpp
  lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
  test/Analysis/cfg-rich-constructors.cpp
  test/Analysis/cxx17-mandatory-elision.cpp

Index: test/Analysis/cxx17-mandatory-elision.cpp
===================================================================
--- /dev/null
+++ test/Analysis/cxx17-mandatory-elision.cpp
@@ -0,0 +1,58 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++11 -verify %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 -verify %s
+
+void clang_analyzer_eval(bool);
+
+template <typename T> struct AddressVector {
+  T *buf[10];
+  int len;
+
+  AddressVector() : len(0) {}
+
+  void push(T *t) {
+    buf[len] = t;
+    ++len;
+  }
+};
+
+class ClassWithoutDestructor {
+  AddressVector<ClassWithoutDestructor> &v;
+
+public:
+  ClassWithoutDestructor(AddressVector<ClassWithoutDestructor> &v) : v(v) {
+    v.push(this);
+  }
+
+  ClassWithoutDestructor(ClassWithoutDestructor &&c) : v(c.v) { v.push(this); }
+  ClassWithoutDestructor(const ClassWithoutDestructor &c) : v(c.v) {
+    v.push(this);
+  }
+};
+
+ClassWithoutDestructor make1(AddressVector<ClassWithoutDestructor> &v) {
+  return ClassWithoutDestructor(v);
+}
+ClassWithoutDestructor make2(AddressVector<ClassWithoutDestructor> &v) {
+  return make1(v);
+}
+ClassWithoutDestructor make3(AddressVector<ClassWithoutDestructor> &v) {
+  return make2(v);
+}
+
+void testMultipleReturns() {
+  AddressVector<ClassWithoutDestructor> v;
+  ClassWithoutDestructor c = make3(v);
+
+#if __cplusplus >= 201703L
+  // FIXME: Both should be TRUE.
+  clang_analyzer_eval(v.len == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(v.buf[0] == &c); // expected-warning{{FALSE}}
+#else
+  clang_analyzer_eval(v.len == 5); // expected-warning{{TRUE}}
+  clang_analyzer_eval(v.buf[0] != v.buf[1]); // expected-warning{{TRUE}}
+  clang_analyzer_eval(v.buf[1] != v.buf[2]); // expected-warning{{TRUE}}
+  clang_analyzer_eval(v.buf[2] != v.buf[3]); // expected-warning{{TRUE}}
+  clang_analyzer_eval(v.buf[3] != v.buf[4]); // expected-warning{{TRUE}}
+  clang_analyzer_eval(v.buf[4] == &c); // expected-warning{{TRUE}}
+#endif
+}
Index: test/Analysis/cfg-rich-constructors.cpp
===================================================================
--- test/Analysis/cfg-rich-constructors.cpp
+++ test/Analysis/cfg-rich-constructors.cpp
@@ -108,6 +108,9 @@
   C c = C::get();
 }
 
+// FIXME: Find construction contexts for both branches in C++17.
+// Note that once it gets detected, the test for the get() branch would not
+// fail, because FileCheck allows partial matches.
 // CHECK: void simpleVariableWithTernaryOperator(bool coin)
 // CHECK:        [B1]
 // CXX11-NEXT:     1: [B4.2] ? [B2.5] : [B3.6]
@@ -122,15 +125,15 @@
 // CXX11-NEXT:     3: [B2.2]() (CXXRecordTypedCall, [B2.4])
 // CXX11-NEXT:     4: [B2.3]
 // CXX11-NEXT:     5: [B2.4] (CXXConstructExpr, [B1.2], class C)
-// CXX17-NEXT:     3: [B2.2]() (CXXRecordTypedCall, [B1.2])
+// CXX17-NEXT:     3: [B2.2]()
 // CHECK:        [B3]
 // CHECK-NEXT:     1: 0
 // CHECK-NEXT:     2: [B3.1] (ImplicitCastExpr, NullToPointer, class C *)
 // CXX11-NEXT:     3: [B3.2] (CXXConstructExpr, [B3.5], class C)
 // CXX11-NEXT:     4: C([B3.3]) (CXXFunctionalCastExpr, ConstructorConversion, class C)
 // CXX11-NEXT:     5: [B3.4]
 // CXX11-NEXT:     6: [B3.5] (CXXConstructExpr, [B1.2], class C)
-// CXX17-NEXT:     3: [B3.2] (CXXConstructExpr, [B1.2], class C)
+// CXX17-NEXT:     3: [B3.2] (CXXConstructExpr, class C)
 // CXX17-NEXT:     4: C([B3.3]) (CXXFunctionalCastExpr, ConstructorConversion, class C)
 // CHECK:        [B4]
 // CHECK-NEXT:     1: coin
Index: lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
===================================================================
--- lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
@@ -203,13 +203,24 @@
       // TODO: What exactly happens when we are? Does the temporary object live
       // long enough in the region store in this case? Would checkers think
       // that this object immediately goes out of scope?
-      // TODO: We assume that the call site has a temporary object construction
-      // context. This is no longer true in C++17 or when copy elision is
-      // performed. We may need to unwrap multiple stack frames here and we
-      // won't necessarily end up with a temporary at the end.
       const LocationContext *TempLCtx = LCtx;
-      if (const LocationContext *CallerLCtx =
-              LCtx->getCurrentStackFrame()->getParent()) {
+      const StackFrameContext *SFC = LCtx->getCurrentStackFrame();
+      if (const LocationContext *CallerLCtx = SFC->getParent()) {
+        auto RTC = (*SFC->getCallSiteBlock())[SFC->getIndex()]
+                       .getAs<CFGCXXRecordTypedCall>();
+        if (!RTC) {
+          // We were unable to find the correct construction context for the
+          // call in the parent stack frame. This is equivalent to not being
+          // able to find construction context at all.
+          CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true;
+        } else if (!isa<TemporaryObjectConstructionContext>(
+                       RTC->getConstructionContext())) {
+          // FXIME: The return value is constructed directly into a
+          // non-temporary due to C++17 mandatory copy elision. This is not
+          // implemented yet.
+          assert(getContext().getLangOpts().CPlusPlus17);
+          CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true;
+        }
         TempLCtx = CallerLCtx;
       }
       CallOpts.IsTemporaryCtorOrDtor = true;
Index: lib/Analysis/CFG.cpp
===================================================================
--- lib/Analysis/CFG.cpp
+++ lib/Analysis/CFG.cpp
@@ -1282,7 +1282,6 @@
   }
   case Stmt::ImplicitCastExprClass: {
     auto *Cast = cast<ImplicitCastExpr>(Child);
-    // TODO: We need to support CK_ConstructorConversion, maybe other kinds?
     switch (Cast->getCastKind()) {
     case CK_NoOp:
     case CK_ConstructorConversion:
@@ -1302,6 +1301,15 @@
   }
   case Stmt::ConditionalOperatorClass: {
     auto *CO = cast<ConditionalOperator>(Child);
+    if (!dyn_cast_or_null<MaterializeTemporaryExpr>(Layer->getTriggerStmt())) {
+      // If the object returned by the conditional operator is not going to be a
+      // temporary object that needs to be immediately materialized, then
+      // it must be C++17 with its mandatory copy elision. Do not yet promise
+      // to support this case.
+      assert(!CO->getType()->getAsCXXRecordDecl() ||
+             Context->getLangOpts().CPlusPlus17);
+      break;
+    }
     findConstructionContexts(Layer, CO->getLHS());
     findConstructionContexts(Layer, CO->getRHS());
     break;
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to