Index: clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -1410,6 +1410,60 @@ } } +static bool isTrivialCopyOrMoveCtor(const CXXConstructExpr *CE) { + if (!CE) + return false; + + const auto *CtorDecl = CE->getConstructor(); + + return CtorDecl->isCopyOrMoveConstructor() && CtorDecl->isTrivial(); +} + +// Assuming we have a region 's.s2.p2' and an initializer list like +// InitListExpr 'S':'struct S' +//`-InitListExpr 'S2':'struct S2' +// |-ImplicitCastExpr 'int *' +// | `-DeclRefExpr 'int *' lvalue Var 'x' 'int *' +// `-ImplicitCastExpr 'int *' +// `-DeclRefExpr 'int *' lvalue Var 'y' 'int *' +// +// We first move up one region at a time until we can match +// the top level region ('s') with the top level initializer +// list (InitListExpr 'S':'struct S'). From that we move back +// down towards the deepest initializers, always choosing the +// initializer that corresponds to the field index of the region. +static const Expr *tryExtractInitializerFromList(const Expr *E, + const MemRegion *R) { + if (!isa(R) || !isa(E)) + return nullptr; + + const auto *FR = R->getAs(); + const auto *FD = FR->getDecl(); + const auto *ILE = cast(E); + + // If we can match the region with the current initializer, return the + // initializer. + if (ILE->getType()->getUnqualifiedDesugaredType() == + FD->getParent()->getTypeForDecl() && + FD->getFieldIndex() < ILE->getNumInits()) + return ILE->getInit(FD->getFieldIndex()); + + // Try to find a point, where we can match the initializer with a super + // region. + const auto *match = tryExtractInitializerFromList(ILE, FR->getSuperRegion()); + ILE = dyn_cast_or_null(match); + + // We probably have the initializer, or an error happened. + if (!ILE) + return match; + + // We can't match the current field with the initializer list. + if (FD->getFieldIndex() >= ILE->getNumInits()) + return nullptr; + + return ILE->getInit(FD->getFieldIndex()); +} + PathDiagnosticPieceRef StoreSiteFinder::VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, PathSensitiveBugReport &BR) { @@ -1456,12 +1510,61 @@ StoreSite = Succ; - // If this is an assignment expression, we can track the value - // being assigned. - if (Optional P = Succ->getLocationAs()) - if (const BinaryOperator *BO = P->getStmtAs()) + if (Optional P = Succ->getLocationAs()) { + // If this is an assignment expression, we can track the value + // being assigned. + if (const BinaryOperator *BO = P->getStmtAs()) { if (BO->isAssignmentOp()) InitE = BO->getRHS(); + } + // If we have a declaration like 'S s{1,2}' that needs special + // handling, we handle it here. + else if (const auto *DS = P->getStmtAs()) { + const auto *Decl = DS->getSingleDecl(); + if (isa(R) && isa(Decl)) { + const auto *VD = cast(Decl); + + // FIXME: Here we only track the top level region, so we lose + // information, but it's still better than a crash or no information + // at all. + // + // E.g.: The region we have is 's.s2.s3.s4.y' and we only track 'y', + // and throw away the rest. + if (const auto *ILE = dyn_cast(VD->getInit())) + InitE = tryExtractInitializerFromList(ILE, R); + } + } else if (const auto *CE = P->getStmtAs()) { + + const auto State = Succ->getState(); + + if (isTrivialCopyOrMoveCtor(CE) && isa(R)) { + const auto *FR = cast(R); + const auto *FD = FR->getDecl(); + + // Move the current field region to the object the we copied/moved + // from. If we track 'a.y' and encounter 'S a = b', then get the + // region 'b.y' and track it. + + const auto *OriginEx = CE->getArg(0); + const auto OriginVal = + State->getSVal(OriginEx, Succ->getLocationContext()); + const auto OriginField = State->getLValue(FD, OriginVal); + + getParentTracker().track(V, OriginField.getAsRegion(), Options); + InitE = OriginEx; + } + } + // This branch can occur in cases like `Ctor() : field{ x, y } {}'. + else if (const auto *ILE = P->getStmtAs()) { + // FIXME: Here we only track the top level region, so we lose + // information, but it's still better than a crash or no information + // at all. + // + // E.g.: The region we have is 's.s2.s3.s4.y' and we only track 'y', and + // throw away the rest. + InitE = tryExtractInitializerFromList(ILE, R); + } + } // If this is a call entry, the variable should be a parameter. // FIXME: Handle CXXThisRegion as well. (This is not a priority because @@ -2395,6 +2498,29 @@ if (!RVNode) return {}; + Tracker::Result CombinedResult; + Tracker &Parent = getParentTracker(); + + const auto track = [&CombinedResult, &Parent, ExprNode, + Opts](const Expr *Inner) { + CombinedResult.combineWith(Parent.track(Inner, ExprNode, Opts)); + }; + + // FIXME: Initializer lists can appear in many different contexts + // and most of them needs a special handling. For now let's handle + // what we can. If the initializer list only has 1 element, we track + // that. + // This snippet even handles nesting, e.g.: int *x{{{{{y}}}}}; + if (const auto *ILE = dyn_cast(E)) { + if (ILE->getNumInits() == 1) { + track(ILE->getInit(0)); + + return CombinedResult; + } + + return {}; + } + ProgramStateRef RVState = RVNode->getState(); SVal V = RVState->getSValAsScalarOrLoc(E, RVNode->getLocationContext()); const auto *BO = dyn_cast(E); @@ -2406,13 +2532,6 @@ SVal LHSV = RVState->getSVal(BO->getLHS(), RVNode->getLocationContext()); // Track both LHS and RHS of a multiplication. - Tracker::Result CombinedResult; - Tracker &Parent = getParentTracker(); - - const auto track = [&CombinedResult, &Parent, ExprNode, Opts](Expr *Inner) { - CombinedResult.combineWith(Parent.track(Inner, ExprNode, Opts)); - }; - if (BO->getOpcode() == BO_Mul) { if (LHSV.isZeroConstant()) track(BO->getLHS()); Index: clang/test/Analysis/ctor-bug-path.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/ctor-bug-path.cpp @@ -0,0 +1,210 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-output=text -std=c++11 -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-output=text -std=c++17 -verify %s + +#include "Inputs/system-header-simulator-cxx.h" + +namespace copyMoveTrackCtor { +struct S { + int *p1, *p2; + S(int *a, int *b) : p1(a), p2(b) {} // expected-note{{Null pointer value stored to 's.p1'}} +}; + +void CtorDirect() { + int *x = nullptr, *y = nullptr; + // expected-note@-1{{'x' initialized to a null pointer value}} + + S s(x, y); + // expected-note@-1{{Passing null pointer value via 1st parameter 'a'}} + // expected-note@-2{{Calling constructor for 'S'}} + // expected-note@-3{{Returning from constructor for 'S'}} + // expected-note@-4{{'s' initialized here}} + S s2 = s; // expected-note{{Null pointer value stored to 's2.p1'}} + // expected-note@-1{{'s2' initialized here}} + S s3 = s2; // expected-note{{Null pointer value stored to 's3.p1'}} + // expected-note@-1{{'s3' initialized here}} + S s4 = std::move(s3); // expected-note{{Null pointer value stored to 's4.p1'}} + // expected-note@-1{{'s4' initialized here}} + S s5 = s4; // expected-note{{Null pointer value stored to 's5.p1'}} + + int i = *s5.p1; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer (loaded from field 'p1')}} + + (void) i; +} +} // namespace copyMoveTrackCtor + +namespace copyMoveTrackInitList { +struct S { + int *p1, *p2; +}; + +void InitListDirect() { + int *x = nullptr, *y = nullptr; //expected-note{{'x' initialized to a null pointer value}} + + S s{x, y}; //expected-note{{'s.p1' initialized to a null pointer value}} + //expected-note@-1{{'s' initialized here}} + S s2 = s; // expected-note{{Null pointer value stored to 's2.p1'}} + // expected-note@-1{{'s2' initialized here}} + S s3 = s2; // expected-note{{Null pointer value stored to 's3.p1'}} + // expected-note@-1{{'s3' initialized here}} + S s4 = std::move(s3); // expected-note{{Null pointer value stored to 's4.p1'}} + // expected-note@-1{{'s4' initialized here}} + S s5 = s4; // expected-note{{Null pointer value stored to 's5.p1'}} + + int i = *s5.p1; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer (loaded from field 'p1')}} + + (void) i; +} + +void InitListAssign() { + int *x = nullptr, *y = nullptr; //expected-note{{'x' initialized to a null pointer value}} + + S s = {x, y}; //expected-note{{'s.p1' initialized to a null pointer value}} + //expected-note@-1{{'s' initialized here}} + S s2 = s; // expected-note{{Null pointer value stored to 's2.p1'}} + // expected-note@-1{{'s2' initialized here}} + S s3 = s2; // expected-note{{Null pointer value stored to 's3.p1'}} + // expected-note@-1{{'s3' initialized here}} + S s4 = std::move(s3); // expected-note{{Null pointer value stored to 's4.p1'}} + // expected-note@-1{{'s4' initialized here}} + S s5 = s4; // expected-note{{Null pointer value stored to 's5.p1'}} + + int i = *s5.p1; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer (loaded from field 'p1')}} + + (void) i; +} + +} // namespace copyMoveTrackInitList + +namespace copyMoveTrackCtorMemberInitList { +struct S { + int *p1, *p2; + S(int *a, int *b) : p1{a}, p2{b} {} // expected-note{{Null pointer value stored to 's.p1'}} +}; + +void CtorDirect() { + int *x = nullptr, *y = nullptr; + // expected-note@-1{{'x' initialized to a null pointer value}} + + S s{x, y}; + // expected-note@-1{{Passing null pointer value via 1st parameter 'a'}} + // expected-note@-2{{Calling constructor for 'S'}} + // expected-note@-3{{Returning from constructor for 'S'}} + // expected-note@-4{{'s' initialized here}} + S s2 = s; // expected-note{{Null pointer value stored to 's2.p1'}} + // expected-note@-1{{'s2' initialized here}} + S s3 = s2; // expected-note{{Null pointer value stored to 's3.p1'}} + // expected-note@-1{{'s3' initialized here}} + S s4 = std::move(s3); // expected-note{{Null pointer value stored to 's4.p1'}} + // expected-note@-1{{'s4' initialized here}} + S s5 = s4; // expected-note{{Null pointer value stored to 's5.p1'}} + + int i = *s5.p1; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer (loaded from field 'p1')}} + + (void) i; +} +} // namespace copyMoveTrackCtorMemberInitList + +namespace directInitList { +struct S { + int *p1, *p2; +}; + +void InitListDirect() { + int *x = nullptr, *y = nullptr; //expected-note{{'y' initialized to a null pointer value}} + + S s{x, y}; //expected-note{{'s.p2' initialized to a null pointer value}} + + int i = *s.p2; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} + (void) i; +} +} // namespace directInitList + +namespace directNestedInitList { +struct S2 { + int *p1, *p2; +}; + +struct S { + S2 s; +}; + +void InitListNestedDirect() { + int *x = nullptr, *y = nullptr; //expected-note{{'y' initialized to a null pointer value}} + + //FIXME: Put more information to the notes. + S s{x, y}; //expected-note{{'s.s.p2' initialized to a null pointer value}} + + int i = *s.s.p2; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} + (void) i; +} +} // namespace directNestedInitList + +#if __cplusplus >= 201703L + +namespace structuredBinding { +struct S { + int *p1, *p2; +}; + +void StructuredBinding() { + int *x = nullptr, *y = nullptr; + //expected-note@-1{{'y' initialized to a null pointer value}} + + S s{x, y}; + //expected-note@-1{{'s.p2' initialized to a null pointer value}} + //expected-note@-2{{'s' initialized here}} + + auto [a, b] = s; //expected-note{{Null pointer value stored to '.p2'}} + + int i = *b; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} + (void) i; +} +} // namespace structuredBinding + +#endif + +namespace nestedCtorInitializer { + struct S5{ + int *x, *y; + }; + + struct S4 { + S5 s5; + }; + + struct S3 { + S4 s4; + }; + + struct S2 { + S3 s3; + }; + + struct S { + S2 s2; + + //FIXME: Put more information to the notes. + S(int *x, int *y) : s2{x, y} {}; + // expected-note@-1{{Null pointer value stored to 's.s2.s3.s4.s5.y'}} + }; + + void nestedCtorInit(){ + int *x = nullptr, *y = nullptr; // expected-note{{'y' initialized to a null pointer value}} + + S s{x,y}; + // expected-note@-1{{Passing null pointer value via 2nd parameter}} + // expected-note@-2{{Calling constructor for 'S'}} + // expected-note@-3{{Returning from constructor for 'S'}} + + int i = *s.s2.s3.s4.s5.y; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} + (void) i; + } +} // namespace nestedCtorInitializer