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 @@ -371,8 +371,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 +405,33 @@ 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); + } + + 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 @@ -2599,7 +2599,7 @@ const auto *DD = cast(BD->getDecomposedDecl()); SVal Base = state->getLValue(DD, LCtx); - if (DD->getType()->isReferenceType()) { + if (DD->getType()->isReferenceType() && !BD->getHoldingVar()) { Base = state->getSVal(Base.getAsRegion()); } @@ -2622,9 +2622,24 @@ 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()) { + + // 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!"); Index: clang/test/Analysis/live-bindings-test.cpp =================================================================== --- clang/test/Analysis/live-bindings-test.cpp +++ clang/test/Analysis/live-bindings-test.cpp @@ -109,7 +109,9 @@ } void no_warning_on_tuple_types_copy(Mytuple t) { - auto [a, b] = t; // no-warning + // Mytuple doesn't initialize it's fields + auto [a, b] = t; + // expected-warning@95{{Undefined or garbage value returned to caller}} } Mytuple getMytuple(); Index: clang/test/Analysis/uninit-structured-binding-tuple.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/uninit-structured-binding-tuple.cpp @@ -0,0 +1,143 @@ +// 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 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}} +} + +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}} +} + +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}} +} + +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}} +}