[PATCH] D44854: [analyzer] Be more careful about C++17 copy elision.

2018-03-30 Thread Phabricator via Phabricator via cfe-commits
This revision was automatically updated to reflect the committed changes.
Closed by commit rC328893: [CFG] [analyzer] Avoid modeling C++17 constructors 
that arent fully supported. (authored by dergachev, committed by ).

Repository:
  rL LLVM

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
  test/Analysis/temp-obj-dtors-cfg-output.cpp

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: test/Analysis/cxx17-mandatory-elision.cpp
===
--- test/Analysis/cxx17-mandatory-elision.cpp
+++ 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  struct AddressVector {
+  T *buf[10];
+  int len;
+
+  AddressVector() : len(0) {}
+
+  void push(T *t) {
+buf[len] = t;
+++len;
+  }
+};
+
+class ClassWithoutDestructor {
+  AddressVector 
+
+public:
+  ClassWithoutDestructor(AddressVector ) : v(v) {
+v.push(this);
+  }
+
+  ClassWithoutDestructor(ClassWithoutDestructor &) : v(c.v) { v.push(this); }
+  ClassWithoutDestructor(const ClassWithoutDestructor ) : v(c.v) {
+v.push(this);
+  }
+};
+
+ClassWithoutDestructor make1(AddressVector ) {
+  return ClassWithoutDestructor(v);
+}
+ClassWithoutDestructor make2(AddressVector ) {
+  return make1(v);
+}
+ClassWithoutDestructor make3(AddressVector ) {
+  return make2(v);
+}
+
+void testMultipleReturns() {
+  AddressVector 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] == ); // 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] == ); // expected-warning{{TRUE}}
+#endif
+}
Index: test/Analysis/temp-obj-dtors-cfg-output.cpp
===
--- test/Analysis/temp-obj-dtors-cfg-output.cpp
+++ test/Analysis/temp-obj-dtors-cfg-output.cpp
@@ -218,6 +218,9 @@
 // Don't try to figure out how to perform construction of the record here.
 const C () { return foo1(); } // no-crash
 C &() { return foo2(); } // no-crash
+const C (bool coin) {
+  return coin ? foo1() : foo1(); // no-crash
+}
 } // end namespace pass_references_through
 
 // CHECK:   [B1 (ENTRY)]
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
-

[PATCH] D44854: [analyzer] Be more careful about C++17 copy elision.

2018-03-30 Thread Phabricator via Phabricator via cfe-commits
This revision was automatically updated to reflect the committed changes.
Closed by commit rL328893: [CFG] [analyzer] Avoid modeling C++17 constructors 
that arent fully supported. (authored by dergachev, committed by ).
Herald added a subscriber: llvm-commits.

Changed prior to commit:
  https://reviews.llvm.org/D44854?vs=140168=140469#toc

Repository:
  rL LLVM

https://reviews.llvm.org/D44854

Files:
  cfe/trunk/lib/Analysis/CFG.cpp
  cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
  cfe/trunk/test/Analysis/cfg-rich-constructors.cpp
  cfe/trunk/test/Analysis/cxx17-mandatory-elision.cpp
  cfe/trunk/test/Analysis/temp-obj-dtors-cfg-output.cpp

