Index: clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -1410,6 +1410,16 @@ } } +static bool isTrivialCopyOrMoveCtor(const CXXConstructExpr *CE) { + if (!CE) + return false; + + const auto *CtorDecl = CE->getConstructor(); + + return (CtorDecl->isCopyConstructor() || CtorDecl->isMoveConstructor()) && + CtorDecl->isTrivial(); +} + PathDiagnosticPieceRef StoreSiteFinder::VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, PathSensitiveBugReport &BR) { @@ -1458,10 +1468,77 @@ // If this is an assignment expression, we can track the value // being assigned. - if (Optional P = Succ->getLocationAs()) + if (Optional P = Succ->getLocationAs()) { if (const BinaryOperator *BO = P->getStmtAs()) if (BO->isAssignmentOp()) InitE = BO->getRHS(); + } + + // We reached a copy/move constructor that hasn't been inlined (most likely + // because of ExprEngine::performTrivialCopy()). The idea is to track the + // value back to the first constructor call. + // + // Note that the copy/move constructors can be chained, so + // in that case we also track one constructor back to the other one. + // E.g.: + // + // struct S { + // int *a, *b; + // S(int *a, int *b) : p1(a), p2(b) {} + // }; + // + // void foo() { + // int *x = nullptr, *y = nullptr; + // + // S s(x, y); + // S s2 = s; + // S s3 = s2; <-- here we track 's3.p1' all + // the way back to 's.p1' and + // launch a separate tracker + // to see where s2 comes from + // + // int i = *s3.p1; <-- null dereference here + // } + // + if (const auto *CE = + dyn_cast_or_null(Succ->getStmtForDiagnostics())) { + const MemRegion *Reg = R; + const ExplodedNode *N = Succ; + + // If the copy or move constructor is user defined it will be inlined and + // handled elsewhere. + while (isTrivialCopyOrMoveCtor(CE)) { + const auto *FR = dyn_cast(Reg); + + if (!FR) { + N = nullptr; + break; + } + + // Look up the value in the origin object. + auto OriginObject = CE->getArg(0); + auto OriginVal = + N->getState()->getSVal(OriginObject, N->getLocationContext()); + auto OriginFieldVal = + N->getState()->getLValue(FR->getDecl(), OriginVal); + Reg = OriginFieldVal.getAsRegion(); + + // Find the node where the value in the origin object was initialized. + N = N->getFirstPred(); + while (N && N->getState()->getSVal(Reg) == V) + N = N->getFirstPred(); + + if (!N) + break; + + // Track the object, we copied or moved from. + CE = dyn_cast(N->getStmtForDiagnostics()); + getParentTracker().track(OriginVal, OriginVal.getAsRegion(), Options); + } + + if (N) + InitE = dyn_cast(N->getStmtForDiagnostics()); + } // If this is a call entry, the variable should be a parameter. // FIXME: Handle CXXThisRegion as well. (This is not a priority because @@ -2418,12 +2495,12 @@ track(BO->getLHS()); if (RHSV.isZeroConstant()) track(BO->getRHS()); - } else { // Track only the LHS of a division or a modulo. - if (LHSV.isZeroConstant()) - track(BO->getLHS()); - } + } else { // Track only the LHS of a division or a modulo. + if (LHSV.isZeroConstant()) + track(BO->getLHS()); + } - return CombinedResult; + return CombinedResult; } }; Index: clang/test/Analysis/ctor-bug-path.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/ctor-bug-path.cpp @@ -0,0 +1,26 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-output=text -verify %s + +#include "Inputs/system-header-simulator-cxx.h" + +namespace copyMoveTrack { +struct S { + int *p1, *p2; + S(int *a, int *b) : p1(a), p2(b) {} +}; + +void foo() { + int *x = nullptr, *y = nullptr; //expected-note{{'x' initialized to a null pointer value}} + + S s(x, y); //expected-note{{'s' initialized here}} + // expected-note@-1{{Passing null pointer value via 1st parameter 'a'}} + S s2 = s; //expected-note{{'s2' initialized here}} + S s3 = s2; //expected-note{{'s3' initialized here}} + S s4 = std::move(s3); //expected-note{{'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}} + + (void) i; +} +} // namespace copyMoveTrack