diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2020-07-26 19:36:28 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2020-07-26 19:36:28 +0000 |
commit | cfca06d7963fa0909f90483b42a6d7d194d01e08 (patch) | |
tree | 209fb2a2d68f8f277793fc8df46c753d31bc853b /clang/lib/StaticAnalyzer/Core | |
parent | 706b4fc47bbc608932d3b491ae19a3b9cde9497b (diff) |
Notes
Diffstat (limited to 'clang/lib/StaticAnalyzer/Core')
35 files changed, 2353 insertions, 887 deletions
diff --git a/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp b/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp index fdd03c75920df..ecfc7106560e8 100644 --- a/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp @@ -13,7 +13,7 @@ using namespace ento; void AnalysisManager::anchor() { } -AnalysisManager::AnalysisManager(ASTContext &ASTCtx, +AnalysisManager::AnalysisManager(ASTContext &ASTCtx, Preprocessor &PP, const PathDiagnosticConsumers &PDC, StoreManagerCreator storemgr, ConstraintManagerCreator constraintmgr, @@ -38,7 +38,7 @@ AnalysisManager::AnalysisManager(ASTContext &ASTCtx, Options.ShouldElideConstructors, /*addVirtualBaseBranches=*/true, injector), - Ctx(ASTCtx), LangOpts(ASTCtx.getLangOpts()), + Ctx(ASTCtx), PP(PP), LangOpts(ASTCtx.getLangOpts()), PathConsumers(PDC), CreateStoreMgr(storemgr), CreateConstraintMgr(constraintmgr), CheckerMgr(checkerMgr), options(Options) { diff --git a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp index 7cd48bf44374c..73f057f09550c 100644 --- a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp +++ b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp @@ -239,7 +239,7 @@ BasicValueFactory::evalAPSInt(BinaryOperator::Opcode Op, if (Amt >= V1.getBitWidth()) return nullptr; - if (!Ctx.getLangOpts().CPlusPlus2a) { + if (!Ctx.getLangOpts().CPlusPlus20) { if (V1.isSigned() && V1.isNegative()) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 1864bcef9b873..72be4e81c83d6 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -34,10 +34,12 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/CheckerRegistryData.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConv.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" #include "llvm/ADT/ArrayRef.h" @@ -51,6 +53,7 @@ #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Statistic.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/iterator_range.h" #include "llvm/Support/Casting.h" @@ -2105,6 +2108,53 @@ void BuiltinBug::anchor() {} // Methods for BugReport and subclasses. //===----------------------------------------------------------------------===// +LLVM_ATTRIBUTE_USED static bool +isDependency(const CheckerRegistryData &Registry, StringRef CheckerName) { + for (const std::pair<StringRef, StringRef> &Pair : Registry.Dependencies) { + if (Pair.second == CheckerName) + return true; + } + return false; +} + +LLVM_ATTRIBUTE_USED static bool isHidden(const CheckerRegistryData &Registry, + StringRef CheckerName) { + for (const CheckerInfo &Checker : Registry.Checkers) { + if (Checker.FullName == CheckerName) + return Checker.IsHidden; + } + llvm_unreachable( + "Checker name not found in CheckerRegistry -- did you retrieve it " + "correctly from CheckerManager::getCurrentCheckerName?"); +} + +PathSensitiveBugReport::PathSensitiveBugReport( + const BugType &bt, StringRef shortDesc, StringRef desc, + const ExplodedNode *errorNode, PathDiagnosticLocation LocationToUnique, + const Decl *DeclToUnique) + : BugReport(Kind::PathSensitive, bt, shortDesc, desc), ErrorNode(errorNode), + ErrorNodeRange(getStmt() ? getStmt()->getSourceRange() : SourceRange()), + UniqueingLocation(LocationToUnique), UniqueingDecl(DeclToUnique) { + assert(!isDependency(ErrorNode->getState() + ->getAnalysisManager() + .getCheckerManager() + ->getCheckerRegistryData(), + bt.getCheckerName()) && + "Some checkers depend on this one! We don't allow dependency " + "checkers to emit warnings, because checkers should depend on " + "*modeling*, not *diagnostics*."); + + assert( + (bt.getCheckerName().startswith("debug") || + !isHidden(ErrorNode->getState() + ->getAnalysisManager() + .getCheckerManager() + ->getCheckerRegistryData(), + bt.getCheckerName())) && + "Hidden checkers musn't emit diagnostics as they are by definition " + "non-user facing!"); +} + void PathSensitiveBugReport::addVisitor( std::unique_ptr<BugReporterVisitor> visitor) { if (!visitor) @@ -2193,12 +2243,12 @@ static void insertToInterestingnessMap( return; case bugreporter::TrackingKind::Condition: return; - } + } - llvm_unreachable( - "BugReport::markInteresting currently can only handle 2 different " - "tracking kinds! Please define what tracking kind should this entitiy" - "have, if it was already marked as interesting with a different kind!"); + llvm_unreachable( + "BugReport::markInteresting currently can only handle 2 different " + "tracking kinds! Please define what tracking kind should this entitiy" + "have, if it was already marked as interesting with a different kind!"); } void PathSensitiveBugReport::markInteresting(SymbolRef sym, @@ -2389,6 +2439,7 @@ ProgramStateManager &PathSensitiveBugReporter::getStateManager() const { return Eng.getStateManager(); } +BugReporter::BugReporter(BugReporterData &d) : D(d) {} BugReporter::~BugReporter() { // Make sure reports are flushed. assert(StrBugTypes.empty() && @@ -2409,7 +2460,7 @@ void BugReporter::FlushReports() { // EmitBasicReport. // FIXME: There are leaks from checkers that assume that the BugTypes they // create will be destroyed by the BugReporter. - llvm::DeleteContainerSeconds(StrBugTypes); + StrBugTypes.clear(); } //===----------------------------------------------------------------------===// @@ -2781,7 +2832,7 @@ Optional<PathDiagnosticBuilder> PathDiagnosticBuilder::findValidReport( R->clearVisitors(); R->addVisitor(std::make_unique<FalsePositiveRefutationBRVisitor>()); - // We don't overrite the notes inserted by other visitors because the + // We don't overwrite the notes inserted by other visitors because the // refutation manager does not add any new note to the path generateVisitorsDiagnostics(R, BugPath->ErrorNode, BRC); } @@ -3262,8 +3313,8 @@ BugType *BugReporter::getBugTypeForName(CheckerNameRef CheckName, SmallString<136> fullDesc; llvm::raw_svector_ostream(fullDesc) << CheckName.getName() << ":" << name << ":" << category; - BugType *&BT = StrBugTypes[fullDesc]; + std::unique_ptr<BugType> &BT = StrBugTypes[fullDesc]; if (!BT) - BT = new BugType(CheckName, name, category); - return BT; + BT = std::make_unique<BugType>(CheckName, name, category); + return BT.get(); } diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 0525b5c41e34d..ef4d38ff498ff 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -45,7 +45,6 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/SMTConv.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/None.h" #include "llvm/ADT/Optional.h" @@ -358,7 +357,7 @@ class NoStoreFuncVisitor final : public BugReporterVisitor { public: NoStoreFuncVisitor(const SubRegion *R, bugreporter::TrackingKind TKind) - : RegionOfInterest(R), MmrMgr(*R->getMemRegionManager()), + : RegionOfInterest(R), MmrMgr(R->getMemRegionManager()), SM(MmrMgr.getContext().getSourceManager()), PP(MmrMgr.getContext().getPrintingPolicy()), TKind(TKind) {} @@ -813,7 +812,7 @@ public: const SourceManager &SMgr = BRC.getSourceManager(); if (auto Loc = matchAssignment(N)) { if (isFunctionMacroExpansion(*Loc, SMgr)) { - std::string MacroName = getMacroName(*Loc, BRC); + std::string MacroName = std::string(getMacroName(*Loc, BRC)); SourceLocation BugLoc = BugPoint->getStmt()->getBeginLoc(); if (!BugLoc.isMacroID() || getMacroName(BugLoc, BRC) != MacroName) BR.markInvalid(getTag(), MacroName.c_str()); @@ -1735,10 +1734,9 @@ constructDebugPieceForTrackedCondition(const Expr *Cond, !BRC.getAnalyzerOptions().ShouldTrackConditionsDebug) return nullptr; - std::string ConditionText = Lexer::getSourceText( + std::string ConditionText = std::string(Lexer::getSourceText( CharSourceRange::getTokenRange(Cond->getSourceRange()), - BRC.getSourceManager(), - BRC.getASTContext().getLangOpts()); + BRC.getSourceManager(), BRC.getASTContext().getLangOpts())); return std::make_shared<PathDiagnosticEventPiece>( PathDiagnosticLocation::createBegin( @@ -2494,7 +2492,7 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( Out << WillBeUsedForACondition; // Convert 'field ...' to 'Field ...' if it is a MemberExpr. - std::string Message = Out.str(); + std::string Message = std::string(Out.str()); Message[0] = toupper(Message[0]); // If we know the value create a pop-up note to the value part of 'BExpr'. @@ -2821,7 +2819,7 @@ void FalsePositiveRefutationBRVisitor::finalizeVisitor( BugReporterContext &BRC, const ExplodedNode *EndPathNode, PathSensitiveBugReport &BR) { // Collect new constraints - VisitNode(EndPathNode, BRC, BR); + addConstraints(EndPathNode, /*OverwriteConstraintsOnExistingSyms=*/true); // Create a refutation manager llvm::SMTSolverRef RefutationSolver = llvm::CreateZ3Solver(); @@ -2832,30 +2830,30 @@ void FalsePositiveRefutationBRVisitor::finalizeVisitor( const SymbolRef Sym = I.first; auto RangeIt = I.second.begin(); - llvm::SMTExprRef Constraints = SMTConv::getRangeExpr( + llvm::SMTExprRef SMTConstraints = SMTConv::getRangeExpr( RefutationSolver, Ctx, Sym, RangeIt->From(), RangeIt->To(), /*InRange=*/true); while ((++RangeIt) != I.second.end()) { - Constraints = RefutationSolver->mkOr( - Constraints, SMTConv::getRangeExpr(RefutationSolver, Ctx, Sym, - RangeIt->From(), RangeIt->To(), - /*InRange=*/true)); + SMTConstraints = RefutationSolver->mkOr( + SMTConstraints, SMTConv::getRangeExpr(RefutationSolver, Ctx, Sym, + RangeIt->From(), RangeIt->To(), + /*InRange=*/true)); } - RefutationSolver->addConstraint(Constraints); + RefutationSolver->addConstraint(SMTConstraints); } // And check for satisfiability - Optional<bool> isSat = RefutationSolver->check(); - if (!isSat.hasValue()) + Optional<bool> IsSAT = RefutationSolver->check(); + if (!IsSAT.hasValue()) return; - if (!isSat.getValue()) + if (!IsSAT.getValue()) BR.markInvalid("Infeasible constraints", EndPathNode->getLocationContext()); } -PathDiagnosticPieceRef FalsePositiveRefutationBRVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &, PathSensitiveBugReport &) { +void FalsePositiveRefutationBRVisitor::addConstraints( + const ExplodedNode *N, bool OverwriteConstraintsOnExistingSyms) { // Collect new constraints const ConstraintRangeTy &NewCs = N->getState()->get<ConstraintRange>(); ConstraintRangeTy::Factory &CF = @@ -2865,10 +2863,19 @@ PathDiagnosticPieceRef FalsePositiveRefutationBRVisitor::VisitNode( for (auto const &C : NewCs) { const SymbolRef &Sym = C.first; if (!Constraints.contains(Sym)) { + // This symbol is new, just add the constraint. + Constraints = CF.add(Constraints, Sym, C.second); + } else if (OverwriteConstraintsOnExistingSyms) { + // Overwrite the associated constraint of the Symbol. + Constraints = CF.remove(Constraints, Sym); Constraints = CF.add(Constraints, Sym, C.second); } } +} +PathDiagnosticPieceRef FalsePositiveRefutationBRVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &, PathSensitiveBugReport &) { + addConstraints(N, /*OverwriteConstraintsOnExistingSyms=*/false); return nullptr; } diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 168d6fe6ec48f..78d13ddfb773c 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -172,23 +172,9 @@ AnalysisDeclContext *CallEvent::getCalleeAnalysisDeclContext() const { if (!D) return nullptr; - // TODO: For now we skip functions without definitions, even if we have - // our own getDecl(), because it's hard to find out which re-declaration - // is going to be used, and usually clients don't really care about this - // situation because there's a loss of precision anyway because we cannot - // inline the call. - RuntimeDefinition RD = getRuntimeDefinition(); - if (!RD.getDecl()) - return nullptr; - AnalysisDeclContext *ADC = LCtx->getAnalysisDeclContext()->getManager()->getContext(D); - // TODO: For now we skip virtual functions, because this also rises - // the problem of which decl to use, but now it's across different classes. - if (RD.mayHaveOtherDefinitions() || RD.getDecl() != ADC->getDecl()) - return nullptr; - return ADC; } @@ -222,39 +208,17 @@ CallEvent::getCalleeStackFrame(unsigned BlockCount) const { return ADC->getManager()->getStackFrame(ADC, LCtx, E, B, BlockCount, Idx); } -const VarRegion *CallEvent::getParameterLocation(unsigned Index, - unsigned BlockCount) const { +const ParamVarRegion +*CallEvent::getParameterLocation(unsigned Index, unsigned BlockCount) const { const StackFrameContext *SFC = getCalleeStackFrame(BlockCount); // We cannot construct a VarRegion without a stack frame. if (!SFC) return nullptr; - // Retrieve parameters of the definition, which are different from - // CallEvent's parameters() because getDecl() isn't necessarily - // the definition. SFC contains the definition that would be used - // during analysis. - const Decl *D = SFC->getDecl(); - - // TODO: Refactor into a virtual method of CallEvent, like parameters(). - const ParmVarDecl *PVD = nullptr; - if (const auto *FD = dyn_cast<FunctionDecl>(D)) - PVD = FD->parameters()[Index]; - else if (const auto *BD = dyn_cast<BlockDecl>(D)) - PVD = BD->parameters()[Index]; - else if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) - PVD = MD->parameters()[Index]; - else if (const auto *CD = dyn_cast<CXXConstructorDecl>(D)) - PVD = CD->parameters()[Index]; - assert(PVD && "Unexpected Decl kind!"); - - const VarRegion *VR = - State->getStateManager().getRegionManager().getVarRegion(PVD, SFC); - - // This sanity check would fail if our parameter declaration doesn't - // correspond to the stack frame's function declaration. - assert(VR->getStackFrame() == SFC); - - return VR; + const ParamVarRegion *PVR = + State->getStateManager().getRegionManager().getParamVarRegion( + getOriginExpr(), Index, SFC); + return PVR; } /// Returns true if a type is a pointer-to-const or reference-to-const @@ -325,8 +289,9 @@ ProgramStateRef CallEvent::invalidateRegions(unsigned BlockCount, if (getKind() != CE_CXXAllocator) if (isArgumentConstructedDirectly(Idx)) if (auto AdjIdx = getAdjustedParameterIndex(Idx)) - if (const VarRegion *VR = getParameterLocation(*AdjIdx, BlockCount)) - ValuesToInvalidate.push_back(loc::MemRegionVal(VR)); + if (const TypedValueRegion *TVR = + getParameterLocation(*AdjIdx, BlockCount)) + ValuesToInvalidate.push_back(loc::MemRegionVal(TVR)); } // Invalidate designated regions using the batch invalidation API. @@ -450,8 +415,7 @@ void CallEvent::dump(raw_ostream &Out) const { return; } - // FIXME: a string representation of the kind would be nice. - Out << "Unknown call (type " << getKind() << ")"; + Out << "Unknown call (type " << getKindAsString() << ")"; } bool CallEvent::isCallStmt(const Stmt *S) { @@ -515,8 +479,7 @@ static void addParameterValuesToBindings(const StackFrameContext *CalleeCtx, unsigned Idx = 0; ArrayRef<ParmVarDecl*>::iterator I = parameters.begin(), E = parameters.end(); for (; I != E && Idx < NumArgs; ++I, ++Idx) { - const ParmVarDecl *ParamDecl = *I; - assert(ParamDecl && "Formal parameter has no decl?"); + assert(*I && "Formal parameter has no decl?"); // TODO: Support allocator calls. if (Call.getKind() != CE_CXXAllocator) @@ -528,7 +491,8 @@ static void addParameterValuesToBindings(const StackFrameContext *CalleeCtx, // which makes getArgSVal() fail and return UnknownVal. SVal ArgVal = Call.getArgSVal(Idx); if (!ArgVal.isUnknown()) { - Loc ParamLoc = SVB.makeLoc(MRMgr.getVarRegion(ParamDecl, CalleeCtx)); + Loc ParamLoc = SVB.makeLoc( + MRMgr.getParamVarRegion(Call.getOriginExpr(), Idx, CalleeCtx)); Bindings.push_back(std::make_pair(ParamLoc, ArgVal)); } } @@ -536,6 +500,37 @@ static void addParameterValuesToBindings(const StackFrameContext *CalleeCtx, // FIXME: Variadic arguments are not handled at all right now. } +const ConstructionContext *CallEvent::getConstructionContext() const { + const StackFrameContext *StackFrame = getCalleeStackFrame(0); + if (!StackFrame) + return nullptr; + + const CFGElement Element = StackFrame->getCallSiteCFGElement(); + if (const auto Ctor = Element.getAs<CFGConstructor>()) { + return Ctor->getConstructionContext(); + } + + if (const auto RecCall = Element.getAs<CFGCXXRecordTypedCall>()) { + return RecCall->getConstructionContext(); + } + + return nullptr; +} + +Optional<SVal> +CallEvent::getReturnValueUnderConstruction() const { + const auto *CC = getConstructionContext(); + if (!CC) + return None; + + EvalCallOptions CallOpts; + ExprEngine &Engine = getState()->getStateManager().getOwningEngine(); + SVal RetVal = + Engine.computeObjectUnderConstruction(getOriginExpr(), getState(), + getLocationContext(), CC, CallOpts); + return RetVal; +} + ArrayRef<ParmVarDecl*> AnyFunctionCall::parameters() const { const FunctionDecl *D = getDecl(); if (!D) @@ -565,7 +560,7 @@ RuntimeDefinition AnyFunctionCall::getRuntimeDefinition() const { return RuntimeDefinition(Decl); } - SubEngine &Engine = getState()->getStateManager().getOwningEngine(); + ExprEngine &Engine = getState()->getStateManager().getOwningEngine(); AnalyzerOptions &Opts = Engine.getAnalysisManager().options; // Try to get CTU definition only if CTUDir is provided. @@ -889,24 +884,22 @@ void BlockCall::getInitialStackFrameContents(const StackFrameContext *CalleeCtx, Params); } -SVal CXXConstructorCall::getCXXThisVal() const { +SVal AnyCXXConstructorCall::getCXXThisVal() const { if (Data) return loc::MemRegionVal(static_cast<const MemRegion *>(Data)); return UnknownVal(); } -void CXXConstructorCall::getExtraInvalidatedValues(ValueList &Values, +void AnyCXXConstructorCall::getExtraInvalidatedValues(ValueList &Values, RegionAndSymbolInvalidationTraits *ETraits) const { - if (Data) { - loc::MemRegionVal MV(static_cast<const MemRegion *>(Data)); - if (SymbolRef Sym = MV.getAsSymbol(true)) - ETraits->setTrait(Sym, - RegionAndSymbolInvalidationTraits::TK_SuppressEscape); - Values.push_back(MV); - } + SVal V = getCXXThisVal(); + if (SymbolRef Sym = V.getAsSymbol(true)) + ETraits->setTrait(Sym, + RegionAndSymbolInvalidationTraits::TK_SuppressEscape); + Values.push_back(V); } -void CXXConstructorCall::getInitialStackFrameContents( +void AnyCXXConstructorCall::getInitialStackFrameContents( const StackFrameContext *CalleeCtx, BindingsTy &Bindings) const { AnyFunctionCall::getInitialStackFrameContents(CalleeCtx, Bindings); @@ -920,6 +913,14 @@ void CXXConstructorCall::getInitialStackFrameContents( } } +const StackFrameContext * +CXXInheritedConstructorCall::getInheritingStackFrame() const { + const StackFrameContext *SFC = getLocationContext()->getStackFrame(); + while (isa<CXXInheritedCtorInitExpr>(SFC->getCallSite())) + SFC = SFC->getParent()->getStackFrame(); + return SFC; +} + SVal CXXDestructorCall::getCXXThisVal() const { if (Data) return loc::MemRegionVal(DtorDataTy::getFromOpaqueValue(Data).getPointer()); @@ -967,14 +968,6 @@ void ObjCMethodCall::getExtraInvalidatedValues( Values.push_back(getReceiverSVal()); } -SVal ObjCMethodCall::getSelfSVal() const { - const LocationContext *LCtx = getLocationContext(); - const ImplicitParamDecl *SelfDecl = LCtx->getSelfDecl(); - if (!SelfDecl) - return SVal(); - return getState()->getSVal(getState()->getRegion(SelfDecl, LCtx)); -} - SVal ObjCMethodCall::getReceiverSVal() const { // FIXME: Is this the best way to handle class receivers? if (!isInstanceMessage()) @@ -986,7 +979,7 @@ SVal ObjCMethodCall::getReceiverSVal() const { // An instance message with no expression means we are sending to super. // In this case the object reference is the same as 'self'. assert(getOriginExpr()->getReceiverKind() == ObjCMessageExpr::SuperInstance); - SVal SelfVal = getSelfSVal(); + SVal SelfVal = getState()->getSelfSVal(getLocationContext()); assert(SelfVal.isValid() && "Calling super but not in ObjC method"); return SelfVal; } @@ -1000,8 +993,9 @@ bool ObjCMethodCall::isReceiverSelfOrSuper() const { return false; SVal RecVal = getSVal(getOriginExpr()->getInstanceReceiver()); + SVal SelfVal = getState()->getSelfSVal(getLocationContext()); - return (RecVal == getSelfSVal()); + return (RecVal == SelfVal); } SourceRange ObjCMethodCall::getSourceRange() const { @@ -1168,23 +1162,77 @@ static const ObjCMethodDecl *findDefiningRedecl(const ObjCMethodDecl *MD) { return MD; } -static bool isCallToSelfClass(const ObjCMessageExpr *ME) { - const Expr* InstRec = ME->getInstanceReceiver(); - if (!InstRec) - return false; - const auto *InstRecIg = dyn_cast<DeclRefExpr>(InstRec->IgnoreParenImpCasts()); +struct PrivateMethodKey { + const ObjCInterfaceDecl *Interface; + Selector LookupSelector; + bool IsClassMethod; +}; - // Check that receiver is called 'self'. - if (!InstRecIg || !InstRecIg->getFoundDecl() || - !InstRecIg->getFoundDecl()->getName().equals("self")) - return false; +namespace llvm { +template <> struct DenseMapInfo<PrivateMethodKey> { + using InterfaceInfo = DenseMapInfo<const ObjCInterfaceDecl *>; + using SelectorInfo = DenseMapInfo<Selector>; - // Check that the method name is 'class'. - if (ME->getSelector().getNumArgs() != 0 || - !ME->getSelector().getNameForSlot(0).equals("class")) - return false; + static inline PrivateMethodKey getEmptyKey() { + return {InterfaceInfo::getEmptyKey(), SelectorInfo::getEmptyKey(), false}; + } - return true; + static inline PrivateMethodKey getTombstoneKey() { + return {InterfaceInfo::getTombstoneKey(), SelectorInfo::getTombstoneKey(), + true}; + } + + static unsigned getHashValue(const PrivateMethodKey &Key) { + return llvm::hash_combine( + llvm::hash_code(InterfaceInfo::getHashValue(Key.Interface)), + llvm::hash_code(SelectorInfo::getHashValue(Key.LookupSelector)), + Key.IsClassMethod); + } + + static bool isEqual(const PrivateMethodKey &LHS, + const PrivateMethodKey &RHS) { + return InterfaceInfo::isEqual(LHS.Interface, RHS.Interface) && + SelectorInfo::isEqual(LHS.LookupSelector, RHS.LookupSelector) && + LHS.IsClassMethod == RHS.IsClassMethod; + } +}; +} // end namespace llvm + +static const ObjCMethodDecl * +lookupRuntimeDefinition(const ObjCInterfaceDecl *Interface, + Selector LookupSelector, bool InstanceMethod) { + // Repeatedly calling lookupPrivateMethod() is expensive, especially + // when in many cases it returns null. We cache the results so + // that repeated queries on the same ObjCIntefaceDecl and Selector + // don't incur the same cost. On some test cases, we can see the + // same query being issued thousands of times. + // + // NOTE: This cache is essentially a "global" variable, but it + // only gets lazily created when we get here. The value of the + // cache probably comes from it being global across ExprEngines, + // where the same queries may get issued. If we are worried about + // concurrency, or possibly loading/unloading ASTs, etc., we may + // need to revisit this someday. In terms of memory, this table + // stays around until clang quits, which also may be bad if we + // need to release memory. + using PrivateMethodCache = + llvm::DenseMap<PrivateMethodKey, Optional<const ObjCMethodDecl *>>; + + static PrivateMethodCache PMC; + Optional<const ObjCMethodDecl *> &Val = + PMC[{Interface, LookupSelector, InstanceMethod}]; + + // Query lookupPrivateMethod() if the cache does not hit. + if (!Val.hasValue()) { + Val = Interface->lookupPrivateMethod(LookupSelector, InstanceMethod); + + if (!*Val) { + // Query 'lookupMethod' as a backup. + Val = Interface->lookupMethod(LookupSelector, InstanceMethod); + } + } + + return Val.getValue(); } RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { @@ -1194,8 +1242,9 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { if (E->isInstanceMessage()) { // Find the receiver type. - const ObjCObjectPointerType *ReceiverT = nullptr; + const ObjCObjectType *ReceiverT = nullptr; bool CanBeSubClassed = false; + bool LookingForInstanceMethod = true; QualType SupersType = E->getSuperType(); const MemRegion *Receiver = nullptr; @@ -1203,7 +1252,7 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { // The receiver is guaranteed to be 'super' in this case. // Super always means the type of immediate predecessor to the method // where the call occurs. - ReceiverT = cast<ObjCObjectPointerType>(SupersType); + ReceiverT = cast<ObjCObjectPointerType>(SupersType)->getObjectType(); } else { Receiver = getReceiverSVal().getAsRegion(); if (!Receiver) @@ -1218,100 +1267,59 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { QualType DynType = DTI.getType(); CanBeSubClassed = DTI.canBeASubClass(); - ReceiverT = dyn_cast<ObjCObjectPointerType>(DynType.getCanonicalType()); - if (ReceiverT && CanBeSubClassed) - if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterfaceDecl()) - if (!canBeOverridenInSubclass(IDecl, Sel)) - CanBeSubClassed = false; - } + const auto *ReceiverDynT = + dyn_cast<ObjCObjectPointerType>(DynType.getCanonicalType()); + + if (ReceiverDynT) { + ReceiverT = ReceiverDynT->getObjectType(); - // Handle special cases of '[self classMethod]' and - // '[[self class] classMethod]', which are treated by the compiler as - // instance (not class) messages. We will statically dispatch to those. - if (auto *PT = dyn_cast_or_null<ObjCObjectPointerType>(ReceiverT)) { - // For [self classMethod], return the compiler visible declaration. - if (PT->getObjectType()->isObjCClass() && - Receiver == getSelfSVal().getAsRegion()) - return RuntimeDefinition(findDefiningRedecl(E->getMethodDecl())); - - // Similarly, handle [[self class] classMethod]. - // TODO: We are currently doing a syntactic match for this pattern with is - // limiting as the test cases in Analysis/inlining/InlineObjCClassMethod.m - // shows. A better way would be to associate the meta type with the symbol - // using the dynamic type info tracking and use it here. We can add a new - // SVal for ObjC 'Class' values that know what interface declaration they - // come from. Then 'self' in a class method would be filled in with - // something meaningful in ObjCMethodCall::getReceiverSVal() and we could - // do proper dynamic dispatch for class methods just like we do for - // instance methods now. - if (E->getInstanceReceiver()) - if (const auto *M = dyn_cast<ObjCMessageExpr>(E->getInstanceReceiver())) - if (isCallToSelfClass(M)) + // It can be actually class methods called with Class object as a + // receiver. This type of messages is treated by the compiler as + // instance (not class). + if (ReceiverT->isObjCClass()) { + + SVal SelfVal = getState()->getSelfSVal(getLocationContext()); + // For [self classMethod], return compiler visible declaration. + if (Receiver == SelfVal.getAsRegion()) { return RuntimeDefinition(findDefiningRedecl(E->getMethodDecl())); + } + + // Otherwise, let's check if we know something about the type + // inside of this class object. + if (SymbolRef ReceiverSym = getReceiverSVal().getAsSymbol()) { + DynamicTypeInfo DTI = + getClassObjectDynamicTypeInfo(getState(), ReceiverSym); + if (DTI.isValid()) { + // Let's use this type for lookup. + ReceiverT = + cast<ObjCObjectType>(DTI.getType().getCanonicalType()); + + CanBeSubClassed = DTI.canBeASubClass(); + // And it should be a class method instead. + LookingForInstanceMethod = false; + } + } + } + + if (CanBeSubClassed) + if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterface()) + // Even if `DynamicTypeInfo` told us that it can be + // not necessarily this type, but its descendants, we still want + // to check again if this selector can be actually overridden. + CanBeSubClassed = canBeOverridenInSubclass(IDecl, Sel); + } } // Lookup the instance method implementation. if (ReceiverT) - if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterfaceDecl()) { - // Repeatedly calling lookupPrivateMethod() is expensive, especially - // when in many cases it returns null. We cache the results so - // that repeated queries on the same ObjCIntefaceDecl and Selector - // don't incur the same cost. On some test cases, we can see the - // same query being issued thousands of times. - // - // NOTE: This cache is essentially a "global" variable, but it - // only gets lazily created when we get here. The value of the - // cache probably comes from it being global across ExprEngines, - // where the same queries may get issued. If we are worried about - // concurrency, or possibly loading/unloading ASTs, etc., we may - // need to revisit this someday. In terms of memory, this table - // stays around until clang quits, which also may be bad if we - // need to release memory. - using PrivateMethodKey = std::pair<const ObjCInterfaceDecl *, Selector>; - using PrivateMethodCache = - llvm::DenseMap<PrivateMethodKey, Optional<const ObjCMethodDecl *>>; - - static PrivateMethodCache PMC; - Optional<const ObjCMethodDecl *> &Val = PMC[std::make_pair(IDecl, Sel)]; - - // Query lookupPrivateMethod() if the cache does not hit. - if (!Val.hasValue()) { - Val = IDecl->lookupPrivateMethod(Sel); - - // If the method is a property accessor, we should try to "inline" it - // even if we don't actually have an implementation. - if (!*Val) - if (const ObjCMethodDecl *CompileTimeMD = E->getMethodDecl()) - if (CompileTimeMD->isPropertyAccessor()) { - if (!CompileTimeMD->getSelfDecl() && - isa<ObjCCategoryDecl>(CompileTimeMD->getDeclContext())) { - // If the method is an accessor in a category, and it doesn't - // have a self declaration, first - // try to find the method in a class extension. This - // works around a bug in Sema where multiple accessors - // are synthesized for properties in class - // extensions that are redeclared in a category and the - // the implicit parameters are not filled in for - // the method on the category. - // This ensures we find the accessor in the extension, which - // has the implicit parameters filled in. - auto *ID = CompileTimeMD->getClassInterface(); - for (auto *CatDecl : ID->visible_extensions()) { - Val = CatDecl->getMethod(Sel, - CompileTimeMD->isInstanceMethod()); - if (*Val) - break; - } - } - if (!*Val) - Val = IDecl->lookupInstanceMethod(Sel); - } - } + if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterface()) { + const ObjCMethodDecl *MD = + lookupRuntimeDefinition(IDecl, Sel, LookingForInstanceMethod); - const ObjCMethodDecl *MD = Val.getValue(); if (MD && !MD->hasBody()) MD = MD->getCanonicalDecl(); + if (CanBeSubClassed) return RuntimeDefinition(MD, Receiver); else @@ -1392,17 +1400,20 @@ CallEventManager::getCaller(const StackFrameContext *CalleeCtx, if (CallEventRef<> Out = getCall(CallSite, State, CallerCtx)) return Out; - // All other cases are handled by getCall. - assert(isa<CXXConstructExpr>(CallSite) && - "This is not an inlineable statement"); - SValBuilder &SVB = State->getStateManager().getSValBuilder(); const auto *Ctor = cast<CXXMethodDecl>(CalleeCtx->getDecl()); Loc ThisPtr = SVB.getCXXThis(Ctor, CalleeCtx); SVal ThisVal = State->getSVal(ThisPtr); - return getCXXConstructorCall(cast<CXXConstructExpr>(CallSite), - ThisVal.getAsRegion(), State, CallerCtx); + if (const auto *CE = dyn_cast<CXXConstructExpr>(CallSite)) + return getCXXConstructorCall(CE, ThisVal.getAsRegion(), State, CallerCtx); + else if (const auto *CIE = dyn_cast<CXXInheritedCtorInitExpr>(CallSite)) + return getCXXInheritedConstructorCall(CIE, ThisVal.getAsRegion(), State, + CallerCtx); + else { + // All other cases are handled by getCall. + llvm_unreachable("This is not an inlineable statement"); + } } // Fall back to the CFG. The only thing we haven't handled yet is diff --git a/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp b/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp index 11693132de687..cae728815b412 100644 --- a/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp @@ -13,6 +13,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" #include "clang/AST/Decl.h" #include "clang/AST/Expr.h" +#include "clang/Lex/Preprocessor.h" namespace clang { @@ -109,6 +110,43 @@ Nullability getNullabilityAnnotation(QualType Type) { return Nullability::Unspecified; } +llvm::Optional<int> tryExpandAsInteger(StringRef Macro, + const Preprocessor &PP) { + const auto *MacroII = PP.getIdentifierInfo(Macro); + if (!MacroII) + return llvm::None; + const MacroInfo *MI = PP.getMacroInfo(MacroII); + if (!MI) + return llvm::None; + + // Filter out parens. + std::vector<Token> FilteredTokens; + FilteredTokens.reserve(MI->tokens().size()); + for (auto &T : MI->tokens()) + if (!T.isOneOf(tok::l_paren, tok::r_paren)) + FilteredTokens.push_back(T); + + // Parse an integer at the end of the macro definition. + const Token &T = FilteredTokens.back(); + // FIXME: EOF macro token coming from a PCH file on macOS while marked as + // literal, doesn't contain any literal data + if (!T.isLiteral() || !T.getLiteralData()) + return llvm::None; + StringRef ValueStr = StringRef(T.getLiteralData(), T.getLength()); + llvm::APInt IntValue; + constexpr unsigned AutoSenseRadix = 0; + if (ValueStr.getAsInteger(AutoSenseRadix, IntValue)) + return llvm::None; + + // Parse an optional minus sign. + size_t Size = FilteredTokens.size(); + if (Size >= 2) { + if (FilteredTokens[Size - 2].is(tok::minus)) + IntValue = -IntValue; + } + + return IntValue.getSExtValue(); +} -} // end namespace ento -} // end namespace clang +} // namespace ento +} // namespace clang diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp index a9361837cf68b..86cecf6524f03 100644 --- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -61,12 +61,12 @@ void CheckerManager::finishedCheckerRegistration() { } void CheckerManager::reportInvalidCheckerOptionValue( - const CheckerBase *C, StringRef OptionName, StringRef ExpectedValueDesc) { + const CheckerBase *C, StringRef OptionName, + StringRef ExpectedValueDesc) const { - Context.getDiagnostics() - .Report(diag::err_analyzer_checker_option_invalid_input) - << (llvm::Twine() + C->getTagDescription() + ":" + OptionName).str() - << ExpectedValueDesc; + getDiagnostics().Report(diag::err_analyzer_checker_option_invalid_input) + << (llvm::Twine() + C->getTagDescription() + ":" + OptionName).str() + << ExpectedValueDesc; } //===----------------------------------------------------------------------===// @@ -243,13 +243,13 @@ void CheckerManager::runCheckersForObjCMessage(ObjCMessageVisitKind visitKind, const ObjCMethodCall &msg, ExprEngine &Eng, bool WasInlined) { - auto &checkers = getObjCMessageCheckers(visitKind); + const auto &checkers = getObjCMessageCheckers(visitKind); CheckObjCMessageContext C(visitKind, checkers, msg, Eng, WasInlined); expandGraphWithCheckers(C, Dst, Src); } const std::vector<CheckerManager::CheckObjCMessageFunc> & -CheckerManager::getObjCMessageCheckers(ObjCMessageVisitKind Kind) { +CheckerManager::getObjCMessageCheckers(ObjCMessageVisitKind Kind) const { switch (Kind) { case ObjCMessageVisitKind::Pre: return PreObjCMessageCheckers; @@ -507,35 +507,38 @@ namespace { using CheckersTy = std::vector<CheckerManager::CheckNewAllocatorFunc>; const CheckersTy &Checkers; - const CXXNewExpr *NE; - SVal Target; + const CXXAllocatorCall &Call; bool WasInlined; ExprEngine &Eng; - CheckNewAllocatorContext(const CheckersTy &Checkers, const CXXNewExpr *NE, - SVal Target, bool WasInlined, ExprEngine &Eng) - : Checkers(Checkers), NE(NE), Target(Target), WasInlined(WasInlined), - Eng(Eng) {} + CheckNewAllocatorContext(const CheckersTy &Checkers, + const CXXAllocatorCall &Call, bool WasInlined, + ExprEngine &Eng) + : Checkers(Checkers), Call(Call), WasInlined(WasInlined), Eng(Eng) {} CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } CheckersTy::const_iterator checkers_end() { return Checkers.end(); } void runChecker(CheckerManager::CheckNewAllocatorFunc checkFn, NodeBuilder &Bldr, ExplodedNode *Pred) { - ProgramPoint L = PostAllocatorCall(NE, Pred->getLocationContext()); + ProgramPoint L = + PostAllocatorCall(Call.getOriginExpr(), Pred->getLocationContext()); CheckerContext C(Bldr, Eng, Pred, L, WasInlined); - checkFn(NE, Target, C); + checkFn(cast<CXXAllocatorCall>(*Call.cloneWithState(Pred->getState())), + C); } }; } // namespace -void CheckerManager::runCheckersForNewAllocator( - const CXXNewExpr *NE, SVal Target, ExplodedNodeSet &Dst, ExplodedNode *Pred, - ExprEngine &Eng, bool WasInlined) { +void CheckerManager::runCheckersForNewAllocator(const CXXAllocatorCall &Call, + ExplodedNodeSet &Dst, + ExplodedNode *Pred, + ExprEngine &Eng, + bool WasInlined) { ExplodedNodeSet Src; Src.insert(Pred); - CheckNewAllocatorContext C(NewAllocatorCheckers, NE, Target, WasInlined, Eng); + CheckNewAllocatorContext C(NewAllocatorCheckers, Call, WasInlined, Eng); expandGraphWithCheckers(C, Dst, Src); } @@ -650,8 +653,9 @@ CheckerManager::runCheckersForEvalAssume(ProgramStateRef state, void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, const CallEvent &Call, - ExprEngine &Eng) { - for (const auto Pred : Src) { + ExprEngine &Eng, + const EvalCallOptions &CallOpts) { + for (auto *const Pred : Src) { bool anyEvaluated = false; ExplodedNodeSet checkDst; @@ -662,10 +666,8 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, // TODO: Support the situation when the call doesn't correspond // to any Expr. ProgramPoint L = ProgramPoint::getProgramPoint( - cast<CallExpr>(Call.getOriginExpr()), - ProgramPoint::PostStmtKind, - Pred->getLocationContext(), - EvalCallChecker.Checker); + Call.getOriginExpr(), ProgramPoint::PostStmtKind, + Pred->getLocationContext(), EvalCallChecker.Checker); bool evaluated = false; { // CheckerContext generates transitions(populates checkDest) on // destruction, so introduce the scope to make sure it gets properly @@ -687,7 +689,7 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, // If none of the checkers evaluated the call, ask ExprEngine to handle it. if (!anyEvaluated) { NodeBuilder B(Pred, Dst, Eng.getBuilderContext()); - Eng.defaultEvalCall(B, Pred, Call); + Eng.defaultEvalCall(B, Pred, Call, CallOpts); } } } @@ -902,8 +904,3 @@ CheckerManager::getCachedStmtCheckersFor(const Stmt *S, bool isPreVisit) { Checkers.push_back(Info.CheckFn); return Checkers; } - -CheckerManager::~CheckerManager() { - for (const auto &CheckerDtor : CheckerDtors) - CheckerDtor(); -} diff --git a/clang/lib/StaticAnalyzer/Core/CheckerRegistryData.cpp b/clang/lib/StaticAnalyzer/Core/CheckerRegistryData.cpp new file mode 100644 index 0000000000000..1b3e8b11549dd --- /dev/null +++ b/clang/lib/StaticAnalyzer/Core/CheckerRegistryData.cpp @@ -0,0 +1,241 @@ +//===- CheckerRegistry.h - Maintains all available checkers -----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Core/CheckerRegistryData.h" +#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" +#include "llvm/ADT/Twine.h" +#include <map> + +using namespace clang; +using namespace ento; + +//===----------------------------------------------------------------------===// +// Methods of CmdLineOption, PackageInfo and CheckerInfo. +//===----------------------------------------------------------------------===// + +LLVM_DUMP_METHOD void CmdLineOption::dump() const { + dumpToStream(llvm::errs()); +} + +LLVM_DUMP_METHOD void +CmdLineOption::dumpToStream(llvm::raw_ostream &Out) const { + // The description can be just checked in Checkers.inc, the point here is to + // debug whether we succeeded in parsing it. + Out << OptionName << " (" << OptionType << ", " + << (IsHidden ? "hidden, " : "") << DevelopmentStatus << ") default: \"" + << DefaultValStr; +} + +static StringRef toString(StateFromCmdLine Kind) { + switch (Kind) { + case StateFromCmdLine::State_Disabled: + return "Disabled"; + case StateFromCmdLine::State_Enabled: + return "Enabled"; + case StateFromCmdLine::State_Unspecified: + return "Unspecified"; + } + llvm_unreachable("Unhandled StateFromCmdLine enum"); +} + +LLVM_DUMP_METHOD void CheckerInfo::dump() const { dumpToStream(llvm::errs()); } + +LLVM_DUMP_METHOD void CheckerInfo::dumpToStream(llvm::raw_ostream &Out) const { + // The description can be just checked in Checkers.inc, the point here is to + // debug whether we succeeded in parsing it. Same with documentation uri. + Out << FullName << " (" << toString(State) << (IsHidden ? ", hidden" : "") + << ")\n"; + Out << " Options:\n"; + for (const CmdLineOption &Option : CmdLineOptions) { + Out << " "; + Option.dumpToStream(Out); + Out << '\n'; + } + Out << " Dependencies:\n"; + for (const CheckerInfo *Dependency : Dependencies) { + Out << " " << Dependency->FullName << '\n'; + } + Out << " Weak dependencies:\n"; + for (const CheckerInfo *Dependency : WeakDependencies) { + Out << " " << Dependency->FullName << '\n'; + } +} + +LLVM_DUMP_METHOD void PackageInfo::dump() const { dumpToStream(llvm::errs()); } + +LLVM_DUMP_METHOD void PackageInfo::dumpToStream(llvm::raw_ostream &Out) const { + Out << FullName << "\n"; + Out << " Options:\n"; + for (const CmdLineOption &Option : CmdLineOptions) { + Out << " "; + Option.dumpToStream(Out); + Out << '\n'; + } +} + +static constexpr char PackageSeparator = '.'; + +static bool isInPackage(const CheckerInfo &Checker, StringRef PackageName) { + // Does the checker's full name have the package as a prefix? + if (!Checker.FullName.startswith(PackageName)) + return false; + + // Is the package actually just the name of a specific checker? + if (Checker.FullName.size() == PackageName.size()) + return true; + + // Is the checker in the package (or a subpackage)? + if (Checker.FullName[PackageName.size()] == PackageSeparator) + return true; + + return false; +} + +CheckerInfoListRange +CheckerRegistryData::getMutableCheckersForCmdLineArg(StringRef CmdLineArg) { + auto It = checker_registry::binaryFind(Checkers, CmdLineArg); + + if (!isInPackage(*It, CmdLineArg)) + return {Checkers.end(), Checkers.end()}; + + // See how large the package is. + // If the package doesn't exist, assume the option refers to a single + // checker. + size_t Size = 1; + llvm::StringMap<size_t>::const_iterator PackageSize = + PackageSizes.find(CmdLineArg); + + if (PackageSize != PackageSizes.end()) + Size = PackageSize->getValue(); + + return {It, It + Size}; +} +//===----------------------------------------------------------------------===// +// Printing functions. +//===----------------------------------------------------------------------===// + +void CheckerRegistryData::printCheckerWithDescList( + const AnalyzerOptions &AnOpts, raw_ostream &Out, + size_t MaxNameChars) const { + // FIXME: Print available packages. + + Out << "CHECKERS:\n"; + + // Find the maximum option length. + size_t OptionFieldWidth = 0; + for (const auto &Checker : Checkers) { + // Limit the amount of padding we are willing to give up for alignment. + // Package.Name Description [Hidden] + size_t NameLength = Checker.FullName.size(); + if (NameLength <= MaxNameChars) + OptionFieldWidth = std::max(OptionFieldWidth, NameLength); + } + + const size_t InitialPad = 2; + + auto Print = [=](llvm::raw_ostream &Out, const CheckerInfo &Checker, + StringRef Description) { + AnalyzerOptions::printFormattedEntry(Out, {Checker.FullName, Description}, + InitialPad, OptionFieldWidth); + Out << '\n'; + }; + + for (const auto &Checker : Checkers) { + // The order of this if branches is significant, we wouldn't like to display + // developer checkers even in the alpha output. For example, + // alpha.cplusplus.IteratorModeling is a modeling checker, hence it's hidden + // by default, and users (even when the user is a developer of an alpha + // checker) shouldn't normally tinker with whether they should be enabled. + + if (Checker.IsHidden) { + if (AnOpts.ShowCheckerHelpDeveloper) + Print(Out, Checker, Checker.Desc); + continue; + } + + if (Checker.FullName.startswith("alpha")) { + if (AnOpts.ShowCheckerHelpAlpha) + Print(Out, Checker, + ("(Enable only for development!) " + Checker.Desc).str()); + continue; + } + + if (AnOpts.ShowCheckerHelp) + Print(Out, Checker, Checker.Desc); + } +} + +void CheckerRegistryData::printEnabledCheckerList(raw_ostream &Out) const { + for (const auto *i : EnabledCheckers) + Out << i->FullName << '\n'; +} + +void CheckerRegistryData::printCheckerOptionList(const AnalyzerOptions &AnOpts, + raw_ostream &Out) const { + Out << "OVERVIEW: Clang Static Analyzer Checker and Package Option List\n\n"; + Out << "USAGE: -analyzer-config <OPTION1=VALUE,OPTION2=VALUE,...>\n\n"; + Out << " -analyzer-config OPTION1=VALUE, -analyzer-config " + "OPTION2=VALUE, ...\n\n"; + Out << "OPTIONS:\n\n"; + + // It's usually ill-advised to use multimap, but clang will terminate after + // this function. + std::multimap<StringRef, const CmdLineOption &> OptionMap; + + for (const CheckerInfo &Checker : Checkers) { + for (const CmdLineOption &Option : Checker.CmdLineOptions) { + OptionMap.insert({Checker.FullName, Option}); + } + } + + for (const PackageInfo &Package : Packages) { + for (const CmdLineOption &Option : Package.CmdLineOptions) { + OptionMap.insert({Package.FullName, Option}); + } + } + + auto Print = [](llvm::raw_ostream &Out, StringRef FullOption, + StringRef Desc) { + AnalyzerOptions::printFormattedEntry(Out, {FullOption, Desc}, + /*InitialPad*/ 2, + /*EntryWidth*/ 50, + /*MinLineWidth*/ 90); + Out << "\n\n"; + }; + for (const std::pair<const StringRef, const CmdLineOption &> &Entry : + OptionMap) { + const CmdLineOption &Option = Entry.second; + std::string FullOption = (Entry.first + ":" + Option.OptionName).str(); + + std::string Desc = + ("(" + Option.OptionType + ") " + Option.Description + " (default: " + + (Option.DefaultValStr.empty() ? "\"\"" : Option.DefaultValStr) + ")") + .str(); + + // The list of these if branches is significant, we wouldn't like to + // display hidden alpha checker options for + // -analyzer-checker-option-help-alpha. + + if (Option.IsHidden) { + if (AnOpts.ShowCheckerOptionDeveloperList) + Print(Out, FullOption, Desc); + continue; + } + + if (Option.DevelopmentStatus == "alpha" || + Entry.first.startswith("alpha")) { + if (AnOpts.ShowCheckerOptionAlphaList) + Print(Out, FullOption, + llvm::Twine("(Enable only for development!) " + Desc).str()); + continue; + } + + if (AnOpts.ShowCheckerOptionList) + Print(Out, FullOption, Desc); + } +} diff --git a/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp b/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp index bdae3e605efff..a601370775b45 100644 --- a/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp +++ b/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp @@ -9,13 +9,18 @@ #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" // Common strings used for the "category" of many static analyzer issues. -namespace clang { namespace ento { namespace categories { +namespace clang { +namespace ento { +namespace categories { -const char * const CoreFoundationObjectiveC = "Core Foundation/Objective-C"; -const char * const LogicError = "Logic error"; -const char * const MemoryRefCount = - "Memory (Core Foundation/Objective-C/OSObject)"; -const char * const MemoryError = "Memory error"; -const char * const UnixAPI = "Unix API"; -const char * const CXXObjectLifecycle = "C++ object lifecycle"; -}}} +const char *const CoreFoundationObjectiveC = "Core Foundation/Objective-C"; +const char *const LogicError = "Logic error"; +const char *const MemoryRefCount = + "Memory (Core Foundation/Objective-C/OSObject)"; +const char *const MemoryError = "Memory error"; +const char *const UnixAPI = "Unix API"; +const char *const CXXObjectLifecycle = "C++ object lifecycle"; +const char *const SecurityError = "Security error"; +} // namespace categories +} // namespace ento +} // namespace clang diff --git a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp index 94cf74de82931..70deb13a8e1ae 100644 --- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp @@ -23,8 +23,8 @@ #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/PathSensitive/BlockCounter.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/FunctionSummary.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/WorkList.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" @@ -52,8 +52,7 @@ STATISTIC(NumPathsExplored, // Core analysis engine. //===----------------------------------------------------------------------===// -static std::unique_ptr<WorkList> generateWorkList(AnalyzerOptions &Opts, - SubEngine &subengine) { +static std::unique_ptr<WorkList> generateWorkList(AnalyzerOptions &Opts) { switch (Opts.getExplorationStrategy()) { case ExplorationStrategyKind::DFS: return WorkList::makeDFS(); @@ -71,9 +70,9 @@ static std::unique_ptr<WorkList> generateWorkList(AnalyzerOptions &Opts, llvm_unreachable("Unknown AnalyzerOptions::ExplorationStrategyKind"); } -CoreEngine::CoreEngine(SubEngine &subengine, FunctionSummariesTy *FS, +CoreEngine::CoreEngine(ExprEngine &exprengine, FunctionSummariesTy *FS, AnalyzerOptions &Opts) - : SubEng(subengine), WList(generateWorkList(Opts, subengine)), + : ExprEng(exprengine), WList(generateWorkList(Opts)), BCounterFactory(G.getAllocator()), FunctionSummaries(FS) {} /// ExecuteWorkList - Run the worklist algorithm for a maximum number of steps. @@ -104,7 +103,7 @@ bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned Steps, WList->setBlockCounter(BCounterFactory.GetEmptyCounter()); if (!InitState) - InitState = SubEng.getInitialState(L); + InitState = ExprEng.getInitialState(L); bool IsNew; ExplodedNode *Node = G.getNode(StartLoc, InitState, false, &IsNew); @@ -113,7 +112,7 @@ bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned Steps, NodeBuilderContext BuilderCtx(*this, StartLoc.getDst(), Node); ExplodedNodeSet DstBegin; - SubEng.processBeginOfFunction(BuilderCtx, Node, DstBegin, StartLoc); + ExprEng.processBeginOfFunction(BuilderCtx, Node, DstBegin, StartLoc); enqueue(DstBegin); } @@ -147,7 +146,7 @@ bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned Steps, dispatchWorkItem(Node, Node->getLocation(), WU); } - SubEng.processEndWorklist(); + ExprEng.processEndWorklist(); return WList->hasWork(); } @@ -172,7 +171,7 @@ void CoreEngine::dispatchWorkItem(ExplodedNode* Pred, ProgramPoint Loc, break; case ProgramPoint::CallExitBeginKind: - SubEng.processCallExit(Pred); + ExprEng.processCallExit(Pred); break; case ProgramPoint::EpsilonKind: { @@ -221,7 +220,7 @@ void CoreEngine::HandleBlockEdge(const BlockEdge &L, ExplodedNode *Pred) { if (L.getSrc()->getTerminator().isVirtualBaseBranch() && L.getDst() == *L.getSrc()->succ_begin()) { ProgramPoint P = L.withTag(getNoteTags().makeNoteTag( - [](BugReporterContext &, BugReport &) -> std::string { + [](BugReporterContext &, PathSensitiveBugReport &) -> std::string { // TODO: Just call out the name of the most derived class // when we know it. return "Virtual base initialization skipped because " @@ -253,17 +252,17 @@ void CoreEngine::HandleBlockEdge(const BlockEdge &L, ExplodedNode *Pred) { } // Process the final state transition. - SubEng.processEndOfFunction(BuilderCtx, Pred, RS); + ExprEng.processEndOfFunction(BuilderCtx, Pred, RS); // This path is done. Don't enqueue any more nodes. return; } - // Call into the SubEngine to process entering the CFGBlock. + // Call into the ExprEngine to process entering the CFGBlock. ExplodedNodeSet dstNodes; BlockEntrance BE(Blk, Pred->getLocationContext()); NodeBuilderWithSinks nodeBuilder(Pred, dstNodes, BuilderCtx, BE); - SubEng.processCFGBlockEntrance(L, nodeBuilder, Pred); + ExprEng.processCFGBlockEntrance(L, nodeBuilder, Pred); // Auto-generate a node. if (!nodeBuilder.hasGeneratedNodes()) { @@ -287,7 +286,7 @@ void CoreEngine::HandleBlockEntrance(const BlockEntrance &L, // Process the entrance of the block. if (Optional<CFGElement> E = L.getFirstElement()) { NodeBuilderContext Ctx(*this, L.getBlock(), Pred); - SubEng.processCFGElement(*E, Pred, 0, &Ctx); + ExprEng.processCFGElement(*E, Pred, 0, &Ctx); } else HandleBlockExit(L.getBlock(), Pred); @@ -367,7 +366,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { builder(Pred, B, cast<IndirectGotoStmt>(Term)->getTarget(), *(B->succ_begin()), this); - SubEng.processIndirectGoto(builder); + ExprEng.processIndirectGoto(builder); return; } @@ -378,7 +377,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { // 'element' variable to a value. // (2) in a terminator, which represents the branch. // - // For (1), subengines will bind a value (i.e., 0 or 1) indicating + // For (1), ExprEngine will bind a value (i.e., 0 or 1) indicating // whether or not collection contains any more elements. We cannot // just test to see if the element is nil because a container can // contain nil elements. @@ -389,7 +388,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { SwitchNodeBuilder builder(Pred, B, cast<SwitchStmt>(Term)->getCond(), this); - SubEng.processSwitch(builder); + ExprEng.processSwitch(builder); return; } @@ -418,7 +417,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { void CoreEngine::HandleCallEnter(const CallEnter &CE, ExplodedNode *Pred) { NodeBuilderContext BuilderCtx(*this, CE.getEntry(), Pred); - SubEng.processCallEnter(BuilderCtx, CE, Pred); + ExprEng.processCallEnter(BuilderCtx, CE, Pred); } void CoreEngine::HandleBranch(const Stmt *Cond, const Stmt *Term, @@ -426,7 +425,7 @@ void CoreEngine::HandleBranch(const Stmt *Cond, const Stmt *Term, assert(B->succ_size() == 2); NodeBuilderContext Ctx(*this, B, Pred); ExplodedNodeSet Dst; - SubEng.processBranch(Cond, Ctx, Pred, Dst, *(B->succ_begin()), + ExprEng.processBranch(Cond, Ctx, Pred, Dst, *(B->succ_begin()), *(B->succ_begin() + 1)); // Enqueue the new frontier onto the worklist. enqueue(Dst); @@ -438,7 +437,7 @@ void CoreEngine::HandleCleanupTemporaryBranch(const CXXBindTemporaryExpr *BTE, assert(B->succ_size() == 2); NodeBuilderContext Ctx(*this, B, Pred); ExplodedNodeSet Dst; - SubEng.processCleanupTemporaryBranch(BTE, Ctx, Pred, Dst, *(B->succ_begin()), + ExprEng.processCleanupTemporaryBranch(BTE, Ctx, Pred, Dst, *(B->succ_begin()), *(B->succ_begin() + 1)); // Enqueue the new frontier onto the worklist. enqueue(Dst); @@ -449,7 +448,7 @@ void CoreEngine::HandleStaticInit(const DeclStmt *DS, const CFGBlock *B, assert(B->succ_size() == 2); NodeBuilderContext Ctx(*this, B, Pred); ExplodedNodeSet Dst; - SubEng.processStaticInitializer(DS, Ctx, Pred, Dst, + ExprEng.processStaticInitializer(DS, Ctx, Pred, Dst, *(B->succ_begin()), *(B->succ_begin()+1)); // Enqueue the new frontier onto the worklist. enqueue(Dst); @@ -464,7 +463,7 @@ void CoreEngine::HandlePostStmt(const CFGBlock *B, unsigned StmtIdx, HandleBlockExit(B, Pred); else { NodeBuilderContext Ctx(*this, B, Pred); - SubEng.processCFGElement((*B)[StmtIdx], Pred, StmtIdx, &Ctx); + ExprEng.processCFGElement((*B)[StmtIdx], Pred, StmtIdx, &Ctx); } } diff --git a/clang/lib/StaticAnalyzer/Core/DynamicSize.cpp b/clang/lib/StaticAnalyzer/Core/DynamicSize.cpp new file mode 100644 index 0000000000000..8b2172db445ce --- /dev/null +++ b/clang/lib/StaticAnalyzer/Core/DynamicSize.cpp @@ -0,0 +1,71 @@ +//===- DynamicSize.cpp - Dynamic size related APIs --------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines APIs that track and query dynamic size information. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" +#include "clang/AST/Expr.h" +#include "clang/Basic/LLVM.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" + +namespace clang { +namespace ento { + +DefinedOrUnknownSVal getDynamicSize(ProgramStateRef State, const MemRegion *MR, + SValBuilder &SVB) { + return MR->getMemRegionManager().getStaticSize(MR, SVB); +} + +DefinedOrUnknownSVal getDynamicElementCount(ProgramStateRef State, + const MemRegion *MR, + SValBuilder &SVB, + QualType ElementTy) { + MemRegionManager &MemMgr = MR->getMemRegionManager(); + ASTContext &Ctx = MemMgr.getContext(); + + DefinedOrUnknownSVal Size = getDynamicSize(State, MR, SVB); + SVal ElementSizeV = SVB.makeIntVal( + Ctx.getTypeSizeInChars(ElementTy).getQuantity(), SVB.getArrayIndexType()); + + SVal DivisionV = + SVB.evalBinOp(State, BO_Div, Size, ElementSizeV, SVB.getArrayIndexType()); + + return DivisionV.castAs<DefinedOrUnknownSVal>(); +} + +SVal getDynamicSizeWithOffset(ProgramStateRef State, const SVal &BufV) { + SValBuilder &SvalBuilder = State->getStateManager().getSValBuilder(); + const MemRegion *MRegion = BufV.getAsRegion(); + if (!MRegion) + return UnknownVal(); + RegionOffset Offset = MRegion->getAsOffset(); + if (Offset.hasSymbolicOffset()) + return UnknownVal(); + const MemRegion *BaseRegion = MRegion->getBaseRegion(); + if (!BaseRegion) + return UnknownVal(); + + NonLoc OffsetInBytes = SvalBuilder.makeArrayIndex( + Offset.getOffset() / + MRegion->getMemRegionManager().getContext().getCharWidth()); + DefinedOrUnknownSVal ExtentInBytes = + getDynamicSize(State, BaseRegion, SvalBuilder); + + return SvalBuilder.evalBinOp(State, BinaryOperator::Opcode::BO_Sub, + ExtentInBytes, OffsetInBytes, + SvalBuilder.getArrayIndexType()); +} + +} // namespace ento +} // namespace clang diff --git a/clang/lib/StaticAnalyzer/Core/DynamicType.cpp b/clang/lib/StaticAnalyzer/Core/DynamicType.cpp index a78e0e05e903b..e9b64fd79614d 100644 --- a/clang/lib/StaticAnalyzer/Core/DynamicType.cpp +++ b/clang/lib/StaticAnalyzer/Core/DynamicType.cpp @@ -34,6 +34,10 @@ REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(CastSet, clang::ento::DynamicCastInfo) REGISTER_MAP_WITH_PROGRAMSTATE(DynamicCastMap, const clang::ento::MemRegion *, CastSet) +// A map from Class object symbols to the most likely pointed-to type. +REGISTER_MAP_WITH_PROGRAMSTATE(DynamicClassObjectMap, clang::ento::SymbolRef, + clang::ento::DynamicTypeInfo) + namespace clang { namespace ento { @@ -76,6 +80,12 @@ const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State, return nullptr; } +DynamicTypeInfo getClassObjectDynamicTypeInfo(ProgramStateRef State, + SymbolRef Sym) { + const DynamicTypeInfo *DTI = State->get<DynamicClassObjectMap>(Sym); + return DTI ? *DTI : DynamicTypeInfo{}; +} + ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, DynamicTypeInfo NewTy) { State = State->set<DynamicTypeMap>(MR->StripCasts(), NewTy); @@ -118,111 +128,165 @@ ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State, return State; } +ProgramStateRef setClassObjectDynamicTypeInfo(ProgramStateRef State, + SymbolRef Sym, + DynamicTypeInfo NewTy) { + State = State->set<DynamicClassObjectMap>(Sym, NewTy); + return State; +} + +ProgramStateRef setClassObjectDynamicTypeInfo(ProgramStateRef State, + SymbolRef Sym, QualType NewTy, + bool CanBeSubClassed) { + return setClassObjectDynamicTypeInfo(State, Sym, + DynamicTypeInfo(NewTy, CanBeSubClassed)); +} + +static bool isLive(SymbolReaper &SR, const MemRegion *MR) { + return SR.isLiveRegion(MR); +} + +static bool isLive(SymbolReaper &SR, SymbolRef Sym) { return SR.isLive(Sym); } + template <typename MapTy> -ProgramStateRef removeDead(ProgramStateRef State, const MapTy &Map, - SymbolReaper &SR) { +static ProgramStateRef removeDeadImpl(ProgramStateRef State, SymbolReaper &SR) { + const auto &Map = State->get<MapTy>(); + for (const auto &Elem : Map) - if (!SR.isLiveRegion(Elem.first)) - State = State->remove<DynamicCastMap>(Elem.first); + if (!isLive(SR, Elem.first)) + State = State->remove<MapTy>(Elem.first); return State; } ProgramStateRef removeDeadTypes(ProgramStateRef State, SymbolReaper &SR) { - return removeDead(State, State->get<DynamicTypeMap>(), SR); + return removeDeadImpl<DynamicTypeMap>(State, SR); } ProgramStateRef removeDeadCasts(ProgramStateRef State, SymbolReaper &SR) { - return removeDead(State, State->get<DynamicCastMap>(), SR); + return removeDeadImpl<DynamicCastMap>(State, SR); } -static void printDynamicTypesJson(raw_ostream &Out, ProgramStateRef State, - const char *NL, unsigned int Space, - bool IsDot) { - Indent(Out, Space, IsDot) << "\"dynamic_types\": "; +ProgramStateRef removeDeadClassObjectTypes(ProgramStateRef State, + SymbolReaper &SR) { + return removeDeadImpl<DynamicClassObjectMap>(State, SR); +} - const DynamicTypeMapTy &Map = State->get<DynamicTypeMap>(); - if (Map.isEmpty()) { - Out << "null," << NL; - return; - } +//===----------------------------------------------------------------------===// +// Implementation of the 'printer-to-JSON' function +//===----------------------------------------------------------------------===// - ++Space; - Out << '[' << NL; - for (DynamicTypeMapTy::iterator I = Map.begin(); I != Map.end(); ++I) { - const MemRegion *MR = I->first; - const DynamicTypeInfo &DTI = I->second; - Indent(Out, Space, IsDot) - << "{ \"region\": \"" << MR << "\", \"dyn_type\": "; - if (!DTI.isValid()) { - Out << "null"; - } else { - Out << '\"' << DTI.getType()->getPointeeType().getAsString() - << "\", \"sub_classable\": " - << (DTI.canBeASubClass() ? "true" : "false"); - } - Out << " }"; - - if (std::next(I) != Map.end()) - Out << ','; - Out << NL; +static raw_ostream &printJson(const MemRegion *Region, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + return Out << "\"region\": \"" << Region << "\""; +} + +static raw_ostream &printJson(const SymExpr *Symbol, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + return Out << "\"symbol\": \"" << Symbol << "\""; +} + +static raw_ostream &printJson(const DynamicTypeInfo &DTI, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + Out << "\"dyn_type\": "; + if (!DTI.isValid()) { + Out << "null"; + } else { + QualType ToPrint = DTI.getType(); + if (ToPrint->isAnyPointerType()) + ToPrint = ToPrint->getPointeeType(); + + Out << '\"' << ToPrint.getAsString() << "\", \"sub_classable\": " + << (DTI.canBeASubClass() ? "true" : "false"); } + return Out; +} - --Space; - Indent(Out, Space, IsDot) << "]," << NL; +static raw_ostream &printJson(const DynamicCastInfo &DCI, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + return Out << "\"from\": \"" << DCI.from().getAsString() << "\", \"to\": \"" + << DCI.to().getAsString() << "\", \"kind\": \"" + << (DCI.succeeds() ? "success" : "fail") << "\""; } -static void printDynamicCastsJson(raw_ostream &Out, ProgramStateRef State, - const char *NL, unsigned int Space, - bool IsDot) { - Indent(Out, Space, IsDot) << "\"dynamic_casts\": "; +template <class T, class U> +static raw_ostream &printJson(const std::pair<T, U> &Pair, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + printJson(Pair.first, Out, NL, Space, IsDot) << ", "; + return printJson(Pair.second, Out, NL, Space, IsDot); +} - const DynamicCastMapTy &Map = State->get<DynamicCastMap>(); - if (Map.isEmpty()) { - Out << "null," << NL; - return; +template <class ContainerTy> +static raw_ostream &printJsonContainer(const ContainerTy &Container, + raw_ostream &Out, const char *NL, + unsigned int Space, bool IsDot) { + if (Container.isEmpty()) { + return Out << "null"; } ++Space; Out << '[' << NL; - for (DynamicCastMapTy::iterator I = Map.begin(); I != Map.end(); ++I) { - const MemRegion *MR = I->first; - const CastSet &Set = I->second; - - Indent(Out, Space, IsDot) << "{ \"region\": \"" << MR << "\", \"casts\": "; - if (Set.isEmpty()) { - Out << "null "; - } else { - ++Space; - Out << '[' << NL; - for (CastSet::iterator SI = Set.begin(); SI != Set.end(); ++SI) { - Indent(Out, Space, IsDot) - << "{ \"from\": \"" << SI->from().getAsString() << "\", \"to\": \"" - << SI->to().getAsString() << "\", \"kind\": \"" - << (SI->succeeds() ? "success" : "fail") << "\" }"; - - if (std::next(SI) != Set.end()) - Out << ','; - Out << NL; - } - --Space; - Indent(Out, Space, IsDot) << ']'; - } - Out << '}'; - - if (std::next(I) != Map.end()) + for (auto I = Container.begin(); I != Container.end(); ++I) { + const auto &Element = *I; + + Indent(Out, Space, IsDot) << "{ "; + printJson(Element, Out, NL, Space, IsDot) << " }"; + + if (std::next(I) != Container.end()) Out << ','; Out << NL; } --Space; - Indent(Out, Space, IsDot) << "]," << NL; + return Indent(Out, Space, IsDot) << "]"; +} + +static raw_ostream &printJson(const CastSet &Set, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + Out << "\"casts\": "; + return printJsonContainer(Set, Out, NL, Space, IsDot); +} + +template <class MapTy> +static void printJsonImpl(raw_ostream &Out, ProgramStateRef State, + const char *Name, const char *NL, unsigned int Space, + bool IsDot, bool PrintEvenIfEmpty = true) { + const auto &Map = State->get<MapTy>(); + if (Map.isEmpty() && !PrintEvenIfEmpty) + return; + + Indent(Out, Space, IsDot) << "\"" << Name << "\": "; + printJsonContainer(Map, Out, NL, Space, IsDot) << "," << NL; +} + +static void printDynamicTypesJson(raw_ostream &Out, ProgramStateRef State, + const char *NL, unsigned int Space, + bool IsDot) { + printJsonImpl<DynamicTypeMap>(Out, State, "dynamic_types", NL, Space, IsDot); +} + +static void printDynamicCastsJson(raw_ostream &Out, ProgramStateRef State, + const char *NL, unsigned int Space, + bool IsDot) { + printJsonImpl<DynamicCastMap>(Out, State, "dynamic_casts", NL, Space, IsDot); +} + +static void printClassObjectDynamicTypesJson(raw_ostream &Out, + ProgramStateRef State, + const char *NL, unsigned int Space, + bool IsDot) { + // Let's print Class object type information only if we have something + // meaningful to print. + printJsonImpl<DynamicClassObjectMap>(Out, State, "class_object_types", NL, + Space, IsDot, + /*PrintEvenIfEmpty=*/false); } void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State, const char *NL, unsigned int Space, bool IsDot) { printDynamicTypesJson(Out, State, NL, Space, IsDot); printDynamicCastsJson(Out, State, NL, Space, IsDot); + printClassObjectDynamicTypesJson(Out, State, NL, Space, IsDot); } } // namespace ento diff --git a/clang/lib/StaticAnalyzer/Core/Environment.cpp b/clang/lib/StaticAnalyzer/Core/Environment.cpp index 1ccf4c6104a65..9e6d79bb7dcc9 100644 --- a/clang/lib/StaticAnalyzer/Core/Environment.cpp +++ b/clang/lib/StaticAnalyzer/Core/Environment.cpp @@ -183,12 +183,18 @@ EnvironmentManager::removeDeadBindings(Environment Env, F.getTreeFactory()); // Iterate over the block-expr bindings. - for (Environment::iterator I = Env.begin(), E = Env.end(); - I != E; ++I) { + for (Environment::iterator I = Env.begin(), E = Env.end(); I != E; ++I) { const EnvironmentEntry &BlkExpr = I.getKey(); const SVal &X = I.getData(); - if (SymReaper.isLive(BlkExpr.getStmt(), BlkExpr.getLocationContext())) { + const bool IsBlkExprLive = + SymReaper.isLive(BlkExpr.getStmt(), BlkExpr.getLocationContext()); + + assert((isa<Expr>(BlkExpr.getStmt()) || !IsBlkExprLive) && + "Only Exprs can be live, LivenessAnalysis argues about the liveness " + "of *values*!"); + + if (IsBlkExprLive) { // Copy the binding to the new map. EBMapRef = EBMapRef.add(BlkExpr, X); diff --git a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp index c4838492271cd..635495e9bf60f 100644 --- a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp @@ -50,9 +50,8 @@ ExplodedGraph::~ExplodedGraph() = default; bool ExplodedGraph::isInterestingLValueExpr(const Expr *Ex) { if (!Ex->isLValue()) return false; - return isa<DeclRefExpr>(Ex) || - isa<MemberExpr>(Ex) || - isa<ObjCIvarRefExpr>(Ex); + return isa<DeclRefExpr>(Ex) || isa<MemberExpr>(Ex) || + isa<ObjCIvarRefExpr>(Ex) || isa<ArraySubscriptExpr>(Ex); } bool ExplodedGraph::shouldCollect(const ExplodedNode *node) { diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index f917a4c8637b6..265dcd134213d 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1210,9 +1210,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, switch (S->getStmtClass()) { // C++, OpenMP and ARC stuff we don't support yet. - case Expr::ObjCIndirectCopyRestoreExprClass: case Stmt::CXXDependentScopeMemberExprClass: - case Stmt::CXXInheritedCtorInitExprClass: case Stmt::CXXTryStmtClass: case Stmt::CXXTypeidExprClass: case Stmt::CXXUuidofExprClass: @@ -1226,6 +1224,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::UnresolvedLookupExprClass: case Stmt::UnresolvedMemberExprClass: case Stmt::TypoExprClass: + case Stmt::RecoveryExprClass: case Stmt::CXXNoexceptExprClass: case Stmt::PackExpansionExprClass: case Stmt::SubstNonTypeTemplateParmPackExprClass: @@ -1258,6 +1257,8 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::OMPTaskwaitDirectiveClass: case Stmt::OMPTaskgroupDirectiveClass: case Stmt::OMPFlushDirectiveClass: + case Stmt::OMPDepobjDirectiveClass: + case Stmt::OMPScanDirectiveClass: case Stmt::OMPOrderedDirectiveClass: case Stmt::OMPAtomicDirectiveClass: case Stmt::OMPTargetDirectiveClass: @@ -1386,6 +1387,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::AsTypeExprClass: case Stmt::ConceptSpecializationExprClass: case Stmt::CXXRewrittenBinaryOperatorClass: + case Stmt::RequiresExprClass: // Fall through. // Cases we intentionally don't evaluate, since they don't need @@ -1410,6 +1412,8 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::SubstNonTypeTemplateParmExprClass: case Stmt::CXXNullPtrLiteralExprClass: case Stmt::OMPArraySectionExprClass: + case Stmt::OMPArrayShapingExprClass: + case Stmt::OMPIteratorExprClass: case Stmt::TypeTraitExprClass: { Bldr.takeNodes(Pred); ExplodedNodeSet preVisit; @@ -1510,6 +1514,10 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, Bldr.addNodes(Dst); break; + case Stmt::MatrixSubscriptExprClass: + llvm_unreachable("Support for MatrixSubscriptExpr is not implemented."); + break; + case Stmt::GCCAsmStmtClass: Bldr.takeNodes(Pred); VisitGCCAsmStmt(cast<GCCAsmStmt>(S), Pred, Dst); @@ -1617,6 +1625,13 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, Bldr.addNodes(Dst); break; + case Stmt::CXXInheritedCtorInitExprClass: + Bldr.takeNodes(Pred); + VisitCXXInheritedCtorInitExpr(cast<CXXInheritedCtorInitExpr>(S), Pred, + Dst); + Bldr.addNodes(Dst); + break; + case Stmt::CXXNewExprClass: { Bldr.takeNodes(Pred); @@ -1637,8 +1652,10 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, ExplodedNodeSet PreVisit; const auto *CDE = cast<CXXDeleteExpr>(S); getCheckerManager().runCheckersForPreStmt(PreVisit, Pred, S, *this); + ExplodedNodeSet PostVisit; + getCheckerManager().runCheckersForPostStmt(PostVisit, PreVisit, S, *this); - for (const auto i : PreVisit) + for (const auto i : PostVisit) VisitCXXDeleteExpr(CDE, i, Dst); Bldr.addNodes(Dst); @@ -1704,7 +1721,8 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::CXXConstCastExprClass: case Stmt::CXXFunctionalCastExprClass: case Stmt::BuiltinBitCastExprClass: - case Stmt::ObjCBridgedCastExprClass: { + case Stmt::ObjCBridgedCastExprClass: + case Stmt::CXXAddrspaceCastExprClass: { Bldr.takeNodes(Pred); const auto *C = cast<CastExpr>(S); ExplodedNodeSet dstExpr; @@ -1851,6 +1869,21 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, Bldr.addNodes(Dst); break; } + + case Expr::ObjCIndirectCopyRestoreExprClass: { + // ObjCIndirectCopyRestoreExpr implies passing a temporary for + // correctness of lifetime management. Due to limited analysis + // of ARC, this is implemented as direct arg passing. + Bldr.takeNodes(Pred); + ProgramStateRef state = Pred->getState(); + const auto *OIE = cast<ObjCIndirectCopyRestoreExpr>(S); + const Expr *E = OIE->getSubExpr(); + SVal V = state->getSVal(E, Pred->getLocationContext()); + Bldr.generateNode(S, Pred, + state->BindExpr(S, Pred->getLocationContext(), V)); + Bldr.addNodes(Dst); + break; + } } } @@ -3160,11 +3193,13 @@ std::string ExprEngine::DumpGraph(bool trim, StringRef Filename) { return DumpGraph(Src, Filename); } else { return llvm::WriteGraph(&G, "ExprEngine", /*ShortNames=*/false, - /*Title=*/"Exploded Graph", /*Filename=*/Filename); + /*Title=*/"Exploded Graph", + /*Filename=*/std::string(Filename)); } -#endif +#else llvm::errs() << "Warning: dumping graph requires assertions" << "\n"; return ""; +#endif } std::string ExprEngine::DumpGraph(ArrayRef<const ExplodedNode*> Nodes, @@ -3178,7 +3213,7 @@ std::string ExprEngine::DumpGraph(ArrayRef<const ExplodedNode*> Nodes, return llvm::WriteGraph(TrimmedG.get(), "TrimmedExprEngine", /*ShortNames=*/false, /*Title=*/"Trimmed Exploded Graph", - /*Filename=*/Filename); + /*Filename=*/std::string(Filename)); } #endif llvm::errs() << "Warning: dumping graph requires assertions" << "\n"; @@ -3189,3 +3224,5 @@ void *ProgramStateTrait<ReplayWithoutInlining>::GDMIndex() { static int index = 0; return &index; } + +void ExprEngine::anchor() { } diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp index b17f26aa9c535..c5e38cc7423df 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -218,7 +218,7 @@ void ExprEngine::VisitBlockExpr(const BlockExpr *BE, ExplodedNode *Pred, auto CE = BD->capture_end(); for (; I != E; ++I) { const VarRegion *capturedR = I.getCapturedRegion(); - const VarRegion *originalR = I.getOriginalRegion(); + const TypedValueRegion *originalR = I.getOriginalRegion(); // If the capture had a copy expression, use the result of evaluating // that expression, otherwise use the original value. @@ -573,6 +573,18 @@ void ExprEngine::VisitCompoundLiteralExpr(const CompoundLiteralExpr *CL, void ExprEngine::VisitDeclStmt(const DeclStmt *DS, ExplodedNode *Pred, ExplodedNodeSet &Dst) { + if (isa<TypedefNameDecl>(*DS->decl_begin())) { + // C99 6.7.7 "Any array size expressions associated with variable length + // array declarators are evaluated each time the declaration of the typedef + // name is reached in the order of execution." + // The checkers should know about typedef to be able to handle VLA size + // expressions. + ExplodedNodeSet DstPre; + getCheckerManager().runCheckersForPreStmt(DstPre, Pred, DS, *this); + getCheckerManager().runCheckersForPostStmt(Dst, DstPre, DS, *this); + return; + } + // Assumption: The CFG has one DeclStmt per Decl. const VarDecl *VD = dyn_cast_or_null<VarDecl>(*DS->decl_begin()); diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index b816aab7c18f8..38a680eb04c00 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -109,15 +109,14 @@ SVal ExprEngine::makeZeroElementRegion(ProgramStateRef State, SVal LValue, return LValue; } -std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( +SVal ExprEngine::computeObjectUnderConstruction( const Expr *E, ProgramStateRef State, const LocationContext *LCtx, const ConstructionContext *CC, EvalCallOptions &CallOpts) { SValBuilder &SVB = getSValBuilder(); MemRegionManager &MRMgr = SVB.getRegionManager(); ASTContext &ACtx = SVB.getContext(); - // See if we're constructing an existing region by looking at the - // current construction context. + // Compute the target region by exploring the construction context. if (CC) { switch (CC->getKind()) { case ConstructionContext::CXX17ElidedCopyVariableKind: @@ -125,13 +124,9 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( const auto *DSCC = cast<VariableConstructionContext>(CC); const auto *DS = DSCC->getDeclStmt(); const auto *Var = cast<VarDecl>(DS->getSingleDecl()); - SVal LValue = State->getLValue(Var, LCtx); QualType Ty = Var->getType(); - LValue = - makeZeroElementRegion(State, LValue, Ty, CallOpts.IsArrayCtorOrDtor); - State = - addObjectUnderConstruction(State, DSCC->getDeclStmt(), LCtx, LValue); - return std::make_pair(State, LValue); + return makeZeroElementRegion(State, State->getLValue(Var, LCtx), Ty, + CallOpts.IsArrayCtorOrDtor); } case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind: case ConstructionContext::SimpleConstructorInitializerKind: { @@ -139,8 +134,7 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( const auto *Init = ICC->getCXXCtorInitializer(); assert(Init->isAnyMemberInitializer()); const CXXMethodDecl *CurCtor = cast<CXXMethodDecl>(LCtx->getDecl()); - Loc ThisPtr = - SVB.getCXXThis(CurCtor, LCtx->getStackFrame()); + Loc ThisPtr = SVB.getCXXThis(CurCtor, LCtx->getStackFrame()); SVal ThisVal = State->getSVal(ThisPtr); const ValueDecl *Field; @@ -154,10 +148,8 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( } QualType Ty = Field->getType(); - FieldVal = makeZeroElementRegion(State, FieldVal, Ty, - CallOpts.IsArrayCtorOrDtor); - State = addObjectUnderConstruction(State, Init, LCtx, FieldVal); - return std::make_pair(State, FieldVal); + return makeZeroElementRegion(State, FieldVal, Ty, + CallOpts.IsArrayCtorOrDtor); } case ConstructionContext::NewAllocatedObjectKind: { if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) { @@ -170,11 +162,10 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( // TODO: In fact, we need to call the constructor for every // allocated element, not just the first one! CallOpts.IsArrayCtorOrDtor = true; - return std::make_pair( - State, loc::MemRegionVal(getStoreManager().GetElementZeroRegion( - MR, NE->getType()->getPointeeType()))); + return loc::MemRegionVal(getStoreManager().GetElementZeroRegion( + MR, NE->getType()->getPointeeType())); } - return std::make_pair(State, V); + return V; } // TODO: Detect when the allocator returns a null pointer. // Constructor shall not be called in this case. @@ -202,7 +193,7 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( CallerLCtx = CallerLCtx->getParent(); assert(!isa<BlockInvocationContext>(CallerLCtx)); } - return prepareForObjectConstruction( + return computeObjectUnderConstruction( cast<Expr>(SFC->getCallSite()), State, CallerLCtx, RTC->getConstructionContext(), CallOpts); } else { @@ -223,64 +214,46 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( assert(RetE && "Void returns should not have a construction context"); QualType ReturnTy = RetE->getType(); QualType RegionTy = ACtx.getPointerType(ReturnTy); - SVal V = SVB.conjureSymbolVal(&TopLevelSymRegionTag, RetE, SFC, - RegionTy, currBldrCtx->blockCount()); - return std::make_pair(State, V); + return SVB.conjureSymbolVal(&TopLevelSymRegionTag, RetE, SFC, RegionTy, + currBldrCtx->blockCount()); } llvm_unreachable("Unhandled return value construction context!"); } case ConstructionContext::ElidedTemporaryObjectKind: { assert(AMgr.getAnalyzerOptions().ShouldElideConstructors); const auto *TCC = cast<ElidedTemporaryObjectConstructionContext>(CC); - const CXXBindTemporaryExpr *BTE = TCC->getCXXBindTemporaryExpr(); - const MaterializeTemporaryExpr *MTE = TCC->getMaterializedTemporaryExpr(); - const CXXConstructExpr *CE = TCC->getConstructorAfterElision(); // Support pre-C++17 copy elision. We'll have the elidable copy // constructor in the AST and in the CFG, but we'll skip it // and construct directly into the final object. This call // also sets the CallOpts flags for us. - SVal V; // If the elided copy/move constructor is not supported, there's still // benefit in trying to model the non-elided constructor. // Stash our state before trying to elide, as it'll get overwritten. ProgramStateRef PreElideState = State; EvalCallOptions PreElideCallOpts = CallOpts; - std::tie(State, V) = prepareForObjectConstruction( - CE, State, LCtx, TCC->getConstructionContextAfterElision(), CallOpts); + SVal V = computeObjectUnderConstruction( + TCC->getConstructorAfterElision(), State, LCtx, + TCC->getConstructionContextAfterElision(), CallOpts); // FIXME: This definition of "copy elision has not failed" is unreliable. // It doesn't indicate that the constructor will actually be inlined - // later; it is still up to evalCall() to decide. - if (!CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion) { - // Remember that we've elided the constructor. - State = addObjectUnderConstruction(State, CE, LCtx, V); - - // Remember that we've elided the destructor. - if (BTE) - State = elideDestructor(State, BTE, LCtx); - - // Instead of materialization, shamelessly return - // the final object destination. - if (MTE) - State = addObjectUnderConstruction(State, MTE, LCtx, V); - - return std::make_pair(State, V); - } else { - // Copy elision failed. Revert the changes and proceed as if we have - // a simple temporary. - State = PreElideState; - CallOpts = PreElideCallOpts; - } + // later; this is still up to evalCall() to decide. + if (!CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion) + return V; + + // Copy elision failed. Revert the changes and proceed as if we have + // a simple temporary. + CallOpts = PreElideCallOpts; + CallOpts.IsElidableCtorThatHasNotBeenElided = true; LLVM_FALLTHROUGH; } case ConstructionContext::SimpleTemporaryObjectKind: { const auto *TCC = cast<TemporaryObjectConstructionContext>(CC); - const CXXBindTemporaryExpr *BTE = TCC->getCXXBindTemporaryExpr(); const MaterializeTemporaryExpr *MTE = TCC->getMaterializedTemporaryExpr(); - SVal V = UnknownVal(); + CallOpts.IsTemporaryCtorOrDtor = true; if (MTE) { if (const ValueDecl *VD = MTE->getExtendingDecl()) { assert(MTE->getStorageDuration() != SD_FullExpression); @@ -296,20 +269,10 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( if (MTE->getStorageDuration() == SD_Static || MTE->getStorageDuration() == SD_Thread) - V = loc::MemRegionVal(MRMgr.getCXXStaticTempObjectRegion(E)); + return loc::MemRegionVal(MRMgr.getCXXStaticTempObjectRegion(E)); } - if (V.isUnknown()) - V = loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)); - - if (BTE) - State = addObjectUnderConstruction(State, BTE, LCtx, V); - - if (MTE) - State = addObjectUnderConstruction(State, MTE, LCtx, V); - - CallOpts.IsTemporaryCtorOrDtor = true; - return std::make_pair(State, V); + return loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)); } case ConstructionContext::ArgumentKind: { // Arguments are technically temporaries. @@ -318,10 +281,8 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( const auto *ACC = cast<ArgumentConstructionContext>(CC); const Expr *E = ACC->getCallLikeExpr(); unsigned Idx = ACC->getIndex(); - const CXXBindTemporaryExpr *BTE = ACC->getCXXBindTemporaryExpr(); CallEventManager &CEMgr = getStateManager().getCallEventManager(); - SVal V = UnknownVal(); auto getArgLoc = [&](CallEventRef<> Caller) -> Optional<SVal> { const LocationContext *FutureSFC = Caller->getCalleeStackFrame(currBldrCtx->blockCount()); @@ -342,76 +303,171 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( // Operator arguments do not correspond to operator parameters // because this-argument is implemented as a normal argument in // operator call expressions but not in operator declarations. - const VarRegion *VR = Caller->getParameterLocation( + const TypedValueRegion *TVR = Caller->getParameterLocation( *Caller->getAdjustedParameterIndex(Idx), currBldrCtx->blockCount()); - if (!VR) + if (!TVR) return None; - return loc::MemRegionVal(VR); + return loc::MemRegionVal(TVR); }; if (const auto *CE = dyn_cast<CallExpr>(E)) { CallEventRef<> Caller = CEMgr.getSimpleCall(CE, State, LCtx); - if (auto OptV = getArgLoc(Caller)) - V = *OptV; + if (Optional<SVal> V = getArgLoc(Caller)) + return *V; else break; - State = addObjectUnderConstruction(State, {CE, Idx}, LCtx, V); } else if (const auto *CCE = dyn_cast<CXXConstructExpr>(E)) { // Don't bother figuring out the target region for the future // constructor because we won't need it. CallEventRef<> Caller = CEMgr.getCXXConstructorCall(CCE, /*Target=*/nullptr, State, LCtx); - if (auto OptV = getArgLoc(Caller)) - V = *OptV; + if (Optional<SVal> V = getArgLoc(Caller)) + return *V; else break; - State = addObjectUnderConstruction(State, {CCE, Idx}, LCtx, V); } else if (const auto *ME = dyn_cast<ObjCMessageExpr>(E)) { CallEventRef<> Caller = CEMgr.getObjCMethodCall(ME, State, LCtx); - if (auto OptV = getArgLoc(Caller)) - V = *OptV; + if (Optional<SVal> V = getArgLoc(Caller)) + return *V; else break; - State = addObjectUnderConstruction(State, {ME, Idx}, LCtx, V); + } + } + } // switch (CC->getKind()) + } + + // If we couldn't find an existing region to construct into, assume we're + // constructing a temporary. Notify the caller of our failure. + CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true; + return loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)); +} + +ProgramStateRef ExprEngine::updateObjectsUnderConstruction( + SVal V, const Expr *E, ProgramStateRef State, const LocationContext *LCtx, + const ConstructionContext *CC, const EvalCallOptions &CallOpts) { + if (CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion) { + // Sounds like we failed to find the target region and therefore + // copy elision failed. There's nothing we can do about it here. + return State; + } + + // See if we're constructing an existing region by looking at the + // current construction context. + assert(CC && "Computed target region without construction context?"); + switch (CC->getKind()) { + case ConstructionContext::CXX17ElidedCopyVariableKind: + case ConstructionContext::SimpleVariableKind: { + const auto *DSCC = cast<VariableConstructionContext>(CC); + return addObjectUnderConstruction(State, DSCC->getDeclStmt(), LCtx, V); + } + case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind: + case ConstructionContext::SimpleConstructorInitializerKind: { + const auto *ICC = cast<ConstructorInitializerConstructionContext>(CC); + return addObjectUnderConstruction(State, ICC->getCXXCtorInitializer(), + LCtx, V); + } + case ConstructionContext::NewAllocatedObjectKind: { + return State; + } + case ConstructionContext::SimpleReturnedValueKind: + case ConstructionContext::CXX17ElidedCopyReturnedValueKind: { + const StackFrameContext *SFC = LCtx->getStackFrame(); + const LocationContext *CallerLCtx = SFC->getParent(); + if (!CallerLCtx) { + // No extra work is necessary in top frame. + return State; } - assert(!V.isUnknown()); + auto RTC = (*SFC->getCallSiteBlock())[SFC->getIndex()] + .getAs<CFGCXXRecordTypedCall>(); + assert(RTC && "Could not have had a target region without it"); + if (isa<BlockInvocationContext>(CallerLCtx)) { + // Unwrap block invocation contexts. They're mostly part of + // the current stack frame. + CallerLCtx = CallerLCtx->getParent(); + assert(!isa<BlockInvocationContext>(CallerLCtx)); + } - if (BTE) + return updateObjectsUnderConstruction(V, + cast<Expr>(SFC->getCallSite()), State, CallerLCtx, + RTC->getConstructionContext(), CallOpts); + } + case ConstructionContext::ElidedTemporaryObjectKind: { + assert(AMgr.getAnalyzerOptions().ShouldElideConstructors); + if (!CallOpts.IsElidableCtorThatHasNotBeenElided) { + const auto *TCC = cast<ElidedTemporaryObjectConstructionContext>(CC); + State = updateObjectsUnderConstruction( + V, TCC->getConstructorAfterElision(), State, LCtx, + TCC->getConstructionContextAfterElision(), CallOpts); + + // Remember that we've elided the constructor. + State = addObjectUnderConstruction( + State, TCC->getConstructorAfterElision(), LCtx, V); + + // Remember that we've elided the destructor. + if (const auto *BTE = TCC->getCXXBindTemporaryExpr()) + State = elideDestructor(State, BTE, LCtx); + + // Instead of materialization, shamelessly return + // the final object destination. + if (const auto *MTE = TCC->getMaterializedTemporaryExpr()) + State = addObjectUnderConstruction(State, MTE, LCtx, V); + + return State; + } + // If we decided not to elide the constructor, proceed as if + // it's a simple temporary. + LLVM_FALLTHROUGH; + } + case ConstructionContext::SimpleTemporaryObjectKind: { + const auto *TCC = cast<TemporaryObjectConstructionContext>(CC); + if (const auto *BTE = TCC->getCXXBindTemporaryExpr()) State = addObjectUnderConstruction(State, BTE, LCtx, V); - return std::make_pair(State, V); + if (const auto *MTE = TCC->getMaterializedTemporaryExpr()) + State = addObjectUnderConstruction(State, MTE, LCtx, V); + + return State; } + case ConstructionContext::ArgumentKind: { + const auto *ACC = cast<ArgumentConstructionContext>(CC); + if (const auto *BTE = ACC->getCXXBindTemporaryExpr()) + State = addObjectUnderConstruction(State, BTE, LCtx, V); + + return addObjectUnderConstruction( + State, {ACC->getCallLikeExpr(), ACC->getIndex()}, LCtx, V); } } - // If we couldn't find an existing region to construct into, assume we're - // constructing a temporary. Notify the caller of our failure. - CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true; - return std::make_pair( - State, loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx))); + llvm_unreachable("Unhandled construction context!"); } -void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, - ExplodedNode *Pred, - ExplodedNodeSet &destNodes) { +void ExprEngine::handleConstructor(const Expr *E, + ExplodedNode *Pred, + ExplodedNodeSet &destNodes) { + const auto *CE = dyn_cast<CXXConstructExpr>(E); + const auto *CIE = dyn_cast<CXXInheritedCtorInitExpr>(E); + assert(CE || CIE); + const LocationContext *LCtx = Pred->getLocationContext(); ProgramStateRef State = Pred->getState(); SVal Target = UnknownVal(); - if (Optional<SVal> ElidedTarget = - getObjectUnderConstruction(State, CE, LCtx)) { - // We've previously modeled an elidable constructor by pretending that it in - // fact constructs into the correct target. This constructor can therefore - // be skipped. - Target = *ElidedTarget; - StmtNodeBuilder Bldr(Pred, destNodes, *currBldrCtx); - State = finishObjectConstruction(State, CE, LCtx); - if (auto L = Target.getAs<Loc>()) - State = State->BindExpr(CE, LCtx, State->getSVal(*L, CE->getType())); - Bldr.generateNode(CE, Pred, State); - return; + if (CE) { + if (Optional<SVal> ElidedTarget = + getObjectUnderConstruction(State, CE, LCtx)) { + // We've previously modeled an elidable constructor by pretending that it + // in fact constructs into the correct target. This constructor can + // therefore be skipped. + Target = *ElidedTarget; + StmtNodeBuilder Bldr(Pred, destNodes, *currBldrCtx); + State = finishObjectConstruction(State, CE, LCtx); + if (auto L = Target.getAs<Loc>()) + State = State->BindExpr(CE, LCtx, State->getSVal(*L, CE->getType())); + Bldr.generateNode(CE, Pred, State); + return; + } } // FIXME: Handle arrays, which run the same constructor for every element. @@ -423,10 +479,16 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, assert(C || getCurrentCFGElement().getAs<CFGStmt>()); const ConstructionContext *CC = C ? C->getConstructionContext() : nullptr; - switch (CE->getConstructionKind()) { + const CXXConstructExpr::ConstructionKind CK = + CE ? CE->getConstructionKind() : CIE->getConstructionKind(); + switch (CK) { case CXXConstructExpr::CK_Complete: { + // Inherited constructors are always base class constructors. + assert(CE && !CIE && "A complete constructor is inherited?!"); + + // The target region is found from construction context. std::tie(State, Target) = - prepareForObjectConstruction(CE, State, LCtx, CC, CallOpts); + handleConstructionContext(CE, State, LCtx, CC, CallOpts); break; } case CXXConstructExpr::CK_VirtualBase: { @@ -455,9 +517,9 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, // FIXME: Instead of relying on the ParentMap, we should have the // trigger-statement (InitListExpr in this case) passed down from CFG or // otherwise always available during construction. - if (dyn_cast_or_null<InitListExpr>(LCtx->getParentMap().getParent(CE))) { + if (dyn_cast_or_null<InitListExpr>(LCtx->getParentMap().getParent(E))) { MemRegionManager &MRMgr = getSValBuilder().getRegionManager(); - Target = loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(CE, LCtx)); + Target = loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)); CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true; break; } @@ -468,14 +530,13 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, LCtx->getStackFrame()); SVal ThisVal = State->getSVal(ThisPtr); - if (CE->getConstructionKind() == CXXConstructExpr::CK_Delegating) { + if (CK == CXXConstructExpr::CK_Delegating) { Target = ThisVal; } else { // Cast to the base type. - bool IsVirtual = - (CE->getConstructionKind() == CXXConstructExpr::CK_VirtualBase); - SVal BaseVal = getStoreManager().evalDerivedToBase(ThisVal, CE->getType(), - IsVirtual); + bool IsVirtual = (CK == CXXConstructExpr::CK_VirtualBase); + SVal BaseVal = + getStoreManager().evalDerivedToBase(ThisVal, E->getType(), IsVirtual); Target = BaseVal; } break; @@ -487,23 +548,27 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, "Prepare for object construction"); ExplodedNodeSet DstPrepare; StmtNodeBuilder BldrPrepare(Pred, DstPrepare, *currBldrCtx); - BldrPrepare.generateNode(CE, Pred, State, &T, ProgramPoint::PreStmtKind); + BldrPrepare.generateNode(E, Pred, State, &T, ProgramPoint::PreStmtKind); assert(DstPrepare.size() <= 1); if (DstPrepare.size() == 0) return; Pred = *BldrPrepare.begin(); } + const MemRegion *TargetRegion = Target.getAsRegion(); CallEventManager &CEMgr = getStateManager().getCallEventManager(); - CallEventRef<CXXConstructorCall> Call = - CEMgr.getCXXConstructorCall(CE, Target.getAsRegion(), State, LCtx); + CallEventRef<> Call = + CIE ? (CallEventRef<>)CEMgr.getCXXInheritedConstructorCall( + CIE, TargetRegion, State, LCtx) + : (CallEventRef<>)CEMgr.getCXXConstructorCall( + CE, TargetRegion, State, LCtx); ExplodedNodeSet DstPreVisit; - getCheckerManager().runCheckersForPreStmt(DstPreVisit, Pred, CE, *this); + getCheckerManager().runCheckersForPreStmt(DstPreVisit, Pred, E, *this); - // FIXME: Is it possible and/or useful to do this before PreStmt? ExplodedNodeSet PreInitialized; - { + if (CE) { + // FIXME: Is it possible and/or useful to do this before PreStmt? StmtNodeBuilder Bldr(DstPreVisit, PreInitialized, *currBldrCtx); for (ExplodedNodeSet::iterator I = DstPreVisit.begin(), E = DstPreVisit.end(); @@ -528,6 +593,8 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, Bldr.generateNode(CE, *I, State, /*tag=*/nullptr, ProgramPoint::PreStmtKind); } + } else { + PreInitialized = DstPreVisit; } ExplodedNodeSet DstPreCall; @@ -537,7 +604,7 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, ExplodedNodeSet DstEvaluated; StmtNodeBuilder Bldr(DstPreCall, DstEvaluated, *currBldrCtx); - if (CE->getConstructor()->isTrivial() && + if (CE && CE->getConstructor()->isTrivial() && CE->getConstructor()->isCopyOrMoveConstructor() && !CallOpts.IsArrayCtorOrDtor) { // FIXME: Handle other kinds of trivial constructors as well. @@ -548,7 +615,8 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, } else { for (ExplodedNodeSet::iterator I = DstPreCall.begin(), E = DstPreCall.end(); I != E; ++I) - defaultEvalCall(Bldr, *I, *Call, CallOpts); + getCheckerManager().runCheckersForEvalCall(DstEvaluated, *I, *Call, *this, + CallOpts); } // If the CFG was constructed without elements for temporary destructors @@ -560,9 +628,10 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, // paths when no-return temporary destructors are used for assertions. const AnalysisDeclContext *ADC = LCtx->getAnalysisDeclContext(); if (!ADC->getCFGBuildOptions().AddTemporaryDtors) { - const MemRegion *Target = Call->getCXXThisVal().getAsRegion(); - if (Target && isa<CXXTempObjectRegion>(Target) && - Call->getDecl()->getParent()->isAnyDestructorNoReturn()) { + if (llvm::isa_and_nonnull<CXXTempObjectRegion>(TargetRegion) && + cast<CXXConstructorDecl>(Call->getDecl()) + ->getParent() + ->isAnyDestructorNoReturn()) { // If we've inlined the constructor, then DstEvaluated would be empty. // In this case we still want a sink, which could be implemented @@ -575,7 +644,7 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, "We should not have inlined this constructor!"); for (ExplodedNode *N : DstEvaluated) { - Bldr.generateSink(CE, N, N->getState()); + Bldr.generateSink(E, N, N->getState()); } // There is no need to run the PostCall and PostStmt checker @@ -586,7 +655,7 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, } ExplodedNodeSet DstPostArgumentCleanup; - for (auto I : DstEvaluated) + for (ExplodedNode *I : DstEvaluated) finishArgumentConstruction(DstPostArgumentCleanup, I, *Call); // If there were other constructors called for object-type arguments @@ -595,7 +664,19 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, getCheckerManager().runCheckersForPostCall(DstPostCall, DstPostArgumentCleanup, *Call, *this); - getCheckerManager().runCheckersForPostStmt(destNodes, DstPostCall, CE, *this); + getCheckerManager().runCheckersForPostStmt(destNodes, DstPostCall, E, *this); +} + +void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, + ExplodedNode *Pred, + ExplodedNodeSet &Dst) { + handleConstructor(CE, Pred, Dst); +} + +void ExprEngine::VisitCXXInheritedCtorInitExpr( + const CXXInheritedCtorInitExpr *CE, ExplodedNode *Pred, + ExplodedNodeSet &Dst) { + handleConstructor(CE, Pred, Dst); } void ExprEngine::VisitCXXDestructor(QualType ObjectType, @@ -683,7 +764,7 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE, ExplodedNodeSet DstPostCall; StmtNodeBuilder CallBldr(DstPreCall, DstPostCall, *currBldrCtx); - for (auto I : DstPreCall) { + for (ExplodedNode *I : DstPreCall) { // FIXME: Provide evalCall for checkers? defaultEvalCall(CallBldr, I, *Call); } @@ -693,7 +774,7 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE, // CXXNewExpr gets processed. ExplodedNodeSet DstPostValue; StmtNodeBuilder ValueBldr(DstPostCall, DstPostValue, *currBldrCtx); - for (auto I : DstPostCall) { + for (ExplodedNode *I : DstPostCall) { // FIXME: Because CNE serves as the "call site" for the allocator (due to // lack of a better expression in the AST), the conjured return value symbol // is going to be of the same type (C++ object pointer type). Technically @@ -727,10 +808,8 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE, ExplodedNodeSet DstPostPostCallCallback; getCheckerManager().runCheckersForPostCall(DstPostPostCallCallback, DstPostValue, *Call, *this); - for (auto I : DstPostPostCallCallback) { - getCheckerManager().runCheckersForNewAllocator( - CNE, *getObjectUnderConstruction(I->getState(), CNE, LCtx), Dst, I, - *this); + for (ExplodedNode *I : DstPostPostCallCallback) { + getCheckerManager().runCheckersForNewAllocator(*Call, Dst, I, *this); } } @@ -846,13 +925,18 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, void ExprEngine::VisitCXXDeleteExpr(const CXXDeleteExpr *CDE, ExplodedNode *Pred, ExplodedNodeSet &Dst) { - StmtNodeBuilder Bldr(Pred, Dst, *currBldrCtx); - ProgramStateRef state = Pred->getState(); - Bldr.generateNode(CDE, Pred, state); + + CallEventManager &CEMgr = getStateManager().getCallEventManager(); + CallEventRef<CXXDeallocatorCall> Call = CEMgr.getCXXDeallocatorCall( + CDE, Pred->getState(), Pred->getLocationContext()); + + ExplodedNodeSet DstPreCall; + getCheckerManager().runCheckersForPreCall(DstPreCall, Pred, *Call, *this); + + getCheckerManager().runCheckersForPostCall(Dst, DstPreCall, *Call, *this); } -void ExprEngine::VisitCXXCatchStmt(const CXXCatchStmt *CS, - ExplodedNode *Pred, +void ExprEngine::VisitCXXCatchStmt(const CXXCatchStmt *CS, ExplodedNode *Pred, ExplodedNodeSet &Dst) { const VarDecl *VD = CS->getExceptionDecl(); if (!VD) { diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index 01a371e664b2e..52ba17d59ae07 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -10,17 +10,19 @@ // //===----------------------------------------------------------------------===// -#include "clang/AST/Decl.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "PrettyStackTraceLocationContext.h" #include "clang/AST/CXXInheritance.h" +#include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/Analysis/Analyses/LiveVariables.h" #include "clang/Analysis/ConstructionContext.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/Statistic.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Compiler.h" #include "llvm/Support/SaveAndRestore.h" using namespace clang; @@ -324,17 +326,14 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { CallEventRef<> UpdatedCall = Call.cloneWithState(CEEState); ExplodedNodeSet DstPostCall; - if (const CXXNewExpr *CNE = dyn_cast_or_null<CXXNewExpr>(CE)) { + if (llvm::isa_and_nonnull<CXXNewExpr>(CE)) { ExplodedNodeSet DstPostPostCallCallback; getCheckerManager().runCheckersForPostCall(DstPostPostCallCallback, CEENode, *UpdatedCall, *this, /*wasInlined=*/true); - for (auto I : DstPostPostCallCallback) { + for (ExplodedNode *I : DstPostPostCallCallback) { getCheckerManager().runCheckersForNewAllocator( - CNE, - *getObjectUnderConstruction(I->getState(), CNE, - calleeCtx->getParent()), - DstPostCall, I, *this, + cast<CXXAllocatorCall>(*UpdatedCall), DstPostCall, I, *this, /*wasInlined=*/true); } } else { @@ -585,12 +584,12 @@ void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred, // defaultEvalCall if all of them fail. ExplodedNodeSet dstCallEvaluated; getCheckerManager().runCheckersForEvalCall(dstCallEvaluated, dstPreVisit, - Call, *this); + Call, *this, EvalCallOptions()); // If there were other constructors called for object-type arguments // of this call, clean them up. ExplodedNodeSet dstArgumentCleanup; - for (auto I : dstCallEvaluated) + for (ExplodedNode *I : dstCallEvaluated) finishArgumentConstruction(dstArgumentCleanup, I, Call); ExplodedNodeSet dstPostCall; @@ -604,7 +603,7 @@ void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred, // Run pointerEscape callback with the newly conjured symbols. SmallVector<std::pair<SVal, SVal>, 8> Escaped; - for (auto I : dstPostCall) { + for (ExplodedNode *I : dstPostCall) { NodeBuilder B(I, Dst, *currBldrCtx); ProgramStateRef State = I->getState(); Escaped.clear(); @@ -668,8 +667,8 @@ ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call, assert(RTC->getStmt() == Call.getOriginExpr()); EvalCallOptions CallOpts; // FIXME: We won't really need those. std::tie(State, Target) = - prepareForObjectConstruction(Call.getOriginExpr(), State, LCtx, - RTC->getConstructionContext(), CallOpts); + handleConstructionContext(Call.getOriginExpr(), State, LCtx, + RTC->getConstructionContext(), CallOpts); const MemRegion *TargetR = Target.getAsRegion(); assert(TargetR); // Invalidate the region so that it didn't look uninitialized. If this is @@ -718,7 +717,7 @@ void ExprEngine::conservativeEvalCall(const CallEvent &Call, NodeBuilder &Bldr, ExprEngine::CallInlinePolicy ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, AnalyzerOptions &Opts, - const ExprEngine::EvalCallOptions &CallOpts) { + const EvalCallOptions &CallOpts) { const LocationContext *CurLC = Pred->getLocationContext(); const StackFrameContext *CallerSFC = CurLC->getStackFrame(); switch (Call.getKind()) { @@ -742,7 +741,7 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, const ConstructionContext *CC = CCE ? CCE->getConstructionContext() : nullptr; - if (CC && isa<NewAllocatedObjectConstructionContext>(CC) && + if (llvm::isa_and_nonnull<NewAllocatedObjectConstructionContext>(CC) && !Opts.MayInlineCXXAllocator) return CIP_DisallowedOnce; @@ -789,6 +788,11 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, break; } + case CE_CXXInheritedConstructor: { + // This doesn't really increase the cost of inlining ever, because + // the stack frame of the inherited constructor is trivial. + return CIP_Allowed; + } case CE_CXXDestructor: { if (!Opts.mayInlineCXXMemberFunction(CIMK_Destructors)) return CIP_DisallowedAlways; @@ -814,6 +818,8 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, return CIP_DisallowedOnce; break; } + case CE_CXXDeallocator: + LLVM_FALLTHROUGH; case CE_CXXAllocator: if (Opts.MayInlineCXXAllocator) break; diff --git a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp index a4918d7179ff5..bc7c41d039c4d 100644 --- a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -66,11 +66,9 @@ class HTMLDiagnostics : public PathDiagnosticConsumer { const bool SupportsCrossFileDiagnostics; public: - HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, - const std::string& prefix, - const Preprocessor &pp, - bool supportsMultipleFiles) - : Directory(prefix), PP(pp), AnalyzerOpts(AnalyzerOpts), + HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, const std::string &OutputDir, + const Preprocessor &pp, bool supportsMultipleFiles) + : Directory(OutputDir), PP(pp), AnalyzerOpts(AnalyzerOpts), SupportsCrossFileDiagnostics(supportsMultipleFiles) {} ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); } @@ -136,16 +134,45 @@ private: void ento::createHTMLDiagnosticConsumer( AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, - const std::string &prefix, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &) { - C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, true)); + const std::string &OutputDir, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { + + // FIXME: HTML is currently our default output type, but if the output + // directory isn't specified, it acts like if it was in the minimal text + // output mode. This doesn't make much sense, we should have the minimal text + // as our default. In the case of backward compatibility concerns, this could + // be preserved with -analyzer-config-compatibility-mode=true. + createTextMinimalPathDiagnosticConsumer(AnalyzerOpts, C, OutputDir, PP, CTU); + + // TODO: Emit an error here. + if (OutputDir.empty()) + return; + + C.push_back(new HTMLDiagnostics(AnalyzerOpts, OutputDir, PP, true)); } void ento::createHTMLSingleFileDiagnosticConsumer( AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &OutputDir, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { + + // TODO: Emit an error here. + if (OutputDir.empty()) + return; + + C.push_back(new HTMLDiagnostics(AnalyzerOpts, OutputDir, PP, false)); + createTextMinimalPathDiagnosticConsumer(AnalyzerOpts, C, OutputDir, PP, CTU); +} + +void ento::createPlistHTMLDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, const std::string &prefix, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &) { - C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, false)); + const cross_tu::CrossTranslationUnitContext &CTU) { + createHTMLDiagnosticConsumer( + AnalyzerOpts, C, std::string(llvm::sys::path::parent_path(prefix)), PP, + CTU); + createPlistMultiFileDiagnosticConsumer(AnalyzerOpts, C, prefix, PP, CTU); + createTextMinimalPathDiagnosticConsumer(AnalyzerOpts, C, prefix, PP, CTU); } //===----------------------------------------------------------------------===// @@ -607,10 +634,17 @@ window.addEventListener("keydown", function (event) { )<<<"; } +static bool shouldDisplayPopUpRange(const SourceRange &Range) { + return !(Range.getBegin().isMacroID() || Range.getEnd().isMacroID()); +} + static void HandlePopUpPieceStartTag(Rewriter &R, const std::vector<SourceRange> &PopUpRanges) { for (const auto &Range : PopUpRanges) { + if (!shouldDisplayPopUpRange(Range)) + continue; + html::HighlightRange(R, Range.getBegin(), Range.getEnd(), "", "<table class='variable_popup'><tbody>", /*IsTokenRange=*/true); @@ -626,6 +660,8 @@ static void HandlePopUpPieceEndTag(Rewriter &R, llvm::raw_svector_ostream Out(Buf); SourceRange Range(Piece.getLocation().asRange()); + if (!shouldDisplayPopUpRange(Range)) + return; // Write out the path indices with a right arrow and the message as a row. Out << "<tr><td valign='top'><div class='PathIndex PathIndexPopUp'>" @@ -870,7 +906,7 @@ void HTMLDiagnostics::HandlePiece(Rewriter &R, FileID BugFileID, << (num - 1) << "\" title=\"Previous event (" << (num - 1) - << ")\">←</a></div></td>"; + << ")\">←</a></div>"; } os << "</td><td>"; @@ -1034,8 +1070,13 @@ StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() { <script type='text/javascript'> var digitMatcher = new RegExp("[0-9]+"); +var querySelectorAllArray = function(selector) { + return Array.prototype.slice.call( + document.querySelectorAll(selector)); +} + document.addEventListener("DOMContentLoaded", function() { - document.querySelectorAll(".PathNav > a").forEach( + querySelectorAllArray(".PathNav > a").forEach( function(currentValue, currentIndex) { var hrefValue = currentValue.getAttribute("href"); currentValue.onclick = function() { @@ -1055,7 +1096,7 @@ var findNum = function() { }; var scrollTo = function(el) { - document.querySelectorAll(".selected").forEach(function(s) { + querySelectorAllArray(".selected").forEach(function(s) { s.classList.remove("selected"); }); el.classList.add("selected"); diff --git a/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp b/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp index 1a09a521f1167..dc268e562237f 100644 --- a/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp +++ b/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp @@ -130,10 +130,10 @@ static internal::Matcher<Stmt> hasSuspiciousStmt(StringRef NodeName) { // Escaping and not known mutation of the loop counter is handled // by exclusion of assigning and address-of operators and // pass-by-ref function calls on the loop counter from the body. - changeIntBoundNode(equalsBoundNode(NodeName)), - callByRef(equalsBoundNode(NodeName)), - getAddrTo(equalsBoundNode(NodeName)), - assignedToRef(equalsBoundNode(NodeName))))); + changeIntBoundNode(equalsBoundNode(std::string(NodeName))), + callByRef(equalsBoundNode(std::string(NodeName))), + getAddrTo(equalsBoundNode(std::string(NodeName))), + assignedToRef(equalsBoundNode(std::string(NodeName)))))); } static internal::Matcher<Stmt> forLoopMatcher() { @@ -164,6 +164,11 @@ static bool isPossiblyEscaped(const VarDecl *VD, ExplodedNode *N) { if (VD->hasGlobalStorage()) return true; + const bool isParm = isa<ParmVarDecl>(VD); + // Reference parameters are assumed as escaped variables. + if (isParm && VD->getType()->isReferenceType()) + return true; + while (!N->pred_empty()) { // FIXME: getStmtForDiagnostics() does nasty things in order to provide // a valid statement for body farms, do we need this behavior here? @@ -193,6 +198,11 @@ static bool isPossiblyEscaped(const VarDecl *VD, ExplodedNode *N) { N = N->getFirstPred(); } + + // Parameter declaration will not be found. + if (isParm) + return false; + llvm_unreachable("Reached root without finding the declaration of VD"); } diff --git a/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp b/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp index 9a7b1a24b819c..47e34dd84b9a0 100644 --- a/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp +++ b/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp @@ -67,8 +67,10 @@ ProgramStateRef getWidenedLoopState(ProgramStateRef PrevState, } // References should not be invalidated. - auto Matches = match(findAll(stmt(hasDescendant(varDecl(hasType(referenceType())).bind(MatchRef)))), - *LCtx->getDecl()->getBody(), ASTCtx); + auto Matches = match( + findAll(stmt(hasDescendant( + varDecl(hasType(hasCanonicalType(referenceType()))).bind(MatchRef)))), + *LCtx->getDecl()->getBody(), ASTCtx); for (BoundNodes Match : Matches) { const VarDecl *VD = Match.getNodeAs<VarDecl>(MatchRef); assert(VD); diff --git a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp index a10d7e69ad7e7..455adf53ac99b 100644 --- a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp +++ b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -142,7 +142,7 @@ bool SubRegion::isSubRegionOf(const MemRegion* R) const { return false; } -MemRegionManager* SubRegion::getMemRegionManager() const { +MemRegionManager &SubRegion::getMemRegionManager() const { const SubRegion* r = this; do { const MemRegion *superRegion = r->getSuperRegion(); @@ -159,62 +159,10 @@ const StackFrameContext *VarRegion::getStackFrame() const { return SSR ? SSR->getStackFrame() : nullptr; } -//===----------------------------------------------------------------------===// -// Region extents. -//===----------------------------------------------------------------------===// - -DefinedOrUnknownSVal TypedValueRegion::getExtent(SValBuilder &svalBuilder) const { - ASTContext &Ctx = svalBuilder.getContext(); - QualType T = getDesugaredValueType(Ctx); - - if (isa<VariableArrayType>(T)) - return nonloc::SymbolVal(svalBuilder.getSymbolManager().getExtentSymbol(this)); - if (T->isIncompleteType()) - return UnknownVal(); - - CharUnits size = Ctx.getTypeSizeInChars(T); - QualType sizeTy = svalBuilder.getArrayIndexType(); - return svalBuilder.makeIntVal(size.getQuantity(), sizeTy); -} - -DefinedOrUnknownSVal FieldRegion::getExtent(SValBuilder &svalBuilder) const { - // Force callers to deal with bitfields explicitly. - if (getDecl()->isBitField()) - return UnknownVal(); - - DefinedOrUnknownSVal Extent = DeclRegion::getExtent(svalBuilder); - - // A zero-length array at the end of a struct often stands for dynamically- - // allocated extra memory. - if (Extent.isZeroConstant()) { - QualType T = getDesugaredValueType(svalBuilder.getContext()); - - if (isa<ConstantArrayType>(T)) - return UnknownVal(); - } - - return Extent; -} - -DefinedOrUnknownSVal AllocaRegion::getExtent(SValBuilder &svalBuilder) const { - return nonloc::SymbolVal(svalBuilder.getSymbolManager().getExtentSymbol(this)); -} - -DefinedOrUnknownSVal SymbolicRegion::getExtent(SValBuilder &svalBuilder) const { - return nonloc::SymbolVal(svalBuilder.getSymbolManager().getExtentSymbol(this)); -} - -DefinedOrUnknownSVal StringRegion::getExtent(SValBuilder &svalBuilder) const { - return svalBuilder.makeIntVal(getStringLiteral()->getByteLength()+1, - svalBuilder.getArrayIndexType()); -} - ObjCIvarRegion::ObjCIvarRegion(const ObjCIvarDecl *ivd, const SubRegion *sReg) - : DeclRegion(ivd, sReg, ObjCIvarRegionKind) {} + : DeclRegion(sReg, ObjCIvarRegionKind), IVD(ivd) {} -const ObjCIvarDecl *ObjCIvarRegion::getDecl() const { - return cast<ObjCIvarDecl>(D); -} +const ObjCIvarDecl *ObjCIvarRegion::getDecl() const { return IVD; } QualType ObjCIvarRegion::getValueType() const { return getDecl()->getType(); @@ -228,6 +176,33 @@ QualType CXXDerivedObjectRegion::getValueType() const { return QualType(getDecl()->getTypeForDecl(), 0); } +QualType ParamVarRegion::getValueType() const { + assert(getDecl() && + "`ParamVarRegion` support functions without `Decl` not implemented" + " yet."); + return getDecl()->getType(); +} + +const ParmVarDecl *ParamVarRegion::getDecl() const { + const Decl *D = getStackFrame()->getDecl(); + + if (const auto *FD = dyn_cast<FunctionDecl>(D)) { + assert(Index < FD->param_size()); + return FD->parameters()[Index]; + } else if (const auto *BD = dyn_cast<BlockDecl>(D)) { + assert(Index < BD->param_size()); + return BD->parameters()[Index]; + } else if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) { + assert(Index < MD->param_size()); + return MD->parameters()[Index]; + } else if (const auto *CD = dyn_cast<CXXConstructorDecl>(D)) { + assert(Index < CD->param_size()); + return CD->parameters()[Index]; + } else { + llvm_unreachable("Unexpected Decl kind!"); + } +} + //===----------------------------------------------------------------------===// // FoldingSet profiling. //===----------------------------------------------------------------------===// @@ -299,25 +274,44 @@ void CXXThisRegion::Profile(llvm::FoldingSetNodeID &ID) const { CXXThisRegion::ProfileRegion(ID, ThisPointerTy, superRegion); } +void FieldRegion::Profile(llvm::FoldingSetNodeID &ID) const { + ProfileRegion(ID, getDecl(), superRegion); +} + void ObjCIvarRegion::ProfileRegion(llvm::FoldingSetNodeID& ID, const ObjCIvarDecl *ivd, const MemRegion* superRegion) { - DeclRegion::ProfileRegion(ID, ivd, superRegion, ObjCIvarRegionKind); + ID.AddInteger(static_cast<unsigned>(ObjCIvarRegionKind)); + ID.AddPointer(ivd); + ID.AddPointer(superRegion); } -void DeclRegion::ProfileRegion(llvm::FoldingSetNodeID& ID, const Decl *D, - const MemRegion* superRegion, Kind k) { - ID.AddInteger(static_cast<unsigned>(k)); - ID.AddPointer(D); +void ObjCIvarRegion::Profile(llvm::FoldingSetNodeID &ID) const { + ProfileRegion(ID, getDecl(), superRegion); +} + +void NonParamVarRegion::ProfileRegion(llvm::FoldingSetNodeID &ID, + const VarDecl *VD, + const MemRegion *superRegion) { + ID.AddInteger(static_cast<unsigned>(NonParamVarRegionKind)); + ID.AddPointer(VD); ID.AddPointer(superRegion); } -void DeclRegion::Profile(llvm::FoldingSetNodeID& ID) const { - DeclRegion::ProfileRegion(ID, D, superRegion, getKind()); +void NonParamVarRegion::Profile(llvm::FoldingSetNodeID &ID) const { + ProfileRegion(ID, getDecl(), superRegion); } -void VarRegion::Profile(llvm::FoldingSetNodeID &ID) const { - VarRegion::ProfileRegion(ID, getDecl(), superRegion); +void ParamVarRegion::ProfileRegion(llvm::FoldingSetNodeID &ID, const Expr *OE, + unsigned Idx, const MemRegion *SReg) { + ID.AddInteger(static_cast<unsigned>(ParamVarRegionKind)); + ID.AddPointer(OE); + ID.AddInteger(Idx); + ID.AddPointer(SReg); +} + +void ParamVarRegion::Profile(llvm::FoldingSetNodeID &ID) const { + ProfileRegion(ID, getOriginExpr(), getIndex(), superRegion); } void SymbolicRegion::ProfileRegion(llvm::FoldingSetNodeID& ID, SymbolRef sym, @@ -529,12 +523,11 @@ void SymbolicRegion::dumpToStream(raw_ostream &os) const { os << "SymRegion{" << sym << '}'; } -void VarRegion::dumpToStream(raw_ostream &os) const { - const auto *VD = cast<VarDecl>(D); +void NonParamVarRegion::dumpToStream(raw_ostream &os) const { if (const IdentifierInfo *ID = VD->getIdentifier()) os << ID->getName(); else - os << "VarRegion{D" << VD->getID() << '}'; + os << "NonParamVarRegion{D" << VD->getID() << '}'; } LLVM_DUMP_METHOD void RegionRawOffset::dump() const { @@ -581,6 +574,18 @@ void StackLocalsSpaceRegion::dumpToStream(raw_ostream &os) const { os << "StackLocalsSpaceRegion"; } +void ParamVarRegion::dumpToStream(raw_ostream &os) const { + const ParmVarDecl *PVD = getDecl(); + assert(PVD && + "`ParamVarRegion` support functions without `Decl` not implemented" + " yet."); + if (const IdentifierInfo *ID = PVD->getIdentifier()) { + os << ID->getName(); + } else { + os << "ParamVarRegion{P" << PVD->getID() << '}'; + } +} + bool MemRegion::canPrintPretty() const { return canPrintPrettyAsExpr(); } @@ -600,11 +605,18 @@ void MemRegion::printPrettyAsExpr(raw_ostream &) const { llvm_unreachable("This region cannot be printed pretty."); } -bool VarRegion::canPrintPrettyAsExpr() const { - return true; +bool NonParamVarRegion::canPrintPrettyAsExpr() const { return true; } + +void NonParamVarRegion::printPrettyAsExpr(raw_ostream &os) const { + os << getDecl()->getName(); } -void VarRegion::printPrettyAsExpr(raw_ostream &os) const { +bool ParamVarRegion::canPrintPrettyAsExpr() const { return true; } + +void ParamVarRegion::printPrettyAsExpr(raw_ostream &os) const { + assert(getDecl() && + "`ParamVarRegion` support functions without `Decl` not implemented" + " yet."); os << getDecl()->getName(); } @@ -717,11 +729,79 @@ SourceRange MemRegion::sourceRange() const { // MemRegionManager methods. //===----------------------------------------------------------------------===// +static DefinedOrUnknownSVal getTypeSize(QualType Ty, ASTContext &Ctx, + SValBuilder &SVB) { + CharUnits Size = Ctx.getTypeSizeInChars(Ty); + QualType SizeTy = SVB.getArrayIndexType(); + return SVB.makeIntVal(Size.getQuantity(), SizeTy); +} + +DefinedOrUnknownSVal MemRegionManager::getStaticSize(const MemRegion *MR, + SValBuilder &SVB) const { + const auto *SR = cast<SubRegion>(MR); + SymbolManager &SymMgr = SVB.getSymbolManager(); + + switch (SR->getKind()) { + case MemRegion::AllocaRegionKind: + case MemRegion::SymbolicRegionKind: + return nonloc::SymbolVal(SymMgr.getExtentSymbol(SR)); + case MemRegion::StringRegionKind: + return SVB.makeIntVal( + cast<StringRegion>(SR)->getStringLiteral()->getByteLength() + 1, + SVB.getArrayIndexType()); + case MemRegion::CompoundLiteralRegionKind: + case MemRegion::CXXBaseObjectRegionKind: + case MemRegion::CXXDerivedObjectRegionKind: + case MemRegion::CXXTempObjectRegionKind: + case MemRegion::CXXThisRegionKind: + case MemRegion::ObjCIvarRegionKind: + case MemRegion::NonParamVarRegionKind: + case MemRegion::ParamVarRegionKind: + case MemRegion::ElementRegionKind: + case MemRegion::ObjCStringRegionKind: { + QualType Ty = cast<TypedValueRegion>(SR)->getDesugaredValueType(Ctx); + if (isa<VariableArrayType>(Ty)) + return nonloc::SymbolVal(SymMgr.getExtentSymbol(SR)); + + if (Ty->isIncompleteType()) + return UnknownVal(); + + return getTypeSize(Ty, Ctx, SVB); + } + case MemRegion::FieldRegionKind: { + // Force callers to deal with bitfields explicitly. + if (cast<FieldRegion>(SR)->getDecl()->isBitField()) + return UnknownVal(); + + QualType Ty = cast<TypedValueRegion>(SR)->getDesugaredValueType(Ctx); + DefinedOrUnknownSVal Size = getTypeSize(Ty, Ctx, SVB); + + // A zero-length array at the end of a struct often stands for dynamically + // allocated extra memory. + if (Size.isZeroConstant()) { + if (isa<ConstantArrayType>(Ty)) + return UnknownVal(); + } + + return Size; + } + // FIXME: The following are being used in 'SimpleSValBuilder' and in + // 'ArrayBoundChecker::checkLocation' because there is no symbol to + // represent the regions more appropriately. + case MemRegion::BlockDataRegionKind: + case MemRegion::BlockCodeRegionKind: + case MemRegion::FunctionCodeRegionKind: + return nonloc::SymbolVal(SymMgr.getExtentSymbol(SR)); + default: + llvm_unreachable("Unhandled region"); + } +} + template <typename REG> const REG *MemRegionManager::LazyAllocate(REG*& region) { if (!region) { region = A.Allocate<REG>(); - new (region) REG(this); + new (region) REG(*this); } return region; @@ -746,7 +826,7 @@ MemRegionManager::getStackLocalsRegion(const StackFrameContext *STC) { return R; R = A.Allocate<StackLocalsSpaceRegion>(); - new (R) StackLocalsSpaceRegion(this, STC); + new (R) StackLocalsSpaceRegion(*this, STC); return R; } @@ -759,7 +839,7 @@ MemRegionManager::getStackArgumentsRegion(const StackFrameContext *STC) { return R; R = A.Allocate<StackArgumentsSpaceRegion>(); - new (R) StackArgumentsSpaceRegion(this, STC); + new (R) StackArgumentsSpaceRegion(*this, STC); return R; } @@ -781,7 +861,7 @@ const GlobalsSpaceRegion return R; R = A.Allocate<StaticGlobalSpaceRegion>(); - new (R) StaticGlobalSpaceRegion(this, CR); + new (R) StaticGlobalSpaceRegion(*this, CR); return R; } @@ -825,15 +905,16 @@ getStackOrCaptureRegionForDeclContext(const LocationContext *LC, return SFC; } if (const auto *BC = dyn_cast<BlockInvocationContext>(LC)) { - const auto *BR = - static_cast<const BlockDataRegion *>(BC->getContextData()); + const auto *BR = static_cast<const BlockDataRegion *>(BC->getData()); // FIXME: This can be made more efficient. for (BlockDataRegion::referenced_vars_iterator I = BR->referenced_vars_begin(), E = BR->referenced_vars_end(); I != E; ++I) { - const VarRegion *VR = I.getOriginalRegion(); - if (VR->getDecl() == VD) - return cast<VarRegion>(I.getCapturedRegion()); + const TypedValueRegion *OrigR = I.getOriginalRegion(); + if (const auto *VR = dyn_cast<VarRegion>(OrigR)) { + if (VR->getDecl() == VD) + return cast<VarRegion>(I.getCapturedRegion()); + } } } @@ -842,15 +923,37 @@ getStackOrCaptureRegionForDeclContext(const LocationContext *LC, return (const StackFrameContext *)nullptr; } -const VarRegion* MemRegionManager::getVarRegion(const VarDecl *D, +const VarRegion *MemRegionManager::getVarRegion(const VarDecl *D, const LocationContext *LC) { + const auto *PVD = dyn_cast<ParmVarDecl>(D); + if (PVD) { + unsigned Index = PVD->getFunctionScopeIndex(); + const StackFrameContext *SFC = LC->getStackFrame(); + const Stmt *CallSite = SFC->getCallSite(); + if (CallSite) { + const Decl *D = SFC->getDecl(); + if (const auto *FD = dyn_cast<FunctionDecl>(D)) { + if (Index < FD->param_size() && FD->parameters()[Index] == PVD) + return getSubRegion<ParamVarRegion>(cast<Expr>(CallSite), Index, + getStackArgumentsRegion(SFC)); + } else if (const auto *BD = dyn_cast<BlockDecl>(D)) { + if (Index < BD->param_size() && BD->parameters()[Index] == PVD) + return getSubRegion<ParamVarRegion>(cast<Expr>(CallSite), Index, + getStackArgumentsRegion(SFC)); + } else { + return getSubRegion<ParamVarRegion>(cast<Expr>(CallSite), Index, + getStackArgumentsRegion(SFC)); + } + } + } + D = D->getCanonicalDecl(); const MemRegion *sReg = nullptr; if (D->hasGlobalStorage() && !D->isStaticLocal()) { // First handle the globals defined in system headers. - if (C.getSourceManager().isInSystemHeader(D->getLocation())) { + if (Ctx.getSourceManager().isInSystemHeader(D->getLocation())) { // Whitelist the system globals which often DO GET modified, assume the // rest are immutable. if (D->getName().find("errno") != StringRef::npos) @@ -914,7 +1017,7 @@ const VarRegion* MemRegionManager::getVarRegion(const VarDecl *D, T = getContext().getBlockPointerType(T); const BlockCodeRegion *BTR = - getBlockCodeRegion(BD, C.getCanonicalType(T), + getBlockCodeRegion(BD, Ctx.getCanonicalType(T), STC->getAnalysisDeclContext()); sReg = getGlobalsRegion(MemRegion::StaticGlobalSpaceRegionKind, BTR); @@ -926,13 +1029,23 @@ const VarRegion* MemRegionManager::getVarRegion(const VarDecl *D, } } - return getSubRegion<VarRegion>(D, sReg); + return getSubRegion<NonParamVarRegion>(D, sReg); } -const VarRegion *MemRegionManager::getVarRegion(const VarDecl *D, - const MemRegion *superR) { +const NonParamVarRegion * +MemRegionManager::getNonParamVarRegion(const VarDecl *D, + const MemRegion *superR) { D = D->getCanonicalDecl(); - return getSubRegion<VarRegion>(D, superR); + return getSubRegion<NonParamVarRegion>(D, superR); +} + +const ParamVarRegion * +MemRegionManager::getParamVarRegion(const Expr *OriginExpr, unsigned Index, + const LocationContext *LC) { + const StackFrameContext *SFC = LC->getStackFrame(); + assert(SFC); + return getSubRegion<ParamVarRegion>(OriginExpr, Index, + getStackArgumentsRegion(SFC)); } const BlockDataRegion * @@ -1325,7 +1438,8 @@ static RegionOffset calculateOffset(const MemRegion *R) { case MemRegion::CXXThisRegionKind: case MemRegion::StringRegionKind: case MemRegion::ObjCStringRegionKind: - case MemRegion::VarRegionKind: + case MemRegion::NonParamVarRegionKind: + case MemRegion::ParamVarRegionKind: case MemRegion::CXXTempObjectRegionKind: // Usual base regions. goto Finish; @@ -1476,12 +1590,12 @@ RegionOffset MemRegion::getAsOffset() const { std::pair<const VarRegion *, const VarRegion *> BlockDataRegion::getCaptureRegions(const VarDecl *VD) { - MemRegionManager &MemMgr = *getMemRegionManager(); + MemRegionManager &MemMgr = getMemRegionManager(); const VarRegion *VR = nullptr; const VarRegion *OriginalVR = nullptr; if (!VD->hasAttr<BlocksAttr>() && VD->hasLocalStorage()) { - VR = MemMgr.getVarRegion(VD, this); + VR = MemMgr.getNonParamVarRegion(VD, this); OriginalVR = MemMgr.getVarRegion(VD, LC); } else { @@ -1490,7 +1604,7 @@ BlockDataRegion::getCaptureRegions(const VarDecl *VD) { OriginalVR = VR; } else { - VR = MemMgr.getVarRegion(VD, MemMgr.getUnknownRegion()); + VR = MemMgr.getNonParamVarRegion(VD, MemMgr.getUnknownRegion()); OriginalVR = MemMgr.getVarRegion(VD, LC); } } @@ -1511,7 +1625,7 @@ void BlockDataRegion::LazyInitializeReferencedVars() { return; } - MemRegionManager &MemMgr = *getMemRegionManager(); + MemRegionManager &MemMgr = getMemRegionManager(); llvm::BumpPtrAllocator &A = MemMgr.getAllocator(); BumpVectorContext BC(A); diff --git a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp index 3a3942a8301bd..ed62778623a80 100644 --- a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -45,8 +45,8 @@ namespace { AnalyzerOptions &AnOpts; const bool SupportsCrossFileDiagnostics; public: - PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, const std::string &prefix, - const Preprocessor &PP, + PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, + const std::string &OutputFile, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU, bool supportsMultipleFiles); @@ -582,19 +582,32 @@ PlistDiagnostics::PlistDiagnostics( void ento::createPlistDiagnosticConsumer( AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, - const std::string &s, const Preprocessor &PP, + const std::string &OutputFile, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU) { - C.push_back(new PlistDiagnostics(AnalyzerOpts, s, PP, CTU, + + // TODO: Emit an error here. + if (OutputFile.empty()) + return; + + C.push_back(new PlistDiagnostics(AnalyzerOpts, OutputFile, PP, CTU, /*supportsMultipleFiles*/ false)); + createTextMinimalPathDiagnosticConsumer(AnalyzerOpts, C, OutputFile, PP, CTU); } void ento::createPlistMultiFileDiagnosticConsumer( AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, - const std::string &s, const Preprocessor &PP, + const std::string &OutputFile, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU) { - C.push_back(new PlistDiagnostics(AnalyzerOpts, s, PP, CTU, + + // TODO: Emit an error here. + if (OutputFile.empty()) + return; + + C.push_back(new PlistDiagnostics(AnalyzerOpts, OutputFile, PP, CTU, /*supportsMultipleFiles*/ true)); + createTextMinimalPathDiagnosticConsumer(AnalyzerOpts, C, OutputFile, PP, CTU); } + void PlistDiagnostics::FlushDiagnosticsImpl( std::vector<const PathDiagnostic *> &Diags, FilesMade *filesMade) { @@ -939,7 +952,7 @@ getExpandedMacro(SourceLocation MacroLoc, const Preprocessor &PP, std::string MacroName = getMacroNameAndPrintExpansion( Printer, MacroLoc, *PPToUse, MacroArgMap{}, AlreadyProcessedTokens); - return { MacroName, OS.str() }; + return {MacroName, std::string(OS.str())}; } static std::string getMacroNameAndPrintExpansion( @@ -960,9 +973,8 @@ static std::string getMacroNameAndPrintExpansion( // in this case we don't get the full expansion text in the Plist file. See // the test file where "value" is expanded to "garbage_" instead of // "garbage_value". - if (AlreadyProcessedTokens.find(IDInfo) != AlreadyProcessedTokens.end()) + if (!AlreadyProcessedTokens.insert(IDInfo).second) return Info.Name; - AlreadyProcessedTokens.insert(IDInfo); if (!Info.MI) return Info.Name; diff --git a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp index 14006f79fd0f6..006a4006b7fc9 100644 --- a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp +++ b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp @@ -16,8 +16,8 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "llvm/Support/raw_ostream.h" using namespace clang; @@ -76,12 +76,12 @@ ProgramStateManager::ProgramStateManager(ASTContext &Ctx, StoreManagerCreator CreateSMgr, ConstraintManagerCreator CreateCMgr, llvm::BumpPtrAllocator &alloc, - SubEngine *SubEng) - : Eng(SubEng), EnvMgr(alloc), GDMFactory(alloc), + ExprEngine *ExprEng) + : Eng(ExprEng), EnvMgr(alloc), GDMFactory(alloc), svalBuilder(createSimpleSValBuilder(alloc, Ctx, *this)), CallEventMgr(new CallEventManager(alloc)), Alloc(alloc) { StoreMgr = (*CreateSMgr)(*this); - ConstraintMgr = (*CreateCMgr)(*this, SubEng); + ConstraintMgr = (*CreateCMgr)(*this, ExprEng); } @@ -189,7 +189,7 @@ ProgramState::invalidateRegionsImpl(ValueList Values, RegionAndSymbolInvalidationTraits *ITraits, const CallEvent *Call) const { ProgramStateManager &Mgr = getStateManager(); - SubEngine &Eng = Mgr.getOwningEngine(); + ExprEngine &Eng = Mgr.getOwningEngine(); InvalidatedSymbols InvalidatedSyms; if (!IS) @@ -240,6 +240,13 @@ ProgramState::enterStackFrame(const CallEvent &Call, return makeWithStore(NewStore); } +SVal ProgramState::getSelfSVal(const LocationContext *LCtx) const { + const ImplicitParamDecl *SelfDecl = LCtx->getSelfDecl(); + if (!SelfDecl) + return SVal(); + return getSVal(getRegion(SelfDecl, LCtx)); +} + SVal ProgramState::getSValAsScalarOrLoc(const MemRegion *R) const { // We only want to do fetches from regions that we can actually bind // values. For example, SymbolicRegions of type 'id<...>' cannot diff --git a/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp b/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp index 9752a0e22832c..cb6f61e86ae3b 100644 --- a/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp @@ -16,6 +16,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/RangedConstraintManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SValVisitor.h" #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/ImmutableSet.h" #include "llvm/Support/raw_ostream.h" @@ -23,10 +24,89 @@ using namespace clang; using namespace ento; +// This class can be extended with other tables which will help to reason +// about ranges more precisely. +class OperatorRelationsTable { + static_assert(BO_LT < BO_GT && BO_GT < BO_LE && BO_LE < BO_GE && + BO_GE < BO_EQ && BO_EQ < BO_NE, + "This class relies on operators order. Rework it otherwise."); + +public: + enum TriStateKind { + False = 0, + True, + Unknown, + }; + +private: + // CmpOpTable holds states which represent the corresponding range for + // branching an exploded graph. We can reason about the branch if there is + // a previously known fact of the existence of a comparison expression with + // operands used in the current expression. + // E.g. assuming (x < y) is true that means (x != y) is surely true. + // if (x previous_operation y) // < | != | > + // if (x operation y) // != | > | < + // tristate // True | Unknown | False + // + // CmpOpTable represents next: + // __|< |> |<=|>=|==|!=|UnknownX2| + // < |1 |0 |* |0 |0 |* |1 | + // > |0 |1 |0 |* |0 |* |1 | + // <=|1 |0 |1 |* |1 |* |0 | + // >=|0 |1 |* |1 |1 |* |0 | + // ==|0 |0 |* |* |1 |0 |1 | + // !=|1 |1 |* |* |0 |1 |0 | + // + // Columns stands for a previous operator. + // Rows stands for a current operator. + // Each row has exactly two `Unknown` cases. + // UnknownX2 means that both `Unknown` previous operators are met in code, + // and there is a special column for that, for example: + // if (x >= y) + // if (x != y) + // if (x <= y) + // False only + static constexpr size_t CmpOpCount = BO_NE - BO_LT + 1; + const TriStateKind CmpOpTable[CmpOpCount][CmpOpCount + 1] = { + // < > <= >= == != UnknownX2 + {True, False, Unknown, False, False, Unknown, True}, // < + {False, True, False, Unknown, False, Unknown, True}, // > + {True, False, True, Unknown, True, Unknown, False}, // <= + {False, True, Unknown, True, True, Unknown, False}, // >= + {False, False, Unknown, Unknown, True, False, True}, // == + {True, True, Unknown, Unknown, False, True, False}, // != + }; + + static size_t getIndexFromOp(BinaryOperatorKind OP) { + return static_cast<size_t>(OP - BO_LT); + } + +public: + constexpr size_t getCmpOpCount() const { return CmpOpCount; } + + static BinaryOperatorKind getOpFromIndex(size_t Index) { + return static_cast<BinaryOperatorKind>(Index + BO_LT); + } + + TriStateKind getCmpOpState(BinaryOperatorKind CurrentOP, + BinaryOperatorKind QueriedOP) const { + return CmpOpTable[getIndexFromOp(CurrentOP)][getIndexFromOp(QueriedOP)]; + } + + TriStateKind getCmpOpStateForUnknownX2(BinaryOperatorKind CurrentOP) const { + return CmpOpTable[getIndexFromOp(CurrentOP)][CmpOpCount]; + } +}; +//===----------------------------------------------------------------------===// +// RangeSet implementation +//===----------------------------------------------------------------------===// + void RangeSet::IntersectInRange(BasicValueFactory &BV, Factory &F, - const llvm::APSInt &Lower, const llvm::APSInt &Upper, - PrimRangeSet &newRanges, PrimRangeSet::iterator &i, - PrimRangeSet::iterator &e) const { + const llvm::APSInt &Lower, + const llvm::APSInt &Upper, + PrimRangeSet &newRanges, + PrimRangeSet::iterator &i, + PrimRangeSet::iterator &e) const { // There are six cases for each range R in the set: // 1. R is entirely before the intersection range. // 2. R is entirely after the intersection range. @@ -62,10 +142,27 @@ void RangeSet::IntersectInRange(BasicValueFactory &BV, Factory &F, const llvm::APSInt &RangeSet::getMinValue() const { assert(!isEmpty()); - return ranges.begin()->From(); + return begin()->From(); +} + +const llvm::APSInt &RangeSet::getMaxValue() const { + assert(!isEmpty()); + // NOTE: It's a shame that we can't implement 'getMaxValue' without scanning + // the whole tree to get to the last element. + // llvm::ImmutableSet should support decrement for 'end' iterators + // or reverse order iteration. + auto It = begin(); + for (auto End = end(); std::next(It) != End; ++It) { + } + return It->To(); } bool RangeSet::pin(llvm::APSInt &Lower, llvm::APSInt &Upper) const { + if (isEmpty()) { + // This range is already infeasible. + return false; + } + // This function has nine cases, the cartesian product of range-testing // both the upper and lower bounds against the symbol's type. // Each case requires a different pinning operation. @@ -155,11 +252,11 @@ bool RangeSet::pin(llvm::APSInt &Lower, llvm::APSInt &Upper) const { // or, alternatively, /removing/ all integers between Upper and Lower. RangeSet RangeSet::Intersect(BasicValueFactory &BV, Factory &F, llvm::APSInt Lower, llvm::APSInt Upper) const { - if (!pin(Lower, Upper)) - return F.getEmptySet(); - PrimRangeSet newRanges = F.getEmptySet(); + if (isEmpty() || !pin(Lower, Upper)) + return newRanges; + PrimRangeSet::iterator i = begin(), e = end(); if (Lower <= Upper) IntersectInRange(BV, F, Lower, Upper, newRanges, i, e); @@ -190,33 +287,78 @@ RangeSet RangeSet::Intersect(BasicValueFactory &BV, Factory &F, return newRanges; } -// Turn all [A, B] ranges to [-B, -A]. Ranges [MIN, B] are turned to range set -// [MIN, MIN] U [-B, MAX], when MIN and MAX are the minimal and the maximal -// signed values of the type. +// Turn all [A, B] ranges to [-B, -A], when "-" is a C-like unary minus +// operation under the values of the type. +// +// We also handle MIN because applying unary minus to MIN does not change it. +// Example 1: +// char x = -128; // -128 is a MIN value in a range of 'char' +// char y = -x; // y: -128 +// Example 2: +// unsigned char x = 0; // 0 is a MIN value in a range of 'unsigned char' +// unsigned char y = -x; // y: 0 +// +// And it makes us to separate the range +// like [MIN, N] to [MIN, MIN] U [-N,MAX]. +// For instance, whole range is {-128..127} and subrange is [-128,-126], +// thus [-128,-127,-126,.....] negates to [-128,.....,126,127]. +// +// Negate restores disrupted ranges on bounds, +// e.g. [MIN, B] => [MIN, MIN] U [-B, MAX] => [MIN, B]. RangeSet RangeSet::Negate(BasicValueFactory &BV, Factory &F) const { PrimRangeSet newRanges = F.getEmptySet(); - for (iterator i = begin(), e = end(); i != e; ++i) { - const llvm::APSInt &from = i->From(), &to = i->To(); - const llvm::APSInt &newTo = (from.isMinSignedValue() ? - BV.getMaxValue(from) : - BV.getValue(- from)); - if (to.isMaxSignedValue() && !newRanges.isEmpty() && - newRanges.begin()->From().isMinSignedValue()) { - assert(newRanges.begin()->To().isMinSignedValue() && - "Ranges should not overlap"); - assert(!from.isMinSignedValue() && "Ranges should not overlap"); - const llvm::APSInt &newFrom = newRanges.begin()->From(); - newRanges = - F.add(F.remove(newRanges, *newRanges.begin()), Range(newFrom, newTo)); - } else if (!to.isMinSignedValue()) { - const llvm::APSInt &newFrom = BV.getValue(- to); - newRanges = F.add(newRanges, Range(newFrom, newTo)); - } - if (from.isMinSignedValue()) { - newRanges = F.add(newRanges, Range(BV.getMinValue(from), - BV.getMinValue(from))); + if (isEmpty()) + return newRanges; + + const llvm::APSInt sampleValue = getMinValue(); + const llvm::APSInt &MIN = BV.getMinValue(sampleValue); + const llvm::APSInt &MAX = BV.getMaxValue(sampleValue); + + // Handle a special case for MIN value. + iterator i = begin(); + const llvm::APSInt &from = i->From(); + const llvm::APSInt &to = i->To(); + if (from == MIN) { + // If [from, to] are [MIN, MAX], then just return the same [MIN, MAX]. + if (to == MAX) { + newRanges = ranges; + } else { + // Add separate range for the lowest value. + newRanges = F.add(newRanges, Range(MIN, MIN)); + // Skip adding the second range in case when [from, to] are [MIN, MIN]. + if (to != MIN) { + newRanges = F.add(newRanges, Range(BV.getValue(-to), MAX)); + } } + // Skip the first range in the loop. + ++i; + } + + // Negate all other ranges. + for (iterator e = end(); i != e; ++i) { + // Negate int values. + const llvm::APSInt &newFrom = BV.getValue(-i->To()); + const llvm::APSInt &newTo = BV.getValue(-i->From()); + // Add a negated range. + newRanges = F.add(newRanges, Range(newFrom, newTo)); + } + + if (newRanges.isSingleton()) + return newRanges; + + // Try to find and unite next ranges: + // [MIN, MIN] & [MIN + 1, N] => [MIN, N]. + iterator iter1 = newRanges.begin(); + iterator iter2 = std::next(iter1); + + if (iter1->To() == MIN && (iter2->From() - 1) == MIN) { + const llvm::APSInt &to = iter2->To(); + // remove adjacent ranges + newRanges = F.remove(newRanges, *iter1); + newRanges = F.remove(newRanges, *newRanges.begin()); + // add united range + newRanges = F.add(newRanges, Range(MIN, to)); } return newRanges; @@ -238,10 +380,534 @@ void RangeSet::print(raw_ostream &os) const { } namespace { + +/// A little component aggregating all of the reasoning we have about +/// the ranges of symbolic expressions. +/// +/// Even when we don't know the exact values of the operands, we still +/// can get a pretty good estimate of the result's range. +class SymbolicRangeInferrer + : public SymExprVisitor<SymbolicRangeInferrer, RangeSet> { +public: + static RangeSet inferRange(BasicValueFactory &BV, RangeSet::Factory &F, + ProgramStateRef State, SymbolRef Sym) { + SymbolicRangeInferrer Inferrer(BV, F, State); + return Inferrer.infer(Sym); + } + + RangeSet VisitSymExpr(SymbolRef Sym) { + // If we got to this function, the actual type of the symbolic + // expression is not supported for advanced inference. + // In this case, we simply backoff to the default "let's simply + // infer the range from the expression's type". + return infer(Sym->getType()); + } + + RangeSet VisitSymIntExpr(const SymIntExpr *Sym) { + return VisitBinaryOperator(Sym); + } + + RangeSet VisitIntSymExpr(const IntSymExpr *Sym) { + return VisitBinaryOperator(Sym); + } + + RangeSet VisitSymSymExpr(const SymSymExpr *Sym) { + return VisitBinaryOperator(Sym); + } + +private: + SymbolicRangeInferrer(BasicValueFactory &BV, RangeSet::Factory &F, + ProgramStateRef S) + : ValueFactory(BV), RangeFactory(F), State(S) {} + + /// Infer range information from the given integer constant. + /// + /// It's not a real "inference", but is here for operating with + /// sub-expressions in a more polymorphic manner. + RangeSet inferAs(const llvm::APSInt &Val, QualType) { + return {RangeFactory, Val}; + } + + /// Infer range information from symbol in the context of the given type. + RangeSet inferAs(SymbolRef Sym, QualType DestType) { + QualType ActualType = Sym->getType(); + // Check that we can reason about the symbol at all. + if (ActualType->isIntegralOrEnumerationType() || + Loc::isLocType(ActualType)) { + return infer(Sym); + } + // Otherwise, let's simply infer from the destination type. + // We couldn't figure out nothing else about that expression. + return infer(DestType); + } + + RangeSet infer(SymbolRef Sym) { + const RangeSet *AssociatedRange = State->get<ConstraintRange>(Sym); + + // If Sym is a difference of symbols A - B, then maybe we have range set + // stored for B - A. + const RangeSet *RangeAssociatedWithNegatedSym = + getRangeForMinusSymbol(State, Sym); + + // If we have range set stored for both A - B and B - A then calculate the + // effective range set by intersecting the range set for A - B and the + // negated range set of B - A. + if (AssociatedRange && RangeAssociatedWithNegatedSym) + return AssociatedRange->Intersect( + ValueFactory, RangeFactory, + RangeAssociatedWithNegatedSym->Negate(ValueFactory, RangeFactory)); + + if (AssociatedRange) + return *AssociatedRange; + + if (RangeAssociatedWithNegatedSym) + return RangeAssociatedWithNegatedSym->Negate(ValueFactory, RangeFactory); + + // If Sym is a comparison expression (except <=>), + // find any other comparisons with the same operands. + // See function description. + const RangeSet CmpRangeSet = getRangeForComparisonSymbol(State, Sym); + if (!CmpRangeSet.isEmpty()) + return CmpRangeSet; + + return Visit(Sym); + } + + /// Infer range information solely from the type. + RangeSet infer(QualType T) { + // Lazily generate a new RangeSet representing all possible values for the + // given symbol type. + RangeSet Result(RangeFactory, ValueFactory.getMinValue(T), + ValueFactory.getMaxValue(T)); + + // References are known to be non-zero. + if (T->isReferenceType()) + return assumeNonZero(Result, T); + + return Result; + } + + template <class BinarySymExprTy> + RangeSet VisitBinaryOperator(const BinarySymExprTy *Sym) { + // TODO #1: VisitBinaryOperator implementation might not make a good + // use of the inferred ranges. In this case, we might be calculating + // everything for nothing. This being said, we should introduce some + // sort of laziness mechanism here. + // + // TODO #2: We didn't go into the nested expressions before, so it + // might cause us spending much more time doing the inference. + // This can be a problem for deeply nested expressions that are + // involved in conditions and get tested continuously. We definitely + // need to address this issue and introduce some sort of caching + // in here. + QualType ResultType = Sym->getType(); + return VisitBinaryOperator(inferAs(Sym->getLHS(), ResultType), + Sym->getOpcode(), + inferAs(Sym->getRHS(), ResultType), ResultType); + } + + RangeSet VisitBinaryOperator(RangeSet LHS, BinaryOperator::Opcode Op, + RangeSet RHS, QualType T) { + switch (Op) { + case BO_Or: + return VisitBinaryOperator<BO_Or>(LHS, RHS, T); + case BO_And: + return VisitBinaryOperator<BO_And>(LHS, RHS, T); + case BO_Rem: + return VisitBinaryOperator<BO_Rem>(LHS, RHS, T); + default: + return infer(T); + } + } + + //===----------------------------------------------------------------------===// + // Ranges and operators + //===----------------------------------------------------------------------===// + + /// Return a rough approximation of the given range set. + /// + /// For the range set: + /// { [x_0, y_0], [x_1, y_1], ... , [x_N, y_N] } + /// it will return the range [x_0, y_N]. + static Range fillGaps(RangeSet Origin) { + assert(!Origin.isEmpty()); + return {Origin.getMinValue(), Origin.getMaxValue()}; + } + + /// Try to convert given range into the given type. + /// + /// It will return llvm::None only when the trivial conversion is possible. + llvm::Optional<Range> convert(const Range &Origin, APSIntType To) { + if (To.testInRange(Origin.From(), false) != APSIntType::RTR_Within || + To.testInRange(Origin.To(), false) != APSIntType::RTR_Within) { + return llvm::None; + } + return Range(ValueFactory.Convert(To, Origin.From()), + ValueFactory.Convert(To, Origin.To())); + } + + template <BinaryOperator::Opcode Op> + RangeSet VisitBinaryOperator(RangeSet LHS, RangeSet RHS, QualType T) { + // We should propagate information about unfeasbility of one of the + // operands to the resulting range. + if (LHS.isEmpty() || RHS.isEmpty()) { + return RangeFactory.getEmptySet(); + } + + Range CoarseLHS = fillGaps(LHS); + Range CoarseRHS = fillGaps(RHS); + + APSIntType ResultType = ValueFactory.getAPSIntType(T); + + // We need to convert ranges to the resulting type, so we can compare values + // and combine them in a meaningful (in terms of the given operation) way. + auto ConvertedCoarseLHS = convert(CoarseLHS, ResultType); + auto ConvertedCoarseRHS = convert(CoarseRHS, ResultType); + + // It is hard to reason about ranges when conversion changes + // borders of the ranges. + if (!ConvertedCoarseLHS || !ConvertedCoarseRHS) { + return infer(T); + } + + return VisitBinaryOperator<Op>(*ConvertedCoarseLHS, *ConvertedCoarseRHS, T); + } + + template <BinaryOperator::Opcode Op> + RangeSet VisitBinaryOperator(Range LHS, Range RHS, QualType T) { + return infer(T); + } + + /// Return a symmetrical range for the given range and type. + /// + /// If T is signed, return the smallest range [-x..x] that covers the original + /// range, or [-min(T), max(T)] if the aforementioned symmetric range doesn't + /// exist due to original range covering min(T)). + /// + /// If T is unsigned, return the smallest range [0..x] that covers the + /// original range. + Range getSymmetricalRange(Range Origin, QualType T) { + APSIntType RangeType = ValueFactory.getAPSIntType(T); + + if (RangeType.isUnsigned()) { + return Range(ValueFactory.getMinValue(RangeType), Origin.To()); + } + + if (Origin.From().isMinSignedValue()) { + // If mini is a minimal signed value, absolute value of it is greater + // than the maximal signed value. In order to avoid these + // complications, we simply return the whole range. + return {ValueFactory.getMinValue(RangeType), + ValueFactory.getMaxValue(RangeType)}; + } + + // At this point, we are sure that the type is signed and we can safely + // use unary - operator. + // + // While calculating absolute maximum, we can use the following formula + // because of these reasons: + // * If From >= 0 then To >= From and To >= -From. + // AbsMax == To == max(To, -From) + // * If To <= 0 then -From >= -To and -From >= From. + // AbsMax == -From == max(-From, To) + // * Otherwise, From <= 0, To >= 0, and + // AbsMax == max(abs(From), abs(To)) + llvm::APSInt AbsMax = std::max(-Origin.From(), Origin.To()); + + // Intersection is guaranteed to be non-empty. + return {ValueFactory.getValue(-AbsMax), ValueFactory.getValue(AbsMax)}; + } + + /// Return a range set subtracting zero from \p Domain. + RangeSet assumeNonZero(RangeSet Domain, QualType T) { + APSIntType IntType = ValueFactory.getAPSIntType(T); + return Domain.Intersect(ValueFactory, RangeFactory, + ++IntType.getZeroValue(), --IntType.getZeroValue()); + } + + // FIXME: Once SValBuilder supports unary minus, we should use SValBuilder to + // obtain the negated symbolic expression instead of constructing the + // symbol manually. This will allow us to support finding ranges of not + // only negated SymSymExpr-type expressions, but also of other, simpler + // expressions which we currently do not know how to negate. + const RangeSet *getRangeForMinusSymbol(ProgramStateRef State, SymbolRef Sym) { + if (const SymSymExpr *SSE = dyn_cast<SymSymExpr>(Sym)) { + if (SSE->getOpcode() == BO_Sub) { + QualType T = Sym->getType(); + SymbolManager &SymMgr = State->getSymbolManager(); + SymbolRef negSym = + SymMgr.getSymSymExpr(SSE->getRHS(), BO_Sub, SSE->getLHS(), T); + + if (const RangeSet *negV = State->get<ConstraintRange>(negSym)) { + // Unsigned range set cannot be negated, unless it is [0, 0]. + if (T->isUnsignedIntegerOrEnumerationType() || + T->isSignedIntegerOrEnumerationType()) + return negV; + } + } + } + return nullptr; + } + + // Returns ranges only for binary comparison operators (except <=>) + // when left and right operands are symbolic values. + // Finds any other comparisons with the same operands. + // Then do logical calculations and refuse impossible branches. + // E.g. (x < y) and (x > y) at the same time are impossible. + // E.g. (x >= y) and (x != y) at the same time makes (x > y) true only. + // E.g. (x == y) and (y == x) are just reversed but the same. + // It covers all possible combinations (see CmpOpTable description). + // Note that `x` and `y` can also stand for subexpressions, + // not only for actual symbols. + RangeSet getRangeForComparisonSymbol(ProgramStateRef State, SymbolRef Sym) { + const RangeSet EmptyRangeSet = RangeFactory.getEmptySet(); + + auto SSE = dyn_cast<SymSymExpr>(Sym); + if (!SSE) + return EmptyRangeSet; + + BinaryOperatorKind CurrentOP = SSE->getOpcode(); + + // We currently do not support <=> (C++20). + if (!BinaryOperator::isComparisonOp(CurrentOP) || (CurrentOP == BO_Cmp)) + return EmptyRangeSet; + + static const OperatorRelationsTable CmpOpTable{}; + + const SymExpr *LHS = SSE->getLHS(); + const SymExpr *RHS = SSE->getRHS(); + QualType T = SSE->getType(); + + SymbolManager &SymMgr = State->getSymbolManager(); + const llvm::APSInt &Zero = ValueFactory.getValue(0, T); + const llvm::APSInt &One = ValueFactory.getValue(1, T); + const RangeSet TrueRangeSet(RangeFactory, One, One); + const RangeSet FalseRangeSet(RangeFactory, Zero, Zero); + + int UnknownStates = 0; + + // Loop goes through all of the columns exept the last one ('UnknownX2'). + // We treat `UnknownX2` column separately at the end of the loop body. + for (size_t i = 0; i < CmpOpTable.getCmpOpCount(); ++i) { + + // Let's find an expression e.g. (x < y). + BinaryOperatorKind QueriedOP = OperatorRelationsTable::getOpFromIndex(i); + const SymSymExpr *SymSym = SymMgr.getSymSymExpr(LHS, QueriedOP, RHS, T); + const RangeSet *QueriedRangeSet = State->get<ConstraintRange>(SymSym); + + // If ranges were not previously found, + // try to find a reversed expression (y > x). + if (!QueriedRangeSet) { + const BinaryOperatorKind ROP = + BinaryOperator::reverseComparisonOp(QueriedOP); + SymSym = SymMgr.getSymSymExpr(RHS, ROP, LHS, T); + QueriedRangeSet = State->get<ConstraintRange>(SymSym); + } + + if (!QueriedRangeSet || QueriedRangeSet->isEmpty()) + continue; + + const llvm::APSInt *ConcreteValue = QueriedRangeSet->getConcreteValue(); + const bool isInFalseBranch = + ConcreteValue ? (*ConcreteValue == 0) : false; + + // If it is a false branch, we shall be guided by opposite operator, + // because the table is made assuming we are in the true branch. + // E.g. when (x <= y) is false, then (x > y) is true. + if (isInFalseBranch) + QueriedOP = BinaryOperator::negateComparisonOp(QueriedOP); + + OperatorRelationsTable::TriStateKind BranchState = + CmpOpTable.getCmpOpState(CurrentOP, QueriedOP); + + if (BranchState == OperatorRelationsTable::Unknown) { + if (++UnknownStates == 2) + // If we met both Unknown states. + // if (x <= y) // assume true + // if (x != y) // assume true + // if (x < y) // would be also true + // Get a state from `UnknownX2` column. + BranchState = CmpOpTable.getCmpOpStateForUnknownX2(CurrentOP); + else + continue; + } + + return (BranchState == OperatorRelationsTable::True) ? TrueRangeSet + : FalseRangeSet; + } + + return EmptyRangeSet; + } + + BasicValueFactory &ValueFactory; + RangeSet::Factory &RangeFactory; + ProgramStateRef State; +}; + +template <> +RangeSet SymbolicRangeInferrer::VisitBinaryOperator<BO_Or>(Range LHS, Range RHS, + QualType T) { + APSIntType ResultType = ValueFactory.getAPSIntType(T); + llvm::APSInt Zero = ResultType.getZeroValue(); + + bool IsLHSPositiveOrZero = LHS.From() >= Zero; + bool IsRHSPositiveOrZero = RHS.From() >= Zero; + + bool IsLHSNegative = LHS.To() < Zero; + bool IsRHSNegative = RHS.To() < Zero; + + // Check if both ranges have the same sign. + if ((IsLHSPositiveOrZero && IsRHSPositiveOrZero) || + (IsLHSNegative && IsRHSNegative)) { + // The result is definitely greater or equal than any of the operands. + const llvm::APSInt &Min = std::max(LHS.From(), RHS.From()); + + // We estimate maximal value for positives as the maximal value for the + // given type. For negatives, we estimate it with -1 (e.g. 0x11111111). + // + // TODO: We basically, limit the resulting range from below, but don't do + // anything with the upper bound. + // + // For positive operands, it can be done as follows: for the upper + // bound of LHS and RHS we calculate the most significant bit set. + // Let's call it the N-th bit. Then we can estimate the maximal + // number to be 2^(N+1)-1, i.e. the number with all the bits up to + // the N-th bit set. + const llvm::APSInt &Max = IsLHSNegative + ? ValueFactory.getValue(--Zero) + : ValueFactory.getMaxValue(ResultType); + + return {RangeFactory, ValueFactory.getValue(Min), Max}; + } + + // Otherwise, let's check if at least one of the operands is negative. + if (IsLHSNegative || IsRHSNegative) { + // This means that the result is definitely negative as well. + return {RangeFactory, ValueFactory.getMinValue(ResultType), + ValueFactory.getValue(--Zero)}; + } + + RangeSet DefaultRange = infer(T); + + // It is pretty hard to reason about operands with different signs + // (and especially with possibly different signs). We simply check if it + // can be zero. In order to conclude that the result could not be zero, + // at least one of the operands should be definitely not zero itself. + if (!LHS.Includes(Zero) || !RHS.Includes(Zero)) { + return assumeNonZero(DefaultRange, T); + } + + // Nothing much else to do here. + return DefaultRange; +} + +template <> +RangeSet SymbolicRangeInferrer::VisitBinaryOperator<BO_And>(Range LHS, + Range RHS, + QualType T) { + APSIntType ResultType = ValueFactory.getAPSIntType(T); + llvm::APSInt Zero = ResultType.getZeroValue(); + + bool IsLHSPositiveOrZero = LHS.From() >= Zero; + bool IsRHSPositiveOrZero = RHS.From() >= Zero; + + bool IsLHSNegative = LHS.To() < Zero; + bool IsRHSNegative = RHS.To() < Zero; + + // Check if both ranges have the same sign. + if ((IsLHSPositiveOrZero && IsRHSPositiveOrZero) || + (IsLHSNegative && IsRHSNegative)) { + // The result is definitely less or equal than any of the operands. + const llvm::APSInt &Max = std::min(LHS.To(), RHS.To()); + + // We conservatively estimate lower bound to be the smallest positive + // or negative value corresponding to the sign of the operands. + const llvm::APSInt &Min = IsLHSNegative + ? ValueFactory.getMinValue(ResultType) + : ValueFactory.getValue(Zero); + + return {RangeFactory, Min, Max}; + } + + // Otherwise, let's check if at least one of the operands is positive. + if (IsLHSPositiveOrZero || IsRHSPositiveOrZero) { + // This makes result definitely positive. + // + // We can also reason about a maximal value by finding the maximal + // value of the positive operand. + const llvm::APSInt &Max = IsLHSPositiveOrZero ? LHS.To() : RHS.To(); + + // The minimal value on the other hand is much harder to reason about. + // The only thing we know for sure is that the result is positive. + return {RangeFactory, ValueFactory.getValue(Zero), + ValueFactory.getValue(Max)}; + } + + // Nothing much else to do here. + return infer(T); +} + +template <> +RangeSet SymbolicRangeInferrer::VisitBinaryOperator<BO_Rem>(Range LHS, + Range RHS, + QualType T) { + llvm::APSInt Zero = ValueFactory.getAPSIntType(T).getZeroValue(); + + Range ConservativeRange = getSymmetricalRange(RHS, T); + + llvm::APSInt Max = ConservativeRange.To(); + llvm::APSInt Min = ConservativeRange.From(); + + if (Max == Zero) { + // It's an undefined behaviour to divide by 0 and it seems like we know + // for sure that RHS is 0. Let's say that the resulting range is + // simply infeasible for that matter. + return RangeFactory.getEmptySet(); + } + + // At this point, our conservative range is closed. The result, however, + // couldn't be greater than the RHS' maximal absolute value. Because of + // this reason, we turn the range into open (or half-open in case of + // unsigned integers). + // + // While we operate on integer values, an open interval (a, b) can be easily + // represented by the closed interval [a + 1, b - 1]. And this is exactly + // what we do next. + // + // If we are dealing with unsigned case, we shouldn't move the lower bound. + if (Min.isSigned()) { + ++Min; + } + --Max; + + bool IsLHSPositiveOrZero = LHS.From() >= Zero; + bool IsRHSPositiveOrZero = RHS.From() >= Zero; + + // Remainder operator results with negative operands is implementation + // defined. Positive cases are much easier to reason about though. + if (IsLHSPositiveOrZero && IsRHSPositiveOrZero) { + // If maximal value of LHS is less than maximal value of RHS, + // the result won't get greater than LHS.To(). + Max = std::min(LHS.To(), Max); + // We want to check if it is a situation similar to the following: + // + // <------------|---[ LHS ]--------[ RHS ]-----> + // -INF 0 +INF + // + // In this situation, we can conclude that (LHS / RHS) == 0 and + // (LHS % RHS) == LHS. + Min = LHS.To() < RHS.From() ? LHS.From() : Zero; + } + + // Nevertheless, the symmetrical range for RHS is a conservative estimate + // for any sign of either LHS, or RHS. + return {RangeFactory, ValueFactory.getValue(Min), ValueFactory.getValue(Max)}; +} + class RangeConstraintManager : public RangedConstraintManager { public: - RangeConstraintManager(SubEngine *SE, SValBuilder &SVB) - : RangedConstraintManager(SE, SVB) {} + RangeConstraintManager(ExprEngine *EE, SValBuilder &SVB) + : RangedConstraintManager(EE, SVB) {} //===------------------------------------------------------------------===// // Implementation for interface from ConstraintManager. @@ -305,8 +971,6 @@ private: RangeSet::Factory F; RangeSet getRange(ProgramStateRef State, SymbolRef Sym); - const RangeSet* getRangeForMinusSymbol(ProgramStateRef State, - SymbolRef Sym); RangeSet getSymLTRange(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, @@ -323,13 +987,13 @@ private: RangeSet getSymGERange(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, const llvm::APSInt &Adjustment); - }; } // end anonymous namespace std::unique_ptr<ConstraintManager> -ento::CreateRangeConstraintManager(ProgramStateManager &StMgr, SubEngine *Eng) { +ento::CreateRangeConstraintManager(ProgramStateManager &StMgr, + ExprEngine *Eng) { return std::make_unique<RangeConstraintManager>(Eng, StMgr.getSValBuilder()); } @@ -429,113 +1093,9 @@ RangeConstraintManager::removeDeadBindings(ProgramStateRef State, return Changed ? State->set<ConstraintRange>(CR) : State; } -/// Return a range set subtracting zero from \p Domain. -static RangeSet assumeNonZero( - BasicValueFactory &BV, - RangeSet::Factory &F, - SymbolRef Sym, - RangeSet Domain) { - APSIntType IntType = BV.getAPSIntType(Sym->getType()); - return Domain.Intersect(BV, F, ++IntType.getZeroValue(), - --IntType.getZeroValue()); -} - -/// Apply implicit constraints for bitwise OR- and AND-. -/// For unsigned types, bitwise OR with a constant always returns -/// a value greater-or-equal than the constant, and bitwise AND -/// returns a value less-or-equal then the constant. -/// -/// Pattern matches the expression \p Sym against those rule, -/// and applies the required constraints. -/// \p Input Previously established expression range set -static RangeSet applyBitwiseConstraints( - BasicValueFactory &BV, - RangeSet::Factory &F, - RangeSet Input, - const SymIntExpr* SIE) { - QualType T = SIE->getType(); - bool IsUnsigned = T->isUnsignedIntegerType(); - const llvm::APSInt &RHS = SIE->getRHS(); - const llvm::APSInt &Zero = BV.getAPSIntType(T).getZeroValue(); - BinaryOperator::Opcode Operator = SIE->getOpcode(); - - // For unsigned types, the output of bitwise-or is bigger-or-equal than RHS. - if (Operator == BO_Or && IsUnsigned) - return Input.Intersect(BV, F, RHS, BV.getMaxValue(T)); - - // Bitwise-or with a non-zero constant is always non-zero. - if (Operator == BO_Or && RHS != Zero) - return assumeNonZero(BV, F, SIE, Input); - - // For unsigned types, or positive RHS, - // bitwise-and output is always smaller-or-equal than RHS (assuming two's - // complement representation of signed types). - if (Operator == BO_And && (IsUnsigned || RHS >= Zero)) - return Input.Intersect(BV, F, BV.getMinValue(T), RHS); - - return Input; -} - RangeSet RangeConstraintManager::getRange(ProgramStateRef State, SymbolRef Sym) { - ConstraintRangeTy::data_type *V = State->get<ConstraintRange>(Sym); - - // If Sym is a difference of symbols A - B, then maybe we have range set - // stored for B - A. - BasicValueFactory &BV = getBasicVals(); - const RangeSet *R = getRangeForMinusSymbol(State, Sym); - - // If we have range set stored for both A - B and B - A then calculate the - // effective range set by intersecting the range set for A - B and the - // negated range set of B - A. - if (V && R) - return V->Intersect(BV, F, R->Negate(BV, F)); - if (V) - return *V; - if (R) - return R->Negate(BV, F); - - // Lazily generate a new RangeSet representing all possible values for the - // given symbol type. - QualType T = Sym->getType(); - - RangeSet Result(F, BV.getMinValue(T), BV.getMaxValue(T)); - - // References are known to be non-zero. - if (T->isReferenceType()) - return assumeNonZero(BV, F, Sym, Result); - - // Known constraints on ranges of bitwise expressions. - if (const SymIntExpr* SIE = dyn_cast<SymIntExpr>(Sym)) - return applyBitwiseConstraints(BV, F, Result, SIE); - - return Result; -} - -// FIXME: Once SValBuilder supports unary minus, we should use SValBuilder to -// obtain the negated symbolic expression instead of constructing the -// symbol manually. This will allow us to support finding ranges of not -// only negated SymSymExpr-type expressions, but also of other, simpler -// expressions which we currently do not know how to negate. -const RangeSet* -RangeConstraintManager::getRangeForMinusSymbol(ProgramStateRef State, - SymbolRef Sym) { - if (const SymSymExpr *SSE = dyn_cast<SymSymExpr>(Sym)) { - if (SSE->getOpcode() == BO_Sub) { - QualType T = Sym->getType(); - SymbolManager &SymMgr = State->getSymbolManager(); - SymbolRef negSym = SymMgr.getSymSymExpr(SSE->getRHS(), BO_Sub, - SSE->getLHS(), T); - if (const RangeSet *negV = State->get<ConstraintRange>(negSym)) { - // Unsigned range set cannot be negated, unless it is [0, 0]. - if ((negV->getConcreteValue() && - (*negV->getConcreteValue() == 0)) || - T->isSignedIntegerOrEnumerationType()) - return negV; - } - } - } - return nullptr; + return SymbolicRangeInferrer::inferRange(getBasicVals(), F, State, Sym); } //===------------------------------------------------------------------------=== diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index 4797f564a8375..57fde32bc01d0 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -23,10 +23,11 @@ #include "clang/Basic/TargetInfo.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "llvm/ADT/ImmutableMap.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/raw_ostream.h" @@ -381,7 +382,7 @@ public: : StoreManager(mgr), Features(f), RBFactory(mgr.getAllocator()), CBFactory(mgr.getAllocator()), SmallStructLimit(0) { - SubEngine &Eng = StateMgr.getOwningEngine(); + ExprEngine &Eng = StateMgr.getOwningEngine(); AnalyzerOptions &Options = Eng.getAnalysisManager().options; SmallStructLimit = Options.RegionStoreSmallStructLimit; } @@ -622,15 +623,6 @@ public: // Part of public interface to class. SymbolReaper& SymReaper) override; //===------------------------------------------------------------------===// - // Region "extents". - //===------------------------------------------------------------------===// - - // FIXME: This method will soon be eliminated; see the note in Store.h. - DefinedOrUnknownSVal getSizeInElements(ProgramStateRef state, - const MemRegion* R, - QualType EleTy) override; - - //===------------------------------------------------------------------===// // Utility methods. //===------------------------------------------------------------------===// @@ -876,7 +868,7 @@ collectSubRegionBindings(SmallVectorImpl<BindingPair> &Bindings, // Find the length (in bits) of the region being invalidated. uint64_t Length = UINT64_MAX; - SVal Extent = Top->getExtent(SVB); + SVal Extent = Top->getMemRegionManager().getStaticSize(Top, SVB); if (Optional<nonloc::ConcreteInt> ExtentCI = Extent.getAs<nonloc::ConcreteInt>()) { const llvm::APSInt &ExtentInt = ExtentCI->getValue(); @@ -1387,37 +1379,6 @@ RegionStoreManager::invalidateRegions(Store store, } //===----------------------------------------------------------------------===// -// Extents for regions. -//===----------------------------------------------------------------------===// - -DefinedOrUnknownSVal -RegionStoreManager::getSizeInElements(ProgramStateRef state, - const MemRegion *R, - QualType EleTy) { - SVal Size = cast<SubRegion>(R)->getExtent(svalBuilder); - const llvm::APSInt *SizeInt = svalBuilder.getKnownValue(state, Size); - if (!SizeInt) - return UnknownVal(); - - CharUnits RegionSize = CharUnits::fromQuantity(SizeInt->getSExtValue()); - - if (Ctx.getAsVariableArrayType(EleTy)) { - // FIXME: We need to track extra state to properly record the size - // of VLAs. Returning UnknownVal here, however, is a stop-gap so that - // we don't have a divide-by-zero below. - return UnknownVal(); - } - - CharUnits EleSize = Ctx.getTypeSizeInChars(EleTy); - - // If a variable is reinterpreted as a type that doesn't fit into a larger - // type evenly, round it down. - // This is a signed value, since it's used in arithmetic with signed indices. - return svalBuilder.makeIntVal(RegionSize / EleSize, - svalBuilder.getArrayIndexType()); -} - -//===----------------------------------------------------------------------===// // Location and region casting. //===----------------------------------------------------------------------===// @@ -1667,10 +1628,6 @@ RegionStoreManager::findLazyBinding(RegionBindingsConstRef B, SVal RegionStoreManager::getBindingForElement(RegionBindingsConstRef B, const ElementRegion* R) { - // We do not currently model bindings of the CompoundLiteralregion. - if (isa<CompoundLiteralRegion>(R->getBaseRegion())) - return UnknownVal(); - // Check if the region has a binding. if (const Optional<SVal> &V = B.getDirectBinding(R)) return *V; diff --git a/clang/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp b/clang/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp index 6ad12ca0a688f..7395622a659ca 100644 --- a/clang/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp @@ -13,6 +13,6 @@ using namespace clang; using namespace ento; std::unique_ptr<ConstraintManager> -ento::CreateZ3ConstraintManager(ProgramStateManager &StMgr, SubEngine *Eng) { +ento::CreateZ3ConstraintManager(ProgramStateManager &StMgr, ExprEngine *Eng) { return std::make_unique<SMTConstraintManager>(Eng, StMgr.getSValBuilder()); } diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index 3a5841137e1a7..c00a2c8ba8a2c 100644 --- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -24,12 +24,12 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/Store.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" #include "llvm/ADT/APSInt.h" diff --git a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp index 12332aaf936fa..8c2e856015768 100644 --- a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "clang/Analysis/PathDiagnostic.h" +#include "clang/Basic/FileManager.h" #include "clang/Basic/Version.h" #include "clang/Lex/Preprocessor.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" @@ -49,8 +50,14 @@ public: void ento::createSarifDiagnosticConsumer( AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, const std::string &Output, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &) { + const cross_tu::CrossTranslationUnitContext &CTU) { + + // TODO: Emit an error here. + if (Output.empty()) + return; + C.push_back(new SarifDiagnostics(AnalyzerOpts, Output, PP.getLangOpts())); + createTextMinimalPathDiagnosticConsumer(AnalyzerOpts, C, Output, PP, CTU); } static StringRef getFileName(const FileEntry &FE) { @@ -106,7 +113,7 @@ static std::string fileNameToURI(StringRef Filename) { } }); - return Ret.str().str(); + return std::string(Ret); } static json::Object createArtifactLocation(const FileEntry &FE) { @@ -322,7 +329,7 @@ static json::Object createRule(const PathDiagnostic &Diag) { {"name", CheckName}, {"id", CheckName}}; - std::string RuleURI = getRuleHelpURIStr(CheckName); + std::string RuleURI = std::string(getRuleHelpURIStr(CheckName)); if (!RuleURI.empty()) Ret["helpUri"] = RuleURI; diff --git a/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp b/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp index 85f60231a2769..3709106ad44ce 100644 --- a/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp @@ -44,8 +44,8 @@ ProgramStateRef SimpleConstraintManager::assume(ProgramStateRef State, ProgramStateRef SimpleConstraintManager::assume(ProgramStateRef State, NonLoc Cond, bool Assumption) { State = assumeAux(State, Cond, Assumption); - if (NotifyAssumeClients && SU) - return SU->processAssume(State, Cond, Assumption); + if (NotifyAssumeClients && EE) + return EE->processAssume(State, Cond, Assumption); return State; } diff --git a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp index 84c52f53ca5e7..2e269f6a596e8 100644 --- a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp @@ -13,8 +13,8 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SValVisitor.h" using namespace clang; @@ -652,6 +652,11 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, if (LHSValue == 0) return evalCastFromNonLoc(lhs, resultTy); return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); + case BO_Rem: + // 0 % x == 0 + if (LHSValue == 0) + return makeZeroVal(resultTy); + LLVM_FALLTHROUGH; default: return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); } diff --git a/clang/lib/StaticAnalyzer/Core/Store.cpp b/clang/lib/StaticAnalyzer/Core/Store.cpp index b33129c88cea4..ea617bbeeba1a 100644 --- a/clang/lib/StaticAnalyzer/Core/Store.cpp +++ b/clang/lib/StaticAnalyzer/Core/Store.cpp @@ -134,7 +134,8 @@ const MemRegion *StoreManager::castRegion(const MemRegion *R, QualType CastToTy) case MemRegion::FieldRegionKind: case MemRegion::ObjCIvarRegionKind: case MemRegion::ObjCStringRegionKind: - case MemRegion::VarRegionKind: + case MemRegion::NonParamVarRegionKind: + case MemRegion::ParamVarRegionKind: case MemRegion::CXXTempObjectRegionKind: case MemRegion::CXXBaseObjectRegionKind: case MemRegion::CXXDerivedObjectRegionKind: diff --git a/clang/lib/StaticAnalyzer/Core/SubEngine.cpp b/clang/lib/StaticAnalyzer/Core/SubEngine.cpp deleted file mode 100644 index d7ddd9cf46105..0000000000000 --- a/clang/lib/StaticAnalyzer/Core/SubEngine.cpp +++ /dev/null @@ -1,13 +0,0 @@ -//== SubEngine.cpp - Interface of the subengine of CoreEngine ------*- C++ -*-// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" - -using namespace clang::ento; - -void SubEngine::anchor() { } diff --git a/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp b/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp index 675209f6fd7e5..6ca7aec9caeca 100644 --- a/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp @@ -34,45 +34,27 @@ using namespace ento; void SymExpr::anchor() {} -LLVM_DUMP_METHOD void SymExpr::dump() const { - dumpToStream(llvm::errs()); -} +LLVM_DUMP_METHOD void SymExpr::dump() const { dumpToStream(llvm::errs()); } -void SymIntExpr::dumpToStream(raw_ostream &os) const { - os << '('; - getLHS()->dumpToStream(os); - os << ") " - << BinaryOperator::getOpcodeStr(getOpcode()) << ' '; - if (getRHS().isUnsigned()) - os << getRHS().getZExtValue(); - else - os << getRHS().getSExtValue(); - if (getRHS().isUnsigned()) - os << 'U'; +void BinarySymExpr::dumpToStreamImpl(raw_ostream &OS, const SymExpr *Sym) { + OS << '('; + Sym->dumpToStream(OS); + OS << ')'; } -void IntSymExpr::dumpToStream(raw_ostream &os) const { - if (getLHS().isUnsigned()) - os << getLHS().getZExtValue(); +void BinarySymExpr::dumpToStreamImpl(raw_ostream &OS, + const llvm::APSInt &Value) { + if (Value.isUnsigned()) + OS << Value.getZExtValue(); else - os << getLHS().getSExtValue(); - if (getLHS().isUnsigned()) - os << 'U'; - os << ' ' - << BinaryOperator::getOpcodeStr(getOpcode()) - << " ("; - getRHS()->dumpToStream(os); - os << ')'; + OS << Value.getSExtValue(); + if (Value.isUnsigned()) + OS << 'U'; } -void SymSymExpr::dumpToStream(raw_ostream &os) const { - os << '('; - getLHS()->dumpToStream(os); - os << ") " - << BinaryOperator::getOpcodeStr(getOpcode()) - << " ("; - getRHS()->dumpToStream(os); - os << ')'; +void BinarySymExpr::dumpToStreamImpl(raw_ostream &OS, + BinaryOperator::Opcode Op) { + OS << ' ' << BinaryOperator::getOpcodeStr(Op) << ' '; } void SymbolCast::dumpToStream(raw_ostream &os) const { @@ -329,7 +311,7 @@ QualType SymbolDerived::getType() const { } QualType SymbolExtent::getType() const { - ASTContext &Ctx = R->getMemRegionManager()->getContext(); + ASTContext &Ctx = R->getMemRegionManager().getContext(); return Ctx.getSizeType(); } @@ -341,10 +323,6 @@ QualType SymbolRegionValue::getType() const { return R->getValueType(); } -SymbolManager::~SymbolManager() { - llvm::DeleteContainerSeconds(SymbolDependencies); -} - bool SymbolManager::canSymbolicate(QualType T) { T = T.getCanonicalType(); @@ -362,13 +340,9 @@ bool SymbolManager::canSymbolicate(QualType T) { void SymbolManager::addSymbolDependency(const SymbolRef Primary, const SymbolRef Dependent) { - SymbolDependTy::iterator I = SymbolDependencies.find(Primary); - SymbolRefSmallVectorTy *dependencies = nullptr; - if (I == SymbolDependencies.end()) { - dependencies = new SymbolRefSmallVectorTy(); - SymbolDependencies[Primary] = dependencies; - } else { - dependencies = I->second; + auto &dependencies = SymbolDependencies[Primary]; + if (!dependencies) { + dependencies = std::make_unique<SymbolRefSmallVectorTy>(); } dependencies->push_back(Dependent); } @@ -378,7 +352,7 @@ const SymbolRefSmallVectorTy *SymbolManager::getDependentSymbols( SymbolDependTy::const_iterator I = SymbolDependencies.find(Primary); if (I == SymbolDependencies.end()) return nullptr; - return I->second; + return I->second.get(); } void SymbolReaper::markDependentsLive(SymbolRef sym) { @@ -542,6 +516,11 @@ bool SymbolReaper::isLive(const VarRegion *VR, bool includeStoreBindings) const{ if (!Loc) return true; + // Anonymous parameters of an inheriting constructor are live for the entire + // duration of the constructor. + if (isa<CXXInheritedCtorInitExpr>(Loc)) + return true; + if (LCtx->getAnalysis<RelaxedLiveVariables>()->isLive(Loc, VR->getDecl())) return true; diff --git a/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp new file mode 100644 index 0000000000000..f4c7e5978e193 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp @@ -0,0 +1,156 @@ +//===--- TextDiagnostics.cpp - Text Diagnostics for Paths -------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines the TextDiagnostics object. +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/PathDiagnostic.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/Version.h" +#include "clang/CrossTU/CrossTranslationUnit.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" +#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Casting.h" + +using namespace clang; +using namespace ento; +using namespace tooling; + +namespace { +/// Emitsd minimal diagnostics (report message + notes) for the 'none' output +/// type to the standard error, or to to compliment many others. Emits detailed +/// diagnostics in textual format for the 'text' output type. +class TextDiagnostics : public PathDiagnosticConsumer { + DiagnosticsEngine &DiagEng; + const LangOptions &LO; + const bool IncludePath = false; + const bool ShouldEmitAsError = false; + const bool ApplyFixIts = false; + const bool ShouldDisplayCheckerName = false; + +public: + TextDiagnostics(DiagnosticsEngine &DiagEng, const LangOptions &LO, + bool ShouldIncludePath, const AnalyzerOptions &AnOpts) + : DiagEng(DiagEng), LO(LO), IncludePath(ShouldIncludePath), + ShouldEmitAsError(AnOpts.AnalyzerWerror), + ApplyFixIts(AnOpts.ShouldApplyFixIts), + ShouldDisplayCheckerName(AnOpts.ShouldDisplayCheckerNameForText) {} + ~TextDiagnostics() override {} + + StringRef getName() const override { return "TextDiagnostics"; } + + bool supportsLogicalOpControlFlow() const override { return true; } + bool supportsCrossFileDiagnostics() const override { return true; } + + PathGenerationScheme getGenerationScheme() const override { + return IncludePath ? Minimal : None; + } + + void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, + FilesMade *filesMade) override { + unsigned WarnID = + ShouldEmitAsError + ? DiagEng.getCustomDiagID(DiagnosticsEngine::Error, "%0") + : DiagEng.getCustomDiagID(DiagnosticsEngine::Warning, "%0"); + unsigned NoteID = DiagEng.getCustomDiagID(DiagnosticsEngine::Note, "%0"); + SourceManager &SM = DiagEng.getSourceManager(); + + Replacements Repls; + auto reportPiece = [&](unsigned ID, FullSourceLoc Loc, StringRef String, + ArrayRef<SourceRange> Ranges, + ArrayRef<FixItHint> Fixits) { + if (!ApplyFixIts) { + DiagEng.Report(Loc, ID) << String << Ranges << Fixits; + return; + } + + DiagEng.Report(Loc, ID) << String << Ranges; + for (const FixItHint &Hint : Fixits) { + Replacement Repl(SM, Hint.RemoveRange, Hint.CodeToInsert); + + if (llvm::Error Err = Repls.add(Repl)) { + llvm::errs() << "Error applying replacement " << Repl.toString() + << ": " << Err << "\n"; + } + } + }; + + for (std::vector<const PathDiagnostic *>::iterator I = Diags.begin(), + E = Diags.end(); + I != E; ++I) { + const PathDiagnostic *PD = *I; + std::string WarningMsg = + (ShouldDisplayCheckerName ? " [" + PD->getCheckerName() + "]" : "") + .str(); + + reportPiece(WarnID, PD->getLocation().asLocation(), + (PD->getShortDescription() + WarningMsg).str(), + PD->path.back()->getRanges(), PD->path.back()->getFixits()); + + // First, add extra notes, even if paths should not be included. + for (const auto &Piece : PD->path) { + if (!isa<PathDiagnosticNotePiece>(Piece.get())) + continue; + + reportPiece(NoteID, Piece->getLocation().asLocation(), + Piece->getString(), Piece->getRanges(), + Piece->getFixits()); + } + + if (!IncludePath) + continue; + + // Then, add the path notes if necessary. + PathPieces FlatPath = PD->path.flatten(/*ShouldFlattenMacros=*/true); + for (const auto &Piece : FlatPath) { + if (isa<PathDiagnosticNotePiece>(Piece.get())) + continue; + + reportPiece(NoteID, Piece->getLocation().asLocation(), + Piece->getString(), Piece->getRanges(), + Piece->getFixits()); + } + } + + if (!ApplyFixIts || Repls.empty()) + return; + + Rewriter Rewrite(SM, LO); + if (!applyAllReplacements(Repls, Rewrite)) { + llvm::errs() << "An error occured during applying fix-it.\n"; + } + + Rewrite.overwriteChangedFiles(); + } +}; +} // end anonymous namespace + +void ento::createTextPathDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &Prefix, const clang::Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { + C.emplace_back(new TextDiagnostics(PP.getDiagnostics(), PP.getLangOpts(), + /*ShouldIncludePath*/ true, AnalyzerOpts)); +} + +void ento::createTextMinimalPathDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &Prefix, const clang::Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { + C.emplace_back(new TextDiagnostics(PP.getDiagnostics(), PP.getLangOpts(), + /*ShouldIncludePath*/ false, + AnalyzerOpts)); +} |