diff options
| author | Dimitry Andric <dim@FreeBSD.org> | 2021-02-16 20:13:02 +0000 |
|---|---|---|
| committer | Dimitry Andric <dim@FreeBSD.org> | 2021-02-16 20:13:02 +0000 |
| commit | b60736ec1405bb0a8dd40989f67ef4c93da068ab (patch) | |
| tree | 5c43fbb7c9fc45f0f87e0e6795a86267dbd12f9d /clang/lib/StaticAnalyzer/Core | |
| parent | cfca06d7963fa0909f90483b42a6d7d194d01e08 (diff) | |
Diffstat (limited to 'clang/lib/StaticAnalyzer/Core')
26 files changed, 1605 insertions, 687 deletions
diff --git a/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp b/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp index 01ac2bc83bb6..8cd7f75e4e38 100644 --- a/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ b/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -40,7 +40,7 @@ void AnalyzerOptions::printFormattedEntry( const size_t PadForDesc = InitialPad + EntryWidth; FOut.PadToColumn(InitialPad) << EntryDescPair.first; - // If the buffer's length is greater then PadForDesc, print a newline. + // If the buffer's length is greater than PadForDesc, print a newline. if (FOut.getColumn() > PadForDesc) FOut << '\n'; diff --git a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp index 73f057f09550..d1f5ac02278f 100644 --- a/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp +++ b/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp @@ -42,7 +42,7 @@ void LazyCompoundValData::Profile(llvm::FoldingSetNodeID& ID, } void PointerToMemberData::Profile( - llvm::FoldingSetNodeID& ID, const DeclaratorDecl *D, + llvm::FoldingSetNodeID &ID, const NamedDecl *D, llvm::ImmutableList<const CXXBaseSpecifier *> L) { ID.AddPointer(D); ID.AddPointer(L.getInternalPointer()); @@ -159,17 +159,17 @@ BasicValueFactory::getLazyCompoundValData(const StoreRef &store, } const PointerToMemberData *BasicValueFactory::getPointerToMemberData( - const DeclaratorDecl *DD, llvm::ImmutableList<const CXXBaseSpecifier *> L) { + const NamedDecl *ND, llvm::ImmutableList<const CXXBaseSpecifier *> L) { llvm::FoldingSetNodeID ID; - PointerToMemberData::Profile(ID, DD, L); + PointerToMemberData::Profile(ID, ND, L); void *InsertPos; PointerToMemberData *D = PointerToMemberDataSet.FindNodeOrInsertPos(ID, InsertPos); if (!D) { - D = (PointerToMemberData*) BPAlloc.Allocate<PointerToMemberData>(); - new (D) PointerToMemberData(DD, L); + D = (PointerToMemberData *)BPAlloc.Allocate<PointerToMemberData>(); + new (D) PointerToMemberData(ND, L); PointerToMemberDataSet.InsertNode(D, InsertPos); } @@ -180,25 +180,24 @@ const PointerToMemberData *BasicValueFactory::accumCXXBase( llvm::iterator_range<CastExpr::path_const_iterator> PathRange, const nonloc::PointerToMember &PTM) { nonloc::PointerToMember::PTMDataType PTMDT = PTM.getPTMData(); - const DeclaratorDecl *DD = nullptr; + const NamedDecl *ND = nullptr; llvm::ImmutableList<const CXXBaseSpecifier *> PathList; - if (PTMDT.isNull() || PTMDT.is<const DeclaratorDecl *>()) { - if (PTMDT.is<const DeclaratorDecl *>()) - DD = PTMDT.get<const DeclaratorDecl *>(); + if (PTMDT.isNull() || PTMDT.is<const NamedDecl *>()) { + if (PTMDT.is<const NamedDecl *>()) + ND = PTMDT.get<const NamedDecl *>(); PathList = CXXBaseListFactory.getEmptyList(); } else { // const PointerToMemberData * - const PointerToMemberData *PTMD = - PTMDT.get<const PointerToMemberData *>(); - DD = PTMD->getDeclaratorDecl(); + const PointerToMemberData *PTMD = PTMDT.get<const PointerToMemberData *>(); + ND = PTMD->getDeclaratorDecl(); PathList = PTMD->getCXXBaseList(); } for (const auto &I : llvm::reverse(PathRange)) PathList = prependCXXBase(I, PathList); - return getPointerToMemberData(DD, PathList); + return getPointerToMemberData(ND, PathList); } const llvm::APSInt* diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 72be4e81c83d..bf38891b370a 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -1570,9 +1570,8 @@ static Optional<size_t> getLengthOnSingleLine(const SourceManager &SM, if (FID != SM.getFileID(ExpansionRange.getEnd())) return None; - bool Invalid; - const llvm::MemoryBuffer *Buffer = SM.getBuffer(FID, &Invalid); - if (Invalid) + Optional<MemoryBufferRef> Buffer = SM.getBufferOrNone(FID); + if (!Buffer) return None; unsigned BeginOffset = SM.getFileOffset(ExpansionRange.getBegin()); @@ -2194,8 +2193,8 @@ void BasicBugReport::Profile(llvm::FoldingSetNodeID& hash) const { for (SourceRange range : Ranges) { if (!range.isValid()) continue; - hash.AddInteger(range.getBegin().getRawEncoding()); - hash.AddInteger(range.getEnd().getRawEncoding()); + hash.Add(range.getBegin()); + hash.Add(range.getEnd()); } } @@ -2217,8 +2216,8 @@ void PathSensitiveBugReport::Profile(llvm::FoldingSetNodeID &hash) const { for (SourceRange range : Ranges) { if (!range.isValid()) continue; - hash.AddInteger(range.getBegin().getRawEncoding()); - hash.AddInteger(range.getEnd().getRawEncoding()); + hash.Add(range.getBegin()); + hash.Add(range.getEnd()); } } diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index ef4d38ff498f..bc72f4f8c1e3 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -2813,7 +2813,7 @@ UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, //===----------------------------------------------------------------------===// FalsePositiveRefutationBRVisitor::FalsePositiveRefutationBRVisitor() - : Constraints(ConstraintRangeTy::Factory().getEmptyMap()) {} + : Constraints(ConstraintMap::Factory().getEmptyMap()) {} void FalsePositiveRefutationBRVisitor::finalizeVisitor( BugReporterContext &BRC, const ExplodedNode *EndPathNode, @@ -2855,9 +2855,8 @@ void FalsePositiveRefutationBRVisitor::finalizeVisitor( void FalsePositiveRefutationBRVisitor::addConstraints( const ExplodedNode *N, bool OverwriteConstraintsOnExistingSyms) { // Collect new constraints - const ConstraintRangeTy &NewCs = N->getState()->get<ConstraintRange>(); - ConstraintRangeTy::Factory &CF = - N->getState()->get_context<ConstraintRange>(); + ConstraintMap NewCs = getConstraintMap(N->getState()); + ConstraintMap::Factory &CF = N->getState()->get_context<ConstraintMap>(); // Add constraints if we don't have them yet for (auto const &C : NewCs) { diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 78d13ddfb773..a55d9302ca58 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -687,7 +687,7 @@ void CXXInstanceCall::getExtraInvalidatedValues( // base class decl, rather than the class of the instance which needs to be // checked for mutable fields. // TODO: We might as well look at the dynamic type of the object. - const Expr *Ex = getCXXThisExpr()->ignoreParenBaseCasts(); + const Expr *Ex = getCXXThisExpr()->IgnoreParenBaseCasts(); QualType T = Ex->getType(); if (T->isPointerType()) // Arrow or implicit-this syntax? T = T->getPointeeType(); diff --git a/clang/lib/StaticAnalyzer/Core/CheckerContext.cpp b/clang/lib/StaticAnalyzer/Core/CheckerContext.cpp index 725ff1002e29..3d44d2cbc069 100644 --- a/clang/lib/StaticAnalyzer/Core/CheckerContext.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerContext.cpp @@ -93,7 +93,7 @@ StringRef CheckerContext::getMacroNameOrSpelling(SourceLocation &Loc) { if (Loc.isMacroID()) return Lexer::getImmediateMacroName(Loc, getSourceManager(), getLangOpts()); - SmallVector<char, 16> buf; + SmallString<16> buf; return Lexer::getSpelling(Loc, buf, getSourceManager(), getLangOpts()); } diff --git a/clang/lib/StaticAnalyzer/Core/DynamicType.cpp b/clang/lib/StaticAnalyzer/Core/DynamicType.cpp index e9b64fd79614..9ed915aafcab 100644 --- a/clang/lib/StaticAnalyzer/Core/DynamicType.cpp +++ b/clang/lib/StaticAnalyzer/Core/DynamicType.cpp @@ -65,6 +65,13 @@ const DynamicTypeInfo *getRawDynamicTypeInfo(ProgramStateRef State, return State->get<DynamicTypeMap>(MR); } +static void unbox(QualType &Ty) { + // FIXME: Why are we being fed references to pointers in the first place? + while (Ty->isReferenceType() || Ty->isPointerType()) + Ty = Ty->getPointeeType(); + Ty = Ty.getCanonicalType().getUnqualifiedType(); +} + const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State, const MemRegion *MR, QualType CastFromTy, @@ -73,6 +80,9 @@ const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State, if (!Lookup) return nullptr; + unbox(CastFromTy); + unbox(CastToTy); + for (const DynamicCastInfo &Cast : *Lookup) if (Cast.equals(CastFromTy, CastToTy)) return &Cast; @@ -112,6 +122,9 @@ ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State, State = State->set<DynamicTypeMap>(MR, CastToTy); } + unbox(CastFromTy); + unbox(CastToTy); + DynamicCastInfo::CastResult ResultKind = CastSucceeds ? DynamicCastInfo::CastResult::Success : DynamicCastInfo::CastResult::Failure; diff --git a/clang/lib/StaticAnalyzer/Core/Environment.cpp b/clang/lib/StaticAnalyzer/Core/Environment.cpp index 9e6d79bb7dcc..ee7474592528 100644 --- a/clang/lib/StaticAnalyzer/Core/Environment.cpp +++ b/clang/lib/StaticAnalyzer/Core/Environment.cpp @@ -15,6 +15,7 @@ #include "clang/AST/ExprCXX.h" #include "clang/AST/PrettyPrinter.h" #include "clang/AST/Stmt.h" +#include "clang/AST/StmtObjC.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/LangOptions.h" @@ -85,6 +86,12 @@ SVal Environment::lookupExpr(const EnvironmentEntry &E) const { SVal Environment::getSVal(const EnvironmentEntry &Entry, SValBuilder& svalBuilder) const { const Stmt *S = Entry.getStmt(); + assert(!isa<ObjCForCollectionStmt>(S) && + "Use ExprEngine::hasMoreIteration()!"); + assert((isa<Expr>(S) || isa<ReturnStmt>(S)) && + "Environment can only argue about Exprs, since only they express " + "a value! Any non-expression statement stored in Environment is a " + "result of a hack!"); const LocationContext *LCtx = Entry.getLocationContext(); switch (S->getStmtClass()) { @@ -109,6 +116,7 @@ SVal Environment::getSVal(const EnvironmentEntry &Entry, case Stmt::StringLiteralClass: case Stmt::TypeTraitExprClass: case Stmt::SizeOfPackExprClass: + case Stmt::PredefinedExprClass: // Known constants; defer to SValBuilder. return svalBuilder.getConstantVal(cast<Expr>(S)).getValue(); @@ -183,18 +191,15 @@ 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(), End = Env.end(); I != End; ++I) { const EnvironmentEntry &BlkExpr = I.getKey(); const SVal &X = I.getData(); - const bool IsBlkExprLive = - SymReaper.isLive(BlkExpr.getStmt(), BlkExpr.getLocationContext()); + const Expr *E = dyn_cast<Expr>(BlkExpr.getStmt()); + if (!E) + continue; - assert((isa<Expr>(BlkExpr.getStmt()) || !IsBlkExprLive) && - "Only Exprs can be live, LivenessAnalysis argues about the liveness " - "of *values*!"); - - if (IsBlkExprLive) { + if (SymReaper.isLive(E, BlkExpr.getLocationContext())) { // Copy the binding to the new map. EBMapRef = EBMapRef.add(BlkExpr, X); diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 265dcd134213..f285b652c175 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -169,7 +169,7 @@ public: if (S) { S->printJson(Out, Helper, PP, /*AddQuotes=*/true); } else { - Out << '\"' << I->getAnyMember()->getNameAsString() << '\"'; + Out << '\"' << I->getAnyMember()->getDeclName() << '\"'; } } @@ -2129,6 +2129,83 @@ static const Stmt *ResolveCondition(const Stmt *Condition, llvm_unreachable("could not resolve condition"); } +using ObjCForLctxPair = + std::pair<const ObjCForCollectionStmt *, const LocationContext *>; + +REGISTER_MAP_WITH_PROGRAMSTATE(ObjCForHasMoreIterations, ObjCForLctxPair, bool) + +ProgramStateRef ExprEngine::setWhetherHasMoreIteration( + ProgramStateRef State, const ObjCForCollectionStmt *O, + const LocationContext *LC, bool HasMoreIteraton) { + assert(!State->contains<ObjCForHasMoreIterations>({O, LC})); + return State->set<ObjCForHasMoreIterations>({O, LC}, HasMoreIteraton); +} + +ProgramStateRef +ExprEngine::removeIterationState(ProgramStateRef State, + const ObjCForCollectionStmt *O, + const LocationContext *LC) { + assert(State->contains<ObjCForHasMoreIterations>({O, LC})); + return State->remove<ObjCForHasMoreIterations>({O, LC}); +} + +bool ExprEngine::hasMoreIteration(ProgramStateRef State, + const ObjCForCollectionStmt *O, + const LocationContext *LC) { + assert(State->contains<ObjCForHasMoreIterations>({O, LC})); + return *State->get<ObjCForHasMoreIterations>({O, LC}); +} + +/// Split the state on whether there are any more iterations left for this loop. +/// Returns a (HasMoreIteration, HasNoMoreIteration) pair, or None when the +/// acquisition of the loop condition value failed. +static Optional<std::pair<ProgramStateRef, ProgramStateRef>> +assumeCondition(const Stmt *Condition, ExplodedNode *N) { + ProgramStateRef State = N->getState(); + if (const auto *ObjCFor = dyn_cast<ObjCForCollectionStmt>(Condition)) { + bool HasMoreIteraton = + ExprEngine::hasMoreIteration(State, ObjCFor, N->getLocationContext()); + // Checkers have already ran on branch conditions, so the current + // information as to whether the loop has more iteration becomes outdated + // after this point. + State = ExprEngine::removeIterationState(State, ObjCFor, + N->getLocationContext()); + if (HasMoreIteraton) + return std::pair<ProgramStateRef, ProgramStateRef>{State, nullptr}; + else + return std::pair<ProgramStateRef, ProgramStateRef>{nullptr, State}; + } + SVal X = State->getSVal(Condition, N->getLocationContext()); + + if (X.isUnknownOrUndef()) { + // Give it a chance to recover from unknown. + if (const auto *Ex = dyn_cast<Expr>(Condition)) { + if (Ex->getType()->isIntegralOrEnumerationType()) { + // Try to recover some path-sensitivity. Right now casts of symbolic + // integers that promote their values are currently not tracked well. + // If 'Condition' is such an expression, try and recover the + // underlying value and use that instead. + SVal recovered = + RecoverCastedSymbol(State, Condition, N->getLocationContext(), + N->getState()->getStateManager().getContext()); + + if (!recovered.isUnknown()) { + X = recovered; + } + } + } + } + + // If the condition is still unknown, give up. + if (X.isUnknownOrUndef()) + return None; + + DefinedSVal V = X.castAs<DefinedSVal>(); + + ProgramStateRef StTrue, StFalse; + return State->assume(V); +} + void ExprEngine::processBranch(const Stmt *Condition, NodeBuilderContext& BldCtx, ExplodedNode *Pred, @@ -2165,48 +2242,28 @@ void ExprEngine::processBranch(const Stmt *Condition, return; BranchNodeBuilder builder(CheckersOutSet, Dst, BldCtx, DstT, DstF); - for (const auto PredI : CheckersOutSet) { - if (PredI->isSink()) + for (ExplodedNode *PredN : CheckersOutSet) { + if (PredN->isSink()) continue; - ProgramStateRef PrevState = PredI->getState(); - SVal X = PrevState->getSVal(Condition, PredI->getLocationContext()); + ProgramStateRef PrevState = PredN->getState(); - if (X.isUnknownOrUndef()) { - // Give it a chance to recover from unknown. - if (const auto *Ex = dyn_cast<Expr>(Condition)) { - if (Ex->getType()->isIntegralOrEnumerationType()) { - // Try to recover some path-sensitivity. Right now casts of symbolic - // integers that promote their values are currently not tracked well. - // If 'Condition' is such an expression, try and recover the - // underlying value and use that instead. - SVal recovered = RecoverCastedSymbol(PrevState, Condition, - PredI->getLocationContext(), - getContext()); - - if (!recovered.isUnknown()) { - X = recovered; - } - } - } - } - - // If the condition is still unknown, give up. - if (X.isUnknownOrUndef()) { - builder.generateNode(PrevState, true, PredI); - builder.generateNode(PrevState, false, PredI); + ProgramStateRef StTrue, StFalse; + if (const auto KnownCondValueAssumption = assumeCondition(Condition, PredN)) + std::tie(StTrue, StFalse) = *KnownCondValueAssumption; + else { + assert(!isa<ObjCForCollectionStmt>(Condition)); + builder.generateNode(PrevState, true, PredN); + builder.generateNode(PrevState, false, PredN); continue; } - - DefinedSVal V = X.castAs<DefinedSVal>(); - - ProgramStateRef StTrue, StFalse; - std::tie(StTrue, StFalse) = PrevState->assume(V); + if (StTrue && StFalse) + assert(!isa<ObjCForCollectionStmt>(Condition));; // Process the true branch. if (builder.isFeasible(true)) { if (StTrue) - builder.generateNode(StTrue, true, PredI); + builder.generateNode(StTrue, true, PredN); else builder.markInfeasible(true); } @@ -2214,7 +2271,7 @@ void ExprEngine::processBranch(const Stmt *Condition, // Process the false branch. if (builder.isFeasible(false)) { if (StFalse) - builder.generateNode(StFalse, false, PredI); + builder.generateNode(StFalse, false, PredN); else builder.markInfeasible(false); } @@ -2530,16 +2587,8 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D, return; } if (isa<FieldDecl>(D) || isa<IndirectFieldDecl>(D)) { - // FIXME: Compute lvalue of field pointers-to-member. - // Right now we just use a non-null void pointer, so that it gives proper - // results in boolean contexts. - // FIXME: Maybe delegate this to the surrounding operator&. - // Note how this expression is lvalue, however pointer-to-member is NonLoc. - SVal V = svalBuilder.conjureSymbolVal(Ex, LCtx, getContext().VoidPtrTy, - currBldrCtx->blockCount()); - state = state->assume(V.castAs<DefinedOrUnknownSVal>(), true); - Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, V), nullptr, - ProgramPoint::PostLValueKind); + // Delegate all work related to pointer to members to the surrounding + // operator&. return; } if (isa<BindingDecl>(D)) { @@ -3100,7 +3149,7 @@ struct DOTGraphTraits<ExplodedGraph*> : public DefaultDOTGraphTraits { if (Stop(N)) return true; - if (N->succ_size() != 1 || !isNodeHidden(N->getFirstSucc())) + if (N->succ_size() != 1 || !isNodeHidden(N->getFirstSucc(), nullptr)) break; PostCallback(N); @@ -3109,7 +3158,7 @@ struct DOTGraphTraits<ExplodedGraph*> : public DefaultDOTGraphTraits { return false; } - static bool isNodeHidden(const ExplodedNode *N) { + static bool isNodeHidden(const ExplodedNode *N, const ExplodedGraph *G) { return N->isTrivial(); } @@ -3162,8 +3211,9 @@ void ExprEngine::ViewGraph(bool trim) { #ifndef NDEBUG std::string Filename = DumpGraph(trim); llvm::DisplayGraph(Filename, false, llvm::GraphProgram::DOT); -#endif +#else llvm::errs() << "Warning: viewing graph requires assertions" << "\n"; +#endif } @@ -3171,8 +3221,9 @@ void ExprEngine::ViewGraph(ArrayRef<const ExplodedNode*> Nodes) { #ifndef NDEBUG std::string Filename = DumpGraph(Nodes); llvm::DisplayGraph(Filename, false, llvm::GraphProgram::DOT); -#endif +#else llvm::errs() << "Warning: viewing graph requires assertions" << "\n"; +#endif } std::string ExprEngine::DumpGraph(bool trim, StringRef Filename) { @@ -3209,15 +3260,17 @@ std::string ExprEngine::DumpGraph(ArrayRef<const ExplodedNode*> Nodes, if (!TrimmedG.get()) { llvm::errs() << "warning: Trimmed ExplodedGraph is empty.\n"; + return ""; } else { return llvm::WriteGraph(TrimmedG.get(), "TrimmedExprEngine", /*ShortNames=*/false, /*Title=*/"Trimmed Exploded Graph", /*Filename=*/std::string(Filename)); } -#endif +#else llvm::errs() << "Warning: dumping graph requires assertions" << "\n"; return ""; +#endif } void *ProgramStateTrait<ReplayWithoutInlining>::GDMIndex() { diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp index c5e38cc7423d..18d1b2169eed 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -418,6 +418,8 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, case CK_ZeroToOCLOpaqueType: case CK_IntToOCLSampler: case CK_LValueBitCast: + case CK_FloatingToFixedPoint: + case CK_FixedPointToFloating: case CK_FixedPointCast: case CK_FixedPointToBoolean: case CK_FixedPointToIntegral: @@ -991,10 +993,11 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, ExplodedNode *Pred, if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Ex)) { const ValueDecl *VD = DRE->getDecl(); - if (isa<CXXMethodDecl>(VD) || isa<FieldDecl>(VD)) { + if (isa<CXXMethodDecl>(VD) || isa<FieldDecl>(VD) || + isa<IndirectFieldDecl>(VD)) { ProgramStateRef State = (*I)->getState(); const LocationContext *LCtx = (*I)->getLocationContext(); - SVal SV = svalBuilder.getMemberPointer(cast<DeclaratorDecl>(VD)); + SVal SV = svalBuilder.getMemberPointer(cast<NamedDecl>(VD)); Bldr.generateNode(U, *I, State->BindExpr(U, LCtx, SV)); break; } diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index 38a680eb04c0..cab65687444b 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -132,10 +132,20 @@ SVal ExprEngine::computeObjectUnderConstruction( case ConstructionContext::SimpleConstructorInitializerKind: { const auto *ICC = cast<ConstructorInitializerConstructionContext>(CC); const auto *Init = ICC->getCXXCtorInitializer(); - assert(Init->isAnyMemberInitializer()); const CXXMethodDecl *CurCtor = cast<CXXMethodDecl>(LCtx->getDecl()); Loc ThisPtr = SVB.getCXXThis(CurCtor, LCtx->getStackFrame()); SVal ThisVal = State->getSVal(ThisPtr); + if (Init->isBaseInitializer()) { + const auto *ThisReg = cast<SubRegion>(ThisVal.getAsRegion()); + const CXXRecordDecl *BaseClass = + Init->getBaseClass()->getAsCXXRecordDecl(); + const auto *BaseReg = + MRMgr.getCXXBaseObjectRegion(BaseClass, ThisReg, + Init->isBaseVirtual()); + return SVB.makeLoc(BaseReg); + } + if (Init->isDelegatingInitializer()) + return ThisVal; const ValueDecl *Field; SVal FieldVal; @@ -364,8 +374,12 @@ ProgramStateRef ExprEngine::updateObjectsUnderConstruction( case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind: case ConstructionContext::SimpleConstructorInitializerKind: { const auto *ICC = cast<ConstructorInitializerConstructionContext>(CC); - return addObjectUnderConstruction(State, ICC->getCXXCtorInitializer(), - LCtx, V); + const auto *Init = ICC->getCXXCtorInitializer(); + // Base and delegating initializers handled above + assert(Init->isAnyMemberInitializer() && + "Base and delegating initializers should have been handled by" + "computeObjectUnderConstruction()"); + return addObjectUnderConstruction(State, Init, LCtx, V); } case ConstructionContext::NewAllocatedObjectKind: { return State; @@ -602,11 +616,11 @@ void ExprEngine::handleConstructor(const Expr *E, *Call, *this); ExplodedNodeSet DstEvaluated; - StmtNodeBuilder Bldr(DstPreCall, DstEvaluated, *currBldrCtx); if (CE && CE->getConstructor()->isTrivial() && CE->getConstructor()->isCopyOrMoveConstructor() && !CallOpts.IsArrayCtorOrDtor) { + StmtNodeBuilder Bldr(DstPreCall, DstEvaluated, *currBldrCtx); // FIXME: Handle other kinds of trivial constructors as well. for (ExplodedNodeSet::iterator I = DstPreCall.begin(), E = DstPreCall.end(); I != E; ++I) @@ -626,6 +640,8 @@ void ExprEngine::handleConstructor(const Expr *E, // in the CFG, would be called at the end of the full expression or // later (for life-time extended temporaries) -- but avoids infeasible // paths when no-return temporary destructors are used for assertions. + ExplodedNodeSet DstEvaluatedPostProcessed; + StmtNodeBuilder Bldr(DstEvaluated, DstEvaluatedPostProcessed, *currBldrCtx); const AnalysisDeclContext *ADC = LCtx->getAnalysisDeclContext(); if (!ADC->getCFGBuildOptions().AddTemporaryDtors) { if (llvm::isa_and_nonnull<CXXTempObjectRegion>(TargetRegion) && @@ -655,7 +671,7 @@ void ExprEngine::handleConstructor(const Expr *E, } ExplodedNodeSet DstPostArgumentCleanup; - for (ExplodedNode *I : DstEvaluated) + for (ExplodedNode *I : DstEvaluatedPostProcessed) finishArgumentConstruction(DstPostArgumentCleanup, I, *Call); // If there were other constructors called for object-type arguments diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index 52ba17d59ae0..996d3644e018 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -842,19 +842,7 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, static bool hasMember(const ASTContext &Ctx, const CXXRecordDecl *RD, StringRef Name) { const IdentifierInfo &II = Ctx.Idents.get(Name); - DeclarationName DeclName = Ctx.DeclarationNames.getIdentifier(&II); - if (!RD->lookup(DeclName).empty()) - return true; - - CXXBasePaths Paths(false, false, false); - if (RD->lookupInBases( - [DeclName](const CXXBaseSpecifier *Specifier, CXXBasePath &Path) { - return CXXRecordDecl::FindOrdinaryMember(Specifier, Path, DeclName); - }, - Paths)) - return true; - - return false; + return RD->hasMemberName(Ctx.DeclarationNames.getIdentifier(&II)); } /// Returns true if the given C++ class is a container or iterator. diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp index eb9a0be2e5d6..5a55e81497b0 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp @@ -53,10 +53,8 @@ static void populateObjCForDestinationSet( ProgramStateRef state = Pred->getState(); const LocationContext *LCtx = Pred->getLocationContext(); - SVal hasElementsV = svalBuilder.makeTruthVal(hasElements); - - // FIXME: S is not an expression. We should not be binding values to it. - ProgramStateRef nextState = state->BindExpr(S, LCtx, hasElementsV); + ProgramStateRef nextState = + ExprEngine::setWhetherHasMoreIteration(state, S, LCtx, hasElements); if (auto MV = elementV.getAs<loc::MemRegionVal>()) if (const auto *R = dyn_cast<TypedValueRegion>(MV->getRegion())) { @@ -93,10 +91,9 @@ void ExprEngine::VisitObjCForCollectionStmt(const ObjCForCollectionStmt *S, // (1) binds the next container value to 'element'. This creates a new // node in the ExplodedGraph. // - // (2) binds the value 0/1 to the ObjCForCollectionStmt* itself, indicating - // whether or not the container has any more elements. This value - // will be tested in ProcessBranch. We need to explicitly bind - // this value because a container can contain nil elements. + // (2) note whether the collection has any more elements (or in other words, + // whether the loop has more iterations). This will be tested in + // processBranch. // // FIXME: Eventually this logic should actually do dispatches to // 'countByEnumeratingWithState:objects:count:' (NSFastEnumeration). diff --git a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp index bc7c41d039c4..149459cf986a 100644 --- a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/Analysis/IssueHash.h" #include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" @@ -23,8 +24,6 @@ #include "clang/Lex/Token.h" #include "clang/Rewrite/Core/HTMLRewrite.h" #include "clang/Rewrite/Core/Rewriter.h" -#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" -#include "clang/StaticAnalyzer/Core/IssueHash.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SmallString.h" @@ -58,17 +57,18 @@ using namespace ento; namespace { class HTMLDiagnostics : public PathDiagnosticConsumer { + PathDiagnosticConsumerOptions DiagOpts; std::string Directory; bool createdDir = false; bool noDir = false; const Preprocessor &PP; - AnalyzerOptions &AnalyzerOpts; const bool SupportsCrossFileDiagnostics; public: - HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, const std::string &OutputDir, - const Preprocessor &pp, bool supportsMultipleFiles) - : Directory(OutputDir), PP(pp), AnalyzerOpts(AnalyzerOpts), + HTMLDiagnostics(PathDiagnosticConsumerOptions DiagOpts, + const std::string &OutputDir, const Preprocessor &pp, + bool supportsMultipleFiles) + : DiagOpts(std::move(DiagOpts)), Directory(OutputDir), PP(pp), SupportsCrossFileDiagnostics(supportsMultipleFiles) {} ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); } @@ -133,7 +133,7 @@ private: } // namespace void ento::createHTMLDiagnosticConsumer( - AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &OutputDir, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU) { @@ -142,37 +142,38 @@ void ento::createHTMLDiagnosticConsumer( // 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); + createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU); // TODO: Emit an error here. if (OutputDir.empty()) return; - C.push_back(new HTMLDiagnostics(AnalyzerOpts, OutputDir, PP, true)); + C.push_back(new HTMLDiagnostics(std::move(DiagOpts), OutputDir, PP, true)); } void ento::createHTMLSingleFileDiagnosticConsumer( - AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &OutputDir, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU) { + createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, 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); + C.push_back(new HTMLDiagnostics(std::move(DiagOpts), OutputDir, PP, false)); } void ento::createPlistHTMLDiagnosticConsumer( - AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &prefix, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU) { createHTMLDiagnosticConsumer( - AnalyzerOpts, C, std::string(llvm::sys::path::parent_path(prefix)), PP, + DiagOpts, C, std::string(llvm::sys::path::parent_path(prefix)), PP, CTU); - createPlistMultiFileDiagnosticConsumer(AnalyzerOpts, C, prefix, PP, CTU); - createTextMinimalPathDiagnosticConsumer(AnalyzerOpts, C, prefix, PP, CTU); + createPlistMultiFileDiagnosticConsumer(DiagOpts, C, prefix, PP, CTU); + createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, prefix, PP, + CTU); } //===----------------------------------------------------------------------===// @@ -245,7 +246,7 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, int FD; SmallString<128> Model, ResultPath; - if (!AnalyzerOpts.ShouldWriteStableReportFilename) { + if (!DiagOpts.ShouldWriteStableReportFilename) { llvm::sys::path::append(Model, Directory, "report-%%%%%%.html"); if (std::error_code EC = llvm::sys::fs::make_absolute(Model)) { @@ -535,7 +536,7 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, <input type="checkbox" class="spoilerhider" id="showinvocation" /> <label for="showinvocation" >Show analyzer invocation</label> <div class="spoiler">clang -cc1 )<<<"; - os << html::EscapeText(AnalyzerOpts.FullCompilerInvocation); + os << html::EscapeText(DiagOpts.ToolInvocation); os << R"<<<( </div> <div id='tooltiphint' hidden="true"> @@ -582,8 +583,8 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, os << "\n<!-- FUNCTIONNAME " << declName << " -->\n"; os << "\n<!-- ISSUEHASHCONTENTOFLINEINCONTEXT " - << GetIssueHash(SMgr, L, D.getCheckerName(), D.getBugType(), - DeclWithIssue, PP.getLangOpts()) + << getIssueHash(L, D.getCheckerName(), D.getBugType(), DeclWithIssue, + PP.getLangOpts()) << " -->\n"; os << "\n<!-- BUGLINE " @@ -786,8 +787,8 @@ void HTMLDiagnostics::HandlePiece(Rewriter &R, FileID BugFileID, if (LPosInfo.first != BugFileID) return; - const llvm::MemoryBuffer *Buf = SM.getBuffer(LPosInfo.first); - const char* FileStart = Buf->getBufferStart(); + llvm::MemoryBufferRef Buf = SM.getBufferOrFake(LPosInfo.first); + const char *FileStart = Buf.getBufferStart(); // Compute the column number. Rewind from the current position to the start // of the line. @@ -797,7 +798,7 @@ void HTMLDiagnostics::HandlePiece(Rewriter &R, FileID BugFileID, // Compute LineEnd. const char *LineEnd = TokInstantiationPtr; - const char* FileEnd = Buf->getBufferEnd(); + const char *FileEnd = Buf.getBufferEnd(); while (*LineEnd != '\n' && LineEnd != FileEnd) ++LineEnd; diff --git a/clang/lib/StaticAnalyzer/Core/IssueHash.cpp b/clang/lib/StaticAnalyzer/Core/IssueHash.cpp deleted file mode 100644 index e7497f3fbdaa..000000000000 --- a/clang/lib/StaticAnalyzer/Core/IssueHash.cpp +++ /dev/null @@ -1,204 +0,0 @@ -//===---------- IssueHash.cpp - Generate identification hashes --*- 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/IssueHash.h" -#include "clang/AST/ASTContext.h" -#include "clang/AST/Decl.h" -#include "clang/AST/DeclCXX.h" -#include "clang/Basic/SourceManager.h" -#include "clang/Basic/Specifiers.h" -#include "clang/Lex/Lexer.h" -#include "llvm/ADT/StringExtras.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/ADT/Twine.h" -#include "llvm/Support/LineIterator.h" -#include "llvm/Support/MD5.h" -#include "llvm/Support/Path.h" - -#include <functional> -#include <sstream> -#include <string> - -using namespace clang; - -// Get a string representation of the parts of the signature that can be -// overloaded on. -static std::string GetSignature(const FunctionDecl *Target) { - if (!Target) - return ""; - std::string Signature; - - // When a flow sensitive bug happens in templated code we should not generate - // distinct hash value for every instantiation. Use the signature from the - // primary template. - if (const FunctionDecl *InstantiatedFrom = - Target->getTemplateInstantiationPattern()) - Target = InstantiatedFrom; - - if (!isa<CXXConstructorDecl>(Target) && !isa<CXXDestructorDecl>(Target) && - !isa<CXXConversionDecl>(Target)) - Signature.append(Target->getReturnType().getAsString()).append(" "); - Signature.append(Target->getQualifiedNameAsString()).append("("); - - for (int i = 0, paramsCount = Target->getNumParams(); i < paramsCount; ++i) { - if (i) - Signature.append(", "); - Signature.append(Target->getParamDecl(i)->getType().getAsString()); - } - - if (Target->isVariadic()) - Signature.append(", ..."); - Signature.append(")"); - - const auto *TargetT = - llvm::dyn_cast_or_null<FunctionType>(Target->getType().getTypePtr()); - - if (!TargetT || !isa<CXXMethodDecl>(Target)) - return Signature; - - if (TargetT->isConst()) - Signature.append(" const"); - if (TargetT->isVolatile()) - Signature.append(" volatile"); - if (TargetT->isRestrict()) - Signature.append(" restrict"); - - if (const auto *TargetPT = - dyn_cast_or_null<FunctionProtoType>(Target->getType().getTypePtr())) { - switch (TargetPT->getRefQualifier()) { - case RQ_LValue: - Signature.append(" &"); - break; - case RQ_RValue: - Signature.append(" &&"); - break; - default: - break; - } - } - - return Signature; -} - -static std::string GetEnclosingDeclContextSignature(const Decl *D) { - if (!D) - return ""; - - if (const auto *ND = dyn_cast<NamedDecl>(D)) { - std::string DeclName; - - switch (ND->getKind()) { - case Decl::Namespace: - case Decl::Record: - case Decl::CXXRecord: - case Decl::Enum: - DeclName = ND->getQualifiedNameAsString(); - break; - case Decl::CXXConstructor: - case Decl::CXXDestructor: - case Decl::CXXConversion: - case Decl::CXXMethod: - case Decl::Function: - DeclName = GetSignature(dyn_cast_or_null<FunctionDecl>(ND)); - break; - case Decl::ObjCMethod: - // ObjC Methods can not be overloaded, qualified name uniquely identifies - // the method. - DeclName = ND->getQualifiedNameAsString(); - break; - default: - break; - } - - return DeclName; - } - - return ""; -} - -static StringRef GetNthLineOfFile(const llvm::MemoryBuffer *Buffer, int Line) { - if (!Buffer) - return ""; - - llvm::line_iterator LI(*Buffer, false); - for (; !LI.is_at_eof() && LI.line_number() != Line; ++LI) - ; - - return *LI; -} - -static std::string NormalizeLine(const SourceManager &SM, FullSourceLoc &L, - const LangOptions &LangOpts) { - static StringRef Whitespaces = " \t\n"; - - StringRef Str = GetNthLineOfFile(SM.getBuffer(L.getFileID(), L), - L.getExpansionLineNumber()); - StringRef::size_type col = Str.find_first_not_of(Whitespaces); - if (col == StringRef::npos) - col = 1; // The line only contains whitespace. - else - col++; - SourceLocation StartOfLine = - SM.translateLineCol(SM.getFileID(L), L.getExpansionLineNumber(), col); - const llvm::MemoryBuffer *Buffer = - SM.getBuffer(SM.getFileID(StartOfLine), StartOfLine); - if (!Buffer) - return {}; - - const char *BufferPos = SM.getCharacterData(StartOfLine); - - Token Token; - Lexer Lexer(SM.getLocForStartOfFile(SM.getFileID(StartOfLine)), LangOpts, - Buffer->getBufferStart(), BufferPos, Buffer->getBufferEnd()); - - size_t NextStart = 0; - std::ostringstream LineBuff; - while (!Lexer.LexFromRawLexer(Token) && NextStart < 2) { - if (Token.isAtStartOfLine() && NextStart++ > 0) - continue; - LineBuff << std::string(SM.getCharacterData(Token.getLocation()), - Token.getLength()); - } - - return LineBuff.str(); -} - -static llvm::SmallString<32> GetHashOfContent(StringRef Content) { - llvm::MD5 Hash; - llvm::MD5::MD5Result MD5Res; - SmallString<32> Res; - - Hash.update(Content); - Hash.final(MD5Res); - llvm::MD5::stringifyResult(MD5Res, Res); - - return Res; -} - -std::string clang::GetIssueString(const SourceManager &SM, - FullSourceLoc &IssueLoc, - StringRef CheckerName, StringRef BugType, - const Decl *D, - const LangOptions &LangOpts) { - static StringRef Delimiter = "$"; - - return (llvm::Twine(CheckerName) + Delimiter + - GetEnclosingDeclContextSignature(D) + Delimiter + - Twine(IssueLoc.getExpansionColumnNumber()) + Delimiter + - NormalizeLine(SM, IssueLoc, LangOpts) + Delimiter + BugType) - .str(); -} - -SmallString<32> clang::GetIssueHash(const SourceManager &SM, - FullSourceLoc &IssueLoc, - StringRef CheckerName, StringRef BugType, - const Decl *D, - const LangOptions &LangOpts) { - - return GetHashOfContent( - GetIssueString(SM, IssueLoc, CheckerName, BugType, D, LangOpts)); -} diff --git a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp index ed62778623a8..35e320c7755f 100644 --- a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/Analysis/IssueHash.h" #include "clang/Analysis/PathDiagnostic.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/PlistSupport.h" @@ -20,13 +21,12 @@ #include "clang/Lex/Preprocessor.h" #include "clang/Lex/TokenConcatenation.h" #include "clang/Rewrite/Core/HTMLRewrite.h" -#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" -#include "clang/StaticAnalyzer/Core/IssueHash.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Statistic.h" #include "llvm/Support/Casting.h" +#include <memory> using namespace clang; using namespace ento; @@ -39,13 +39,17 @@ using namespace markup; namespace { class PlistDiagnostics : public PathDiagnosticConsumer { + PathDiagnosticConsumerOptions DiagOpts; const std::string OutputFile; const Preprocessor &PP; const cross_tu::CrossTranslationUnitContext &CTU; - AnalyzerOptions &AnOpts; const bool SupportsCrossFileDiagnostics; + + void printBugPath(llvm::raw_ostream &o, const FIDMap &FM, + const PathPieces &Path); + public: - PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, + PlistDiagnostics(PathDiagnosticConsumerOptions DiagOpts, const std::string &OutputFile, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU, bool supportsMultipleFiles); @@ -74,23 +78,19 @@ namespace { /// A helper class for emitting a single report. class PlistPrinter { const FIDMap& FM; - AnalyzerOptions &AnOpts; const Preprocessor &PP; const cross_tu::CrossTranslationUnitContext &CTU; llvm::SmallVector<const PathDiagnosticMacroPiece *, 0> MacroPieces; public: - PlistPrinter(const FIDMap& FM, AnalyzerOptions &AnOpts, + PlistPrinter(const FIDMap& FM, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU) - : FM(FM), AnOpts(AnOpts), PP(PP), CTU(CTU) { + : FM(FM), PP(PP), CTU(CTU) { } void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P) { ReportPiece(o, P, /*indent*/ 4, /*depth*/ 0, /*includeControlFlow*/ true); - - // Don't emit a warning about an unused private field. - (void)AnOpts; } /// Print the expansions of the collected macro pieces. @@ -165,11 +165,6 @@ struct ExpansionInfo { } // end of anonymous namespace -static void printBugPath(llvm::raw_ostream &o, const FIDMap& FM, - AnalyzerOptions &AnOpts, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU, - const PathPieces &Path); - /// Print coverage information to output stream {@code o}. /// May modify the used list of files {@code Fids} by inserting new ones. static void printCoverage(const PathDiagnostic *D, @@ -520,11 +515,53 @@ static void printCoverage(const PathDiagnostic *D, assert(IndentLevel == InputIndentLevel); } -static void printBugPath(llvm::raw_ostream &o, const FIDMap& FM, - AnalyzerOptions &AnOpts, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU, - const PathPieces &Path) { - PlistPrinter Printer(FM, AnOpts, PP, CTU); +//===----------------------------------------------------------------------===// +// Methods of PlistDiagnostics. +//===----------------------------------------------------------------------===// + +PlistDiagnostics::PlistDiagnostics( + PathDiagnosticConsumerOptions DiagOpts, const std::string &output, + const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU, + bool supportsMultipleFiles) + : DiagOpts(std::move(DiagOpts)), OutputFile(output), PP(PP), CTU(CTU), + SupportsCrossFileDiagnostics(supportsMultipleFiles) { + // FIXME: Will be used by a later planned change. + (void)this->CTU; +} + +void ento::createPlistDiagnosticConsumer( + PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, + const std::string &OutputFile, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { + + // TODO: Emit an error here. + if (OutputFile.empty()) + return; + + C.push_back(new PlistDiagnostics(DiagOpts, OutputFile, PP, CTU, + /*supportsMultipleFiles=*/false)); + createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, OutputFile, + PP, CTU); +} + +void ento::createPlistMultiFileDiagnosticConsumer( + PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, + const std::string &OutputFile, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { + + // TODO: Emit an error here. + if (OutputFile.empty()) + return; + + C.push_back(new PlistDiagnostics(DiagOpts, OutputFile, PP, CTU, + /*supportsMultipleFiles=*/true)); + createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, OutputFile, + PP, CTU); +} + +void PlistDiagnostics::printBugPath(llvm::raw_ostream &o, const FIDMap &FM, + const PathPieces &Path) { + PlistPrinter Printer(FM, PP, CTU); assert(std::is_partitioned(Path.begin(), Path.end(), [](const PathDiagnosticPieceRef &E) { return E->getKind() == PathDiagnosticPiece::Note; @@ -557,7 +594,7 @@ static void printBugPath(llvm::raw_ostream &o, const FIDMap& FM, o << " </array>\n"; - if (!AnOpts.ShouldDisplayMacroExpansions) + if (!DiagOpts.ShouldDisplayMacroExpansions) return; o << " <key>macro_expansions</key>\n" @@ -566,48 +603,6 @@ static void printBugPath(llvm::raw_ostream &o, const FIDMap& FM, o << " </array>\n"; } -//===----------------------------------------------------------------------===// -// Methods of PlistDiagnostics. -//===----------------------------------------------------------------------===// - -PlistDiagnostics::PlistDiagnostics( - AnalyzerOptions &AnalyzerOpts, const std::string &output, - const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU, - bool supportsMultipleFiles) - : OutputFile(output), PP(PP), CTU(CTU), AnOpts(AnalyzerOpts), - SupportsCrossFileDiagnostics(supportsMultipleFiles) { - // FIXME: Will be used by a later planned change. - (void)this->CTU; -} - -void ento::createPlistDiagnosticConsumer( - AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, - const std::string &OutputFile, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &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 &OutputFile, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &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) { @@ -682,7 +677,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl( o << " <dict>\n"; const PathDiagnostic *D = *DI; - printBugPath(o, FM, AnOpts, PP, CTU, D->path); + printBugPath(o, FM, D->path); // Output the bug type and bug category. o << " <key>description</key>"; @@ -702,7 +697,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl( : D->getLocation().asLocation()), SM); const Decl *DeclWithIssue = D->getDeclWithIssue(); - EmitString(o, GetIssueHash(SM, L, D->getCheckerName(), D->getBugType(), + EmitString(o, getIssueHash(L, D->getCheckerName(), D->getBugType(), DeclWithIssue, LangOpts)) << '\n'; @@ -806,7 +801,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl( EmitString(o << " ", SM.getFileEntryForID(FID)->getName()) << '\n'; o << " </array>\n"; - if (llvm::AreStatisticsEnabled() && AnOpts.ShouldSerializeStats) { + if (llvm::AreStatisticsEnabled() && DiagOpts.ShouldSerializeStats) { o << " <key>statistics</key>\n"; std::string stats; llvm::raw_string_ostream os(stats); @@ -825,22 +820,36 @@ void PlistDiagnostics::FlushDiagnosticsImpl( namespace { -using ExpArgTokens = llvm::SmallVector<Token, 2>; +using ArgTokensTy = llvm::SmallVector<Token, 2>; + +} // end of anonymous namespace + +LLVM_DUMP_METHOD static void dumpArgTokensToStream(llvm::raw_ostream &Out, + const Preprocessor &PP, + const ArgTokensTy &Toks); -/// Maps unexpanded macro arguments to expanded arguments. A macro argument may +namespace { +/// Maps unexpanded macro parameters to expanded arguments. A macro argument may /// need to expanded further when it is nested inside another macro. -class MacroArgMap : public std::map<const IdentifierInfo *, ExpArgTokens> { +class MacroParamMap : public std::map<const IdentifierInfo *, ArgTokensTy> { public: - void expandFromPrevMacro(const MacroArgMap &Super); + void expandFromPrevMacro(const MacroParamMap &Super); + + LLVM_DUMP_METHOD void dump(const Preprocessor &PP) const { + dumpToStream(llvm::errs(), PP); + } + + LLVM_DUMP_METHOD void dumpToStream(llvm::raw_ostream &Out, + const Preprocessor &PP) const; }; -struct MacroNameAndArgs { +struct MacroExpansionInfo { std::string Name; const MacroInfo *MI = nullptr; - MacroArgMap Args; + MacroParamMap ParamMap; - MacroNameAndArgs(std::string N, const MacroInfo *MI, MacroArgMap M) - : Name(std::move(N)), MI(MI), Args(std::move(M)) {} + MacroExpansionInfo(std::string N, const MacroInfo *MI, MacroParamMap M) + : Name(std::move(N)), MI(MI), ParamMap(std::move(M)) {} }; class TokenPrinter { @@ -860,6 +869,46 @@ public: void printToken(const Token &Tok); }; +/// Wrapper around a Lexer object that can lex tokens one-by-one. Its possible +/// to "inject" a range of tokens into the stream, in which case the next token +/// is retrieved from the next element of the range, until the end of the range +/// is reached. +class TokenStream { +public: + TokenStream(SourceLocation ExpanLoc, const SourceManager &SM, + const LangOptions &LangOpts) + : ExpanLoc(ExpanLoc) { + FileID File; + unsigned Offset; + std::tie(File, Offset) = SM.getDecomposedLoc(ExpanLoc); + llvm::MemoryBufferRef MB = SM.getBufferOrFake(File); + const char *MacroNameTokenPos = MB.getBufferStart() + Offset; + + RawLexer = std::make_unique<Lexer>(SM.getLocForStartOfFile(File), LangOpts, + MB.getBufferStart(), MacroNameTokenPos, + MB.getBufferEnd()); + } + + void next(Token &Result) { + if (CurrTokenIt == TokenRange.end()) { + RawLexer->LexFromRawLexer(Result); + return; + } + Result = *CurrTokenIt; + CurrTokenIt++; + } + + void injectRange(const ArgTokensTy &Range) { + TokenRange = Range; + CurrTokenIt = TokenRange.begin(); + } + + std::unique_ptr<Lexer> RawLexer; + ArgTokensTy TokenRange; + ArgTokensTy::iterator CurrTokenIt = TokenRange.begin(); + SourceLocation ExpanLoc; +}; + } // end of anonymous namespace /// The implementation method of getMacroExpansion: It prints the expansion of @@ -878,7 +927,7 @@ public: /// /// As we expand the last line, we'll immediately replace PRINT(str) with /// print(x). The information that both 'str' and 'x' refers to the same string -/// is an information we have to forward, hence the argument \p PrevArgs. +/// is an information we have to forward, hence the argument \p PrevParamMap. /// /// To avoid infinite recursion we maintain the already processed tokens in /// a set. This is carried as a parameter through the recursive calls. The set @@ -888,13 +937,11 @@ public: /// #define f(y) x /// #define x f(x) static std::string getMacroNameAndPrintExpansion( - TokenPrinter &Printer, - SourceLocation MacroLoc, - const Preprocessor &PP, - const MacroArgMap &PrevArgs, + TokenPrinter &Printer, SourceLocation MacroLoc, const Preprocessor &PP, + const MacroParamMap &PrevParamMap, llvm::SmallPtrSet<IdentifierInfo *, 8> &AlreadyProcessedTokens); -/// Retrieves the name of the macro and what it's arguments expand into +/// Retrieves the name of the macro and what it's parameters expand into /// at \p ExpanLoc. /// /// For example, for the following macro expansion: @@ -916,8 +963,9 @@ static std::string getMacroNameAndPrintExpansion( /// When \p ExpanLoc references "SET_TO_NULL(a)" within the definition of /// "NOT_SUSPICOUS", the macro name "SET_TO_NULL" and the MacroArgMap map /// { (x, a) } will be returned. -static MacroNameAndArgs getMacroNameAndArgs(SourceLocation ExpanLoc, - const Preprocessor &PP); +static MacroExpansionInfo +getMacroExpansionInfo(const MacroParamMap &PrevParamMap, + SourceLocation ExpanLoc, const Preprocessor &PP); /// Retrieves the ')' token that matches '(' \p It points to. static MacroInfo::tokens_iterator getMatchingRParen( @@ -951,21 +999,20 @@ getExpandedMacro(SourceLocation MacroLoc, const Preprocessor &PP, llvm::SmallPtrSet<IdentifierInfo*, 8> AlreadyProcessedTokens; std::string MacroName = getMacroNameAndPrintExpansion( - Printer, MacroLoc, *PPToUse, MacroArgMap{}, AlreadyProcessedTokens); + Printer, MacroLoc, *PPToUse, MacroParamMap{}, AlreadyProcessedTokens); return {MacroName, std::string(OS.str())}; } static std::string getMacroNameAndPrintExpansion( - TokenPrinter &Printer, - SourceLocation MacroLoc, - const Preprocessor &PP, - const MacroArgMap &PrevArgs, + TokenPrinter &Printer, SourceLocation MacroLoc, const Preprocessor &PP, + const MacroParamMap &PrevParamMap, llvm::SmallPtrSet<IdentifierInfo *, 8> &AlreadyProcessedTokens) { const SourceManager &SM = PP.getSourceManager(); - MacroNameAndArgs Info = getMacroNameAndArgs(SM.getExpansionLoc(MacroLoc), PP); - IdentifierInfo* IDInfo = PP.getIdentifierInfo(Info.Name); + MacroExpansionInfo MExpInfo = + getMacroExpansionInfo(PrevParamMap, SM.getExpansionLoc(MacroLoc), PP); + IdentifierInfo *MacroNameII = PP.getIdentifierInfo(MExpInfo.Name); // TODO: If the macro definition contains another symbol then this function is // called recursively. In case this symbol is the one being defined, it will @@ -973,18 +1020,18 @@ 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.insert(IDInfo).second) - return Info.Name; + if (!AlreadyProcessedTokens.insert(MacroNameII).second) + return MExpInfo.Name; - if (!Info.MI) - return Info.Name; + if (!MExpInfo.MI) + return MExpInfo.Name; // Manually expand its arguments from the previous macro. - Info.Args.expandFromPrevMacro(PrevArgs); + MExpInfo.ParamMap.expandFromPrevMacro(PrevParamMap); // Iterate over the macro's tokens and stringify them. - for (auto It = Info.MI->tokens_begin(), E = Info.MI->tokens_end(); It != E; - ++It) { + for (auto It = MExpInfo.MI->tokens_begin(), E = MExpInfo.MI->tokens_end(); + It != E; ++It) { Token T = *It; // If this token is not an identifier, we only need to print it. @@ -1000,8 +1047,8 @@ static std::string getMacroNameAndPrintExpansion( // If this token is a macro that should be expanded inside the current // macro. if (getMacroInfoForLocation(PP, SM, II, T.getLocation())) { - getMacroNameAndPrintExpansion(Printer, T.getLocation(), PP, Info.Args, - AlreadyProcessedTokens); + getMacroNameAndPrintExpansion(Printer, T.getLocation(), PP, + MExpInfo.ParamMap, AlreadyProcessedTokens); // If this is a function-like macro, skip its arguments, as // getExpandedMacro() already printed them. If this is the case, let's @@ -1013,10 +1060,10 @@ static std::string getMacroNameAndPrintExpansion( } // If this token is the current macro's argument, we should expand it. - auto ArgMapIt = Info.Args.find(II); - if (ArgMapIt != Info.Args.end()) { - for (MacroInfo::tokens_iterator ArgIt = ArgMapIt->second.begin(), - ArgEnd = ArgMapIt->second.end(); + auto ParamToArgIt = MExpInfo.ParamMap.find(II); + if (ParamToArgIt != MExpInfo.ParamMap.end()) { + for (MacroInfo::tokens_iterator ArgIt = ParamToArgIt->second.begin(), + ArgEnd = ParamToArgIt->second.end(); ArgIt != ArgEnd; ++ArgIt) { // These tokens may still be macros, if that is the case, handle it the @@ -1034,7 +1081,8 @@ static std::string getMacroNameAndPrintExpansion( } getMacroNameAndPrintExpansion(Printer, ArgIt->getLocation(), PP, - Info.Args, AlreadyProcessedTokens); + MExpInfo.ParamMap, + AlreadyProcessedTokens); // Peek the next token if it is a tok::l_paren. This way we can decide // if this is the application or just a reference to a function maxro // symbol: @@ -1055,34 +1103,30 @@ static std::string getMacroNameAndPrintExpansion( Printer.printToken(T); } - AlreadyProcessedTokens.erase(IDInfo); + AlreadyProcessedTokens.erase(MacroNameII); - return Info.Name; + return MExpInfo.Name; } -static MacroNameAndArgs getMacroNameAndArgs(SourceLocation ExpanLoc, - const Preprocessor &PP) { +static MacroExpansionInfo +getMacroExpansionInfo(const MacroParamMap &PrevParamMap, + SourceLocation ExpanLoc, const Preprocessor &PP) { const SourceManager &SM = PP.getSourceManager(); const LangOptions &LangOpts = PP.getLangOpts(); // First, we create a Lexer to lex *at the expansion location* the tokens // referring to the macro's name and its arguments. - std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(ExpanLoc); - const llvm::MemoryBuffer *MB = SM.getBuffer(LocInfo.first); - const char *MacroNameTokenPos = MB->getBufferStart() + LocInfo.second; - - Lexer RawLexer(SM.getLocForStartOfFile(LocInfo.first), LangOpts, - MB->getBufferStart(), MacroNameTokenPos, MB->getBufferEnd()); + TokenStream TStream(ExpanLoc, SM, LangOpts); // Acquire the macro's name. Token TheTok; - RawLexer.LexFromRawLexer(TheTok); + TStream.next(TheTok); std::string MacroName = PP.getSpelling(TheTok); const auto *II = PP.getIdentifierInfo(MacroName); - assert(II && "Failed to acquire the IndetifierInfo for the macro!"); + assert(II && "Failed to acquire the IdentifierInfo for the macro!"); const MacroInfo *MI = getMacroInfoForLocation(PP, SM, II, ExpanLoc); // assert(MI && "The macro must've been defined at it's expansion location!"); @@ -1094,18 +1138,18 @@ static MacroNameAndArgs getMacroNameAndArgs(SourceLocation ExpanLoc, if (!MI) return { MacroName, MI, {} }; - // Acquire the macro's arguments. + // Acquire the macro's arguments at the expansion point. // // The rough idea here is to lex from the first left parentheses to the last - // right parentheses, and map the macro's unexpanded arguments to what they - // will be expanded to. An expanded macro argument may contain several tokens - // (like '3 + 4'), so we'll lex until we find a tok::comma or tok::r_paren, at - // which point we start lexing the next argument or finish. - ArrayRef<const IdentifierInfo *> MacroArgs = MI->params(); - if (MacroArgs.empty()) + // right parentheses, and map the macro's parameter to what they will be + // expanded to. A macro argument may contain several token (like '3 + 4'), so + // we'll lex until we find a tok::comma or tok::r_paren, at which point we + // start lexing the next argument or finish. + ArrayRef<const IdentifierInfo *> MacroParams = MI->params(); + if (MacroParams.empty()) return { MacroName, MI, {} }; - RawLexer.LexFromRawLexer(TheTok); + TStream.next(TheTok); // When this is a token which expands to another macro function then its // parentheses are not at its expansion locaiton. For example: // @@ -1117,9 +1161,9 @@ static MacroNameAndArgs getMacroNameAndArgs(SourceLocation ExpanLoc, if (TheTok.isNot(tok::l_paren)) return { MacroName, MI, {} }; - MacroArgMap Args; + MacroParamMap ParamMap; - // When the macro's argument is a function call, like + // When the argument is a function call, like // CALL_FN(someFunctionName(param1, param2)) // we will find tok::l_paren, tok::r_paren, and tok::comma that do not divide // actual macro arguments, or do not represent the macro argument's closing @@ -1130,12 +1174,19 @@ static MacroNameAndArgs getMacroNameAndArgs(SourceLocation ExpanLoc, // * > 1, then tok::comma is a part of the current arg. int ParenthesesDepth = 1; - // If we encounter __VA_ARGS__, we will lex until the closing tok::r_paren, - // even if we lex a tok::comma and ParanthesesDepth == 1. - const IdentifierInfo *__VA_ARGS__II = PP.getIdentifierInfo("__VA_ARGS__"); + // If we encounter the variadic arg, we will lex until the closing + // tok::r_paren, even if we lex a tok::comma and ParanthesesDepth == 1. + const IdentifierInfo *VariadicParamII = PP.getIdentifierInfo("__VA_ARGS__"); + if (MI->isGNUVarargs()) { + // If macro uses GNU-style variadic args, the param name is user-supplied, + // an not "__VA_ARGS__". E.g.: + // #define FOO(a, b, myvargs...) + // In this case, just use the last parameter: + VariadicParamII = *(MacroParams.rbegin()); + } - for (const IdentifierInfo *UnexpArgII : MacroArgs) { - MacroArgMap::mapped_type ExpandedArgTokens; + for (const IdentifierInfo *CurrParamII : MacroParams) { + MacroParamMap::mapped_type ArgTokens; // One could also simply not supply a single argument to __VA_ARGS__ -- this // results in a preprocessor warning, but is not an error: @@ -1149,10 +1200,10 @@ static MacroNameAndArgs getMacroNameAndArgs(SourceLocation ExpanLoc, if (ParenthesesDepth != 0) { // Lex the first token of the next macro parameter. - RawLexer.LexFromRawLexer(TheTok); + TStream.next(TheTok); - while (!(ParenthesesDepth == 1 && - (UnexpArgII == __VA_ARGS__II ? false : TheTok.is(tok::comma)))) { + while (CurrParamII == VariadicParamII || ParenthesesDepth != 1 || + !TheTok.is(tok::comma)) { assert(TheTok.isNot(tok::eof) && "EOF encountered while looking for expanded macro args!"); @@ -1165,24 +1216,51 @@ static MacroNameAndArgs getMacroNameAndArgs(SourceLocation ExpanLoc, if (ParenthesesDepth == 0) break; - if (TheTok.is(tok::raw_identifier)) + if (TheTok.is(tok::raw_identifier)) { PP.LookUpIdentifierInfo(TheTok); + // This token is a variadic parameter: + // + // #define PARAMS_RESOLVE_TO_VA_ARGS(i, fmt) foo(i, fmt); \ + // i = 0; + // #define DISPATCH(...) \ + // PARAMS_RESOLVE_TO_VA_ARGS(__VA_ARGS__); + // // ^~~~~~~~~~~ Variadic parameter here + // + // void multipleParamsResolveToVA_ARGS(void) { + // int x = 1; + // DISPATCH(x, "LF1M healer"); // Multiple arguments are mapped to + // // a single __VA_ARGS__ parameter. + // (void)(10 / x); + // } + // + // We will stumble across this while trying to expand + // PARAMS_RESOLVE_TO_VA_ARGS. By this point, we already noted during + // the processing of DISPATCH what __VA_ARGS__ maps to, so we'll + // retrieve the next series of tokens from that. + if (TheTok.getIdentifierInfo() == VariadicParamII) { + TStream.injectRange(PrevParamMap.at(VariadicParamII)); + TStream.next(TheTok); + continue; + } + } - ExpandedArgTokens.push_back(TheTok); - RawLexer.LexFromRawLexer(TheTok); + ArgTokens.push_back(TheTok); + TStream.next(TheTok); } } else { - assert(UnexpArgII == __VA_ARGS__II); + assert(CurrParamII == VariadicParamII && + "No more macro arguments are found, but the current parameter " + "isn't the variadic arg!"); } - Args.emplace(UnexpArgII, std::move(ExpandedArgTokens)); + ParamMap.emplace(CurrParamII, std::move(ArgTokens)); } assert(TheTok.is(tok::r_paren) && "Expanded macro argument acquisition failed! After the end of the loop" " this token should be ')'!"); - return { MacroName, MI, Args }; + return {MacroName, MI, ParamMap}; } static MacroInfo::tokens_iterator getMatchingRParen( @@ -1222,14 +1300,14 @@ static const MacroInfo *getMacroInfoForLocation(const Preprocessor &PP, return MD->findDirectiveAtLoc(Loc, SM).getMacroInfo(); } -void MacroArgMap::expandFromPrevMacro(const MacroArgMap &Super) { +void MacroParamMap::expandFromPrevMacro(const MacroParamMap &Super) { for (value_type &Pair : *this) { - ExpArgTokens &CurrExpArgTokens = Pair.second; + ArgTokensTy &CurrArgTokens = Pair.second; // For each token in the expanded macro argument. - auto It = CurrExpArgTokens.begin(); - while (It != CurrExpArgTokens.end()) { + auto It = CurrArgTokens.begin(); + while (It != CurrArgTokens.end()) { if (It->isNot(tok::identifier)) { ++It; continue; @@ -1244,17 +1322,43 @@ void MacroArgMap::expandFromPrevMacro(const MacroArgMap &Super) { continue; } - const ExpArgTokens &SuperExpArgTokens = Super.at(II); + const ArgTokensTy &SuperArgTokens = Super.at(II); - It = CurrExpArgTokens.insert( - It, SuperExpArgTokens.begin(), SuperExpArgTokens.end()); - std::advance(It, SuperExpArgTokens.size()); - It = CurrExpArgTokens.erase(It); + It = CurrArgTokens.insert(It, SuperArgTokens.begin(), + SuperArgTokens.end()); + std::advance(It, SuperArgTokens.size()); + It = CurrArgTokens.erase(It); } } } +void MacroParamMap::dumpToStream(llvm::raw_ostream &Out, + const Preprocessor &PP) const { + for (const std::pair<const IdentifierInfo *, ArgTokensTy> Pair : *this) { + Out << Pair.first->getName() << " -> "; + dumpArgTokensToStream(Out, PP, Pair.second); + Out << '\n'; + } +} + +static void dumpArgTokensToStream(llvm::raw_ostream &Out, + const Preprocessor &PP, + const ArgTokensTy &Toks) { + TokenPrinter Printer(Out, PP); + for (Token Tok : Toks) + Printer.printToken(Tok); +} + void TokenPrinter::printToken(const Token &Tok) { + // TODO: Handle GNU extensions where hash and hashhash occurs right before + // __VA_ARGS__. + // cppreference.com: "some compilers offer an extension that allows ## to + // appear after a comma and before __VA_ARGS__, in which case the ## does + // nothing when the variable arguments are present, but removes the comma when + // the variable arguments are not present: this makes it possible to define + // macros such as fprintf (stderr, format, ##__VA_ARGS__)" + // FIXME: Handle named variadic macro parameters (also a GNU extension). + // If this is the first token to be printed, don't print space. if (PrevTok.isNot(tok::unknown)) { // If the tokens were already space separated, or if they must be to avoid diff --git a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp index 006a4006b7fc..1ccb0de92fba 100644 --- a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp +++ b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp @@ -582,9 +582,6 @@ bool ScanReachableSymbols::scan(SVal val) { if (SymbolRef Sym = val.getAsSymbol()) return scan(Sym); - if (const SymExpr *Sym = val.getAsSymbolicExpression()) - return scan(Sym); - if (Optional<nonloc::CompoundVal> X = val.getAs<nonloc::CompoundVal>()) return scan(*X); diff --git a/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp b/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp index cb6f61e86ae3..a481bde1651b 100644 --- a/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp @@ -89,7 +89,7 @@ public: } TriStateKind getCmpOpState(BinaryOperatorKind CurrentOP, - BinaryOperatorKind QueriedOP) const { + BinaryOperatorKind QueriedOP) const { return CmpOpTable[getIndexFromOp(CurrentOP)][getIndexFromOp(QueriedOP)]; } @@ -364,6 +364,18 @@ RangeSet RangeSet::Negate(BasicValueFactory &BV, Factory &F) const { return newRanges; } +RangeSet RangeSet::Delete(BasicValueFactory &BV, Factory &F, + const llvm::APSInt &Point) const { + llvm::APSInt Upper = Point; + llvm::APSInt Lower = Point; + + ++Upper; + --Lower; + + // Notice that the lower bound is greater than the upper bound. + return Intersect(BV, F, Upper, Lower); +} + void RangeSet::print(raw_ostream &os) const { bool isFirst = true; os << "{ "; @@ -379,7 +391,315 @@ void RangeSet::print(raw_ostream &os) const { os << " }"; } +REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(SymbolSet, SymbolRef) + namespace { +class EquivalenceClass; +} // end anonymous namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(ClassMap, SymbolRef, EquivalenceClass) +REGISTER_MAP_WITH_PROGRAMSTATE(ClassMembers, EquivalenceClass, SymbolSet) +REGISTER_MAP_WITH_PROGRAMSTATE(ConstraintRange, EquivalenceClass, RangeSet) + +REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(ClassSet, EquivalenceClass) +REGISTER_MAP_WITH_PROGRAMSTATE(DisequalityMap, EquivalenceClass, ClassSet) + +namespace { +/// This class encapsulates a set of symbols equal to each other. +/// +/// The main idea of the approach requiring such classes is in narrowing +/// and sharing constraints between symbols within the class. Also we can +/// conclude that there is no practical need in storing constraints for +/// every member of the class separately. +/// +/// Main terminology: +/// +/// * "Equivalence class" is an object of this class, which can be efficiently +/// compared to other classes. It represents the whole class without +/// storing the actual in it. The members of the class however can be +/// retrieved from the state. +/// +/// * "Class members" are the symbols corresponding to the class. This means +/// that A == B for every member symbols A and B from the class. Members of +/// each class are stored in the state. +/// +/// * "Trivial class" is a class that has and ever had only one same symbol. +/// +/// * "Merge operation" merges two classes into one. It is the main operation +/// to produce non-trivial classes. +/// If, at some point, we can assume that two symbols from two distinct +/// classes are equal, we can merge these classes. +class EquivalenceClass : public llvm::FoldingSetNode { +public: + /// Find equivalence class for the given symbol in the given state. + LLVM_NODISCARD static inline EquivalenceClass find(ProgramStateRef State, + SymbolRef Sym); + + /// Merge classes for the given symbols and return a new state. + LLVM_NODISCARD static inline ProgramStateRef + merge(BasicValueFactory &BV, RangeSet::Factory &F, ProgramStateRef State, + SymbolRef First, SymbolRef Second); + // Merge this class with the given class and return a new state. + LLVM_NODISCARD inline ProgramStateRef merge(BasicValueFactory &BV, + RangeSet::Factory &F, + ProgramStateRef State, + EquivalenceClass Other); + + /// Return a set of class members for the given state. + LLVM_NODISCARD inline SymbolSet getClassMembers(ProgramStateRef State); + /// Return true if the current class is trivial in the given state. + LLVM_NODISCARD inline bool isTrivial(ProgramStateRef State); + /// Return true if the current class is trivial and its only member is dead. + LLVM_NODISCARD inline bool isTriviallyDead(ProgramStateRef State, + SymbolReaper &Reaper); + + LLVM_NODISCARD static inline ProgramStateRef + markDisequal(BasicValueFactory &BV, RangeSet::Factory &F, + ProgramStateRef State, SymbolRef First, SymbolRef Second); + LLVM_NODISCARD static inline ProgramStateRef + markDisequal(BasicValueFactory &BV, RangeSet::Factory &F, + ProgramStateRef State, EquivalenceClass First, + EquivalenceClass Second); + LLVM_NODISCARD inline ProgramStateRef + markDisequal(BasicValueFactory &BV, RangeSet::Factory &F, + ProgramStateRef State, EquivalenceClass Other) const; + LLVM_NODISCARD static inline ClassSet + getDisequalClasses(ProgramStateRef State, SymbolRef Sym); + LLVM_NODISCARD inline ClassSet + getDisequalClasses(ProgramStateRef State) const; + LLVM_NODISCARD inline ClassSet + getDisequalClasses(DisequalityMapTy Map, ClassSet::Factory &Factory) const; + + LLVM_NODISCARD static inline Optional<bool> + areEqual(ProgramStateRef State, SymbolRef First, SymbolRef Second); + + /// Check equivalence data for consistency. + LLVM_NODISCARD LLVM_ATTRIBUTE_UNUSED static bool + isClassDataConsistent(ProgramStateRef State); + + LLVM_NODISCARD QualType getType() const { + return getRepresentativeSymbol()->getType(); + } + + EquivalenceClass() = delete; + EquivalenceClass(const EquivalenceClass &) = default; + EquivalenceClass &operator=(const EquivalenceClass &) = delete; + EquivalenceClass(EquivalenceClass &&) = default; + EquivalenceClass &operator=(EquivalenceClass &&) = delete; + + bool operator==(const EquivalenceClass &Other) const { + return ID == Other.ID; + } + bool operator<(const EquivalenceClass &Other) const { return ID < Other.ID; } + bool operator!=(const EquivalenceClass &Other) const { + return !operator==(Other); + } + + static void Profile(llvm::FoldingSetNodeID &ID, uintptr_t CID) { + ID.AddInteger(CID); + } + + void Profile(llvm::FoldingSetNodeID &ID) const { Profile(ID, this->ID); } + +private: + /* implicit */ EquivalenceClass(SymbolRef Sym) + : ID(reinterpret_cast<uintptr_t>(Sym)) {} + + /// This function is intended to be used ONLY within the class. + /// The fact that ID is a pointer to a symbol is an implementation detail + /// and should stay that way. + /// In the current implementation, we use it to retrieve the only member + /// of the trivial class. + SymbolRef getRepresentativeSymbol() const { + return reinterpret_cast<SymbolRef>(ID); + } + static inline SymbolSet::Factory &getMembersFactory(ProgramStateRef State); + + inline ProgramStateRef mergeImpl(BasicValueFactory &BV, RangeSet::Factory &F, + ProgramStateRef State, SymbolSet Members, + EquivalenceClass Other, + SymbolSet OtherMembers); + static inline void + addToDisequalityInfo(DisequalityMapTy &Info, ConstraintRangeTy &Constraints, + BasicValueFactory &BV, RangeSet::Factory &F, + ProgramStateRef State, EquivalenceClass First, + EquivalenceClass Second); + + /// This is a unique identifier of the class. + uintptr_t ID; +}; + +//===----------------------------------------------------------------------===// +// Constraint functions +//===----------------------------------------------------------------------===// + +LLVM_NODISCARD inline const RangeSet *getConstraint(ProgramStateRef State, + EquivalenceClass Class) { + return State->get<ConstraintRange>(Class); +} + +LLVM_NODISCARD inline const RangeSet *getConstraint(ProgramStateRef State, + SymbolRef Sym) { + return getConstraint(State, EquivalenceClass::find(State, Sym)); +} + +//===----------------------------------------------------------------------===// +// Equality/diseqiality abstraction +//===----------------------------------------------------------------------===// + +/// A small helper structure representing symbolic equality. +/// +/// Equality check can have different forms (like a == b or a - b) and this +/// class encapsulates those away if the only thing the user wants to check - +/// whether it's equality/diseqiality or not and have an easy access to the +/// compared symbols. +struct EqualityInfo { +public: + SymbolRef Left, Right; + // true for equality and false for disequality. + bool IsEquality = true; + + void invert() { IsEquality = !IsEquality; } + /// Extract equality information from the given symbol and the constants. + /// + /// This function assumes the following expression Sym + Adjustment != Int. + /// It is a default because the most widespread case of the equality check + /// is (A == B) + 0 != 0. + static Optional<EqualityInfo> extract(SymbolRef Sym, const llvm::APSInt &Int, + const llvm::APSInt &Adjustment) { + // As of now, the only equality form supported is Sym + 0 != 0. + if (!Int.isNullValue() || !Adjustment.isNullValue()) + return llvm::None; + + return extract(Sym); + } + /// Extract equality information from the given symbol. + static Optional<EqualityInfo> extract(SymbolRef Sym) { + return EqualityExtractor().Visit(Sym); + } + +private: + class EqualityExtractor + : public SymExprVisitor<EqualityExtractor, Optional<EqualityInfo>> { + public: + Optional<EqualityInfo> VisitSymSymExpr(const SymSymExpr *Sym) const { + switch (Sym->getOpcode()) { + case BO_Sub: + // This case is: A - B != 0 -> disequality check. + return EqualityInfo{Sym->getLHS(), Sym->getRHS(), false}; + case BO_EQ: + // This case is: A == B != 0 -> equality check. + return EqualityInfo{Sym->getLHS(), Sym->getRHS(), true}; + case BO_NE: + // This case is: A != B != 0 -> diseqiality check. + return EqualityInfo{Sym->getLHS(), Sym->getRHS(), false}; + default: + return llvm::None; + } + } + }; +}; + +//===----------------------------------------------------------------------===// +// Intersection functions +//===----------------------------------------------------------------------===// + +template <class SecondTy, class... RestTy> +LLVM_NODISCARD inline RangeSet intersect(BasicValueFactory &BV, + RangeSet::Factory &F, RangeSet Head, + SecondTy Second, RestTy... Tail); + +template <class... RangeTy> struct IntersectionTraits; + +template <class... TailTy> struct IntersectionTraits<RangeSet, TailTy...> { + // Found RangeSet, no need to check any further + using Type = RangeSet; +}; + +template <> struct IntersectionTraits<> { + // We ran out of types, and we didn't find any RangeSet, so the result should + // be optional. + using Type = Optional<RangeSet>; +}; + +template <class OptionalOrPointer, class... TailTy> +struct IntersectionTraits<OptionalOrPointer, TailTy...> { + // If current type is Optional or a raw pointer, we should keep looking. + using Type = typename IntersectionTraits<TailTy...>::Type; +}; + +template <class EndTy> +LLVM_NODISCARD inline EndTy intersect(BasicValueFactory &BV, + RangeSet::Factory &F, EndTy End) { + // If the list contains only RangeSet or Optional<RangeSet>, simply return + // that range set. + return End; +} + +LLVM_NODISCARD LLVM_ATTRIBUTE_UNUSED inline Optional<RangeSet> +intersect(BasicValueFactory &BV, RangeSet::Factory &F, const RangeSet *End) { + // This is an extraneous conversion from a raw pointer into Optional<RangeSet> + if (End) { + return *End; + } + return llvm::None; +} + +template <class... RestTy> +LLVM_NODISCARD inline RangeSet intersect(BasicValueFactory &BV, + RangeSet::Factory &F, RangeSet Head, + RangeSet Second, RestTy... Tail) { + // Here we call either the <RangeSet,RangeSet,...> or <RangeSet,...> version + // of the function and can be sure that the result is RangeSet. + return intersect(BV, F, Head.Intersect(BV, F, Second), Tail...); +} + +template <class SecondTy, class... RestTy> +LLVM_NODISCARD inline RangeSet intersect(BasicValueFactory &BV, + RangeSet::Factory &F, RangeSet Head, + SecondTy Second, RestTy... Tail) { + if (Second) { + // Here we call the <RangeSet,RangeSet,...> version of the function... + return intersect(BV, F, Head, *Second, Tail...); + } + // ...and here it is either <RangeSet,RangeSet,...> or <RangeSet,...>, which + // means that the result is definitely RangeSet. + return intersect(BV, F, Head, Tail...); +} + +/// Main generic intersect function. +/// It intersects all of the given range sets. If some of the given arguments +/// don't hold a range set (nullptr or llvm::None), the function will skip them. +/// +/// Available representations for the arguments are: +/// * RangeSet +/// * Optional<RangeSet> +/// * RangeSet * +/// Pointer to a RangeSet is automatically assumed to be nullable and will get +/// checked as well as the optional version. If this behaviour is undesired, +/// please dereference the pointer in the call. +/// +/// Return type depends on the arguments' types. If we can be sure in compile +/// time that there will be a range set as a result, the returning type is +/// simply RangeSet, in other cases we have to back off to Optional<RangeSet>. +/// +/// Please, prefer optional range sets to raw pointers. If the last argument is +/// a raw pointer and all previous arguments are None, it will cost one +/// additional check to convert RangeSet * into Optional<RangeSet>. +template <class HeadTy, class SecondTy, class... RestTy> +LLVM_NODISCARD inline + typename IntersectionTraits<HeadTy, SecondTy, RestTy...>::Type + intersect(BasicValueFactory &BV, RangeSet::Factory &F, HeadTy Head, + SecondTy Second, RestTy... Tail) { + if (Head) { + return intersect(BV, F, *Head, Second, Tail...); + } + return intersect(BV, F, Second, Tail...); +} + +//===----------------------------------------------------------------------===// +// Symbolic reasoning logic +//===----------------------------------------------------------------------===// /// A little component aggregating all of the reasoning we have about /// the ranges of symbolic expressions. @@ -389,10 +709,11 @@ namespace { class SymbolicRangeInferrer : public SymExprVisitor<SymbolicRangeInferrer, RangeSet> { public: + template <class SourceType> static RangeSet inferRange(BasicValueFactory &BV, RangeSet::Factory &F, - ProgramStateRef State, SymbolRef Sym) { + ProgramStateRef State, SourceType Origin) { SymbolicRangeInferrer Inferrer(BV, F, State); - return Inferrer.infer(Sym); + return Inferrer.infer(Origin); } RangeSet VisitSymExpr(SymbolRef Sym) { @@ -442,37 +763,35 @@ private: } 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 (Optional<RangeSet> ConstraintBasedRange = intersect( + ValueFactory, RangeFactory, getConstraint(State, Sym), + // If Sym is a difference of symbols A - B, then maybe we have range + // set stored for B - A. + // + // 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. + getRangeForNegatedSub(Sym), getRangeForEqualities(Sym))) { + return *ConstraintBasedRange; + } // 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; + if (Optional<RangeSet> CmpRangeSet = getRangeForComparisonSymbol(Sym)) { + return *CmpRangeSet; + } return Visit(Sym); } + RangeSet infer(EquivalenceClass Class) { + if (const RangeSet *AssociatedConstraint = getConstraint(State, Class)) + return *AssociatedConstraint; + + return infer(Class.getType()); + } + /// Infer range information solely from the type. RangeSet infer(QualType T) { // Lazily generate a new RangeSet representing all possible values for the @@ -621,8 +940,7 @@ private: /// 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()); + return Domain.Delete(ValueFactory, RangeFactory, IntType.getZeroValue()); } // FIXME: Once SValBuilder supports unary minus, we should use SValBuilder to @@ -630,23 +948,26 @@ private: // 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) { + Optional<RangeSet> getRangeForNegatedSub(SymbolRef Sym) { if (const SymSymExpr *SSE = dyn_cast<SymSymExpr>(Sym)) { if (SSE->getOpcode() == BO_Sub) { QualType T = Sym->getType(); + + // Do not negate unsigned ranges + if (!T->isUnsignedIntegerOrEnumerationType() && + !T->isSignedIntegerOrEnumerationType()) + return llvm::None; + SymbolManager &SymMgr = State->getSymbolManager(); - SymbolRef negSym = + SymbolRef NegatedSym = 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; + if (const RangeSet *NegatedRange = getConstraint(State, NegatedSym)) { + return NegatedRange->Negate(ValueFactory, RangeFactory); } } } - return nullptr; + return llvm::None; } // Returns ranges only for binary comparison operators (except <=>) @@ -659,18 +980,16 @@ private: // 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); + Optional<RangeSet> getRangeForComparisonSymbol(SymbolRef Sym) { + const auto *SSE = dyn_cast<SymSymExpr>(Sym); if (!SSE) - return EmptyRangeSet; + return llvm::None; BinaryOperatorKind CurrentOP = SSE->getOpcode(); // We currently do not support <=> (C++20). if (!BinaryOperator::isComparisonOp(CurrentOP) || (CurrentOP == BO_Cmp)) - return EmptyRangeSet; + return llvm::None; static const OperatorRelationsTable CmpOpTable{}; @@ -679,10 +998,6 @@ private: 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; @@ -693,7 +1008,7 @@ private: // 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); + const RangeSet *QueriedRangeSet = getConstraint(State, SymSym); // If ranges were not previously found, // try to find a reversed expression (y > x). @@ -701,7 +1016,7 @@ private: const BinaryOperatorKind ROP = BinaryOperator::reverseComparisonOp(QueriedOP); SymSym = SymMgr.getSymSymExpr(RHS, ROP, LHS, T); - QueriedRangeSet = State->get<ConstraintRange>(SymSym); + QueriedRangeSet = getConstraint(State, SymSym); } if (!QueriedRangeSet || QueriedRangeSet->isEmpty()) @@ -732,11 +1047,38 @@ private: continue; } - return (BranchState == OperatorRelationsTable::True) ? TrueRangeSet - : FalseRangeSet; + return (BranchState == OperatorRelationsTable::True) ? getTrueRange(T) + : getFalseRange(T); + } + + return llvm::None; + } + + Optional<RangeSet> getRangeForEqualities(SymbolRef Sym) { + Optional<EqualityInfo> Equality = EqualityInfo::extract(Sym); + + if (!Equality) + return llvm::None; + + if (Optional<bool> AreEqual = EquivalenceClass::areEqual( + State, Equality->Left, Equality->Right)) { + if (*AreEqual == Equality->IsEquality) { + return getTrueRange(Sym->getType()); + } + return getFalseRange(Sym->getType()); } - return EmptyRangeSet; + return llvm::None; + } + + RangeSet getTrueRange(QualType T) { + RangeSet TypeRange = infer(T); + return assumeNonZero(TypeRange, T); + } + + RangeSet getFalseRange(QualType T) { + const llvm::APSInt &Zero = ValueFactory.getValue(0, T); + return RangeSet(RangeFactory, Zero); } BasicValueFactory &ValueFactory; @@ -744,6 +1086,10 @@ private: ProgramStateRef State; }; +//===----------------------------------------------------------------------===// +// Range-based reasoning about symbolic operations +//===----------------------------------------------------------------------===// + template <> RangeSet SymbolicRangeInferrer::VisitBinaryOperator<BO_Or>(Range LHS, Range RHS, QualType T) { @@ -904,6 +1250,10 @@ RangeSet SymbolicRangeInferrer::VisitBinaryOperator<BO_Rem>(Range LHS, return {RangeFactory, ValueFactory.getValue(Min), ValueFactory.getValue(Max)}; } +//===----------------------------------------------------------------------===// +// Constraint manager implementation details +//===----------------------------------------------------------------------===// + class RangeConstraintManager : public RangedConstraintManager { public: RangeConstraintManager(ExprEngine *EE, SValBuilder &SVB) @@ -915,7 +1265,11 @@ public: bool haveEqualConstraints(ProgramStateRef S1, ProgramStateRef S2) const override { - return S1->get<ConstraintRange>() == S2->get<ConstraintRange>(); + // NOTE: ClassMembers are as simple as back pointers for ClassMap, + // so comparing constraint ranges and class maps should be + // sufficient. + return S1->get<ConstraintRange>() == S2->get<ConstraintRange>() && + S1->get<ClassMap>() == S2->get<ClassMap>(); } bool canReasonAbout(SVal X) const override; @@ -971,6 +1325,7 @@ private: RangeSet::Factory F; RangeSet getRange(ProgramStateRef State, SymbolRef Sym); + RangeSet getRange(ProgramStateRef State, EquivalenceClass Class); RangeSet getSymLTRange(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, @@ -987,6 +1342,87 @@ private: RangeSet getSymGERange(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, const llvm::APSInt &Adjustment); + + //===------------------------------------------------------------------===// + // Equality tracking implementation + //===------------------------------------------------------------------===// + + ProgramStateRef trackEQ(RangeSet NewConstraint, ProgramStateRef State, + SymbolRef Sym, const llvm::APSInt &Int, + const llvm::APSInt &Adjustment) { + return track<true>(NewConstraint, State, Sym, Int, Adjustment); + } + + ProgramStateRef trackNE(RangeSet NewConstraint, ProgramStateRef State, + SymbolRef Sym, const llvm::APSInt &Int, + const llvm::APSInt &Adjustment) { + return track<false>(NewConstraint, State, Sym, Int, Adjustment); + } + + template <bool EQ> + ProgramStateRef track(RangeSet NewConstraint, ProgramStateRef State, + SymbolRef Sym, const llvm::APSInt &Int, + const llvm::APSInt &Adjustment) { + if (NewConstraint.isEmpty()) + // This is an infeasible assumption. + return nullptr; + + ProgramStateRef NewState = setConstraint(State, Sym, NewConstraint); + if (auto Equality = EqualityInfo::extract(Sym, Int, Adjustment)) { + // If the original assumption is not Sym + Adjustment !=/</> Int, + // we should invert IsEquality flag. + Equality->IsEquality = Equality->IsEquality != EQ; + return track(NewState, *Equality); + } + + return NewState; + } + + ProgramStateRef track(ProgramStateRef State, EqualityInfo ToTrack) { + if (ToTrack.IsEquality) { + return trackEquality(State, ToTrack.Left, ToTrack.Right); + } + return trackDisequality(State, ToTrack.Left, ToTrack.Right); + } + + ProgramStateRef trackDisequality(ProgramStateRef State, SymbolRef LHS, + SymbolRef RHS) { + return EquivalenceClass::markDisequal(getBasicVals(), F, State, LHS, RHS); + } + + ProgramStateRef trackEquality(ProgramStateRef State, SymbolRef LHS, + SymbolRef RHS) { + return EquivalenceClass::merge(getBasicVals(), F, State, LHS, RHS); + } + + LLVM_NODISCARD inline ProgramStateRef setConstraint(ProgramStateRef State, + EquivalenceClass Class, + RangeSet Constraint) { + ConstraintRangeTy Constraints = State->get<ConstraintRange>(); + ConstraintRangeTy::Factory &CF = State->get_context<ConstraintRange>(); + + // Add new constraint. + Constraints = CF.add(Constraints, Class, Constraint); + + // There is a chance that we might need to update constraints for the + // classes that are known to be disequal to Class. + // + // In order for this to be even possible, the new constraint should + // be simply a constant because we can't reason about range disequalities. + if (const llvm::APSInt *Point = Constraint.getConcreteValue()) + for (EquivalenceClass DisequalClass : Class.getDisequalClasses(State)) { + RangeSet UpdatedConstraint = + getRange(State, DisequalClass).Delete(getBasicVals(), F, *Point); + Constraints = CF.add(Constraints, DisequalClass, UpdatedConstraint); + } + + return State->set<ConstraintRange>(Constraints); + } + + LLVM_NODISCARD inline ProgramStateRef + setConstraint(ProgramStateRef State, SymbolRef Sym, RangeSet Constraint) { + return setConstraint(State, EquivalenceClass::find(State, Sym), Constraint); + } }; } // end anonymous namespace @@ -997,6 +1433,372 @@ ento::CreateRangeConstraintManager(ProgramStateManager &StMgr, return std::make_unique<RangeConstraintManager>(Eng, StMgr.getSValBuilder()); } +ConstraintMap ento::getConstraintMap(ProgramStateRef State) { + ConstraintMap::Factory &F = State->get_context<ConstraintMap>(); + ConstraintMap Result = F.getEmptyMap(); + + ConstraintRangeTy Constraints = State->get<ConstraintRange>(); + for (std::pair<EquivalenceClass, RangeSet> ClassConstraint : Constraints) { + EquivalenceClass Class = ClassConstraint.first; + SymbolSet ClassMembers = Class.getClassMembers(State); + assert(!ClassMembers.isEmpty() && + "Class must always have at least one member!"); + + SymbolRef Representative = *ClassMembers.begin(); + Result = F.add(Result, Representative, ClassConstraint.second); + } + + return Result; +} + +//===----------------------------------------------------------------------===// +// EqualityClass implementation details +//===----------------------------------------------------------------------===// + +inline EquivalenceClass EquivalenceClass::find(ProgramStateRef State, + SymbolRef Sym) { + // We store far from all Symbol -> Class mappings + if (const EquivalenceClass *NontrivialClass = State->get<ClassMap>(Sym)) + return *NontrivialClass; + + // This is a trivial class of Sym. + return Sym; +} + +inline ProgramStateRef EquivalenceClass::merge(BasicValueFactory &BV, + RangeSet::Factory &F, + ProgramStateRef State, + SymbolRef First, + SymbolRef Second) { + EquivalenceClass FirstClass = find(State, First); + EquivalenceClass SecondClass = find(State, Second); + + return FirstClass.merge(BV, F, State, SecondClass); +} + +inline ProgramStateRef EquivalenceClass::merge(BasicValueFactory &BV, + RangeSet::Factory &F, + ProgramStateRef State, + EquivalenceClass Other) { + // It is already the same class. + if (*this == Other) + return State; + + // FIXME: As of now, we support only equivalence classes of the same type. + // This limitation is connected to the lack of explicit casts in + // our symbolic expression model. + // + // That means that for `int x` and `char y` we don't distinguish + // between these two very different cases: + // * `x == y` + // * `(char)x == y` + // + // The moment we introduce symbolic casts, this restriction can be + // lifted. + if (getType() != Other.getType()) + return State; + + SymbolSet Members = getClassMembers(State); + SymbolSet OtherMembers = Other.getClassMembers(State); + + // We estimate the size of the class by the height of tree containing + // its members. Merging is not a trivial operation, so it's easier to + // merge the smaller class into the bigger one. + if (Members.getHeight() >= OtherMembers.getHeight()) { + return mergeImpl(BV, F, State, Members, Other, OtherMembers); + } else { + return Other.mergeImpl(BV, F, State, OtherMembers, *this, Members); + } +} + +inline ProgramStateRef +EquivalenceClass::mergeImpl(BasicValueFactory &ValueFactory, + RangeSet::Factory &RangeFactory, + ProgramStateRef State, SymbolSet MyMembers, + EquivalenceClass Other, SymbolSet OtherMembers) { + // Essentially what we try to recreate here is some kind of union-find + // data structure. It does have certain limitations due to persistence + // and the need to remove elements from classes. + // + // In this setting, EquialityClass object is the representative of the class + // or the parent element. ClassMap is a mapping of class members to their + // parent. Unlike the union-find structure, they all point directly to the + // class representative because we don't have an opportunity to actually do + // path compression when dealing with immutability. This means that we + // compress paths every time we do merges. It also means that we lose + // the main amortized complexity benefit from the original data structure. + ConstraintRangeTy Constraints = State->get<ConstraintRange>(); + ConstraintRangeTy::Factory &CRF = State->get_context<ConstraintRange>(); + + // 1. If the merged classes have any constraints associated with them, we + // need to transfer them to the class we have left. + // + // Intersection here makes perfect sense because both of these constraints + // must hold for the whole new class. + if (Optional<RangeSet> NewClassConstraint = + intersect(ValueFactory, RangeFactory, getConstraint(State, *this), + getConstraint(State, Other))) { + // NOTE: Essentially, NewClassConstraint should NEVER be infeasible because + // range inferrer shouldn't generate ranges incompatible with + // equivalence classes. However, at the moment, due to imperfections + // in the solver, it is possible and the merge function can also + // return infeasible states aka null states. + if (NewClassConstraint->isEmpty()) + // Infeasible state + return nullptr; + + // No need in tracking constraints of a now-dissolved class. + Constraints = CRF.remove(Constraints, Other); + // Assign new constraints for this class. + Constraints = CRF.add(Constraints, *this, *NewClassConstraint); + + State = State->set<ConstraintRange>(Constraints); + } + + // 2. Get ALL equivalence-related maps + ClassMapTy Classes = State->get<ClassMap>(); + ClassMapTy::Factory &CMF = State->get_context<ClassMap>(); + + ClassMembersTy Members = State->get<ClassMembers>(); + ClassMembersTy::Factory &MF = State->get_context<ClassMembers>(); + + DisequalityMapTy DisequalityInfo = State->get<DisequalityMap>(); + DisequalityMapTy::Factory &DF = State->get_context<DisequalityMap>(); + + ClassSet::Factory &CF = State->get_context<ClassSet>(); + SymbolSet::Factory &F = getMembersFactory(State); + + // 2. Merge members of the Other class into the current class. + SymbolSet NewClassMembers = MyMembers; + for (SymbolRef Sym : OtherMembers) { + NewClassMembers = F.add(NewClassMembers, Sym); + // *this is now the class for all these new symbols. + Classes = CMF.add(Classes, Sym, *this); + } + + // 3. Adjust member mapping. + // + // No need in tracking members of a now-dissolved class. + Members = MF.remove(Members, Other); + // Now only the current class is mapped to all the symbols. + Members = MF.add(Members, *this, NewClassMembers); + + // 4. Update disequality relations + ClassSet DisequalToOther = Other.getDisequalClasses(DisequalityInfo, CF); + if (!DisequalToOther.isEmpty()) { + ClassSet DisequalToThis = getDisequalClasses(DisequalityInfo, CF); + DisequalityInfo = DF.remove(DisequalityInfo, Other); + + for (EquivalenceClass DisequalClass : DisequalToOther) { + DisequalToThis = CF.add(DisequalToThis, DisequalClass); + + // Disequality is a symmetric relation meaning that if + // DisequalToOther not null then the set for DisequalClass is not + // empty and has at least Other. + ClassSet OriginalSetLinkedToOther = + *DisequalityInfo.lookup(DisequalClass); + + // Other will be eliminated and we should replace it with the bigger + // united class. + ClassSet NewSet = CF.remove(OriginalSetLinkedToOther, Other); + NewSet = CF.add(NewSet, *this); + + DisequalityInfo = DF.add(DisequalityInfo, DisequalClass, NewSet); + } + + DisequalityInfo = DF.add(DisequalityInfo, *this, DisequalToThis); + State = State->set<DisequalityMap>(DisequalityInfo); + } + + // 5. Update the state + State = State->set<ClassMap>(Classes); + State = State->set<ClassMembers>(Members); + + return State; +} + +inline SymbolSet::Factory & +EquivalenceClass::getMembersFactory(ProgramStateRef State) { + return State->get_context<SymbolSet>(); +} + +SymbolSet EquivalenceClass::getClassMembers(ProgramStateRef State) { + if (const SymbolSet *Members = State->get<ClassMembers>(*this)) + return *Members; + + // This class is trivial, so we need to construct a set + // with just that one symbol from the class. + SymbolSet::Factory &F = getMembersFactory(State); + return F.add(F.getEmptySet(), getRepresentativeSymbol()); +} + +bool EquivalenceClass::isTrivial(ProgramStateRef State) { + return State->get<ClassMembers>(*this) == nullptr; +} + +bool EquivalenceClass::isTriviallyDead(ProgramStateRef State, + SymbolReaper &Reaper) { + return isTrivial(State) && Reaper.isDead(getRepresentativeSymbol()); +} + +inline ProgramStateRef EquivalenceClass::markDisequal(BasicValueFactory &VF, + RangeSet::Factory &RF, + ProgramStateRef State, + SymbolRef First, + SymbolRef Second) { + return markDisequal(VF, RF, State, find(State, First), find(State, Second)); +} + +inline ProgramStateRef EquivalenceClass::markDisequal(BasicValueFactory &VF, + RangeSet::Factory &RF, + ProgramStateRef State, + EquivalenceClass First, + EquivalenceClass Second) { + return First.markDisequal(VF, RF, State, Second); +} + +inline ProgramStateRef +EquivalenceClass::markDisequal(BasicValueFactory &VF, RangeSet::Factory &RF, + ProgramStateRef State, + EquivalenceClass Other) const { + // If we know that two classes are equal, we can only produce an infeasible + // state. + if (*this == Other) { + return nullptr; + } + + DisequalityMapTy DisequalityInfo = State->get<DisequalityMap>(); + ConstraintRangeTy Constraints = State->get<ConstraintRange>(); + + // Disequality is a symmetric relation, so if we mark A as disequal to B, + // we should also mark B as disequalt to A. + addToDisequalityInfo(DisequalityInfo, Constraints, VF, RF, State, *this, + Other); + addToDisequalityInfo(DisequalityInfo, Constraints, VF, RF, State, Other, + *this); + + State = State->set<DisequalityMap>(DisequalityInfo); + State = State->set<ConstraintRange>(Constraints); + + return State; +} + +inline void EquivalenceClass::addToDisequalityInfo( + DisequalityMapTy &Info, ConstraintRangeTy &Constraints, + BasicValueFactory &VF, RangeSet::Factory &RF, ProgramStateRef State, + EquivalenceClass First, EquivalenceClass Second) { + + // 1. Get all of the required factories. + DisequalityMapTy::Factory &F = State->get_context<DisequalityMap>(); + ClassSet::Factory &CF = State->get_context<ClassSet>(); + ConstraintRangeTy::Factory &CRF = State->get_context<ConstraintRange>(); + + // 2. Add Second to the set of classes disequal to First. + const ClassSet *CurrentSet = Info.lookup(First); + ClassSet NewSet = CurrentSet ? *CurrentSet : CF.getEmptySet(); + NewSet = CF.add(NewSet, Second); + + Info = F.add(Info, First, NewSet); + + // 3. If Second is known to be a constant, we can delete this point + // from the constraint asociated with First. + // + // So, if Second == 10, it means that First != 10. + // At the same time, the same logic does not apply to ranges. + if (const RangeSet *SecondConstraint = Constraints.lookup(Second)) + if (const llvm::APSInt *Point = SecondConstraint->getConcreteValue()) { + + RangeSet FirstConstraint = SymbolicRangeInferrer::inferRange( + VF, RF, State, First.getRepresentativeSymbol()); + + FirstConstraint = FirstConstraint.Delete(VF, RF, *Point); + Constraints = CRF.add(Constraints, First, FirstConstraint); + } +} + +inline Optional<bool> EquivalenceClass::areEqual(ProgramStateRef State, + SymbolRef FirstSym, + SymbolRef SecondSym) { + EquivalenceClass First = find(State, FirstSym); + EquivalenceClass Second = find(State, SecondSym); + + // The same equivalence class => symbols are equal. + if (First == Second) + return true; + + // Let's check if we know anything about these two classes being not equal to + // each other. + ClassSet DisequalToFirst = First.getDisequalClasses(State); + if (DisequalToFirst.contains(Second)) + return false; + + // It is not clear. + return llvm::None; +} + +inline ClassSet EquivalenceClass::getDisequalClasses(ProgramStateRef State, + SymbolRef Sym) { + return find(State, Sym).getDisequalClasses(State); +} + +inline ClassSet +EquivalenceClass::getDisequalClasses(ProgramStateRef State) const { + return getDisequalClasses(State->get<DisequalityMap>(), + State->get_context<ClassSet>()); +} + +inline ClassSet +EquivalenceClass::getDisequalClasses(DisequalityMapTy Map, + ClassSet::Factory &Factory) const { + if (const ClassSet *DisequalClasses = Map.lookup(*this)) + return *DisequalClasses; + + return Factory.getEmptySet(); +} + +bool EquivalenceClass::isClassDataConsistent(ProgramStateRef State) { + ClassMembersTy Members = State->get<ClassMembers>(); + + for (std::pair<EquivalenceClass, SymbolSet> ClassMembersPair : Members) { + for (SymbolRef Member : ClassMembersPair.second) { + // Every member of the class should have a mapping back to the class. + if (find(State, Member) == ClassMembersPair.first) { + continue; + } + + return false; + } + } + + DisequalityMapTy Disequalities = State->get<DisequalityMap>(); + for (std::pair<EquivalenceClass, ClassSet> DisequalityInfo : Disequalities) { + EquivalenceClass Class = DisequalityInfo.first; + ClassSet DisequalClasses = DisequalityInfo.second; + + // There is no use in keeping empty sets in the map. + if (DisequalClasses.isEmpty()) + return false; + + // Disequality is symmetrical, i.e. for every Class A and B that A != B, + // B != A should also be true. + for (EquivalenceClass DisequalClass : DisequalClasses) { + const ClassSet *DisequalToDisequalClasses = + Disequalities.lookup(DisequalClass); + + // It should be a set of at least one element: Class + if (!DisequalToDisequalClasses || + !DisequalToDisequalClasses->contains(Class)) + return false; + } + } + + return true; +} + +//===----------------------------------------------------------------------===// +// RangeConstraintManager implementation +//===----------------------------------------------------------------------===// + bool RangeConstraintManager::canReasonAbout(SVal X) const { Optional<nonloc::SymbolVal> SymVal = X.getAs<nonloc::SymbolVal>(); if (SymVal && SymVal->isExpression()) { @@ -1045,7 +1847,7 @@ bool RangeConstraintManager::canReasonAbout(SVal X) const { ConditionTruthVal RangeConstraintManager::checkNull(ProgramStateRef State, SymbolRef Sym) { - const RangeSet *Ranges = State->get<ConstraintRange>(Sym); + const RangeSet *Ranges = getConstraint(State, Sym); // If we don't have any information about this symbol, it's underconstrained. if (!Ranges) @@ -1069,28 +1871,148 @@ ConditionTruthVal RangeConstraintManager::checkNull(ProgramStateRef State, const llvm::APSInt *RangeConstraintManager::getSymVal(ProgramStateRef St, SymbolRef Sym) const { - const ConstraintRangeTy::data_type *T = St->get<ConstraintRange>(Sym); + const RangeSet *T = getConstraint(St, Sym); return T ? T->getConcreteValue() : nullptr; } +//===----------------------------------------------------------------------===// +// Remove dead symbols from existing constraints +//===----------------------------------------------------------------------===// + /// Scan all symbols referenced by the constraints. If the symbol is not alive /// as marked in LSymbols, mark it as dead in DSymbols. ProgramStateRef RangeConstraintManager::removeDeadBindings(ProgramStateRef State, SymbolReaper &SymReaper) { - bool Changed = false; - ConstraintRangeTy CR = State->get<ConstraintRange>(); - ConstraintRangeTy::Factory &CRFactory = State->get_context<ConstraintRange>(); + ClassMembersTy ClassMembersMap = State->get<ClassMembers>(); + ClassMembersTy NewClassMembersMap = ClassMembersMap; + ClassMembersTy::Factory &EMFactory = State->get_context<ClassMembers>(); + SymbolSet::Factory &SetFactory = State->get_context<SymbolSet>(); + + ConstraintRangeTy Constraints = State->get<ConstraintRange>(); + ConstraintRangeTy NewConstraints = Constraints; + ConstraintRangeTy::Factory &ConstraintFactory = + State->get_context<ConstraintRange>(); + + ClassMapTy Map = State->get<ClassMap>(); + ClassMapTy NewMap = Map; + ClassMapTy::Factory &ClassFactory = State->get_context<ClassMap>(); + + DisequalityMapTy Disequalities = State->get<DisequalityMap>(); + DisequalityMapTy::Factory &DisequalityFactory = + State->get_context<DisequalityMap>(); + ClassSet::Factory &ClassSetFactory = State->get_context<ClassSet>(); + + bool ClassMapChanged = false; + bool MembersMapChanged = false; + bool ConstraintMapChanged = false; + bool DisequalitiesChanged = false; + + auto removeDeadClass = [&](EquivalenceClass Class) { + // Remove associated constraint ranges. + Constraints = ConstraintFactory.remove(Constraints, Class); + ConstraintMapChanged = true; + + // Update disequality information to not hold any information on the + // removed class. + ClassSet DisequalClasses = + Class.getDisequalClasses(Disequalities, ClassSetFactory); + if (!DisequalClasses.isEmpty()) { + for (EquivalenceClass DisequalClass : DisequalClasses) { + ClassSet DisequalToDisequalSet = + DisequalClass.getDisequalClasses(Disequalities, ClassSetFactory); + // DisequalToDisequalSet is guaranteed to be non-empty for consistent + // disequality info. + assert(!DisequalToDisequalSet.isEmpty()); + ClassSet NewSet = ClassSetFactory.remove(DisequalToDisequalSet, Class); + + // No need in keeping an empty set. + if (NewSet.isEmpty()) { + Disequalities = + DisequalityFactory.remove(Disequalities, DisequalClass); + } else { + Disequalities = + DisequalityFactory.add(Disequalities, DisequalClass, NewSet); + } + } + // Remove the data for the class + Disequalities = DisequalityFactory.remove(Disequalities, Class); + DisequalitiesChanged = true; + } + }; + + // 1. Let's see if dead symbols are trivial and have associated constraints. + for (std::pair<EquivalenceClass, RangeSet> ClassConstraintPair : + Constraints) { + EquivalenceClass Class = ClassConstraintPair.first; + if (Class.isTriviallyDead(State, SymReaper)) { + // If this class is trivial, we can remove its constraints right away. + removeDeadClass(Class); + } + } + + // 2. We don't need to track classes for dead symbols. + for (std::pair<SymbolRef, EquivalenceClass> SymbolClassPair : Map) { + SymbolRef Sym = SymbolClassPair.first; - for (ConstraintRangeTy::iterator I = CR.begin(), E = CR.end(); I != E; ++I) { - SymbolRef Sym = I.getKey(); if (SymReaper.isDead(Sym)) { - Changed = true; - CR = CRFactory.remove(CR, Sym); + ClassMapChanged = true; + NewMap = ClassFactory.remove(NewMap, Sym); + } + } + + // 3. Remove dead members from classes and remove dead non-trivial classes + // and their constraints. + for (std::pair<EquivalenceClass, SymbolSet> ClassMembersPair : + ClassMembersMap) { + EquivalenceClass Class = ClassMembersPair.first; + SymbolSet LiveMembers = ClassMembersPair.second; + bool MembersChanged = false; + + for (SymbolRef Member : ClassMembersPair.second) { + if (SymReaper.isDead(Member)) { + MembersChanged = true; + LiveMembers = SetFactory.remove(LiveMembers, Member); + } + } + + // Check if the class changed. + if (!MembersChanged) + continue; + + MembersMapChanged = true; + + if (LiveMembers.isEmpty()) { + // The class is dead now, we need to wipe it out of the members map... + NewClassMembersMap = EMFactory.remove(NewClassMembersMap, Class); + + // ...and remove all of its constraints. + removeDeadClass(Class); + } else { + // We need to change the members associated with the class. + NewClassMembersMap = + EMFactory.add(NewClassMembersMap, Class, LiveMembers); } } - return Changed ? State->set<ConstraintRange>(CR) : State; + // 4. Update the state with new maps. + // + // Here we try to be humble and update a map only if it really changed. + if (ClassMapChanged) + State = State->set<ClassMap>(NewMap); + + if (MembersMapChanged) + State = State->set<ClassMembers>(NewClassMembersMap); + + if (ConstraintMapChanged) + State = State->set<ConstraintRange>(Constraints); + + if (DisequalitiesChanged) + State = State->set<DisequalityMap>(Disequalities); + + assert(EquivalenceClass::isClassDataConsistent(State)); + + return State; } RangeSet RangeConstraintManager::getRange(ProgramStateRef State, @@ -1098,6 +2020,11 @@ RangeSet RangeConstraintManager::getRange(ProgramStateRef State, return SymbolicRangeInferrer::inferRange(getBasicVals(), F, State, Sym); } +RangeSet RangeConstraintManager::getRange(ProgramStateRef State, + EquivalenceClass Class) { + return SymbolicRangeInferrer::inferRange(getBasicVals(), F, State, Class); +} + //===------------------------------------------------------------------------=== // assumeSymX methods: protected interface for RangeConstraintManager. //===------------------------------------------------------------------------===/ @@ -1119,15 +2046,11 @@ RangeConstraintManager::assumeSymNE(ProgramStateRef St, SymbolRef Sym, if (AdjustmentType.testInRange(Int, true) != APSIntType::RTR_Within) return St; - llvm::APSInt Lower = AdjustmentType.convert(Int) - Adjustment; - llvm::APSInt Upper = Lower; - --Lower; - ++Upper; + llvm::APSInt Point = AdjustmentType.convert(Int) - Adjustment; - // [Int-Adjustment+1, Int-Adjustment-1] - // Notice that the lower bound is greater than the upper bound. - RangeSet New = getRange(St, Sym).Intersect(getBasicVals(), F, Upper, Lower); - return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New); + RangeSet New = getRange(St, Sym).Delete(getBasicVals(), F, Point); + + return trackNE(New, St, Sym, Int, Adjustment); } ProgramStateRef @@ -1142,7 +2065,8 @@ RangeConstraintManager::assumeSymEQ(ProgramStateRef St, SymbolRef Sym, // [Int-Adjustment, Int-Adjustment] llvm::APSInt AdjInt = AdjustmentType.convert(Int) - Adjustment; RangeSet New = getRange(St, Sym).Intersect(getBasicVals(), F, AdjInt, AdjInt); - return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New); + + return trackEQ(New, St, Sym, Int, Adjustment); } RangeSet RangeConstraintManager::getSymLTRange(ProgramStateRef St, @@ -1178,7 +2102,7 @@ RangeConstraintManager::assumeSymLT(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, const llvm::APSInt &Adjustment) { RangeSet New = getSymLTRange(St, Sym, Int, Adjustment); - return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New); + return trackNE(New, St, Sym, Int, Adjustment); } RangeSet RangeConstraintManager::getSymGTRange(ProgramStateRef St, @@ -1214,7 +2138,7 @@ RangeConstraintManager::assumeSymGT(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, const llvm::APSInt &Adjustment) { RangeSet New = getSymGTRange(St, Sym, Int, Adjustment); - return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New); + return trackNE(New, St, Sym, Int, Adjustment); } RangeSet RangeConstraintManager::getSymGERange(ProgramStateRef St, @@ -1250,13 +2174,13 @@ RangeConstraintManager::assumeSymGE(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, const llvm::APSInt &Adjustment) { RangeSet New = getSymGERange(St, Sym, Int, Adjustment); - return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New); + return New.isEmpty() ? nullptr : setConstraint(St, Sym, New); } -RangeSet RangeConstraintManager::getSymLERange( - llvm::function_ref<RangeSet()> RS, - const llvm::APSInt &Int, - const llvm::APSInt &Adjustment) { +RangeSet +RangeConstraintManager::getSymLERange(llvm::function_ref<RangeSet()> RS, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment) { // Before we do any real work, see if the value can even show up. APSIntType AdjustmentType(Adjustment); switch (AdjustmentType.testInRange(Int, true)) { @@ -1293,7 +2217,7 @@ RangeConstraintManager::assumeSymLE(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, const llvm::APSInt &Adjustment) { RangeSet New = getSymLERange(St, Sym, Int, Adjustment); - return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New); + return New.isEmpty() ? nullptr : setConstraint(St, Sym, New); } ProgramStateRef RangeConstraintManager::assumeSymWithinInclusiveRange( @@ -1303,7 +2227,7 @@ ProgramStateRef RangeConstraintManager::assumeSymWithinInclusiveRange( if (New.isEmpty()) return nullptr; RangeSet Out = getSymLERange([&] { return New; }, To, Adjustment); - return Out.isEmpty() ? nullptr : State->set<ConstraintRange>(Sym, Out); + return Out.isEmpty() ? nullptr : setConstraint(State, Sym, Out); } ProgramStateRef RangeConstraintManager::assumeSymOutsideInclusiveRange( @@ -1312,7 +2236,7 @@ ProgramStateRef RangeConstraintManager::assumeSymOutsideInclusiveRange( RangeSet RangeLT = getSymLTRange(State, Sym, From, Adjustment); RangeSet RangeGT = getSymGTRange(State, Sym, To, Adjustment); RangeSet New(RangeLT.addRange(F, RangeGT)); - return New.isEmpty() ? nullptr : State->set<ConstraintRange>(Sym, New); + return New.isEmpty() ? nullptr : setConstraint(State, Sym, New); } //===----------------------------------------------------------------------===// @@ -1332,17 +2256,25 @@ void RangeConstraintManager::printJson(raw_ostream &Out, ProgramStateRef State, ++Space; Out << '[' << NL; - for (ConstraintRangeTy::iterator I = Constraints.begin(); - I != Constraints.end(); ++I) { - Indent(Out, Space, IsDot) - << "{ \"symbol\": \"" << I.getKey() << "\", \"range\": \""; - I.getData().print(Out); - Out << "\" }"; + bool First = true; + for (std::pair<EquivalenceClass, RangeSet> P : Constraints) { + SymbolSet ClassMembers = P.first.getClassMembers(State); - if (std::next(I) != Constraints.end()) - Out << ','; - Out << NL; + // We can print the same constraint for every class member. + for (SymbolRef ClassMember : ClassMembers) { + if (First) { + First = false; + } else { + Out << ','; + Out << NL; + } + Indent(Out, Space, IsDot) + << "{ \"symbol\": \"" << ClassMember << "\", \"range\": \""; + P.second.print(Out); + Out << "\" }"; + } } + Out << NL; --Space; Indent(Out, Space, IsDot) << "]," << NL; diff --git a/clang/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp b/clang/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp index 4748c106eb55..e7a03e6ed582 100644 --- a/clang/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp @@ -40,19 +40,20 @@ ProgramStateRef RangedConstraintManager::assumeSym(ProgramStateRef State, } } else if (const SymSymExpr *SSE = dyn_cast<SymSymExpr>(Sym)) { - // Translate "a != b" to "(b - a) != 0". - // We invert the order of the operands as a heuristic for how loop - // conditions are usually written ("begin != end") as compared to length - // calculations ("end - begin"). The more correct thing to do would be to - // canonicalize "a - b" and "b - a", which would allow us to treat - // "a != b" and "b != a" the same. - SymbolManager &SymMgr = getSymbolManager(); BinaryOperator::Opcode Op = SSE->getOpcode(); assert(BinaryOperator::isComparisonOp(Op)); - // For now, we only support comparing pointers. + // We convert equality operations for pointers only. if (Loc::isLocType(SSE->getLHS()->getType()) && Loc::isLocType(SSE->getRHS()->getType())) { + // Translate "a != b" to "(b - a) != 0". + // We invert the order of the operands as a heuristic for how loop + // conditions are usually written ("begin != end") as compared to length + // calculations ("end - begin"). The more correct thing to do would be to + // canonicalize "a - b" and "b - a", which would allow us to treat + // "a != b" and "b != a" the same. + + SymbolManager &SymMgr = getSymbolManager(); QualType DiffTy = SymMgr.getContext().getPointerDiffType(); SymbolRef Subtraction = SymMgr.getSymSymExpr(SSE->getRHS(), BO_Sub, SSE->getLHS(), DiffTy); @@ -63,6 +64,25 @@ ProgramStateRef RangedConstraintManager::assumeSym(ProgramStateRef State, Op = BinaryOperator::negateComparisonOp(Op); return assumeSymRel(State, Subtraction, Op, Zero); } + + if (BinaryOperator::isEqualityOp(Op)) { + SymbolManager &SymMgr = getSymbolManager(); + + QualType ExprType = SSE->getType(); + SymbolRef CanonicalEquality = + SymMgr.getSymSymExpr(SSE->getLHS(), BO_EQ, SSE->getRHS(), ExprType); + + bool WasEqual = SSE->getOpcode() == BO_EQ; + bool IsExpectedEqual = WasEqual == Assumption; + + const llvm::APSInt &Zero = getBasicVals().getValue(0, ExprType); + + if (IsExpectedEqual) { + return assumeSymNE(State, CanonicalEquality, Zero, Zero); + } + + return assumeSymEQ(State, CanonicalEquality, Zero, Zero); + } } // If we get here, there's nothing else we can do but treat the symbol as @@ -199,11 +219,6 @@ void RangedConstraintManager::computeAdjustment(SymbolRef &Sym, } } -void *ProgramStateTrait<ConstraintRange>::GDMIndex() { - static int Index; - return &Index; -} - } // end of namespace ento } // end of namespace clang diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index c00a2c8ba8a2..72b8ada1dfab 100644 --- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -236,10 +236,11 @@ SValBuilder::getDerivedRegionValueSymbolVal(SymbolRef parentSymbol, return nonloc::SymbolVal(sym); } -DefinedSVal SValBuilder::getMemberPointer(const DeclaratorDecl *DD) { - assert(!DD || isa<CXXMethodDecl>(DD) || isa<FieldDecl>(DD)); +DefinedSVal SValBuilder::getMemberPointer(const NamedDecl *ND) { + assert(!ND || isa<CXXMethodDecl>(ND) || isa<FieldDecl>(ND) || + isa<IndirectFieldDecl>(ND)); - if (const auto *MD = dyn_cast_or_null<CXXMethodDecl>(DD)) { + if (const auto *MD = dyn_cast_or_null<CXXMethodDecl>(ND)) { // Sema treats pointers to static member functions as have function pointer // type, so return a function pointer for the method. // We don't need to play a similar trick for static member fields @@ -249,7 +250,7 @@ DefinedSVal SValBuilder::getMemberPointer(const DeclaratorDecl *DD) { return getFunctionPointer(MD); } - return nonloc::PointerToMember(DD); + return nonloc::PointerToMember(ND); } DefinedSVal SValBuilder::getFunctionPointer(const FunctionDecl *func) { @@ -305,6 +306,14 @@ Optional<SVal> SValBuilder::getConstantVal(const Expr *E) { return makeLoc(getRegionManager().getStringRegion(SL)); } + case Stmt::PredefinedExprClass: { + const auto *PE = cast<PredefinedExpr>(E); + assert(PE->getFunctionName() && + "Since we analyze only instantiated functions, PredefinedExpr " + "should have a function name."); + return makeLoc(getRegionManager().getStringRegion(PE->getFunctionName())); + } + // Fast-path some expressions to avoid the overhead of going through the AST's // constant evaluator case Stmt::CharacterLiteralClass: { @@ -377,8 +386,8 @@ Optional<SVal> SValBuilder::getConstantVal(const Expr *E) { SVal SValBuilder::makeSymExprValNN(BinaryOperator::Opcode Op, NonLoc LHS, NonLoc RHS, QualType ResultTy) { - const SymExpr *symLHS = LHS.getAsSymExpr(); - const SymExpr *symRHS = RHS.getAsSymExpr(); + SymbolRef symLHS = LHS.getAsSymbol(); + SymbolRef symRHS = RHS.getAsSymbol(); // TODO: When the Max Complexity is reached, we should conjure a symbol // instead of generating an Unknown value and propagate the taint info to it. @@ -492,7 +501,7 @@ SVal SValBuilder::evalIntegralCast(ProgramStateRef state, SVal val, if (getContext().getTypeSize(castTy) >= getContext().getTypeSize(originalTy)) return evalCast(val, castTy, originalTy); - const SymExpr *se = val.getAsSymbolicExpression(); + SymbolRef se = val.getAsSymbol(); if (!se) // Let evalCast handle non symbolic expressions. return evalCast(val, castTy, originalTy); diff --git a/clang/lib/StaticAnalyzer/Core/SVals.cpp b/clang/lib/StaticAnalyzer/Core/SVals.cpp index 9b5de6c3eb92..252596887e4f 100644 --- a/clang/lib/StaticAnalyzer/Core/SVals.cpp +++ b/clang/lib/StaticAnalyzer/Core/SVals.cpp @@ -84,16 +84,12 @@ const FunctionDecl *SVal::getAsFunctionDecl() const { /// the first symbolic parent region is returned. SymbolRef SVal::getAsLocSymbol(bool IncludeBaseRegions) const { // FIXME: should we consider SymbolRef wrapped in CodeTextRegion? - if (Optional<nonloc::LocAsInteger> X = getAs<nonloc::LocAsInteger>()) - return X->getLoc().getAsLocSymbol(IncludeBaseRegions); - - if (Optional<loc::MemRegionVal> X = getAs<loc::MemRegionVal>()) { - const MemRegion *R = X->getRegion(); - if (const SymbolicRegion *SymR = IncludeBaseRegions ? - R->getSymbolicBase() : - dyn_cast<SymbolicRegion>(R->StripCasts())) + if (const MemRegion *R = getAsRegion()) + if (const SymbolicRegion *SymR = + IncludeBaseRegions ? R->getSymbolicBase() + : dyn_cast<SymbolicRegion>(R->StripCasts())) return SymR->getSymbol(); - } + return nullptr; } @@ -116,8 +112,6 @@ SymbolRef SVal::getLocSymbolInBase() const { return nullptr; } -// TODO: The next 3 functions have to be simplified. - /// If this SVal wraps a symbol return that SymbolRef. /// Otherwise, return 0. /// @@ -132,22 +126,6 @@ SymbolRef SVal::getAsSymbol(bool IncludeBaseRegions) const { return getAsLocSymbol(IncludeBaseRegions); } -/// getAsSymbolicExpression - If this Sval wraps a symbolic expression then -/// return that expression. Otherwise return NULL. -const SymExpr *SVal::getAsSymbolicExpression() const { - if (Optional<nonloc::SymbolVal> X = getAs<nonloc::SymbolVal>()) - return X->getSymbol(); - - return getAsSymbol(); -} - -const SymExpr* SVal::getAsSymExpr() const { - const SymExpr* Sym = getAsSymbol(); - if (!Sym) - Sym = getAsSymbolicExpression(); - return Sym; -} - const MemRegion *SVal::getAsRegion() const { if (Optional<loc::MemRegionVal> X = getAs<loc::MemRegionVal>()) return X->getRegion(); @@ -175,18 +153,18 @@ bool nonloc::PointerToMember::isNullMemberPointer() const { return getPTMData().isNull(); } -const DeclaratorDecl *nonloc::PointerToMember::getDecl() const { +const NamedDecl *nonloc::PointerToMember::getDecl() const { const auto PTMD = this->getPTMData(); if (PTMD.isNull()) return nullptr; - const DeclaratorDecl *DD = nullptr; - if (PTMD.is<const DeclaratorDecl *>()) - DD = PTMD.get<const DeclaratorDecl *>(); + const NamedDecl *ND = nullptr; + if (PTMD.is<const NamedDecl *>()) + ND = PTMD.get<const NamedDecl *>(); else - DD = PTMD.get<const PointerToMemberData *>()->getDeclaratorDecl(); + ND = PTMD.get<const PointerToMemberData *>()->getDeclaratorDecl(); - return DD; + return ND; } //===----------------------------------------------------------------------===// @@ -203,14 +181,14 @@ nonloc::CompoundVal::iterator nonloc::CompoundVal::end() const { nonloc::PointerToMember::iterator nonloc::PointerToMember::begin() const { const PTMDataType PTMD = getPTMData(); - if (PTMD.is<const DeclaratorDecl *>()) + if (PTMD.is<const NamedDecl *>()) return {}; return PTMD.get<const PointerToMemberData *>()->begin(); } nonloc::PointerToMember::iterator nonloc::PointerToMember::end() const { const PTMDataType PTMD = getPTMData(); - if (PTMD.is<const DeclaratorDecl *>()) + if (PTMD.is<const NamedDecl *>()) return {}; return PTMD.get<const PointerToMemberData *>()->end(); } diff --git a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp index 8c2e85601576..f93d04ccd61a 100644 --- a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp @@ -14,7 +14,6 @@ #include "clang/Basic/FileManager.h" #include "clang/Basic/Version.h" #include "clang/Lex/Preprocessor.h" -#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringMap.h" @@ -32,8 +31,7 @@ class SarifDiagnostics : public PathDiagnosticConsumer { const LangOptions &LO; public: - SarifDiagnostics(AnalyzerOptions &, const std::string &Output, - const LangOptions &LO) + SarifDiagnostics(const std::string &Output, const LangOptions &LO) : OutputFile(Output), LO(LO) {} ~SarifDiagnostics() override = default; @@ -48,7 +46,7 @@ public: } // end anonymous namespace void ento::createSarifDiagnosticConsumer( - AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &Output, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU) { @@ -56,8 +54,9 @@ void ento::createSarifDiagnosticConsumer( if (Output.empty()) return; - C.push_back(new SarifDiagnostics(AnalyzerOpts, Output, PP.getLangOpts())); - createTextMinimalPathDiagnosticConsumer(AnalyzerOpts, C, Output, PP, CTU); + C.push_back(new SarifDiagnostics(Output, PP.getLangOpts())); + createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, Output, PP, + CTU); } static StringRef getFileName(const FileEntry &FE) { @@ -160,9 +159,8 @@ static unsigned int adjustColumnPos(const SourceManager &SM, SourceLocation Loc, assert(LocInfo.second > SM.getExpansionColumnNumber(Loc) && "position in file is before column number?"); - bool InvalidBuffer = false; - const MemoryBuffer *Buf = SM.getBuffer(LocInfo.first, &InvalidBuffer); - assert(!InvalidBuffer && "got an invalid buffer for the location's file"); + Optional<MemoryBufferRef> Buf = SM.getBufferOrNone(LocInfo.first); + assert(Buf && "got an invalid buffer for the location's file"); assert(Buf->getBufferSize() >= (LocInfo.second + TokenLen) && "token extends past end of buffer?"); diff --git a/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp b/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp index 3709106ad44c..f96974f97dcc 100644 --- a/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp @@ -57,7 +57,7 @@ ProgramStateRef SimpleConstraintManager::assumeAux(ProgramStateRef State, // SymIntExprs. if (!canReasonAbout(Cond)) { // Just add the constraint to the expression without trying to simplify. - SymbolRef Sym = Cond.getAsSymExpr(); + SymbolRef Sym = Cond.getAsSymbol(); assert(Sym); return assumeSymUnsupported(State, Sym, Assumption); } @@ -101,7 +101,7 @@ ProgramStateRef SimpleConstraintManager::assumeInclusiveRange( if (!canReasonAbout(Value)) { // Just add the constraint to the expression without trying to simplify. - SymbolRef Sym = Value.getAsSymExpr(); + SymbolRef Sym = Value.getAsSymbol(); assert(Sym); return assumeSymInclusiveRange(State, Sym, From, To, InRange); } diff --git a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp index 2e269f6a596e..facadaf1225f 100644 --- a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp @@ -86,7 +86,7 @@ SVal SimpleSValBuilder::evalCastFromNonLoc(NonLoc val, QualType castTy) { return makeLocAsInteger(LI->getLoc(), castSize); } - if (const SymExpr *se = val.getAsSymbolicExpression()) { + if (SymbolRef se = val.getAsSymbol()) { QualType T = Context.getCanonicalType(se->getType()); // If types are the same or both are integers, ignore the cast. // FIXME: Remove this hack when we support symbolic truncation/extension. @@ -1106,19 +1106,28 @@ SVal SimpleSValBuilder::evalBinOpLL(ProgramStateRef state, } SVal SimpleSValBuilder::evalBinOpLN(ProgramStateRef state, - BinaryOperator::Opcode op, - Loc lhs, NonLoc rhs, QualType resultTy) { + BinaryOperator::Opcode op, Loc lhs, + NonLoc rhs, QualType resultTy) { if (op >= BO_PtrMemD && op <= BO_PtrMemI) { if (auto PTMSV = rhs.getAs<nonloc::PointerToMember>()) { if (PTMSV->isNullMemberPointer()) return UndefinedVal(); - if (const FieldDecl *FD = PTMSV->getDeclAs<FieldDecl>()) { + + auto getFieldLValue = [&](const auto *FD) -> SVal { SVal Result = lhs; for (const auto &I : *PTMSV) Result = StateMgr.getStoreManager().evalDerivedToBase( - Result, I->getType(),I->isVirtual()); + Result, I->getType(), I->isVirtual()); + return state->getLValue(FD, Result); + }; + + if (const auto *FD = PTMSV->getDeclAs<FieldDecl>()) { + return getFieldLValue(FD); + } + if (const auto *FD = PTMSV->getDeclAs<IndirectFieldDecl>()) { + return getFieldLValue(FD); } } diff --git a/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp b/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp index 6ca7aec9caec..79a8eef30576 100644 --- a/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp @@ -14,6 +14,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Expr.h" +#include "clang/AST/StmtObjC.h" #include "clang/Analysis/Analyses/LiveVariables.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Basic/LLVM.h" @@ -34,6 +35,12 @@ using namespace ento; void SymExpr::anchor() {} +StringRef SymbolConjured::getKindStr() const { return "conj_$"; } +StringRef SymbolDerived::getKindStr() const { return "derived_$"; } +StringRef SymbolExtent::getKindStr() const { return "extent_$"; } +StringRef SymbolMetadata::getKindStr() const { return "meta_$"; } +StringRef SymbolRegionValue::getKindStr() const { return "reg_$"; } + LLVM_DUMP_METHOD void SymExpr::dump() const { dumpToStream(llvm::errs()); } void BinarySymExpr::dumpToStreamImpl(raw_ostream &OS, const SymExpr *Sym) { @@ -64,7 +71,7 @@ void SymbolCast::dumpToStream(raw_ostream &os) const { } void SymbolConjured::dumpToStream(raw_ostream &os) const { - os << "conj_$" << getSymbolID() << '{' << T.getAsString() << ", LC" + os << getKindStr() << getSymbolID() << '{' << T.getAsString() << ", LC" << LCtx->getID(); if (S) os << ", S" << S->getID(LCtx->getDecl()->getASTContext()); @@ -74,24 +81,24 @@ void SymbolConjured::dumpToStream(raw_ostream &os) const { } void SymbolDerived::dumpToStream(raw_ostream &os) const { - os << "derived_$" << getSymbolID() << '{' - << getParentSymbol() << ',' << getRegion() << '}'; + os << getKindStr() << getSymbolID() << '{' << getParentSymbol() << ',' + << getRegion() << '}'; } void SymbolExtent::dumpToStream(raw_ostream &os) const { - os << "extent_$" << getSymbolID() << '{' << getRegion() << '}'; + os << getKindStr() << getSymbolID() << '{' << getRegion() << '}'; } void SymbolMetadata::dumpToStream(raw_ostream &os) const { - os << "meta_$" << getSymbolID() << '{' - << getRegion() << ',' << T.getAsString() << '}'; + os << getKindStr() << getSymbolID() << '{' << getRegion() << ',' + << T.getAsString() << '}'; } void SymbolData::anchor() {} void SymbolRegionValue::dumpToStream(raw_ostream &os) const { - os << "reg_$" << getSymbolID() - << '<' << getType().getAsString() << ' ' << R << '>'; + os << getKindStr() << getSymbolID() << '<' << getType().getAsString() << ' ' + << R << '>'; } bool SymExpr::symbol_iterator::operator==(const symbol_iterator &X) const { @@ -482,7 +489,7 @@ bool SymbolReaper::isLive(SymbolRef sym) { } bool -SymbolReaper::isLive(const Stmt *ExprVal, const LocationContext *ELCtx) const { +SymbolReaper::isLive(const Expr *ExprVal, const LocationContext *ELCtx) const { if (LCtx == nullptr) return false; @@ -494,7 +501,8 @@ SymbolReaper::isLive(const Stmt *ExprVal, const LocationContext *ELCtx) const { return true; } - // If no statement is provided, everything is this and parent contexts is live. + // If no statement is provided, everything in this and parent contexts is + // live. if (!Loc) return true; diff --git a/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp index f4c7e5978e19..ae2bad7ee77c 100644 --- a/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp @@ -34,20 +34,17 @@ namespace { /// 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 { + PathDiagnosticConsumerOptions DiagOpts; DiagnosticsEngine &DiagEng; const LangOptions &LO; - const bool IncludePath = false; - const bool ShouldEmitAsError = false; - const bool ApplyFixIts = false; - const bool ShouldDisplayCheckerName = false; + bool ShouldDisplayPathNotes; 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(PathDiagnosticConsumerOptions DiagOpts, + DiagnosticsEngine &DiagEng, const LangOptions &LO, + bool ShouldDisplayPathNotes) + : DiagOpts(std::move(DiagOpts)), DiagEng(DiagEng), LO(LO), + ShouldDisplayPathNotes(ShouldDisplayPathNotes) {} ~TextDiagnostics() override {} StringRef getName() const override { return "TextDiagnostics"; } @@ -56,13 +53,13 @@ public: bool supportsCrossFileDiagnostics() const override { return true; } PathGenerationScheme getGenerationScheme() const override { - return IncludePath ? Minimal : None; + return ShouldDisplayPathNotes ? Minimal : None; } void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, FilesMade *filesMade) override { unsigned WarnID = - ShouldEmitAsError + DiagOpts.ShouldDisplayWarningsAsErrors ? DiagEng.getCustomDiagID(DiagnosticsEngine::Error, "%0") : DiagEng.getCustomDiagID(DiagnosticsEngine::Warning, "%0"); unsigned NoteID = DiagEng.getCustomDiagID(DiagnosticsEngine::Note, "%0"); @@ -72,7 +69,7 @@ public: auto reportPiece = [&](unsigned ID, FullSourceLoc Loc, StringRef String, ArrayRef<SourceRange> Ranges, ArrayRef<FixItHint> Fixits) { - if (!ApplyFixIts) { + if (!DiagOpts.ShouldApplyFixIts) { DiagEng.Report(Loc, ID) << String << Ranges << Fixits; return; } @@ -92,9 +89,10 @@ public: E = Diags.end(); I != E; ++I) { const PathDiagnostic *PD = *I; - std::string WarningMsg = - (ShouldDisplayCheckerName ? " [" + PD->getCheckerName() + "]" : "") - .str(); + std::string WarningMsg = (DiagOpts.ShouldDisplayDiagnosticName + ? " [" + PD->getCheckerName() + "]" + : "") + .str(); reportPiece(WarnID, PD->getLocation().asLocation(), (PD->getShortDescription() + WarningMsg).str(), @@ -110,7 +108,7 @@ public: Piece->getFixits()); } - if (!IncludePath) + if (!ShouldDisplayPathNotes) continue; // Then, add the path notes if necessary. @@ -125,7 +123,7 @@ public: } } - if (!ApplyFixIts || Repls.empty()) + if (Repls.empty()) return; Rewriter Rewrite(SM, LO); @@ -139,18 +137,19 @@ public: } // end anonymous namespace void ento::createTextPathDiagnosticConsumer( - AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + PathDiagnosticConsumerOptions DiagOpts, 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)); + C.emplace_back(new TextDiagnostics(std::move(DiagOpts), PP.getDiagnostics(), + PP.getLangOpts(), + /*ShouldDisplayPathNotes=*/true)); } void ento::createTextMinimalPathDiagnosticConsumer( - AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + PathDiagnosticConsumerOptions DiagOpts, 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)); + C.emplace_back(new TextDiagnostics(std::move(DiagOpts), PP.getDiagnostics(), + PP.getLangOpts(), + /*ShouldDisplayPathNotes=*/false)); } |
