[analyzer] Structured binding to tuple-like types
authorisuckatcs <65320245+isuckatcs@users.noreply.github.com>
Wed, 29 Jun 2022 16:42:07 +0000 (18:42 +0200)
committerisuckatcs <65320245+isuckatcs@users.noreply.github.com>
Tue, 26 Jul 2022 08:24:29 +0000 (10:24 +0200)
Introducing support for creating structured binding
to tuple-like types.

Differential Revision: https://reviews.llvm.org/D128837

clang/lib/Analysis/CFG.cpp
clang/lib/Analysis/LiveVariables.cpp
clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
clang/test/Analysis/live-bindings-test.cpp
clang/test/Analysis/uninit-structured-binding-tuple.cpp [new file with mode: 0644]

index 3937fff..84178ff 100644 (file)
@@ -2932,6 +2932,20 @@ CFGBlock *CFGBuilder::VisitDeclSubExpr(DeclStmt *DS) {
     }
   }
 
+  // If we bind to a tuple-like type, we iterate over the HoldingVars, and
+  // create a DeclStmt for each of them.
+  if (const auto *DD = dyn_cast<DecompositionDecl>(VD)) {
+    for (auto BD : llvm::reverse(DD->bindings())) {
+      if (auto *VD = BD->getHoldingVar()) {
+        DeclGroupRef DG(VD);
+        DeclStmt *DSNew =
+            new (Context) DeclStmt(DG, VD->getLocation(), GetEndLoc(VD));
+        cfg->addSyntheticDeclStmt(DSNew, DS);
+        Block = VisitDeclSubExpr(DSNew);
+      }
+    }
+  }
+
   autoCreateBlock();
   appendStmt(Block, DS);
 
index 6c601c2..ff7f3eb 100644 (file)
@@ -72,6 +72,11 @@ bool LiveVariables::LivenessValues::isLive(const VarDecl *D) const {
     bool alive = false;
     for (const BindingDecl *BD : DD->bindings())
       alive |= liveBindings.contains(BD);
+
+    // Note: the only known case this condition is necessary, is when a bindig
+    // to a tuple-like structure is created. The HoldingVar initializers have a
+    // DeclRefExpr to the DecompositionDecl.
+    alive |= liveDecls.contains(DD);
     return alive;
   }
   return liveDecls.contains(D);
