Index: clang/lib/Analysis/CFG.cpp =================================================================== --- clang/lib/Analysis/CFG.cpp +++ clang/lib/Analysis/CFG.cpp @@ -2927,6 +2927,26 @@ autoCreateBlock(); appendStmt(Block, DS); + // Visit the binding initializers before the DS initializer, so they + // appear after it in the CFG. The binding initializer will reference the + // new object, created after the the decomposition, so it has to appear + // later in the CFG. + if (const auto *DD = dyn_cast(VD)) { + for (auto BD : llvm::reverse(DD->bindings())) { + if (auto *VD = BD->getHoldingVar()) { + // HACK: If the get<>() function returns by value, we materialize a + // temporary expression, + // however we want't to skip that in this case, and handle it + // ourselves. + if (const auto *EWC = dyn_cast_or_null(VD->getInit())) + Visit( + cast(EWC->getSubExpr())->getSubExpr()); + else + Visit(VD->getInit()); + } + } + } + findConstructionContexts( ConstructionContextLayer::create(cfg->getBumpVectorContext(), DS), Init); Index: clang/lib/Analysis/LiveVariables.cpp =================================================================== --- clang/lib/Analysis/LiveVariables.cpp +++ clang/lib/Analysis/LiveVariables.cpp @@ -72,6 +72,8 @@ bool alive = false; for (const BindingDecl *BD : DD->bindings()) alive |= liveBindings.contains(BD); + + alive |= liveDecls.contains(DD); return alive; } return liveDecls.contains(D); @@ -371,8 +373,12 @@ 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()) + val.liveExprs = LV.ESetFact.add(val.liveExprs, HV->getInit()); + 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 +388,24 @@ 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()) { + + // HACK: keep the expr alive, so that we can read the values when the + // new object is declared + if (const auto *HV = BD->getHoldingVar()) { + if (const auto *EWC = + dyn_cast_or_null(HV->getInit())) + val.liveExprs = LV.ESetFact.add( + val.liveExprs, cast(EWC->getSubExpr()) + ->getSubExpr()); + else + val.liveExprs = LV.ESetFact.add(val.liveExprs, HV->getInit()); + } + val.liveBindings = LV.BSetFact.remove(val.liveBindings, BD); + } + + val.liveDecls = LV.DSetFact.remove(val.liveDecls, DD); } 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 @@ -2622,9 +2622,18 @@ V = state->getLValue(BD->getType(), Idx, Base); } // Handle binding to tuple-like strcutures - else if (BD->getHoldingVar()) { - // FIXME: handle tuples - return; + else if (const auto *HV = BD->getHoldingVar()) { + unsigned idx = 0; + for (const auto BDI : DD->bindings()) { + if (BDI == BD) { + V = state->getLValue(BD->getType(), svalBuilder.makeArrayIndex(idx), + Base); + break; + } + + ++idx; + } + } else llvm_unreachable("An unknown case of structured binding encountered!"); Index: clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -611,6 +611,38 @@ SVal InitVal = state->getSVal(InitEx, LC); assert(DS->isSingleDecl()); + + unsigned idx = 0; + if (const auto *DD = dyn_cast(VD)) { + for (auto BD : DD->bindings()) { + if (!BD->getHoldingVar()) + break; + + SVal Base = state->getLValue(DD, LC); + Base = state->getLValue(BD->getType(), + svalBuilder.makeArrayIndex(idx++), Base); + + const auto *V = BD->getHoldingVar(); + auto *Init = V->getInit(); + + SVal RetVal = UnknownVal(); + + // This check is for whether get<>() returns by value + if (const auto *EWC = dyn_cast_or_null(Init)) { + Init = + cast(EWC->getSubExpr())->getSubExpr(); + RetVal = state->getSVal(Init, LC); + } + // If the above condition is false, get<>() will return by reference + else { + RetVal = state->getSVal(Init, LC); + RetVal = state->getSVal(RetVal.getAsRegion()); + } + + state = state->bindLoc(Base, RetVal, LC); + } + } + if (getObjectUnderConstruction(state, DS, LC)) { state = finishObjectConstruction(state, DS, LC); // We constructed the object directly in the variable. Index: clang/test/Analysis/live-bindings-test.cpp =================================================================== --- clang/test/Analysis/live-bindings-test.cpp +++ clang/test/Analysis/live-bindings-test.cpp @@ -115,6 +115,7 @@ Mytuple getMytuple(); void deconstruct_tuple_types_warning() { + auto [a, b] = getMytuple(); // expected-warning{{Value stored to '[a, b]' during its initialization is never read}} } Index: clang/test/Analysis/uninit-structured-binding-tuple.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/uninit-structured-binding-tuple.cpp @@ -0,0 +1,119 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 -verify %s + +#include "Inputs/system-header-simulator-cxx.h" + +void clang_analyzer_eval(bool); + +void a(void) { + std::pair p = {1, 2}; + + auto [u, v] = p; + + int x = u; + int y = v; + + clang_analyzer_eval(x == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(y == 2); // 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 b(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}} +} + +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 c(void) { + Test2 p; + + // FIXME: The temporary is removed from the store, even thoug it is still supposed to be alive + // This test fails because of it + 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}} +}