diff --git a/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp --- a/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp @@ -68,6 +68,7 @@ bool updateMovedSmartPointers(CheckerContext &C, const MemRegion *ThisRegion, const MemRegion *OtherSmartPtrRegion) const; void handleBoolConversion(const CallEvent &Call, CheckerContext &C) const; + bool handleOstreamOperator(const CallEvent &Call, CheckerContext &C) const; using SmartPtrMethodHandlerFn = void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const; @@ -81,6 +82,31 @@ REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal) +// Checks if RD has name in Names and is in std namespace +static bool hasStdClassWithName(const CXXRecordDecl *RD, + ArrayRef Names) { + if (!RD || !RD->getDeclContext()->isStdNamespace()) + return false; + if (RD->getDeclName().isIdentifier()) { + StringRef Name = RD->getName(); + return llvm::any_of(Names, [&Name](StringRef GivenName) -> bool { + return Name == GivenName; + }); + } + return false; +} + +constexpr llvm::StringLiteral STD_PTR_NAMES[] = {"shared_ptr", "unique_ptr", + "weak_ptr"}; + +static bool isStdSmartPtr(const CXXRecordDecl *RD) { + return hasStdClassWithName(RD, STD_PTR_NAMES); +} + +static bool isStdSmartPtr(const Expr *E) { + return isStdSmartPtr(E->getType()->getAsCXXRecordDecl()); +} + // Define the inter-checker API. namespace clang { namespace ento { @@ -89,16 +115,7 @@ const auto *MethodDecl = dyn_cast_or_null(Call.getDecl()); if (!MethodDecl || !MethodDecl->getParent()) return false; - - const auto *RecordDecl = MethodDecl->getParent(); - if (!RecordDecl || !RecordDecl->getDeclContext()->isStdNamespace()) - return false; - - if (RecordDecl->getDeclName().isIdentifier()) { - StringRef Name = RecordDecl->getName(); - return Name == "shared_ptr" || Name == "unique_ptr" || Name == "weak_ptr"; - } - return false; + return isStdSmartPtr(MethodDecl->getParent()); } bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion) { @@ -175,9 +192,37 @@ return CD && CD->getConversionType()->isBooleanType(); } +constexpr llvm::StringLiteral BASIC_OSTREAM_NAMES[] = {"basic_ostream"}; + +bool isStdBasicOstream(const Expr *E) { + const auto *RD = E->getType()->getAsCXXRecordDecl(); + return hasStdClassWithName(RD, BASIC_OSTREAM_NAMES); +} + +bool isStdOstreamOperatorCall(const CallEvent &Call) { + if (Call.getNumArgs() != 2 || + !Call.getDecl()->getDeclContext()->isStdNamespace()) + return false; + const auto *FC = dyn_cast(&Call); + if (!FC) + return false; + const FunctionDecl *FD = FC->getDecl(); + if (!FD->isOverloadedOperator()) + return false; + const OverloadedOperatorKind OOK = FD->getOverloadedOperator(); + if (OOK != clang::OO_LessLess) + return false; + return isStdSmartPtr(Call.getArgExpr(1)) && + isStdBasicOstream(Call.getArgExpr(0)); +} + bool SmartPtrModeling::evalCall(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); + + if (isStdOstreamOperatorCall(Call)) + return handleOstreamOperator(Call, C); + if (!smartptr::isStdSmartPtrCall(Call)) return false; @@ -272,6 +317,30 @@ return C.isDifferent(); } +bool SmartPtrModeling::handleOstreamOperator(const CallEvent &Call, + CheckerContext &C) const { + // operator<< does not modify the smart pointer. + // And we don't really have much of modelling of basic_ostream. + // So, we are better off: + // 1) Invalidating the mem-region of the ostream object at hand. + // 2) Setting the SVal of the basic_ostream as the return value. + // Not very satisfying, but it gets the job done, and is better + // than the default handling. :) + + ProgramStateRef State = C.getState(); + const auto StreamVal = Call.getArgSVal(0); + const MemRegion *StreamThisRegion = StreamVal.getAsRegion(); + if (!StreamThisRegion) + return false; + State = + State->invalidateRegions({StreamThisRegion}, Call.getOriginExpr(), + C.blockCount(), C.getLocationContext(), false); + State = + State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), StreamVal); + C.addTransition(State); + return true; +} + void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { ProgramStateRef State = C.getState(); diff --git a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h --- a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h +++ b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h @@ -981,6 +981,22 @@ } // namespace std #endif +namespace std { +template +class basic_ostream; + +using ostream = basic_ostream; + +extern std::ostream cout; + +ostream &operator<<(ostream &, const string &); + +#if __cplusplus >= 202002L +template +ostream &operator<<(ostream &, const std::unique_ptr &); +#endif +} // namespace std + #ifdef TEST_INLINABLE_ALLOCATORS namespace std { void *malloc(size_t); diff --git a/clang/test/Analysis/smart-ptr.cpp b/clang/test/Analysis/smart-ptr.cpp --- a/clang/test/Analysis/smart-ptr.cpp +++ b/clang/test/Analysis/smart-ptr.cpp @@ -3,6 +3,11 @@ // RUN: -analyzer-config cplusplus.SmartPtrModeling:ModelSmartPtrDereference=true\ // RUN: -std=c++11 -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection\ +// RUN: -analyzer-checker cplusplus.Move,alpha.cplusplus.SmartPtr\ +// RUN: -analyzer-config cplusplus.SmartPtrModeling:ModelSmartPtrDereference=true\ +// RUN: -std=c++20 -verify %s + #include "Inputs/system-header-simulator-cxx.h" void clang_analyzer_warnIfReached(); @@ -457,3 +462,28 @@ P->foo(); // expected-warning {{Dereference of null smart pointer 'P' [alpha.cplusplus.SmartPtr]}} } } + +#if __cplusplus >= 202002L + +void testOstreamOverload(std::unique_ptr P) { + auto &Cout = std::cout; + auto &PtrCout = std::cout << P; + auto &StringCout = std::cout << "hello"; + // We are testing the fact that in our modelling of + // operator<<(basic_ostream &, const unique_ptr &) + // we set the return SVal to the SVal of the ostream arg. + clang_analyzer_eval(&Cout == &PtrCout); // expected-warning {{TRUE}} + // FIXME: Technically, they should be equal, + // that hasn't been modelled yet. + clang_analyzer_eval(&Cout == &StringCout); // expected-warning {{UNKNOWN}} +} + +int glob; +void testOstreamDoesntInvalidateGlobals(std::unique_ptr P) { + int x = glob; + std::cout << P; + int y = glob; + clang_analyzer_eval(x == y); // expected-warning {{TRUE}} +} + +#endif