Index: clang/lib/Analysis/CFG.cpp =================================================================== --- clang/lib/Analysis/CFG.cpp +++ clang/lib/Analysis/CFG.cpp @@ -2924,6 +2924,13 @@ } } + if (const auto *DD = dyn_cast(VD)) { + for (auto BD : llvm::reverse(DD->bindings())) { + if (auto *VD = BD->getHoldingVar()) + Visit(VD->getInit()); + } + } + autoCreateBlock(); appendStmt(Block, DS); Index: clang/lib/Analysis/LiveVariables.cpp =================================================================== --- clang/lib/Analysis/LiveVariables.cpp +++ clang/lib/Analysis/LiveVariables.cpp @@ -72,6 +72,11 @@ 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); @@ -371,8 +376,31 @@ const Decl* D = DR->getDecl(); bool InAssignment = LV.inAssignment[DR]; if (const auto *BD = dyn_cast(D)) { - if (!InAssignment) + if (!InAssignment) { + if (const auto *HV = BD->getHoldingVar()) { + // The get<>() call is the one that's present in the environment, so we + // want to keep that alive. + // + // If the mentioned function returns by value, the CallExpr is wrapped + // in 2 additional nodes, and we need the `MaterializeTemporaryExpr`, so + // extract it. + // + // `-BindingDecl + // |-VarDecl + // | `-ExprWithCleanups + // | `-MaterializeTemporaryExpr + // | `-CallExpr + + const auto *Init = HV->getInit(); + if (const auto *EWC = dyn_cast(Init)) + Init = EWC->getSubExpr(); + + val.liveExprs = LV.ESetFact.add(val.liveExprs, Init); + val.liveDecls = LV.DSetFact.add(val.liveDecls, HV); + } + val.liveBindings = LV.BSetFact.add(val.liveBindings, BD); + } } else if (const auto *VD = dyn_cast(D)) { if (!InAssignment && !isAlwaysAlive(VD)) val.liveDecls = LV.DSetFact.add(val.liveDecls, VD); @@ -382,8 +410,31 @@ void TransferFunctions::VisitDeclStmt(DeclStmt *DS) { for (const auto *DI : DS->decls()) { if (const auto *DD = dyn_cast(DI)) { - for (const auto *BD : DD->bindings()) + for (const auto *BD : DD->bindings()) { + if (const auto *HV = BD->getHoldingVar()) { + // The get<>() call is the one that's present in the environment, so + // that's what we want to remove. + // + // If the mentioned function returns by value, the CallExpr is wrapped + // in 2 additional nodes, and we need the `MaterializeTemporaryExpr`, + // so extract it. + // + // `-BindingDecl + // |-VarDecl + // | `-ExprWithCleanups + // | `-MaterializeTemporaryExpr + // | `-CallExpr + + const auto *Init = HV->getInit(); + if (const auto *EWC = dyn_cast(Init)) + Init = EWC->getSubExpr(); + + val.liveExprs = LV.ESetFact.remove(val.liveExprs, Init); + val.liveDecls = LV.DSetFact.remove(val.liveDecls, HV); + } + val.liveBindings = LV.BSetFact.remove(val.liveBindings, BD); + } } else if (const auto *VD = dyn_cast(DI)) { if (!isAlwaysAlive(VD)) val.liveDecls = LV.DSetFact.remove(val.liveDecls, VD); Index: clang/lib/StaticAnalyzer/Core/ExprEngine.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -2599,9 +2599,8 @@ const auto *DD = cast(BD->getDecomposedDecl()); SVal Base = state->getLValue(DD, LCtx); - if (DD->getType()->isReferenceType()) { + if (DD->getType()->isReferenceType()) Base = state->getSVal(Base.getAsRegion()); - } SVal V = UnknownVal(); @@ -2621,14 +2620,31 @@ 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()) { + + // Try to read the holding var from the store + V = state->getLValue(HV, LCtx); + + // Peek the contained val, if it is Unknown, this might be the first time + // we encounter this BindingDecl, so we attempt to create the HoldingVar + // lazyly + if (state->getSVal(V.getAsRegion()).isUnknownOrUndef()) { + auto RV = state->getSVal(HV->getInit(), LCtx); + auto LV = state->getLValue(HV, LCtx); + + state = state->bindLoc(LV, RV, LCtx); + } + + if (HV->getType()->isReferenceType()) + V = state->getSVal(V.getAsRegion()); + } else llvm_unreachable("An unknown case of structured binding encountered!"); - if (BD->getType()->isReferenceType()) + // In case of tuple-like types the references are already handled, so we + // don't want to handle it again. + if (BD->getType()->isReferenceType() && !BD->getHoldingVar()) V = state->getSVal(V.getAsRegion()); Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, V), nullptr, Index: clang/test/Analysis/live-bindings-test.cpp =================================================================== --- clang/test/Analysis/live-bindings-test.cpp +++ clang/test/Analysis/live-bindings-test.cpp @@ -115,7 +115,8 @@ 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 + auto [a, b] = getMytuple(); // no-warning } int deconstruct_tuple_types_no_warning() { Index: clang/test/Analysis/uninit-structured-binding-tuple.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/uninit-structured-binding-tuple.cpp @@ -0,0 +1,359 @@ +// 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); + +void non_user_defined_by_value(void) { + std::pair 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::pair 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::pair 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}} +} + +struct Test { + int u = 1; + int v = 2; +}; + +namespace std { +template struct tuple_size {}; +template <> +struct tuple_size { + static const std::size_t value = 2; +}; + +template struct tuple_element {}; +template +struct tuple_element { + using type = int; +}; + +} // namespace std + +template +int get(Test t) { + if (I == 0) { + t.v = 10; + return t.u; + } else { + t.u = 20; + return t.v; + } +} + +void user_defined_get_val_by_val(void) { + Test p; + 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.u == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(p.v == 2); // expected-warning{{TRUE}} + + p.u = 5; + + clang_analyzer_eval(u == 8); // expected-warning{{TRUE}} + clang_analyzer_eval(p.u == 5); // expected-warning{{TRUE}} +} + +struct Test2 { + int u = 1; + int v = 2; +}; + +namespace std { +template <> +struct tuple_size { + static const std::size_t value = 2; +}; + +template +struct tuple_element { + using type = int; +}; + +} // namespace std + +template +int get(Test2 &t) { + if (I == 0) { + t.v = 10; + return t.u; + } else { + t.u = 20; + return t.v; + } +} + +void user_defined_get_val_by_lref(void) { + Test2 p; + + 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.u == 20); // expected-warning{{TRUE}} + clang_analyzer_eval(p.v == 10); // expected-warning{{TRUE}} + + p.u = 5; + + clang_analyzer_eval(u == 8); // expected-warning{{TRUE}} + clang_analyzer_eval(p.u == 5); // expected-warning{{TRUE}} +} + +void user_defined_get_val_by_rref(void) { + Test2 p; + + 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.u == 20); // expected-warning{{TRUE}} + clang_analyzer_eval(p.v == 10); // expected-warning{{TRUE}} + + p.u = 5; + + clang_analyzer_eval(u == 8); // expected-warning{{TRUE}} + clang_analyzer_eval(p.u == 5); // expected-warning{{TRUE}} +} + +struct Mixed_test { + int x; + char &&y; + int &z; +}; + +namespace std { +template <> +struct tuple_size { + static const std::size_t value = 3; +}; + +template <> +struct tuple_element<0, Mixed_test> { + using type = int; +}; + +template <> +struct tuple_element<1, Mixed_test> { + using type = char &&; +}; + +template <> +struct tuple_element<2, Mixed_test> { + using type = int &; +}; + +template +using tuple_element_t = typename tuple_element::type; + +} // namespace std + +template +const std::tuple_element_t &get(const Mixed_test &t) {} + +template <> +const std::tuple_element_t<0, Mixed_test> &get<0>(const Mixed_test &t) { + return t.x; +} + +template <> +const std::tuple_element_t<1, Mixed_test> &get<1>(const Mixed_test &t) { + return t.y; +} + +template <> +const std::tuple_element_t<2, Mixed_test> &get<2>(const Mixed_test &t) { + return t.z; +} + +void mixed_type_cref(void) { + int x = 1; + char y = 2; + int z = 3; + + Mixed_test 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::tuple_element_t &get(Mixed_test &t) {} + +template <> +std::tuple_element_t<0, Mixed_test> &get<0>(Mixed_test &t) { + return t.x; +} + +template <> +std::tuple_element_t<1, Mixed_test> &get<1>(Mixed_test &t) { + return t.y; +} + +template <> +std::tuple_element_t<2, Mixed_test> &get<2>(Mixed_test &t) { + return t.z; +} + +void mixed_type_lref(void) { + int x = 1; + char y = 2; + int z = 3; + + Mixed_test 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; + + Mixed_test 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::pair 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}} +}