Index: cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
===
--- cfe/trunk/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ cfe/trunk/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();
+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(
+   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: cfe/trunk/lib/Analysis/CFG.cpp
===
--- cfe/trunk/lib/Analysis/CFG.cpp
+++ cfe/trunk/lib/Analysis/CFG.cpp
@@ -1302,6 +1302,15 @@
   }
   case Stmt::ConditionalOperatorClass: {
 auto *CO = cast(Child);
+if (!dyn_cast_or_null(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() || CO->isGLValue() ||
+ Context->getLangOpts().CPlusPlus17);
+  break;
+}
 findConstructionContexts(Layer, CO->getLHS());
 findConstructionContexts(Layer, CO->getRHS());
 break;
Index: cfe/trunk/test/Analysis/cxx17-mandatory-elision.cpp
===
--- cfe/trunk/test/Analysis/cxx17-mandatory-elision.cpp
+++ cfe/trunk/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  struct AddressVector {
+  T *buf[10];
+  int len;
+
+  AddressVector() : len(0) {}
+
+  void push(T *t) {
+buf[len] = t;
+++len;
+  }
+};
+
+class ClassWithoutDestructor {
+  AddressVector 
+
+public:
+  ClassWithoutDestructor(AddressVector ) : v(v) {
+v.push(this);
+  }
+
+  ClassWithoutDestructor(ClassWithoutDestructor &) : v(c.v) { v.push(this); }
+  ClassWithoutDestructor(const ClassWithoutDestructor ) : v(c.v) {
+v.push(this);
+  }
+};
+
+ClassWithoutDestructor make1(AddressVector ) {
+  return ClassWithoutDestructor(v);
+}
+ClassWithoutDestructor make2(AddressVector ) {
+  return make1(v);
+}
+ClassWithoutDestructor make3(AddressVector ) {
+  return make2(v);
+}
+
+void testMultipleReturns() {
+  AddressVector 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] == ); // expected-warning{{FALSE}}
+#else
+  clang_analyzer_eval(v.len == 5); // 

[PATCH] D44854: [analyzer] Be more careful about C++17 copy elision.

2018-03-30 Thread Artem Dergachev via Phabricator via cfe-commits
NoQ added inline comments.