@@ -343,8 +348,12 @@ void TransferFunctions::VisitBinaryOperator(BinaryOperator *B) {
 
       if (const BindingDecl* BD = dyn_cast<BindingDecl>(D)) {
         Killed = !BD->getType()->isReferenceType();
-        if (Killed)
+        if (Killed) {
+          if (const auto *HV = BD->getHoldingVar())
+            val.liveDecls = LV.DSetFact.remove(val.liveDecls, HV);
+
           val.liveBindings = LV.BSetFact.remove(val.liveBindings, BD);
+        }
       } else if (const auto *VD = dyn_cast<VarDecl>(D)) {
         Killed = writeShouldKill(VD);
         if (Killed)
@@ -371,8 +380,12 @@ void TransferFunctions::VisitDeclRefExpr(DeclRefExpr *DR) {
   const Decl* D = DR->getDecl();
   bool InAssignment = LV.inAssignment[DR];
   if (const auto *BD = dyn_cast<BindingDecl>(D)) {
-    if (!InAssignment)
+    if (!InAssignment) {
+      if (const auto *HV = BD->getHoldingVar())
+        val.liveDecls = LV.DSetFact.add(val.liveDecls, HV);
+
       val.liveBindings = LV.BSetFact.add(val.liveBindings, BD);
+    }
   } else if (const auto *VD = dyn_cast<VarDecl>(D)) {
     if (!InAssignment && !isAlwaysAlive(VD))
       val.liveDecls = LV.DSetFact.add(val.liveDecls, VD);
@@ -382,8 +395,16 @@ void TransferFunctions::VisitDeclRefExpr(DeclRefExpr *DR) {
 void TransferFunctions::VisitDeclStmt(DeclStmt *DS) {
   for (const auto *DI : DS->decls()) {
     if (const auto *DD = dyn_cast<DecompositionDecl>(DI)) {
-      for (const auto *BD : DD->bindings())
+      for (const auto *BD : DD->bindings()) {
+        if (const auto *HV = BD->getHoldingVar())
+          val.liveDecls = LV.DSetFact.remove(val.liveDecls, HV);
+
         val.liveBindings = LV.BSetFact.remove(val.liveBindings, BD);
+      }
+
+      // When a bindig to a tuple-like structure is created, the HoldingVar
+      // initializers have a DeclRefExpr to the DecompositionDecl.
+      val.liveDecls = LV.DSetFact.remove(val.liveDecls, DD);
     } else if (const auto *VD = dyn_cast<VarDecl>(DI)) {
       if (!isAlwaysAlive(VD))
         val.liveDecls = LV.DSetFact.remove(val.liveDecls, VD);
index bb2fdc8..936d4ed 100644 (file)
@@ -2788,7 +2788,10 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D,
 
     SVal Base = state->getLValue(DD, LCtx);
     if (DD->getType()->isReferenceType()) {
-      Base = state->getSVal(Base.getAsRegion());
+      if (const MemRegion *R = Base.getAsRegion())
+        Base = state->getSVal(R);
+      else
+        Base = UnknownVal();
     }
 
     SVal V = UnknownVal();
@@ -2809,15 +2812,27 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D,
 
       V = state->getLValue(BD->getType(), Idx, Base);
     }
-    // Handle binding to tuple-like strcutures
-    else if (BD->getHoldingVar()) {
-      // FIXME: handle tuples
-      return;
+    // Handle binding to tuple-like structures
+    else if (const auto *HV = BD->getHoldingVar()) {
+      V = state->getLValue(HV, LCtx);
+
+      if (HV->getType()->isReferenceType()) {
+        if (const MemRegion *R = V.getAsRegion())
+          V = state->getSVal(R);
+        else
+          V = UnknownVal();
+      }
     } else
       llvm_unreachable("An unknown case of structured binding encountered!");
 
-    if (BD->getType()->isReferenceType())
-      V = state->getSVal(V.getAsRegion());
+    // In case of tuple-like types the references are already handled, so we
+    // don't want to handle them again.
+    if (BD->getType()->isReferenceType() && !BD->getHoldingVar()) {
+      if (const MemRegion *R = V.getAsRegion())
+        V = state->getSVal(R);
+      else
+        V = UnknownVal();
+    }
 
     Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, V), nullptr,
                       ProgramPoint::PostLValueKind);
index afbb1b3..7660e9c 100644 (file)
@@ -115,7 +115,10 @@ void no_warning_on_tuple_types_copy(Mytuple t) {
 Mytuple getMytuple();
 
 void deconstruct_tuple_types_warning() {
-  auto [a, b] = getMytuple(); // expected-warning{{Value stored to '[a, b]' during its initialization is never read}}
+  // The initializers reference the decomposed region, so the warning is not reported
+  // FIXME: ideally we want to ignore that the initializers reference the decomposed region, and report the warning,
+  // though the first step towards that is to handle DeadCode if the initializer is CXXConstructExpr.
+  auto [a, b] = getMytuple(); // no-warning
 }
 
 int deconstruct_tuple_types_no_warning() {
diff --git a/clang/test/Analysis/uninit-structured-binding-tuple.cpp b/clang/test/Analysis/uninit-structured-binding-tuple.cpp
new file mode 100644 (file)
index 0000000..bc56444
--- /dev/null
@@ -0,0 +1,580 @@
+// RUN: %clang_analyze_cc1 -Wno-ignored-reference-qualifiers -analyzer-checker=core,debug.ExprInspection -std=c++17 -verify %s
+
+#include "Inputs/system-header-simulator-cxx.h"
+
+void clang_analyzer_eval(bool);
+
+namespace std {
+template <typename T>
+struct tuple_size {
+};
+
+template <std::size_t I, typename T>
+struct tuple_element {
+};
+
+// The std::pair in our system header simulator is not tuple-like, so a tuple-like mock is created here
+template <typename T1, typename T2>
+struct mock_pair {
+  T1 first;
+  T2 second;
+};
+template <typename T1, typename T2>
+struct tuple_size<mock_pair<T1, T2>> {
+  static const std::size_t value = 2;
+};
+
+template <typename T1, typename T2>
+struct tuple_element<0, mock_pair<T1, T2>> {
+  using type = T1;
+};
+
+template <typename T1, typename T2>
+struct tuple_element<1, mock_pair<T1, T2>> {
+  using type = T2;
+};
+
+template <std::size_t I, class T>
+using tuple_element_t = typename tuple_element<I, T>::type;
+
+template <std::size_t I, class T1, class T2>
+constexpr std::tuple_element_t<I, std::mock_pair<T1, T2>> &
+get(std::mock_pair<T1, T2> &p) noexcept {
+  if (I == 0)
+    return p.first;
+  else
+    return p.second;
+}
+
+template <std::size_t I, class T1, class T2>
+constexpr const std::tuple_element_t<I, std::mock_pair<T1, T2>> &
+get(const std::mock_pair<T1, T2> &p) noexcept {
+  if (I == 0)
+    return p.first;
+  else
+    return p.second;
+}
+
+template <std::size_t I, class T1, class T2>
+constexpr std::tuple_element_t<I, std::mock_pair<T1, T2>> &&
+get(std::mock_pair<T1, T2> &&p) noexcept {
+
+  if (I == 0)
+    return static_cast<std::tuple_element_t<I, std::mock_pair<T1, T2>> &&>(p.first);
+  else
+    return static_cast<std::tuple_element_t<I, std::mock_pair<T1, T2>> &&>(p.second);
+}
+
+template <std::size_t I, class T1, class T2>
+constexpr const std::tuple_element_t<I, std::mock_pair<T1, T2>> &&
+get(const std::mock_pair<T1, T2> &&p) noexcept {
+  if (I == 0)
+    return static_cast<std::tuple_element_t<I, std::mock_pair<T1, T2>> &&>(p.first);
+  else
+    return static_cast<std::tuple_element_t<I, std::mock_pair<T1, T2>> &&>(p.second);
+}
+
+} // namespace std
+// A utility that generates a tuple-like struct with 2 fields
+//  of the same type. The fields are 'first' and 'second'
+#define GENERATE_TUPLE_LIKE_STRUCT(name, element_type) \
+  struct name {                                        \
+    element_type first;                                \
+    element_type second;                               \
+  };                                                   \
+                                                       \
+  namespace std {                                      \
+  template <>                                          \
+  struct tuple_size<name> {                            \
+    static const std::size_t value = 2;                \
+  };                                                   \
+                                                       \
+  template <std::size_t I>                             \
+  struct tuple_element<I, name> {                      \
+    using type = element_type;                         \
+  };                                                   \
+  }
+
+void non_user_defined_by_value(void) {
+  std::mock_pair<int, int> p = {1, 2};
+
+  auto [u, v] = p;
+
+  clang_analyzer_eval(u == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(v == 2); // expected-warning{{TRUE}}
+
+  int x = u;
+  u = 10;
+  int y = u;
+
+  clang_analyzer_eval(x == 1);  // expected-warning{{TRUE}}
+  clang_analyzer_eval(u == 10); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(y == 10);      // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.first == 1); // expected-warning{{TRUE}}
+
+  p.first = 5;
+
+  clang_analyzer_eval(u == 10); // expected-warning{{TRUE}}
+}
+
+void non_user_defined_by_lref(void) {
+  std::mock_pair<int, int> p = {1, 2};
+
+  auto &[u, v] = p;
+
+  int x = u;
+  u = 10;
+  int y = u;
+
+  clang_analyzer_eval(x == 1);  // expected-warning{{TRUE}}
+  clang_analyzer_eval(u == 10); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(y == 10);       // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.first == 10); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(v == 2);        // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.second == 2); // expected-warning{{TRUE}}
+
+  p.first = 5;
+
+  clang_analyzer_eval(u == 5); // expected-warning{{TRUE}}
+}
+
+void non_user_defined_by_rref(void) {
+  std::mock_pair<int, int> p = {1, 2};
+
+  auto &&[u, v] = p;
+
+  int x = u;
+  u = 10;
+  int y = u;
+
+  clang_analyzer_eval(x == 1);  // expected-warning{{TRUE}}
+  clang_analyzer_eval(u == 10); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(y == 10);       // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.first == 10); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(v == 2);        // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.second == 2); // expected-warning{{TRUE}}
+
+  p.first = 5;
+
+  clang_analyzer_eval(u == 5); // expected-warning{{TRUE}}
+}
+
+GENERATE_TUPLE_LIKE_STRUCT(Test, int);
+
+template <std::size_t I>
+int get(Test t) {
+  if (I == 0) {
+    t.second = 10;
+    return t.first;
+  } else {
+    t.first = 20;
+    return t.second;
+  }
+}
+
+void user_defined_get_val_by_val(void) {
+  Test p{1, 2};
+  auto [u, v] = p;
+
+  clang_analyzer_eval(u == 1); // expected-warning{{TRUE}}
+
+  u = 8;
+
+  int x = u;
+
+  clang_analyzer_eval(x == 8); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(u == 8); // expected-warning{{TRUE}}
+  clang_analyzer_eval(v == 2); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(p.first == 1);  // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.second == 2); // expected-warning{{TRUE}}
+
+  p.first = 5;
+
+  clang_analyzer_eval(u == 8);       // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.first == 5); // expected-warning{{TRUE}}
+}
+
+GENERATE_TUPLE_LIKE_STRUCT(Test2, int);
+
+template <std::size_t I>
+int get(Test2 &t) {
+  if (I == 0) {
+    t.second = 10;
+    return t.first;
+  } else {
+    t.first = 20;
+    return t.second;
+  }
+}
+
+void user_defined_get_val_by_lref(void) {
+  Test2 p{1, 2};
+
+  auto &[u, v] = p;
+
+  clang_analyzer_eval(u == 1);  // expected-warning{{TRUE}}
+  clang_analyzer_eval(v == 10); // expected-warning{{TRUE}}
+
+  u = 8;
+
+  int x = u;
+
+  clang_analyzer_eval(x == 8); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(u == 8);  // expected-warning{{TRUE}}
+  clang_analyzer_eval(v == 10); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(p.first == 20);  // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.second == 10); // expected-warning{{TRUE}}
+
+  p.first = 5;
+
+  clang_analyzer_eval(u == 8);       // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.first == 5); // expected-warning{{TRUE}}
+}
+
+void user_defined_get_val_by_rref(void) {
+  Test2 p{1, 2};
+
+  auto &&[u, v] = p;
+
+  clang_analyzer_eval(u == 1);  // expected-warning{{TRUE}}
+  clang_analyzer_eval(v == 10); // expected-warning{{TRUE}}
+
+  u = 8;
+
+  int x = u;
+
+  clang_analyzer_eval(x == 8); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(u == 8);  // expected-warning{{TRUE}}
+  clang_analyzer_eval(v == 10); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(p.first == 20);  // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.second == 10); // expected-warning{{TRUE}}
+
+  p.first = 5;
+
+  clang_analyzer_eval(u == 8);       // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.first == 5); // expected-warning{{TRUE}}
+}
+
+struct MixedTest {
+  int x;
+  char &&y;
+  int &z;
+};
+
+namespace std {
+template <>
+struct tuple_size<MixedTest> {
+  static const std::size_t value = 3;
+};
+
+template <>
+struct tuple_element<0, MixedTest> {
+  using type = int;
+};
+
+template <>
+struct tuple_element<1, MixedTest> {
+  using type = char &&;
+};
+
+template <>
+struct tuple_element<2, MixedTest> {
+  using type = int &;
+};
+
+template <std::size_t I, typename T>
+using tuple_element_t = typename tuple_element<I, T>::type;
+
+} // namespace std
+
+template <std::size_t I>
+const std::tuple_element_t<I, MixedTest> &get(const MixedTest &t) {}
+
+template <>
+const std::tuple_element_t<0, MixedTest> &get<0>(const MixedTest &t) {
+  return t.x;
+}
+
+template <>
+const std::tuple_element_t<1, MixedTest> &get<1>(const MixedTest &t) {
+  return t.y;
+}
+
+template <>
+const std::tuple_element_t<2, MixedTest> &get<2>(const MixedTest &t) {
+  return t.z;
+}
+
+void mixed_type_cref(void) {
+  int x = 1;
+  char y = 2;
+  int z = 3;
+
+  MixedTest m{x, std::move(y), z};
+  const auto &[a, b, c] = m;
+
+  clang_analyzer_eval(a == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b == 2); // expected-warning{{TRUE}}
+  clang_analyzer_eval(c == 3); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(a == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b == 2); // expected-warning{{TRUE}}
+  clang_analyzer_eval(c == 3); // expected-warning{{TRUE}}
+}
+
+template <std::size_t I>
+std::tuple_element_t<I, MixedTest> &get(MixedTest &t) {}
+
+template <>
+std::tuple_element_t<0, MixedTest> &get<0>(MixedTest &t) {
+  return t.x;
+}
+
+template <>
+std::tuple_element_t<1, MixedTest> &get<1>(MixedTest &t) {
+  return t.y;
+}
+
+template <>
+std::tuple_element_t<2, MixedTest> &get<2>(MixedTest &t) {
+  return t.z;
+}
+
+void mixed_type_lref(void) {
+  int x = 1;
+  char y = 2;
+  int z = 3;
+
+  MixedTest m{x, std::move(y), z};
+  auto &[a, b, c] = m;
+
+  a = 4;
+  b = 5;
+  c = 6;
+
+  clang_analyzer_eval(get<0>(m) == 4); // expected-warning{{TRUE}}
+  clang_analyzer_eval(get<1>(m) == 5); // expected-warning{{TRUE}}
+  clang_analyzer_eval(get<2>(m) == 6); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(get<0>(m) == 4); // expected-warning{{TRUE}}
+  clang_analyzer_eval(get<1>(m) == 5); // expected-warning{{TRUE}}
+  clang_analyzer_eval(get<2>(m) == 6); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(z == 6); // expected-warning{{TRUE}}
+}
+
+void mixed_type_rref(void) {
+  int x = 1;
+  char y = 2;
+  int z = 3;
+
+  MixedTest m{x, std::move(y), z};
+  auto &&[a, b, c] = m;
+
+  a = 4;
+  b = 5;
+  c = 6;
+
+  clang_analyzer_eval(get<0>(m) == 4); // expected-warning{{TRUE}}
+  clang_analyzer_eval(get<1>(m) == 5); // expected-warning{{TRUE}}
+  clang_analyzer_eval(get<2>(m) == 6); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(get<0>(m) == 4); // expected-warning{{TRUE}}
+  clang_analyzer_eval(get<1>(m) == 5); // expected-warning{{TRUE}}
+  clang_analyzer_eval(get<2>(m) == 6); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(z == 6); // expected-warning{{TRUE}}
+}
+
+void ref_val(void) {
+  int i = 1, j = 2;
+  std::mock_pair<int &, int &> p{i, j};
+
+  auto [a, b] = p;
+  clang_analyzer_eval(a == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b == 2); // expected-warning{{TRUE}}
+
+  a = 3;
+  b = 4;
+
+  clang_analyzer_eval(p.first == 3);  // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.second == 4); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(a == 3); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b == 4); // expected-warning{{TRUE}}
+}
+
+struct Small_Non_POD {
+  int i;
+  int j;
+};
+
+void non_user_defined_small_non_pod_by_value(void) {
+  std::mock_pair<Small_Non_POD, Small_Non_POD> p{{1, 2}, {1, 2}};
+
+  auto [a, b] = p;
+
+  clang_analyzer_eval(a.i == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(a.j == 2); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(b.i == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b.j == 2); // expected-warning{{TRUE}}
+
+  a.i = 3;
+  a.j = 4;
+
+  b.i = 5;
+  b.j = 6;
+
+  clang_analyzer_eval(a.i == 3); // expected-warning{{TRUE}}
+  clang_analyzer_eval(a.j == 4); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(b.i == 5); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b.j == 6); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(p.first.i == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.first.j == 2); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(p.second.i == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.second.j == 2); // expected-warning{{TRUE}}
+}
+
+void non_user_defined_small_non_pod_by_lref(void) {
+  std::mock_pair<Small_Non_POD, Small_Non_POD> p{{1, 2}, {1, 2}};
+
+  auto &[a, b] = p;
+
+  clang_analyzer_eval(a.i == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(a.j == 2); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(b.i == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b.j == 2); // expected-warning{{TRUE}}
+
+  a.i = 3;
+  a.j = 4;
+
+  b.i = 5;
+  b.j = 6;
+
+  clang_analyzer_eval(a.i == 3); // expected-warning{{TRUE}}
+  clang_analyzer_eval(a.j == 4); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(b.i == 5); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b.j == 6); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(p.first.i == 3); // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.first.j == 4); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(p.second.i == 5); // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.second.j == 6); // expected-warning{{TRUE}}
+}
+
+void non_user_defined_small_non_pod_by_rref(void) {
+  std::mock_pair<Small_Non_POD, Small_Non_POD> p{{1, 2}, {1, 2}};
+
+  auto &&[a, b] = p;
+
+  clang_analyzer_eval(a.i == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(a.j == 2); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(b.i == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b.j == 2); // expected-warning{{TRUE}}
+
+  a.i = 3;
+  a.j = 4;
+
+  b.i = 5;
+  b.j = 6;
+
+  clang_analyzer_eval(a.i == 3); // expected-warning{{TRUE}}
+  clang_analyzer_eval(a.j == 4); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(b.i == 5); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b.j == 6); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(p.first.i == 3); // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.first.j == 4); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(p.second.i == 5); // expected-warning{{TRUE}}
+  clang_analyzer_eval(p.second.j == 6); // expected-warning{{TRUE}}
+}
+
+GENERATE_TUPLE_LIKE_STRUCT(Uninit, int);
+template <std::size_t I>
+int &get(Uninit &&t) {
+  if (I == 0) {
+    return t.first;
+  } else {
+    return t.second;
+  }
+}
+
+void uninit_a(void) {
+  Uninit u;
+
+  auto [a, b] = u;
+
+  int x = a; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+void uninit_b(void) {
+  Uninit u;
+
+  auto [a, b] = u;
+
+  int x = b; // expected-warning{{Assigned value is garbage or undefined}}
+}
+
+GENERATE_TUPLE_LIKE_STRUCT(UninitCall, int);
+template <std::size_t I>
+int get(UninitCall t) {
+  if (I == 0) {
+    return t.first;
+  } else {
+    return t.second;
+  }
+}
+
+void uninit_call(void) {
+  UninitCall u;
+
+  auto [a, b] = u;
+
+  int x = a;
+  // expected-warning@543{{Undefined or garbage value returned to caller}}
+}
+
+void syntax_2() {
+  std::mock_pair<Small_Non_POD, Small_Non_POD> p{{1, 2}, {3, 4}};
+
+  auto [a, b]{p};
+
+  clang_analyzer_eval(a.i == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(a.j == 2); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(b.i == 3); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b.j == 4); // expected-warning{{TRUE}}
+}
+
+void syntax_3() {
+  std::mock_pair<Small_Non_POD, Small_Non_POD> p{{1, 2}, {3, 4}};
+
+  auto [a, b](p);
+
+  clang_analyzer_eval(a.i == 1); // expected-warning{{TRUE}}
+  clang_analyzer_eval(a.j == 2); // expected-warning{{TRUE}}
+
+  clang_analyzer_eval(b.i == 3); // expected-warning{{TRUE}}
+  clang_analyzer_eval(b.j == 4); // expected-warning{{TRUE}}
+}