diff options
Diffstat (limited to 'clang/lib/StaticAnalyzer')
27 files changed, 433 insertions, 156 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp index bf35bee..3ddd659 100644 --- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp @@ -104,7 +104,7 @@ class RAIIMutexDescriptor { // this function is called instead of early returning it. To avoid this, a // bool variable (IdentifierInfoInitialized) is used and the function will // be run only once. - const auto &ASTCtx = Call.getState()->getStateManager().getContext(); + const auto &ASTCtx = Call.getASTContext(); Guard = &ASTCtx.Idents.get(GuardName); } } diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index 0ae784c..1444114 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -251,6 +251,8 @@ public: const Expr *Ex, const MemRegion *MR, bool hypothetical); + static const StringLiteral *getStringLiteralFromRegion(const MemRegion *MR); + SVal getCStringLength(CheckerContext &C, ProgramStateRef &state, const Expr *Ex, @@ -983,6 +985,21 @@ SVal CStringChecker::getCStringLengthForRegion(CheckerContext &C, return strLength; } +const StringLiteral * +CStringChecker::getStringLiteralFromRegion(const MemRegion *MR) { + switch (MR->getKind()) { + case MemRegion::StringRegionKind: + return cast<StringRegion>(MR)->getStringLiteral(); + case MemRegion::NonParamVarRegionKind: + if (const VarDecl *Decl = cast<NonParamVarRegion>(MR)->getDecl(); + Decl->getType().isConstQualified() && Decl->hasGlobalStorage()) + return dyn_cast_or_null<StringLiteral>(Decl->getInit()); + return nullptr; + default: + return nullptr; + } +} + SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, const Expr *Ex, SVal Buf, bool hypothetical) const { @@ -1013,30 +1030,19 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, // its length. For anything we can't figure out, just return UnknownVal. MR = MR->StripCasts(); - switch (MR->getKind()) { - case MemRegion::StringRegionKind: { - // Modifying the contents of string regions is undefined [C99 6.4.5p6], - // so we can assume that the byte length is the correct C string length. - SValBuilder &svalBuilder = C.getSValBuilder(); - QualType sizeTy = svalBuilder.getContext().getSizeType(); - const StringLiteral *strLit = cast<StringRegion>(MR)->getStringLiteral(); - return svalBuilder.makeIntVal(strLit->getLength(), sizeTy); - } - case MemRegion::NonParamVarRegionKind: { + if (const StringLiteral *StrLit = getStringLiteralFromRegion(MR)) { // If we have a global constant with a string literal initializer, // compute the initializer's length. - const VarDecl *Decl = cast<NonParamVarRegion>(MR)->getDecl(); - if (Decl->getType().isConstQualified() && Decl->hasGlobalStorage()) { - if (const Expr *Init = Decl->getInit()) { - if (auto *StrLit = dyn_cast<StringLiteral>(Init)) { - SValBuilder &SvalBuilder = C.getSValBuilder(); - QualType SizeTy = SvalBuilder.getContext().getSizeType(); - return SvalBuilder.makeIntVal(StrLit->getLength(), SizeTy); - } - } - } - [[fallthrough]]; + // Modifying the contents of string regions is undefined [C99 6.4.5p6], + // so we can assume that the byte length is the correct C string length. + // FIXME: Embedded null characters are not handled. + SValBuilder &SVB = C.getSValBuilder(); + return SVB.makeIntVal(StrLit->getLength(), SVB.getContext().getSizeType()); } + + switch (MR->getKind()) { + case MemRegion::StringRegionKind: + case MemRegion::NonParamVarRegionKind: case MemRegion::SymbolicRegionKind: case MemRegion::AllocaRegionKind: case MemRegion::ParamVarRegionKind: @@ -1046,10 +1052,28 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, case MemRegion::CompoundLiteralRegionKind: // FIXME: Can we track this? Is it necessary? return UnknownVal(); - case MemRegion::ElementRegionKind: - // FIXME: How can we handle this? It's not good enough to subtract the - // offset from the base string length; consider "123\x00567" and &a[5]. + case MemRegion::ElementRegionKind: { + // If an offset into the string literal is used, use the original length + // minus the offset. + // FIXME: Embedded null characters are not handled. + const ElementRegion *ER = cast<ElementRegion>(MR); + const SubRegion *SuperReg = + cast<SubRegion>(ER->getSuperRegion()->StripCasts()); + const StringLiteral *StrLit = getStringLiteralFromRegion(SuperReg); + if (!StrLit) + return UnknownVal(); + SValBuilder &SVB = C.getSValBuilder(); + NonLoc Idx = ER->getIndex(); + QualType SizeTy = SVB.getContext().getSizeType(); + NonLoc LengthVal = + SVB.makeIntVal(StrLit->getLength(), SizeTy).castAs<NonLoc>(); + if (state->assume(SVB.evalBinOpNN(state, BO_LE, Idx, LengthVal, + SVB.getConditionType()) + .castAs<DefinedOrUnknownSVal>(), + true)) + return SVB.evalBinOp(state, BO_Sub, LengthVal, Idx, SizeTy); return UnknownVal(); + } default: // Other regions (mostly non-data) can't have a reliable C string length. // In this case, an error is emitted and UndefinedVal is returned. @@ -1074,6 +1098,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, const StringLiteral *CStringChecker::getCStringLiteral(CheckerContext &C, ProgramStateRef &state, const Expr *expr, SVal val) const { + // FIXME: use getStringLiteralFromRegion (and remove unused parameters)? // Get the memory region pointed to by the val. const MemRegion *bufRegion = val.getAsRegion(); diff --git a/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp index b304350..7cc146e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp @@ -246,7 +246,7 @@ public: bool Find(const TypedValueRegion *R) { QualType T = R->getValueType(); if (const RecordType *RT = T->getAsStructureType()) { - const RecordDecl *RD = RT->getOriginalDecl()->getDefinition(); + const RecordDecl *RD = RT->getDecl()->getDefinition(); assert(RD && "Referred record has no definition"); for (const auto *I : RD->fields()) { if (I->isUnnamedBitField()) diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp index 9d3aeff..2420848 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp @@ -929,7 +929,7 @@ ObjCDeallocChecker::getValueReleasedByNillingOut(const ObjCMethodCall &M, SVal Arg = M.getArgSVal(0); ProgramStateRef notNilState, nilState; std::tie(notNilState, nilState) = - M.getState()->assume(Arg.castAs<DefinedOrUnknownSVal>()); + C.getState()->assume(Arg.castAs<DefinedOrUnknownSVal>()); if (!(nilState && !notNilState)) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp index 17af1ae..5e75c1c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp @@ -154,15 +154,15 @@ void WalkAST::VisitCallExpr(CallExpr *CE) { .Case("mkstemp", &WalkAST::checkCall_mkstemp) .Case("mkdtemp", &WalkAST::checkCall_mkstemp) .Case("mkstemps", &WalkAST::checkCall_mkstemp) - .Cases("strcpy", "__strcpy_chk", &WalkAST::checkCall_strcpy) - .Cases("strcat", "__strcat_chk", &WalkAST::checkCall_strcat) - .Cases("sprintf", "vsprintf", "scanf", "wscanf", "fscanf", "fwscanf", - "vscanf", "vwscanf", "vfscanf", "vfwscanf", + .Cases({"strcpy", "__strcpy_chk"}, &WalkAST::checkCall_strcpy) + .Cases({"strcat", "__strcat_chk"}, &WalkAST::checkCall_strcat) + .Cases({"sprintf", "vsprintf", "scanf", "wscanf", "fscanf", "fwscanf", + "vscanf", "vwscanf", "vfscanf", "vfwscanf"}, &WalkAST::checkDeprecatedOrUnsafeBufferHandling) - .Cases("sscanf", "swscanf", "vsscanf", "vswscanf", "swprintf", - "snprintf", "vswprintf", "vsnprintf", "memcpy", "memmove", + .Cases({"sscanf", "swscanf", "vsscanf", "vswscanf", "swprintf", + "snprintf", "vswprintf", "vsnprintf", "memcpy", "memmove"}, &WalkAST::checkDeprecatedOrUnsafeBufferHandling) - .Cases("strncpy", "strncat", "memset", "fprintf", + .Cases({"strncpy", "strncat", "memset", "fprintf"}, &WalkAST::checkDeprecatedOrUnsafeBufferHandling) .Case("drand48", &WalkAST::checkCall_rand) .Case("erand48", &WalkAST::checkCall_rand) @@ -766,12 +766,14 @@ void WalkAST::checkDeprecatedOrUnsafeBufferHandling(const CallExpr *CE, int ArgIndex = llvm::StringSwitch<int>(Name) - .Cases("scanf", "wscanf", "vscanf", "vwscanf", 0) - .Cases("fscanf", "fwscanf", "vfscanf", "vfwscanf", "sscanf", - "swscanf", "vsscanf", "vswscanf", 1) - .Cases("sprintf", "vsprintf", "fprintf", 1) - .Cases("swprintf", "snprintf", "vswprintf", "vsnprintf", "memcpy", - "memmove", "memset", "strncpy", "strncat", DEPR_ONLY) + .Cases({"scanf", "wscanf", "vscanf", "vwscanf"}, 0) + .Cases({"fscanf", "fwscanf", "vfscanf", "vfwscanf", "sscanf", + "swscanf", "vsscanf", "vswscanf"}, + 1) + .Cases({"sprintf", "vsprintf", "fprintf"}, 1) + .Cases({"swprintf", "snprintf", "vswprintf", "vsnprintf", "memcpy", + "memmove", "memset", "strncpy", "strncat"}, + DEPR_ONLY) .Default(UNKNOWN_CALL); assert(ArgIndex != UNKNOWN_CALL && "Unsupported function"); diff --git a/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp index 395d724..37f5ec3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp @@ -19,6 +19,7 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/raw_ostream.h" using namespace clang; @@ -39,9 +40,10 @@ public: class DereferenceChecker : public CheckerFamily<check::Location, check::Bind, + check::PreStmt<BinaryOperator>, EventDispatcher<ImplicitNullDerefEvent>> { - void reportBug(const DerefBugType &BT, ProgramStateRef State, const Stmt *S, - CheckerContext &C) const; + void reportDerefBug(const DerefBugType &BT, ProgramStateRef State, + const Stmt *S, CheckerContext &C) const; bool suppressReport(CheckerContext &C, const Expr *E) const; @@ -50,6 +52,7 @@ public: CheckerContext &C) const; void checkBind(SVal L, SVal V, const Stmt *S, bool AtDeclInit, CheckerContext &C) const; + void checkPreStmt(const BinaryOperator *Op, CheckerContext &C) const; static void AddDerefSource(raw_ostream &os, SmallVectorImpl<SourceRange> &Ranges, @@ -57,7 +60,7 @@ public: const LocationContext *LCtx, bool loadedFrom = false); - CheckerFrontend NullDerefChecker, FixedDerefChecker; + CheckerFrontend NullDerefChecker, FixedDerefChecker, NullPointerArithmChecker; const DerefBugType NullBug{&NullDerefChecker, "Dereference of null pointer", "a null pointer dereference", "a dereference of a null pointer"}; @@ -72,9 +75,22 @@ public: const DerefBugType FixedAddressBug{&FixedDerefChecker, "Dereference of a fixed address", "a dereference of a fixed address"}; + const BugType NullPointerArithmBug{ + &NullPointerArithmChecker, + "Possibly undefined arithmetic operation involving a null pointer"}; StringRef getDebugTag() const override { return "DereferenceChecker"; } }; + +struct ValueDescStr { + SmallVectorImpl<SourceRange> &Ranges; + const Expr *Ex; + const ProgramState *State; + const LocationContext *LCtx; + bool IsPointer; + ConditionTruthVal IsNull; +}; + } // end anonymous namespace void @@ -173,9 +189,9 @@ static bool isDeclRefExprToReference(const Expr *E) { return false; } -void DereferenceChecker::reportBug(const DerefBugType &BT, - ProgramStateRef State, const Stmt *S, - CheckerContext &C) const { +void DereferenceChecker::reportDerefBug(const DerefBugType &BT, + ProgramStateRef State, const Stmt *S, + CheckerContext &C) const { if (&BT == &FixedAddressBug) { if (!FixedDerefChecker.isEnabled()) // Deliberately don't add a sink node if check is disabled. @@ -249,9 +265,8 @@ void DereferenceChecker::reportBug(const DerefBugType &BT, bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *BR); - for (SmallVectorImpl<SourceRange>::iterator - I = Ranges.begin(), E = Ranges.end(); I!=E; ++I) - BR->addRange(*I); + for (const auto &R : Ranges) + BR->addRange(R); C.emitReport(std::move(BR)); } @@ -262,7 +277,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, if (l.isUndef()) { const Expr *DerefExpr = getDereferenceExpr(S); if (!suppressReport(C, DerefExpr)) - reportBug(UndefBug, C.getState(), DerefExpr, C); + reportDerefBug(UndefBug, C.getState(), DerefExpr, C); return; } @@ -283,7 +298,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, // we call an "explicit" null dereference. const Expr *expr = getDereferenceExpr(S); if (!suppressReport(C, expr)) { - reportBug(NullBug, nullState, expr, C); + reportDerefBug(NullBug, nullState, expr, C); return; } } @@ -301,7 +316,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, if (location.isConstant()) { const Expr *DerefExpr = getDereferenceExpr(S, isLoad); if (!suppressReport(C, DerefExpr)) - reportBug(FixedAddressBug, notNullState, DerefExpr, C); + reportDerefBug(FixedAddressBug, notNullState, DerefExpr, C); return; } @@ -317,7 +332,7 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S, // One should never write to label addresses. if (auto Label = L.getAs<loc::GotoLabel>()) { - reportBug(LabelBug, C.getState(), S, C); + reportDerefBug(LabelBug, C.getState(), S, C); return; } @@ -338,7 +353,7 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S, if (!StNonNull) { const Expr *expr = getDereferenceExpr(S, /*IsBind=*/true); if (!suppressReport(C, expr)) { - reportBug(NullBug, StNull, expr, C); + reportDerefBug(NullBug, StNull, expr, C); return; } } @@ -356,7 +371,7 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S, if (V.isConstant()) { const Expr *DerefExpr = getDereferenceExpr(S, true); if (!suppressReport(C, DerefExpr)) - reportBug(FixedAddressBug, State, DerefExpr, C); + reportDerefBug(FixedAddressBug, State, DerefExpr, C); return; } @@ -379,6 +394,96 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S, C.addTransition(State, this); } +namespace llvm { +template <> struct format_provider<ValueDescStr> { + static void format(const ValueDescStr &V, raw_ostream &Stream, + StringRef Style) { + static const char *ValueStr[2][3] = { + {"zero", "nonzero integer value", "probably nonzero integer value"}, + {"null pointer", "non-null pointer", "probably non-null pointer"}, + }; + Stream + << ValueStr[V.IsPointer][V.IsNull.isConstrainedTrue() + ? 0 + : (V.IsNull.isConstrainedFalse() ? 1 : 2)]; + DereferenceChecker::AddDerefSource(Stream, V.Ranges, V.Ex, V.State, V.LCtx, + false); + } +}; +} // namespace llvm + +void DereferenceChecker::checkPreStmt(const BinaryOperator *Op, + CheckerContext &C) const { + if (!Op->isAdditiveOp() || !NullPointerArithmChecker.isEnabled()) + return; + const Expr *E1 = Op->getLHS(); + const Expr *E2 = Op->getRHS(); + QualType T1 = E1->getType().getCanonicalType(); + QualType T2 = E2->getType().getCanonicalType(); + bool T1IsPointer = T1->isPointerType(); + bool T2IsPointer = T2->isPointerType(); + if (T1->isIntegerType() && T2->isIntegerType()) + return; + if (!T1IsPointer && !T1->isIntegerType() && !T2IsPointer && + !T2->isIntegerType()) + return; + + ProgramStateRef State = C.getState(); + ConditionTruthVal V1IsNull = State->isNull(C.getSVal(E1)); + ConditionTruthVal V2IsNull = State->isNull(C.getSVal(E2)); + bool IsConstrained = true; + + // Check cases 'NULL + x' and 'NULL - x' + if (T1IsPointer && !T2IsPointer) { + if (!V1IsNull.isConstrainedTrue() || V2IsNull.isConstrainedTrue()) + return; + IsConstrained = V2IsNull.isConstrainedFalse(); + } + + // Check case 'x + NULL' + if (!T1IsPointer && T2IsPointer) { + if (V1IsNull.isConstrainedTrue() || !V2IsNull.isConstrainedTrue()) + return; + IsConstrained = V1IsNull.isConstrainedFalse(); + } + + // Check case 'NULL - p' or 'p - NULL' + if (T1IsPointer && T2IsPointer) { + if (!V1IsNull.isConstrainedTrue() && !V2IsNull.isConstrainedTrue()) + return; + if (V1IsNull.isConstrainedTrue() && V2IsNull.isConstrainedTrue()) + return; + IsConstrained = + V1IsNull.isConstrainedFalse() || V2IsNull.isConstrainedFalse(); + } + + SmallVector<SourceRange, 2> Ranges; + const char *OpcodeStr = + Op->getOpcode() == BO_Add ? "Addition" : "Subtraction"; + const char *ResultStr = IsConstrained ? "results" : "may result"; + ValueDescStr DerefArg1{ + Ranges, E1, State.get(), C.getLocationContext(), T1IsPointer, V1IsNull}; + ValueDescStr DerefArg2{ + Ranges, E2, State.get(), C.getLocationContext(), T2IsPointer, V2IsNull}; + std::string Msg = + llvm::formatv("{0} of a {1} and a {2} {3} in undefined behavior", + OpcodeStr, DerefArg1, DerefArg2, ResultStr); + + ExplodedNode *N = C.generateErrorNode(State); + if (!N) + return; + auto BR = + std::make_unique<PathSensitiveBugReport>(NullPointerArithmBug, Msg, N); + if (V1IsNull.isConstrainedTrue()) + bugreporter::trackExpressionValue(N, E1, *BR); + if (V2IsNull.isConstrainedTrue()) + bugreporter::trackExpressionValue(N, E2, *BR); + for (const auto &R : Ranges) + BR->addRange(R); + + C.emitReport(std::move(BR)); +} + void ento::registerNullDereferenceChecker(CheckerManager &Mgr) { Mgr.getChecker<DereferenceChecker>()->NullDerefChecker.enable(Mgr); } @@ -395,3 +500,11 @@ bool ento::shouldRegisterFixedAddressDereferenceChecker( const CheckerManager &) { return true; } + +void ento::registerNullPointerArithmChecker(CheckerManager &Mgr) { + Mgr.getChecker<DereferenceChecker>()->NullPointerArithmChecker.enable(Mgr); +} + +bool ento::shouldRegisterNullPointerArithmChecker(const CheckerManager &) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp index b1a7cd7..bc67391 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp @@ -148,9 +148,8 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, QualType T = ArgE->getType(); const RecordType *UT = T->getAsUnionType(); - if (!UT || !UT->getOriginalDecl() - ->getMostRecentDecl() - ->hasAttr<TransparentUnionAttr>()) + if (!UT || + !UT->getDecl()->getMostRecentDecl()->hasAttr<TransparentUnionAttr>()) continue; auto CSV = DV->getAs<nonloc::CompoundVal>(); diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp index f984caf..227cbfa 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp @@ -34,7 +34,7 @@ class ObjCSuperDeallocChecker this, "[super dealloc] should not be called more than once", categories::CoreFoundationObjectiveC}; - void initIdentifierInfoAndSelectors(ASTContext &Ctx) const; + void initIdentifierInfoAndSelectors(const ASTContext &Ctx) const; bool isSuperDeallocMessage(const ObjCMethodCall &M) const; @@ -214,8 +214,8 @@ void ObjCSuperDeallocChecker::diagnoseCallArguments(const CallEvent &CE, } } -void -ObjCSuperDeallocChecker::initIdentifierInfoAndSelectors(ASTContext &Ctx) const { +void ObjCSuperDeallocChecker::initIdentifierInfoAndSelectors( + const ASTContext &Ctx) const { if (IIdealloc) return; @@ -230,7 +230,7 @@ ObjCSuperDeallocChecker::isSuperDeallocMessage(const ObjCMethodCall &M) const { if (M.getOriginExpr()->getReceiverKind() != ObjCMessageExpr::SuperInstance) return false; - ASTContext &Ctx = M.getState()->getStateManager().getContext(); + const ASTContext &Ctx = M.getASTContext(); initIdentifierInfoAndSelectors(Ctx); return M.getSelector() == SELdealloc; diff --git a/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp index 4fc1c57..db8bbee 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp @@ -211,13 +211,13 @@ private: if (!DefaultType) return; - ProgramStateRef State = ConstructorCall->getState(); + ProgramStateRef State = C.getState(); State = State->set<VariantHeldTypeMap>(ThisMemRegion, *DefaultType); C.addTransition(State); } bool handleStdGetCall(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = Call.getState(); + ProgramStateRef State = C.getState(); const auto &ArgType = Call.getArgSVal(0) .getType(C.getASTContext()) diff --git a/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h b/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h index dec4612..b8fb572 100644 --- a/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h +++ b/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h @@ -52,7 +52,7 @@ removeInformationStoredForDeadInstances(const CallEvent &Call, template <class TypeMap> void handleConstructorAndAssignment(const CallEvent &Call, CheckerContext &C, SVal ThisSVal) { - ProgramStateRef State = Call.getState(); + ProgramStateRef State = C.getState(); if (!State) return; diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp index 66cfccb..84adbf3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp @@ -26,6 +26,7 @@ bool tryToFindPtrOrigin( const Expr *E, bool StopAtFirstRefCountedObj, std::function<bool(const clang::CXXRecordDecl *)> isSafePtr, std::function<bool(const clang::QualType)> isSafePtrType, + std::function<bool(const clang::Decl *)> isSafeGlobalDecl, std::function<bool(const clang::Expr *, bool)> callback) { while (E) { if (auto *DRE = dyn_cast<DeclRefExpr>(E)) { @@ -34,6 +35,8 @@ bool tryToFindPtrOrigin( auto IsImmortal = safeGetName(VD) == "NSApp"; if (VD->hasGlobalStorage() && (IsImmortal || QT.isConstQualified())) return callback(E, true); + if (VD->hasGlobalStorage() && isSafeGlobalDecl(VD)) + return callback(E, true); } } if (auto *tempExpr = dyn_cast<MaterializeTemporaryExpr>(E)) { @@ -71,9 +74,11 @@ bool tryToFindPtrOrigin( } if (auto *Expr = dyn_cast<ConditionalOperator>(E)) { return tryToFindPtrOrigin(Expr->getTrueExpr(), StopAtFirstRefCountedObj, - isSafePtr, isSafePtrType, callback) && + isSafePtr, isSafePtrType, isSafeGlobalDecl, + callback) && tryToFindPtrOrigin(Expr->getFalseExpr(), StopAtFirstRefCountedObj, - isSafePtr, isSafePtrType, callback); + isSafePtr, isSafePtrType, isSafeGlobalDecl, + callback); } if (auto *cast = dyn_cast<CastExpr>(E)) { if (StopAtFirstRefCountedObj) { @@ -93,7 +98,8 @@ bool tryToFindPtrOrigin( if (auto *call = dyn_cast<CallExpr>(E)) { if (auto *Callee = call->getCalleeDecl()) { if (Callee->hasAttr<CFReturnsRetainedAttr>() || - Callee->hasAttr<NSReturnsRetainedAttr>()) { + Callee->hasAttr<NSReturnsRetainedAttr>() || + Callee->hasAttr<NSReturnsAutoreleasedAttr>()) { return callback(E, true); } } @@ -158,13 +164,23 @@ bool tryToFindPtrOrigin( auto Name = safeGetName(callee); if (Name == "__builtin___CFStringMakeConstantString" || - Name == "NSClassFromString") + Name == "NSStringFromSelector" || Name == "NSSelectorFromString" || + Name == "NSStringFromClass" || Name == "NSClassFromString" || + Name == "NSStringFromProtocol" || Name == "NSProtocolFromString") return callback(E, true); } else if (auto *CalleeE = call->getCallee()) { if (auto *E = dyn_cast<DeclRefExpr>(CalleeE->IgnoreParenCasts())) { if (isSingleton(E->getFoundDecl())) return callback(E, true); } + + if (auto *MemberExpr = dyn_cast<CXXDependentScopeMemberExpr>(CalleeE)) { + auto *Base = MemberExpr->getBase(); + auto MemberName = MemberExpr->getMember().getAsString(); + bool IsGetter = MemberName == "get" || MemberName == "ptr"; + if (Base && isSafePtrType(Base->getType()) && IsGetter) + return callback(E, true); + } } // Sometimes, canonical type erroneously turns Ref<T> into T. @@ -176,7 +192,7 @@ bool tryToFindPtrOrigin( if (auto *Subst = dyn_cast<SubstTemplateTypeParmType>(RetType)) { if (auto *SubstType = Subst->desugar().getTypePtr()) { if (auto *RD = dyn_cast<RecordType>(SubstType)) { - if (auto *CXX = dyn_cast<CXXRecordDecl>(RD->getOriginalDecl())) + if (auto *CXX = dyn_cast<CXXRecordDecl>(RD->getDecl())) if (isSafePtr(CXX)) return callback(E, true); } @@ -196,6 +212,8 @@ bool tryToFindPtrOrigin( !Selector.getNumArgs()) return callback(E, true); } + if (auto *ObjCProtocol = dyn_cast<ObjCProtocolExpr>(E)) + return callback(ObjCProtocol, true); if (auto *ObjCDict = dyn_cast<ObjCDictionaryLiteral>(E)) return callback(ObjCDict, true); if (auto *ObjCArray = dyn_cast<ObjCArrayLiteral>(E)) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h index 3a009d6..9fff456 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h @@ -56,6 +56,7 @@ bool tryToFindPtrOrigin( const clang::Expr *E, bool StopAtFirstRefCountedObj, std::function<bool(const clang::CXXRecordDecl *)> isSafePtr, std::function<bool(const clang::QualType)> isSafePtrType, + std::function<bool(const clang::Decl *)> isSafeGlobalDecl, std::function<bool(const clang::Expr *, bool)> callback); /// For \p E referring to a ref-countable/-counted pointer/reference we return diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp index d8539ea..1d4e6dd 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp @@ -263,18 +263,43 @@ public: void visitCallArg(const Expr *Arg, const ParmVarDecl *Param, const Decl *DeclWithIssue) const { auto *ArgExpr = Arg->IgnoreParenCasts(); - if (auto *InnerCE = dyn_cast<CallExpr>(Arg)) { - auto *InnerCallee = InnerCE->getDirectCallee(); - if (InnerCallee && InnerCallee->isInStdNamespace() && - safeGetName(InnerCallee) == "move" && InnerCE->getNumArgs() == 1) { - ArgExpr = InnerCE->getArg(0); - if (ArgExpr) - ArgExpr = ArgExpr->IgnoreParenCasts(); + while (ArgExpr) { + ArgExpr = ArgExpr->IgnoreParenCasts(); + if (auto *InnerCE = dyn_cast<CallExpr>(ArgExpr)) { + auto *InnerCallee = InnerCE->getDirectCallee(); + if (InnerCallee && InnerCallee->isInStdNamespace() && + safeGetName(InnerCallee) == "move" && InnerCE->getNumArgs() == 1) { + ArgExpr = InnerCE->getArg(0); + continue; + } + } + if (auto *UO = dyn_cast<UnaryOperator>(ArgExpr)) { + auto OpCode = UO->getOpcode(); + if (OpCode == UO_Deref || OpCode == UO_AddrOf) { + ArgExpr = UO->getSubExpr(); + continue; + } } + break; } + + if (auto *MemberCallExpr = dyn_cast<CXXMemberCallExpr>(ArgExpr)) { + if (isOwnerPtrType(MemberCallExpr->getObjectType())) + return; + } + + if (auto *OpCE = dyn_cast<CXXOperatorCallExpr>(ArgExpr)) { + auto *Method = dyn_cast_or_null<CXXMethodDecl>(OpCE->getDirectCallee()); + if (Method && isOwnerPtr(safeGetName(Method->getParent()))) { + if (OpCE->getOperator() == OO_Star && OpCE->getNumArgs() == 1) + return; + } + } + if (isNullPtr(ArgExpr) || isa<IntegerLiteral>(ArgExpr) || isa<CXXDefaultArgExpr>(ArgExpr)) return; + if (auto *DRE = dyn_cast<DeclRefExpr>(ArgExpr)) { if (auto *ValDecl = DRE->getDecl()) { if (isa<ParmVarDecl>(ValDecl)) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index e5c74bb..d3d1f13 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -138,6 +138,11 @@ bool isCheckedPtr(const std::string &Name) { return Name == "CheckedPtr" || Name == "CheckedRef"; } +bool isOwnerPtr(const std::string &Name) { + return isRefType(Name) || isCheckedPtr(Name) || Name == "unique_ptr" || + Name == "UniqueRef" || Name == "LazyUniqueRef"; +} + bool isSmartPtrClass(const std::string &Name) { return isRefType(Name) || isCheckedPtr(Name) || isRetainPtrOrOSPtr(Name) || Name == "WeakPtr" || Name == "WeakPtrFactory" || @@ -206,10 +211,7 @@ bool isRetainPtrOrOSPtrType(const clang::QualType T) { } bool isOwnerPtrType(const clang::QualType T) { - return isPtrOfType(T, [](auto Name) { - return isRefType(Name) || isCheckedPtr(Name) || Name == "unique_ptr" || - Name == "UniqueRef" || Name == "LazyUniqueRef"; - }); + return isPtrOfType(T, [](auto Name) { return isOwnerPtr(Name); }); } std::optional<bool> isUncounted(const QualType T) { @@ -255,7 +257,7 @@ void RetainTypeChecker::visitTypedef(const TypedefDecl *TD) { return; } - for (auto *Redecl : RT->getOriginalDecl()->getMostRecentDecl()->redecls()) { + for (auto *Redecl : RT->getDecl()->getMostRecentDecl()->redecls()) { if (Redecl->getAttr<ObjCBridgeAttr>() || Redecl->getAttr<ObjCBridgeMutableAttr>()) { CFPointees.insert(RT); @@ -296,7 +298,7 @@ std::optional<bool> isUnretained(const QualType T, bool IsARCEnabled) { auto *Record = PointeeType->getAsStructureType(); if (!Record) return false; - auto *Decl = Record->getOriginalDecl(); + auto *Decl = Record->getDecl(); if (!Decl) return false; auto TypeName = Decl->getName(); diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h index 8300a6c..12e2e2d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h @@ -143,6 +143,10 @@ bool isCheckedPtr(const std::string &Name); /// \returns true if \p Name is RetainPtr or its variant, false if not. bool isRetainPtrOrOSPtr(const std::string &Name); +/// \returns true if \p Name is an owning smar pointer such as Ref, CheckedPtr, +/// and unique_ptr. +bool isOwnerPtr(const std::string &Name); + /// \returns true if \p Name is a smart pointer type name, false if not. bool isSmartPtrClass(const std::string &Name); diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp index 9585ceb..791e709 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp @@ -29,12 +29,12 @@ namespace { class RawPtrRefCallArgsChecker : public Checker<check::ASTDecl<TranslationUnitDecl>> { BugType Bug; - mutable BugReporter *BR; TrivialFunctionAnalysis TFA; EnsureFunctionAnalysis EFA; protected: + mutable BugReporter *BR; mutable std::optional<RetainTypeChecker> RTC; public: @@ -46,6 +46,7 @@ public: virtual bool isSafePtr(const CXXRecordDecl *Record) const = 0; virtual bool isSafePtrType(const QualType type) const = 0; virtual bool isSafeExpr(const Expr *) const { return false; } + virtual bool isSafeDecl(const Decl *) const { return false; } virtual const char *ptrKind() const = 0; void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, @@ -214,6 +215,7 @@ public: Arg, /*StopAtFirstRefCountedObj=*/true, [&](const clang::CXXRecordDecl *Record) { return isSafePtr(Record); }, [&](const clang::QualType T) { return isSafePtrType(T); }, + [&](const clang::Decl *D) { return isSafeDecl(D); }, [&](const clang::Expr *ArgOrigin, bool IsSafe) { if (IsSafe) return true; @@ -479,6 +481,11 @@ public: isa<ObjCMessageExpr>(E); } + bool isSafeDecl(const Decl *D) const final { + // Treat NS/CF globals in system header as immortal. + return BR->getSourceManager().isInSystemHeader(D->getLocation()); + } + const char *ptrKind() const final { return "unretained"; } }; diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp index dd9701f..c13df479 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp @@ -166,10 +166,10 @@ bool isGuardedScopeEmbeddedInGuardianScope(const VarDecl *Guarded, class RawPtrRefLocalVarsChecker : public Checker<check::ASTDecl<TranslationUnitDecl>> { BugType Bug; - mutable BugReporter *BR; EnsureFunctionAnalysis EFA; protected: + mutable BugReporter *BR; mutable std::optional<RetainTypeChecker> RTC; public: @@ -180,6 +180,7 @@ public: virtual bool isSafePtr(const CXXRecordDecl *) const = 0; virtual bool isSafePtrType(const QualType) const = 0; virtual bool isSafeExpr(const Expr *) const { return false; } + virtual bool isSafeDecl(const Decl *) const { return false; } virtual const char *ptrKind() const = 0; void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, @@ -288,6 +289,7 @@ public: return isSafePtr(Record); }, [&](const clang::QualType Type) { return isSafePtrType(Type); }, + [&](const clang::Decl *D) { return isSafeDecl(D); }, [&](const clang::Expr *InitArgOrigin, bool IsSafe) { if (!InitArgOrigin || IsSafe) return true; @@ -443,6 +445,10 @@ public: return ento::cocoa::isCocoaObjectRef(E->getType()) && isa<ObjCMessageExpr>(E); } + bool isSafeDecl(const Decl *D) const final { + // Treat NS/CF globals in system header as immortal. + return BR->getSourceManager().isInSystemHeader(D->getLocation()); + } const char *ptrKind() const final { return "unretained"; } }; diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp index 6f3a280..c6421f8 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp @@ -121,13 +121,13 @@ public: return true; } } else if (auto *RD = dyn_cast<RecordType>(PointeeType)) { - if (declaresSameEntity(RD->getOriginalDecl(), ClassDecl)) + if (declaresSameEntity(RD->getDecl(), ClassDecl)) return true; } else if (auto *ST = dyn_cast<SubstTemplateTypeParmType>(PointeeType)) { auto Type = ST->getReplacementType(); if (auto *RD = dyn_cast<RecordType>(Type)) { - if (declaresSameEntity(RD->getOriginalDecl(), ClassDecl)) + if (declaresSameEntity(RD->getDecl(), ClassDecl)) return true; } } diff --git a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp index 02f34bc..c905ee6 100644 --- a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp +++ b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp @@ -173,7 +173,7 @@ const PointerToMemberData *BasicValueFactory::getPointerToMemberData( return D; } -LLVM_ATTRIBUTE_UNUSED static bool hasNoRepeatedElements( +[[maybe_unused]] static bool hasNoRepeatedElements( llvm::ImmutableList<const CXXBaseSpecifier *> BaseSpecList) { llvm::SmallPtrSet<QualType, 16> BaseSpecSeen; for (const CXXBaseSpecifier *BaseSpec : BaseSpecList) { diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 06ba015..62460cc 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -89,7 +89,7 @@ static bool isCallback(QualType T) { T = T->getPointeeType(); if (const RecordType *RT = T->getAsStructureType()) { - const RecordDecl *RD = RT->getOriginalDecl()->getDefinitionOrSelf(); + const RecordDecl *RD = RT->getDecl()->getDefinitionOrSelf(); for (const auto *I : RD->fields()) { QualType FieldT = I->getType(); if (FieldT->isBlockPointerType() || FieldT->isFunctionPointerType()) @@ -391,9 +391,8 @@ bool CallEvent::isVariadic(const Decl *D) { static bool isTransparentUnion(QualType T) { const RecordType *UT = T->getAsUnionType(); - return UT && UT->getOriginalDecl() - ->getMostRecentDecl() - ->hasAttr<TransparentUnionAttr>(); + return UT && + UT->getDecl()->getMostRecentDecl()->hasAttr<TransparentUnionAttr>(); } // In some cases, symbolic cases should be transformed before we associate diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp index 44c6f9f..8ee4832 100644 --- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -731,19 +731,22 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, ExplodedNodeSet checkDst; NodeBuilder B(Pred, checkDst, Eng.getBuilderContext()); + ProgramStateRef State = Pred->getState(); + CallEventRef<> UpdatedCall = Call.cloneWithState(State); + // Check if any of the EvalCall callbacks can evaluate the call. for (const auto &EvalCallChecker : EvalCallCheckers) { // TODO: Support the situation when the call doesn't correspond // to any Expr. ProgramPoint L = ProgramPoint::getProgramPoint( - Call.getOriginExpr(), ProgramPoint::PostStmtKind, + UpdatedCall->getOriginExpr(), ProgramPoint::PostStmtKind, Pred->getLocationContext(), EvalCallChecker.Checker); bool evaluated = false; - { // CheckerContext generates transitions(populates checkDest) on + { // CheckerContext generates transitions (populates checkDest) on // destruction, so introduce the scope to make sure it gets properly // populated. CheckerContext C(B, Eng, Pred, L); - evaluated = EvalCallChecker(Call, C); + evaluated = EvalCallChecker(*UpdatedCall, C); } #ifndef NDEBUG if (evaluated && evaluatorChecker) { @@ -774,7 +777,7 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, // If none of the checkers evaluated the call, ask ExprEngine to handle it. if (!evaluatorChecker) { NodeBuilder B(Pred, Dst, Eng.getBuilderContext()); - Eng.defaultEvalCall(B, Pred, Call, CallOpts); + Eng.defaultEvalCall(B, Pred, *UpdatedCall, CallOpts); } } } diff --git a/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp index abfb176..c207a7b 100644 --- a/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp +++ b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp @@ -24,15 +24,21 @@ using namespace ento; namespace { struct Registry { + std::vector<UnsignedEPStat *> ExplicitlySetStats; + std::vector<UnsignedMaxEPStat *> MaxStats; std::vector<CounterEPStat *> CounterStats; - std::vector<UnsignedMaxEPStat *> UnsignedMaxStats; - std::vector<UnsignedEPStat *> UnsignedStats; bool IsLocked = false; struct Snapshot { const Decl *EntryPoint; - std::vector<unsigned> UnsignedStatValues; + // Explicitly set statistics may not have a value set, so they are separate + // from other unsigned statistics + std::vector<std::optional<unsigned>> ExplicitlySetStatValues; + // These are counting and maximizing statistics that initialize to 0, which + // is meaningful even if they are never updated, so their value is always + // present. + std::vector<unsigned> MaxOrCountStatValues; void dumpAsCSV(llvm::raw_ostream &OS) const; }; @@ -46,10 +52,16 @@ static llvm::ManagedStatic<Registry> StatsRegistry; namespace { template <typename Callback> void enumerateStatVectors(const Callback &Fn) { + // This order is important, it matches the order of the Snapshot fields: + // - ExplicitlySetStatValues + Fn(StatsRegistry->ExplicitlySetStats); + // - MaxOrCountStatValues + Fn(StatsRegistry->MaxStats); Fn(StatsRegistry->CounterStats); - Fn(StatsRegistry->UnsignedMaxStats); - Fn(StatsRegistry->UnsignedStats); } + +void clearSnapshots(void *) { StatsRegistry->Snapshots.clear(); } + } // namespace static void checkStatName(const EntryPointStat *M) { @@ -69,7 +81,8 @@ static void checkStatName(const EntryPointStat *M) { } } -void EntryPointStat::lockRegistry(llvm::StringRef CPPFileName) { +void EntryPointStat::lockRegistry(llvm::StringRef CPPFileName, + ASTContext &Ctx) { auto CmpByNames = [](const EntryPointStat *L, const EntryPointStat *R) { return L->name() < R->name(); }; @@ -80,6 +93,10 @@ void EntryPointStat::lockRegistry(llvm::StringRef CPPFileName) { StatsRegistry->IsLocked = true; llvm::raw_string_ostream OS(StatsRegistry->EscapedCPPFileName); llvm::printEscapedString(CPPFileName, OS); + // Make sure snapshots (that reference function Decl's) do not persist after + // the AST is destroyed. This is especially relevant in the context of unit + // tests that construct and destruct multiple ASTs in the same process. + Ctx.AddDeallocation(clearSnapshots, nullptr); } [[maybe_unused]] static bool isRegistered(llvm::StringLiteral Name) { @@ -101,30 +118,36 @@ UnsignedMaxEPStat::UnsignedMaxEPStat(llvm::StringLiteral Name) : EntryPointStat(Name) { assert(!StatsRegistry->IsLocked); assert(!isRegistered(Name)); - StatsRegistry->UnsignedMaxStats.push_back(this); + StatsRegistry->MaxStats.push_back(this); } UnsignedEPStat::UnsignedEPStat(llvm::StringLiteral Name) : EntryPointStat(Name) { assert(!StatsRegistry->IsLocked); assert(!isRegistered(Name)); - StatsRegistry->UnsignedStats.push_back(this); + StatsRegistry->ExplicitlySetStats.push_back(this); } -static std::vector<unsigned> consumeUnsignedStats() { - std::vector<unsigned> Result; - Result.reserve(StatsRegistry->CounterStats.size() + - StatsRegistry->UnsignedMaxStats.size() + - StatsRegistry->UnsignedStats.size()); - for (auto *M : StatsRegistry->CounterStats) { +static std::vector<std::optional<unsigned>> consumeExplicitlySetStats() { + std::vector<std::optional<unsigned>> Result; + Result.reserve(StatsRegistry->ExplicitlySetStats.size()); + for (auto *M : StatsRegistry->ExplicitlySetStats) { Result.push_back(M->value()); M->reset(); } - for (auto *M : StatsRegistry->UnsignedMaxStats) { + return Result; +} + +static std::vector<unsigned> consumeMaxAndCounterStats() { + std::vector<unsigned> Result; + Result.reserve(StatsRegistry->CounterStats.size() + + StatsRegistry->MaxStats.size()); + // Order is important, it must match the order in enumerateStatVectors + for (auto *M : StatsRegistry->MaxStats) { Result.push_back(M->value()); M->reset(); } - for (auto *M : StatsRegistry->UnsignedStats) { + for (auto *M : StatsRegistry->CounterStats) { Result.push_back(M->value()); M->reset(); } @@ -150,20 +173,33 @@ static std::string getUSR(const Decl *D) { } void Registry::Snapshot::dumpAsCSV(llvm::raw_ostream &OS) const { + auto PrintAsUnsignOpt = [&OS](std::optional<unsigned> U) { + OS << (U.has_value() ? std::to_string(*U) : ""); + }; + auto CommaIfNeeded = [&OS](const auto &Vec1, const auto &Vec2) { + if (!Vec1.empty() && !Vec2.empty()) + OS << ","; + }; + auto PrintAsUnsigned = [&OS](unsigned U) { OS << U; }; + OS << '"'; llvm::printEscapedString(getUSR(EntryPoint), OS); OS << "\",\""; OS << StatsRegistry->EscapedCPPFileName << "\",\""; llvm::printEscapedString( clang::AnalysisDeclContext::getFunctionName(EntryPoint), OS); - OS << "\""; - OS << (UnsignedStatValues.empty() ? "" : ","); - llvm::interleave(UnsignedStatValues, OS, [&OS](unsigned U) { OS << U; }, ","); + OS << "\","; + llvm::interleave(ExplicitlySetStatValues, OS, PrintAsUnsignOpt, ","); + CommaIfNeeded(ExplicitlySetStatValues, MaxOrCountStatValues); + llvm::interleave(MaxOrCountStatValues, OS, PrintAsUnsigned, ","); } void EntryPointStat::takeSnapshot(const Decl *EntryPoint) { - auto UnsignedValues = consumeUnsignedStats(); - StatsRegistry->Snapshots.push_back({EntryPoint, std::move(UnsignedValues)}); + auto ExplicitlySetValues = consumeExplicitlySetStats(); + auto MaxOrCounterValues = consumeMaxAndCounterStats(); + StatsRegistry->Snapshots.push_back({EntryPoint, + std::move(ExplicitlySetValues), + std::move(MaxOrCounterValues)}); } void EntryPointStat::dumpStatsAsCSV(llvm::StringRef FileName) { diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index 0c491b8..ac6c1d7 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -628,6 +628,8 @@ void ExprEngine::VisitCallExpr(const CallExpr *CE, ExplodedNode *Pred, ProgramStateRef ExprEngine::finishArgumentConstruction(ProgramStateRef State, const CallEvent &Call) { + // WARNING: The state attached to 'Call' may be obsolete, do not call any + // methods that rely on it! const Expr *E = Call.getOriginExpr(); // FIXME: Constructors to placement arguments of operator new // are not supported yet. @@ -653,6 +655,8 @@ ProgramStateRef ExprEngine::finishArgumentConstruction(ProgramStateRef State, void ExprEngine::finishArgumentConstruction(ExplodedNodeSet &Dst, ExplodedNode *Pred, const CallEvent &Call) { + // WARNING: The state attached to 'Call' may be obsolete, do not call any + // methods that rely on it! ProgramStateRef State = Pred->getState(); ProgramStateRef CleanedState = finishArgumentConstruction(State, Call); if (CleanedState == State) { @@ -670,35 +674,33 @@ void ExprEngine::finishArgumentConstruction(ExplodedNodeSet &Dst, } void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred, - const CallEvent &Call) { - // WARNING: At this time, the state attached to 'Call' may be older than the - // state in 'Pred'. This is a minor optimization since CheckerManager will - // use an updated CallEvent instance when calling checkers, but if 'Call' is - // ever used directly in this function all callers should be updated to pass - // the most recent state. (It is probably not worth doing the work here since - // for some callers this will not be necessary.) + const CallEvent &CallTemplate) { + // NOTE: CallTemplate is called a "template" because its attached state may + // be obsolete (compared to the state of Pred). The state-dependent methods + // of CallEvent should be used only after a `cloneWithState` call that + // attaches the up-to-date state to this template object. // Run any pre-call checks using the generic call interface. ExplodedNodeSet dstPreVisit; - getCheckerManager().runCheckersForPreCall(dstPreVisit, Pred, - Call, *this); + getCheckerManager().runCheckersForPreCall(dstPreVisit, Pred, CallTemplate, + *this); // Actually evaluate the function call. We try each of the checkers // to see if the can evaluate the function call, and get a callback at // defaultEvalCall if all of them fail. ExplodedNodeSet dstCallEvaluated; - getCheckerManager().runCheckersForEvalCall(dstCallEvaluated, dstPreVisit, - Call, *this, EvalCallOptions()); + getCheckerManager().runCheckersForEvalCall( + dstCallEvaluated, dstPreVisit, CallTemplate, *this, EvalCallOptions()); // If there were other constructors called for object-type arguments // of this call, clean them up. ExplodedNodeSet dstArgumentCleanup; for (ExplodedNode *I : dstCallEvaluated) - finishArgumentConstruction(dstArgumentCleanup, I, Call); + finishArgumentConstruction(dstArgumentCleanup, I, CallTemplate); ExplodedNodeSet dstPostCall; getCheckerManager().runCheckersForPostCall(dstPostCall, dstArgumentCleanup, - Call, *this); + CallTemplate, *this); // Escaping symbols conjured during invalidating the regions above. // Note that, for inlined calls the nodes were put back into the worklist, @@ -708,12 +710,13 @@ void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred, // Run pointerEscape callback with the newly conjured symbols. SmallVector<std::pair<SVal, SVal>, 8> Escaped; for (ExplodedNode *I : dstPostCall) { - NodeBuilder B(I, Dst, *currBldrCtx); ProgramStateRef State = I->getState(); + CallEventRef<> Call = CallTemplate.cloneWithState(State); + NodeBuilder B(I, Dst, *currBldrCtx); Escaped.clear(); { unsigned Arg = -1; - for (const ParmVarDecl *PVD : Call.parameters()) { + for (const ParmVarDecl *PVD : Call->parameters()) { ++Arg; QualType ParamTy = PVD->getType(); if (ParamTy.isNull() || @@ -722,13 +725,13 @@ void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred, QualType Pointee = ParamTy->getPointeeType(); if (Pointee.isConstQualified() || Pointee->isVoidType()) continue; - if (const MemRegion *MR = Call.getArgSVal(Arg).getAsRegion()) + if (const MemRegion *MR = Call->getArgSVal(Arg).getAsRegion()) Escaped.emplace_back(loc::MemRegionVal(MR), State->getSVal(MR, Pointee)); } } State = processPointerEscapedOnBind(State, Escaped, I->getLocationContext(), - PSK_EscapeOutParameters, &Call); + PSK_EscapeOutParameters, &*Call); if (State == I->getState()) Dst.insert(I); @@ -1212,48 +1215,47 @@ static bool isTrivialObjectAssignment(const CallEvent &Call) { } void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred, - const CallEvent &CallTemplate, + const CallEvent &Call, const EvalCallOptions &CallOpts) { // Make sure we have the most recent state attached to the call. ProgramStateRef State = Pred->getState(); - CallEventRef<> Call = CallTemplate.cloneWithState(State); // Special-case trivial assignment operators. - if (isTrivialObjectAssignment(*Call)) { - performTrivialCopy(Bldr, Pred, *Call); + if (isTrivialObjectAssignment(Call)) { + performTrivialCopy(Bldr, Pred, Call); return; } // Try to inline the call. // The origin expression here is just used as a kind of checksum; // this should still be safe even for CallEvents that don't come from exprs. - const Expr *E = Call->getOriginExpr(); + const Expr *E = Call.getOriginExpr(); ProgramStateRef InlinedFailedState = getInlineFailedState(State, E); if (InlinedFailedState) { // If we already tried once and failed, make sure we don't retry later. State = InlinedFailedState; } else { - RuntimeDefinition RD = Call->getRuntimeDefinition(); - Call->setForeign(RD.isForeign()); + RuntimeDefinition RD = Call.getRuntimeDefinition(); + Call.setForeign(RD.isForeign()); const Decl *D = RD.getDecl(); - if (shouldInlineCall(*Call, D, Pred, CallOpts)) { + if (shouldInlineCall(Call, D, Pred, CallOpts)) { if (RD.mayHaveOtherDefinitions()) { AnalyzerOptions &Options = getAnalysisManager().options; // Explore with and without inlining the call. if (Options.getIPAMode() == IPAK_DynamicDispatchBifurcate) { - BifurcateCall(RD.getDispatchRegion(), *Call, D, Bldr, Pred); + BifurcateCall(RD.getDispatchRegion(), Call, D, Bldr, Pred); return; } // Don't inline if we're not in any dynamic dispatch mode. if (Options.getIPAMode() != IPAK_DynamicDispatch) { - conservativeEvalCall(*Call, Bldr, Pred, State); + conservativeEvalCall(Call, Bldr, Pred, State); return; } } - ctuBifurcate(*Call, D, Bldr, Pred, State); + ctuBifurcate(Call, D, Bldr, Pred, State); return; } } @@ -1261,10 +1263,10 @@ void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred, // If we can't inline it, clean up the state traits used only if the function // is inlined. State = removeStateTraitsUsedForArrayEvaluation( - State, dyn_cast_or_null<CXXConstructExpr>(E), Call->getLocationContext()); + State, dyn_cast_or_null<CXXConstructExpr>(E), Call.getLocationContext()); // Also handle the return value and invalidate the regions. - conservativeEvalCall(*Call, Bldr, Pred, State); + conservativeEvalCall(Call, Bldr, Pred, State); } void ExprEngine::BifurcateCall(const MemRegion *BifurReg, diff --git a/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp b/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp index ab45e67..245a730 100644 --- a/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp @@ -983,7 +983,7 @@ public: } /// Check equivalence data for consistency. - [[nodiscard]] LLVM_ATTRIBUTE_UNUSED static bool + [[nodiscard]] [[maybe_unused]] static bool isClassDataConsistent(ProgramStateRef State); [[nodiscard]] QualType getType() const { @@ -1041,8 +1041,7 @@ private: // Constraint functions //===----------------------------------------------------------------------===// -[[nodiscard]] LLVM_ATTRIBUTE_UNUSED bool -areFeasible(ConstraintRangeTy Constraints) { +[[nodiscard]] [[maybe_unused]] bool areFeasible(ConstraintRangeTy Constraints) { return llvm::none_of( Constraints, [](const std::pair<EquivalenceClass, RangeSet> &ClassConstraint) { @@ -1134,7 +1133,7 @@ template <class EndTy> return End; } -[[nodiscard]] LLVM_ATTRIBUTE_UNUSED inline std::optional<RangeSet> +[[nodiscard]] [[maybe_unused]] inline std::optional<RangeSet> intersect(RangeSet::Factory &F, const RangeSet *End) { // This is an extraneous conversion from a raw pointer into // std::optional<RangeSet> diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index af0ef52..2838533 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -2457,7 +2457,7 @@ NonLoc RegionStoreManager::createLazyBinding(RegionBindingsConstRef B, SVal RegionStoreManager::getBindingForStruct(RegionBindingsConstRef B, const TypedValueRegion *R) { const RecordDecl *RD = - R->getValueType()->castAsCanonical<RecordType>()->getOriginalDecl(); + R->getValueType()->castAsCanonical<RecordType>()->getDecl(); if (!RD->getDefinition()) return UnknownVal(); diff --git a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp index 84a9c43..6108931 100644 --- a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp @@ -1111,6 +1111,10 @@ SVal SimpleSValBuilder::evalBinOpLN(ProgramStateRef state, assert(!BinaryOperator::isComparisonOp(op) && "arguments to comparison ops must be of the same type"); + SVal simplifiedRhs = simplifySVal(state, rhs); + if (auto simplifiedRhsAsNonLoc = simplifiedRhs.getAs<NonLoc>()) + rhs = *simplifiedRhsAsNonLoc; + // Special case: rhs is a zero constant. if (rhs.isZeroConstant()) return lhs; diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index cf01e2f..f6a3e79 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -39,6 +39,7 @@ #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/Timer.h" #include "llvm/Support/raw_ostream.h" +#include <cmath> #include <memory> #include <utility> @@ -61,7 +62,9 @@ ALWAYS_ENABLED_STATISTIC( "The # of visited basic blocks in the analyzed functions."); ALWAYS_ENABLED_STATISTIC(PercentReachableBlocks, "The % of reachable basic blocks."); -STAT_MAX(MaxCFGSize, "The maximum number of basic blocks in a function."); +ALWAYS_ENABLED_STATISTIC(MaxCFGSize, + "The maximum number of basic blocks in a function."); +static UnsignedEPStat CFGSize("CFGSize"); //===----------------------------------------------------------------------===// // AnalysisConsumer declaration. //===----------------------------------------------------------------------===// @@ -125,6 +128,7 @@ public: std::unique_ptr<llvm::Timer> SyntaxCheckTimer; std::unique_ptr<llvm::Timer> ExprEngineTimer; std::unique_ptr<llvm::Timer> BugReporterTimer; + bool ShouldClearTimersToPreventDisplayingThem; /// The information about analyzed functions shared throughout the /// translation unit. @@ -138,11 +142,12 @@ public: Injector(std::move(injector)), CTU(CI), MacroExpansions(CI.getLangOpts()) { - EntryPointStat::lockRegistry(getMainFileName(CI.getInvocation())); + EntryPointStat::lockRegistry(getMainFileName(CI.getInvocation()), + CI.getASTContext()); DigestAnalyzerOptions(); if (Opts.AnalyzerDisplayProgress || Opts.PrintStats || - Opts.ShouldSerializeStats) { + Opts.ShouldSerializeStats || !Opts.DumpEntryPointStatsToCSV.empty()) { AnalyzerTimers = std::make_unique<llvm::TimerGroup>( "analyzer", "Analyzer timers"); SyntaxCheckTimer = std::make_unique<llvm::Timer>( @@ -154,6 +159,12 @@ public: *AnalyzerTimers); } + // Avoid displaying the timers created above in case we only want to record + // per-entry-point stats. + ShouldClearTimersToPreventDisplayingThem = !Opts.AnalyzerDisplayProgress && + !Opts.PrintStats && + !Opts.ShouldSerializeStats; + if (Opts.PrintStats || Opts.ShouldSerializeStats) { llvm::EnableStatistics(/* DoPrintOnExit= */ false); } @@ -276,6 +287,9 @@ public: checkerMgr->runCheckersOnASTDecl(D, *Mgr, *RecVisitorBR); if (SyntaxCheckTimer) SyntaxCheckTimer->stopTimer(); + if (AnalyzerTimers && ShouldClearTimersToPreventDisplayingThem) { + AnalyzerTimers->clear(); + } } return true; } @@ -569,6 +583,9 @@ void AnalysisConsumer::runAnalysisOnTranslationUnit(ASTContext &C) { checkerMgr->runCheckersOnASTDecl(TU, *Mgr, BR); if (SyntaxCheckTimer) SyntaxCheckTimer->stopTimer(); + if (AnalyzerTimers && ShouldClearTimersToPreventDisplayingThem) { + AnalyzerTimers->clear(); + } // Run the AST-only checks using the order in which functions are defined. // If inlining is not turned on, use the simplest function order for path @@ -745,6 +762,9 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode, llvm::TimeRecord CheckerEndTime = SyntaxCheckTimer->getTotalTime(); CheckerEndTime -= CheckerStartTime; DisplayTime(CheckerEndTime); + if (AnalyzerTimers && ShouldClearTimersToPreventDisplayingThem) { + AnalyzerTimers->clear(); + } } } @@ -765,15 +785,19 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode, void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, ExprEngine::InliningModes IMode, SetOfConstDecls *VisitedCallees) { + auto *CFG = Mgr->getCFG(D); + // Construct the analysis engine. First check if the CFG is valid. // FIXME: Inter-procedural analysis will need to handle invalid CFGs. - if (!Mgr->getCFG(D)) + if (!CFG) return; // See if the LiveVariables analysis scales. if (!Mgr->getAnalysisDeclContext(D)->getAnalysis<RelaxedLiveVariables>()) return; + CFGSize.set(CFG->size()); + ExprEngine Eng(CTU, *Mgr, VisitedCallees, &FunctionSummaries, IMode); // Execute the worklist algorithm. @@ -788,7 +812,12 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, ExprEngineTimer->stopTimer(); llvm::TimeRecord ExprEngineEndTime = ExprEngineTimer->getTotalTime(); ExprEngineEndTime -= ExprEngineStartTime; + PathRunningTime.set(static_cast<unsigned>( + std::lround(ExprEngineEndTime.getWallTime() * 1000))); DisplayTime(ExprEngineEndTime); + if (AnalyzerTimers && ShouldClearTimersToPreventDisplayingThem) { + AnalyzerTimers->clear(); + } } if (!Mgr->options.DumpExplodedGraphTo.empty()) @@ -799,6 +828,9 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, Eng.ViewGraph(Mgr->options.TrimGraph); flushReports(BugReporterTimer.get(), Eng.getBugReporter()); + if (AnalyzerTimers && ShouldClearTimersToPreventDisplayingThem) { + AnalyzerTimers->clear(); + } } //===----------------------------------------------------------------------===// |