Index: clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp +++ clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp @@ -13,26 +13,60 @@ #include "Move.h" +#include "clang/AST/DeclCXX.h" #include "clang/AST/ExprCXX.h" +#include "clang/AST/Type.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" using namespace clang; using namespace ento; namespace { -class SmartPtrModeling : public Checker { +class SmartPtrModeling + : public Checker { bool isNullAfterMoveMethod(const CallEvent &Call) const; + BugType NullDereferenceBugType{this, "Null-smartPtr-deref", + "C++ smart pointer"}; public: bool evalCall(const CallEvent &Call, CheckerContext &C) const; + void checkPostCall(const CallEvent &MC, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + +private: + void reportBug(CheckerContext &C, const CallEvent &Call) const; + bool isStdSmartPointerClass(const CXXRecordDecl *RD) const; + void updateTrackedRegion(const CallEvent &Call, CheckerContext &C, + const MemRegion *Region) const; + void handleReset(const CallEvent &Call, CheckerContext &C) const; + void handleSwap(const CallEvent &Call, CheckerContext &C) const; + void checkDeferenceOps(const CallEvent &Call, CheckerContext &C) const; + + // Set of STL smart pointer class which we are trying to model. + const llvm::StringSet<> StdSmartPtrs = { + "shared_ptr", + "unique_ptr", + "weak_ptr", + }; + + using SmartPtrMethodHandlerFn = + void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const; + CallDescriptionMap SmartPtrMethodHandlers{ + {{"reset"}, &SmartPtrModeling::handleReset}, + {{"release"}, &SmartPtrModeling::handleReset}, + {{"swap", 1}, &SmartPtrModeling::handleSwap}}; }; } // end of anonymous namespace +REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal) + bool SmartPtrModeling::isNullAfterMoveMethod(const CallEvent &Call) const { // TODO: Update CallDescription to support anonymous calls? // TODO: Handle other methods, such as .get() or .release(). @@ -44,23 +78,146 @@ bool SmartPtrModeling::evalCall(const CallEvent &Call, CheckerContext &C) const { - if (!isNullAfterMoveMethod(Call)) + if (isNullAfterMoveMethod(Call)) { + ProgramStateRef State = C.getState(); + const MemRegion *ThisR = + cast(&Call)->getCXXThisVal().getAsRegion(); + + if (!move::isMovedFrom(State, ThisR)) { + // TODO: Model this case as well. At least, avoid invalidation of globals. + return false; + } + + // TODO: Add a note to bug reports describing this decision. + C.addTransition( + State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), + C.getSValBuilder().makeZeroVal(Call.getResultType()))); + return true; + } + + const auto *MethodDecl = dyn_cast(Call.getDecl()); + if (!MethodDecl || !isStdSmartPointerClass(MethodDecl->getParent())) return false; + checkDeferenceOps(Call, C); + + const SmartPtrMethodHandlerFn *Handler = SmartPtrMethodHandlers.lookup(Call); + if (!Handler) + return false; + (this->**Handler)(Call, C); + + return C.isDifferent(); +} + +void SmartPtrModeling::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + const auto *MethodDecl = dyn_cast(Call.getDecl()); + if (!MethodDecl || !isStdSmartPointerClass(MethodDecl->getParent())) + return; + + const auto *CC = dyn_cast(&Call); + if (!CC) + return; + const MemRegion *ThisValRegion = CC->getCXXThisVal().getAsRegion(); + if (!ThisValRegion) + return; + updateTrackedRegion(Call, C, ThisValRegion); +} + +void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + // Clean up dead regions from the region map. + TrackedRegionMapTy TrackedRegions = State->get(); + for (auto E : TrackedRegions) { + const MemRegion *Region = E.first; + bool IsRegDead = !SymReaper.isLiveRegion(Region); + + if (IsRegDead) { + State = State->remove(Region); + } + } + C.addTransition(State); +} + +void SmartPtrModeling::checkDeferenceOps(const CallEvent &Call, + CheckerContext &C) const { ProgramStateRef State = C.getState(); - const MemRegion *ThisR = - cast(&Call)->getCXXThisVal().getAsRegion(); + const auto *OC = dyn_cast(&Call); + if (!OC) + return; + const MemRegion *ThisRegion = OC->getCXXThisVal().getAsRegion(); + if (!ThisRegion) + return; + + const auto *MethodDecl = dyn_cast_or_null(OC->getDecl()); + + if (MethodDecl && MethodDecl->isOverloadedOperator()) { + OverloadedOperatorKind OOK = MethodDecl->getOverloadedOperator(); + if (OOK == OO_Star || OOK == OO_Arrow) { + const auto *InnerPointVal = State->get(ThisRegion); + if (InnerPointVal && InnerPointVal->isZeroConstant()) { + reportBug(C, Call); + } + } + } +} + +void SmartPtrModeling::handleReset(const CallEvent &Call, + CheckerContext &C) const { + const auto *IC = dyn_cast(&Call); + if (!IC) + return; - if (!move::isMovedFrom(State, ThisR)) { - // TODO: Model this case as well. At least, avoid invalidation of globals. + const MemRegion *ThisValRegion = IC->getCXXThisVal().getAsRegion(); + if (!ThisValRegion) + return; + updateTrackedRegion(Call, C, ThisValRegion); +} + +void SmartPtrModeling::handleSwap(const CallEvent &Call, + CheckerContext &C) const { + // TODO: Add support to handle swap method. +} + +bool SmartPtrModeling::isStdSmartPointerClass( + const CXXRecordDecl *RecordDec) const { + if (!RecordDec || !RecordDec->getDeclContext()->isStdNamespace()) return false; + + if (RecordDec->getDeclName().isIdentifier()) { + return StdSmartPtrs.count(RecordDec->getName().lower()); + } + return false; +} + +void SmartPtrModeling::reportBug(CheckerContext &C, + const CallEvent &Call) const { + ExplodedNode *ErrNode = C.generateErrorNode(); + if (!ErrNode) + return; + + auto R = std::make_unique( + NullDereferenceBugType, "Dereference of null smart pointer", ErrNode); + R->addRange(Call.getSourceRange()); + C.emitReport(std::move(R)); +} + +void SmartPtrModeling::updateTrackedRegion( + const CallEvent &Call, CheckerContext &C, + const MemRegion *ThisValRegion) const { + auto State = C.getState(); + auto NumArgs = Call.getNumArgs(); + + if (NumArgs == 0) { + auto NullSVal = C.getSValBuilder().makeNull(); + State = State->set(ThisValRegion, NullSVal); + } else if (NumArgs == 1) { + auto ArgVal = Call.getArgSVal(0); + State = State->set(ThisValRegion, ArgVal); } - // TODO: Add a note to bug reports describing this decision. - C.addTransition( - State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), - C.getSValBuilder().makeZeroVal(Call.getResultType()))); - return true; + C.addTransition(State); } void ento::registerSmartPtrModeling(CheckerManager &Mgr) { Index: clang/test/Analysis/Inputs/system-header-simulator-cxx.h =================================================================== --- clang/test/Analysis/Inputs/system-header-simulator-cxx.h +++ clang/test/Analysis/Inputs/system-header-simulator-cxx.h @@ -946,10 +946,15 @@ template // TODO: Implement the stub for deleter. class unique_ptr { public: + unique_ptr() {} + unique_ptr(T *) {} unique_ptr(const unique_ptr &) = delete; unique_ptr(unique_ptr &&); T *get() const; + T *release() const; + void reset(T *p = nullptr) const; + void swap(unique_ptr &p) const; typename std::add_lvalue_reference::type operator*() const; T *operator->() const; Index: clang/test/Analysis/smart-ptr.cpp =================================================================== --- clang/test/Analysis/smart-ptr.cpp +++ clang/test/Analysis/smart-ptr.cpp @@ -10,7 +10,7 @@ std::unique_ptr Q = std::move(P); if (Q) clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}} - *Q.get() = 1; // no-warning + *Q.get() = 1; // no-warning if (P) clang_analyzer_warnIfReached(); // no-warning // TODO: Report a null dereference (instead). @@ -26,3 +26,64 @@ (s->*func)(); // no-crash } } // namespace testUnknownCallee + +class A { +public: + A(){}; + void foo(); +}; + +A *return_null() { + return nullptr; +} + +void derefAfterValidCtr() { + std::unique_ptr P(new A()); + P->foo(); // No warning. +} + +void derefAfterDefaultCtr() { + std::unique_ptr P; + P->foo(); // expected-warning {{Dereference of null smart pointer [cplusplus.SmartPtr]}} +} + +void derefAfterCtrWithNull() { + std::unique_ptr P(nullptr); + *P; // expected-warning {{Dereference of null smart pointer [cplusplus.SmartPtr]}} +} + +void derefAfterCtrWithNullReturnMethod() { + std::unique_ptr P(return_null()); + P->foo(); // expected-warning {{Dereference of null smart pointer [cplusplus.SmartPtr]}} +} + +void derefAfterRelease() { + std::unique_ptr P(new A()); + P.release(); + P->foo(); // expected-warning {{Dereference of null smart pointer [cplusplus.SmartPtr]}} +} + +void derefAfterReset() { + std::unique_ptr P(new A()); + P.reset(); + P->foo(); // expected-warning {{Dereference of null smart pointer [cplusplus.SmartPtr]}} +} + +void derefAfterResetWithNull() { + std::unique_ptr P(new A()); + P.reset(nullptr); + P->foo(); // expected-warning {{Dereference of null smart pointer [cplusplus.SmartPtr]}} +} + +void derefAfterResetWithNonNull() { + std::unique_ptr P; + P.reset(new A()); + P->foo(); // No warning. +} + +void derefAfterReleaseAndResetWithNonNull() { + std::unique_ptr P(new A()); + P.release(); + P.reset(new A()); + P->foo(); // No warning. +} \ No newline at end of file