Comment at: lib/StaticAnalyzer/Core/ExprEngineCXX.cpp:215
+  // able to find construction context at all.
+  CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true;
+} else if (!isa(

george.karpenkov wrote:
> Is this field `false` by default? Also, double negation is harder to read 
> (e.g. `not modelled properly = false`  vs. `modelled property = true`), but I 
> guess that should have been said earlier.
Yeah, flags within `EvalCallOptions` are all "red" flags to warn `evalCall()` 
that something is fishy about the call. They're all off by default. I'm not 
sure what's the best way to change that. I don't want each branch to set like 4 
different flags, so they should be non-"red" by default.


https://reviews.llvm.org/D44854



___
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D44854: [analyzer] Be more careful about C++17 copy elision.

2018-03-30 Thread George Karpenkov via Phabricator via cfe-commits
george.karpenkov accepted this revision.
george.karpenkov added a comment.
This revision is now accepted and ready to land.

LGTM provided comments are answered. Field rename would be appreciated, if 
possible.




Comment at: lib/StaticAnalyzer/Core/ExprEngineCXX.cpp:215
+  // able to find construction context at all.
+  CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true;
+} else if (!isa(

Is this field `false` by default? Also, double negation is harder to read (e.g. 
`not modelled properly = false`  vs. `modelled property = true`), but I guess 
that should have been said earlier.


https://reviews.llvm.org/D44854



___
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D44854: [analyzer] Be more careful about C++17 copy elision.

2018-03-28 Thread Artem Dergachev via Phabricator via cfe-commits
NoQ updated this revision to Diff 140168.
NoQ added a comment.

Fix a comment in tests.


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
  test/Analysis/temp-obj-dtors-cfg-output.cpp

Index: test/Analysis/temp-obj-dtors-cfg-output.cpp
===
--- test/Analysis/temp-obj-dtors-cfg-output.cpp
+++ test/Analysis/temp-obj-dtors-cfg-output.cpp
@@ -218,6 +218,9 @@
 // Don't try to figure out how to perform construction of the record here.
 const C () { return foo1(); } // no-crash
 C &() { return foo2(); } // no-crash
+const C (bool coin) {
+  return coin ? foo1() : foo1(); // no-crash
+}
 } // end namespace pass_references_through
 
 // CHECK:   [B1 (ENTRY)]
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  struct AddressVector {
+  T *buf[10];
+  int len;
+
+  AddressVector() : len(0) {}
+
+  void push(T *t) {
+buf[len] = t;
+++len;
+  }
+};
+
+class ClassWithoutDestructor {
+  AddressVector 
+
+public:
+  ClassWithoutDestructor(AddressVector ) : v(v) {
+v.push(this);
+  }
+
+  ClassWithoutDestructor(ClassWithoutDestructor &) : v(c.v) { v.push(this); }
+  ClassWithoutDestructor(const ClassWithoutDestructor ) : v(c.v) {
+v.push(this);
+  }
+};
+
+ClassWithoutDestructor make1(AddressVector ) {
+  return ClassWithoutDestructor(v);
+}
+ClassWithoutDestructor make2(AddressVector ) {
+  return make1(v);
+}
+ClassWithoutDestructor make3(AddressVector ) {
+  return make2(v);
+}
+
+void testMultipleReturns() {
+  AddressVector 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] == ); // 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] == ); // 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;
-   

[PATCH] D44854: [analyzer] Be more careful about C++17 copy elision.

2018-03-28 Thread Artem Dergachev via Phabricator via cfe-commits
NoQ updated this revision to Diff 140167.
NoQ added a comment.

Conditional operators, like call-expressions in 
https://reviews.llvm.org/D44273, may also be glvalues of record type instead of 
simply being reference-typed.


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
  test/Analysis/temp-obj-dtors-cfg-output.cpp

Index: test/Analysis/temp-obj-dtors-cfg-output.cpp
===
--- test/Analysis/temp-obj-dtors-cfg-output.cpp
+++ test/Analysis/temp-obj-dtors-cfg-output.cpp
@@ -218,6 +218,10 @@
 // Don't try to figure out how to perform construction of the record here.
 const C () { return foo1(); } // no-crash
 C &() { return foo2(); } // no-crash
+
+const C (bool coin) {
+  return coin ? foo1() : foo1();
+}
 } // end namespace pass_references_through
 
 // CHECK:   [B1 (ENTRY)]
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  struct AddressVector {
+  T *buf[10];
+  int len;
+
+  AddressVector() : len(0) {}
+
+  void push(T *t) {
+buf[len] = t;
+++len;
+  }
+};
+
+class ClassWithoutDestructor {
+  AddressVector 
+
+public:
+  ClassWithoutDestructor(AddressVector ) : v(v) {
+v.push(this);
+  }
+
+  ClassWithoutDestructor(ClassWithoutDestructor &) : v(c.v) { v.push(this); }
+  ClassWithoutDestructor(const ClassWithoutDestructor ) : v(c.v) {
+v.push(this);
+  }
+};
+
+ClassWithoutDestructor make1(AddressVector ) {
+  return ClassWithoutDestructor(v);
+}
+ClassWithoutDestructor make2(AddressVector ) {
+  return make1(v);
+}
+ClassWithoutDestructor make3(AddressVector ) {
+  return make2(v);
+}
+
+void testMultipleReturns() {
+  AddressVector 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] == ); // 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] == ); // 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 

[PATCH] D44854: [analyzer] Be more careful about C++17 copy elision.

2018-03-23 Thread Artem Dergachev via Phabricator via cfe-commits
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  struct AddressVector {
+  T *buf[10];
+  int len;
+
+  AddressVector() : len(0) {}
+
+  void push(T *t) {
+buf[len] = t;
+++len;
+  }
+};
+
+class ClassWithoutDestructor {
+  AddressVector 
+
+public:
+  ClassWithoutDestructor(AddressVector ) : v(v) {
+v.push(this);
+  }
+
+  ClassWithoutDestructor(ClassWithoutDestructor &) : v(c.v) { v.push(this); }
+  ClassWithoutDestructor(const ClassWithoutDestructor ) : v(c.v) {
+v.push(this);
+  }
+};
+
+ClassWithoutDestructor make1(AddressVector ) {
+  return ClassWithoutDestructor(v);
+}
+ClassWithoutDestructor make2(AddressVector ) {
+  return make1(v);
+}
+ClassWithoutDestructor make3(AddressVector ) {
+  return make2(v);
+}
+
+void testMultipleReturns() {
+  AddressVector 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] == ); // 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] == ); // 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: