diff options
Diffstat (limited to 'lib/StaticAnalyzer')
116 files changed, 10343 insertions, 6201 deletions
diff --git a/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.h b/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.h index 048418ef62db..62b7fab0739a 100644 --- a/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.h +++ b/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.h @@ -18,7 +18,7 @@ namespace clang { namespace ento { -/// \brief Returns true if leak diagnostics should directly reference +/// Returns true if leak diagnostics should directly reference /// the allocatin site (where possible). /// /// The default is false. diff --git a/lib/StaticAnalyzer/Checkers/AllocationState.h b/lib/StaticAnalyzer/Checkers/AllocationState.h new file mode 100644 index 000000000000..a6908bd7a651 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/AllocationState.h @@ -0,0 +1,34 @@ +//===--- AllocationState.h ------------------------------------- *- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ALLOCATIONSTATE_H +#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ALLOCATIONSTATE_H + +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" + +namespace clang { +namespace ento { + +namespace allocation_state { + +ProgramStateRef markReleased(ProgramStateRef State, SymbolRef Sym, + const Expr *Origin); + +/// This function provides an additional visitor that augments the bug report +/// with information relevant to memory errors caused by the misuse of +/// AF_InnerBuffer symbols. +std::unique_ptr<BugReporterVisitor> getInnerPointerBRVisitor(SymbolRef Sym); + +} // end namespace allocation_state + +} // end namespace ento +} // end namespace clang + +#endif diff --git a/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp b/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp index 90d5c0e36a47..e4cdc500de6a 100644 --- a/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp @@ -15,8 +15,10 @@ //===----------------------------------------------------------------------===// #include "ClangSACheckers.h" +#include "clang/AST/ExprCXX.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" using namespace clang; @@ -29,8 +31,17 @@ class AnalysisOrderChecker check::PostStmt<CastExpr>, check::PreStmt<ArraySubscriptExpr>, check::PostStmt<ArraySubscriptExpr>, + check::PreStmt<CXXNewExpr>, + check::PostStmt<CXXNewExpr>, + check::PreStmt<OffsetOfExpr>, + check::PostStmt<OffsetOfExpr>, + check::PreCall, + check::PostCall, + check::NewAllocator, check::Bind, - check::RegionChanges> { + check::RegionChanges, + check::LiveSymbols> { + bool isCallbackEnabled(AnalyzerOptions &Opts, StringRef CallbackName) const { return Opts.getBooleanOption("*", false, this) || Opts.getBooleanOption(CallbackName, false, this); @@ -72,11 +83,60 @@ public: llvm::errs() << "PostStmt<ArraySubscriptExpr>\n"; } + void checkPreStmt(const CXXNewExpr *NE, CheckerContext &C) const { + if (isCallbackEnabled(C, "PreStmtCXXNewExpr")) + llvm::errs() << "PreStmt<CXXNewExpr>\n"; + } + + void checkPostStmt(const CXXNewExpr *NE, CheckerContext &C) const { + if (isCallbackEnabled(C, "PostStmtCXXNewExpr")) + llvm::errs() << "PostStmt<CXXNewExpr>\n"; + } + + void checkPreStmt(const OffsetOfExpr *OOE, CheckerContext &C) const { + if (isCallbackEnabled(C, "PreStmtOffsetOfExpr")) + llvm::errs() << "PreStmt<OffsetOfExpr>\n"; + } + + void checkPostStmt(const OffsetOfExpr *OOE, CheckerContext &C) const { + if (isCallbackEnabled(C, "PostStmtOffsetOfExpr")) + llvm::errs() << "PostStmt<OffsetOfExpr>\n"; + } + + void checkPreCall(const CallEvent &Call, CheckerContext &C) const { + if (isCallbackEnabled(C, "PreCall")) { + llvm::errs() << "PreCall"; + if (const NamedDecl *ND = dyn_cast_or_null<NamedDecl>(Call.getDecl())) + llvm::errs() << " (" << ND->getQualifiedNameAsString() << ')'; + llvm::errs() << '\n'; + } + } + + void checkPostCall(const CallEvent &Call, CheckerContext &C) const { + if (isCallbackEnabled(C, "PostCall")) { + llvm::errs() << "PostCall"; + if (const NamedDecl *ND = dyn_cast_or_null<NamedDecl>(Call.getDecl())) + llvm::errs() << " (" << ND->getQualifiedNameAsString() << ')'; + llvm::errs() << '\n'; + } + } + + void checkNewAllocator(const CXXNewExpr *CNE, SVal Target, + CheckerContext &C) const { + if (isCallbackEnabled(C, "NewAllocator")) + llvm::errs() << "NewAllocator\n"; + } + void checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &C) const { if (isCallbackEnabled(C, "Bind")) llvm::errs() << "Bind\n"; } + void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SymReaper) const { + if (isCallbackEnabled(State, "LiveSymbols")) + llvm::errs() << "LiveSymbols\n"; + } + ProgramStateRef checkRegionChanges(ProgramStateRef State, const InvalidatedSymbols *Invalidated, diff --git a/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp b/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp index 64c30e7a82c1..aadc6bac8d00 100644 --- a/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp @@ -122,6 +122,8 @@ void AnalyzerStatsChecker::checkEndAnalysis(ExplodedGraph &G, E = CE.blocks_exhausted_end(); I != E; ++I) { const BlockEdge &BE = I->first; const CFGBlock *Exit = BE.getDst(); + if (Exit->empty()) + continue; const CFGElement &CE = Exit->front(); if (Optional<CFGStmt> CS = CE.getAs<CFGStmt>()) { SmallString<128> bufI; diff --git a/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp b/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp index b944f90539d4..933380d494a4 100644 --- a/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp +++ b/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp @@ -33,8 +33,8 @@ class ArrayBoundCheckerV2 : enum OOB_Kind { OOB_Precedes, OOB_Excedes, OOB_Tainted }; - void reportOOB(CheckerContext &C, ProgramStateRef errorState, - OOB_Kind kind) const; + void reportOOB(CheckerContext &C, ProgramStateRef errorState, OOB_Kind kind, + std::unique_ptr<BugReporterVisitor> Visitor = nullptr) const; public: void checkLocation(SVal l, bool isLoad, const Stmt*S, @@ -125,7 +125,6 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, // have some flexibility in defining the base region, we can achieve // various levels of conservatism in our buffer overflow checking. ProgramStateRef state = checkerContext.getState(); - ProgramStateRef originalState = state; SValBuilder &svalBuilder = checkerContext.getSValBuilder(); const RegionRawOffsetV2 &rawOffset = @@ -205,8 +204,10 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, // If we are under constrained and the index variables are tainted, report. if (state_exceedsUpperBound && state_withinUpperBound) { - if (state->isTainted(rawOffset.getByteOffset())) { - reportOOB(checkerContext, state_exceedsUpperBound, OOB_Tainted); + SVal ByteOffset = rawOffset.getByteOffset(); + if (state->isTainted(ByteOffset)) { + reportOOB(checkerContext, state_exceedsUpperBound, OOB_Tainted, + llvm::make_unique<TaintBugVisitor>(ByteOffset)); return; } } else if (state_exceedsUpperBound) { @@ -222,13 +223,12 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, } while (false); - if (state != originalState) - checkerContext.addTransition(state); + checkerContext.addTransition(state); } -void ArrayBoundCheckerV2::reportOOB(CheckerContext &checkerContext, - ProgramStateRef errorState, - OOB_Kind kind) const { +void ArrayBoundCheckerV2::reportOOB( + CheckerContext &checkerContext, ProgramStateRef errorState, OOB_Kind kind, + std::unique_ptr<BugReporterVisitor> Visitor) const { ExplodedNode *errorNode = checkerContext.generateErrorNode(errorState); if (!errorNode) @@ -255,8 +255,9 @@ void ArrayBoundCheckerV2::reportOOB(CheckerContext &checkerContext, break; } - checkerContext.emitReport( - llvm::make_unique<BugReport>(*BT, os.str(), errorNode)); + auto BR = llvm::make_unique<BugReport>(*BT, os.str(), errorNode); + BR->addVisitor(std::move(Visitor)); + checkerContext.emitReport(std::move(BR)); } #ifndef NDEBUG diff --git a/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp b/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp index 371187747f03..7d6358acbbac 100644 --- a/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp +++ b/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp @@ -436,8 +436,7 @@ void CFNumberChecker::checkPreStmt(const CallExpr *CE, return; // Get the value of the "theType" argument. - const LocationContext *LCtx = C.getLocationContext(); - SVal TheTypeVal = state->getSVal(CE->getArg(1), LCtx); + SVal TheTypeVal = C.getSVal(CE->getArg(1)); // FIXME: We really should allow ranges of valid theType values, and // bifurcate the state appropriately. @@ -457,7 +456,7 @@ void CFNumberChecker::checkPreStmt(const CallExpr *CE, // Look at the value of the integer being passed by reference. Essentially // we want to catch cases where the value passed in is not equal to the // size of the type being created. - SVal TheValueExpr = state->getSVal(CE->getArg(2), LCtx); + SVal TheValueExpr = C.getSVal(CE->getArg(2)); // FIXME: Eventually we should handle arbitrary locations. We can do this // by having an enhanced memory model that does low-level typing. @@ -571,7 +570,7 @@ void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE, // Get the argument's value. const Expr *Arg = CE->getArg(0); - SVal ArgVal = state->getSVal(Arg, C.getLocationContext()); + SVal ArgVal = C.getSVal(Arg); Optional<DefinedSVal> DefArgVal = ArgVal.getAs<DefinedSVal>(); if (!DefArgVal) return; @@ -977,8 +976,7 @@ assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State, if (!State) return nullptr; - SymbolRef CollectionS = - State->getSVal(FCS->getCollection(), C.getLocationContext()).getAsSymbol(); + SymbolRef CollectionS = C.getSVal(FCS->getCollection()).getAsSymbol(); return assumeCollectionNonEmpty(C, State, CollectionS, Assumption); } @@ -1166,7 +1164,7 @@ void ObjCLoopChecker::checkDeadSymbols(SymbolReaper &SymReaper, namespace { /// \class ObjCNonNilReturnValueChecker -/// \brief The checker restricts the return values of APIs known to +/// The checker restricts the return values of APIs known to /// never (or almost never) return 'nil'. class ObjCNonNilReturnValueChecker : public Checker<check::PostObjCMessage, @@ -1206,7 +1204,7 @@ ProgramStateRef ObjCNonNilReturnValueChecker::assumeExprIsNonNull(const Expr *NonNullExpr, ProgramStateRef State, CheckerContext &C) const { - SVal Val = State->getSVal(NonNullExpr, C.getLocationContext()); + SVal Val = C.getSVal(NonNullExpr); if (Optional<DefinedOrUnknownSVal> DV = Val.getAs<DefinedOrUnknownSVal>()) return State->assume(*DV, true); return State; diff --git a/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp b/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp index 097d4198800d..0e781d08e24c 100644 --- a/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp @@ -43,7 +43,7 @@ bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, case Builtin::BI__builtin_assume: { assert (CE->arg_begin() != CE->arg_end()); - SVal ArgSVal = state->getSVal(CE->getArg(0), LCtx); + SVal ArgSVal = C.getSVal(CE->getArg(0)); if (ArgSVal.isUndef()) return true; // Return true to model purity. @@ -68,7 +68,7 @@ bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, // __builtin_addressof is going from a reference to a pointer, but those // are represented the same way in the analyzer. assert (CE->arg_begin() != CE->arg_end()); - SVal X = state->getSVal(*(CE->arg_begin()), LCtx); + SVal X = C.getSVal(*(CE->arg_begin())); C.addTransition(state->BindExpr(CE, LCtx, X)); return true; } @@ -83,8 +83,7 @@ bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, // Set the extent of the region in bytes. This enables us to use the // SVal of the argument directly. If we save the extent in bits, we // cannot represent values like symbol*8. - DefinedOrUnknownSVal Size = - state->getSVal(*(CE->arg_begin()), LCtx).castAs<DefinedOrUnknownSVal>(); + auto Size = C.getSVal(*(CE->arg_begin())).castAs<DefinedOrUnknownSVal>(); SValBuilder& svalBuilder = C.getSValBuilder(); DefinedOrUnknownSVal Extent = R->getExtent(svalBuilder); @@ -97,7 +96,8 @@ bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, return true; } - case Builtin::BI__builtin_object_size: { + case Builtin::BI__builtin_object_size: + case Builtin::BI__builtin_constant_p: { // This must be resolvable at compile time, so we defer to the constant // evaluator for a value. SVal V = UnknownVal(); diff --git a/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 7ab9c6114eae..5bb4770b5675 100644 --- a/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -37,9 +37,11 @@ add_clang_library(clangStaticAnalyzerCheckers DynamicTypeChecker.cpp ExprInspectionChecker.cpp FixedAddressChecker.cpp + GCDAntipatternChecker.cpp GenericTaintChecker.cpp GTestChecker.cpp IdenticalExprChecker.cpp + InnerPointerChecker.cpp IteratorChecker.cpp IvarInvalidationChecker.cpp LLVMConventionsChecker.cpp @@ -49,6 +51,7 @@ add_clang_library(clangStaticAnalyzerCheckers MallocChecker.cpp MallocOverflowSecurityChecker.cpp MallocSizeofChecker.cpp + MmapWriteExecChecker.cpp MisusedMovedObjectChecker.cpp MPI-Checker/MPIBugReporter.cpp MPI-Checker/MPIChecker.cpp @@ -61,6 +64,7 @@ add_clang_library(clangStaticAnalyzerCheckers NullabilityChecker.cpp NumberObjectConversionChecker.cpp ObjCAtSyncChecker.cpp + ObjCAutoreleaseWriteChecker.cpp ObjCContainersASTChecker.cpp ObjCContainersChecker.cpp ObjCMissingSuperCallChecker.cpp @@ -75,6 +79,7 @@ add_clang_library(clangStaticAnalyzerCheckers RetainCountChecker.cpp ReturnPointerRangeChecker.cpp ReturnUndefChecker.cpp + RunLoopAutoreleaseLeakChecker.cpp SimpleStreamChecker.cpp StackAddrEscapeChecker.cpp StdLibraryFunctionsChecker.cpp @@ -82,11 +87,13 @@ add_clang_library(clangStaticAnalyzerCheckers TaintTesterChecker.cpp TestAfterDivZeroChecker.cpp TraversalChecker.cpp + TrustNonnullChecker.cpp UndefBranchChecker.cpp UndefCapturedBlockVarChecker.cpp UndefResultChecker.cpp UndefinedArraySubscriptChecker.cpp UndefinedAssignmentChecker.cpp + UninitializedObjectChecker.cpp UnixAPIChecker.cpp UnreachableCodeChecker.cpp VforkChecker.cpp diff --git a/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index 28ad7e9e5071..278452ec994a 100644 --- a/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -97,14 +97,17 @@ public: void evalStrcpy(CheckerContext &C, const CallExpr *CE) const; void evalStrncpy(CheckerContext &C, const CallExpr *CE) const; void evalStpcpy(CheckerContext &C, const CallExpr *CE) const; + void evalStrlcpy(CheckerContext &C, const CallExpr *CE) const; void evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, bool returnEnd, bool isBounded, - bool isAppending) const; + bool isAppending, + bool returnPtr = true) const; void evalStrcat(CheckerContext &C, const CallExpr *CE) const; void evalStrncat(CheckerContext &C, const CallExpr *CE) const; + void evalStrlcat(CheckerContext &C, const CallExpr *CE) const; void evalStrcmp(CheckerContext &C, const CallExpr *CE) const; void evalStrncmp(CheckerContext &C, const CallExpr *CE) const; @@ -155,6 +158,10 @@ public: static bool SummarizeRegion(raw_ostream &os, ASTContext &Ctx, const MemRegion *MR); + static bool memsetAux(const Expr *DstBuffer, const Expr *CharE, + const Expr *Size, CheckerContext &C, + ProgramStateRef &State); + // Re-usable checks ProgramStateRef checkNonNull(CheckerContext &C, ProgramStateRef state, @@ -194,6 +201,14 @@ public: const Stmt *First, const Stmt *Second) const; + void emitNullArgBug(CheckerContext &C, ProgramStateRef State, const Stmt *S, + StringRef WarningMsg) const; + void emitOutOfBoundsBug(CheckerContext &C, ProgramStateRef State, + const Stmt *S, StringRef WarningMsg) const; + void emitNotCStringBug(CheckerContext &C, ProgramStateRef State, + const Stmt *S, StringRef WarningMsg) const; + void emitAdditionOverflowBug(CheckerContext &C, ProgramStateRef State) const; + ProgramStateRef checkAdditionOverflow(CheckerContext &C, ProgramStateRef state, NonLoc left, @@ -239,30 +254,14 @@ ProgramStateRef CStringChecker::checkNonNull(CheckerContext &C, std::tie(stateNull, stateNonNull) = assumeZero(C, state, l, S->getType()); if (stateNull && !stateNonNull) { - if (!Filter.CheckCStringNullArg) - return nullptr; - - ExplodedNode *N = C.generateErrorNode(stateNull); - if (!N) - return nullptr; - - if (!BT_Null) - BT_Null.reset(new BuiltinBug( - Filter.CheckNameCStringNullArg, categories::UnixAPI, - "Null pointer argument in call to byte string function")); - - SmallString<80> buf; - llvm::raw_svector_ostream os(buf); - assert(CurrentFunctionDescription); - os << "Null pointer argument in call to " << CurrentFunctionDescription; - - // Generate a report for this bug. - BuiltinBug *BT = static_cast<BuiltinBug*>(BT_Null.get()); - auto report = llvm::make_unique<BugReport>(*BT, os.str(), N); + if (Filter.CheckCStringNullArg) { + SmallString<80> buf; + llvm::raw_svector_ostream os(buf); + assert(CurrentFunctionDescription); + os << "Null pointer argument in call to " << CurrentFunctionDescription; - report->addRange(S->getSourceRange()); - bugreporter::trackNullOrUndefValue(N, S, *report); - C.emitReport(std::move(report)); + emitNullArgBug(C, stateNull, S, os.str()); + } return nullptr; } @@ -305,21 +304,14 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C, ProgramStateRef StInBound = state->assumeInBound(Idx, Size, true); ProgramStateRef StOutBound = state->assumeInBound(Idx, Size, false); if (StOutBound && !StInBound) { - ExplodedNode *N = C.generateErrorNode(StOutBound); - if (!N) + // These checks are either enabled by the CString out-of-bounds checker + // explicitly or implicitly by the Malloc checker. + // In the latter case we only do modeling but do not emit warning. + if (!Filter.CheckCStringOutOfBounds) return nullptr; - - if (!BT_Bounds) { - BT_Bounds.reset(new BuiltinBug( - Filter.CheckNameCStringOutOfBounds, "Out-of-bound array access", - "Byte string function accesses out-of-bound array element")); - } - BuiltinBug *BT = static_cast<BuiltinBug*>(BT_Bounds.get()); - - // Generate a report for this bug. - std::unique_ptr<BugReport> report; + // Emit a bug report. if (warningMsg) { - report = llvm::make_unique<BugReport>(*BT, warningMsg, N); + emitOutOfBoundsBug(C, StOutBound, S, warningMsg); } else { assert(CurrentFunctionDescription); assert(CurrentFunctionDescription[0] != '\0'); @@ -329,15 +321,8 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C, os << toUppercase(CurrentFunctionDescription[0]) << &CurrentFunctionDescription[1] << " accesses out-of-bound array element"; - report = llvm::make_unique<BugReport>(*BT, os.str(), N); + emitOutOfBoundsBug(C, StOutBound, S, os.str()); } - - // FIXME: It would be nice to eventually make this diagnostic more clear, - // e.g., by referencing the original declaration or by saying *why* this - // reference is outside the range. - - report->addRange(S->getSourceRange()); - C.emitReport(std::move(report)); return nullptr; } @@ -366,7 +351,7 @@ ProgramStateRef CStringChecker::CheckBufferAccess(CheckerContext &C, QualType PtrTy = Ctx.getPointerType(Ctx.CharTy); // Check that the first buffer is non-null. - SVal BufVal = state->getSVal(FirstBuf, LCtx); + SVal BufVal = C.getSVal(FirstBuf); state = checkNonNull(C, state, FirstBuf, BufVal); if (!state) return nullptr; @@ -378,15 +363,17 @@ ProgramStateRef CStringChecker::CheckBufferAccess(CheckerContext &C, // Get the access length and make sure it is known. // FIXME: This assumes the caller has already checked that the access length // is positive. And that it's unsigned. - SVal LengthVal = state->getSVal(Size, LCtx); + SVal LengthVal = C.getSVal(Size); Optional<NonLoc> Length = LengthVal.getAs<NonLoc>(); if (!Length) return state; // Compute the offset of the last element to be accessed: size-1. NonLoc One = svalBuilder.makeIntVal(1, sizeTy).castAs<NonLoc>(); - NonLoc LastOffset = svalBuilder - .evalBinOpNN(state, BO_Sub, *Length, One, sizeTy).castAs<NonLoc>(); + SVal Offset = svalBuilder.evalBinOpNN(state, BO_Sub, *Length, One, sizeTy); + if (Offset.isUnknown()) + return nullptr; + NonLoc LastOffset = Offset.castAs<NonLoc>(); // Check that the first buffer is sufficiently long. SVal BufStart = svalBuilder.evalCast(BufVal, PtrTy, FirstBuf->getType()); @@ -555,6 +542,79 @@ void CStringChecker::emitOverlapBug(CheckerContext &C, ProgramStateRef state, C.emitReport(std::move(report)); } +void CStringChecker::emitNullArgBug(CheckerContext &C, ProgramStateRef State, + const Stmt *S, StringRef WarningMsg) const { + if (ExplodedNode *N = C.generateErrorNode(State)) { + if (!BT_Null) + BT_Null.reset(new BuiltinBug( + Filter.CheckNameCStringNullArg, categories::UnixAPI, + "Null pointer argument in call to byte string function")); + + BuiltinBug *BT = static_cast<BuiltinBug *>(BT_Null.get()); + auto Report = llvm::make_unique<BugReport>(*BT, WarningMsg, N); + bugreporter::trackNullOrUndefValue(N, S, *Report); + C.emitReport(std::move(Report)); + } +} + +void CStringChecker::emitOutOfBoundsBug(CheckerContext &C, + ProgramStateRef State, const Stmt *S, + StringRef WarningMsg) const { + if (ExplodedNode *N = C.generateErrorNode(State)) { + if (!BT_Bounds) + BT_Bounds.reset(new BuiltinBug( + Filter.CheckCStringOutOfBounds ? Filter.CheckNameCStringOutOfBounds + : Filter.CheckNameCStringNullArg, + "Out-of-bound array access", + "Byte string function accesses out-of-bound array element")); + + BuiltinBug *BT = static_cast<BuiltinBug *>(BT_Bounds.get()); + + // FIXME: It would be nice to eventually make this diagnostic more clear, + // e.g., by referencing the original declaration or by saying *why* this + // reference is outside the range. + auto Report = llvm::make_unique<BugReport>(*BT, WarningMsg, N); + Report->addRange(S->getSourceRange()); + C.emitReport(std::move(Report)); + } +} + +void CStringChecker::emitNotCStringBug(CheckerContext &C, ProgramStateRef State, + const Stmt *S, + StringRef WarningMsg) const { + if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { + if (!BT_NotCString) + BT_NotCString.reset(new BuiltinBug( + Filter.CheckNameCStringNotNullTerm, categories::UnixAPI, + "Argument is not a null-terminated string.")); + + auto Report = llvm::make_unique<BugReport>(*BT_NotCString, WarningMsg, N); + + Report->addRange(S->getSourceRange()); + C.emitReport(std::move(Report)); + } +} + +void CStringChecker::emitAdditionOverflowBug(CheckerContext &C, + ProgramStateRef State) const { + if (ExplodedNode *N = C.generateErrorNode(State)) { + if (!BT_NotCString) + BT_NotCString.reset( + new BuiltinBug(Filter.CheckNameCStringOutOfBounds, "API", + "Sum of expressions causes overflow.")); + + // This isn't a great error message, but this should never occur in real + // code anyway -- you'd have to create a buffer longer than a size_t can + // represent, which is sort of a contradiction. + const char *WarningMsg = + "This expression will create a string whose length is too big to " + "be represented as a size_t"; + + auto Report = llvm::make_unique<BugReport>(*BT_NotCString, WarningMsg, N); + C.emitReport(std::move(Report)); + } +} + ProgramStateRef CStringChecker::checkAdditionOverflow(CheckerContext &C, ProgramStateRef state, NonLoc left, @@ -598,26 +658,7 @@ ProgramStateRef CStringChecker::checkAdditionOverflow(CheckerContext &C, if (stateOverflow && !stateOkay) { // We have an overflow. Emit a bug report. - ExplodedNode *N = C.generateErrorNode(stateOverflow); - if (!N) - return nullptr; - - if (!BT_AdditionOverflow) - BT_AdditionOverflow.reset( - new BuiltinBug(Filter.CheckNameCStringOutOfBounds, "API", - "Sum of expressions causes overflow")); - - // This isn't a great error message, but this should never occur in real - // code anyway -- you'd have to create a buffer longer than a size_t can - // represent, which is sort of a contradiction. - const char *warning = - "This expression will create a string whose length is too big to " - "be represented as a size_t"; - - // Generate a report for this bug. - C.emitReport( - llvm::make_unique<BugReport>(*BT_AdditionOverflow, warning, N)); - + emitAdditionOverflowBug(C, stateOverflow); return nullptr; } @@ -717,15 +758,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, // C string. In the context of locations, the only time we can issue such // a warning is for labels. if (Optional<loc::GotoLabel> Label = Buf.getAs<loc::GotoLabel>()) { - if (!Filter.CheckCStringNotNullTerm) - return UndefinedVal(); - - if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) { - if (!BT_NotCString) - BT_NotCString.reset(new BuiltinBug( - Filter.CheckNameCStringNotNullTerm, categories::UnixAPI, - "Argument is not a null-terminated string.")); - + if (Filter.CheckCStringNotNullTerm) { SmallString<120> buf; llvm::raw_svector_ostream os(buf); assert(CurrentFunctionDescription); @@ -733,14 +766,9 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, << " is the address of the label '" << Label->getLabel()->getName() << "', which is not a null-terminated string"; - // Generate a report for this bug. - auto report = llvm::make_unique<BugReport>(*BT_NotCString, os.str(), N); - - report->addRange(Ex->getSourceRange()); - C.emitReport(std::move(report)); + emitNotCStringBug(C, state, Ex, os.str()); } return UndefinedVal(); - } // If it's not a region and not a label, give up. @@ -777,15 +805,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, // Other regions (mostly non-data) can't have a reliable C string length. // In this case, an error is emitted and UndefinedVal is returned. // The caller should always be prepared to handle this case. - if (!Filter.CheckCStringNotNullTerm) - return UndefinedVal(); - - if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) { - if (!BT_NotCString) - BT_NotCString.reset(new BuiltinBug( - Filter.CheckNameCStringNotNullTerm, categories::UnixAPI, - "Argument is not a null-terminated string.")); - + if (Filter.CheckCStringNotNullTerm) { SmallString<120> buf; llvm::raw_svector_ostream os(buf); @@ -797,13 +817,8 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, else os << "not a null-terminated string"; - // Generate a report for this bug. - auto report = llvm::make_unique<BugReport>(*BT_NotCString, os.str(), N); - - report->addRange(Ex->getSourceRange()); - C.emitReport(std::move(report)); + emitNotCStringBug(C, state, Ex, os.str()); } - return UndefinedVal(); } } @@ -852,9 +867,10 @@ bool CStringChecker::IsFirstBufInBound(CheckerContext &C, // Compute the offset of the last element to be accessed: size-1. NonLoc One = svalBuilder.makeIntVal(1, sizeTy).castAs<NonLoc>(); - NonLoc LastOffset = - svalBuilder.evalBinOpNN(state, BO_Sub, *Length, One, sizeTy) - .castAs<NonLoc>(); + SVal Offset = svalBuilder.evalBinOpNN(state, BO_Sub, *Length, One, sizeTy); + if (Offset.isUnknown()) + return true; // cf top comment + NonLoc LastOffset = Offset.castAs<NonLoc>(); // Check that the first buffer is sufficiently long. SVal BufStart = svalBuilder.evalCast(BufVal, PtrTy, FirstBuf->getType()); @@ -987,6 +1003,95 @@ bool CStringChecker::SummarizeRegion(raw_ostream &os, ASTContext &Ctx, } } +bool CStringChecker::memsetAux(const Expr *DstBuffer, const Expr *CharE, + const Expr *Size, CheckerContext &C, + ProgramStateRef &State) { + SVal MemVal = C.getSVal(DstBuffer); + SVal CharVal = C.getSVal(CharE); + SVal SizeVal = C.getSVal(Size); + const MemRegion *MR = MemVal.getAsRegion(); + if (!MR) + return false; + + // We're about to model memset by producing a "default binding" in the Store. + // Our current implementation - RegionStore - doesn't support default bindings + // that don't cover the whole base region. So we should first get the offset + // and the base region to figure out whether the offset of buffer is 0. + RegionOffset Offset = MR->getAsOffset(); + const MemRegion *BR = Offset.getRegion(); + + Optional<NonLoc> SizeNL = SizeVal.getAs<NonLoc>(); + if (!SizeNL) + return false; + + SValBuilder &svalBuilder = C.getSValBuilder(); + ASTContext &Ctx = C.getASTContext(); + + // void *memset(void *dest, int ch, size_t count); + // For now we can only handle the case of offset is 0 and concrete char value. + if (Offset.isValid() && !Offset.hasSymbolicOffset() && + Offset.getOffset() == 0) { + // Get the base region's extent. + auto *SubReg = cast<SubRegion>(BR); + DefinedOrUnknownSVal Extent = SubReg->getExtent(svalBuilder); + + ProgramStateRef StateWholeReg, StateNotWholeReg; + std::tie(StateWholeReg, StateNotWholeReg) = + State->assume(svalBuilder.evalEQ(State, Extent, *SizeNL)); + + // With the semantic of 'memset()', we should convert the CharVal to + // unsigned char. + CharVal = svalBuilder.evalCast(CharVal, Ctx.UnsignedCharTy, Ctx.IntTy); + + ProgramStateRef StateNullChar, StateNonNullChar; + std::tie(StateNullChar, StateNonNullChar) = + assumeZero(C, State, CharVal, Ctx.UnsignedCharTy); + + if (StateWholeReg && !StateNotWholeReg && StateNullChar && + !StateNonNullChar) { + // If the 'memset()' acts on the whole region of destination buffer and + // the value of the second argument of 'memset()' is zero, bind the second + // argument's value to the destination buffer with 'default binding'. + // FIXME: Since there is no perfect way to bind the non-zero character, we + // can only deal with zero value here. In the future, we need to deal with + // the binding of non-zero value in the case of whole region. + State = State->bindDefaultZero(svalBuilder.makeLoc(BR), + C.getLocationContext()); + } else { + // If the destination buffer's extent is not equal to the value of + // third argument, just invalidate buffer. + State = InvalidateBuffer(C, State, DstBuffer, MemVal, + /*IsSourceBuffer*/ false, Size); + } + + if (StateNullChar && !StateNonNullChar) { + // If the value of the second argument of 'memset()' is zero, set the + // string length of destination buffer to 0 directly. + State = setCStringLength(State, MR, + svalBuilder.makeZeroVal(Ctx.getSizeType())); + } else if (!StateNullChar && StateNonNullChar) { + SVal NewStrLen = svalBuilder.getMetadataSymbolVal( + CStringChecker::getTag(), MR, DstBuffer, Ctx.getSizeType(), + C.getLocationContext(), C.blockCount()); + + // If the value of second argument is not zero, then the string length + // is at least the size argument. + SVal NewStrLenGESize = svalBuilder.evalBinOp( + State, BO_GE, NewStrLen, SizeVal, svalBuilder.getConditionType()); + + State = setCStringLength( + State->assume(NewStrLenGESize.castAs<DefinedOrUnknownSVal>(), true), + MR, NewStrLen); + } + } else { + // If the offset is not zero and char value is not concrete, we can do + // nothing but invalidate the buffer. + State = InvalidateBuffer(C, State, DstBuffer, MemVal, + /*IsSourceBuffer*/ false, Size); + } + return true; +} + //===----------------------------------------------------------------------===// // evaluation of individual function calls. //===----------------------------------------------------------------------===// @@ -1384,6 +1489,18 @@ void CStringChecker::evalStpcpy(CheckerContext &C, const CallExpr *CE) const { /* isAppending = */ false); } +void CStringChecker::evalStrlcpy(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 3) + return; + + // char *strlcpy(char *dst, const char *src, size_t n); + evalStrcpyCommon(C, CE, + /* returnEnd = */ true, + /* isBounded = */ true, + /* isAppending = */ false, + /* returnPtr = */ false); +} + void CStringChecker::evalStrcat(CheckerContext &C, const CallExpr *CE) const { if (CE->getNumArgs() < 2) return; @@ -1406,9 +1523,21 @@ void CStringChecker::evalStrncat(CheckerContext &C, const CallExpr *CE) const { /* isAppending = */ true); } +void CStringChecker::evalStrlcat(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() < 3) + return; + + //char *strlcat(char *s1, const char *s2, size_t n); + evalStrcpyCommon(C, CE, + /* returnEnd = */ false, + /* isBounded = */ true, + /* isAppending = */ true, + /* returnPtr = */ false); +} + void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, bool returnEnd, bool isBounded, - bool isAppending) const { + bool isAppending, bool returnPtr) const { CurrentFunctionDescription = "string copy function"; ProgramStateRef state = C.getState(); const LocationContext *LCtx = C.getLocationContext(); @@ -1446,6 +1575,11 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, SVal maxLastElementIndex = UnknownVal(); const char *boundWarning = nullptr; + state = CheckOverlap(C, state, isBounded ? CE->getArg(2) : CE->getArg(1), Dst, srcExpr); + + if (!state) + return; + // If the function is strncpy, strncat, etc... it is bounded. if (isBounded) { // Get the max number of characters to copy. @@ -1518,7 +1652,11 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // If the size is known to be zero, we're done. if (StateZeroSize && !StateNonZeroSize) { - StateZeroSize = StateZeroSize->BindExpr(CE, LCtx, DstVal); + if (returnPtr) { + StateZeroSize = StateZeroSize->BindExpr(CE, LCtx, DstVal); + } else { + StateZeroSize = StateZeroSize->BindExpr(CE, LCtx, *lenValNL); + } C.addTransition(StateZeroSize); return; } @@ -1649,16 +1787,22 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, finalStrLength = amountCopied; } - // The final result of the function will either be a pointer past the last - // copied element, or a pointer to the start of the destination buffer. - SVal Result = (returnEnd ? UnknownVal() : DstVal); + SVal Result; + + if (returnPtr) { + // The final result of the function will either be a pointer past the last + // copied element, or a pointer to the start of the destination buffer. + Result = (returnEnd ? UnknownVal() : DstVal); + } else { + Result = finalStrLength; + } assert(state); // If the destination is a MemRegion, try to check for a buffer overflow and // record the new string length. if (Optional<loc::MemRegionVal> dstRegVal = - DstVal.getAs<loc::MemRegionVal>()) { + DstVal.getAs<loc::MemRegionVal>()) { QualType ptrTy = Dst->getType(); // If we have an exact value on a bounded copy, use that to check for @@ -1666,9 +1810,9 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, if (boundWarning) { if (Optional<NonLoc> maxLastNL = maxLastElementIndex.getAs<NonLoc>()) { SVal maxLastElement = svalBuilder.evalBinOpLN(state, BO_Add, *dstRegVal, - *maxLastNL, ptrTy); + *maxLastNL, ptrTy); state = CheckLocation(C, state, CE->getArg(2), maxLastElement, - boundWarning); + boundWarning); if (!state) return; } @@ -1677,7 +1821,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // Then, if the final length is known... if (Optional<NonLoc> knownStrLength = finalStrLength.getAs<NonLoc>()) { SVal lastElement = svalBuilder.evalBinOpLN(state, BO_Add, *dstRegVal, - *knownStrLength, ptrTy); + *knownStrLength, ptrTy); // ...and we haven't checked the bound, we'll check the actual copy. if (!boundWarning) { @@ -1689,7 +1833,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, } // If this is a stpcpy-style copy, the last element is the return value. - if (returnEnd) + if (returnPtr && returnEnd) Result = lastElement; } @@ -1701,12 +1845,12 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // This would probably remove any existing bindings past the end of the // string, but that's still an improvement over blank invalidation. state = InvalidateBuffer(C, state, Dst, *dstRegVal, - /*IsSourceBuffer*/false, nullptr); + /*IsSourceBuffer*/false, nullptr); // Invalidate the source (const-invalidation without const-pointer-escaping // the address of the top-level region). state = InvalidateBuffer(C, state, srcExpr, srcVal, /*IsSourceBuffer*/true, - nullptr); + nullptr); // Set the C string length of the destination, if we know it. if (isBounded && !isAppending) { @@ -1722,12 +1866,13 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, assert(state); - // If this is a stpcpy-style copy, but we were unable to check for a buffer - // overflow, we still need a result. Conjure a return value. - if (returnEnd && Result.isUnknown()) { - Result = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()); + if (returnPtr) { + // If this is a stpcpy-style copy, but we were unable to check for a buffer + // overflow, we still need a result. Conjure a return value. + if (returnEnd && Result.isUnknown()) { + Result = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()); + } } - // Set the return value. state = state->BindExpr(CE, LCtx, Result); C.addTransition(state); @@ -1750,7 +1895,7 @@ void CStringChecker::evalStrncmp(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStrcasecmp(CheckerContext &C, - const CallExpr *CE) const { + const CallExpr *CE) const { if (CE->getNumArgs() < 2) return; @@ -1759,7 +1904,7 @@ void CStringChecker::evalStrcasecmp(CheckerContext &C, } void CStringChecker::evalStrncasecmp(CheckerContext &C, - const CallExpr *CE) const { + const CallExpr *CE) const { if (CE->getNumArgs() < 3) return; @@ -1768,7 +1913,7 @@ void CStringChecker::evalStrncasecmp(CheckerContext &C, } void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, - bool isBounded, bool ignoreCase) const { + bool isBounded, bool ignoreCase) const { CurrentFunctionDescription = "string comparison function"; ProgramStateRef state = C.getState(); const LocationContext *LCtx = C.getLocationContext(); @@ -1813,7 +1958,7 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, // and we only need to check one size. if (StSameBuf) { StSameBuf = StSameBuf->BindExpr(CE, LCtx, - svalBuilder.makeZeroVal(CE->getType())); + svalBuilder.makeZeroVal(CE->getType())); C.addTransition(StSameBuf); // If the two arguments are GUARANTEED to be the same, we're done! @@ -1832,7 +1977,7 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, const StringLiteral *s2StrLiteral = getCStringLiteral(C, state, s2, s2Val); bool canComputeResult = false; SVal resultVal = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, - C.blockCount()); + C.blockCount()); if (s1StrLiteral && s2StrLiteral) { StringRef s1StrRef = s1StrLiteral->getString(); @@ -1867,7 +2012,7 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, // Use StringRef's comparison methods to compute the actual result. int compareRes = ignoreCase ? s1StrRef.compare_lower(s2StrRef) - : s1StrRef.compare(s2StrRef); + : s1StrRef.compare(s2StrRef); // The strcmp function returns an integer greater than, equal to, or less // than zero, [c11, p7.24.4.2]. @@ -1881,7 +2026,7 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, BinaryOperatorKind op = (compareRes == 1) ? BO_GT : BO_LT; SVal compareWithZero = svalBuilder.evalBinOp(state, op, resultVal, zeroVal, - svalBuilder.getConditionType()); + svalBuilder.getConditionType()); DefinedSVal compareWithZeroVal = compareWithZero.castAs<DefinedSVal>(); state = state->assume(compareWithZeroVal, true); } @@ -1933,17 +2078,17 @@ void CStringChecker::evalStrsep(CheckerContext &C, const CallExpr *CE) const { // Invalidate the search string, representing the change of one delimiter // character to NUL. State = InvalidateBuffer(C, State, SearchStrPtr, Result, - /*IsSourceBuffer*/false, nullptr); + /*IsSourceBuffer*/false, nullptr); // Overwrite the search string pointer. The new value is either an address // further along in the same string, or NULL if there are no more tokens. State = State->bindLoc(*SearchStrLoc, - SVB.conjureSymbolVal(getTag(), - CE, - LCtx, - CharPtrTy, - C.blockCount()), - LCtx); + SVB.conjureSymbolVal(getTag(), + CE, + LCtx, + CharPtrTy, + C.blockCount()), + LCtx); } else { assert(SearchStrVal.isUnknown()); // Conjure a symbolic value. It's the best we can do. @@ -1961,12 +2106,12 @@ void CStringChecker::evalStdCopy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStdCopyBackward(CheckerContext &C, - const CallExpr *CE) const { + const CallExpr *CE) const { evalStdCopyCommon(C, CE); } void CStringChecker::evalStdCopyCommon(CheckerContext &C, - const CallExpr *CE) const { + const CallExpr *CE) const { if (CE->getNumArgs() < 3) return; @@ -1983,7 +2128,7 @@ void CStringChecker::evalStdCopyCommon(CheckerContext &C, const Expr *Dst = CE->getArg(2); SVal DstVal = State->getSVal(Dst, LCtx); State = InvalidateBuffer(C, State, Dst, DstVal, /*IsSource=*/false, - /*Size=*/nullptr); + /*Size=*/nullptr); SValBuilder &SVB = C.getSValBuilder(); @@ -2000,6 +2145,7 @@ void CStringChecker::evalMemset(CheckerContext &C, const CallExpr *CE) const { CurrentFunctionDescription = "memory set function"; const Expr *Mem = CE->getArg(0); + const Expr *CharE = CE->getArg(1); const Expr *Size = CE->getArg(2); ProgramStateRef State = C.getState(); @@ -2032,9 +2178,11 @@ void CStringChecker::evalMemset(CheckerContext &C, const CallExpr *CE) const { State = CheckBufferAccess(C, State, Size, Mem); if (!State) return; - State = InvalidateBuffer(C, State, Mem, C.getSVal(Mem), - /*IsSourceBuffer*/false, Size); - if (!State) + + // According to the values of the arguments, bind the value of the second + // argument to the destination buffer and set string length, or just + // invalidate the destination buffer. + if (!memsetAux(Mem, CharE, Size, C, State)) return; State = State->BindExpr(CE, LCtx, MemVal); @@ -2082,10 +2230,14 @@ bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { evalFunction = &CStringChecker::evalStrncpy; else if (C.isCLibraryFunction(FDecl, "stpcpy")) evalFunction = &CStringChecker::evalStpcpy; + else if (C.isCLibraryFunction(FDecl, "strlcpy")) + evalFunction = &CStringChecker::evalStrlcpy; else if (C.isCLibraryFunction(FDecl, "strcat")) evalFunction = &CStringChecker::evalStrcat; else if (C.isCLibraryFunction(FDecl, "strncat")) evalFunction = &CStringChecker::evalStrncat; + else if (C.isCLibraryFunction(FDecl, "strlcat")) + evalFunction = &CStringChecker::evalStrlcat; else if (C.isCLibraryFunction(FDecl, "strlen")) evalFunction = &CStringChecker::evalstrLength; else if (C.isCLibraryFunction(FDecl, "strnlen")) @@ -2149,10 +2301,10 @@ void CStringChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { if (!MR) continue; - SVal StrVal = state->getSVal(Init, C.getLocationContext()); + SVal StrVal = C.getSVal(Init); assert(StrVal.isValid() && "Initializer string is unknown or undefined"); DefinedOrUnknownSVal strLength = - getCStringLength(C, state, Init, StrVal).castAs<DefinedOrUnknownSVal>(); + getCStringLength(C, state, Init, StrVal).castAs<DefinedOrUnknownSVal>(); state = state->set<CStringLength>(MR, strLength); } @@ -2162,11 +2314,11 @@ void CStringChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { ProgramStateRef CStringChecker::checkRegionChanges(ProgramStateRef state, - const InvalidatedSymbols *, - ArrayRef<const MemRegion *> ExplicitRegions, - ArrayRef<const MemRegion *> Regions, - const LocationContext *LCtx, - const CallEvent *Call) const { + const InvalidatedSymbols *, + ArrayRef<const MemRegion *> ExplicitRegions, + ArrayRef<const MemRegion *> Regions, + const LocationContext *LCtx, + const CallEvent *Call) const { CStringLengthTy Entries = state->get<CStringLength>(); if (Entries.isEmpty()) return state; @@ -2176,7 +2328,7 @@ CStringChecker::checkRegionChanges(ProgramStateRef state, // First build sets for the changed regions and their super-regions. for (ArrayRef<const MemRegion *>::iterator - I = Regions.begin(), E = Regions.end(); I != E; ++I) { + I = Regions.begin(), E = Regions.end(); I != E; ++I) { const MemRegion *MR = *I; Invalidated.insert(MR); @@ -2191,7 +2343,7 @@ CStringChecker::checkRegionChanges(ProgramStateRef state, // Then loop over the entries in the current state. for (CStringLengthTy::iterator I = Entries.begin(), - E = Entries.end(); I != E; ++I) { + E = Entries.end(); I != E; ++I) { const MemRegion *MR = I.getKey(); // Is this entry for a super-region of a changed region? @@ -2215,22 +2367,22 @@ CStringChecker::checkRegionChanges(ProgramStateRef state, } void CStringChecker::checkLiveSymbols(ProgramStateRef state, - SymbolReaper &SR) const { + SymbolReaper &SR) const { // Mark all symbols in our string length map as valid. CStringLengthTy Entries = state->get<CStringLength>(); for (CStringLengthTy::iterator I = Entries.begin(), E = Entries.end(); - I != E; ++I) { + I != E; ++I) { SVal Len = I.getData(); for (SymExpr::symbol_iterator si = Len.symbol_begin(), - se = Len.symbol_end(); si != se; ++si) + se = Len.symbol_end(); si != se; ++si) SR.markInUse(*si); } } void CStringChecker::checkDeadSymbols(SymbolReaper &SR, - CheckerContext &C) const { + CheckerContext &C) const { if (!SR.hasDeadSymbols()) return; @@ -2241,7 +2393,7 @@ void CStringChecker::checkDeadSymbols(SymbolReaper &SR, CStringLengthTy::Factory &F = state->get_context<CStringLength>(); for (CStringLengthTy::iterator I = Entries.begin(), E = Entries.end(); - I != E; ++I) { + I != E; ++I) { SVal Len = I.getData(); if (SymbolRef Sym = Len.getAsSymbol()) { if (SR.isDead(Sym)) @@ -2260,11 +2412,11 @@ void CStringChecker::checkDeadSymbols(SymbolReaper &SR, checker->Filter.CheckName##name = mgr.getCurrentCheckName(); \ } -REGISTER_CHECKER(CStringNullArg) -REGISTER_CHECKER(CStringOutOfBounds) -REGISTER_CHECKER(CStringBufferOverlap) + REGISTER_CHECKER(CStringNullArg) + REGISTER_CHECKER(CStringOutOfBounds) + REGISTER_CHECKER(CStringBufferOverlap) REGISTER_CHECKER(CStringNotNullTerm) -void ento::registerCStringCheckerBasic(CheckerManager &Mgr) { - registerCStringNullArg(Mgr); -} + void ento::registerCStringCheckerBasic(CheckerManager &Mgr) { + Mgr.registerChecker<CStringChecker>(); + } diff --git a/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp b/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp index 4b5e97b69295..8b4aa857e775 100644 --- a/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp @@ -80,6 +80,18 @@ class WalkAST: public StmtVisitor<WalkAST> { /// of bytes to copy. bool containsBadStrncatPattern(const CallExpr *CE); + /// Identify erroneous patterns in the last argument to strlcpy - the number + /// of bytes to copy. + /// The bad pattern checked is when the size is known + /// to be larger than the destination can handle. + /// char dst[2]; + /// size_t cpy = 4; + /// strlcpy(dst, "abcd", sizeof("abcd") - 1); + /// strlcpy(dst, "abcd", 4); + /// strlcpy(dst + 3, "abcd", 2); + /// strlcpy(dst, "abcd", cpy); + bool containsBadStrlcpyPattern(const CallExpr *CE); + public: WalkAST(const CheckerBase *Checker, BugReporter &BR, AnalysisDeclContext *AC) : Checker(Checker), BR(BR), AC(AC) {} @@ -130,6 +142,54 @@ bool WalkAST::containsBadStrncatPattern(const CallExpr *CE) { return false; } +bool WalkAST::containsBadStrlcpyPattern(const CallExpr *CE) { + if (CE->getNumArgs() != 3) + return false; + const Expr *DstArg = CE->getArg(0); + const Expr *LenArg = CE->getArg(2); + + const auto *DstArgDecl = dyn_cast<DeclRefExpr>(DstArg->IgnoreParenImpCasts()); + const auto *LenArgDecl = dyn_cast<DeclRefExpr>(LenArg->IgnoreParenLValueCasts()); + uint64_t DstOff = 0; + // - size_t dstlen = sizeof(dst) + if (LenArgDecl) { + const auto *LenArgVal = dyn_cast<VarDecl>(LenArgDecl->getDecl()); + if (LenArgVal->getInit()) + LenArg = LenArgVal->getInit(); + } + + // - integral value + // We try to figure out if the last argument is possibly longer + // than the destination can possibly handle if its size can be defined. + if (const auto *IL = dyn_cast<IntegerLiteral>(LenArg->IgnoreParenImpCasts())) { + uint64_t ILRawVal = IL->getValue().getZExtValue(); + + // Case when there is pointer arithmetic on the destination buffer + // especially when we offset from the base decreasing the + // buffer length accordingly. + if (!DstArgDecl) { + if (const auto *BE = dyn_cast<BinaryOperator>(DstArg->IgnoreParenImpCasts())) { + DstArgDecl = dyn_cast<DeclRefExpr>(BE->getLHS()->IgnoreParenImpCasts()); + if (BE->getOpcode() == BO_Add) { + if ((IL = dyn_cast<IntegerLiteral>(BE->getRHS()->IgnoreParenImpCasts()))) { + DstOff = IL->getValue().getZExtValue(); + } + } + } + } + if (DstArgDecl) { + if (const auto *Buffer = dyn_cast<ConstantArrayType>(DstArgDecl->getType())) { + ASTContext &C = BR.getContext(); + uint64_t BufferLen = C.getTypeSize(Buffer) / 8; + if ((BufferLen - DstOff) < ILRawVal) + return true; + } + } + } + + return false; +} + void WalkAST::VisitCallExpr(CallExpr *CE) { const FunctionDecl *FD = CE->getDirectCallee(); if (!FD) @@ -159,6 +219,25 @@ void WalkAST::VisitCallExpr(CallExpr *CE) { "C String API", os.str(), Loc, LenArg->getSourceRange()); } + } else if (CheckerContext::isCLibraryFunction(FD, "strlcpy")) { + if (containsBadStrlcpyPattern(CE)) { + const Expr *DstArg = CE->getArg(0); + const Expr *LenArg = CE->getArg(2); + PathDiagnosticLocation Loc = + PathDiagnosticLocation::createBegin(LenArg, BR.getSourceManager(), AC); + + StringRef DstName = getPrintableName(DstArg); + + SmallString<256> S; + llvm::raw_svector_ostream os(S); + os << "The third argument is larger than the size of the input buffer. "; + if (!DstName.empty()) + os << "Replace with the value 'sizeof(" << DstName << ")` or lower"; + + BR.EmitBasicReport(FD, Checker, "Anti-pattern in the argument", + "C String API", os.str(), Loc, + LenArg->getSourceRange()); + } } // Recurse and check children. diff --git a/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp b/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp index 668e772fe1b3..d1d37c75dfcc 100644 --- a/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp @@ -48,7 +48,7 @@ void CXXSelfAssignmentChecker::checkBeginFunction(CheckerContext &C) const { auto &State = C.getState(); auto &SVB = C.getSValBuilder(); auto ThisVal = - State->getSVal(SVB.getCXXThis(MD, LCtx->getCurrentStackFrame())); + State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); auto Param = SVB.makeLoc(State->getRegion(MD->getParamDecl(0), LCtx)); auto ParamVal = State->getSVal(Param); ProgramStateRef SelfAssignState = State->bindLoc(Param, ThisVal, LCtx); diff --git a/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp b/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp index 3e178152d925..059553b21995 100644 --- a/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp @@ -101,7 +101,7 @@ void CastSizeChecker::checkPreStmt(const CastExpr *CE,CheckerContext &C) const { return; ProgramStateRef state = C.getState(); - const MemRegion *R = state->getSVal(E, C.getLocationContext()).getAsRegion(); + const MemRegion *R = C.getSVal(E).getAsRegion(); if (!R) return; diff --git a/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp b/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp index 65e81315f095..00e903355720 100644 --- a/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp @@ -78,7 +78,7 @@ bool CastToStructVisitor::VisitCastExpr(const CastExpr *CE) { // Don't warn for references const ValueDecl *VD = nullptr; if (const auto *SE = dyn_cast<DeclRefExpr>(U->getSubExpr())) - VD = dyn_cast<ValueDecl>(SE->getDecl()); + VD = SE->getDecl(); else if (const auto *SE = dyn_cast<MemberExpr>(U->getSubExpr())) VD = SE->getMemberDecl(); if (!VD || VD->getType()->isReferenceType()) diff --git a/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp b/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp index 2818c9d9fd4a..f4d2e32cef11 100644 --- a/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp @@ -126,7 +126,7 @@ public: const CallEvent *Call, PointerEscapeKind Kind) const; void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const; - void checkEndFunction(CheckerContext &Ctx) const; + void checkEndFunction(const ReturnStmt *RS, CheckerContext &Ctx) const; private: void diagnoseMissingReleases(CheckerContext &C) const; @@ -398,7 +398,7 @@ void ObjCDeallocChecker::checkPostObjCMessage( /// Check for missing releases even when -dealloc does not call /// '[super dealloc]'. void ObjCDeallocChecker::checkEndFunction( - CheckerContext &C) const { + const ReturnStmt *RS, CheckerContext &C) const { diagnoseMissingReleases(C); } @@ -535,7 +535,7 @@ void ObjCDeallocChecker::diagnoseMissingReleases(CheckerContext &C) const { continue; // Prevents diagnosing multiple times for the same instance variable - // at, for example, both a return and at the end of of the function. + // at, for example, both a return and at the end of the function. NewUnreleased = F.remove(NewUnreleased, IvarSymbol); if (State->getStateManager() @@ -645,7 +645,7 @@ ObjCDeallocChecker::findPropertyOnDeallocatingInstance( bool ObjCDeallocChecker::diagnoseExtraRelease(SymbolRef ReleasedValue, const ObjCMethodCall &M, CheckerContext &C) const { - // Try to get the region from which the the released value was loaded. + // Try to get the region from which the released value was loaded. // Note that, unlike diagnosing for missing releases, here we don't track // values that must not be released in the state. This is because even if // these values escape, it is still an error under the rules of MRR to diff --git a/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp b/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp index 6dbacad7f2ea..202233acffab 100644 --- a/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp @@ -37,6 +37,9 @@ static bool isArc4RandomAvailable(const ASTContext &Ctx) { namespace { struct ChecksFilter { + DefaultBool check_bcmp; + DefaultBool check_bcopy; + DefaultBool check_bzero; DefaultBool check_gets; DefaultBool check_getpw; DefaultBool check_mktemp; @@ -47,6 +50,9 @@ struct ChecksFilter { DefaultBool check_FloatLoopCounter; DefaultBool check_UncheckedReturn; + CheckName checkName_bcmp; + CheckName checkName_bcopy; + CheckName checkName_bzero; CheckName checkName_gets; CheckName checkName_getpw; CheckName checkName_mktemp; @@ -89,6 +95,9 @@ public: // Checker-specific methods. void checkLoopConditionForFloat(const ForStmt *FS); + void checkCall_bcmp(const CallExpr *CE, const FunctionDecl *FD); + void checkCall_bcopy(const CallExpr *CE, const FunctionDecl *FD); + void checkCall_bzero(const CallExpr *CE, const FunctionDecl *FD); void checkCall_gets(const CallExpr *CE, const FunctionDecl *FD); void checkCall_getpw(const CallExpr *CE, const FunctionDecl *FD); void checkCall_mktemp(const CallExpr *CE, const FunctionDecl *FD); @@ -129,6 +138,9 @@ void WalkAST::VisitCallExpr(CallExpr *CE) { // Set the evaluation function by switching on the callee name. FnCheck evalFunction = llvm::StringSwitch<FnCheck>(Name) + .Case("bcmp", &WalkAST::checkCall_bcmp) + .Case("bcopy", &WalkAST::checkCall_bcopy) + .Case("bzero", &WalkAST::checkCall_bzero) .Case("gets", &WalkAST::checkCall_gets) .Case("getpw", &WalkAST::checkCall_getpw) .Case("mktemp", &WalkAST::checkCall_mktemp) @@ -296,6 +308,132 @@ void WalkAST::checkLoopConditionForFloat(const ForStmt *FS) { } //===----------------------------------------------------------------------===// +// Check: Any use of bcmp. +// CWE-477: Use of Obsolete Functions +// bcmp was deprecated in POSIX.1-2008 +//===----------------------------------------------------------------------===// + +void WalkAST::checkCall_bcmp(const CallExpr *CE, const FunctionDecl *FD) { + if (!filter.check_bcmp) + return; + + const FunctionProtoType *FPT = FD->getType()->getAs<FunctionProtoType>(); + if (!FPT) + return; + + // Verify that the function takes three arguments. + if (FPT->getNumParams() != 3) + return; + + for (int i = 0; i < 2; i++) { + // Verify the first and second argument type is void*. + const PointerType *PT = FPT->getParamType(i)->getAs<PointerType>(); + if (!PT) + return; + + if (PT->getPointeeType().getUnqualifiedType() != BR.getContext().VoidTy) + return; + } + + // Verify the third argument type is integer. + if (!FPT->getParamType(2)->isIntegralOrUnscopedEnumerationType()) + return; + + // Issue a warning. + PathDiagnosticLocation CELoc = + PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); + BR.EmitBasicReport(AC->getDecl(), filter.checkName_bcmp, + "Use of deprecated function in call to 'bcmp()'", + "Security", + "The bcmp() function is obsoleted by memcmp().", + CELoc, CE->getCallee()->getSourceRange()); +} + +//===----------------------------------------------------------------------===// +// Check: Any use of bcopy. +// CWE-477: Use of Obsolete Functions +// bcopy was deprecated in POSIX.1-2008 +//===----------------------------------------------------------------------===// + +void WalkAST::checkCall_bcopy(const CallExpr *CE, const FunctionDecl *FD) { + if (!filter.check_bcopy) + return; + + const FunctionProtoType *FPT = FD->getType()->getAs<FunctionProtoType>(); + if (!FPT) + return; + + // Verify that the function takes three arguments. + if (FPT->getNumParams() != 3) + return; + + for (int i = 0; i < 2; i++) { + // Verify the first and second argument type is void*. + const PointerType *PT = FPT->getParamType(i)->getAs<PointerType>(); + if (!PT) + return; + + if (PT->getPointeeType().getUnqualifiedType() != BR.getContext().VoidTy) + return; + } + + // Verify the third argument type is integer. + if (!FPT->getParamType(2)->isIntegralOrUnscopedEnumerationType()) + return; + + // Issue a warning. + PathDiagnosticLocation CELoc = + PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); + BR.EmitBasicReport(AC->getDecl(), filter.checkName_bcopy, + "Use of deprecated function in call to 'bcopy()'", + "Security", + "The bcopy() function is obsoleted by memcpy() " + "or memmove().", + CELoc, CE->getCallee()->getSourceRange()); +} + +//===----------------------------------------------------------------------===// +// Check: Any use of bzero. +// CWE-477: Use of Obsolete Functions +// bzero was deprecated in POSIX.1-2008 +//===----------------------------------------------------------------------===// + +void WalkAST::checkCall_bzero(const CallExpr *CE, const FunctionDecl *FD) { + if (!filter.check_bzero) + return; + + const FunctionProtoType *FPT = FD->getType()->getAs<FunctionProtoType>(); + if (!FPT) + return; + + // Verify that the function takes two arguments. + if (FPT->getNumParams() != 2) + return; + + // Verify the first argument type is void*. + const PointerType *PT = FPT->getParamType(0)->getAs<PointerType>(); + if (!PT) + return; + + if (PT->getPointeeType().getUnqualifiedType() != BR.getContext().VoidTy) + return; + + // Verify the second argument type is integer. + if (!FPT->getParamType(1)->isIntegralOrUnscopedEnumerationType()) + return; + + // Issue a warning. + PathDiagnosticLocation CELoc = + PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); + BR.EmitBasicReport(AC->getDecl(), filter.checkName_bzero, + "Use of deprecated function in call to 'bzero()'", + "Security", + "The bzero() function is obsoleted by memset().", + CELoc, CE->getCallee()->getSourceRange()); +} + + +//===----------------------------------------------------------------------===// // Check: Any use of 'gets' is insecure. // Originally: <rdar://problem/6335715> // Implements (part of): 300-BSI (buildsecurityin.us-cert.gov) @@ -510,6 +648,17 @@ void WalkAST::checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD) { if (!checkCall_strCommon(CE, FD)) return; + const auto *Target = CE->getArg(0)->IgnoreImpCasts(), + *Source = CE->getArg(1)->IgnoreImpCasts(); + if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Target)) + if (const auto *Array = dyn_cast<ConstantArrayType>(DeclRef->getType())) { + uint64_t ArraySize = BR.getContext().getTypeSize(Array) / 8; + if (const auto *String = dyn_cast<StringLiteral>(Source)) { + if (ArraySize >= String->getLength() + 1) + return; + } + } + // Issue a warning. PathDiagnosticLocation CELoc = PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); @@ -764,6 +913,9 @@ public: checker->filter.checkName_##name = mgr.getCurrentCheckName(); \ } +REGISTER_CHECKER(bcmp) +REGISTER_CHECKER(bcopy) +REGISTER_CHECKER(bzero) REGISTER_CHECKER(gets) REGISTER_CHECKER(getpw) REGISTER_CHECKER(mkstemp) diff --git a/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp b/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp index 95b6c4d3775d..7862a4c25681 100644 --- a/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp @@ -42,6 +42,7 @@ class CheckerDocumentation : public Checker< check::PreStmt<ReturnStmt>, check::PreCall, check::PostCall, check::BranchCondition, + check::NewAllocator, check::Location, check::Bind, check::DeadSymbols, @@ -58,7 +59,7 @@ class CheckerDocumentation : public Checker< check::PreStmt<ReturnStmt>, check::Event<ImplicitNullDerefEvent>, check::ASTDecl<FunctionDecl> > { public: - /// \brief Pre-visit the Statement. + /// Pre-visit the Statement. /// /// The method will be called before the analyzer core processes the /// statement. The notification is performed for every explored CFGElement, @@ -71,7 +72,7 @@ public: /// check::PreStmt<ReturnStmt> void checkPreStmt(const ReturnStmt *DS, CheckerContext &C) const {} - /// \brief Post-visit the Statement. + /// Post-visit the Statement. /// /// The method will be called after the analyzer core processes the /// statement. The notification is performed for every explored CFGElement, @@ -81,7 +82,7 @@ public: /// check::PostStmt<DeclStmt> void checkPostStmt(const DeclStmt *DS, CheckerContext &C) const; - /// \brief Pre-visit the Objective C message. + /// Pre-visit the Objective C message. /// /// This will be called before the analyzer core processes the method call. /// This is called for any action which produces an Objective-C message send, @@ -90,13 +91,13 @@ public: /// check::PreObjCMessage void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const {} - /// \brief Post-visit the Objective C message. + /// Post-visit the Objective C message. /// \sa checkPreObjCMessage() /// /// check::PostObjCMessage void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const {} - /// \brief Visit an Objective-C message whose receiver is nil. + /// Visit an Objective-C message whose receiver is nil. /// /// This will be called when the analyzer core processes a method call whose /// receiver is definitely nil. In this case, check{Pre/Post}ObjCMessage and @@ -105,7 +106,7 @@ public: /// check::ObjCMessageNil void checkObjCMessageNil(const ObjCMethodCall &M, CheckerContext &C) const {} - /// \brief Pre-visit an abstract "call" event. + /// Pre-visit an abstract "call" event. /// /// This is used for checkers that want to check arguments or attributed /// behavior for functions and methods no matter how they are being invoked. @@ -117,16 +118,32 @@ public: /// check::PreCall void checkPreCall(const CallEvent &Call, CheckerContext &C) const {} - /// \brief Post-visit an abstract "call" event. + /// Post-visit an abstract "call" event. /// \sa checkPreObjCMessage() /// /// check::PostCall void checkPostCall(const CallEvent &Call, CheckerContext &C) const {} - /// \brief Pre-visit of the condition statement of a branch (such as IfStmt). + /// Pre-visit of the condition statement of a branch (such as IfStmt). void checkBranchCondition(const Stmt *Condition, CheckerContext &Ctx) const {} - /// \brief Called on a load from and a store to a location. + /// Post-visit the C++ operator new's allocation call. + /// + /// Execution of C++ operator new consists of the following phases: (1) call + /// default or overridden operator new() to allocate memory (2) cast the + /// return value of operator new() from void pointer type to class pointer + /// type, (3) assuming that the value is non-null, call the object's + /// constructor over this pointer, (4) declare that the value of the + /// new-expression is this pointer. This callback is called between steps + /// (2) and (3). Post-call for the allocator is called after step (1). + /// Pre-statement for the new-expression is called on step (4) when the value + /// of the expression is evaluated. + /// \param NE The C++ new-expression that triggered the allocation. + /// \param Target The allocated region, casted to the class type. + void checkNewAllocator(const CXXNewExpr *NE, SVal Target, + CheckerContext &) const {} + + /// Called on a load from and a store to a location. /// /// The method will be called each time a location (pointer) value is /// accessed. @@ -138,7 +155,7 @@ public: void checkLocation(SVal Loc, bool IsLoad, const Stmt *S, CheckerContext &) const {} - /// \brief Called on binding of a value to a location. + /// Called on binding of a value to a location. /// /// \param Loc The value of the location (pointer). /// \param Val The value which will be stored at the location Loc. @@ -147,7 +164,7 @@ public: /// check::Bind void checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &) const {} - /// \brief Called whenever a symbol becomes dead. + /// Called whenever a symbol becomes dead. /// /// This callback should be used by the checkers to aggressively clean /// up/reduce the checker state, which is important for reducing the overall @@ -164,20 +181,20 @@ public: void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const {} - /// \brief Called when the analyzer core starts analyzing a function, + /// Called when the analyzer core starts analyzing a function, /// regardless of whether it is analyzed at the top level or is inlined. /// /// check::BeginFunction void checkBeginFunction(CheckerContext &Ctx) const {} - /// \brief Called when the analyzer core reaches the end of a + /// Called when the analyzer core reaches the end of a /// function being analyzed regardless of whether it is analyzed at the top /// level or is inlined. /// /// check::EndFunction - void checkEndFunction(CheckerContext &Ctx) const {} + void checkEndFunction(const ReturnStmt *RS, CheckerContext &Ctx) const {} - /// \brief Called after all the paths in the ExplodedGraph reach end of path + /// Called after all the paths in the ExplodedGraph reach end of path /// - the symbolic execution graph is fully explored. /// /// This callback should be used in cases when a checker needs to have a @@ -190,14 +207,14 @@ public: BugReporter &BR, ExprEngine &Eng) const {} - /// \brief Called after analysis of a TranslationUnit is complete. + /// Called after analysis of a TranslationUnit is complete. /// /// check::EndOfTranslationUnit void checkEndOfTranslationUnit(const TranslationUnitDecl *TU, AnalysisManager &Mgr, BugReporter &BR) const {} - /// \brief Evaluates function call. + /// Evaluates function call. /// /// The analysis core threats all function calls in the same way. However, some /// functions have special meaning, which should be reflected in the program @@ -212,7 +229,7 @@ public: /// eval::Call bool evalCall(const CallExpr *CE, CheckerContext &C) const { return true; } - /// \brief Handles assumptions on symbolic values. + /// Handles assumptions on symbolic values. /// /// This method is called when a symbolic expression is assumed to be true or /// false. For example, the assumptions are performed when evaluating a @@ -231,7 +248,7 @@ public: /// check::LiveSymbols void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const {} - /// \brief Called when the contents of one or more regions change. + /// Called when the contents of one or more regions change. /// /// This can occur in many different ways: an explicit bind, a blanket /// invalidation of the region contents, or by passing a region to a function @@ -263,7 +280,7 @@ public: return State; } - /// \brief Called when pointers escape. + /// Called when pointers escape. /// /// This notifies the checkers about pointer escape, which occurs whenever /// the analyzer cannot track the symbol any more. For example, as a @@ -283,7 +300,7 @@ public: return State; } - /// \brief Called when const pointers escape. + /// Called when const pointers escape. /// /// Note: in most cases checkPointerEscape callback is sufficient. /// \sa checkPointerEscape @@ -297,7 +314,7 @@ public: /// check::Event<ImplicitNullDerefEvent> void checkEvent(ImplicitNullDerefEvent Event) const {} - /// \brief Check every declaration in the AST. + /// Check every declaration in the AST. /// /// An AST traversal callback, which should only be used when the checker is /// not path sensitive. It will be called for every Declaration in the AST and diff --git a/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp b/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp index 9e9939ae25c0..b38992b0e030 100644 --- a/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp @@ -105,7 +105,7 @@ void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const { // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED. const Expr *ArgExpr = CE->getArg(0); - SVal ArgVal = state->getSVal(ArgExpr, C.getLocationContext()); + SVal ArgVal = C.getSVal(ArgExpr); if (const MemRegion *R = ArgVal.getAsRegion()) { R = R->StripCasts(); @@ -132,7 +132,7 @@ void ChrootChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { if (!II_chdir) II_chdir = &Ctx.Idents.get("chdir"); - // Ingnore chroot and chdir. + // Ignore chroot and chdir. if (FD->getIdentifier() == II_chroot || FD->getIdentifier() == II_chdir) return; diff --git a/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp b/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp index e04e2ab2c320..d3489282ab62 100644 --- a/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp @@ -39,7 +39,7 @@ class DeleteWithNonVirtualDtorChecker : public Checker<check::PreStmt<CXXDeleteExpr>> { mutable std::unique_ptr<BugType> BT; - class DeleteBugVisitor : public BugReporterVisitorImpl<DeleteBugVisitor> { + class DeleteBugVisitor : public BugReporterVisitor { public: DeleteBugVisitor() : Satisfied(false) {} void Profile(llvm::FoldingSetNodeID &ID) const override { @@ -110,8 +110,6 @@ DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode( if (Satisfied) return nullptr; - ProgramStateRef State = N->getState(); - const LocationContext *LC = N->getLocationContext(); const Stmt *S = PathDiagnosticLocation::getStmt(N); if (!S) return nullptr; @@ -128,7 +126,7 @@ DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode( } // Region associated with the current cast expression. - const MemRegion *M = State->getSVal(CastE, LC).getAsRegion(); + const MemRegion *M = N->getSVal(CastE).getAsRegion(); if (!M) return nullptr; diff --git a/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp b/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp index 598502305633..bc39c92ea970 100644 --- a/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp @@ -24,22 +24,23 @@ using namespace ento; namespace { class DivZeroChecker : public Checker< check::PreStmt<BinaryOperator> > { mutable std::unique_ptr<BuiltinBug> BT; - void reportBug(const char *Msg, - ProgramStateRef StateZero, - CheckerContext &C) const ; + void reportBug(const char *Msg, ProgramStateRef StateZero, CheckerContext &C, + std::unique_ptr<BugReporterVisitor> Visitor = nullptr) const; + public: void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const; }; } // end anonymous namespace -void DivZeroChecker::reportBug(const char *Msg, - ProgramStateRef StateZero, - CheckerContext &C) const { +void DivZeroChecker::reportBug( + const char *Msg, ProgramStateRef StateZero, CheckerContext &C, + std::unique_ptr<BugReporterVisitor> Visitor) const { if (ExplodedNode *N = C.generateErrorNode(StateZero)) { if (!BT) BT.reset(new BuiltinBug(this, "Division by zero")); auto R = llvm::make_unique<BugReport>(*BT, Msg, N); + R->addVisitor(std::move(Visitor)); bugreporter::trackNullOrUndefValue(N, bugreporter::GetDenomExpr(N), *R); C.emitReport(std::move(R)); } @@ -57,7 +58,7 @@ void DivZeroChecker::checkPreStmt(const BinaryOperator *B, if (!B->getRHS()->getType()->isScalarType()) return; - SVal Denom = C.getState()->getSVal(B->getRHS(), C.getLocationContext()); + SVal Denom = C.getSVal(B->getRHS()); Optional<DefinedSVal> DV = Denom.getAs<DefinedSVal>(); // Divide-by-undefined handled in the generic checking for uses of @@ -78,7 +79,8 @@ void DivZeroChecker::checkPreStmt(const BinaryOperator *B, bool TaintedD = C.getState()->isTainted(*DV); if ((stateNotZero && stateZero && TaintedD)) { - reportBug("Division by a tainted value, possibly zero", stateZero, C); + reportBug("Division by a tainted value, possibly zero", stateZero, C, + llvm::make_unique<TaintBugVisitor>(*DV)); return; } diff --git a/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp b/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp index 109897be2931..4e4d81cd6714 100644 --- a/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp @@ -38,8 +38,7 @@ class DynamicTypeChecker : public Checker<check::PostStmt<ImplicitCastExpr>> { new BugType(this, "Dynamic and static type mismatch", "Type Error")); } - class DynamicTypeBugVisitor - : public BugReporterVisitorImpl<DynamicTypeBugVisitor> { + class DynamicTypeBugVisitor : public BugReporterVisitor { public: DynamicTypeBugVisitor(const MemRegion *Reg) : Reg(Reg) {} diff --git a/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index db9179e018a1..126e57645a43 100644 --- a/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ b/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -59,7 +59,7 @@ class DynamicTypePropagation: const ObjCObjectType *getObjectTypeForAllocAndNew(const ObjCMessageExpr *MsgE, CheckerContext &C) const; - /// \brief Return a better dynamic type if one can be derived from the cast. + /// Return a better dynamic type if one can be derived from the cast. const ObjCObjectPointerType *getBetterObjCType(const Expr *CastE, CheckerContext &C) const; @@ -74,7 +74,7 @@ class DynamicTypePropagation: new BugType(this, "Generics", categories::CoreFoundationObjectiveC)); } - class GenericsBugVisitor : public BugReporterVisitorImpl<GenericsBugVisitor> { + class GenericsBugVisitor : public BugReporterVisitor { public: GenericsBugVisitor(SymbolRef S) : Sym(S) {} @@ -562,7 +562,7 @@ void DynamicTypePropagation::checkPostStmt(const CastExpr *CE, DestObjectPtrType->isUnspecialized()) return; - SymbolRef Sym = State->getSVal(CE, C.getLocationContext()).getAsSymbol(); + SymbolRef Sym = C.getSVal(CE).getAsSymbol(); if (!Sym) return; @@ -631,7 +631,7 @@ static const Expr *stripCastsAndSugar(const Expr *E) { } static bool isObjCTypeParamDependent(QualType Type) { - // It is illegal to typedef parameterized types inside an interface. Therfore + // It is illegal to typedef parameterized types inside an interface. Therefore // an Objective-C type can only be dependent on a type parameter when the type // parameter structurally present in the type itself. class IsObjCTypeParamDependentTypeVisitor diff --git a/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp b/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp index 0005ec470d20..8de653c10f7e 100644 --- a/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp @@ -149,7 +149,7 @@ void ExprInspectionChecker::analyzerEval(const CallExpr *CE, // A specific instantiation of an inlined function may have more constrained // values than can generally be assumed. Skip the check. - if (LC->getCurrentStackFrame()->getParent() != nullptr) + if (LC->getStackFrame()->getParent() != nullptr) return; reportBug(getArgumentValueString(CE, C), C); @@ -178,7 +178,7 @@ void ExprInspectionChecker::analyzerCheckInlined(const CallExpr *CE, // when we are analyzing it as an inlined function. This means that // clang_analyzer_checkInlined(true) should always print TRUE, but // clang_analyzer_checkInlined(false) should never actually print anything. - if (LC->getCurrentStackFrame()->getParent() == nullptr) + if (LC->getStackFrame()->getParent() == nullptr) return; reportBug(getArgumentValueString(CE, C), C); diff --git a/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp b/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp index 3fe89f96a43b..059203fca730 100644 --- a/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp @@ -44,8 +44,7 @@ void FixedAddressChecker::checkPreStmt(const BinaryOperator *B, if (!T->isPointerType()) return; - ProgramStateRef state = C.getState(); - SVal RV = state->getSVal(B->getRHS(), C.getLocationContext()); + SVal RV = C.getSVal(B->getRHS()); if (!RV.isConstant() || RV.isZeroConstant()) return; diff --git a/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp b/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp new file mode 100644 index 000000000000..5cb51b01f044 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp @@ -0,0 +1,229 @@ +//===- GCDAntipatternChecker.cpp ---------------------------------*- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines GCDAntipatternChecker which checks against a common +// antipattern when synchronous API is emulated from asynchronous callbacks +// using a semaphore: +// +// dispatch_semaphore_t sema = dispatch_semaphore_create(0); +// +// AnyCFunctionCall(^{ +// // code… +// dispatch_semaphore_signal(sema); +// }) +// dispatch_semaphore_wait(sema, *) +// +// Such code is a common performance problem, due to inability of GCD to +// properly handle QoS when a combination of queues and semaphores is used. +// Good code would either use asynchronous API (when available), or perform +// the necessary action in asynchronous callback. +// +// Currently, the check is performed using a simple heuristical AST pattern +// matching. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "llvm/Support/Debug.h" + +using namespace clang; +using namespace ento; +using namespace ast_matchers; + +namespace { + +// ID of a node at which the diagnostic would be emitted. +const char *WarnAtNode = "waitcall"; + +class GCDAntipatternChecker : public Checker<check::ASTCodeBody> { +public: + void checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const; +}; + +auto callsName(const char *FunctionName) + -> decltype(callee(functionDecl())) { + return callee(functionDecl(hasName(FunctionName))); +} + +auto equalsBoundArgDecl(int ArgIdx, const char *DeclName) + -> decltype(hasArgument(0, expr())) { + return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr( + to(varDecl(equalsBoundNode(DeclName)))))); +} + +auto bindAssignmentToDecl(const char *DeclName) -> decltype(hasLHS(expr())) { + return hasLHS(ignoringParenImpCasts( + declRefExpr(to(varDecl().bind(DeclName))))); +} + +/// The pattern is very common in tests, and it is OK to use it there. +/// We have to heuristics for detecting tests: method name starts with "test" +/// (used in XCTest), and a class name contains "mock" or "test" (used in +/// helpers which are not tests themselves, but used exclusively in tests). +static bool isTest(const Decl *D) { + if (const auto* ND = dyn_cast<NamedDecl>(D)) { + std::string DeclName = ND->getNameAsString(); + if (StringRef(DeclName).startswith("test")) + return true; + } + if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) { + if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) { + std::string ContainerName = CD->getNameAsString(); + StringRef CN(ContainerName); + if (CN.contains_lower("test") || CN.contains_lower("mock")) + return true; + } + } + return false; +} + +static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) { + + const char *SemaphoreBinding = "semaphore_name"; + auto SemaphoreCreateM = callExpr(allOf( + callsName("dispatch_semaphore_create"), + hasArgument(0, ignoringParenCasts(integerLiteral(equals(0)))))); + + auto SemaphoreBindingM = anyOf( + forEachDescendant( + varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)), + forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding), + hasRHS(SemaphoreCreateM)))); + + auto HasBlockArgumentM = hasAnyArgument(hasType( + hasCanonicalType(blockPointerType()) + )); + + auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr( + allOf( + callsName("dispatch_semaphore_signal"), + equalsBoundArgDecl(0, SemaphoreBinding) + ))))); + + auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM); + + auto HasBlockCallingSignalM = + forEachDescendant( + stmt(anyOf( + callExpr(HasBlockAndCallsSignalM), + objcMessageExpr(HasBlockAndCallsSignalM) + ))); + + auto SemaphoreWaitM = forEachDescendant( + callExpr( + allOf( + callsName("dispatch_semaphore_wait"), + equalsBoundArgDecl(0, SemaphoreBinding) + ) + ).bind(WarnAtNode)); + + return compoundStmt( + SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM); +} + +static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) { + + const char *GroupBinding = "group_name"; + auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create")); + + auto GroupBindingM = anyOf( + forEachDescendant( + varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)), + forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding), + hasRHS(DispatchGroupCreateM)))); + + auto GroupEnterM = forEachDescendant( + stmt(callExpr(allOf(callsName("dispatch_group_enter"), + equalsBoundArgDecl(0, GroupBinding))))); + + auto HasBlockArgumentM = hasAnyArgument(hasType( + hasCanonicalType(blockPointerType()) + )); + + auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr( + allOf( + callsName("dispatch_group_leave"), + equalsBoundArgDecl(0, GroupBinding) + ))))); + + auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM); + + auto AcceptsBlockM = + forEachDescendant( + stmt(anyOf( + callExpr(HasBlockAndCallsLeaveM), + objcMessageExpr(HasBlockAndCallsLeaveM) + ))); + + auto GroupWaitM = forEachDescendant( + callExpr( + allOf( + callsName("dispatch_group_wait"), + equalsBoundArgDecl(0, GroupBinding) + ) + ).bind(WarnAtNode)); + + return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM); +} + +static void emitDiagnostics(const BoundNodes &Nodes, + const char* Type, + BugReporter &BR, + AnalysisDeclContext *ADC, + const GCDAntipatternChecker *Checker) { + const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode); + assert(SW); + + std::string Diagnostics; + llvm::raw_string_ostream OS(Diagnostics); + OS << "Waiting on a callback using a " << Type << " creates useless threads " + << "and is subject to priority inversion; consider " + << "using a synchronous API or changing the caller to be asynchronous"; + + BR.EmitBasicReport( + ADC->getDecl(), + Checker, + /*Name=*/"GCD performance anti-pattern", + /*Category=*/"Performance", + OS.str(), + PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC), + SW->getSourceRange()); +} + +void GCDAntipatternChecker::checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const { + if (isTest(D)) + return; + + AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); + + auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore(); + auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext()); + for (BoundNodes Match : Matches) + emitDiagnostics(Match, "semaphore", BR, ADC, this); + + auto GroupMatcherM = findGCDAntiPatternWithGroup(); + Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext()); + for (BoundNodes Match : Matches) + emitDiagnostics(Match, "group", BR, ADC, this); +} + +} + +void ento::registerGCDAntipattern(CheckerManager &Mgr) { + Mgr.registerChecker<GCDAntipatternChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/GTestChecker.cpp b/lib/StaticAnalyzer/Checkers/GTestChecker.cpp index f0be41b293e4..3ef95e673b87 100644 --- a/lib/StaticAnalyzer/Checkers/GTestChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/GTestChecker.cpp @@ -161,7 +161,7 @@ void GTestChecker::modelAssertionResultCopyConstructor( const CXXConstructorCall *Call, CheckerContext &C) const { assert(Call->getNumArgs() == 1); - // The first parameter of the the copy constructor must be the other + // The first parameter of the copy constructor must be the other // instance to initialize this instances fields from. SVal OtherVal = Call->getArgSVal(0); SVal ThisVal = Call->getCXXThisVal(); diff --git a/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp index 43966656cd8d..899586745a0b 100644 --- a/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -48,24 +48,24 @@ private: BT.reset(new BugType(this, "Use of Untrusted Data", "Untrusted Data")); } - /// \brief Catch taint related bugs. Check if tainted data is passed to a + /// Catch taint related bugs. Check if tainted data is passed to a /// system call etc. bool checkPre(const CallExpr *CE, CheckerContext &C) const; - /// \brief Add taint sources on a pre-visit. + /// Add taint sources on a pre-visit. void addSourcesPre(const CallExpr *CE, CheckerContext &C) const; - /// \brief Propagate taint generated at pre-visit. + /// Propagate taint generated at pre-visit. bool propagateFromPre(const CallExpr *CE, CheckerContext &C) const; - /// \brief Add taint sources on a post visit. + /// Add taint sources on a post visit. void addSourcesPost(const CallExpr *CE, CheckerContext &C) const; /// Check if the region the expression evaluates to is the standard input, /// and thus, is tainted. static bool isStdin(const Expr *E, CheckerContext &C); - /// \brief Given a pointer argument, return the value it points to. + /// Given a pointer argument, return the value it points to. static Optional<SVal> getPointedToSVal(CheckerContext &C, const Expr *Arg); /// Functions defining the attack surface. @@ -100,26 +100,9 @@ private: bool generateReportIfTainted(const Expr *E, const char Msg[], CheckerContext &C) const; - /// The bug visitor prints a diagnostic message at the location where a given - /// variable was tainted. - class TaintBugVisitor - : public BugReporterVisitorImpl<TaintBugVisitor> { - private: - const SVal V; - - public: - TaintBugVisitor(const SVal V) : V(V) {} - void Profile(llvm::FoldingSetNodeID &ID) const override { ID.Add(V); } - - std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, - BugReporterContext &BRC, - BugReport &BR) override; - }; - typedef SmallVector<unsigned, 2> ArgVector; - /// \brief A struct used to specify taint propagation rules for a function. + /// A struct used to specify taint propagation rules for a function. /// /// If any of the possible taint source arguments is tainted, all of the /// destination arguments should also be tainted. Use InvalidArgIndex in the @@ -183,7 +166,7 @@ private: return (V && State->isTainted(*V)); } - /// \brief Pre-process a function which propagates taint according to the + /// Pre-process a function which propagates taint according to the /// taint rule. ProgramStateRef process(const CallExpr *CE, CheckerContext &C) const; @@ -214,28 +197,6 @@ const char GenericTaintChecker::MsgTaintedBufferSize[] = /// points to data, which should be tainted on return. REGISTER_SET_WITH_PROGRAMSTATE(TaintArgsOnPostVisit, unsigned) -std::shared_ptr<PathDiagnosticPiece> -GenericTaintChecker::TaintBugVisitor::VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) { - - // Find the ExplodedNode where the taint was first introduced - if (!N->getState()->isTainted(V) || PrevN->getState()->isTainted(V)) - return nullptr; - - const Stmt *S = PathDiagnosticLocation::getStmt(N); - if (!S) - return nullptr; - - const LocationContext *NCtx = N->getLocationContext(); - PathDiagnosticLocation L = - PathDiagnosticLocation::createBegin(S, BRC.getSourceManager(), NCtx); - if (!L.isValid() || !L.asLocation().isValid()) - return nullptr; - - return std::make_shared<PathDiagnosticEventPiece>( - L, "Taint originated here"); -} - GenericTaintChecker::TaintPropagationRule GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( const FunctionDecl *FDecl, @@ -468,7 +429,7 @@ bool GenericTaintChecker::checkPre(const CallExpr *CE, CheckerContext &C) const{ Optional<SVal> GenericTaintChecker::getPointedToSVal(CheckerContext &C, const Expr *Arg) { ProgramStateRef State = C.getState(); - SVal AddrVal = State->getSVal(Arg->IgnoreParens(), C.getLocationContext()); + SVal AddrVal = C.getSVal(Arg->IgnoreParens()); if (AddrVal.isUnknownOrUndef()) return None; @@ -621,7 +582,7 @@ ProgramStateRef GenericTaintChecker::postRetTaint(const CallExpr *CE, bool GenericTaintChecker::isStdin(const Expr *E, CheckerContext &C) { ProgramStateRef State = C.getState(); - SVal Val = State->getSVal(E, C.getLocationContext()); + SVal Val = C.getSVal(E); // stdin is a pointer, so it would be a region. const MemRegion *MemReg = Val.getAsRegion(); @@ -646,7 +607,8 @@ bool GenericTaintChecker::isStdin(const Expr *E, CheckerContext &C) { if ((D->getName().find("stdin") != StringRef::npos) && D->isExternC()) if (const PointerType * PtrTy = dyn_cast<PointerType>(D->getType().getTypePtr())) - if (PtrTy->getPointeeType() == C.getASTContext().getFILEType()) + if (PtrTy->getPointeeType().getCanonicalType() == + C.getASTContext().getFILEType().getCanonicalType()) return true; } return false; diff --git a/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp b/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp index cf57b8dca063..f102ca96a5c1 100644 --- a/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp @@ -8,7 +8,7 @@ //===----------------------------------------------------------------------===// /// /// \file -/// \brief This defines IdenticalExprChecker, a check that warns about +/// This defines IdenticalExprChecker, a check that warns about /// unintended use of identical expressions. /// /// It checks for use of identical expressions with comparison operators and @@ -296,7 +296,7 @@ bool FindIdenticalExprVisitor::VisitConditionalOperator( return true; } -/// \brief Determines whether two statement trees are identical regarding +/// Determines whether two statement trees are identical regarding /// operators and symbols. /// /// Exceptions: expressions containing macros or functions with possible side diff --git a/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp b/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp new file mode 100644 index 000000000000..ed877ab34518 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp @@ -0,0 +1,252 @@ +//=== InnerPointerChecker.cpp -------------------------------------*- C++ -*--// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines a check that marks a raw pointer to a C++ container's +// inner buffer released when the object is destroyed. This information can +// be used by MallocChecker to detect use-after-free problems. +// +//===----------------------------------------------------------------------===// + +#include "AllocationState.h" +#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +using PtrSet = llvm::ImmutableSet<SymbolRef>; + +// Associate container objects with a set of raw pointer symbols. +REGISTER_MAP_WITH_PROGRAMSTATE(RawPtrMap, const MemRegion *, PtrSet) + +// This is a trick to gain access to PtrSet's Factory. +namespace clang { +namespace ento { +template <> +struct ProgramStateTrait<PtrSet> : public ProgramStatePartialTrait<PtrSet> { + static void *GDMIndex() { + static int Index = 0; + return &Index; + } +}; +} // end namespace ento +} // end namespace clang + +namespace { + +class InnerPointerChecker + : public Checker<check::DeadSymbols, check::PostCall> { + + CallDescription AppendFn, AssignFn, ClearFn, CStrFn, DataFn, EraseFn, + InsertFn, PopBackFn, PushBackFn, ReplaceFn, ReserveFn, ResizeFn, + ShrinkToFitFn, SwapFn; + +public: + class InnerPointerBRVisitor : public BugReporterVisitor { + SymbolRef PtrToBuf; + + public: + InnerPointerBRVisitor(SymbolRef Sym) : PtrToBuf(Sym) {} + + static void *getTag() { + static int Tag = 0; + return &Tag; + } + + void Profile(llvm::FoldingSetNodeID &ID) const override { + ID.AddPointer(getTag()); + } + + std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) override; + + // FIXME: Scan the map once in the visitor's constructor and do a direct + // lookup by region. + bool isSymbolTracked(ProgramStateRef State, SymbolRef Sym) { + RawPtrMapTy Map = State->get<RawPtrMap>(); + for (const auto Entry : Map) { + if (Entry.second.contains(Sym)) + return true; + } + return false; + } + }; + + InnerPointerChecker() + : AppendFn("append"), AssignFn("assign"), ClearFn("clear"), + CStrFn("c_str"), DataFn("data"), EraseFn("erase"), InsertFn("insert"), + PopBackFn("pop_back"), PushBackFn("push_back"), ReplaceFn("replace"), + ReserveFn("reserve"), ResizeFn("resize"), + ShrinkToFitFn("shrink_to_fit"), SwapFn("swap") {} + + /// Check whether the function called on the container object is a + /// member function that potentially invalidates pointers referring + /// to the objects's internal buffer. + bool mayInvalidateBuffer(const CallEvent &Call) const; + + /// Record the connection between the symbol returned by c_str() and the + /// corresponding string object region in the ProgramState. Mark the symbol + /// released if the string object is destroyed. + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + + /// Clean up the ProgramState map. + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; +}; + +} // end anonymous namespace + +// [string.require] +// +// "References, pointers, and iterators referring to the elements of a +// basic_string sequence may be invalidated by the following uses of that +// basic_string object: +// +// -- TODO: As an argument to any standard library function taking a reference +// to non-const basic_string as an argument. For example, as an argument to +// non-member functions swap(), operator>>(), and getline(), or as an argument +// to basic_string::swap(). +// +// -- Calling non-const member functions, except operator[], at, front, back, +// begin, rbegin, end, and rend." +// +bool InnerPointerChecker::mayInvalidateBuffer(const CallEvent &Call) const { + if (const auto *MemOpCall = dyn_cast<CXXMemberOperatorCall>(&Call)) { + OverloadedOperatorKind Opc = MemOpCall->getOriginExpr()->getOperator(); + if (Opc == OO_Equal || Opc == OO_PlusEqual) + return true; + return false; + } + return (isa<CXXDestructorCall>(Call) || Call.isCalled(AppendFn) || + Call.isCalled(AssignFn) || Call.isCalled(ClearFn) || + Call.isCalled(EraseFn) || Call.isCalled(InsertFn) || + Call.isCalled(PopBackFn) || Call.isCalled(PushBackFn) || + Call.isCalled(ReplaceFn) || Call.isCalled(ReserveFn) || + Call.isCalled(ResizeFn) || Call.isCalled(ShrinkToFitFn) || + Call.isCalled(SwapFn)); +} + +void InnerPointerChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + const auto *ICall = dyn_cast<CXXInstanceCall>(&Call); + if (!ICall) + return; + + SVal Obj = ICall->getCXXThisVal(); + const auto *ObjRegion = dyn_cast_or_null<TypedValueRegion>(Obj.getAsRegion()); + if (!ObjRegion) + return; + + auto *TypeDecl = ObjRegion->getValueType()->getAsCXXRecordDecl(); + if (TypeDecl->getName() != "basic_string") + return; + + ProgramStateRef State = C.getState(); + + if (Call.isCalled(CStrFn) || Call.isCalled(DataFn)) { + SVal RawPtr = Call.getReturnValue(); + if (SymbolRef Sym = RawPtr.getAsSymbol(/*IncludeBaseRegions=*/true)) { + // Start tracking this raw pointer by adding it to the set of symbols + // associated with this container object in the program state map. + PtrSet::Factory &F = State->getStateManager().get_context<PtrSet>(); + const PtrSet *SetPtr = State->get<RawPtrMap>(ObjRegion); + PtrSet Set = SetPtr ? *SetPtr : F.getEmptySet(); + assert(C.wasInlined || !Set.contains(Sym)); + Set = F.add(Set, Sym); + State = State->set<RawPtrMap>(ObjRegion, Set); + C.addTransition(State); + } + return; + } + + if (mayInvalidateBuffer(Call)) { + if (const PtrSet *PS = State->get<RawPtrMap>(ObjRegion)) { + // Mark all pointer symbols associated with the deleted object released. + const Expr *Origin = Call.getOriginExpr(); + for (const auto Symbol : *PS) { + // NOTE: `Origin` may be null, and will be stored so in the symbol's + // `RefState` in MallocChecker's `RegionState` program state map. + State = allocation_state::markReleased(State, Symbol, Origin); + } + State = State->remove<RawPtrMap>(ObjRegion); + C.addTransition(State); + return; + } + } +} + +void InnerPointerChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + PtrSet::Factory &F = State->getStateManager().get_context<PtrSet>(); + RawPtrMapTy RPM = State->get<RawPtrMap>(); + for (const auto Entry : RPM) { + if (!SymReaper.isLiveRegion(Entry.first)) { + // Due to incomplete destructor support, some dead regions might + // remain in the program state map. Clean them up. + State = State->remove<RawPtrMap>(Entry.first); + } + if (const PtrSet *OldSet = State->get<RawPtrMap>(Entry.first)) { + PtrSet CleanedUpSet = *OldSet; + for (const auto Symbol : Entry.second) { + if (!SymReaper.isLive(Symbol)) + CleanedUpSet = F.remove(CleanedUpSet, Symbol); + } + State = CleanedUpSet.isEmpty() + ? State->remove<RawPtrMap>(Entry.first) + : State->set<RawPtrMap>(Entry.first, CleanedUpSet); + } + } + C.addTransition(State); +} + +std::shared_ptr<PathDiagnosticPiece> +InnerPointerChecker::InnerPointerBRVisitor::VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) { + + if (!isSymbolTracked(N->getState(), PtrToBuf) || + isSymbolTracked(PrevN->getState(), PtrToBuf)) + return nullptr; + + const Stmt *S = PathDiagnosticLocation::getStmt(N); + if (!S) + return nullptr; + + SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); + OS << "Dangling inner pointer obtained here"; + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true, + nullptr); +} + +namespace clang { +namespace ento { +namespace allocation_state { + +std::unique_ptr<BugReporterVisitor> getInnerPointerBRVisitor(SymbolRef Sym) { + return llvm::make_unique<InnerPointerChecker::InnerPointerBRVisitor>(Sym); +} + +} // end namespace allocation_state +} // end namespace ento +} // end namespace clang + +void ento::registerInnerPointerChecker(CheckerManager &Mgr) { + registerNewDeleteChecker(Mgr); + Mgr.registerChecker<InnerPointerChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp b/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp index 0f9b749506fa..56c250cd1678 100644 --- a/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp @@ -46,19 +46,25 @@ // use setter and getters functions which separate the three cases. To store // them we use a pointer union of symbol and memory region. // -// The checker works the following way: We record the past-end iterator for -// all containers whenever their `.end()` is called. Since the Constraint -// Manager cannot handle SVals we need to take over its role. We post-check -// equality and non-equality comparisons and propagate the position of the -// iterator to the other side of the comparison if it is past-end and we are in -// the 'equal' branch (true-branch for `==` and false-branch for `!=`). +// The checker works the following way: We record the begin and the +// past-end iterator for all containers whenever their `.begin()` and `.end()` +// are called. Since the Constraint Manager cannot handle such SVals we need +// to take over its role. We post-check equality and non-equality comparisons +// and record that the two sides are equal if we are in the 'equal' branch +// (true-branch for `==` and false-branch for `!=`). // // In case of type-I or type-II iterators we get a concrete integer as a result // of the comparison (1 or 0) but in case of type-III we only get a Symbol. In // this latter case we record the symbol and reload it in evalAssume() and do // the propagation there. We also handle (maybe double) negated comparisons -// which are represented in the form of (x == 0 or x !=0 ) where x is the +// which are represented in the form of (x == 0 or x != 0) where x is the // comparison itself. +// +// Since `SimpleConstraintManager` cannot handle complex symbolic expressions +// we only use expressions of the format S, S+n or S-n for iterator positions +// where S is a conjured symbol and n is an unsigned concrete integer. When +// making an assumption e.g. `S1 + n == S2 + m` we store `S1 - S2 == m - n` as +// a constraint which we later retrieve when doing an actual comparison. #include "ClangSACheckers.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" @@ -80,7 +86,7 @@ private: const MemRegion *Cont; // Abstract offset - SymbolRef Offset; + const SymbolRef Offset; IteratorPosition(const MemRegion *C, SymbolRef Of) : Cont(C), Offset(Of) {} @@ -113,31 +119,39 @@ public: typedef llvm::PointerUnion<const MemRegion *, SymbolRef> RegionOrSymbol; -// Structure to record the symbolic end position of a container +// Structure to record the symbolic begin and end position of a container struct ContainerData { private: - SymbolRef End; + const SymbolRef Begin, End; - ContainerData(SymbolRef E) : End(E) {} + ContainerData(SymbolRef B, SymbolRef E) : Begin(B), End(E) {} public: + static ContainerData fromBegin(SymbolRef B) { + return ContainerData(B, nullptr); + } + static ContainerData fromEnd(SymbolRef E) { - return ContainerData(E); + return ContainerData(nullptr, E); } + SymbolRef getBegin() const { return Begin; } SymbolRef getEnd() const { return End; } - ContainerData newEnd(SymbolRef E) const { return ContainerData(E); } + ContainerData newBegin(SymbolRef B) const { return ContainerData(B, End); } + + ContainerData newEnd(SymbolRef E) const { return ContainerData(Begin, E); } bool operator==(const ContainerData &X) const { - return End == X.End; + return Begin == X.Begin && End == X.End; } bool operator!=(const ContainerData &X) const { - return End != X.End; + return Begin != X.Begin || End != X.End; } void Profile(llvm::FoldingSetNodeID &ID) const { + ID.Add(Begin); ID.Add(End); } }; @@ -167,8 +181,9 @@ public: class IteratorChecker : public Checker<check::PreCall, check::PostCall, + check::PreStmt<CXXOperatorCallExpr>, check::PostStmt<MaterializeTemporaryExpr>, - check::DeadSymbols, + check::LiveSymbols, check::DeadSymbols, eval::Assume> { std::unique_ptr<BugType> OutOfRangeBugType; @@ -176,10 +191,22 @@ class IteratorChecker void handleComparison(CheckerContext &C, const SVal &RetVal, const SVal &LVal, const SVal &RVal, OverloadedOperatorKind Op) const; void verifyDereference(CheckerContext &C, const SVal &Val) const; + void handleIncrement(CheckerContext &C, const SVal &RetVal, const SVal &Iter, + bool Postfix) const; + void handleDecrement(CheckerContext &C, const SVal &RetVal, const SVal &Iter, + bool Postfix) const; + void handleRandomIncrOrDecr(CheckerContext &C, OverloadedOperatorKind Op, + const SVal &RetVal, const SVal &LHS, + const SVal &RHS) const; + void handleBegin(CheckerContext &C, const Expr *CE, const SVal &RetVal, + const SVal &Cont) const; void handleEnd(CheckerContext &C, const Expr *CE, const SVal &RetVal, const SVal &Cont) const; void assignToContainer(CheckerContext &C, const Expr *CE, const SVal &RetVal, const MemRegion *Cont) const; + void verifyRandomIncrOrDecr(CheckerContext &C, OverloadedOperatorKind Op, + const SVal &RetVal, const SVal &LHS, + const SVal &RHS) const; void reportOutOfRangeBug(const StringRef &Message, const SVal &Val, CheckerContext &C, ExplodedNode *ErrNode) const; @@ -196,8 +223,10 @@ public: void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + void checkPreStmt(const CXXOperatorCallExpr *COCE, CheckerContext &C) const; void checkPostStmt(const MaterializeTemporaryExpr *MTE, CheckerContext &C) const; + void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond, bool Assumption) const; @@ -217,9 +246,13 @@ namespace { bool isIteratorType(const QualType &Type); bool isIterator(const CXXRecordDecl *CRD); +bool isBeginCall(const FunctionDecl *Func); bool isEndCall(const FunctionDecl *Func); bool isSimpleComparisonOperator(OverloadedOperatorKind OK); bool isDereferenceOperator(OverloadedOperatorKind OK); +bool isIncrementOperator(OverloadedOperatorKind OK); +bool isDecrementOperator(OverloadedOperatorKind OK); +bool isRandomIncrOrDecrOperator(OverloadedOperatorKind OK); BinaryOperator::Opcode getOpcode(const SymExpr *SE); const RegionOrSymbol getRegionOrSymbol(const SVal &Val); const ProgramStateRef processComparison(ProgramStateRef State, @@ -230,7 +263,11 @@ const ProgramStateRef saveComparison(ProgramStateRef State, const SVal &RVal, bool Eq); const IteratorComparison *loadComparison(ProgramStateRef State, const SymExpr *Condition); +SymbolRef getContainerBegin(ProgramStateRef State, const MemRegion *Cont); SymbolRef getContainerEnd(ProgramStateRef State, const MemRegion *Cont); +ProgramStateRef createContainerBegin(ProgramStateRef State, + const MemRegion *Cont, + const SymbolRef Sym); ProgramStateRef createContainerEnd(ProgramStateRef State, const MemRegion *Cont, const SymbolRef Sym); const IteratorPosition *getIteratorPosition(ProgramStateRef State, @@ -255,6 +292,7 @@ const ContainerData *getContainerData(ProgramStateRef State, ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont, const ContainerData &CData); bool isOutOfRange(ProgramStateRef State, const IteratorPosition &Pos); +bool isZero(ProgramStateRef State, const NonLoc &Val); } // namespace IteratorChecker::IteratorChecker() { @@ -272,6 +310,22 @@ void IteratorChecker::checkPreCall(const CallEvent &Call, if (Func->isOverloadedOperator()) { if (ChecksEnabled[CK_IteratorRangeChecker] && + isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + // Check for out-of-range incrementions and decrementions + if (Call.getNumArgs() >= 1) { + verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), + Call.getReturnValue(), + InstCall->getCXXThisVal(), Call.getArgSVal(0)); + } + } else { + if (Call.getNumArgs() >= 2) { + verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), + Call.getReturnValue(), Call.getArgSVal(0), + Call.getArgSVal(1)); + } + } + } else if (ChecksEnabled[CK_IteratorRangeChecker] && isDereferenceOperator(Func->getOverloadedOperator())) { // Check for dereference of out-of-range iterators if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { @@ -300,6 +354,36 @@ void IteratorChecker::checkPostCall(const CallEvent &Call, handleComparison(C, Call.getReturnValue(), Call.getArgSVal(0), Call.getArgSVal(1), Op); } + } else if (isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + if (Call.getNumArgs() >= 1) { + handleRandomIncrOrDecr(C, Func->getOverloadedOperator(), + Call.getReturnValue(), + InstCall->getCXXThisVal(), Call.getArgSVal(0)); + } + } else { + if (Call.getNumArgs() >= 2) { + handleRandomIncrOrDecr(C, Func->getOverloadedOperator(), + Call.getReturnValue(), Call.getArgSVal(0), + Call.getArgSVal(1)); + } + } + } else if (isIncrementOperator(Func->getOverloadedOperator())) { + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + handleIncrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(), + Call.getNumArgs()); + } else { + handleIncrement(C, Call.getReturnValue(), Call.getArgSVal(0), + Call.getNumArgs()); + } + } else if (isDecrementOperator(Func->getOverloadedOperator())) { + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + handleDecrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(), + Call.getNumArgs()); + } else { + handleDecrement(C, Call.getReturnValue(), Call.getArgSVal(0), + Call.getNumArgs()); + } } } else { const auto *OrigExpr = Call.getOriginExpr(); @@ -315,6 +399,11 @@ void IteratorChecker::checkPostCall(const CallEvent &Call, return; if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + if (isBeginCall(Func)) { + handleBegin(C, OrigExpr, Call.getReturnValue(), + InstCall->getCXXThisVal()); + return; + } if (isEndCall(Func)) { handleEnd(C, OrigExpr, Call.getReturnValue(), InstCall->getCXXThisVal()); @@ -351,19 +440,80 @@ void IteratorChecker::checkPostCall(const CallEvent &Call, } } +void IteratorChecker::checkPreStmt(const CXXOperatorCallExpr *COCE, + CheckerContext &C) const { + const auto *ThisExpr = COCE->getArg(0); + + auto State = C.getState(); + const auto *LCtx = C.getLocationContext(); + + const auto CurrentThis = State->getSVal(ThisExpr, LCtx); + if (const auto *Reg = CurrentThis.getAsRegion()) { + if (!Reg->getAs<CXXTempObjectRegion>()) + return; + const auto OldState = C.getPredecessor()->getFirstPred()->getState(); + const auto OldThis = OldState->getSVal(ThisExpr, LCtx); + // FIXME: This solution is unreliable. It may happen that another checker + // subscribes to the pre-statement check of `CXXOperatorCallExpr` + // and adds a transition before us. The proper fix is to make the + // CFG provide a `ConstructionContext` for the `CXXOperatorCallExpr`, + // which would turn the corresponding `CFGStmt` element into a + // `CFGCXXRecordTypedCall` element, which will allow `ExprEngine` to + // foresee that the `begin()`/`end()` call constructs the object + // directly in the temporary region that `CXXOperatorCallExpr` takes + // as its implicit object argument. + const auto *Pos = getIteratorPosition(OldState, OldThis); + if (!Pos) + return; + State = setIteratorPosition(State, CurrentThis, *Pos); + C.addTransition(State); + } +} + void IteratorChecker::checkPostStmt(const MaterializeTemporaryExpr *MTE, CheckerContext &C) const { /* Transfer iterator state to temporary objects */ auto State = C.getState(); - const auto *LCtx = C.getLocationContext(); const auto *Pos = - getIteratorPosition(State, State->getSVal(MTE->GetTemporaryExpr(), LCtx)); + getIteratorPosition(State, C.getSVal(MTE->GetTemporaryExpr())); if (!Pos) return; - State = setIteratorPosition(State, State->getSVal(MTE, LCtx), *Pos); + State = setIteratorPosition(State, C.getSVal(MTE), *Pos); C.addTransition(State); } +void IteratorChecker::checkLiveSymbols(ProgramStateRef State, + SymbolReaper &SR) const { + // Keep symbolic expressions of iterator positions, container begins and ends + // alive + auto RegionMap = State->get<IteratorRegionMap>(); + for (const auto Reg : RegionMap) { + const auto Offset = Reg.second.getOffset(); + for (auto i = Offset->symbol_begin(); i != Offset->symbol_end(); ++i) + if (isa<SymbolData>(*i)) + SR.markLive(*i); + } + + auto SymbolMap = State->get<IteratorSymbolMap>(); + for (const auto Sym : SymbolMap) { + const auto Offset = Sym.second.getOffset(); + for (auto i = Offset->symbol_begin(); i != Offset->symbol_end(); ++i) + if (isa<SymbolData>(*i)) + SR.markLive(*i); + } + + auto ContMap = State->get<ContainerMap>(); + for (const auto Cont : ContMap) { + const auto CData = Cont.second; + if (CData.getBegin()) { + SR.markLive(CData.getBegin()); + } + if (CData.getEnd()) { + SR.markLive(CData.getEnd()); + } + } +} + void IteratorChecker::checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const { // Cleanup @@ -471,13 +621,209 @@ void IteratorChecker::verifyDereference(CheckerContext &C, static CheckerProgramPointTag Tag("IteratorRangeChecker", "IteratorOutOfRange"); auto *N = C.generateNonFatalErrorNode(State, &Tag); - if (!N) { + if (!N) return; - } reportOutOfRangeBug("Iterator accessed outside of its range.", Val, C, N); } } +void IteratorChecker::handleIncrement(CheckerContext &C, const SVal &RetVal, + const SVal &Iter, bool Postfix) const { + // Increment the symbolic expressions which represents the position of the + // iterator + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Iter); + if (Pos) { + auto &SymMgr = C.getSymbolManager(); + auto &BVF = SymMgr.getBasicVals(); + auto &SVB = C.getSValBuilder(); + const auto OldOffset = Pos->getOffset(); + auto NewOffset = + SVB.evalBinOp(State, BO_Add, + nonloc::SymbolVal(OldOffset), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), + SymMgr.getType(OldOffset)).getAsSymbol(); + auto NewPos = Pos->setTo(NewOffset); + State = setIteratorPosition(State, Iter, NewPos); + State = setIteratorPosition(State, RetVal, Postfix ? *Pos : NewPos); + C.addTransition(State); + } +} + +void IteratorChecker::handleDecrement(CheckerContext &C, const SVal &RetVal, + const SVal &Iter, bool Postfix) const { + // Decrement the symbolic expressions which represents the position of the + // iterator + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Iter); + if (Pos) { + auto &SymMgr = C.getSymbolManager(); + auto &BVF = SymMgr.getBasicVals(); + auto &SVB = C.getSValBuilder(); + const auto OldOffset = Pos->getOffset(); + auto NewOffset = + SVB.evalBinOp(State, BO_Sub, + nonloc::SymbolVal(OldOffset), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), + SymMgr.getType(OldOffset)).getAsSymbol(); + auto NewPos = Pos->setTo(NewOffset); + State = setIteratorPosition(State, Iter, NewPos); + State = setIteratorPosition(State, RetVal, Postfix ? *Pos : NewPos); + C.addTransition(State); + } +} + +// This function tells the analyzer's engine that symbols produced by our +// checker, most notably iterator positions, are relatively small. +// A distance between items in the container should not be very large. +// By assuming that it is within around 1/8 of the address space, +// we can help the analyzer perform operations on these symbols +// without being afraid of integer overflows. +// FIXME: Should we provide it as an API, so that all checkers could use it? +static ProgramStateRef assumeNoOverflow(ProgramStateRef State, SymbolRef Sym, + long Scale) { + SValBuilder &SVB = State->getStateManager().getSValBuilder(); + BasicValueFactory &BV = SVB.getBasicValueFactory(); + + QualType T = Sym->getType(); + assert(T->isSignedIntegerOrEnumerationType()); + APSIntType AT = BV.getAPSIntType(T); + + ProgramStateRef NewState = State; + + llvm::APSInt Max = AT.getMaxValue() / AT.getValue(Scale); + SVal IsCappedFromAbove = + SVB.evalBinOpNN(State, BO_LE, nonloc::SymbolVal(Sym), + nonloc::ConcreteInt(Max), SVB.getConditionType()); + if (auto DV = IsCappedFromAbove.getAs<DefinedSVal>()) { + NewState = NewState->assume(*DV, true); + if (!NewState) + return State; + } + + llvm::APSInt Min = -Max; + SVal IsCappedFromBelow = + SVB.evalBinOpNN(State, BO_GE, nonloc::SymbolVal(Sym), + nonloc::ConcreteInt(Min), SVB.getConditionType()); + if (auto DV = IsCappedFromBelow.getAs<DefinedSVal>()) { + NewState = NewState->assume(*DV, true); + if (!NewState) + return State; + } + + return NewState; +} + +void IteratorChecker::handleRandomIncrOrDecr(CheckerContext &C, + OverloadedOperatorKind Op, + const SVal &RetVal, + const SVal &LHS, + const SVal &RHS) const { + // Increment or decrement the symbolic expressions which represents the + // position of the iterator + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, LHS); + if (!Pos) + return; + + const auto *value = &RHS; + if (auto loc = RHS.getAs<Loc>()) { + const auto val = State->getRawSVal(*loc); + value = &val; + } + + auto &SymMgr = C.getSymbolManager(); + auto &SVB = C.getSValBuilder(); + auto BinOp = (Op == OO_Plus || Op == OO_PlusEqual) ? BO_Add : BO_Sub; + const auto OldOffset = Pos->getOffset(); + SymbolRef NewOffset; + if (const auto intValue = value->getAs<nonloc::ConcreteInt>()) { + // For concrete integers we can calculate the new position + NewOffset = SVB.evalBinOp(State, BinOp, nonloc::SymbolVal(OldOffset), + *intValue, + SymMgr.getType(OldOffset)).getAsSymbol(); + } else { + // For other symbols create a new symbol to keep expressions simple + const auto &LCtx = C.getLocationContext(); + NewOffset = SymMgr.conjureSymbol(nullptr, LCtx, SymMgr.getType(OldOffset), + C.blockCount()); + State = assumeNoOverflow(State, NewOffset, 4); + } + auto NewPos = Pos->setTo(NewOffset); + auto &TgtVal = (Op == OO_PlusEqual || Op == OO_MinusEqual) ? LHS : RetVal; + State = setIteratorPosition(State, TgtVal, NewPos); + C.addTransition(State); +} + +void IteratorChecker::verifyRandomIncrOrDecr(CheckerContext &C, + OverloadedOperatorKind Op, + const SVal &RetVal, + const SVal &LHS, + const SVal &RHS) const { + auto State = C.getState(); + + // If the iterator is initially inside its range, then the operation is valid + const auto *Pos = getIteratorPosition(State, LHS); + if (!Pos || !isOutOfRange(State, *Pos)) + return; + + auto value = RHS; + if (auto loc = RHS.getAs<Loc>()) { + value = State->getRawSVal(*loc); + } + + // Incremention or decremention by 0 is never bug + if (isZero(State, value.castAs<NonLoc>())) + return; + + auto &SymMgr = C.getSymbolManager(); + auto &SVB = C.getSValBuilder(); + auto BinOp = (Op == OO_Plus || Op == OO_PlusEqual) ? BO_Add : BO_Sub; + const auto OldOffset = Pos->getOffset(); + const auto intValue = value.getAs<nonloc::ConcreteInt>(); + if (!intValue) + return; + + auto NewOffset = SVB.evalBinOp(State, BinOp, nonloc::SymbolVal(OldOffset), + *intValue, + SymMgr.getType(OldOffset)).getAsSymbol(); + auto NewPos = Pos->setTo(NewOffset); + + // If out of range, the only valid operation is to step into the range + if (isOutOfRange(State, NewPos)) { + auto *N = C.generateNonFatalErrorNode(State); + if (!N) + return; + reportOutOfRangeBug("Iterator accessed past its end.", LHS, C, N); + } +} + +void IteratorChecker::handleBegin(CheckerContext &C, const Expr *CE, + const SVal &RetVal, const SVal &Cont) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + while (const auto *CBOR = ContReg->getAs<CXXBaseObjectRegion>()) { + ContReg = CBOR->getSuperRegion(); + } + + // If the container already has a begin symbol then use it. Otherwise first + // create a new one. + auto State = C.getState(); + auto BeginSym = getContainerBegin(State, ContReg); + if (!BeginSym) { + auto &SymMgr = C.getSymbolManager(); + BeginSym = SymMgr.conjureSymbol(CE, C.getLocationContext(), + C.getASTContext().LongTy, C.blockCount()); + State = assumeNoOverflow(State, BeginSym, 4); + State = createContainerBegin(State, ContReg, BeginSym); + } + State = setIteratorPosition(State, RetVal, + IteratorPosition::getPosition(ContReg, BeginSym)); + C.addTransition(State); +} + void IteratorChecker::handleEnd(CheckerContext &C, const Expr *CE, const SVal &RetVal, const SVal &Cont) const { const auto *ContReg = Cont.getAsRegion(); @@ -496,6 +842,7 @@ void IteratorChecker::handleEnd(CheckerContext &C, const Expr *CE, auto &SymMgr = C.getSymbolManager(); EndSym = SymMgr.conjureSymbol(CE, C.getLocationContext(), C.getASTContext().LongTy, C.blockCount()); + State = assumeNoOverflow(State, EndSym, 4); State = createContainerEnd(State, ContReg, EndSym); } State = setIteratorPosition(State, RetVal, @@ -514,6 +861,7 @@ void IteratorChecker::assignToContainer(CheckerContext &C, const Expr *CE, auto &SymMgr = C.getSymbolManager(); auto Sym = SymMgr.conjureSymbol(CE, C.getLocationContext(), C.getASTContext().LongTy, C.blockCount()); + State = assumeNoOverflow(State, Sym, 4); State = setIteratorPosition(State, RetVal, IteratorPosition::getPosition(Cont, Sym)); C.addTransition(State); @@ -529,9 +877,12 @@ void IteratorChecker::reportOutOfRangeBug(const StringRef &Message, namespace { +bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); bool isGreaterOrEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, BinaryOperator::Opcode Opc); +bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2, + BinaryOperator::Opcode Opc); bool isIteratorType(const QualType &Type) { if (Type->isPointerType()) @@ -585,6 +936,13 @@ bool isIterator(const CXXRecordDecl *CRD) { HasPostIncrOp && HasDerefOp; } +bool isBeginCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + return IdInfo->getName().endswith_lower("begin"); +} + bool isEndCall(const FunctionDecl *Func) { const auto *IdInfo = Func->getIdentifier(); if (!IdInfo) @@ -601,11 +959,24 @@ bool isDereferenceOperator(OverloadedOperatorKind OK) { OK == OO_Subscript; } +bool isIncrementOperator(OverloadedOperatorKind OK) { + return OK == OO_PlusPlus; +} + +bool isDecrementOperator(OverloadedOperatorKind OK) { + return OK == OO_MinusMinus; +} + +bool isRandomIncrOrDecrOperator(OverloadedOperatorKind OK) { + return OK == OO_Plus || OK == OO_PlusEqual || OK == OO_Minus || + OK == OO_MinusEqual; +} + BinaryOperator::Opcode getOpcode(const SymExpr *SE) { if (const auto *BSE = dyn_cast<BinarySymExpr>(SE)) { return BSE->getOpcode(); } else if (const auto *SC = dyn_cast<SymbolConjured>(SE)) { - const auto *COE = dyn_cast<CXXOperatorCallExpr>(SC->getStmt()); + const auto *COE = dyn_cast_or_null<CXXOperatorCallExpr>(SC->getStmt()); if (!COE) return BO_Comma; // Extremal value, neither EQ nor NE if (COE->getOperator() == OO_EqualEqual) { @@ -660,6 +1031,14 @@ const IteratorComparison *loadComparison(ProgramStateRef State, return State->get<IteratorComparisonMap>(Condition); } +SymbolRef getContainerBegin(ProgramStateRef State, const MemRegion *Cont) { + const auto *CDataPtr = getContainerData(State, Cont); + if (!CDataPtr) + return nullptr; + + return CDataPtr->getBegin(); +} + SymbolRef getContainerEnd(ProgramStateRef State, const MemRegion *Cont) { const auto *CDataPtr = getContainerData(State, Cont); if (!CDataPtr) @@ -668,6 +1047,22 @@ SymbolRef getContainerEnd(ProgramStateRef State, const MemRegion *Cont) { return CDataPtr->getEnd(); } +ProgramStateRef createContainerBegin(ProgramStateRef State, + const MemRegion *Cont, + const SymbolRef Sym) { + // Only create if it does not exist + const auto *CDataPtr = getContainerData(State, Cont); + if (CDataPtr) { + if (CDataPtr->getBegin()) { + return State; + } + const auto CData = CDataPtr->newBegin(Sym); + return setContainerData(State, Cont, CData); + } + const auto CData = ContainerData::fromBegin(Sym); + return setContainerData(State, Cont, CData); +} + ProgramStateRef createContainerEnd(ProgramStateRef State, const MemRegion *Cont, const SymbolRef Sym) { // Only create if it does not exist @@ -675,14 +1070,12 @@ ProgramStateRef createContainerEnd(ProgramStateRef State, const MemRegion *Cont, if (CDataPtr) { if (CDataPtr->getEnd()) { return State; - } else { - const auto CData = CDataPtr->newEnd(Sym); - return setContainerData(State, Cont, CData); } - } else { - const auto CData = ContainerData::fromEnd(Sym); + const auto CData = CDataPtr->newEnd(Sym); return setContainerData(State, Cont, CData); } + const auto CData = ContainerData::fromEnd(Sym); + return setContainerData(State, Cont, CData); } const ContainerData *getContainerData(ProgramStateRef State, @@ -767,17 +1160,39 @@ ProgramStateRef relateIteratorPositions(ProgramStateRef State, const IteratorPosition &Pos1, const IteratorPosition &Pos2, bool Equal) { - // Try to compare them and get a defined value auto &SVB = State->getStateManager().getSValBuilder(); + + // FIXME: This code should be reworked as follows: + // 1. Subtract the operands using evalBinOp(). + // 2. Assume that the result doesn't overflow. + // 3. Compare the result to 0. + // 4. Assume the result of the comparison. const auto comparison = SVB.evalBinOp(State, BO_EQ, nonloc::SymbolVal(Pos1.getOffset()), - nonloc::SymbolVal(Pos2.getOffset()), SVB.getConditionType()) - .getAs<DefinedSVal>(); - if (comparison) { - return State->assume(*comparison, Equal); + nonloc::SymbolVal(Pos2.getOffset()), + SVB.getConditionType()); + + assert(comparison.getAs<DefinedSVal>() && + "Symbol comparison must be a `DefinedSVal`"); + + auto NewState = State->assume(comparison.castAs<DefinedSVal>(), Equal); + if (const auto CompSym = comparison.getAsSymbol()) { + assert(isa<SymIntExpr>(CompSym) && + "Symbol comparison must be a `SymIntExpr`"); + assert(BinaryOperator::isComparisonOp( + cast<SymIntExpr>(CompSym)->getOpcode()) && + "Symbol comparison must be a comparison"); + return assumeNoOverflow(NewState, cast<SymIntExpr>(CompSym)->getLHS(), 2); } - return State; + return NewState; +} + +bool isZero(ProgramStateRef State, const NonLoc &Val) { + auto &BVF = State->getBasicVals(); + return compare(State, Val, + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(0))), + BO_EQ); } bool isOutOfRange(ProgramStateRef State, const IteratorPosition &Pos) { @@ -789,6 +1204,13 @@ bool isOutOfRange(ProgramStateRef State, const IteratorPosition &Pos) { // Out of range means less than the begin symbol or greater or equal to the // end symbol. + const auto Beg = CData->getBegin(); + if (Beg) { + if (isLess(State, Pos.getOffset(), Beg)) { + return true; + } + } + const auto End = CData->getEnd(); if (End) { if (isGreaterOrEqual(State, Pos.getOffset(), End)) { @@ -799,25 +1221,30 @@ bool isOutOfRange(ProgramStateRef State, const IteratorPosition &Pos) { return false; } +bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { + return compare(State, Sym1, Sym2, BO_LT); +} + bool isGreaterOrEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { return compare(State, Sym1, Sym2, BO_GE); } bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, BinaryOperator::Opcode Opc) { - auto &SMgr = State->getStateManager(); - auto &SVB = SMgr.getSValBuilder(); + return compare(State, nonloc::SymbolVal(Sym1), nonloc::SymbolVal(Sym2), Opc); +} + +bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2, + BinaryOperator::Opcode Opc) { + auto &SVB = State->getStateManager().getSValBuilder(); const auto comparison = - SVB.evalBinOp(State, Opc, nonloc::SymbolVal(Sym1), - nonloc::SymbolVal(Sym2), SVB.getConditionType()) - .getAs<DefinedSVal>(); + SVB.evalBinOp(State, Opc, NL1, NL2, SVB.getConditionType()); - if(comparison) { - return !!State->assume(*comparison, true); - } + assert(comparison.getAs<DefinedSVal>() && + "Symbol comparison must be a `DefinedSVal`"); - return false; + return !State->assume(comparison.castAs<DefinedSVal>(), false); } } // namespace @@ -831,3 +1258,4 @@ bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, } REGISTER_CHECKER(IteratorRangeChecker) + diff --git a/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp b/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp index 8076ca09591f..2fb627184eb9 100644 --- a/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp @@ -20,7 +20,7 @@ // been called on them. An invalidation method should either invalidate all // the ivars or call another invalidation method (on self). // -// Partial invalidor annotation allows to addess cases when ivars are +// Partial invalidor annotation allows to address cases when ivars are // invalidated by other methods, which might or might not be called from // the invalidation method. The checker checks that each invalidation // method and all the partial methods cumulatively invalidate all ivars. @@ -402,13 +402,13 @@ visit(const ObjCImplementationDecl *ImplD) const { // Find the setter and the getter. const ObjCMethodDecl *SetterD = PD->getSetterMethodDecl(); if (SetterD) { - SetterD = cast<ObjCMethodDecl>(SetterD->getCanonicalDecl()); + SetterD = SetterD->getCanonicalDecl(); PropSetterToIvarMap[SetterD] = ID; } const ObjCMethodDecl *GetterD = PD->getGetterMethodDecl(); if (GetterD) { - GetterD = cast<ObjCMethodDecl>(GetterD->getCanonicalDecl()); + GetterD = GetterD->getCanonicalDecl(); PropGetterToIvarMap[GetterD] = ID; } } @@ -606,7 +606,7 @@ void IvarInvalidationCheckerImpl::MethodCrawler::checkObjCMessageExpr( const ObjCMessageExpr *ME) { const ObjCMethodDecl *MD = ME->getMethodDecl(); if (MD) { - MD = cast<ObjCMethodDecl>(MD->getCanonicalDecl()); + MD = MD->getCanonicalDecl(); MethToIvarMapTy::const_iterator IvI = PropertyGetterToIvarMap.find(MD); if (IvI != PropertyGetterToIvarMap.end()) markInvalidated(IvI->second); @@ -630,7 +630,7 @@ void IvarInvalidationCheckerImpl::MethodCrawler::checkObjCPropertyRefExpr( if (PA->isImplicitProperty()) { const ObjCMethodDecl *MD = PA->getImplicitPropertySetter(); if (MD) { - MD = cast<ObjCMethodDecl>(MD->getCanonicalDecl()); + MD = MD->getCanonicalDecl(); MethToIvarMapTy::const_iterator IvI =PropertyGetterToIvarMap.find(MD); if (IvI != PropertyGetterToIvarMap.end()) markInvalidated(IvI->second); @@ -702,7 +702,7 @@ void IvarInvalidationCheckerImpl::MethodCrawler::VisitObjCMessageExpr( // Check if we call a setter and set the property to 'nil'. if (MD && (ME->getNumArgs() == 1) && isZero(ME->getArg(0))) { - MD = cast<ObjCMethodDecl>(MD->getCanonicalDecl()); + MD = MD->getCanonicalDecl(); MethToIvarMapTy::const_iterator IvI = PropertySetterToIvarMap.find(MD); if (IvI != PropertySetterToIvarMap.end()) { markInvalidated(IvI->second); diff --git a/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp index 655ce33390c9..849b1193c042 100644 --- a/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp @@ -113,8 +113,7 @@ NonLocalizedStringChecker::NonLocalizedStringChecker() { } namespace { -class NonLocalizedStringBRVisitor final - : public BugReporterVisitorImpl<NonLocalizedStringBRVisitor> { +class NonLocalizedStringBRVisitor final : public BugReporterVisitor { const MemRegion *NonLocalizedString; bool Satisfied; @@ -1017,8 +1016,7 @@ NonLocalizedStringBRVisitor::VisitNode(const ExplodedNode *Succ, if (!LiteralExpr) return nullptr; - ProgramStateRef State = Succ->getState(); - SVal LiteralSVal = State->getSVal(LiteralExpr, Succ->getLocationContext()); + SVal LiteralSVal = Succ->getSVal(LiteralExpr); if (LiteralSVal.getAsRegion() != NonLocalizedString) return nullptr; @@ -1108,7 +1106,7 @@ void EmptyLocalizationContextChecker::checkASTDecl( void EmptyLocalizationContextChecker::MethodCrawler::VisitObjCMessageExpr( const ObjCMessageExpr *ME) { - // FIXME: We may be able to use PPCallbacks to check for empy context + // FIXME: We may be able to use PPCallbacks to check for empty context // comments as part of preprocessing and avoid this re-lexing hack. const ObjCInterfaceDecl *OD = ME->getReceiverInterface(); if (!OD) @@ -1389,7 +1387,7 @@ void PluralMisuseChecker::MethodCrawler::reportPluralMisuseError( // Generate the bug report. BR.EmitBasicReport(AC->getDecl(), Checker, "Plural Misuse", "Localizability Issue (Apple)", - "Plural cases are not supported accross all languages. " + "Plural cases are not supported across all languages. " "Use a .stringsdict file instead", PathDiagnosticLocation(S, BR.getSourceManager(), AC)); } diff --git a/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h b/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h index 0ee91cca4793..40eb0631d7c5 100644 --- a/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h +++ b/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h @@ -78,7 +78,7 @@ private: /// Bug visitor class to find the node where the request region was previously /// used in order to include it into the BugReport path. - class RequestNodeVisitor : public BugReporterVisitorImpl<RequestNodeVisitor> { + class RequestNodeVisitor : public BugReporterVisitor { public: RequestNodeVisitor(const MemRegion *const MemoryRegion, const std::string &ErrText) diff --git a/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp index f8473dbd7647..b8ef6701c0df 100644 --- a/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp @@ -120,8 +120,7 @@ private: /// The bug visitor which allows us to print extra diagnostics along the /// BugReport path. For example, showing the allocation site of the leaked /// region. - class SecKeychainBugVisitor - : public BugReporterVisitorImpl<SecKeychainBugVisitor> { + class SecKeychainBugVisitor : public BugReporterVisitor { protected: // The allocated region symbol tracked by the main analysis. SymbolRef Sym; @@ -202,7 +201,7 @@ static bool isBadDeallocationArgument(const MemRegion *Arg) { static SymbolRef getAsPointeeSymbol(const Expr *Expr, CheckerContext &C) { ProgramStateRef State = C.getState(); - SVal ArgV = State->getSVal(Expr, C.getLocationContext()); + SVal ArgV = C.getSVal(Expr); if (Optional<loc::MemRegionVal> X = ArgV.getAs<loc::MemRegionVal>()) { StoreManager& SM = C.getStoreManager(); @@ -297,7 +296,7 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, // Check the argument to the deallocator. const Expr *ArgExpr = CE->getArg(paramIdx); - SVal ArgSVal = State->getSVal(ArgExpr, C.getLocationContext()); + SVal ArgSVal = C.getSVal(ArgExpr); // Undef is reported by another checker. if (ArgSVal.isUndef()) @@ -426,8 +425,7 @@ void MacOSKeychainAPIChecker::checkPostStmt(const CallExpr *CE, // allocated value symbol, since our diagnostics depend on the value // returned by the call. Ex: Data should only be freed if noErr was // returned during allocation.) - SymbolRef RetStatusSymbol = - State->getSVal(CE, C.getLocationContext()).getAsSymbol(); + SymbolRef RetStatusSymbol = C.getSVal(CE).getAsSymbol(); C.getSymbolManager().addSymbolDependency(V, RetStatusSymbol); // Track the allocated value in the checker state. diff --git a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 851114004b96..8f07f413e81f 100644 --- a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -30,6 +30,7 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" +#include "AllocationState.h" #include <climits> #include <utility> @@ -45,7 +46,8 @@ enum AllocationFamily { AF_CXXNew, AF_CXXNewArray, AF_IfNameIndex, - AF_Alloca + AF_Alloca, + AF_InnerBuffer }; class RefState { @@ -134,10 +136,10 @@ enum ReallocPairKind { }; /// \class ReallocPair -/// \brief Stores information about the symbol being reallocated by a call to +/// Stores information about the symbol being reallocated by a call to /// 'realloc' to allow modeling failed reallocation later in the path. struct ReallocPair { - // \brief The symbol which realloc reallocated. + // The symbol which realloc reallocated. SymbolRef ReallocatedSym; ReallocPairKind Kind; @@ -162,6 +164,7 @@ class MallocChecker : public Checker<check::DeadSymbols, check::PreCall, check::PostStmt<CallExpr>, check::PostStmt<CXXNewExpr>, + check::NewAllocator, check::PreStmt<CXXDeleteExpr>, check::PostStmt<BlockExpr>, check::PostObjCMessage, @@ -207,6 +210,8 @@ public: void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; void checkPostStmt(const CXXNewExpr *NE, CheckerContext &C) const; + void checkNewAllocator(const CXXNewExpr *NE, SVal Target, + CheckerContext &C) const; void checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const; void checkPostObjCMessage(const ObjCMethodCall &Call, CheckerContext &C) const; void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const; @@ -253,20 +258,20 @@ private: void initIdentifierInfo(ASTContext &C) const; - /// \brief Determine family of a deallocation expression. + /// Determine family of a deallocation expression. AllocationFamily getAllocationFamily(CheckerContext &C, const Stmt *S) const; - /// \brief Print names of allocators and deallocators. + /// Print names of allocators and deallocators. /// /// \returns true on success. bool printAllocDeallocName(raw_ostream &os, CheckerContext &C, const Expr *E) const; - /// \brief Print expected name of an allocator based on the deallocator's + /// Print expected name of an allocator based on the deallocator's /// family derived from the DeallocExpr. void printExpectedAllocName(raw_ostream &os, CheckerContext &C, const Expr *DeallocExpr) const; - /// \brief Print expected name of a deallocator based on the allocator's + /// Print expected name of a deallocator based on the allocator's /// family. void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) const; @@ -281,10 +286,18 @@ private: bool isStandardNewDelete(const FunctionDecl *FD, ASTContext &C) const; ///@} - /// \brief Perform a zero-allocation check. + /// Process C++ operator new()'s allocation, which is the part of C++ + /// new-expression that goes before the constructor. + void processNewAllocation(const CXXNewExpr *NE, CheckerContext &C, + SVal Target) const; + + /// Perform a zero-allocation check. + /// The optional \p RetVal parameter specifies the newly allocated pointer + /// value; if unspecified, the value of expression \p E is used. ProgramStateRef ProcessZeroAllocation(CheckerContext &C, const Expr *E, const unsigned AllocationSizeArg, - ProgramStateRef State) const; + ProgramStateRef State, + Optional<SVal> RetVal = None) const; ProgramStateRef MallocMemReturnsAttr(CheckerContext &C, const CallExpr *CE, @@ -300,7 +313,7 @@ private: AllocationFamily Family = AF_Malloc); static ProgramStateRef addExtentSize(CheckerContext &C, const CXXNewExpr *NE, - ProgramStateRef State); + ProgramStateRef State, SVal Target); // Check if this malloc() for special flags. At present that means M_ZERO or // __GFP_ZERO (in which case, treat it like calloc). @@ -309,9 +322,12 @@ private: const ProgramStateRef &State) const; /// Update the RefState to reflect the new memory allocation. + /// The optional \p RetVal parameter specifies the newly allocated pointer + /// value; if unspecified, the value of expression \p E is used. static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, ProgramStateRef State, - AllocationFamily Family = AF_Malloc); + AllocationFamily Family = AF_Malloc, + Optional<SVal> RetVal = None); ProgramStateRef FreeMemAttr(CheckerContext &C, const CallExpr *CE, const OwnershipAttr* Att, @@ -337,7 +353,7 @@ private: static ProgramStateRef CallocMem(CheckerContext &C, const CallExpr *CE, ProgramStateRef State); - ///\brief Check if the memory associated with this symbol was released. + ///Check if the memory associated with this symbol was released. bool isReleased(SymbolRef Sym, CheckerContext &C) const; bool checkUseAfterFree(SymbolRef Sym, CheckerContext &C, const Stmt *S) const; @@ -415,8 +431,7 @@ private: /// The bug visitor which allows us to print extra diagnostics along the /// BugReport path. For example, showing the allocation site of the leaked /// region. - class MallocBugVisitor final - : public BugReporterVisitorImpl<MallocBugVisitor> { + class MallocBugVisitor final : public BugReporterVisitor { protected: enum NotificationMode { Normal, @@ -432,15 +447,24 @@ private: // A symbol from when the primary region should have been reallocated. SymbolRef FailedReallocSymbol; + // A C++ destructor stack frame in which memory was released. Used for + // miscellaneous false positive suppression. + const StackFrameContext *ReleaseDestructorLC; + bool IsLeak; public: MallocBugVisitor(SymbolRef S, bool isLeak = false) - : Sym(S), Mode(Normal), FailedReallocSymbol(nullptr), IsLeak(isLeak) {} + : Sym(S), Mode(Normal), FailedReallocSymbol(nullptr), + ReleaseDestructorLC(nullptr), IsLeak(isLeak) {} + + static void *getTag() { + static int Tag = 0; + return &Tag; + } void Profile(llvm::FoldingSetNodeID &ID) const override { - static int X = 0; - ID.AddPointer(&X); + ID.AddPointer(getTag()); ID.AddPointer(Sym); } @@ -456,8 +480,13 @@ private: inline bool isReleased(const RefState *S, const RefState *SPrev, const Stmt *Stmt) { // Did not track -> released. Other state (allocated) -> released. - return (Stmt && (isa<CallExpr>(Stmt) || isa<CXXDeleteExpr>(Stmt)) && - (S && S->isReleased()) && (!SPrev || !SPrev->isReleased())); + // The statement associated with the release might be missing. + bool IsReleased = (S && S->isReleased()) && + (!SPrev || !SPrev->isReleased()); + assert(!IsReleased || + (Stmt && (isa<CallExpr>(Stmt) || isa<CXXDeleteExpr>(Stmt))) || + (!Stmt && S->getAllocationFamily() == AF_InnerBuffer)); + return IsReleased; } inline bool isRelinquished(const RefState *S, const RefState *SPrev, @@ -486,7 +515,7 @@ private: BugReporterContext &BRC, BugReport &BR) override; - std::unique_ptr<PathDiagnosticPiece> + std::shared_ptr<PathDiagnosticPiece> getEndPath(BugReporterContext &BRC, const ExplodedNode *EndPathNode, BugReport &BR) override { if (!IsLeak) @@ -496,7 +525,7 @@ private: PathDiagnosticLocation::createEndOfPath(EndPathNode, BRC.getSourceManager()); // Do not add the statement itself as a range in case of leak. - return llvm::make_unique<PathDiagnosticEventPiece>(L, BR.getDescription(), + return std::make_shared<PathDiagnosticEventPiece>(L, BR.getDescription(), false); } @@ -758,7 +787,7 @@ llvm::Optional<ProgramStateRef> MallocChecker::performKernelMalloc( return None; const Expr *FlagsEx = CE->getArg(CE->getNumArgs() - 1); - const SVal V = State->getSVal(FlagsEx, C.getLocationContext()); + const SVal V = C.getSVal(FlagsEx); if (!V.getAs<NonLoc>()) { // The case where 'V' can be a location can only be due to a bad header, // so in this case bail out. @@ -949,13 +978,15 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { } // Performs a 0-sized allocations check. -ProgramStateRef MallocChecker::ProcessZeroAllocation(CheckerContext &C, - const Expr *E, - const unsigned AllocationSizeArg, - ProgramStateRef State) const { +ProgramStateRef MallocChecker::ProcessZeroAllocation( + CheckerContext &C, const Expr *E, const unsigned AllocationSizeArg, + ProgramStateRef State, Optional<SVal> RetVal) const { if (!State) return nullptr; + if (!RetVal) + RetVal = C.getSVal(E); + const Expr *Arg = nullptr; if (const CallExpr *CE = dyn_cast<CallExpr>(E)) { @@ -972,8 +1003,7 @@ ProgramStateRef MallocChecker::ProcessZeroAllocation(CheckerContext &C, assert(Arg); - Optional<DefinedSVal> DefArgVal = - State->getSVal(Arg, C.getLocationContext()).getAs<DefinedSVal>(); + Optional<DefinedSVal> DefArgVal = C.getSVal(Arg).getAs<DefinedSVal>(); if (!DefArgVal) return State; @@ -988,8 +1018,7 @@ ProgramStateRef MallocChecker::ProcessZeroAllocation(CheckerContext &C, State->assume(SvalBuilder.evalEQ(State, *DefArgVal, Zero)); if (TrueState && !FalseState) { - SVal retVal = State->getSVal(E, C.getLocationContext()); - SymbolRef Sym = retVal.getAsLocSymbol(); + SymbolRef Sym = RetVal->getAsLocSymbol(); if (!Sym) return State; @@ -1050,9 +1079,9 @@ static bool treatUnusedNewEscaped(const CXXNewExpr *NE) { return false; } -void MallocChecker::checkPostStmt(const CXXNewExpr *NE, - CheckerContext &C) const { - +void MallocChecker::processNewAllocation(const CXXNewExpr *NE, + CheckerContext &C, + SVal Target) const { if (NE->getNumPlacementArgs()) for (CXXNewExpr::const_arg_iterator I = NE->placement_arg_begin(), E = NE->placement_arg_end(); I != E; ++I) @@ -1072,37 +1101,48 @@ void MallocChecker::checkPostStmt(const CXXNewExpr *NE, // MallocUpdateRefState() instead of MallocMemAux() which breakes the // existing binding. State = MallocUpdateRefState(C, NE, State, NE->isArray() ? AF_CXXNewArray - : AF_CXXNew); - State = addExtentSize(C, NE, State); - State = ProcessZeroAllocation(C, NE, 0, State); + : AF_CXXNew, Target); + State = addExtentSize(C, NE, State, Target); + State = ProcessZeroAllocation(C, NE, 0, State, Target); C.addTransition(State); } +void MallocChecker::checkPostStmt(const CXXNewExpr *NE, + CheckerContext &C) const { + if (!C.getAnalysisManager().getAnalyzerOptions().mayInlineCXXAllocator()) + processNewAllocation(NE, C, C.getSVal(NE)); +} + +void MallocChecker::checkNewAllocator(const CXXNewExpr *NE, SVal Target, + CheckerContext &C) const { + if (!C.wasInlined) + processNewAllocation(NE, C, Target); +} + // Sets the extent value of the MemRegion allocated by // new expression NE to its size in Bytes. // ProgramStateRef MallocChecker::addExtentSize(CheckerContext &C, const CXXNewExpr *NE, - ProgramStateRef State) { + ProgramStateRef State, + SVal Target) { if (!State) return nullptr; SValBuilder &svalBuilder = C.getSValBuilder(); SVal ElementCount; - const LocationContext *LCtx = C.getLocationContext(); const SubRegion *Region; if (NE->isArray()) { const Expr *SizeExpr = NE->getArraySize(); - ElementCount = State->getSVal(SizeExpr, C.getLocationContext()); + ElementCount = C.getSVal(SizeExpr); // Store the extent size for the (symbolic)region // containing the elements. - Region = (State->getSVal(NE, LCtx)) - .getAsRegion() + Region = Target.getAsRegion() ->getAs<SubRegion>() - ->getSuperRegion() + ->StripCasts() ->getAs<SubRegion>(); } else { ElementCount = svalBuilder.makeIntVal(1, true); - Region = (State->getSVal(NE, LCtx)).getAsRegion()->getAs<SubRegion>(); + Region = Target.getAsRegion()->getAs<SubRegion>(); } assert(Region); @@ -1199,7 +1239,8 @@ MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallExpr *CE, OwnershipAttr::args_iterator I = Att->args_begin(), E = Att->args_end(); if (I != E) { - return MallocMemAux(C, CE, CE->getArg(*I), UndefinedVal(), State); + return MallocMemAux(C, CE, CE->getArg(I->getASTIndex()), UndefinedVal(), + State); } return MallocMemAux(C, CE, UnknownVal(), UndefinedVal(), State); } @@ -1212,8 +1253,7 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, if (!State) return nullptr; - return MallocMemAux(C, CE, State->getSVal(SizeEx, C.getLocationContext()), - Init, State, Family); + return MallocMemAux(C, CE, C.getSVal(SizeEx), Init, State, Family); } ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, @@ -1239,7 +1279,7 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, State = State->BindExpr(CE, C.getLocationContext(), RetVal); // Fill the region with the initialization value. - State = State->bindDefault(RetVal, Init, LCtx); + State = State->bindDefaultInitial(RetVal, Init, LCtx); // Set the region's extent equal to the Size parameter. const SymbolicRegion *R = @@ -1263,18 +1303,22 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, ProgramStateRef MallocChecker::MallocUpdateRefState(CheckerContext &C, const Expr *E, ProgramStateRef State, - AllocationFamily Family) { + AllocationFamily Family, + Optional<SVal> RetVal) { if (!State) return nullptr; // Get the return value. - SVal retVal = State->getSVal(E, C.getLocationContext()); + if (!RetVal) + RetVal = C.getSVal(E); // We expect the malloc functions to return a pointer. - if (!retVal.getAs<Loc>()) + if (!RetVal->getAs<Loc>()) return nullptr; - SymbolRef Sym = retVal.getAsLocSymbol(); + SymbolRef Sym = RetVal->getAsLocSymbol(); + // This is a return value of a function that was not inlined, such as malloc() + // or new(). We've checked that in the caller. Therefore, it must be a symbol. assert(Sym); // Set the symbol's state to Allocated. @@ -1294,9 +1338,9 @@ ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C, bool ReleasedAllocated = false; for (const auto &Arg : Att->args()) { - ProgramStateRef StateI = FreeMemAux(C, CE, State, Arg, - Att->getOwnKind() == OwnershipAttr::Holds, - ReleasedAllocated); + ProgramStateRef StateI = FreeMemAux( + C, CE, State, Arg.getASTIndex(), + Att->getOwnKind() == OwnershipAttr::Holds, ReleasedAllocated); if (StateI) State = StateI; } @@ -1429,6 +1473,7 @@ void MallocChecker::printExpectedAllocName(raw_ostream &os, CheckerContext &C, case AF_CXXNew: os << "'new'"; return; case AF_CXXNewArray: os << "'new[]'"; return; case AF_IfNameIndex: os << "'if_nameindex()'"; return; + case AF_InnerBuffer: os << "container-specific allocator"; return; case AF_Alloca: case AF_None: llvm_unreachable("not a deallocation expression"); } @@ -1441,6 +1486,7 @@ void MallocChecker::printExpectedDeallocName(raw_ostream &os, case AF_CXXNew: os << "'delete'"; return; case AF_CXXNewArray: os << "'delete[]'"; return; case AF_IfNameIndex: os << "'if_freenameindex()'"; return; + case AF_InnerBuffer: os << "container-specific deallocator"; return; case AF_Alloca: case AF_None: llvm_unreachable("suspicious argument"); } @@ -1457,7 +1503,7 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, if (!State) return nullptr; - SVal ArgVal = State->getSVal(ArgExpr, C.getLocationContext()); + SVal ArgVal = C.getSVal(ArgExpr); if (!ArgVal.getAs<DefinedOrUnknownSVal>()) return nullptr; DefinedOrUnknownSVal location = ArgVal.castAs<DefinedOrUnknownSVal>(); @@ -1615,7 +1661,9 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family, return Optional<MallocChecker::CheckKind>(); } case AF_CXXNew: - case AF_CXXNewArray: { + case AF_CXXNewArray: + // FIXME: Add new CheckKind for AF_InnerBuffer. + case AF_InnerBuffer: { if (IsALeakCheck) { if (ChecksEnabled[CK_NewDeleteLeaksChecker]) return CK_NewDeleteLeaksChecker; @@ -1945,6 +1993,11 @@ void MallocChecker::ReportUseAfterFree(CheckerContext &C, SourceRange Range, R->markInteresting(Sym); R->addRange(Range); R->addVisitor(llvm::make_unique<MallocBugVisitor>(Sym)); + + const RefState *RS = C.getState()->get<RegionState>(Sym); + if (RS->getAllocationFamily() == AF_InnerBuffer) + R->addVisitor(allocation_state::getInnerPointerBRVisitor(Sym)); + C.emitReport(std::move(R)); } } @@ -2047,8 +2100,8 @@ void MallocChecker::ReportFunctionPointerFree(CheckerContext &C, SVal ArgVal, if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_BadFree[*CheckKind]) - BT_BadFree[*CheckKind].reset( - new BugType(CheckNames[*CheckKind], "Bad free", "Memory Error")); + BT_BadFree[*CheckKind].reset(new BugType( + CheckNames[*CheckKind], "Bad free", categories::MemoryError)); SmallString<100> Buf; llvm::raw_svector_ostream Os(Buf); @@ -2084,8 +2137,7 @@ ProgramStateRef MallocChecker::ReallocMemAux(CheckerContext &C, return nullptr; const Expr *arg0Expr = CE->getArg(0); - const LocationContext *LCtx = C.getLocationContext(); - SVal Arg0Val = State->getSVal(arg0Expr, LCtx); + SVal Arg0Val = C.getSVal(arg0Expr); if (!Arg0Val.getAs<DefinedOrUnknownSVal>()) return nullptr; DefinedOrUnknownSVal arg0Val = Arg0Val.castAs<DefinedOrUnknownSVal>(); @@ -2099,7 +2151,7 @@ ProgramStateRef MallocChecker::ReallocMemAux(CheckerContext &C, const Expr *Arg1 = CE->getArg(1); // Get the value of the size argument. - SVal TotalSize = State->getSVal(Arg1, LCtx); + SVal TotalSize = C.getSVal(Arg1); if (SuffixWithN) TotalSize = evalMulForBufferSize(C, Arg1, CE->getArg(2)); if (!TotalSize.getAs<DefinedOrUnknownSVal>()) @@ -2133,7 +2185,7 @@ ProgramStateRef MallocChecker::ReallocMemAux(CheckerContext &C, // Get the from and to pointer symbols as in toPtr = realloc(fromPtr, size). assert(!PrtIsNull); SymbolRef FromPtr = arg0Val.getAsSymbol(); - SVal RetVal = State->getSVal(CE, LCtx); + SVal RetVal = C.getSVal(CE); SymbolRef ToPtr = RetVal.getAsSymbol(); if (!FromPtr || !ToPtr) return nullptr; @@ -2216,7 +2268,7 @@ MallocChecker::getAllocationSite(const ExplodedNode *N, SymbolRef Sym, // Do not show local variables belonging to a function other than // where the error is reported. if (!VR || - (VR->getStackFrame() == LeakContext->getCurrentStackFrame())) + (VR->getStackFrame() == LeakContext->getStackFrame())) ReferenceRegion = MR; } } @@ -2406,7 +2458,7 @@ void MallocChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const { // Check if we are returning a symbol. ProgramStateRef State = C.getState(); - SVal RetVal = State->getSVal(E, C.getLocationContext()); + SVal RetVal = C.getSVal(E); SymbolRef Sym = RetVal.getAsSymbol(); if (!Sym) // If we are returning a field of the allocated struct or an array element, @@ -2436,8 +2488,7 @@ void MallocChecker::checkPostStmt(const BlockExpr *BE, ProgramStateRef state = C.getState(); const BlockDataRegion *R = - cast<BlockDataRegion>(state->getSVal(BE, - C.getLocationContext()).getAsRegion()); + cast<BlockDataRegion>(C.getSVal(BE).getAsRegion()); BlockDataRegion::referenced_vars_iterator I = R->referenced_vars_begin(), E = R->referenced_vars_end(); @@ -2793,36 +2844,133 @@ static SymbolRef findFailedReallocSymbol(ProgramStateRef currState, return nullptr; } +static bool isReferenceCountingPointerDestructor(const CXXDestructorDecl *DD) { + if (const IdentifierInfo *II = DD->getParent()->getIdentifier()) { + StringRef N = II->getName(); + if (N.contains_lower("ptr") || N.contains_lower("pointer")) { + if (N.contains_lower("ref") || N.contains_lower("cnt") || + N.contains_lower("intrusive") || N.contains_lower("shared")) { + return true; + } + } + } + return false; +} + std::shared_ptr<PathDiagnosticPiece> MallocChecker::MallocBugVisitor::VisitNode( const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) { + ProgramStateRef state = N->getState(); ProgramStateRef statePrev = PrevN->getState(); const RefState *RS = state->get<RegionState>(Sym); const RefState *RSPrev = statePrev->get<RegionState>(Sym); - if (!RS) - return nullptr; const Stmt *S = PathDiagnosticLocation::getStmt(N); - if (!S) + // When dealing with containers, we sometimes want to give a note + // even if the statement is missing. + if (!S && (!RS || RS->getAllocationFamily() != AF_InnerBuffer)) return nullptr; + const LocationContext *CurrentLC = N->getLocationContext(); + + // If we find an atomic fetch_add or fetch_sub within the destructor in which + // the pointer was released (before the release), this is likely a destructor + // of a shared pointer. + // Because we don't model atomics, and also because we don't know that the + // original reference count is positive, we should not report use-after-frees + // on objects deleted in such destructors. This can probably be improved + // through better shared pointer modeling. + if (ReleaseDestructorLC) { + if (const auto *AE = dyn_cast<AtomicExpr>(S)) { + AtomicExpr::AtomicOp Op = AE->getOp(); + if (Op == AtomicExpr::AO__c11_atomic_fetch_add || + Op == AtomicExpr::AO__c11_atomic_fetch_sub) { + if (ReleaseDestructorLC == CurrentLC || + ReleaseDestructorLC->isParentOf(CurrentLC)) { + BR.markInvalid(getTag(), S); + } + } + } + } + // FIXME: We will eventually need to handle non-statement-based events // (__attribute__((cleanup))). // Find out if this is an interesting point and what is the kind. - const char *Msg = nullptr; + StringRef Msg; StackHintGeneratorForSymbol *StackHint = nullptr; + SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); + if (Mode == Normal) { if (isAllocated(RS, RSPrev, S)) { Msg = "Memory is allocated"; StackHint = new StackHintGeneratorForSymbol(Sym, "Returned allocated memory"); } else if (isReleased(RS, RSPrev, S)) { - Msg = "Memory is released"; + const auto Family = RS->getAllocationFamily(); + switch (Family) { + case AF_Alloca: + case AF_Malloc: + case AF_CXXNew: + case AF_CXXNewArray: + case AF_IfNameIndex: + Msg = "Memory is released"; + break; + case AF_InnerBuffer: { + OS << "Inner pointer invalidated by call to "; + if (N->getLocation().getKind() == ProgramPoint::PostImplicitCallKind) { + OS << "destructor"; + } else { + OS << "'"; + const Stmt *S = RS->getStmt(); + if (const auto *MemCallE = dyn_cast<CXXMemberCallExpr>(S)) { + OS << MemCallE->getMethodDecl()->getNameAsString(); + } else if (const auto *OpCallE = dyn_cast<CXXOperatorCallExpr>(S)) { + OS << OpCallE->getDirectCallee()->getNameAsString(); + } + OS << "'"; + } + Msg = OS.str(); + break; + } + case AF_None: + llvm_unreachable("Unhandled allocation family!"); + } StackHint = new StackHintGeneratorForSymbol(Sym, "Returning; memory was released"); + + // See if we're releasing memory while inlining a destructor + // (or one of its callees). This turns on various common + // false positive suppressions. + bool FoundAnyDestructor = false; + for (const LocationContext *LC = CurrentLC; LC; LC = LC->getParent()) { + if (const auto *DD = dyn_cast<CXXDestructorDecl>(LC->getDecl())) { + if (isReferenceCountingPointerDestructor(DD)) { + // This immediately looks like a reference-counting destructor. + // We're bad at guessing the original reference count of the object, + // so suppress the report for now. + BR.markInvalid(getTag(), DD); + } else if (!FoundAnyDestructor) { + assert(!ReleaseDestructorLC && + "There can be only one release point!"); + // Suspect that it's a reference counting pointer destructor. + // On one of the next nodes might find out that it has atomic + // reference counting operations within it (see the code above), + // and if so, we'd conclude that it likely is a reference counting + // pointer destructor. + ReleaseDestructorLC = LC->getStackFrame(); + // It is unlikely that releasing memory is delegated to a destructor + // inside a destructor of a shared pointer, because it's fairly hard + // to pass the information that the pointer indeed needs to be + // released into it. So we're only interested in the innermost + // destructor. + FoundAnyDestructor = true; + } + } + } } else if (isRelinquished(RS, RSPrev, S)) { Msg = "Memory ownership is transferred"; StackHint = new StackHintGeneratorForSymbol(Sym, ""); @@ -2856,13 +3004,24 @@ std::shared_ptr<PathDiagnosticPiece> MallocChecker::MallocBugVisitor::VisitNode( } } - if (!Msg) + if (Msg.empty()) return nullptr; assert(StackHint); // Generate the extra diagnostic. - PathDiagnosticLocation Pos(S, BRC.getSourceManager(), - N->getLocationContext()); + PathDiagnosticLocation Pos; + if (!S) { + assert(RS->getAllocationFamily() == AF_InnerBuffer); + auto PostImplCall = N->getLocation().getAs<PostImplicitCall>(); + if (!PostImplCall) + return nullptr; + Pos = PathDiagnosticLocation(PostImplCall->getLocation(), + BRC.getSourceManager()); + } else { + Pos = PathDiagnosticLocation(S, BRC.getSourceManager(), + N->getLocationContext()); + } + return std::make_shared<PathDiagnosticEventPiece>(Pos, Msg, true, StackHint); } @@ -2890,6 +3049,20 @@ void MallocChecker::printState(raw_ostream &Out, ProgramStateRef State, } } +namespace clang { +namespace ento { +namespace allocation_state { + +ProgramStateRef +markReleased(ProgramStateRef State, SymbolRef Sym, const Expr *Origin) { + AllocationFamily Family = AF_InnerBuffer; + return State->set<RegionState>(Sym, RefState::getReleased(Family, Origin)); +} + +} // end namespace allocation_state +} // end namespace ento +} // end namespace clang + void ento::registerNewDeleteLeaksChecker(CheckerManager &mgr) { registerCStringCheckerBasic(mgr); MallocChecker *checker = mgr.registerChecker<MallocChecker>(); @@ -2900,8 +3073,13 @@ void ento::registerNewDeleteLeaksChecker(CheckerManager &mgr) { mgr.getCurrentCheckName(); // We currently treat NewDeleteLeaks checker as a subchecker of NewDelete // checker. - if (!checker->ChecksEnabled[MallocChecker::CK_NewDeleteChecker]) + if (!checker->ChecksEnabled[MallocChecker::CK_NewDeleteChecker]) { checker->ChecksEnabled[MallocChecker::CK_NewDeleteChecker] = true; + // FIXME: This does not set the correct name, but without this workaround + // no name will be set at all. + checker->CheckNames[MallocChecker::CK_NewDeleteChecker] = + mgr.getCurrentCheckName(); + } } #define REGISTER_CHECKER(name) \ diff --git a/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp b/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp index 497978f07815..19c1d077afa1 100644 --- a/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp @@ -46,7 +46,7 @@ class MisusedMovedObjectChecker : public Checker<check::PreCall, check::PostCall, check::EndFunction, check::DeadSymbols, check::RegionChanges> { public: - void checkEndFunction(CheckerContext &C) const; + void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; void checkPreCall(const CallEvent &MC, CheckerContext &C) const; void checkPostCall(const CallEvent &MC, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; @@ -61,7 +61,7 @@ public: private: enum MisuseKind {MK_FunCall, MK_Copy, MK_Move}; - class MovedBugVisitor : public BugReporterVisitorImpl<MovedBugVisitor> { + class MovedBugVisitor : public BugReporterVisitor { public: MovedBugVisitor(const MemRegion *R) : Region(R), Found(false) {} @@ -101,8 +101,6 @@ static ProgramStateRef removeFromState(ProgramStateRef State, const MemRegion *Region) { if (!Region) return State; - // Note: The isSubRegionOf function is not reflexive. - State = State->remove<TrackedRegionMap>(Region); for (auto &E : State->get<TrackedRegionMap>()) { if (E.first->isSubRegionOf(Region)) State = State->remove<TrackedRegionMap>(E.first); @@ -224,7 +222,8 @@ ExplodedNode *MisusedMovedObjectChecker::reportBug(const MemRegion *Region, // Removing the function parameters' MemRegion from the state. This is needed // for PODs where the trivial destructor does not even created nor executed. -void MisusedMovedObjectChecker::checkEndFunction(CheckerContext &C) const { +void MisusedMovedObjectChecker::checkEndFunction(const ReturnStmt *RS, + CheckerContext &C) const { auto State = C.getState(); TrackedRegionMapTy Objects = State->get<TrackedRegionMap>(); if (Objects.isEmpty()) diff --git a/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp b/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp new file mode 100644 index 000000000000..5060b0e0a6e0 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp @@ -0,0 +1,88 @@ +// MmapWriteExecChecker.cpp - Check for the prot argument -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This checker tests the 3rd argument of mmap's calls to check if +// it is writable and executable in the same time. It's somehow +// an optional checker since for example in JIT libraries it is pretty common. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" + +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; +using llvm::APSInt; + +namespace { +class MmapWriteExecChecker : public Checker<check::PreCall> { + CallDescription MmapFn; + CallDescription MprotectFn; + static int ProtWrite; + static int ProtExec; + static int ProtRead; + mutable std::unique_ptr<BugType> BT; +public: + MmapWriteExecChecker() : MmapFn("mmap", 6), MprotectFn("mprotect", 3) {} + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + int ProtExecOv; + int ProtReadOv; +}; +} + +int MmapWriteExecChecker::ProtWrite = 0x02; +int MmapWriteExecChecker::ProtExec = 0x04; +int MmapWriteExecChecker::ProtRead = 0x01; + +void MmapWriteExecChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + if (Call.isCalled(MmapFn) || Call.isCalled(MprotectFn)) { + SVal ProtVal = Call.getArgSVal(2); + Optional<nonloc::ConcreteInt> ProtLoc = ProtVal.getAs<nonloc::ConcreteInt>(); + int64_t Prot = ProtLoc->getValue().getSExtValue(); + if (ProtExecOv != ProtExec) + ProtExec = ProtExecOv; + if (ProtReadOv != ProtRead) + ProtRead = ProtReadOv; + + // Wrong settings + if (ProtRead == ProtExec) + return; + + if ((Prot & (ProtWrite | ProtExec)) == (ProtWrite | ProtExec)) { + if (!BT) + BT.reset(new BugType(this, "W^X check fails, Write Exec prot flags set", "Security")); + + ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return; + + auto Report = llvm::make_unique<BugReport>( + *BT, "Both PROT_WRITE and PROT_EXEC flags are set. This can " + "lead to exploitable memory regions, which could be overwritten " + "with malicious code", N); + Report->addRange(Call.getArgSourceRange(2)); + C.emitReport(std::move(Report)); + } + } +} + +void ento::registerMmapWriteExecChecker(CheckerManager &mgr) { + MmapWriteExecChecker *Mwec = + mgr.registerChecker<MmapWriteExecChecker>(); + Mwec->ProtExecOv = + mgr.getAnalyzerOptions().getOptionAsInteger("MmapProtExec", 0x04, Mwec); + Mwec->ProtReadOv = + mgr.getAnalyzerOptions().getOptionAsInteger("MmapProtRead", 0x01, Mwec); +} diff --git a/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp b/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp index 559c75d7a5b0..2bd68b625c1f 100644 --- a/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp @@ -186,8 +186,7 @@ static void setFlag(ProgramStateRef state, SVal val, CheckerContext &C) { } static QualType parameterTypeFromSVal(SVal val, CheckerContext &C) { - const StackFrameContext * - SFC = C.getLocationContext()->getCurrentStackFrame(); + const StackFrameContext * SFC = C.getStackFrame(); if (Optional<loc::MemRegionVal> X = val.getAs<loc::MemRegionVal>()) { const MemRegion* R = X->getRegion(); if (const VarRegion *VR = R->getAs<VarRegion>()) diff --git a/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp b/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp index 6d05159e51b0..01d2c0491b85 100644 --- a/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp @@ -44,13 +44,9 @@ public: }; } // end anonymous namespace -void NonNullParamChecker::checkPreCall(const CallEvent &Call, - CheckerContext &C) const { +/// \return Bitvector marking non-null attributes. +static llvm::SmallBitVector getNonNullAttrs(const CallEvent &Call) { const Decl *FD = Call.getDecl(); - if (!FD) - return; - - // Merge all non-null attributes unsigned NumArgs = Call.getNumArgs(); llvm::SmallBitVector AttrNonNull(NumArgs); for (const auto *NonNull : FD->specific_attrs<NonNullAttr>()) { @@ -58,49 +54,54 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, AttrNonNull.set(0, NumArgs); break; } - for (unsigned Val : NonNull->args()) { - if (Val >= NumArgs) + for (const ParamIdx &Idx : NonNull->args()) { + unsigned IdxAST = Idx.getASTIndex(); + if (IdxAST >= NumArgs) continue; - AttrNonNull.set(Val); + AttrNonNull.set(IdxAST); } } + return AttrNonNull; +} - ProgramStateRef state = C.getState(); +void NonNullParamChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + if (!Call.getDecl()) + return; + + llvm::SmallBitVector AttrNonNull = getNonNullAttrs(Call); + unsigned NumArgs = Call.getNumArgs(); - CallEvent::param_type_iterator TyI = Call.param_type_begin(), - TyE = Call.param_type_end(); + ProgramStateRef state = C.getState(); + ArrayRef<ParmVarDecl*> parms = Call.parameters(); for (unsigned idx = 0; idx < NumArgs; ++idx) { + // For vararg functions, a corresponding parameter decl may not exist. + bool HasParam = idx < parms.size(); // Check if the parameter is a reference. We want to report when reference // to a null pointer is passed as a parameter. - bool haveRefTypeParam = false; - if (TyI != TyE) { - haveRefTypeParam = (*TyI)->isReferenceType(); - TyI++; - } - + bool haveRefTypeParam = + HasParam ? parms[idx]->getType()->isReferenceType() : false; bool haveAttrNonNull = AttrNonNull[idx]; - if (!haveAttrNonNull) { - // Check if the parameter is also marked 'nonnull'. - ArrayRef<ParmVarDecl*> parms = Call.parameters(); - if (idx < parms.size()) - haveAttrNonNull = parms[idx]->hasAttr<NonNullAttr>(); - } - if (!haveRefTypeParam && !haveAttrNonNull) + // Check if the parameter is also marked 'nonnull'. + if (!haveAttrNonNull && HasParam) + haveAttrNonNull = parms[idx]->hasAttr<NonNullAttr>(); + + if (!haveAttrNonNull && !haveRefTypeParam) continue; // If the value is unknown or undefined, we can't perform this check. const Expr *ArgE = Call.getArgExpr(idx); SVal V = Call.getArgSVal(idx); - Optional<DefinedSVal> DV = V.getAs<DefinedSVal>(); + auto DV = V.getAs<DefinedSVal>(); if (!DV) continue; - // Process the case when the argument is not a location. assert(!haveRefTypeParam || DV->getAs<Loc>()); + // Process the case when the argument is not a location. if (haveAttrNonNull && !DV->getAs<Loc>()) { // If the argument is a union type, we want to handle a potential // transparent_union GCC extension. @@ -112,66 +113,63 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, if (!UT || !UT->getDecl()->hasAttr<TransparentUnionAttr>()) continue; - if (Optional<nonloc::CompoundVal> CSV = - DV->getAs<nonloc::CompoundVal>()) { - nonloc::CompoundVal::iterator CSV_I = CSV->begin(); - assert(CSV_I != CSV->end()); - V = *CSV_I; - DV = V.getAs<DefinedSVal>(); - assert(++CSV_I == CSV->end()); - // FIXME: Handle (some_union){ some_other_union_val }, which turns into - // a LazyCompoundVal inside a CompoundVal. - if (!V.getAs<Loc>()) - continue; - // Retrieve the corresponding expression. - if (const CompoundLiteralExpr *CE = dyn_cast<CompoundLiteralExpr>(ArgE)) - if (const InitListExpr *IE = - dyn_cast<InitListExpr>(CE->getInitializer())) - ArgE = dyn_cast<Expr>(*(IE->begin())); - - } else { - // FIXME: Handle LazyCompoundVals? + auto CSV = DV->getAs<nonloc::CompoundVal>(); + + // FIXME: Handle LazyCompoundVals? + if (!CSV) continue; - } + + V = *(CSV->begin()); + DV = V.getAs<DefinedSVal>(); + assert(++CSV->begin() == CSV->end()); + // FIXME: Handle (some_union){ some_other_union_val }, which turns into + // a LazyCompoundVal inside a CompoundVal. + if (!V.getAs<Loc>()) + continue; + + // Retrieve the corresponding expression. + if (const auto *CE = dyn_cast<CompoundLiteralExpr>(ArgE)) + if (const auto *IE = dyn_cast<InitListExpr>(CE->getInitializer())) + ArgE = dyn_cast<Expr>(*(IE->begin())); } ConstraintManager &CM = C.getConstraintManager(); ProgramStateRef stateNotNull, stateNull; std::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV); - if (stateNull) { - if (!stateNotNull) { - // Generate an error node. Check for a null node in case - // we cache out. - if (ExplodedNode *errorNode = C.generateErrorNode(stateNull)) { - - std::unique_ptr<BugReport> R; - if (haveAttrNonNull) - R = genReportNullAttrNonNull(errorNode, ArgE); - else if (haveRefTypeParam) - R = genReportReferenceToNullPointer(errorNode, ArgE); - - // Highlight the range of the argument that was null. - R->addRange(Call.getArgSourceRange(idx)); - - // Emit the bug report. - C.emitReport(std::move(R)); - } - - // Always return. Either we cached out or we just emitted an error. - return; + // Generate an error node. Check for a null node in case + // we cache out. + if (stateNull && !stateNotNull) { + if (ExplodedNode *errorNode = C.generateErrorNode(stateNull)) { + + std::unique_ptr<BugReport> R; + if (haveAttrNonNull) + R = genReportNullAttrNonNull(errorNode, ArgE); + else if (haveRefTypeParam) + R = genReportReferenceToNullPointer(errorNode, ArgE); + + // Highlight the range of the argument that was null. + R->addRange(Call.getArgSourceRange(idx)); + + // Emit the bug report. + C.emitReport(std::move(R)); } + + // Always return. Either we cached out or we just emitted an error. + return; + } + + if (stateNull) { if (ExplodedNode *N = C.generateSink(stateNull, C.getPredecessor())) { ImplicitNullDerefEvent event = { - V, false, N, &C.getBugReporter(), - /*IsDirectDereference=*/haveRefTypeParam}; + V, false, N, &C.getBugReporter(), + /*IsDirectDereference=*/haveRefTypeParam}; dispatchEvent(event); } } // If a pointer value passed the check we should assume that it is // indeed not null from this point forward. - assert(stateNotNull); state = stateNotNull; } diff --git a/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp b/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp index 0b4ecb41d20f..6f3180eb839a 100644 --- a/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp @@ -54,7 +54,7 @@ private: } // namespace -/// Lazily initialize cache for required identifier informations. +/// Lazily initialize cache for required identifier information. void NonnullGlobalConstantsChecker::initIdentifierInfo(ASTContext &Ctx) const { if (NSStringII) return; @@ -73,9 +73,9 @@ void NonnullGlobalConstantsChecker::checkLocation(SVal location, bool isLoad, return; ProgramStateRef State = C.getState(); - SVal V = State->getSVal(location.castAs<Loc>()); if (isGlobalConstString(location)) { + SVal V = State->getSVal(location.castAs<Loc>()); Optional<DefinedOrUnknownSVal> Constr = V.getAs<DefinedOrUnknownSVal>(); if (Constr) { diff --git a/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp index fa9a317683ba..7d1ca61c97a9 100644 --- a/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -30,6 +30,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" @@ -40,21 +41,6 @@ using namespace clang; using namespace ento; namespace { -// Do not reorder! The getMostNullable method relies on the order. -// Optimization: Most pointers expected to be unspecified. When a symbol has an -// unspecified or nonnull type non of the rules would indicate any problem for -// that symbol. For this reason only nullable and contradicted nullability are -// stored for a symbol. When a symbol is already contradicted, it can not be -// casted back to nullable. -enum class Nullability : char { - Contradicted, // Tracked nullability is contradicted by an explicit cast. Do - // not report any nullability related issue for this symbol. - // This nullability is propagated aggressively to avoid false - // positive results. See the comment on getMostNullable method. - Nullable, - Unspecified, - Nonnull -}; /// Returns the most nullable nullability. This is used for message expressions /// like [receiver method], where the nullability of this expression is either @@ -142,8 +128,7 @@ public: DefaultBool NeedTracking; private: - class NullabilityBugVisitor - : public BugReporterVisitorImpl<NullabilityBugVisitor> { + class NullabilityBugVisitor : public BugReporterVisitor { public: NullabilityBugVisitor(const MemRegion *M) : Region(M) {} @@ -265,7 +250,7 @@ REGISTER_MAP_WITH_PROGRAMSTATE(NullabilityMap, const MemRegion *, // initial direct violation has been discovered, and (3) warning after a direct // violation that has been implicitly or explicitly suppressed (for // example, with a cast of NULL to _Nonnull). In essence, once an invariant -// violation is detected on a path, this checker will be esentially turned off +// violation is detected on a path, this checker will be essentially turned off // for the rest of the analysis // // The analyzer takes this approach (rather than generating a sink node) to @@ -345,17 +330,6 @@ NullabilityChecker::NullabilityBugVisitor::VisitNode(const ExplodedNode *N, nullptr); } -static Nullability getNullabilityAnnotation(QualType Type) { - const auto *AttrType = Type->getAs<AttributedType>(); - if (!AttrType) - return Nullability::Unspecified; - if (AttrType->getAttrKind() == AttributedType::attr_nullable) - return Nullability::Nullable; - else if (AttrType->getAttrKind() == AttributedType::attr_nonnull) - return Nullability::Nonnull; - return Nullability::Unspecified; -} - /// Returns true when the value stored at the given location is null /// and the passed in type is nonnnull. static bool checkValueAtLValForInvariantViolation(ProgramStateRef State, @@ -560,8 +534,7 @@ void NullabilityChecker::checkPreStmt(const ReturnStmt *S, if (State->get<InvariantViolated>()) return; - auto RetSVal = - State->getSVal(S, C.getLocationContext()).getAs<DefinedOrUnknownSVal>(); + auto RetSVal = C.getSVal(S).getAs<DefinedOrUnknownSVal>(); if (!RetSVal) return; @@ -873,7 +846,7 @@ void NullabilityChecker::checkPostObjCMessage(const ObjCMethodCall &M, // are either item retrieval related or not interesting nullability wise. // Using this fact, to keep the code easier to read just ignore the return // value of every instance method of dictionaries. - if (M.isInstanceMessage() && Name.find("Dictionary") != StringRef::npos) { + if (M.isInstanceMessage() && Name.contains("Dictionary")) { State = State->set<NullabilityMap>(ReturnRegion, Nullability::Contradicted); C.addTransition(State); @@ -881,7 +854,7 @@ void NullabilityChecker::checkPostObjCMessage(const ObjCMethodCall &M, } // For similar reasons ignore some methods of Cocoa arrays. StringRef FirstSelectorSlot = M.getSelector().getNameForSlot(0); - if (Name.find("Array") != StringRef::npos && + if (Name.contains("Array") && (FirstSelectorSlot == "firstObject" || FirstSelectorSlot == "lastObject")) { State = @@ -894,7 +867,7 @@ void NullabilityChecker::checkPostObjCMessage(const ObjCMethodCall &M, // encodings are used. Using lossless encodings is so frequent that ignoring // this class of methods reduced the emitted diagnostics by about 30% on // some projects (and all of that was false positives). - if (Name.find("String") != StringRef::npos) { + if (Name.contains("String")) { for (auto Param : M.parameters()) { if (Param->getName() == "encoding") { State = State->set<NullabilityMap>(ReturnRegion, @@ -977,8 +950,7 @@ void NullabilityChecker::checkPostStmt(const ExplicitCastExpr *CE, if (DestNullability == Nullability::Unspecified) return; - auto RegionSVal = - State->getSVal(CE, C.getLocationContext()).getAs<DefinedOrUnknownSVal>(); + auto RegionSVal = C.getSVal(CE).getAs<DefinedOrUnknownSVal>(); const MemRegion *Region = getTrackRegion(*RegionSVal); if (!Region) return; diff --git a/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp b/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp index 40e379cb2efc..d1749cfdbe27 100644 --- a/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp @@ -270,8 +270,10 @@ void NumberObjectConversionChecker::checkASTCodeBody(const Decl *D, hasRHS(SuspiciousNumberObjectExprM))); auto ConversionThroughBranchingM = - ifStmt(hasCondition(SuspiciousNumberObjectExprM)) - .bind("pedantic"); + ifStmt(allOf( + hasCondition(SuspiciousNumberObjectExprM), + unless(hasConditionVariableStatement(declStmt()) + ))).bind("pedantic"); auto ConversionThroughCallM = callExpr(hasAnyArgument(allOf(hasType(SuspiciousScalarTypeM), diff --git a/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp index cbaa5c23592d..b7339fe79f69 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp @@ -39,7 +39,7 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, const Expr *Ex = S->getSynchExpr(); ProgramStateRef state = C.getState(); - SVal V = state->getSVal(Ex, C.getLocationContext()); + SVal V = C.getSVal(Ex); // Uninitialized value used for the mutex? if (V.getAs<UndefinedVal>()) { diff --git a/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp new file mode 100644 index 000000000000..81bcda51b8f8 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp @@ -0,0 +1,209 @@ +//===- ObjCAutoreleaseWriteChecker.cpp ----------------------------*- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines ObjCAutoreleaseWriteChecker which warns against writes +// into autoreleased out parameters which cause crashes. +// An example of a problematic write is a write to {@code error} in the example +// below: +// +// - (BOOL) mymethod:(NSError *__autoreleasing *)error list:(NSArray*) list { +// [list enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { +// NSString *myString = obj; +// if ([myString isEqualToString:@"error"] && error) +// *error = [NSError errorWithDomain:@"MyDomain" code:-1]; +// }]; +// return false; +// } +// +// Such code will crash on read from `*error` due to the autorelease pool +// in `enumerateObjectsUsingBlock` implementation freeing the error object +// on exit from the function. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "llvm/ADT/Twine.h" + +using namespace clang; +using namespace ento; +using namespace ast_matchers; + +namespace { + +const char *ProblematicWriteBind = "problematicwrite"; +const char *CapturedBind = "capturedbind"; +const char *ParamBind = "parambind"; +const char *IsMethodBind = "ismethodbind"; + +class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> { +public: + void checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const; +private: + std::vector<std::string> SelectorsWithAutoreleasingPool = { + // Common to NSArray, NSSet, NSOrderedSet + "enumerateObjectsUsingBlock:", + "enumerateObjectsWithOptions:usingBlock:", + + // Common to NSArray and NSOrderedSet + "enumerateObjectsAtIndexes:options:usingBlock:", + "indexOfObjectAtIndexes:options:passingTest:", + "indexesOfObjectsAtIndexes:options:passingTest:", + "indexOfObjectPassingTest:", + "indexOfObjectWithOptions:passingTest:", + "indexesOfObjectsPassingTest:", + "indexesOfObjectsWithOptions:passingTest:", + + // NSDictionary + "enumerateKeysAndObjectsUsingBlock:", + "enumerateKeysAndObjectsWithOptions:usingBlock:", + "keysOfEntriesPassingTest:", + "keysOfEntriesWithOptions:passingTest:", + + // NSSet + "objectsPassingTest:", + "objectsWithOptions:passingTest:", + "enumerateIndexPathsWithOptions:usingBlock:", + + // NSIndexSet + "enumerateIndexesWithOptions:usingBlock:", + "enumerateIndexesUsingBlock:", + "enumerateIndexesInRange:options:usingBlock:", + "enumerateRangesUsingBlock:", + "enumerateRangesWithOptions:usingBlock:", + "enumerateRangesInRange:options:usingBlock:", + "indexPassingTest:", + "indexesPassingTest:", + "indexWithOptions:passingTest:", + "indexesWithOptions:passingTest:", + "indexInRange:options:passingTest:", + "indexesInRange:options:passingTest:" + }; + + std::vector<std::string> FunctionsWithAutoreleasingPool = { + "dispatch_async", "dispatch_group_async", "dispatch_barrier_async"}; +}; +} + +static inline std::vector<llvm::StringRef> toRefs(std::vector<std::string> V) { + return std::vector<llvm::StringRef>(V.begin(), V.end()); +} + +static auto callsNames(std::vector<std::string> FunctionNames) + -> decltype(callee(functionDecl())) { + return callee(functionDecl(hasAnyName(toRefs(FunctionNames)))); +} + +static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR, + AnalysisManager &AM, + const ObjCAutoreleaseWriteChecker *Checker) { + AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); + + const auto *PVD = Match.getNodeAs<ParmVarDecl>(ParamBind); + QualType Ty = PVD->getType(); + if (Ty->getPointeeType().getObjCLifetime() != Qualifiers::OCL_Autoreleasing) + return; + const char *ActionMsg = "Write to"; + const auto *MarkedStmt = Match.getNodeAs<Expr>(ProblematicWriteBind); + bool IsCapture = false; + + // Prefer to warn on write, but if not available, warn on capture. + if (!MarkedStmt) { + MarkedStmt = Match.getNodeAs<Expr>(CapturedBind); + assert(MarkedStmt); + ActionMsg = "Capture of"; + IsCapture = true; + } + + SourceRange Range = MarkedStmt->getSourceRange(); + PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin( + MarkedStmt, BR.getSourceManager(), ADC); + bool IsMethod = Match.getNodeAs<ObjCMethodDecl>(IsMethodBind) != nullptr; + const char *Name = IsMethod ? "method" : "function"; + + BR.EmitBasicReport( + ADC->getDecl(), Checker, + /*Name=*/(llvm::Twine(ActionMsg) + + " autoreleasing out parameter inside autorelease pool").str(), + /*Category=*/"Memory", + (llvm::Twine(ActionMsg) + " autoreleasing out parameter " + + (IsCapture ? "'" + PVD->getName() + "'" + " " : "") + "inside " + + "autorelease pool that may exit before " + Name + " returns; consider " + "writing first to a strong local variable declared outside of the block") + .str(), + Location, + Range); +} + +void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const { + + auto DoublePointerParamM = + parmVarDecl(hasType(hasCanonicalType(pointerType( + pointee(hasCanonicalType(objcObjectPointerType())))))) + .bind(ParamBind); + + auto ReferencedParamM = + declRefExpr(to(parmVarDecl(DoublePointerParamM))).bind(CapturedBind); + + // Write into a binded object, e.g. *ParamBind = X. + auto WritesIntoM = binaryOperator( + hasLHS(unaryOperator( + hasOperatorName("*"), + hasUnaryOperand( + ignoringParenImpCasts(ReferencedParamM)) + )), + hasOperatorName("=") + ).bind(ProblematicWriteBind); + + auto ArgumentCaptureM = hasAnyArgument( + ignoringParenImpCasts(ReferencedParamM)); + auto CapturedInParamM = stmt(anyOf( + callExpr(ArgumentCaptureM), + objcMessageExpr(ArgumentCaptureM))); + + // WritesIntoM happens inside a block passed as an argument. + auto WritesOrCapturesInBlockM = hasAnyArgument(allOf( + hasType(hasCanonicalType(blockPointerType())), + forEachDescendant( + stmt(anyOf(WritesIntoM, CapturedInParamM)) + ))); + + auto BlockPassedToMarkedFuncM = stmt(anyOf( + callExpr(allOf( + callsNames(FunctionsWithAutoreleasingPool), WritesOrCapturesInBlockM)), + objcMessageExpr(allOf( + hasAnySelector(toRefs(SelectorsWithAutoreleasingPool)), + WritesOrCapturesInBlockM)) + )); + + auto HasParamAndWritesInMarkedFuncM = allOf( + hasAnyParameter(DoublePointerParamM), + forEachDescendant(BlockPassedToMarkedFuncM)); + + auto MatcherM = decl(anyOf( + objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind), + functionDecl(HasParamAndWritesInMarkedFuncM), + blockDecl(HasParamAndWritesInMarkedFuncM))); + + auto Matches = match(MatcherM, *D, AM.getASTContext()); + for (BoundNodes Match : Matches) + emitDiagnostics(Match, D, BR, AM, this); +} + +void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) { + Mgr.registerChecker<ObjCAutoreleaseWriteChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp index 58ebf72660b6..fb05ca630b45 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp @@ -39,7 +39,7 @@ class ObjCContainersChecker : public Checker< check::PreStmt<CallExpr>, } inline SymbolRef getArraySym(const Expr *E, CheckerContext &C) const { - SVal ArrayRef = C.getState()->getSVal(E, C.getLocationContext()); + SVal ArrayRef = C.getSVal(E); SymbolRef ArraySym = ArrayRef.getAsSymbol(); return ArraySym; } @@ -66,13 +66,13 @@ REGISTER_MAP_WITH_PROGRAMSTATE(ArraySizeMap, SymbolRef, DefinedSVal) void ObjCContainersChecker::addSizeInfo(const Expr *Array, const Expr *Size, CheckerContext &C) const { ProgramStateRef State = C.getState(); - SVal SizeV = State->getSVal(Size, C.getLocationContext()); + SVal SizeV = C.getSVal(Size); // Undefined is reported by another checker. if (SizeV.isUnknownOrUndef()) return; // Get the ArrayRef symbol. - SVal ArrayRef = State->getSVal(Array, C.getLocationContext()); + SVal ArrayRef = C.getSVal(Array); SymbolRef ArraySym = ArrayRef.getAsSymbol(); if (!ArraySym) return; @@ -128,7 +128,7 @@ void ObjCContainersChecker::checkPreStmt(const CallExpr *CE, // Get the index. const Expr *IdxExpr = CE->getArg(1); - SVal IdxVal = State->getSVal(IdxExpr, C.getLocationContext()); + SVal IdxVal = C.getSVal(IdxExpr); if (IdxVal.isUnknownOrUndef()) return; DefinedSVal Idx = IdxVal.castAs<DefinedSVal>(); diff --git a/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp index 32a1adb587bf..d01c6ae6e093 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp @@ -81,7 +81,7 @@ private: } -/// \brief Determine whether the given class has a superclass that we want +/// Determine whether the given class has a superclass that we want /// to check. The name of the found superclass is stored in SuperclassName. /// /// \param D The declaration to check for superclasses. diff --git a/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp index ffa3a2700616..629520437369 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp @@ -86,11 +86,11 @@ public: namespace { enum SelfFlagEnum { - /// \brief No flag set. + /// No flag set. SelfFlag_None = 0x0, - /// \brief Value came from 'self'. + /// Value came from 'self'. SelfFlag_Self = 0x1, - /// \brief Value came from the result of an initializer (e.g. [super init]). + /// Value came from the result of an initializer (e.g. [super init]). SelfFlag_InitRes = 0x2 }; } @@ -98,7 +98,7 @@ enum SelfFlagEnum { REGISTER_MAP_WITH_PROGRAMSTATE(SelfFlag, SymbolRef, unsigned) REGISTER_TRAIT_WITH_PROGRAMSTATE(CalledInit, bool) -/// \brief A call receiving a reference to 'self' invalidates the object that +/// A call receiving a reference to 'self' invalidates the object that /// 'self' contains. This keeps the "self flags" assigned to the 'self' /// object before the call so we can assign them to the new object that 'self' /// points to after the call. @@ -128,11 +128,11 @@ static bool hasSelfFlag(SVal val, SelfFlagEnum flag, CheckerContext &C) { return getSelfFlags(val, C) & flag; } -/// \brief Returns true of the value of the expression is the object that 'self' +/// Returns true of the value of the expression is the object that 'self' /// points to and is an object that did not come from the result of calling /// an initializer. static bool isInvalidSelf(const Expr *E, CheckerContext &C) { - SVal exprVal = C.getState()->getSVal(E, C.getLocationContext()); + SVal exprVal = C.getSVal(E); if (!hasSelfFlag(exprVal, SelfFlag_Self, C)) return false; // value did not come from 'self'. if (hasSelfFlag(exprVal, SelfFlag_InitRes, C)) @@ -183,7 +183,7 @@ void ObjCSelfInitChecker::checkPostObjCMessage(const ObjCMethodCall &Msg, // value out when we return from this method. state = state->set<CalledInit>(true); - SVal V = state->getSVal(Msg.getOriginExpr(), C.getLocationContext()); + SVal V = C.getSVal(Msg.getOriginExpr()); addSelfFlag(state, V, SelfFlag_InitRes, C); return; } @@ -407,7 +407,7 @@ static bool shouldRunOnFunctionOrMethod(const NamedDecl *ND) { return ID != nullptr; } -/// \brief Returns true if the location is 'self'. +/// Returns true if the location is 'self'. static bool isSelfVar(SVal location, CheckerContext &C) { AnalysisDeclContext *analCtx = C.getCurrentAnalysisDeclContext(); if (!analCtx->getSelfDecl()) diff --git a/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp index 69b19a785938..fcba3b33f3e0 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp @@ -62,9 +62,7 @@ private: REGISTER_SET_WITH_PROGRAMSTATE(CalledSuperDealloc, SymbolRef) namespace { -class SuperDeallocBRVisitor final - : public BugReporterVisitorImpl<SuperDeallocBRVisitor> { - +class SuperDeallocBRVisitor final : public BugReporterVisitor { SymbolRef ReceiverSymbol; bool Satisfied; diff --git a/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp b/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp index 6c0c53dd64cb..f69f3492edb1 100644 --- a/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp @@ -67,7 +67,7 @@ public: visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD)); } - /// \brief Look for records of overly padded types. If padding * + /// Look for records of overly padded types. If padding * /// PadMultiplier exceeds AllowedPad, then generate a report. /// PadMultiplier is used to share code with the array padding /// checker. @@ -97,7 +97,7 @@ public: reportRecord(RD, BaselinePad, OptimalPad, OptimalFieldsOrder); } - /// \brief Look for arrays of overly padded types. If the padding of the + /// Look for arrays of overly padded types. If the padding of the /// array type exceeds AllowedPad, then generate a report. void visitVariable(const VarDecl *VD) const { const ArrayType *ArrTy = VD->getType()->getAsArrayTypeUnsafe(); @@ -237,7 +237,7 @@ public: }; std::transform(RD->field_begin(), RD->field_end(), std::back_inserter(Fields), GatherSizesAndAlignments); - std::sort(Fields.begin(), Fields.end()); + llvm::sort(Fields.begin(), Fields.end()); // This lets us skip over vptrs and non-virtual bases, // so that we can just worry about the fields in our object. // Note that this does cause us to miss some cases where we diff --git a/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp b/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp index 8caf6df4d970..63f82b275ba2 100644 --- a/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp @@ -154,8 +154,7 @@ void PointerArithChecker::reportPointerArithMisuse(const Expr *E, return; ProgramStateRef State = C.getState(); - const MemRegion *Region = - State->getSVal(E, C.getLocationContext()).getAsRegion(); + const MemRegion *Region = C.getSVal(E).getAsRegion(); if (!Region) return; if (PointedNeeded) @@ -227,7 +226,7 @@ void PointerArithChecker::checkPostStmt(const CallExpr *CE, if (AllocFunctions.count(FunI) == 0) return; - SVal SV = State->getSVal(CE, C.getLocationContext()); + SVal SV = C.getSVal(CE); const MemRegion *Region = SV.getAsRegion(); if (!Region) return; @@ -248,7 +247,7 @@ void PointerArithChecker::checkPostStmt(const CXXNewExpr *NE, AllocKind Kind = getKindOfNewOp(NE, FD); ProgramStateRef State = C.getState(); - SVal AllocedVal = State->getSVal(NE, C.getLocationContext()); + SVal AllocedVal = C.getSVal(NE); const MemRegion *Region = AllocedVal.getAsRegion(); if (!Region) return; @@ -263,7 +262,7 @@ void PointerArithChecker::checkPostStmt(const CastExpr *CE, const Expr *CastedExpr = CE->getSubExpr(); ProgramStateRef State = C.getState(); - SVal CastedVal = State->getSVal(CastedExpr, C.getLocationContext()); + SVal CastedVal = C.getSVal(CastedExpr); const MemRegion *Region = CastedVal.getAsRegion(); if (!Region) @@ -281,7 +280,7 @@ void PointerArithChecker::checkPreStmt(const CastExpr *CE, const Expr *CastedExpr = CE->getSubExpr(); ProgramStateRef State = C.getState(); - SVal CastedVal = State->getSVal(CastedExpr, C.getLocationContext()); + SVal CastedVal = C.getSVal(CastedExpr); const MemRegion *Region = CastedVal.getAsRegion(); if (!Region) @@ -304,12 +303,15 @@ void PointerArithChecker::checkPreStmt(const UnaryOperator *UOp, void PointerArithChecker::checkPreStmt(const ArraySubscriptExpr *SubsExpr, CheckerContext &C) const { - ProgramStateRef State = C.getState(); - SVal Idx = State->getSVal(SubsExpr->getIdx(), C.getLocationContext()); + SVal Idx = C.getSVal(SubsExpr->getIdx()); // Indexing with 0 is OK. if (Idx.isZeroConstant()) return; + + // Indexing vector-type expressions is also OK. + if (SubsExpr->getBase()->getType()->isVectorType()) + return; reportPointerArithMisuse(SubsExpr->getBase(), C); } @@ -324,14 +326,14 @@ void PointerArithChecker::checkPreStmt(const BinaryOperator *BOp, ProgramStateRef State = C.getState(); if (Rhs->getType()->isIntegerType() && Lhs->getType()->isPointerType()) { - SVal RHSVal = State->getSVal(Rhs, C.getLocationContext()); + SVal RHSVal = C.getSVal(Rhs); if (State->isNull(RHSVal).isConstrainedTrue()) return; reportPointerArithMisuse(Lhs, C, !BOp->isAdditiveOp()); } // The int += ptr; case is not valid C++. if (Lhs->getType()->isIntegerType() && Rhs->getType()->isPointerType()) { - SVal LHSVal = State->getSVal(Lhs, C.getLocationContext()); + SVal LHSVal = C.getSVal(Lhs); if (State->isNull(LHSVal).isConstrainedTrue()) return; reportPointerArithMisuse(Rhs, C); diff --git a/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp b/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp index 2d33ebc2610d..9aa5348e4c34 100644 --- a/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp @@ -39,10 +39,8 @@ void PointerSubChecker::checkPreStmt(const BinaryOperator *B, if (B->getOpcode() != BO_Sub) return; - ProgramStateRef state = C.getState(); - const LocationContext *LCtx = C.getLocationContext(); - SVal LV = state->getSVal(B->getLHS(), LCtx); - SVal RV = state->getSVal(B->getRHS(), LCtx); + SVal LV = C.getSVal(B->getLHS()); + SVal RV = C.getSVal(B->getRHS()); const MemRegion *LR = LV.getAsRegion(); const MemRegion *RR = RV.getAsRegion(); diff --git a/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp index dab29be1c8fb..10ab952e069b 100644 --- a/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp @@ -109,8 +109,6 @@ REGISTER_MAP_WITH_PROGRAMSTATE(DestroyRetVal, const MemRegion *, SymbolRef) void PthreadLockChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { - ProgramStateRef state = C.getState(); - const LocationContext *LCtx = C.getLocationContext(); StringRef FName = C.getCalleeName(CE); if (FName.empty()) return; @@ -121,34 +119,31 @@ void PthreadLockChecker::checkPostStmt(const CallExpr *CE, if (FName == "pthread_mutex_lock" || FName == "pthread_rwlock_rdlock" || FName == "pthread_rwlock_wrlock") - AcquireLock(C, CE, state->getSVal(CE->getArg(0), LCtx), - false, PthreadSemantics); + AcquireLock(C, CE, C.getSVal(CE->getArg(0)), false, PthreadSemantics); else if (FName == "lck_mtx_lock" || FName == "lck_rw_lock_exclusive" || FName == "lck_rw_lock_shared") - AcquireLock(C, CE, state->getSVal(CE->getArg(0), LCtx), - false, XNUSemantics); + AcquireLock(C, CE, C.getSVal(CE->getArg(0)), false, XNUSemantics); else if (FName == "pthread_mutex_trylock" || FName == "pthread_rwlock_tryrdlock" || FName == "pthread_rwlock_trywrlock") - AcquireLock(C, CE, state->getSVal(CE->getArg(0), LCtx), + AcquireLock(C, CE, C.getSVal(CE->getArg(0)), true, PthreadSemantics); else if (FName == "lck_mtx_try_lock" || FName == "lck_rw_try_lock_exclusive" || FName == "lck_rw_try_lock_shared") - AcquireLock(C, CE, state->getSVal(CE->getArg(0), LCtx), - true, XNUSemantics); + AcquireLock(C, CE, C.getSVal(CE->getArg(0)), true, XNUSemantics); else if (FName == "pthread_mutex_unlock" || FName == "pthread_rwlock_unlock" || FName == "lck_mtx_unlock" || FName == "lck_rw_done") - ReleaseLock(C, CE, state->getSVal(CE->getArg(0), LCtx)); + ReleaseLock(C, CE, C.getSVal(CE->getArg(0))); else if (FName == "pthread_mutex_destroy") - DestroyLock(C, CE, state->getSVal(CE->getArg(0), LCtx), PthreadSemantics); + DestroyLock(C, CE, C.getSVal(CE->getArg(0)), PthreadSemantics); else if (FName == "lck_mtx_destroy") - DestroyLock(C, CE, state->getSVal(CE->getArg(0), LCtx), XNUSemantics); + DestroyLock(C, CE, C.getSVal(CE->getArg(0)), XNUSemantics); else if (FName == "pthread_mutex_init") - InitLock(C, CE, state->getSVal(CE->getArg(0), LCtx)); + InitLock(C, CE, C.getSVal(CE->getArg(0))); } // When a lock is destroyed, in some semantics(like PthreadSemantics) we are not @@ -232,7 +227,7 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, if (sym) state = resolvePossiblyDestroyedMutex(state, lockR, sym); - SVal X = state->getSVal(CE, C.getLocationContext()); + SVal X = C.getSVal(CE); if (X.isUnknownOrUndef()) return; diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp b/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp index e47494a3e90b..2c1e139330d6 100644 --- a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp @@ -555,7 +555,7 @@ public: } const RetainSummary *find(IdentifierInfo* II, Selector S) { - // FIXME: Class method lookup. Right now we dont' have a good way + // FIXME: Class method lookup. Right now we don't have a good way // of going between IdentifierInfo* and the class hierarchy. MapTy::iterator I = M.find(ObjCSummaryKey(II, S)); @@ -883,21 +883,22 @@ RetainSummaryManager::getPersistentSummary(const RetainSummary &OldSumm) { //===----------------------------------------------------------------------===// static bool isRetain(const FunctionDecl *FD, StringRef FName) { - return FName.endswith("Retain"); + return FName.startswith_lower("retain") || FName.endswith_lower("retain"); } static bool isRelease(const FunctionDecl *FD, StringRef FName) { - return FName.endswith("Release"); + return FName.startswith_lower("release") || FName.endswith_lower("release"); } static bool isAutorelease(const FunctionDecl *FD, StringRef FName) { - return FName.endswith("Autorelease"); + return FName.startswith_lower("autorelease") || + FName.endswith_lower("autorelease"); } static bool isMakeCollectable(const FunctionDecl *FD, StringRef FName) { // FIXME: Remove FunctionDecl parameter. // FIXME: Is it really okay if MakeCollectable isn't a suffix? - return FName.find("MakeCollectable") != StringRef::npos; + return FName.find_lower("MakeCollectable") != StringRef::npos; } static ArgEffect getStopTrackingHardEquivalent(ArgEffect E) { @@ -1787,8 +1788,7 @@ namespace { //===---------===// // Bug Reports. // //===---------===// - - class CFRefReportVisitor : public BugReporterVisitorImpl<CFRefReportVisitor> { + class CFRefReportVisitor : public BugReporterVisitor { protected: SymbolRef Sym; const SummaryLogTy &SummaryLog; @@ -1809,7 +1809,7 @@ namespace { BugReporterContext &BRC, BugReport &BR) override; - std::unique_ptr<PathDiagnosticPiece> getEndPath(BugReporterContext &BRC, + std::shared_ptr<PathDiagnosticPiece> getEndPath(BugReporterContext &BRC, const ExplodedNode *N, BugReport &BR) override; }; @@ -1820,18 +1820,9 @@ namespace { const SummaryLogTy &log) : CFRefReportVisitor(sym, GCEnabled, log) {} - std::unique_ptr<PathDiagnosticPiece> getEndPath(BugReporterContext &BRC, + std::shared_ptr<PathDiagnosticPiece> getEndPath(BugReporterContext &BRC, const ExplodedNode *N, BugReport &BR) override; - - std::unique_ptr<BugReporterVisitor> clone() const override { - // The curiously-recurring template pattern only works for one level of - // subclassing. Rather than make a new template base for - // CFRefReportVisitor, we simply override clone() to do the right thing. - // This could be trouble someday if BugReporterVisitorImpl is ever - // used for something else besides a convenient implementation of clone(). - return llvm::make_unique<CFRefLeakReportVisitor>(*this); - } }; class CFRefReport : public BugReport { @@ -1929,6 +1920,14 @@ static bool isNumericLiteralExpression(const Expr *E) { isa<CXXBoolLiteralExpr>(E); } +static Optional<std::string> describeRegion(const MemRegion *MR) { + if (const auto *VR = dyn_cast_or_null<VarRegion>(MR)) + return std::string(VR->getDecl()->getName()); + // Once we support more storage locations for bindings, + // this would need to be improved. + return None; +} + /// Returns true if this stack frame is for an Objective-C method that is a /// property getter or setter whose body has been synthesized by the analyzer. static bool isSynthesizedAccessor(const StackFrameContext *SFC) { @@ -1969,8 +1968,8 @@ CFRefReportVisitor::VisitNode(const ExplodedNode *N, const ExplodedNode *PrevN, const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt(); if (isa<ObjCIvarRefExpr>(S) && - isSynthesizedAccessor(LCtx->getCurrentStackFrame())) { - S = LCtx->getCurrentStackFrame()->getCallSite(); + isSynthesizedAccessor(LCtx->getStackFrame())) { + S = LCtx->getStackFrame()->getCallSite(); } if (isa<ObjCArrayLiteral>(S)) { @@ -2298,7 +2297,7 @@ GetAllocationSite(ProgramStateManager& StateMgr, const ExplodedNode *N, const VarRegion *VR = R->getBaseRegion()->getAs<VarRegion>(); // Do not show local variables belonging to a function other than // where the error is reported. - if (!VR || VR->getStackFrame() == LeakContext->getCurrentStackFrame()) + if (!VR || VR->getStackFrame() == LeakContext->getStackFrame()) FirstBinding = R; } @@ -2356,14 +2355,14 @@ GetAllocationSite(ProgramStateManager& StateMgr, const ExplodedNode *N, InterestingMethodContext); } -std::unique_ptr<PathDiagnosticPiece> +std::shared_ptr<PathDiagnosticPiece> CFRefReportVisitor::getEndPath(BugReporterContext &BRC, const ExplodedNode *EndN, BugReport &BR) { BR.markInteresting(Sym); return BugReporterVisitor::getDefaultEndPath(BRC, EndN, BR); } -std::unique_ptr<PathDiagnosticPiece> +std::shared_ptr<PathDiagnosticPiece> CFRefLeakReportVisitor::getEndPath(BugReporterContext &BRC, const ExplodedNode *EndN, BugReport &BR) { @@ -2393,9 +2392,9 @@ CFRefLeakReportVisitor::getEndPath(BugReporterContext &BRC, os << "Object leaked: "; - if (FirstBinding) { - os << "object allocated and stored into '" - << FirstBinding->getString() << '\''; + Optional<std::string> RegionDescription = describeRegion(FirstBinding); + if (RegionDescription) { + os << "object allocated and stored into '" << *RegionDescription << '\''; } else os << "allocated object"; @@ -2450,7 +2449,7 @@ CFRefLeakReportVisitor::getEndPath(BugReporterContext &BRC, os << " is not referenced later in this execution path and has a retain " "count of +" << RV->getCount(); - return llvm::make_unique<PathDiagnosticEventPiece>(L, os.str()); + return std::make_shared<PathDiagnosticEventPiece>(L, os.str()); } void CFRefLeakReport::deriveParamLocation(CheckerContext &Ctx, SymbolRef sym) { @@ -2513,7 +2512,8 @@ void CFRefLeakReport::deriveAllocLocation(CheckerContext &Ctx,SymbolRef sym) { UniqueingDecl = AllocNode->getLocationContext()->getDecl(); } -void CFRefLeakReport::createDescription(CheckerContext &Ctx, bool GCEnabled, bool IncludeAllocationLine) { +void CFRefLeakReport::createDescription(CheckerContext &Ctx, bool GCEnabled, + bool IncludeAllocationLine) { assert(Location.isValid() && UniqueingDecl && UniqueingLocation.isValid()); Description.clear(); llvm::raw_string_ostream os(Description); @@ -2522,8 +2522,9 @@ void CFRefLeakReport::createDescription(CheckerContext &Ctx, bool GCEnabled, boo os << "(when using garbage collection) "; os << "of an object"; - if (AllocBinding) { - os << " stored into '" << AllocBinding->getString() << '\''; + Optional<std::string> RegionDescription = describeRegion(AllocBinding); + if (RegionDescription) { + os << " stored into '" << *RegionDescription << '\''; if (IncludeAllocationLine) { FullSourceLoc SL(AllocStmt->getLocStart(), Ctx.getSourceManager()); os << " (allocated on line " << SL.getSpellingLineNumber() << ")"; @@ -2742,7 +2743,7 @@ public: void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; void checkBeginFunction(CheckerContext &C) const; - void checkEndFunction(CheckerContext &C) const; + void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; ProgramStateRef updateSymbol(ProgramStateRef state, SymbolRef sym, RefVal V, ArgEffect E, RefVal::Kind &hasErr, @@ -2799,9 +2800,7 @@ void RetainCountChecker::checkPostStmt(const BlockExpr *BE, return; ProgramStateRef state = C.getState(); - const BlockDataRegion *R = - cast<BlockDataRegion>(state->getSVal(BE, - C.getLocationContext()).getAsRegion()); + auto *R = cast<BlockDataRegion>(C.getSVal(BE).getAsRegion()); BlockDataRegion::referenced_vars_iterator I = R->referenced_vars_begin(), E = R->referenced_vars_end(); @@ -2851,7 +2850,7 @@ void RetainCountChecker::checkPostStmt(const CastExpr *CE, } ProgramStateRef state = C.getState(); - SymbolRef Sym = state->getSVal(CE, C.getLocationContext()).getAsLocSymbol(); + SymbolRef Sym = C.getSVal(CE).getAsLocSymbol(); if (!Sym) return; const RefVal* T = getRefBinding(state, Sym); @@ -2874,7 +2873,7 @@ void RetainCountChecker::processObjCLiterals(CheckerContext &C, ProgramStateRef state = C.getState(); const ExplodedNode *pred = C.getPredecessor(); for (const Stmt *Child : Ex->children()) { - SVal V = state->getSVal(Child, pred->getLocationContext()); + SVal V = pred->getSVal(Child); if (SymbolRef sym = V.getAsSymbol()) if (const RefVal* T = getRefBinding(state, sym)) { RefVal::Kind hasErr = (RefVal::Kind) 0; @@ -2913,10 +2912,9 @@ void RetainCountChecker::checkPostStmt(const ObjCDictionaryLiteral *DL, void RetainCountChecker::checkPostStmt(const ObjCBoxedExpr *Ex, CheckerContext &C) const { const ExplodedNode *Pred = C.getPredecessor(); - const LocationContext *LCtx = Pred->getLocationContext(); ProgramStateRef State = Pred->getState(); - if (SymbolRef Sym = State->getSVal(Ex, LCtx).getAsSymbol()) { + if (SymbolRef Sym = Pred->getSVal(Ex).getAsSymbol()) { QualType ResultTy = Ex->getType(); State = setRefBinding(State, Sym, RefVal::makeNotOwned(RetEffect::ObjC, ResultTy)); @@ -3993,7 +3991,8 @@ void RetainCountChecker::checkBeginFunction(CheckerContext &Ctx) const { Ctx.addTransition(state); } -void RetainCountChecker::checkEndFunction(CheckerContext &Ctx) const { +void RetainCountChecker::checkEndFunction(const ReturnStmt *RS, + CheckerContext &Ctx) const { ProgramStateRef state = Ctx.getState(); RefBindingsTy B = state->get<RefBindings>(); ExplodedNode *Pred = Ctx.getPredecessor(); diff --git a/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp b/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp index 19fa0fb193cc..1952715a9b7c 100644 --- a/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp @@ -40,7 +40,7 @@ void ReturnPointerRangeChecker::checkPreStmt(const ReturnStmt *RS, if (!RetE) return; - SVal V = state->getSVal(RetE, C.getLocationContext()); + SVal V = C.getSVal(RetE); const MemRegion *R = V.getAsRegion(); const ElementRegion *ER = dyn_cast_or_null<ElementRegion>(R); diff --git a/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp b/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp new file mode 100644 index 000000000000..64b61a0213d2 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp @@ -0,0 +1,217 @@ +//=- RunLoopAutoreleaseLeakChecker.cpp --------------------------*- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +// +//===----------------------------------------------------------------------===// +// +// A checker for detecting leaks resulting from allocating temporary +// autoreleased objects before starting the main run loop. +// +// Checks for two antipatterns: +// 1. ObjCMessageExpr followed by [[NSRunLoop mainRunLoop] run] in the same +// autorelease pool. +// 2. ObjCMessageExpr followed by [[NSRunLoop mainRunLoop] run] in no +// autorelease pool. +// +// Any temporary objects autoreleased in code called in those expressions +// will not be deallocated until the program exits, and are effectively leaks. +// +//===----------------------------------------------------------------------===// +// + +#include "ClangSACheckers.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclObjC.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" + +using namespace clang; +using namespace ento; +using namespace ast_matchers; + +namespace { + +const char * RunLoopBind = "NSRunLoopM"; +const char * RunLoopRunBind = "RunLoopRunM"; +const char * OtherMsgBind = "OtherMessageSentM"; +const char * AutoreleasePoolBind = "AutoreleasePoolM"; + +class RunLoopAutoreleaseLeakChecker : public Checker< + check::ASTCodeBody> { + +public: + void checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const; + +}; + +} // end anonymous namespace + + +using TriBoolTy = Optional<bool>; +using MemoizationMapTy = llvm::DenseMap<const Stmt *, Optional<TriBoolTy>>; + +static TriBoolTy +seenBeforeRec(const Stmt *Parent, const Stmt *A, const Stmt *B, + MemoizationMapTy &Memoization) { + for (const Stmt *C : Parent->children()) { + if (C == A) + return true; + + if (C == B) + return false; + + Optional<TriBoolTy> &Cached = Memoization[C]; + if (!Cached) + Cached = seenBeforeRec(C, A, B, Memoization); + + if (Cached->hasValue()) + return Cached->getValue(); + } + + return None; +} + +/// \return Whether {@code A} occurs before {@code B} in traversal of +/// {@code Parent}. +/// Conceptually a very incomplete/unsound approximation of happens-before +/// relationship (A is likely to be evaluated before B), +/// but useful enough in this case. +static bool seenBefore(const Stmt *Parent, const Stmt *A, const Stmt *B) { + MemoizationMapTy Memoization; + TriBoolTy Val = seenBeforeRec(Parent, A, B, Memoization); + return Val.getValue(); +} + +static void emitDiagnostics(BoundNodes &Match, + const Decl *D, + BugReporter &BR, + AnalysisManager &AM, + const RunLoopAutoreleaseLeakChecker *Checker) { + + assert(D->hasBody()); + const Stmt *DeclBody = D->getBody(); + + AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); + + const auto *ME = Match.getNodeAs<ObjCMessageExpr>(OtherMsgBind); + assert(ME); + + const auto *AP = + Match.getNodeAs<ObjCAutoreleasePoolStmt>(AutoreleasePoolBind); + bool HasAutoreleasePool = (AP != nullptr); + + const auto *RL = Match.getNodeAs<ObjCMessageExpr>(RunLoopBind); + const auto *RLR = Match.getNodeAs<Stmt>(RunLoopRunBind); + assert(RLR && "Run loop launch not found"); + + assert(ME != RLR); + if (HasAutoreleasePool && seenBefore(AP, RLR, ME)) + return; + + if (!HasAutoreleasePool && seenBefore(DeclBody, RLR, ME)) + return; + + PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin( + ME, BR.getSourceManager(), ADC); + SourceRange Range = ME->getSourceRange(); + + BR.EmitBasicReport(ADC->getDecl(), Checker, + /*Name=*/"Memory leak inside autorelease pool", + /*Category=*/"Memory", + /*Name=*/ + (Twine("Temporary objects allocated in the") + + " autorelease pool " + + (HasAutoreleasePool ? "" : "of last resort ") + + "followed by the launch of " + + (RL ? "main run loop " : "xpc_main ") + + "may never get released; consider moving them to a " + "separate autorelease pool") + .str(), + Location, Range); +} + +static StatementMatcher getRunLoopRunM(StatementMatcher Extra = anything()) { + StatementMatcher MainRunLoopM = + objcMessageExpr(hasSelector("mainRunLoop"), + hasReceiverType(asString("NSRunLoop")), + Extra) + .bind(RunLoopBind); + + StatementMatcher MainRunLoopRunM = objcMessageExpr(hasSelector("run"), + hasReceiver(MainRunLoopM), + Extra).bind(RunLoopRunBind); + + StatementMatcher XPCRunM = + callExpr(callee(functionDecl(hasName("xpc_main")))).bind(RunLoopRunBind); + return anyOf(MainRunLoopRunM, XPCRunM); +} + +static StatementMatcher getOtherMessageSentM(StatementMatcher Extra = anything()) { + return objcMessageExpr(unless(anyOf(equalsBoundNode(RunLoopBind), + equalsBoundNode(RunLoopRunBind))), + Extra) + .bind(OtherMsgBind); +} + +static void +checkTempObjectsInSamePool(const Decl *D, AnalysisManager &AM, BugReporter &BR, + const RunLoopAutoreleaseLeakChecker *Chkr) { + StatementMatcher RunLoopRunM = getRunLoopRunM(); + StatementMatcher OtherMessageSentM = getOtherMessageSentM(); + + StatementMatcher RunLoopInAutorelease = + autoreleasePoolStmt( + hasDescendant(RunLoopRunM), + hasDescendant(OtherMessageSentM)).bind(AutoreleasePoolBind); + + DeclarationMatcher GroupM = decl(hasDescendant(RunLoopInAutorelease)); + + auto Matches = match(GroupM, *D, AM.getASTContext()); + for (BoundNodes Match : Matches) + emitDiagnostics(Match, D, BR, AM, Chkr); +} + +static void +checkTempObjectsInNoPool(const Decl *D, AnalysisManager &AM, BugReporter &BR, + const RunLoopAutoreleaseLeakChecker *Chkr) { + + auto NoPoolM = unless(hasAncestor(autoreleasePoolStmt())); + + StatementMatcher RunLoopRunM = getRunLoopRunM(NoPoolM); + StatementMatcher OtherMessageSentM = getOtherMessageSentM(NoPoolM); + + DeclarationMatcher GroupM = functionDecl( + isMain(), + hasDescendant(RunLoopRunM), + hasDescendant(OtherMessageSentM) + ); + + auto Matches = match(GroupM, *D, AM.getASTContext()); + + for (BoundNodes Match : Matches) + emitDiagnostics(Match, D, BR, AM, Chkr); + +} + +void RunLoopAutoreleaseLeakChecker::checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const { + checkTempObjectsInSamePool(D, AM, BR, this); + checkTempObjectsInNoPool(D, AM, BR, this); +} + +void ento::registerRunLoopAutoreleaseLeakChecker(CheckerManager &mgr) { + mgr.registerChecker<RunLoopAutoreleaseLeakChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp index 25975628c553..feae9e59b343 100644 --- a/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp @@ -47,7 +47,7 @@ public: void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const; - void checkEndFunction(CheckerContext &Ctx) const; + void checkEndFunction(const ReturnStmt *RS, CheckerContext &Ctx) const; private: void checkReturnedBlockCaptures(const BlockDataRegion &B, @@ -120,7 +120,7 @@ bool StackAddrEscapeChecker::isArcManagedBlock(const MemRegion *R, bool StackAddrEscapeChecker::isNotInCurrentFrame(const MemRegion *R, CheckerContext &C) { const StackSpaceRegion *S = cast<StackSpaceRegion>(R->getMemorySpace()); - return S->getStackFrame() != C.getLocationContext()->getCurrentStackFrame(); + return S->getStackFrame() != C.getStackFrame(); } bool StackAddrEscapeChecker::isSemaphoreCaptured(const BlockDecl &B) const { @@ -255,8 +255,7 @@ void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS, return; RetE = RetE->IgnoreParens(); - const LocationContext *LCtx = C.getLocationContext(); - SVal V = C.getState()->getSVal(RetE, LCtx); + SVal V = C.getSVal(RetE); const MemRegion *R = V.getAsRegion(); if (!R) return; @@ -288,7 +287,8 @@ void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS, EmitStackError(C, R, RetE); } -void StackAddrEscapeChecker::checkEndFunction(CheckerContext &Ctx) const { +void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, + CheckerContext &Ctx) const { if (!ChecksEnabled[CK_StackAddrEscapeChecker]) return; @@ -304,8 +304,7 @@ void StackAddrEscapeChecker::checkEndFunction(CheckerContext &Ctx) const { public: SmallVector<std::pair<const MemRegion *, const MemRegion *>, 10> V; - CallBack(CheckerContext &CC) - : Ctx(CC), CurSFC(CC.getLocationContext()->getCurrentStackFrame()) {} + CallBack(CheckerContext &CC) : Ctx(CC), CurSFC(CC.getStackFrame()) {} bool HandleBinding(StoreManager &SMgr, Store S, const MemRegion *Region, SVal Val) override { diff --git a/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/lib/StaticAnalyzer/Checkers/StreamChecker.cpp index 915514b42133..d77975559e3f 100644 --- a/lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -242,22 +242,19 @@ void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) const { void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(3), C.getLocationContext()), - state, C)) + if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C)) return; } void StreamChecker::Fwrite(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(3), C.getLocationContext()), - state, C)) + if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C)) return; } void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); - if (!(state = CheckNullStream(state->getSVal(CE->getArg(0), - C.getLocationContext()), state, C))) + if (!(state = CheckNullStream(C.getSVal(CE->getArg(0)), state, C))) return; // Check the legality of the 'whence' argument of 'fseek'. SVal Whence = state->getSVal(CE->getArg(2), C.getLocationContext()); @@ -283,57 +280,49 @@ void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { void StreamChecker::Ftell(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) + if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } void StreamChecker::Rewind(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) + if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } void StreamChecker::Fgetpos(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) + if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } void StreamChecker::Fsetpos(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) + if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } void StreamChecker::Clearerr(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) + if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } void StreamChecker::Feof(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) + if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } void StreamChecker::Ferror(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) + if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } void StreamChecker::Fileno(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) + if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) return; } @@ -363,8 +352,7 @@ ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state, ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE, ProgramStateRef state, CheckerContext &C) const { - SymbolRef Sym = - state->getSVal(CE->getArg(0), C.getLocationContext()).getAsSymbol(); + SymbolRef Sym = C.getSVal(CE->getArg(0)).getAsSymbol(); if (!Sym) return state; diff --git a/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp b/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp index 5268bbf5562e..f4c0edbab3f0 100644 --- a/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp @@ -55,7 +55,7 @@ public: } }; -class DivisionBRVisitor : public BugReporterVisitorImpl<DivisionBRVisitor> { +class DivisionBRVisitor : public BugReporterVisitor { private: SymbolRef ZeroSymbol; const StackFrameContext *SFC; @@ -85,7 +85,7 @@ class TestAfterDivZeroChecker public: void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const; void checkBranchCondition(const Stmt *Condition, CheckerContext &C) const; - void checkEndFunction(CheckerContext &C) const; + void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; void setDivZeroMap(SVal Var, CheckerContext &C) const; bool hasDivZeroMap(SVal Var, const CheckerContext &C) const; bool isZero(SVal S, CheckerContext &C) const; @@ -114,8 +114,7 @@ DivisionBRVisitor::VisitNode(const ExplodedNode *Succ, const ExplodedNode *Pred, if (!E) return nullptr; - ProgramStateRef State = Succ->getState(); - SVal S = State->getSVal(E, Succ->getLocationContext()); + SVal S = Succ->getSVal(E); if (ZeroSymbol == S.getAsSymbol() && SFC == Succ->getStackFrame()) { Satisfied = true; @@ -181,7 +180,8 @@ void TestAfterDivZeroChecker::reportBug(SVal Val, CheckerContext &C) const { } } -void TestAfterDivZeroChecker::checkEndFunction(CheckerContext &C) const { +void TestAfterDivZeroChecker::checkEndFunction(const ReturnStmt *RS, + CheckerContext &C) const { ProgramStateRef State = C.getState(); DivZeroMapTy DivZeroes = State->get<DivZeroMap>(); diff --git a/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp b/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp index 8ad962875b06..ee185b813611 100644 --- a/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp @@ -30,7 +30,7 @@ class TraversalDumper : public Checker< check::BranchCondition, public: void checkBranchCondition(const Stmt *Condition, CheckerContext &C) const; void checkBeginFunction(CheckerContext &C) const; - void checkEndFunction(CheckerContext &C) const; + void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; }; } @@ -56,7 +56,8 @@ void TraversalDumper::checkBeginFunction(CheckerContext &C) const { llvm::outs() << "--BEGIN FUNCTION--\n"; } -void TraversalDumper::checkEndFunction(CheckerContext &C) const { +void TraversalDumper::checkEndFunction(const ReturnStmt *RS, + CheckerContext &C) const { llvm::outs() << "--END FUNCTION--\n"; } diff --git a/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp b/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp new file mode 100644 index 000000000000..f3d68014224d --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp @@ -0,0 +1,90 @@ +//== TrustNonnullChecker.cpp - Checker for trusting annotations -*- C++ -*--==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This checker adds an assumption that methods annotated with _Nonnull +// which come from system headers actually return a non-null pointer. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" + +using namespace clang; +using namespace ento; + +namespace { + +class TrustNonnullChecker : public Checker<check::PostCall> { +private: + /// \returns Whether we trust the result of the method call to be + /// a non-null pointer. + bool isNonNullPtr(const CallEvent &Call, CheckerContext &C) const { + QualType ExprRetType = Call.getResultType(); + if (!ExprRetType->isAnyPointerType()) + return false; + + if (getNullabilityAnnotation(ExprRetType) == Nullability::Nonnull) + return true; + + // The logic for ObjC instance method calls is more complicated, + // as the return value is nil when the receiver is nil. + if (!isa<ObjCMethodCall>(&Call)) + return false; + + const auto *MCall = cast<ObjCMethodCall>(&Call); + const ObjCMethodDecl *MD = MCall->getDecl(); + + // Distrust protocols. + if (isa<ObjCProtocolDecl>(MD->getDeclContext())) + return false; + + QualType DeclRetType = MD->getReturnType(); + if (getNullabilityAnnotation(DeclRetType) != Nullability::Nonnull) + return false; + + // For class messages it is sufficient for the declaration to be + // annotated _Nonnull. + if (!MCall->isInstanceMessage()) + return true; + + // Alternatively, the analyzer could know that the receiver is not null. + SVal Receiver = MCall->getReceiverSVal(); + ConditionTruthVal TV = C.getState()->isNonNull(Receiver); + if (TV.isConstrainedTrue()) + return true; + + return false; + } + +public: + void checkPostCall(const CallEvent &Call, CheckerContext &C) const { + // Only trust annotations for system headers for non-protocols. + if (!Call.isInSystemHeader()) + return; + + ProgramStateRef State = C.getState(); + + if (isNonNullPtr(Call, C)) + if (auto L = Call.getReturnValue().getAs<Loc>()) + State = State->assume(*L, /*Assumption=*/true); + + C.addTransition(State); + } +}; + +} // end empty namespace + + +void ento::registerTrustNonnullChecker(CheckerManager &Mgr) { + Mgr.registerChecker<TrustNonnullChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp index 0a274292aa39..934ee63318fa 100644 --- a/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp @@ -59,7 +59,7 @@ public: void UndefBranchChecker::checkBranchCondition(const Stmt *Condition, CheckerContext &Ctx) const { - SVal X = Ctx.getState()->getSVal(Condition, Ctx.getLocationContext()); + SVal X = Ctx.getSVal(Condition); if (X.isUndef()) { // Generate a sink node, which implicitly marks both outgoing branches as // infeasible. diff --git a/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp index 17fe8610da06..6a93c10c7644 100644 --- a/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp @@ -55,9 +55,7 @@ UndefCapturedBlockVarChecker::checkPostStmt(const BlockExpr *BE, return; ProgramStateRef state = C.getState(); - const BlockDataRegion *R = - cast<BlockDataRegion>(state->getSVal(BE, - C.getLocationContext()).getAsRegion()); + auto *R = cast<BlockDataRegion>(C.getSVal(BE).getAsRegion()); BlockDataRegion::referenced_vars_iterator I = R->referenced_vars_begin(), E = R->referenced_vars_end(); diff --git a/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp index 172ce346f1ba..b9a93bedca2e 100644 --- a/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp @@ -37,12 +37,11 @@ public: static bool isArrayIndexOutOfBounds(CheckerContext &C, const Expr *Ex) { ProgramStateRef state = C.getState(); - const LocationContext *LCtx = C.getLocationContext(); if (!isa<ArraySubscriptExpr>(Ex)) return false; - SVal Loc = state->getSVal(Ex, LCtx); + SVal Loc = C.getSVal(Ex); if (!Loc.isValid()) return false; @@ -64,11 +63,18 @@ static bool isShiftOverflow(const BinaryOperator *B, CheckerContext &C) { B->getRHS(), C.getASTContext().getIntWidth(B->getLHS()->getType())); } +static bool isLeftShiftResultUnrepresentable(const BinaryOperator *B, + CheckerContext &C) { + SValBuilder &SB = C.getSValBuilder(); + ProgramStateRef State = C.getState(); + const llvm::APSInt *LHS = SB.getKnownValue(State, C.getSVal(B->getLHS())); + const llvm::APSInt *RHS = SB.getKnownValue(State, C.getSVal(B->getRHS())); + return (unsigned)RHS->getZExtValue() > LHS->countLeadingZeros(); +} + void UndefResultChecker::checkPostStmt(const BinaryOperator *B, CheckerContext &C) const { - ProgramStateRef state = C.getState(); - const LocationContext *LCtx = C.getLocationContext(); - if (state->getSVal(B, LCtx).isUndef()) { + if (C.getSVal(B).isUndef()) { // Do not report assignments of uninitialized values inside swap functions. // This should allow to swap partially uninitialized structs @@ -92,11 +98,11 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B, const Expr *Ex = nullptr; bool isLeft = true; - if (state->getSVal(B->getLHS(), LCtx).isUndef()) { + if (C.getSVal(B->getLHS()).isUndef()) { Ex = B->getLHS()->IgnoreParenCasts(); isLeft = true; } - else if (state->getSVal(B->getRHS(), LCtx).isUndef()) { + else if (C.getSVal(B->getRHS()).isUndef()) { Ex = B->getRHS()->IgnoreParenCasts(); isLeft = false; } @@ -141,6 +147,19 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B, C.isNegative(B->getLHS())) { OS << "The result of the left shift is undefined because the left " "operand is negative"; + } else if (B->getOpcode() == BinaryOperatorKind::BO_Shl && + isLeftShiftResultUnrepresentable(B, C)) { + ProgramStateRef State = C.getState(); + SValBuilder &SB = C.getSValBuilder(); + const llvm::APSInt *LHS = + SB.getKnownValue(State, C.getSVal(B->getLHS())); + const llvm::APSInt *RHS = + SB.getKnownValue(State, C.getSVal(B->getRHS())); + OS << "The result of the left shift is undefined due to shifting \'" + << LHS->getSExtValue() << "\' by \'" << RHS->getZExtValue() + << "\', which is unrepresentable in the unsigned version of " + << "the return type \'" << B->getLHS()->getType().getAsString() + << "\'"; } else { OS << "The result of the '" << BinaryOperator::getOpcodeStr(B->getOpcode()) diff --git a/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp index c3dcf1fac197..2ef6855ba6b7 100644 --- a/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp @@ -51,17 +51,20 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, if (!N) return; - const char *str = "Assigned value is garbage or undefined"; - + static const char *const DefaultMsg = + "Assigned value is garbage or undefined"; if (!BT) - BT.reset(new BuiltinBug(this, str)); + BT.reset(new BuiltinBug(this, DefaultMsg)); // Generate a report for this bug. + llvm::SmallString<128> Str; + llvm::raw_svector_ostream OS(Str); + const Expr *ex = nullptr; while (StoreE) { if (const UnaryOperator *U = dyn_cast<UnaryOperator>(StoreE)) { - str = "The expression is an uninitialized value. " + OS << "The expression is an uninitialized value. " "The computed value will also be garbage"; ex = U->getSubExpr(); @@ -70,9 +73,8 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, if (const BinaryOperator *B = dyn_cast<BinaryOperator>(StoreE)) { if (B->isCompoundAssignmentOp()) { - ProgramStateRef state = C.getState(); - if (state->getSVal(B->getLHS(), C.getLocationContext()).isUndef()) { - str = "The left expression of the compound assignment is an " + if (C.getSVal(B->getLHS()).isUndef()) { + OS << "The left expression of the compound assignment is an " "uninitialized value. The computed value will also be garbage"; ex = B->getLHS(); break; @@ -88,10 +90,26 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, ex = VD->getInit(); } + if (const auto *CD = + dyn_cast<CXXConstructorDecl>(C.getStackFrame()->getDecl())) { + if (CD->isImplicit()) { + for (auto I : CD->inits()) { + if (I->getInit()->IgnoreImpCasts() == StoreE) { + OS << "Value assigned to field '" << I->getMember()->getName() + << "' in implicit constructor is garbage or undefined"; + break; + } + } + } + } + break; } - auto R = llvm::make_unique<BugReport>(*BT, str, N); + if (OS.str().empty()) + OS << DefaultMsg; + + auto R = llvm::make_unique<BugReport>(*BT, OS.str(), N); if (ex) { R->addRange(ex->getSourceRange()); bugreporter::trackNullOrUndefValue(N, ex, *R); diff --git a/lib/StaticAnalyzer/Checkers/UninitializedObjectChecker.cpp b/lib/StaticAnalyzer/Checkers/UninitializedObjectChecker.cpp new file mode 100644 index 000000000000..398228a9d887 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/UninitializedObjectChecker.cpp @@ -0,0 +1,688 @@ +//===----- UninitializedObjectChecker.cpp ------------------------*- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines a checker that reports uninitialized fields in objects +// created after a constructor call. +// +// This checker has two options: +// - "Pedantic" (boolean). If its not set or is set to false, the checker +// won't emit warnings for objects that don't have at least one initialized +// field. This may be set with +// +// `-analyzer-config alpha.cplusplus.UninitializedObject:Pedantic=true`. +// +// - "NotesAsWarnings" (boolean). If set to true, the checker will emit a +// warning for each uninitalized field, as opposed to emitting one warning +// per constructor call, and listing the uninitialized fields that belongs +// to it in notes. Defaults to false. +// +// `-analyzer-config alpha.cplusplus.UninitializedObject:NotesAsWarnings=true`. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include <algorithm> + +using namespace clang; +using namespace clang::ento; + +namespace { + +class UninitializedObjectChecker : public Checker<check::EndFunction> { + std::unique_ptr<BuiltinBug> BT_uninitField; + +public: + // These fields will be initialized when registering the checker. + bool IsPedantic; + bool ShouldConvertNotesToWarnings; + + UninitializedObjectChecker() + : BT_uninitField(new BuiltinBug(this, "Uninitialized fields")) {} + void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; +}; + +/// Represents a field chain. A field chain is a vector of fields where the +/// first element of the chain is the object under checking (not stored), and +/// every other element is a field, and the element that precedes it is the +/// object that contains it. +/// +/// Note that this class is immutable, and new fields may only be added through +/// constructor calls. +class FieldChainInfo { + using FieldChain = llvm::ImmutableList<const FieldRegion *>; + + FieldChain Chain; + + const bool IsDereferenced = false; + +public: + FieldChainInfo() = default; + + FieldChainInfo(const FieldChainInfo &Other, const bool IsDereferenced) + : Chain(Other.Chain), IsDereferenced(IsDereferenced) {} + + FieldChainInfo(const FieldChainInfo &Other, const FieldRegion *FR, + const bool IsDereferenced = false); + + bool contains(const FieldRegion *FR) const { return Chain.contains(FR); } + bool isPointer() const; + + /// If this is a fieldchain whose last element is an uninitialized region of a + /// pointer type, `IsDereferenced` will store whether the pointer itself or + /// the pointee is uninitialized. + bool isDereferenced() const; + const FieldDecl *getEndOfChain() const; + void print(llvm::raw_ostream &Out) const; + +private: + /// Prints every element except the last to `Out`. Since ImmutableLists store + /// elements in reverse order, and have no reverse iterators, we use a + /// recursive function to print the fieldchain correctly. The last element in + /// the chain is to be printed by `print`. + static void printTail(llvm::raw_ostream &Out, + const llvm::ImmutableListImpl<const FieldRegion *> *L); + friend struct FieldChainInfoComparator; +}; + +struct FieldChainInfoComparator { + bool operator()(const FieldChainInfo &lhs, const FieldChainInfo &rhs) const { + assert(!lhs.Chain.isEmpty() && !rhs.Chain.isEmpty() && + "Attempted to store an empty fieldchain!"); + return *lhs.Chain.begin() < *rhs.Chain.begin(); + } +}; + +using UninitFieldSet = std::set<FieldChainInfo, FieldChainInfoComparator>; + +/// Searches for and stores uninitialized fields in a non-union object. +class FindUninitializedFields { + ProgramStateRef State; + const TypedValueRegion *const ObjectR; + + const bool IsPedantic; + bool IsAnyFieldInitialized = false; + + UninitFieldSet UninitFields; + +public: + FindUninitializedFields(ProgramStateRef State, + const TypedValueRegion *const R, bool IsPedantic); + const UninitFieldSet &getUninitFields(); + +private: + /// Adds a FieldChainInfo object to UninitFields. Return true if an insertion + /// took place. + bool addFieldToUninits(FieldChainInfo LocalChain); + + // For the purposes of this checker, we'll regard the object under checking as + // a directed tree, where + // * the root is the object under checking + // * every node is an object that is + // - a union + // - a non-union record + // - a pointer/reference + // - an array + // - of a primitive type, which we'll define later in a helper function. + // * the parent of each node is the object that contains it + // * every leaf is an array, a primitive object, a nullptr or an undefined + // pointer. + // + // Example: + // + // struct A { + // struct B { + // int x, y = 0; + // }; + // B b; + // int *iptr = new int; + // B* bptr; + // + // A() {} + // }; + // + // The directed tree: + // + // ->x + // / + // ->b--->y + // / + // A-->iptr->(int value) + // \ + // ->bptr + // + // From this we'll construct a vector of fieldchains, where each fieldchain + // represents an uninitialized field. An uninitialized field may be a + // primitive object, a pointer, a pointee or a union without a single + // initialized field. + // In the above example, for the default constructor call we'll end up with + // these fieldchains: + // + // this->b.x + // this->iptr (pointee uninit) + // this->bptr (pointer uninit) + // + // We'll traverse each node of the above graph with the appropiate one of + // these methods: + + /// This method checks a region of a union object, and returns true if no + /// field is initialized within the region. + bool isUnionUninit(const TypedValueRegion *R); + + /// This method checks a region of a non-union object, and returns true if + /// an uninitialized field is found within the region. + bool isNonUnionUninit(const TypedValueRegion *R, FieldChainInfo LocalChain); + + /// This method checks a region of a pointer or reference object, and returns + /// true if the ptr/ref object itself or any field within the pointee's region + /// is uninitialized. + bool isPointerOrReferenceUninit(const FieldRegion *FR, + FieldChainInfo LocalChain); + + /// This method returns true if the value of a primitive object is + /// uninitialized. + bool isPrimitiveUninit(const SVal &V); + + // Note that we don't have a method for arrays -- the elements of an array are + // often left uninitialized intentionally even when it is of a C++ record + // type, so we'll assume that an array is always initialized. + // TODO: Add a support for nonloc::LocAsInteger. +}; + +} // end of anonymous namespace + +// Static variable instantionations. + +static llvm::ImmutableListFactory<const FieldRegion *> Factory; + +// Utility function declarations. + +/// Returns the object that was constructed by CtorDecl, or None if that isn't +/// possible. +static Optional<nonloc::LazyCompoundVal> +getObjectVal(const CXXConstructorDecl *CtorDecl, CheckerContext &Context); + +/// Checks whether the constructor under checking is called by another +/// constructor. +static bool isCalledByConstructor(const CheckerContext &Context); + +/// Returns whether FD can be (transitively) dereferenced to a void pointer type +/// (void*, void**, ...). The type of the region behind a void pointer isn't +/// known, and thus FD can not be analyzed. +static bool isVoidPointer(const FieldDecl *FD); + +/// Returns true if T is a primitive type. We defined this type so that for +/// objects that we'd only like analyze as much as checking whether their +/// value is undefined or not, such as ints and doubles, can be analyzed with +/// ease. This also helps ensuring that every special field type is handled +/// correctly. +static bool isPrimitiveType(const QualType &T) { + return T->isBuiltinType() || T->isEnumeralType() || T->isMemberPointerType(); +} + +/// Constructs a note message for a given FieldChainInfo object. +static void printNoteMessage(llvm::raw_ostream &Out, + const FieldChainInfo &Chain); + +/// Returns with Field's name. This is a helper function to get the correct name +/// even if Field is a captured lambda variable. +static StringRef getVariableName(const FieldDecl *Field); + +//===----------------------------------------------------------------------===// +// Methods for UninitializedObjectChecker. +//===----------------------------------------------------------------------===// + +void UninitializedObjectChecker::checkEndFunction( + const ReturnStmt *RS, CheckerContext &Context) const { + + const auto *CtorDecl = dyn_cast_or_null<CXXConstructorDecl>( + Context.getLocationContext()->getDecl()); + if (!CtorDecl) + return; + + if (!CtorDecl->isUserProvided()) + return; + + if (CtorDecl->getParent()->isUnion()) + return; + + // This avoids essentially the same error being reported multiple times. + if (isCalledByConstructor(Context)) + return; + + Optional<nonloc::LazyCompoundVal> Object = getObjectVal(CtorDecl, Context); + if (!Object) + return; + + FindUninitializedFields F(Context.getState(), Object->getRegion(), + IsPedantic); + + const UninitFieldSet &UninitFields = F.getUninitFields(); + + if (UninitFields.empty()) + return; + + // There are uninitialized fields in the record. + + ExplodedNode *Node = Context.generateNonFatalErrorNode(Context.getState()); + if (!Node) + return; + + PathDiagnosticLocation LocUsedForUniqueing; + const Stmt *CallSite = Context.getStackFrame()->getCallSite(); + if (CallSite) + LocUsedForUniqueing = PathDiagnosticLocation::createBegin( + CallSite, Context.getSourceManager(), Node->getLocationContext()); + + // For Plist consumers that don't support notes just yet, we'll convert notes + // to warnings. + if (ShouldConvertNotesToWarnings) { + for (const auto &Chain : UninitFields) { + SmallString<100> WarningBuf; + llvm::raw_svector_ostream WarningOS(WarningBuf); + + printNoteMessage(WarningOS, Chain); + + auto Report = llvm::make_unique<BugReport>( + *BT_uninitField, WarningOS.str(), Node, LocUsedForUniqueing, + Node->getLocationContext()->getDecl()); + Context.emitReport(std::move(Report)); + } + return; + } + + SmallString<100> WarningBuf; + llvm::raw_svector_ostream WarningOS(WarningBuf); + WarningOS << UninitFields.size() << " uninitialized field" + << (UninitFields.size() == 1 ? "" : "s") + << " at the end of the constructor call"; + + auto Report = llvm::make_unique<BugReport>( + *BT_uninitField, WarningOS.str(), Node, LocUsedForUniqueing, + Node->getLocationContext()->getDecl()); + + for (const auto &Chain : UninitFields) { + SmallString<200> NoteBuf; + llvm::raw_svector_ostream NoteOS(NoteBuf); + + printNoteMessage(NoteOS, Chain); + + Report->addNote(NoteOS.str(), + PathDiagnosticLocation::create(Chain.getEndOfChain(), + Context.getSourceManager())); + } + Context.emitReport(std::move(Report)); +} + +//===----------------------------------------------------------------------===// +// Methods for FindUninitializedFields. +//===----------------------------------------------------------------------===// + +FindUninitializedFields::FindUninitializedFields( + ProgramStateRef State, const TypedValueRegion *const R, bool IsPedantic) + : State(State), ObjectR(R), IsPedantic(IsPedantic) {} + +const UninitFieldSet &FindUninitializedFields::getUninitFields() { + isNonUnionUninit(ObjectR, FieldChainInfo()); + + if (!IsPedantic && !IsAnyFieldInitialized) + UninitFields.clear(); + + return UninitFields; +} + +bool FindUninitializedFields::addFieldToUninits(FieldChainInfo Chain) { + if (State->getStateManager().getContext().getSourceManager().isInSystemHeader( + Chain.getEndOfChain()->getLocation())) + return false; + + return UninitFields.insert(Chain).second; +} + +bool FindUninitializedFields::isNonUnionUninit(const TypedValueRegion *R, + FieldChainInfo LocalChain) { + assert(R->getValueType()->isRecordType() && + !R->getValueType()->isUnionType() && + "This method only checks non-union record objects!"); + + const RecordDecl *RD = + R->getValueType()->getAs<RecordType>()->getDecl()->getDefinition(); + assert(RD && "Referred record has no definition"); + + bool ContainsUninitField = false; + + // Are all of this non-union's fields initialized? + for (const FieldDecl *I : RD->fields()) { + + const auto FieldVal = + State->getLValue(I, loc::MemRegionVal(R)).castAs<loc::MemRegionVal>(); + const auto *FR = FieldVal.getRegionAs<FieldRegion>(); + QualType T = I->getType(); + + // If LocalChain already contains FR, then we encountered a cyclic + // reference. In this case, region FR is already under checking at an + // earlier node in the directed tree. + if (LocalChain.contains(FR)) + return false; + + if (T->isStructureOrClassType()) { + if (isNonUnionUninit(FR, {LocalChain, FR})) + ContainsUninitField = true; + continue; + } + + if (T->isUnionType()) { + if (isUnionUninit(FR)) { + if (addFieldToUninits({LocalChain, FR})) + ContainsUninitField = true; + } else + IsAnyFieldInitialized = true; + continue; + } + + if (T->isArrayType()) { + IsAnyFieldInitialized = true; + continue; + } + + if (T->isPointerType() || T->isReferenceType()) { + if (isPointerOrReferenceUninit(FR, LocalChain)) + ContainsUninitField = true; + continue; + } + + if (isPrimitiveType(T)) { + SVal V = State->getSVal(FieldVal); + + if (isPrimitiveUninit(V)) { + if (addFieldToUninits({LocalChain, FR})) + ContainsUninitField = true; + } + continue; + } + + llvm_unreachable("All cases are handled!"); + } + + // Checking bases. + // FIXME: As of now, because of `isCalledByConstructor`, objects whose type + // is a descendant of another type will emit warnings for uninitalized + // inherited members. + // This is not the only way to analyze bases of an object -- if we didn't + // filter them out, and didn't analyze the bases, this checker would run for + // each base of the object in order of base initailization and in theory would + // find every uninitalized field. This approach could also make handling + // diamond inheritances more easily. + // + // This rule (that a descendant type's cunstructor is responsible for + // initializing inherited data members) is not obvious, and should it should + // be. + const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD); + if (!CXXRD) + return ContainsUninitField; + + for (const CXXBaseSpecifier &BaseSpec : CXXRD->bases()) { + const auto *BaseRegion = State->getLValue(BaseSpec, R) + .castAs<loc::MemRegionVal>() + .getRegionAs<TypedValueRegion>(); + + if (isNonUnionUninit(BaseRegion, LocalChain)) + ContainsUninitField = true; + } + + return ContainsUninitField; +} + +bool FindUninitializedFields::isUnionUninit(const TypedValueRegion *R) { + assert(R->getValueType()->isUnionType() && + "This method only checks union objects!"); + // TODO: Implement support for union fields. + return false; +} + +// Note that pointers/references don't contain fields themselves, so in this +// function we won't add anything to LocalChain. +bool FindUninitializedFields::isPointerOrReferenceUninit( + const FieldRegion *FR, FieldChainInfo LocalChain) { + + assert((FR->getDecl()->getType()->isPointerType() || + FR->getDecl()->getType()->isReferenceType()) && + "This method only checks pointer/reference objects!"); + + SVal V = State->getSVal(FR); + + if (V.isUnknown() || V.isZeroConstant()) { + IsAnyFieldInitialized = true; + return false; + } + + if (V.isUndef()) { + return addFieldToUninits({LocalChain, FR}); + } + + const FieldDecl *FD = FR->getDecl(); + + // TODO: The dynamic type of a void pointer may be retrieved with + // `getDynamicTypeInfo`. + if (isVoidPointer(FD)) { + IsAnyFieldInitialized = true; + return false; + } + + assert(V.getAs<Loc>() && "V should be Loc at this point!"); + + // At this point the pointer itself is initialized and points to a valid + // location, we'll now check the pointee. + SVal DerefdV = State->getSVal(V.castAs<Loc>()); + + // TODO: Dereferencing should be done according to the dynamic type. + while (Optional<Loc> L = DerefdV.getAs<Loc>()) { + DerefdV = State->getSVal(*L); + } + + // If V is a pointer pointing to a record type. + if (Optional<nonloc::LazyCompoundVal> RecordV = + DerefdV.getAs<nonloc::LazyCompoundVal>()) { + + const TypedValueRegion *R = RecordV->getRegion(); + + // We can't reason about symbolic regions, assume its initialized. + // Note that this also avoids a potential infinite recursion, because + // constructors for list-like classes are checked without being called, and + // the Static Analyzer will construct a symbolic region for Node *next; or + // similar code snippets. + if (R->getSymbolicBase()) { + IsAnyFieldInitialized = true; + return false; + } + + const QualType T = R->getValueType(); + + if (T->isStructureOrClassType()) + return isNonUnionUninit(R, {LocalChain, FR}); + + if (T->isUnionType()) { + if (isUnionUninit(R)) { + return addFieldToUninits({LocalChain, FR, /*IsDereferenced*/ true}); + } else { + IsAnyFieldInitialized = true; + return false; + } + } + + if (T->isArrayType()) { + IsAnyFieldInitialized = true; + return false; + } + + llvm_unreachable("All cases are handled!"); + } + + // TODO: If possible, it should be asserted that the DerefdV at this point is + // primitive. + + if (isPrimitiveUninit(DerefdV)) + return addFieldToUninits({LocalChain, FR, /*IsDereferenced*/ true}); + + IsAnyFieldInitialized = true; + return false; +} + +bool FindUninitializedFields::isPrimitiveUninit(const SVal &V) { + if (V.isUndef()) + return true; + + IsAnyFieldInitialized = true; + return false; +} + +//===----------------------------------------------------------------------===// +// Methods for FieldChainInfo. +//===----------------------------------------------------------------------===// + +FieldChainInfo::FieldChainInfo(const FieldChainInfo &Other, + const FieldRegion *FR, const bool IsDereferenced) + : FieldChainInfo(Other, IsDereferenced) { + assert(!contains(FR) && "Can't add a field that is already a part of the " + "fieldchain! Is this a cyclic reference?"); + Chain = Factory.add(FR, Other.Chain); +} + +bool FieldChainInfo::isPointer() const { + assert(!Chain.isEmpty() && "Empty fieldchain!"); + return (*Chain.begin())->getDecl()->getType()->isPointerType(); +} + +bool FieldChainInfo::isDereferenced() const { + assert(isPointer() && "Only pointers may or may not be dereferenced!"); + return IsDereferenced; +} + +const FieldDecl *FieldChainInfo::getEndOfChain() const { + assert(!Chain.isEmpty() && "Empty fieldchain!"); + return (*Chain.begin())->getDecl(); +} + +// TODO: This function constructs an incorrect fieldchain string in the +// following case: +// +// struct Base { int x; }; +// struct D1 : Base {}; struct D2 : Base {}; +// +// struct MostDerived : D1, D2 { +// MostDerived() {} +// } +// +// A call to MostDerived::MostDerived() will cause two notes that say +// "uninitialized field 'this->x'", but we can't refer to 'x' directly, +// we need an explicit namespace resolution whether the uninit field was +// 'D1::x' or 'D2::x'. +void FieldChainInfo::print(llvm::raw_ostream &Out) const { + if (Chain.isEmpty()) + return; + + const llvm::ImmutableListImpl<const FieldRegion *> *L = + Chain.getInternalPointer(); + printTail(Out, L->getTail()); + Out << getVariableName(L->getHead()->getDecl()); +} + +void FieldChainInfo::printTail( + llvm::raw_ostream &Out, + const llvm::ImmutableListImpl<const FieldRegion *> *L) { + if (!L) + return; + + printTail(Out, L->getTail()); + const FieldDecl *Field = L->getHead()->getDecl(); + Out << getVariableName(Field); + Out << (Field->getType()->isPointerType() ? "->" : "."); +} + +//===----------------------------------------------------------------------===// +// Utility functions. +//===----------------------------------------------------------------------===// + +static bool isVoidPointer(const FieldDecl *FD) { + QualType T = FD->getType(); + + while (!T.isNull()) { + if (T->isVoidPointerType()) + return true; + T = T->getPointeeType(); + } + return false; +} + +static Optional<nonloc::LazyCompoundVal> +getObjectVal(const CXXConstructorDecl *CtorDecl, CheckerContext &Context) { + + Loc ThisLoc = Context.getSValBuilder().getCXXThis(CtorDecl->getParent(), + Context.getStackFrame()); + // Getting the value for 'this'. + SVal This = Context.getState()->getSVal(ThisLoc); + + // Getting the value for '*this'. + SVal Object = Context.getState()->getSVal(This.castAs<Loc>()); + + return Object.getAs<nonloc::LazyCompoundVal>(); +} + +// TODO: We should also check that if the constructor was called by another +// constructor, whether those two are in any relation to one another. In it's +// current state, this introduces some false negatives. +static bool isCalledByConstructor(const CheckerContext &Context) { + const LocationContext *LC = Context.getLocationContext()->getParent(); + + while (LC) { + if (isa<CXXConstructorDecl>(LC->getDecl())) + return true; + + LC = LC->getParent(); + } + return false; +} + +static void printNoteMessage(llvm::raw_ostream &Out, + const FieldChainInfo &Chain) { + if (Chain.isPointer()) { + if (Chain.isDereferenced()) + Out << "uninitialized pointee 'this->"; + else + Out << "uninitialized pointer 'this->"; + } else + Out << "uninitialized field 'this->"; + Chain.print(Out); + Out << "'"; +} + +static StringRef getVariableName(const FieldDecl *Field) { + // If Field is a captured lambda variable, Field->getName() will return with + // an empty string. We can however acquire it's name from the lambda's + // captures. + const auto *CXXParent = dyn_cast<CXXRecordDecl>(Field->getParent()); + + if (CXXParent && CXXParent->isLambda()) { + assert(CXXParent->captures_begin()); + auto It = CXXParent->captures_begin() + Field->getFieldIndex(); + return It->getCapturedVar()->getName(); + } + + return Field->getName(); +} + +void ento::registerUninitializedObjectChecker(CheckerManager &Mgr) { + auto Chk = Mgr.registerChecker<UninitializedObjectChecker>(); + Chk->IsPedantic = Mgr.getAnalyzerOptions().getBooleanOption( + "Pedantic", /*DefaultVal*/ false, Chk); + Chk->ShouldConvertNotesToWarnings = Mgr.getAnalyzerOptions().getBooleanOption( + "NotesAsWarnings", /*DefaultVal*/ false, Chk); +} diff --git a/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp index 7f9a00ff876d..a6b50dc37740 100644 --- a/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp @@ -194,7 +194,7 @@ void UnixAPIChecker::CheckOpenVariant(CheckerContext &C, // Now check if oflags has O_CREAT set. const Expr *oflagsEx = CE->getArg(FlagsArgIndex); - const SVal V = state->getSVal(oflagsEx, C.getLocationContext()); + const SVal V = C.getSVal(oflagsEx); if (!V.getAs<NonLoc>()) { // The case where 'V' can be a location can only be due to a bad header, // so in this case bail out. @@ -248,8 +248,7 @@ void UnixAPIChecker::CheckPthreadOnce(CheckerContext &C, // Check if the first argument is stack allocated. If so, issue a warning // because that's likely to be bad news. ProgramStateRef state = C.getState(); - const MemRegion *R = - state->getSVal(CE->getArg(0), C.getLocationContext()).getAsRegion(); + const MemRegion *R = C.getSVal(CE->getArg(0)).getAsRegion(); if (!R || !isa<StackSpaceRegion>(R->getMemorySpace())) return; @@ -336,7 +335,7 @@ void UnixAPIChecker::BasicAllocationCheck(CheckerContext &C, ProgramStateRef state = C.getState(); ProgramStateRef trueState = nullptr, falseState = nullptr; const Expr *arg = CE->getArg(sizeArg); - SVal argVal = state->getSVal(arg, C.getLocationContext()); + SVal argVal = C.getSVal(arg); if (argVal.isUnknownOrUndef()) return; @@ -364,7 +363,7 @@ void UnixAPIChecker::CheckCallocZero(CheckerContext &C, unsigned int i; for (i = 0; i < nArgs; i++) { const Expr *arg = CE->getArg(i); - SVal argVal = state->getSVal(arg, C.getLocationContext()); + SVal argVal = C.getSVal(arg); if (argVal.isUnknownOrUndef()) { if (i == 0) continue; diff --git a/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp b/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp index 6f21e868b174..dbd12cc9b65a 100644 --- a/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp @@ -132,7 +132,8 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, ci != ce; ++ci) { if (Optional<CFGStmt> S = (*ci).getAs<CFGStmt>()) if (const CallExpr *CE = dyn_cast<CallExpr>(S->getStmt())) { - if (CE->getBuiltinCallee() == Builtin::BI__builtin_unreachable) { + if (CE->getBuiltinCallee() == Builtin::BI__builtin_unreachable || + CE->isBuiltinAssumeFalse(Eng.getContext())) { foundUnreachable = true; break; } diff --git a/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp b/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp index 40217bdee892..2584f2011819 100644 --- a/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp @@ -32,19 +32,18 @@ class VLASizeChecker : public Checker< check::PreStmt<DeclStmt> > { mutable std::unique_ptr<BugType> BT; enum VLASize_Kind { VLA_Garbage, VLA_Zero, VLA_Tainted, VLA_Negative }; - void reportBug(VLASize_Kind Kind, - const Expr *SizeE, - ProgramStateRef State, - CheckerContext &C) const; + void reportBug(VLASize_Kind Kind, const Expr *SizeE, ProgramStateRef State, + CheckerContext &C, + std::unique_ptr<BugReporterVisitor> Visitor = nullptr) const; + public: void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const; }; } // end anonymous namespace -void VLASizeChecker::reportBug(VLASize_Kind Kind, - const Expr *SizeE, - ProgramStateRef State, - CheckerContext &C) const { +void VLASizeChecker::reportBug( + VLASize_Kind Kind, const Expr *SizeE, ProgramStateRef State, + CheckerContext &C, std::unique_ptr<BugReporterVisitor> Visitor) const { // Generate an error node. ExplodedNode *N = C.generateErrorNode(State); if (!N) @@ -73,6 +72,7 @@ void VLASizeChecker::reportBug(VLASize_Kind Kind, } auto report = llvm::make_unique<BugReport>(*BT, os.str(), N); + report->addVisitor(std::move(Visitor)); report->addRange(SizeE->getSourceRange()); bugreporter::trackNullOrUndefValue(N, SizeE, *report); C.emitReport(std::move(report)); @@ -94,7 +94,7 @@ void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { // FIXME: Handle multi-dimensional VLAs. const Expr *SE = VLA->getSizeExpr(); ProgramStateRef state = C.getState(); - SVal sizeV = state->getSVal(SE, C.getLocationContext()); + SVal sizeV = C.getSVal(SE); if (sizeV.isUndef()) { reportBug(VLA_Garbage, SE, state, C); @@ -108,7 +108,8 @@ void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { // Check if the size is tainted. if (state->isTainted(sizeV)) { - reportBug(VLA_Tainted, SE, nullptr, C); + reportBug(VLA_Tainted, SE, nullptr, C, + llvm::make_unique<TaintBugVisitor>(sizeV)); return; } diff --git a/lib/StaticAnalyzer/Checkers/ValistChecker.cpp b/lib/StaticAnalyzer/Checkers/ValistChecker.cpp index 06c4ef71d80b..bd657340fcfb 100644 --- a/lib/StaticAnalyzer/Checkers/ValistChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ValistChecker.cpp @@ -56,7 +56,6 @@ public: private: const MemRegion *getVAListAsRegion(SVal SV, const Expr *VAExpr, bool &IsSymbolic, CheckerContext &C) const; - StringRef getVariableNameFromRegion(const MemRegion *Reg) const; const ExplodedNode *getStartCallSite(const ExplodedNode *N, const MemRegion *Reg) const; @@ -64,13 +63,13 @@ private: CheckerContext &C) const; void reportLeakedVALists(const RegionVector &LeakedVALists, StringRef Msg1, StringRef Msg2, CheckerContext &C, ExplodedNode *N, - bool ForceReport = false) const; + bool ReportUninit = false) const; void checkVAListStartCall(const CallEvent &Call, CheckerContext &C, bool IsCopy) const; void checkVAListEndCall(const CallEvent &Call, CheckerContext &C) const; - class ValistBugVisitor : public BugReporterVisitorImpl<ValistBugVisitor> { + class ValistBugVisitor : public BugReporterVisitor { public: ValistBugVisitor(const MemRegion *Reg, bool IsLeak = false) : Reg(Reg), IsLeak(IsLeak) {} @@ -79,7 +78,7 @@ private: ID.AddPointer(&X); ID.AddPointer(Reg); } - std::unique_ptr<PathDiagnosticPiece> + std::shared_ptr<PathDiagnosticPiece> getEndPath(BugReporterContext &BRC, const ExplodedNode *EndPathNode, BugReport &BR) override { if (!IsLeak) @@ -88,8 +87,7 @@ private: PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath( EndPathNode, BRC.getSourceManager()); // Do not add the statement itself as a range in case of leak. - return llvm::make_unique<PathDiagnosticEventPiece>(L, BR.getDescription(), - false); + return std::make_shared<PathDiagnosticEventPiece>(L, BR.getDescription(), false); } std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, const ExplodedNode *PrevN, @@ -189,7 +187,7 @@ void ValistChecker::checkPreStmt(const VAArgExpr *VAA, CheckerContext &C) const { ProgramStateRef State = C.getState(); const Expr *VASubExpr = VAA->getSubExpr(); - SVal VAListSVal = State->getSVal(VASubExpr, C.getLocationContext()); + SVal VAListSVal = C.getSVal(VASubExpr); bool Symbolic; const MemRegion *VAList = getVAListAsRegion(VAListSVal, VASubExpr, Symbolic, C); @@ -267,15 +265,19 @@ void ValistChecker::reportUninitializedAccess(const MemRegion *VAList, void ValistChecker::reportLeakedVALists(const RegionVector &LeakedVALists, StringRef Msg1, StringRef Msg2, CheckerContext &C, ExplodedNode *N, - bool ForceReport) const { + bool ReportUninit) const { if (!(ChecksEnabled[CK_Unterminated] || - (ChecksEnabled[CK_Uninitialized] && ForceReport))) + (ChecksEnabled[CK_Uninitialized] && ReportUninit))) return; for (auto Reg : LeakedVALists) { if (!BT_leakedvalist) { - BT_leakedvalist.reset(new BugType(CheckNames[CK_Unterminated], - "Leaked va_list", - categories::MemoryError)); + // FIXME: maybe creating a new check name for this type of bug is a better + // solution. + BT_leakedvalist.reset( + new BugType(CheckNames[CK_Unterminated].getName().empty() + ? CheckNames[CK_Uninitialized] + : CheckNames[CK_Unterminated], + "Leaked va_list", categories::MemoryError)); BT_leakedvalist->setSuppressOnSink(true); } @@ -375,7 +377,7 @@ void ValistChecker::checkVAListEndCall(const CallEvent &Call, std::shared_ptr<PathDiagnosticPiece> ValistChecker::ValistBugVisitor::VisitNode( const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, - BugReport &BR) { + BugReport &) { ProgramStateRef State = N->getState(); ProgramStateRef StatePrev = PrevN->getState(); diff --git a/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp index c5010f53785a..5b602468cdd4 100644 --- a/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -48,7 +48,7 @@ public: DefaultBool IsPureOnly; void checkBeginFunction(CheckerContext &C) const; - void checkEndFunction(CheckerContext &C) const; + void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; void checkPreCall(const CallEvent &Call, CheckerContext &C) const; private: @@ -57,7 +57,7 @@ private: void reportBug(StringRef Msg, bool PureError, const MemRegion *Reg, CheckerContext &C) const; - class VirtualBugVisitor : public BugReporterVisitorImpl<VirtualBugVisitor> { + class VirtualBugVisitor : public BugReporterVisitor { private: const MemRegion *ObjectRegion; bool Found; @@ -108,7 +108,7 @@ VirtualCallChecker::VirtualBugVisitor::VisitNode(const ExplodedNode *N, if (!MD) return nullptr; auto ThiSVal = - State->getSVal(SVB.getCXXThis(MD, LCtx->getCurrentStackFrame())); + State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); const MemRegion *Reg = ThiSVal.castAs<loc::MemRegionVal>().getRegion(); if (!Reg) return nullptr; @@ -167,7 +167,8 @@ void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const { } // The EndFunction callback when leave a constructor or a destructor. -void VirtualCallChecker::checkEndFunction(CheckerContext &C) const { +void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS, + CheckerContext &C) const { registerCtorDtorCallInState(false, C); } @@ -230,7 +231,7 @@ void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction, // Enter a constructor, set the corresponding memregion be true. if (isa<CXXConstructorDecl>(MD)) { auto ThiSVal = - State->getSVal(SVB.getCXXThis(MD, LCtx->getCurrentStackFrame())); + State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); const MemRegion *Reg = ThiSVal.getAsRegion(); if (IsBeginFunction) State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled); @@ -244,7 +245,7 @@ void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction, // Enter a Destructor, set the corresponding memregion be true. if (isa<CXXDestructorDecl>(MD)) { auto ThiSVal = - State->getSVal(SVB.getCXXThis(MD, LCtx->getCurrentStackFrame())); + State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); const MemRegion *Reg = ThiSVal.getAsRegion(); if (IsBeginFunction) State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled); diff --git a/lib/StaticAnalyzer/Core/AnalysisManager.cpp b/lib/StaticAnalyzer/Core/AnalysisManager.cpp index 1cc08f0d9fe7..dc0d3ec8493a 100644 --- a/lib/StaticAnalyzer/Core/AnalysisManager.cpp +++ b/lib/StaticAnalyzer/Core/AnalysisManager.cpp @@ -26,9 +26,12 @@ AnalysisManager::AnalysisManager( // Adding LoopExit elements to the CFG is a requirement for loop // unrolling. Options.includeLoopExitInCFG() || Options.shouldUnrollLoops(), + Options.includeScopesInCFG(), Options.shouldSynthesizeBodies(), Options.shouldConditionalizeStaticInitializers(), /*addCXXNewAllocator=*/true, + Options.includeRichConstructorsInCFG(), + Options.shouldElideConstructors(), injector), Ctx(ASTCtx), Diags(diags), LangOpts(lang), PathConsumers(PDC), CreateStoreMgr(storemgr), CreateConstraintMgr(constraintmgr), diff --git a/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp b/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp index 48e3e22af04a..9b2dc32e0600 100644 --- a/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ b/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -1,4 +1,4 @@ -//===-- AnalyzerOptions.cpp - Analysis Engine Options -----------*- C++ -*-===// +//===- AnalyzerOptions.cpp - Analysis Engine Options ----------------------===// // // The LLVM Compiler Infrastructure // @@ -16,8 +16,15 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringSwitch.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" #include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/FileSystem.h" #include "llvm/Support/raw_ostream.h" +#include <cassert> +#include <cstddef> +#include <utility> +#include <vector> using namespace clang; using namespace ento; @@ -55,9 +62,32 @@ AnalyzerOptions::UserModeKind AnalyzerOptions::getUserMode() { return UserMode; } +AnalyzerOptions::ExplorationStrategyKind +AnalyzerOptions::getExplorationStrategy() { + if (ExplorationStrategy == ExplorationStrategyKind::NotSet) { + StringRef StratStr = + Config + .insert(std::make_pair("exploration_strategy", "unexplored_first_queue")) + .first->second; + ExplorationStrategy = + llvm::StringSwitch<ExplorationStrategyKind>(StratStr) + .Case("dfs", ExplorationStrategyKind::DFS) + .Case("bfs", ExplorationStrategyKind::BFS) + .Case("unexplored_first", + ExplorationStrategyKind::UnexploredFirst) + .Case("unexplored_first_queue", + ExplorationStrategyKind::UnexploredFirstQueue) + .Case("bfs_block_dfs_contents", + ExplorationStrategyKind::BFSBlockDFSContents) + .Default(ExplorationStrategyKind::NotSet); + assert(ExplorationStrategy != ExplorationStrategyKind::NotSet && + "User mode is invalid."); + } + return ExplorationStrategy; +} + IPAKind AnalyzerOptions::getIPAMode() { if (IPAMode == IPAK_NotSet) { - // Use the User Mode to set the default IPA value. // Note, we have to add the string to the Config map for the ConfigDumper // checker to function properly. @@ -169,7 +199,7 @@ bool AnalyzerOptions::getBooleanOption(Optional<bool> &V, StringRef Name, bool AnalyzerOptions::includeTemporaryDtorsInCFG() { return getBooleanOption(IncludeTemporaryDtorsInCFG, "cfg-temporary-dtors", - /* Default = */ false); + /* Default = */ true); } bool AnalyzerOptions::includeImplicitDtorsInCFG() { @@ -185,7 +215,19 @@ bool AnalyzerOptions::includeLifetimeInCFG() { bool AnalyzerOptions::includeLoopExitInCFG() { return getBooleanOption(IncludeLoopExitInCFG, "cfg-loopexit", - /* Default = */ false); + /* Default = */ false); +} + +bool AnalyzerOptions::includeRichConstructorsInCFG() { + return getBooleanOption(IncludeRichConstructorsInCFG, + "cfg-rich-constructors", + /* Default = */ true); +} + +bool AnalyzerOptions::includeScopesInCFG() { + return getBooleanOption(IncludeScopesInCFG, + "cfg-scopes", + /* Default = */ false); } bool AnalyzerOptions::mayInlineCXXStandardLibrary() { @@ -203,7 +245,7 @@ bool AnalyzerOptions::mayInlineTemplateFunctions() { bool AnalyzerOptions::mayInlineCXXAllocator() { return getBooleanOption(InlineCXXAllocator, "c++-allocator-inlining", - /*Default=*/false); + /*Default=*/true); } bool AnalyzerOptions::mayInlineCXXContainerMethods() { @@ -218,6 +260,11 @@ bool AnalyzerOptions::mayInlineCXXSharedPtrDtor() { /*Default=*/false); } +bool AnalyzerOptions::mayInlineCXXTemporaryDtors() { + return getBooleanOption(InlineCXXTemporaryDtors, + "c++-temp-dtor-inlining", + /*Default=*/true); +} bool AnalyzerOptions::mayInlineObjCMethod() { return getBooleanOption(ObjCInliningMode, @@ -249,6 +296,12 @@ bool AnalyzerOptions::shouldSuppressFromCXXStandardLibrary() { /* Default = */ true); } +bool AnalyzerOptions::shouldCrosscheckWithZ3() { + return getBooleanOption(CrosscheckWithZ3, + "crosscheck-with-z3", + /* Default = */ false); +} + bool AnalyzerOptions::shouldReportIssuesInMainSourceFile() { return getBooleanOption(ReportIssuesInMainSourceFile, "report-in-main-source-file", @@ -262,6 +315,18 @@ bool AnalyzerOptions::shouldWriteStableReportFilename() { /* Default = */ false); } +bool AnalyzerOptions::shouldSerializeStats() { + return getBooleanOption(SerializeStats, + "serialize-stats", + /* Default = */ false); +} + +bool AnalyzerOptions::shouldElideConstructors() { + return getBooleanOption(ElideConstructors, + "elide-constructors", + /* Default = */ true); +} + int AnalyzerOptions::getOptionAsInteger(StringRef Name, int DefaultVal, const CheckerBase *C, bool SearchInParents) { @@ -299,7 +364,6 @@ unsigned AnalyzerOptions::getAlwaysInlineSize() { unsigned AnalyzerOptions::getMaxInlinableSize() { if (!MaxInlinableSize.hasValue()) { - int DefaultValue = 0; UserModeKind HighLevelMode = getUserMode(); switch (HighLevelMode) { @@ -324,6 +388,12 @@ unsigned AnalyzerOptions::getGraphTrimInterval() { return GraphTrimInterval.getValue(); } +unsigned AnalyzerOptions::getMaxSymbolComplexity() { + if (!MaxSymbolComplexity.hasValue()) + MaxSymbolComplexity = getOptionAsInteger("max-symbol-complexity", 35); + return MaxSymbolComplexity.getValue(); +} + unsigned AnalyzerOptions::getMaxTimesInlineLarge() { if (!MaxTimesInlineLarge.hasValue()) MaxTimesInlineLarge = getOptionAsInteger("max-times-inline-large", 32); @@ -392,3 +462,34 @@ bool AnalyzerOptions::shouldDisplayNotesAsEvents() { getBooleanOption("notes-as-events", /*Default=*/false); return DisplayNotesAsEvents.getValue(); } + +bool AnalyzerOptions::shouldAggressivelySimplifyBinaryOperation() { + if (!AggressiveBinaryOperationSimplification.hasValue()) + AggressiveBinaryOperationSimplification = + getBooleanOption("aggressive-binary-operation-simplification", + /*Default=*/false); + return AggressiveBinaryOperationSimplification.getValue(); +} + +StringRef AnalyzerOptions::getCTUDir() { + if (!CTUDir.hasValue()) { + CTUDir = getOptionAsString("ctu-dir", ""); + if (!llvm::sys::fs::is_directory(*CTUDir)) + CTUDir = ""; + } + return CTUDir.getValue(); +} + +bool AnalyzerOptions::naiveCTUEnabled() { + if (!NaiveCTU.hasValue()) { + NaiveCTU = getBooleanOption("experimental-enable-naive-ctu-analysis", + /*Default=*/false); + } + return NaiveCTU.getValue(); +} + +StringRef AnalyzerOptions::getCTUIndexName() { + if (!CTUIndexName.hasValue()) + CTUIndexName = getOptionAsString("ctu-index-name", "externalFnMap.txt"); + return CTUIndexName.getValue(); +} diff --git a/lib/StaticAnalyzer/Core/BasicValueFactory.cpp b/lib/StaticAnalyzer/Core/BasicValueFactory.cpp index ec7a7e9e4b1c..db4c1432ccc3 100644 --- a/lib/StaticAnalyzer/Core/BasicValueFactory.cpp +++ b/lib/StaticAnalyzer/Core/BasicValueFactory.cpp @@ -1,4 +1,4 @@ -//=== BasicValueFactory.cpp - Basic values for Path Sens analysis --*- C++ -*-// +//===- BasicValueFactory.cpp - Basic values for Path Sens analysis --------===// // // The LLVM Compiler Infrastructure // @@ -13,9 +13,18 @@ // //===----------------------------------------------------------------------===// -#include "clang/AST/ASTContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/Store.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/StoreRef.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/ImmutableList.h" +#include "llvm/ADT/STLExtras.h" +#include <cassert> +#include <cstdint> +#include <utility> using namespace clang; using namespace ento; @@ -40,10 +49,11 @@ void PointerToMemberData::Profile( ID.AddPointer(L.getInternalPointer()); } -typedef std::pair<SVal, uintptr_t> SValData; -typedef std::pair<SVal, SVal> SValPair; +using SValData = std::pair<SVal, uintptr_t>; +using SValPair = std::pair<SVal, SVal>; namespace llvm { + template<> struct FoldingSetTrait<SValData> { static inline void Profile(const SValData& X, llvm::FoldingSetNodeID& ID) { X.first.Profile(ID); @@ -57,20 +67,21 @@ template<> struct FoldingSetTrait<SValPair> { X.second.Profile(ID); } }; -} -typedef llvm::FoldingSet<llvm::FoldingSetNodeWrapper<SValData> > - PersistentSValsTy; +} // namespace llvm -typedef llvm::FoldingSet<llvm::FoldingSetNodeWrapper<SValPair> > - PersistentSValPairsTy; +using PersistentSValsTy = + llvm::FoldingSet<llvm::FoldingSetNodeWrapper<SValData>>; + +using PersistentSValPairsTy = + llvm::FoldingSet<llvm::FoldingSetNodeWrapper<SValPair>>; BasicValueFactory::~BasicValueFactory() { // Note that the dstor for the contents of APSIntSet will never be called, // so we iterate over the set and invoke the dstor for each APSInt. This // frees an aux. memory allocated to represent very large constants. - for (APSIntSetTy::iterator I=APSIntSet.begin(), E=APSIntSet.end(); I!=E; ++I) - I->getValue().~APSInt(); + for (const auto &I : APSIntSet) + I.getValue().~APSInt(); delete (PersistentSValsTy*) PersistentSVals; delete (PersistentSValPairsTy*) PersistentSValPairs; @@ -79,7 +90,8 @@ BasicValueFactory::~BasicValueFactory() { const llvm::APSInt& BasicValueFactory::getValue(const llvm::APSInt& X) { llvm::FoldingSetNodeID ID; void *InsertPos; - typedef llvm::FoldingSetNodeWrapper<llvm::APSInt> FoldNodeTy; + + using FoldNodeTy = llvm::FoldingSetNodeWrapper<llvm::APSInt>; X.Profile(ID); FoldNodeTy* P = APSIntSet.FindNodeOrInsertPos(ID, InsertPos); @@ -107,14 +119,12 @@ const llvm::APSInt& BasicValueFactory::getValue(uint64_t X, unsigned BitWidth, } const llvm::APSInt& BasicValueFactory::getValue(uint64_t X, QualType T) { - return getValue(getAPSIntType(T).getValue(X)); } const CompoundValData* BasicValueFactory::getCompoundValData(QualType T, llvm::ImmutableList<SVal> Vals) { - llvm::FoldingSetNodeID ID; CompoundValData::Profile(ID, T, Vals); void *InsertPos; @@ -150,7 +160,7 @@ BasicValueFactory::getLazyCompoundValData(const StoreRef &store, } const PointerToMemberData *BasicValueFactory::getPointerToMemberData( - const DeclaratorDecl *DD, llvm::ImmutableList<const CXXBaseSpecifier*> L) { + const DeclaratorDecl *DD, llvm::ImmutableList<const CXXBaseSpecifier *> L) { llvm::FoldingSetNodeID ID; PointerToMemberData::Profile(ID, DD, L); void *InsertPos; @@ -167,7 +177,7 @@ const PointerToMemberData *BasicValueFactory::getPointerToMemberData( return D; } -const clang::ento::PointerToMemberData *BasicValueFactory::accumCXXBase( +const PointerToMemberData *BasicValueFactory::accumCXXBase( llvm::iterator_range<CastExpr::path_const_iterator> PathRange, const nonloc::PointerToMember &PTM) { nonloc::PointerToMember::PTMDataType PTMDT = PTM.getPTMData(); @@ -195,10 +205,9 @@ const clang::ento::PointerToMemberData *BasicValueFactory::accumCXXBase( const llvm::APSInt* BasicValueFactory::evalAPSInt(BinaryOperator::Opcode Op, const llvm::APSInt& V1, const llvm::APSInt& V2) { - switch (Op) { default: - assert (false && "Invalid Opcode."); + assert(false && "Invalid Opcode."); case BO_Mul: return &getValue( V1 * V2 ); @@ -220,11 +229,9 @@ BasicValueFactory::evalAPSInt(BinaryOperator::Opcode Op, return &getValue( V1 - V2 ); case BO_Shl: { - // FIXME: This logic should probably go higher up, where we can // test these conditions symbolically. - // FIXME: Expand these checks to include all undefined behavior. if (V1.isSigned() && V1.isNegative()) return nullptr; @@ -236,16 +243,16 @@ BasicValueFactory::evalAPSInt(BinaryOperator::Opcode Op, if (Amt >= V1.getBitWidth()) return nullptr; + if (V1.isSigned() && Amt > V1.countLeadingZeros()) + return nullptr; + return &getValue( V1.operator<<( (unsigned) Amt )); } case BO_Shr: { - // FIXME: This logic should probably go higher up, where we can // test these conditions symbolically. - // FIXME: Expand these checks to include all undefined behavior. - if (V2.isSigned() && V2.isNegative()) return nullptr; @@ -288,10 +295,8 @@ BasicValueFactory::evalAPSInt(BinaryOperator::Opcode Op, } } - const std::pair<SVal, uintptr_t>& BasicValueFactory::getPersistentSValWithData(const SVal& V, uintptr_t Data) { - // Lazily create the folding set. if (!PersistentSVals) PersistentSVals = new PersistentSValsTy(); @@ -302,7 +307,8 @@ BasicValueFactory::getPersistentSValWithData(const SVal& V, uintptr_t Data) { PersistentSValsTy& Map = *((PersistentSValsTy*) PersistentSVals); - typedef llvm::FoldingSetNodeWrapper<SValData> FoldNodeTy; + using FoldNodeTy = llvm::FoldingSetNodeWrapper<SValData>; + FoldNodeTy* P = Map.FindNodeOrInsertPos(ID, InsertPos); if (!P) { @@ -316,7 +322,6 @@ BasicValueFactory::getPersistentSValWithData(const SVal& V, uintptr_t Data) { const std::pair<SVal, SVal>& BasicValueFactory::getPersistentSValPair(const SVal& V1, const SVal& V2) { - // Lazily create the folding set. if (!PersistentSValPairs) PersistentSValPairs = new PersistentSValPairsTy(); @@ -327,7 +332,8 @@ BasicValueFactory::getPersistentSValPair(const SVal& V1, const SVal& V2) { PersistentSValPairsTy& Map = *((PersistentSValPairsTy*) PersistentSValPairs); - typedef llvm::FoldingSetNodeWrapper<SValPair> FoldNodeTy; + using FoldNodeTy = llvm::FoldingSetNodeWrapper<SValPair>; + FoldNodeTy* P = Map.FindNodeOrInsertPos(ID, InsertPos); if (!P) { diff --git a/lib/StaticAnalyzer/Core/BugReporter.cpp b/lib/StaticAnalyzer/Core/BugReporter.cpp index dc284888eb03..f990eb6a058d 100644 --- a/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -1,4 +1,4 @@ -// BugReporter.cpp - Generate PathDiagnostics for Bugs ------------*- C++ -*--// +//===- BugReporter.cpp - Generate PathDiagnostics for bugs ----------------===// // // The LLVM Compiler Infrastructure // @@ -13,28 +13,62 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" -#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/ParentMap.h" +#include "clang/AST/Stmt.h" #include "clang/AST/StmtCXX.h" #include "clang/AST/StmtObjC.h" +#include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/CFGStmtMap.h" #include "clang/Analysis/ProgramPoint.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" +#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Statistic.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/iterator_range.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/raw_ostream.h" +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <iterator> #include <memory> #include <queue> +#include <string> +#include <tuple> +#include <utility> +#include <vector> using namespace clang; using namespace ento; @@ -47,7 +81,7 @@ STATISTIC(MaxValidBugClassSize, "The maximum number of bug reports in the same equivalence class " "where at least one report is valid (not suppressed)"); -BugReporterVisitor::~BugReporterVisitor() {} +BugReporterVisitor::~BugReporterVisitor() = default; void BugReporterContext::anchor() {} @@ -127,10 +161,9 @@ static void removeRedundantMsgs(PathPieces &path) { if (i == N-1) break; - if (PathDiagnosticEventPiece *nextEvent = + if (auto *nextEvent = dyn_cast<PathDiagnosticEventPiece>(path.front().get())) { - PathDiagnosticEventPiece *event = - cast<PathDiagnosticEventPiece>(piece.get()); + auto *event = cast<PathDiagnosticEventPiece>(piece.get()); // Check to see if we should keep one of the two pieces. If we // come up with a preference, record which piece to keep, and consume // another piece from the path. @@ -152,15 +185,16 @@ static void removeRedundantMsgs(PathPieces &path) { /// A map from PathDiagnosticPiece to the LocationContext of the inlined /// function call it represents. -typedef llvm::DenseMap<const PathPieces *, const LocationContext *> - LocationContextMap; +using LocationContextMap = + llvm::DenseMap<const PathPieces *, const LocationContext *>; /// Recursively scan through a path and prune out calls and macros pieces /// that aren't needed. Return true if afterwards the path contains /// "interesting stuff" which means it shouldn't be pruned from the parent path. static bool removeUnneededCalls(PathPieces &pieces, BugReport *R, - LocationContextMap &LCM) { - bool containsSomethingInteresting = false; + LocationContextMap &LCM, + bool IsInteresting = false) { + bool containsSomethingInteresting = IsInteresting; const unsigned N = pieces.size(); for (unsigned i = 0 ; i < N ; ++i) { @@ -174,12 +208,8 @@ static bool removeUnneededCalls(PathPieces &pieces, BugReport *R, auto &call = cast<PathDiagnosticCallPiece>(*piece); // Check if the location context is interesting. assert(LCM.count(&call.path)); - if (R->isInteresting(LCM[&call.path])) { - containsSomethingInteresting = true; - break; - } - - if (!removeUnneededCalls(call.path, R, LCM)) + if (!removeUnneededCalls(call.path, R, LCM, + R->isInteresting(LCM[&call.path]))) continue; containsSomethingInteresting = true; @@ -187,7 +217,7 @@ static bool removeUnneededCalls(PathPieces &pieces, BugReport *R, } case PathDiagnosticPiece::Macro: { auto ¯o = cast<PathDiagnosticMacroPiece>(*piece); - if (!removeUnneededCalls(macro.subPieces, R, LCM)) + if (!removeUnneededCalls(macro.subPieces, R, LCM, IsInteresting)) continue; containsSomethingInteresting = true; break; @@ -225,13 +255,11 @@ static bool hasImplicitBody(const Decl *D) { static void adjustCallLocations(PathPieces &Pieces, PathDiagnosticLocation *LastCallLocation = nullptr) { - for (PathPieces::iterator I = Pieces.begin(), E = Pieces.end(); I != E; ++I) { - PathDiagnosticCallPiece *Call = dyn_cast<PathDiagnosticCallPiece>(I->get()); + for (const auto &I : Pieces) { + auto *Call = dyn_cast<PathDiagnosticCallPiece>(I.get()); - if (!Call) { - assert((*I)->getLocation().asLocation().isValid()); + if (!Call) continue; - } if (LastCallLocation) { bool CallerIsImplicit = hasImplicitBody(Call->getCaller()); @@ -314,29 +342,19 @@ static void removePiecesWithInvalidLocations(PathPieces &Pieces) { //===----------------------------------------------------------------------===// namespace { -class NodeMapClosure : public BugReport::NodeResolver { - InterExplodedGraphMap &M; -public: - NodeMapClosure(InterExplodedGraphMap &m) : M(m) {} - - const ExplodedNode *getOriginalNode(const ExplodedNode *N) override { - return M.lookup(N); - } -}; class PathDiagnosticBuilder : public BugReporterContext { BugReport *R; PathDiagnosticConsumer *PDC; - NodeMapClosure NMC; + public: const LocationContext *LC; PathDiagnosticBuilder(GRBugReporter &br, BugReport *r, InterExplodedGraphMap &Backmap, PathDiagnosticConsumer *pdc) - : BugReporterContext(br), - R(r), PDC(pdc), NMC(Backmap), LC(r->getErrorNode()->getLocationContext()) - {} + : BugReporterContext(br, Backmap), R(r), PDC(pdc), + LC(r->getErrorNode()->getLocationContext()) {} PathDiagnosticLocation ExecutionContinues(const ExplodedNode *N); @@ -353,19 +371,18 @@ public: return getParentMap().getParent(S); } - NodeMapClosure& getNodeResolver() override { return NMC; } - PathDiagnosticLocation getEnclosingStmtLocation(const Stmt *S); PathDiagnosticConsumer::PathGenerationScheme getGenerationScheme() const { - return PDC ? PDC->getGenerationScheme() : PathDiagnosticConsumer::Extensive; + return PDC ? PDC->getGenerationScheme() : PathDiagnosticConsumer::Minimal; } bool supportsLogicalOpControlFlow() const { return PDC ? PDC->supportsLogicalOpControlFlow() : true; } }; -} // end anonymous namespace + +} // namespace PathDiagnosticLocation PathDiagnosticBuilder::ExecutionContinues(const ExplodedNode *N) { @@ -379,7 +396,6 @@ PathDiagnosticBuilder::ExecutionContinues(const ExplodedNode *N) { PathDiagnosticLocation PathDiagnosticBuilder::ExecutionContinues(llvm::raw_string_ostream &os, const ExplodedNode *N) { - // Slow, but probably doesn't matter. if (os.str().empty()) os << ' '; @@ -433,12 +449,12 @@ static PathDiagnosticLocation getEnclosingStmtLocation(const Stmt *S, SourceManager &SMgr, const ParentMap &P, const LocationContext *LC, bool allowNestedContexts) { if (!S) - return PathDiagnosticLocation(); + return {}; while (const Stmt *Parent = getEnclosingParent(S, P)) { switch (Parent->getStmtClass()) { case Stmt::BinaryOperatorClass: { - const BinaryOperator *B = cast<BinaryOperator>(Parent); + const auto *B = cast<BinaryOperator>(Parent); if (B->isLogicalOp()) return PathDiagnosticLocation(allowNestedContexts ? B : S, SMgr, LC); break; @@ -504,46 +520,21 @@ PathDiagnosticBuilder::getEnclosingStmtLocation(const Stmt *S) { } //===----------------------------------------------------------------------===// -// "Visitors only" path diagnostic generation algorithm. -//===----------------------------------------------------------------------===// -static bool GenerateVisitorsOnlyPathDiagnostic( - PathDiagnostic &PD, PathDiagnosticBuilder &PDB, const ExplodedNode *N, - ArrayRef<std::unique_ptr<BugReporterVisitor>> visitors) { - // All path generation skips the very first node (the error node). - // This is because there is special handling for the end-of-path note. - N = N->getFirstPred(); - if (!N) - return true; - - BugReport *R = PDB.getBugReport(); - while (const ExplodedNode *Pred = N->getFirstPred()) { - for (auto &V : visitors) - // Visit all the node pairs, but throw the path pieces away. - V->VisitNode(N, Pred, PDB, *R); - - N = Pred; - } - - return R->isValid(); -} - -//===----------------------------------------------------------------------===// // "Minimal" path diagnostic generation algorithm. //===----------------------------------------------------------------------===// -typedef std::pair<PathDiagnosticCallPiece*, const ExplodedNode*> StackDiagPair; -typedef SmallVector<StackDiagPair, 6> StackDiagVector; +using StackDiagPair = + std::pair<PathDiagnosticCallPiece *, const ExplodedNode *>; +using StackDiagVector = SmallVector<StackDiagPair, 6>; static void updateStackPiecesWithMessage(PathDiagnosticPiece &P, StackDiagVector &CallStack) { // If the piece contains a special message, add it to all the call // pieces on the active stack. - if (PathDiagnosticEventPiece *ep = dyn_cast<PathDiagnosticEventPiece>(&P)) { - + if (auto *ep = dyn_cast<PathDiagnosticEventPiece>(&P)) { if (ep->hasCallStackHint()) - for (StackDiagVector::iterator I = CallStack.begin(), - E = CallStack.end(); I != E; ++I) { - PathDiagnosticCallPiece *CP = I->first; - const ExplodedNode *N = I->second; + for (const auto &I : CallStack) { + PathDiagnosticCallPiece *CP = I.first; + const ExplodedNode *N = I.second; std::string stackMsg = ep->getCallStackMessage(N); // The last message on the path to final bug is the most important @@ -557,693 +548,271 @@ static void updateStackPiecesWithMessage(PathDiagnosticPiece &P, static void CompactPathDiagnostic(PathPieces &path, const SourceManager& SM); -static bool GenerateMinimalPathDiagnostic( - PathDiagnostic &PD, PathDiagnosticBuilder &PDB, const ExplodedNode *N, - LocationContextMap &LCM, - ArrayRef<std::unique_ptr<BugReporterVisitor>> visitors) { - - SourceManager& SMgr = PDB.getSourceManager(); - const LocationContext *LC = PDB.LC; - const ExplodedNode *NextNode = N->pred_empty() - ? nullptr : *(N->pred_begin()); - - StackDiagVector CallStack; - - while (NextNode) { - N = NextNode; - PDB.LC = N->getLocationContext(); - NextNode = N->getFirstPred(); - - ProgramPoint P = N->getLocation(); - - do { - if (Optional<CallExitEnd> CE = P.getAs<CallExitEnd>()) { - auto C = PathDiagnosticCallPiece::construct(N, *CE, SMgr); - // Record the mapping from call piece to LocationContext. - LCM[&C->path] = CE->getCalleeContext(); - auto *P = C.get(); - PD.getActivePath().push_front(std::move(C)); - PD.pushActivePath(&P->path); - CallStack.push_back(StackDiagPair(P, N)); - break; - } - - if (Optional<CallEnter> CE = P.getAs<CallEnter>()) { - // Flush all locations, and pop the active path. - bool VisitedEntireCall = PD.isWithinCall(); - PD.popActivePath(); - - // Either we just added a bunch of stuff to the top-level path, or - // we have a previous CallExitEnd. If the former, it means that the - // path terminated within a function call. We must then take the - // current contents of the active path and place it within - // a new PathDiagnosticCallPiece. - PathDiagnosticCallPiece *C; - if (VisitedEntireCall) { - C = cast<PathDiagnosticCallPiece>(PD.getActivePath().front().get()); - } else { - const Decl *Caller = CE->getLocationContext()->getDecl(); - C = PathDiagnosticCallPiece::construct(PD.getActivePath(), Caller); - // Record the mapping from call piece to LocationContext. - LCM[&C->path] = CE->getCalleeContext(); - } - - C->setCallee(*CE, SMgr); - if (!CallStack.empty()) { - assert(CallStack.back().first == C); - CallStack.pop_back(); - } - break; - } - - if (Optional<BlockEdge> BE = P.getAs<BlockEdge>()) { - const CFGBlock *Src = BE->getSrc(); - const CFGBlock *Dst = BE->getDst(); - const Stmt *T = Src->getTerminator(); - - if (!T) - break; - - PathDiagnosticLocation Start = - PathDiagnosticLocation::createBegin(T, SMgr, - N->getLocationContext()); - - switch (T->getStmtClass()) { - default: - break; - - case Stmt::GotoStmtClass: - case Stmt::IndirectGotoStmtClass: { - const Stmt *S = PathDiagnosticLocation::getNextStmt(N); - - if (!S) - break; - - std::string sbuf; - llvm::raw_string_ostream os(sbuf); - const PathDiagnosticLocation &End = PDB.getEnclosingStmtLocation(S); - - os << "Control jumps to line " - << End.asLocation().getExpansionLineNumber(); - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, - os.str())); - break; - } - - case Stmt::SwitchStmtClass: { - // Figure out what case arm we took. - std::string sbuf; - llvm::raw_string_ostream os(sbuf); - - if (const Stmt *S = Dst->getLabel()) { - PathDiagnosticLocation End(S, SMgr, LC); - - switch (S->getStmtClass()) { - default: - os << "No cases match in the switch statement. " - "Control jumps to line " - << End.asLocation().getExpansionLineNumber(); - break; - case Stmt::DefaultStmtClass: - os << "Control jumps to the 'default' case at line " - << End.asLocation().getExpansionLineNumber(); - break; - - case Stmt::CaseStmtClass: { - os << "Control jumps to 'case "; - const CaseStmt *Case = cast<CaseStmt>(S); - const Expr *LHS = Case->getLHS()->IgnoreParenCasts(); - - // Determine if it is an enum. - bool GetRawInt = true; - - if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(LHS)) { - // FIXME: Maybe this should be an assertion. Are there cases - // were it is not an EnumConstantDecl? - const EnumConstantDecl *D = - dyn_cast<EnumConstantDecl>(DR->getDecl()); - - if (D) { - GetRawInt = false; - os << *D; - } - } - - if (GetRawInt) - os << LHS->EvaluateKnownConstInt(PDB.getASTContext()); - - os << ":' at line " - << End.asLocation().getExpansionLineNumber(); - break; - } - } - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, - os.str())); - } - else { - os << "'Default' branch taken. "; - const PathDiagnosticLocation &End = PDB.ExecutionContinues(os, N); - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, - os.str())); - } - - break; - } - - case Stmt::BreakStmtClass: - case Stmt::ContinueStmtClass: { - std::string sbuf; - llvm::raw_string_ostream os(sbuf); - PathDiagnosticLocation End = PDB.ExecutionContinues(os, N); - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, - os.str())); - break; - } - - // Determine control-flow for ternary '?'. - case Stmt::BinaryConditionalOperatorClass: - case Stmt::ConditionalOperatorClass: { - std::string sbuf; - llvm::raw_string_ostream os(sbuf); - os << "'?' condition is "; - - if (*(Src->succ_begin()+1) == Dst) - os << "false"; - else - os << "true"; - - PathDiagnosticLocation End = PDB.ExecutionContinues(N); - - if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); - - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, - os.str())); - break; - } - - // Determine control-flow for short-circuited '&&' and '||'. - case Stmt::BinaryOperatorClass: { - if (!PDB.supportsLogicalOpControlFlow()) - break; - const BinaryOperator *B = cast<BinaryOperator>(T); - std::string sbuf; - llvm::raw_string_ostream os(sbuf); - os << "Left side of '"; - - if (B->getOpcode() == BO_LAnd) { - os << "&&" << "' is "; - - if (*(Src->succ_begin()+1) == Dst) { - os << "false"; - PathDiagnosticLocation End(B->getLHS(), SMgr, LC); - PathDiagnosticLocation Start = - PathDiagnosticLocation::createOperatorLoc(B, SMgr); - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, - os.str())); - } - else { - os << "true"; - PathDiagnosticLocation Start(B->getLHS(), SMgr, LC); - PathDiagnosticLocation End = PDB.ExecutionContinues(N); - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, - os.str())); - } - } - else { - assert(B->getOpcode() == BO_LOr); - os << "||" << "' is "; - - if (*(Src->succ_begin()+1) == Dst) { - os << "false"; - PathDiagnosticLocation Start(B->getLHS(), SMgr, LC); - PathDiagnosticLocation End = PDB.ExecutionContinues(N); - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, - os.str())); - } - else { - os << "true"; - PathDiagnosticLocation End(B->getLHS(), SMgr, LC); - PathDiagnosticLocation Start = - PathDiagnosticLocation::createOperatorLoc(B, SMgr); - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, - os.str())); - } - } - - break; - } +std::shared_ptr<PathDiagnosticControlFlowPiece> generateDiagForSwitchOP( + const ExplodedNode *N, + const CFGBlock *Dst, + const SourceManager &SM, + const LocationContext *LC, + PathDiagnosticBuilder &PDB, + PathDiagnosticLocation &Start + ) { + // Figure out what case arm we took. + std::string sbuf; + llvm::raw_string_ostream os(sbuf); + PathDiagnosticLocation End; - case Stmt::DoStmtClass: { - if (*(Src->succ_begin()) == Dst) { - std::string sbuf; - llvm::raw_string_ostream os(sbuf); - - os << "Loop condition is true. "; - PathDiagnosticLocation End = PDB.ExecutionContinues(os, N); - - if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); - - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, - os.str())); - } - else { - PathDiagnosticLocation End = PDB.ExecutionContinues(N); - - if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); - - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>( - Start, End, "Loop condition is false. Exiting loop")); - } - - break; - } - - case Stmt::WhileStmtClass: - case Stmt::ForStmtClass: { - if (*(Src->succ_begin()+1) == Dst) { - std::string sbuf; - llvm::raw_string_ostream os(sbuf); - - os << "Loop condition is false. "; - PathDiagnosticLocation End = PDB.ExecutionContinues(os, N); - if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); - - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, - os.str())); - } - else { - PathDiagnosticLocation End = PDB.ExecutionContinues(N); - if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); - - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>( - Start, End, "Loop condition is true. Entering loop body")); - } - - break; - } + if (const Stmt *S = Dst->getLabel()) { + End = PathDiagnosticLocation(S, SM, LC); - case Stmt::IfStmtClass: { - PathDiagnosticLocation End = PDB.ExecutionContinues(N); + switch (S->getStmtClass()) { + default: + os << "No cases match in the switch statement. " + "Control jumps to line " + << End.asLocation().getExpansionLineNumber(); + break; + case Stmt::DefaultStmtClass: + os << "Control jumps to the 'default' case at line " + << End.asLocation().getExpansionLineNumber(); + break; - if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); + case Stmt::CaseStmtClass: { + os << "Control jumps to 'case "; + const auto *Case = cast<CaseStmt>(S); + const Expr *LHS = Case->getLHS()->IgnoreParenCasts(); - if (*(Src->succ_begin()+1) == Dst) - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>( - Start, End, "Taking false branch")); - else - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>( - Start, End, "Taking true branch")); + // Determine if it is an enum. + bool GetRawInt = true; - break; - } - } - } - } while(0); - - if (NextNode) { - // Add diagnostic pieces from custom visitors. - BugReport *R = PDB.getBugReport(); - llvm::FoldingSet<PathDiagnosticPiece> DeduplicationSet; - for (auto &V : visitors) { - if (auto p = V->VisitNode(N, NextNode, PDB, *R)) { - if (DeduplicationSet.GetOrInsertNode(p.get()) != p.get()) - continue; + if (const auto *DR = dyn_cast<DeclRefExpr>(LHS)) { + // FIXME: Maybe this should be an assertion. Are there cases + // were it is not an EnumConstantDecl? + const auto *D = dyn_cast<EnumConstantDecl>(DR->getDecl()); - updateStackPiecesWithMessage(*p, CallStack); - PD.getActivePath().push_front(std::move(p)); + if (D) { + GetRawInt = false; + os << *D; } } - } - } - - if (!PDB.getBugReport()->isValid()) - return false; - - // After constructing the full PathDiagnostic, do a pass over it to compact - // PathDiagnosticPieces that occur within a macro. - CompactPathDiagnostic(PD.getMutablePieces(), PDB.getSourceManager()); - return true; -} - -//===----------------------------------------------------------------------===// -// "Extensive" PathDiagnostic generation. -//===----------------------------------------------------------------------===// - -static bool IsControlFlowExpr(const Stmt *S) { - const Expr *E = dyn_cast<Expr>(S); - - if (!E) - return false; - - E = E->IgnoreParenCasts(); - - if (isa<AbstractConditionalOperator>(E)) - return true; - - if (const BinaryOperator *B = dyn_cast<BinaryOperator>(E)) - if (B->isLogicalOp()) - return true; - return false; -} - -namespace { -class ContextLocation : public PathDiagnosticLocation { - bool IsDead; -public: - ContextLocation(const PathDiagnosticLocation &L, bool isdead = false) - : PathDiagnosticLocation(L), IsDead(isdead) {} - - void markDead() { IsDead = true; } - bool isDead() const { return IsDead; } -}; - -static PathDiagnosticLocation cleanUpLocation(PathDiagnosticLocation L, - const LocationContext *LC, - bool firstCharOnly = false) { - if (const Stmt *S = L.asStmt()) { - const Stmt *Original = S; - while (1) { - // Adjust the location for some expressions that are best referenced - // by one of their subexpressions. - switch (S->getStmtClass()) { - default: - break; - case Stmt::ParenExprClass: - case Stmt::GenericSelectionExprClass: - S = cast<Expr>(S)->IgnoreParens(); - firstCharOnly = true; - continue; - case Stmt::BinaryConditionalOperatorClass: - case Stmt::ConditionalOperatorClass: - S = cast<AbstractConditionalOperator>(S)->getCond(); - firstCharOnly = true; - continue; - case Stmt::ChooseExprClass: - S = cast<ChooseExpr>(S)->getCond(); - firstCharOnly = true; - continue; - case Stmt::BinaryOperatorClass: - S = cast<BinaryOperator>(S)->getLHS(); - firstCharOnly = true; - continue; - } + if (GetRawInt) + os << LHS->EvaluateKnownConstInt(PDB.getASTContext()); + os << ":' at line " << End.asLocation().getExpansionLineNumber(); break; } - - if (S != Original) - L = PathDiagnosticLocation(S, L.getManager(), LC); - } - - if (firstCharOnly) - L = PathDiagnosticLocation::createSingleLocation(L); - - return L; -} - -class EdgeBuilder { - std::vector<ContextLocation> CLocs; - typedef std::vector<ContextLocation>::iterator iterator; - PathDiagnostic &PD; - PathDiagnosticBuilder &PDB; - PathDiagnosticLocation PrevLoc; - - bool IsConsumedExpr(const PathDiagnosticLocation &L); - - bool containsLocation(const PathDiagnosticLocation &Container, - const PathDiagnosticLocation &Containee); - - PathDiagnosticLocation getContextLocation(const PathDiagnosticLocation &L); - - - - void popLocation() { - if (!CLocs.back().isDead() && CLocs.back().asLocation().isFileID()) { - // For contexts, we only one the first character as the range. - rawAddEdge(cleanUpLocation(CLocs.back(), PDB.LC, true)); } - CLocs.pop_back(); + } else { + os << "'Default' branch taken. "; + End = PDB.ExecutionContinues(os, N); + } + return std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, + os.str()); +} + + +std::shared_ptr<PathDiagnosticControlFlowPiece> generateDiagForGotoOP( + const Stmt *S, + PathDiagnosticBuilder &PDB, + PathDiagnosticLocation &Start) { + std::string sbuf; + llvm::raw_string_ostream os(sbuf); + const PathDiagnosticLocation &End = PDB.getEnclosingStmtLocation(S); + os << "Control jumps to line " << End.asLocation().getExpansionLineNumber(); + return std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, os.str()); + +} + +std::shared_ptr<PathDiagnosticControlFlowPiece> generateDiagForBinaryOP( + const ExplodedNode *N, + const Stmt *T, + const CFGBlock *Src, + const CFGBlock *Dst, + const SourceManager &SM, + PathDiagnosticBuilder &PDB, + const LocationContext *LC) { + const auto *B = cast<BinaryOperator>(T); + std::string sbuf; + llvm::raw_string_ostream os(sbuf); + os << "Left side of '"; + PathDiagnosticLocation Start, End; + + if (B->getOpcode() == BO_LAnd) { + os << "&&" + << "' is "; + + if (*(Src->succ_begin() + 1) == Dst) { + os << "false"; + End = PathDiagnosticLocation(B->getLHS(), SM, LC); + Start = + PathDiagnosticLocation::createOperatorLoc(B, SM); + } else { + os << "true"; + Start = PathDiagnosticLocation(B->getLHS(), SM, LC); + End = PDB.ExecutionContinues(N); + } + } else { + assert(B->getOpcode() == BO_LOr); + os << "||" + << "' is "; + + if (*(Src->succ_begin() + 1) == Dst) { + os << "false"; + Start = PathDiagnosticLocation(B->getLHS(), SM, LC); + End = PDB.ExecutionContinues(N); + } else { + os << "true"; + End = PathDiagnosticLocation(B->getLHS(), SM, LC); + Start = + PathDiagnosticLocation::createOperatorLoc(B, SM); + } } + return std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, + os.str()); +} -public: - EdgeBuilder(PathDiagnostic &pd, PathDiagnosticBuilder &pdb) - : PD(pd), PDB(pdb) { - - // If the PathDiagnostic already has pieces, add the enclosing statement - // of the first piece as a context as well. - if (!PD.path.empty()) { - PrevLoc = (*PD.path.begin())->getLocation(); - - if (const Stmt *S = PrevLoc.asStmt()) - addExtendedContext(PDB.getEnclosingStmtLocation(S).asStmt()); - } - } +void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, + const SourceManager &SM, + PathDiagnosticBuilder &PDB, + PathDiagnostic &PD) { + const LocationContext *LC = N->getLocationContext(); + const CFGBlock *Src = BE.getSrc(); + const CFGBlock *Dst = BE.getDst(); + const Stmt *T = Src->getTerminator(); + if (!T) + return; - ~EdgeBuilder() { - while (!CLocs.empty()) popLocation(); + auto Start = PathDiagnosticLocation::createBegin(T, SM, LC); + switch (T->getStmtClass()) { + default: + break; - // Finally, add an initial edge from the start location of the first - // statement (if it doesn't already exist). - PathDiagnosticLocation L = PathDiagnosticLocation::createDeclBegin( - PDB.LC, - PDB.getSourceManager()); - if (L.isValid()) - rawAddEdge(L); + case Stmt::GotoStmtClass: + case Stmt::IndirectGotoStmtClass: { + if (const Stmt *S = PathDiagnosticLocation::getNextStmt(N)) + PD.getActivePath().push_front(generateDiagForGotoOP(S, PDB, Start)); + break; } - void flushLocations() { - while (!CLocs.empty()) - popLocation(); - PrevLoc = PathDiagnosticLocation(); + case Stmt::SwitchStmtClass: { + PD.getActivePath().push_front( + generateDiagForSwitchOP(N, Dst, SM, LC, PDB, Start)); + break; } - void addEdge(PathDiagnosticLocation NewLoc, bool alwaysAdd = false, - bool IsPostJump = false); - - void rawAddEdge(PathDiagnosticLocation NewLoc); - - void addContext(const Stmt *S); - void addContext(const PathDiagnosticLocation &L); - void addExtendedContext(const Stmt *S); -}; -} // end anonymous namespace - - -PathDiagnosticLocation -EdgeBuilder::getContextLocation(const PathDiagnosticLocation &L) { - if (const Stmt *S = L.asStmt()) { - if (IsControlFlowExpr(S)) - return L; - - return PDB.getEnclosingStmtLocation(S); + case Stmt::BreakStmtClass: + case Stmt::ContinueStmtClass: { + std::string sbuf; + llvm::raw_string_ostream os(sbuf); + PathDiagnosticLocation End = PDB.ExecutionContinues(os, N); + PD.getActivePath().push_front( + std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, os.str())); + break; } - return L; -} - -bool EdgeBuilder::containsLocation(const PathDiagnosticLocation &Container, - const PathDiagnosticLocation &Containee) { - - if (Container == Containee) - return true; + // Determine control-flow for ternary '?'. + case Stmt::BinaryConditionalOperatorClass: + case Stmt::ConditionalOperatorClass: { + std::string sbuf; + llvm::raw_string_ostream os(sbuf); + os << "'?' condition is "; - if (Container.asDecl()) - return true; + if (*(Src->succ_begin() + 1) == Dst) + os << "false"; + else + os << "true"; - if (const Stmt *S = Containee.asStmt()) - if (const Stmt *ContainerS = Container.asStmt()) { - while (S) { - if (S == ContainerS) - return true; - S = PDB.getParent(S); - } - return false; - } + PathDiagnosticLocation End = PDB.ExecutionContinues(N); - // Less accurate: compare using source ranges. - SourceRange ContainerR = Container.asRange(); - SourceRange ContaineeR = Containee.asRange(); + if (const Stmt *S = End.asStmt()) + End = PDB.getEnclosingStmtLocation(S); - SourceManager &SM = PDB.getSourceManager(); - SourceLocation ContainerRBeg = SM.getExpansionLoc(ContainerR.getBegin()); - SourceLocation ContainerREnd = SM.getExpansionLoc(ContainerR.getEnd()); - SourceLocation ContaineeRBeg = SM.getExpansionLoc(ContaineeR.getBegin()); - SourceLocation ContaineeREnd = SM.getExpansionLoc(ContaineeR.getEnd()); - - unsigned ContainerBegLine = SM.getExpansionLineNumber(ContainerRBeg); - unsigned ContainerEndLine = SM.getExpansionLineNumber(ContainerREnd); - unsigned ContaineeBegLine = SM.getExpansionLineNumber(ContaineeRBeg); - unsigned ContaineeEndLine = SM.getExpansionLineNumber(ContaineeREnd); - - assert(ContainerBegLine <= ContainerEndLine); - assert(ContaineeBegLine <= ContaineeEndLine); - - return (ContainerBegLine <= ContaineeBegLine && - ContainerEndLine >= ContaineeEndLine && - (ContainerBegLine != ContaineeBegLine || - SM.getExpansionColumnNumber(ContainerRBeg) <= - SM.getExpansionColumnNumber(ContaineeRBeg)) && - (ContainerEndLine != ContaineeEndLine || - SM.getExpansionColumnNumber(ContainerREnd) >= - SM.getExpansionColumnNumber(ContaineeREnd))); -} - -void EdgeBuilder::rawAddEdge(PathDiagnosticLocation NewLoc) { - if (!PrevLoc.isValid()) { - PrevLoc = NewLoc; - return; + PD.getActivePath().push_front( + std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, os.str())); + break; } - const PathDiagnosticLocation &NewLocClean = cleanUpLocation(NewLoc, PDB.LC); - const PathDiagnosticLocation &PrevLocClean = cleanUpLocation(PrevLoc, PDB.LC); + // Determine control-flow for short-circuited '&&' and '||'. + case Stmt::BinaryOperatorClass: { + if (!PDB.supportsLogicalOpControlFlow()) + break; - if (PrevLocClean.asLocation().isInvalid()) { - PrevLoc = NewLoc; - return; + std::shared_ptr<PathDiagnosticControlFlowPiece> Diag = + generateDiagForBinaryOP(N, T, Src, Dst, SM, PDB, LC); + PD.getActivePath().push_front(Diag); + break; } - if (NewLocClean.asLocation() == PrevLocClean.asLocation()) - return; - - // FIXME: Ignore intra-macro edges for now. - if (NewLocClean.asLocation().getExpansionLoc() == - PrevLocClean.asLocation().getExpansionLoc()) - return; - - PD.getActivePath().push_front( - std::make_shared<PathDiagnosticControlFlowPiece>(NewLocClean, - PrevLocClean)); - PrevLoc = NewLoc; -} - -void EdgeBuilder::addEdge(PathDiagnosticLocation NewLoc, bool alwaysAdd, - bool IsPostJump) { - - if (!alwaysAdd && NewLoc.asLocation().isMacroID()) - return; + case Stmt::DoStmtClass: + if (*(Src->succ_begin()) == Dst) { + std::string sbuf; + llvm::raw_string_ostream os(sbuf); - const PathDiagnosticLocation &CLoc = getContextLocation(NewLoc); + os << "Loop condition is true. "; + PathDiagnosticLocation End = PDB.ExecutionContinues(os, N); - while (!CLocs.empty()) { - ContextLocation &TopContextLoc = CLocs.back(); + if (const Stmt *S = End.asStmt()) + End = PDB.getEnclosingStmtLocation(S); - // Is the top location context the same as the one for the new location? - if (TopContextLoc == CLoc) { - if (alwaysAdd) { - if (IsConsumedExpr(TopContextLoc)) - TopContextLoc.markDead(); + PD.getActivePath().push_front( + std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, + os.str())); + } else { + PathDiagnosticLocation End = PDB.ExecutionContinues(N); - rawAddEdge(NewLoc); - } + if (const Stmt *S = End.asStmt()) + End = PDB.getEnclosingStmtLocation(S); - if (IsPostJump) - TopContextLoc.markDead(); - return; + PD.getActivePath().push_front( + std::make_shared<PathDiagnosticControlFlowPiece>( + Start, End, "Loop condition is false. Exiting loop")); } + break; - if (containsLocation(TopContextLoc, CLoc)) { - if (alwaysAdd) { - rawAddEdge(NewLoc); - - if (IsConsumedExpr(CLoc)) { - CLocs.push_back(ContextLocation(CLoc, /*IsDead=*/true)); - return; - } - } + case Stmt::WhileStmtClass: + case Stmt::ForStmtClass: + if (*(Src->succ_begin() + 1) == Dst) { + std::string sbuf; + llvm::raw_string_ostream os(sbuf); + + os << "Loop condition is false. "; + PathDiagnosticLocation End = PDB.ExecutionContinues(os, N); + if (const Stmt *S = End.asStmt()) + End = PDB.getEnclosingStmtLocation(S); + + PD.getActivePath().push_front( + std::make_shared<PathDiagnosticControlFlowPiece>(Start, End, + os.str())); + } else { + PathDiagnosticLocation End = PDB.ExecutionContinues(N); + if (const Stmt *S = End.asStmt()) + End = PDB.getEnclosingStmtLocation(S); - CLocs.push_back(ContextLocation(CLoc, /*IsDead=*/IsPostJump)); - return; + PD.getActivePath().push_front( + std::make_shared<PathDiagnosticControlFlowPiece>( + Start, End, "Loop condition is true. Entering loop body")); } - // Context does not contain the location. Flush it. - popLocation(); - } - - // If we reach here, there is no enclosing context. Just add the edge. - rawAddEdge(NewLoc); -} + break; -bool EdgeBuilder::IsConsumedExpr(const PathDiagnosticLocation &L) { - if (const Expr *X = dyn_cast_or_null<Expr>(L.asStmt())) - return PDB.getParentMap().isConsumedExpr(X) && !IsControlFlowExpr(X); + case Stmt::IfStmtClass: { + PathDiagnosticLocation End = PDB.ExecutionContinues(N); - return false; -} + if (const Stmt *S = End.asStmt()) + End = PDB.getEnclosingStmtLocation(S); -void EdgeBuilder::addExtendedContext(const Stmt *S) { - if (!S) - return; - - const Stmt *Parent = PDB.getParent(S); - while (Parent) { - if (isa<CompoundStmt>(Parent)) - Parent = PDB.getParent(Parent); + if (*(Src->succ_begin() + 1) == Dst) + PD.getActivePath().push_front( + std::make_shared<PathDiagnosticControlFlowPiece>( + Start, End, "Taking false branch")); else - break; - } + PD.getActivePath().push_front( + std::make_shared<PathDiagnosticControlFlowPiece>( + Start, End, "Taking true branch")); - if (Parent) { - switch (Parent->getStmtClass()) { - case Stmt::DoStmtClass: - case Stmt::ObjCAtSynchronizedStmtClass: - addContext(Parent); - default: - break; - } + break; } - - addContext(S); -} - -void EdgeBuilder::addContext(const Stmt *S) { - if (!S) - return; - - PathDiagnosticLocation L(S, PDB.getSourceManager(), PDB.LC); - addContext(L); -} - -void EdgeBuilder::addContext(const PathDiagnosticLocation &L) { - while (!CLocs.empty()) { - const PathDiagnosticLocation &TopContextLoc = CLocs.back(); - - // Is the top location context the same as the one for the new location? - if (TopContextLoc == L) - return; - - if (containsLocation(TopContextLoc, L)) { - CLocs.push_back(L); - return; - } - - // Context does not contain the location. Flush it. - popLocation(); } - - CLocs.push_back(L); } // Cone-of-influence: support the reverse propagation of "interesting" symbols @@ -1257,7 +826,7 @@ void EdgeBuilder::addContext(const PathDiagnosticLocation &L) { // because the constraint solver sometimes simplifies certain symbolic values // into constants when appropriate, and this complicates reasoning about // interesting values. -typedef llvm::DenseSet<const Expr *> InterestingExprs; +using InterestingExprs = llvm::DenseSet<const Expr *>; static void reversePropagateIntererstingSymbols(BugReport &R, InterestingExprs &IE, @@ -1276,7 +845,7 @@ static void reversePropagateIntererstingSymbols(BugReport &R, case Stmt::BinaryOperatorClass: case Stmt::UnaryOperatorClass: { for (const Stmt *SubStmt : Ex->children()) { - if (const Expr *child = dyn_cast_or_null<Expr>(SubStmt)) { + if (const auto *child = dyn_cast_or_null<Expr>(SubStmt)) { IE.insert(child); SVal ChildV = State->getSVal(child, LCtx); R.markInteresting(ChildV); @@ -1296,10 +865,10 @@ static void reversePropagateInterestingSymbols(BugReport &R, const LocationContext *CallerCtx) { // FIXME: Handle non-CallExpr-based CallEvents. - const StackFrameContext *Callee = CalleeCtx->getCurrentStackFrame(); + const StackFrameContext *Callee = CalleeCtx->getStackFrame(); const Stmt *CallSite = Callee->getCallSite(); - if (const CallExpr *CE = dyn_cast_or_null<CallExpr>(CallSite)) { - if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(CalleeCtx->getDecl())) { + if (const auto *CE = dyn_cast_or_null<CallExpr>(CallSite)) { + if (const auto *FD = dyn_cast<FunctionDecl>(CalleeCtx->getDecl())) { FunctionDecl::param_const_iterator PI = FD->param_begin(), PE = FD->param_end(); CallExpr::const_arg_iterator AI = CE->arg_begin(), AE = CE->arg_end(); @@ -1339,16 +908,6 @@ static bool isJumpToFalseBranch(const BlockEdge *BE) { return (*(Src->succ_begin()+1) == BE->getDst()); } -/// Return true if the terminator is a loop and the destination is the -/// false branch. -static bool isLoopJumpPastBody(const Stmt *Term, const BlockEdge *BE) { - if (!isLoop(Term)) - return false; - - // Did we take the false branch? - return isJumpToFalseBranch(BE); -} - static bool isContainedByStmt(ParentMap &PM, const Stmt *S, const Stmt *SubS) { while (SubS) { if (SubS == S) @@ -1376,7 +935,7 @@ static bool isInLoopBody(ParentMap &PM, const Stmt *S, const Stmt *Term) { const Stmt *LoopBody = nullptr; switch (Term->getStmtClass()) { case Stmt::CXXForRangeStmtClass: { - const CXXForRangeStmt *FR = cast<CXXForRangeStmt>(Term); + const auto *FR = cast<CXXForRangeStmt>(Term); if (isContainedByStmt(PM, FR->getInc(), S)) return true; if (isContainedByStmt(PM, FR->getLoopVarStmt(), S)) @@ -1385,14 +944,14 @@ static bool isInLoopBody(ParentMap &PM, const Stmt *S, const Stmt *Term) { break; } case Stmt::ForStmtClass: { - const ForStmt *FS = cast<ForStmt>(Term); + const auto *FS = cast<ForStmt>(Term); if (isContainedByStmt(PM, FS->getInc(), S)) return true; LoopBody = FS->getBody(); break; } case Stmt::ObjCForCollectionStmtClass: { - const ObjCForCollectionStmt *FC = cast<ObjCForCollectionStmt>(Term); + const auto *FC = cast<ObjCForCollectionStmt>(Term); LoopBody = FC->getBody(); break; } @@ -1405,210 +964,7 @@ static bool isInLoopBody(ParentMap &PM, const Stmt *S, const Stmt *Term) { return isContainedByStmt(PM, LoopBody, S); } -//===----------------------------------------------------------------------===// -// Top-level logic for generating extensive path diagnostics. -//===----------------------------------------------------------------------===// - -static bool GenerateExtensivePathDiagnostic( - PathDiagnostic &PD, PathDiagnosticBuilder &PDB, const ExplodedNode *N, - LocationContextMap &LCM, - ArrayRef<std::unique_ptr<BugReporterVisitor>> visitors) { - EdgeBuilder EB(PD, PDB); - const SourceManager& SM = PDB.getSourceManager(); - StackDiagVector CallStack; - InterestingExprs IE; - - const ExplodedNode *NextNode = N->pred_empty() ? nullptr : *(N->pred_begin()); - while (NextNode) { - N = NextNode; - NextNode = N->getFirstPred(); - ProgramPoint P = N->getLocation(); - - do { - if (Optional<PostStmt> PS = P.getAs<PostStmt>()) { - if (const Expr *Ex = PS->getStmtAs<Expr>()) - reversePropagateIntererstingSymbols(*PDB.getBugReport(), IE, - N->getState().get(), Ex, - N->getLocationContext()); - } - - if (Optional<CallExitEnd> CE = P.getAs<CallExitEnd>()) { - const Stmt *S = CE->getCalleeContext()->getCallSite(); - if (const Expr *Ex = dyn_cast_or_null<Expr>(S)) { - reversePropagateIntererstingSymbols(*PDB.getBugReport(), IE, - N->getState().get(), Ex, - N->getLocationContext()); - } - - auto C = PathDiagnosticCallPiece::construct(N, *CE, SM); - LCM[&C->path] = CE->getCalleeContext(); - - EB.addEdge(C->callReturn, /*AlwaysAdd=*/true, /*IsPostJump=*/true); - EB.flushLocations(); - - auto *P = C.get(); - PD.getActivePath().push_front(std::move(C)); - PD.pushActivePath(&P->path); - CallStack.push_back(StackDiagPair(P, N)); - break; - } - - // Pop the call hierarchy if we are done walking the contents - // of a function call. - if (Optional<CallEnter> CE = P.getAs<CallEnter>()) { - // Add an edge to the start of the function. - const Decl *D = CE->getCalleeContext()->getDecl(); - PathDiagnosticLocation pos = - PathDiagnosticLocation::createBegin(D, SM); - EB.addEdge(pos); - - // Flush all locations, and pop the active path. - bool VisitedEntireCall = PD.isWithinCall(); - EB.flushLocations(); - PD.popActivePath(); - PDB.LC = N->getLocationContext(); - - // Either we just added a bunch of stuff to the top-level path, or - // we have a previous CallExitEnd. If the former, it means that the - // path terminated within a function call. We must then take the - // current contents of the active path and place it within - // a new PathDiagnosticCallPiece. - PathDiagnosticCallPiece *C; - if (VisitedEntireCall) { - C = cast<PathDiagnosticCallPiece>(PD.getActivePath().front().get()); - } else { - const Decl *Caller = CE->getLocationContext()->getDecl(); - C = PathDiagnosticCallPiece::construct(PD.getActivePath(), Caller); - LCM[&C->path] = CE->getCalleeContext(); - } - - C->setCallee(*CE, SM); - EB.addContext(C->getLocation()); - - if (!CallStack.empty()) { - assert(CallStack.back().first == C); - CallStack.pop_back(); - } - break; - } - - // Note that is important that we update the LocationContext - // after looking at CallExits. CallExit basically adds an - // edge in the *caller*, so we don't want to update the LocationContext - // too soon. - PDB.LC = N->getLocationContext(); - - // Block edges. - if (Optional<BlockEdge> BE = P.getAs<BlockEdge>()) { - // Does this represent entering a call? If so, look at propagating - // interesting symbols across call boundaries. - if (NextNode) { - const LocationContext *CallerCtx = NextNode->getLocationContext(); - const LocationContext *CalleeCtx = PDB.LC; - if (CallerCtx != CalleeCtx) { - reversePropagateInterestingSymbols(*PDB.getBugReport(), IE, - N->getState().get(), - CalleeCtx, CallerCtx); - } - } - - // Are we jumping to the head of a loop? Add a special diagnostic. - if (const Stmt *Loop = BE->getSrc()->getLoopTarget()) { - PathDiagnosticLocation L(Loop, SM, PDB.LC); - const CompoundStmt *CS = nullptr; - - if (const ForStmt *FS = dyn_cast<ForStmt>(Loop)) - CS = dyn_cast<CompoundStmt>(FS->getBody()); - else if (const WhileStmt *WS = dyn_cast<WhileStmt>(Loop)) - CS = dyn_cast<CompoundStmt>(WS->getBody()); - - auto p = std::make_shared<PathDiagnosticEventPiece>( - L, "Looping back to the head of the loop"); - p->setPrunable(true); - - EB.addEdge(p->getLocation(), true); - PD.getActivePath().push_front(std::move(p)); - - if (CS) { - PathDiagnosticLocation BL = - PathDiagnosticLocation::createEndBrace(CS, SM); - EB.addEdge(BL); - } - } - - const CFGBlock *BSrc = BE->getSrc(); - ParentMap &PM = PDB.getParentMap(); - - if (const Stmt *Term = BSrc->getTerminator()) { - // Are we jumping past the loop body without ever executing the - // loop (because the condition was false)? - if (isLoopJumpPastBody(Term, &*BE) && - !isInLoopBody(PM, - getStmtBeforeCond(PM, - BSrc->getTerminatorCondition(), - N), - Term)) { - PathDiagnosticLocation L(Term, SM, PDB.LC); - auto PE = std::make_shared<PathDiagnosticEventPiece>( - L, "Loop body executed 0 times"); - PE->setPrunable(true); - - EB.addEdge(PE->getLocation(), true); - PD.getActivePath().push_front(std::move(PE)); - } - - // In any case, add the terminator as the current statement - // context for control edges. - EB.addContext(Term); - } - - break; - } - - if (Optional<BlockEntrance> BE = P.getAs<BlockEntrance>()) { - Optional<CFGElement> First = BE->getFirstElement(); - if (Optional<CFGStmt> S = First ? First->getAs<CFGStmt>() : None) { - const Stmt *stmt = S->getStmt(); - if (IsControlFlowExpr(stmt)) { - // Add the proper context for '&&', '||', and '?'. - EB.addContext(stmt); - } - else - EB.addExtendedContext(PDB.getEnclosingStmtLocation(stmt).asStmt()); - } - - break; - } - - - } while (0); - - if (!NextNode) - continue; - - // Add pieces from custom visitors. - BugReport *R = PDB.getBugReport(); - llvm::FoldingSet<PathDiagnosticPiece> DeduplicationSet; - for (auto &V : visitors) { - if (auto p = V->VisitNode(N, NextNode, PDB, *R)) { - if (DeduplicationSet.GetOrInsertNode(p.get()) != p.get()) - continue; - - const PathDiagnosticLocation &Loc = p->getLocation(); - EB.addEdge(Loc, true); - updateStackPiecesWithMessage(*p, CallStack); - PD.getActivePath().push_front(std::move(p)); - - if (const Stmt *S = Loc.asStmt()) - EB.addExtendedContext(PDB.getEnclosingStmtLocation(S).asStmt()); - } - } - } - - return PDB.getBugReport()->isValid(); -} - -/// \brief Adds a sanitized control-flow diagnostic edge to a path. +/// Adds a sanitized control-flow diagnostic edge to a path. static void addEdgeToPath(PathPieces &path, PathDiagnosticLocation &PrevLoc, PathDiagnosticLocation NewLoc, @@ -1639,8 +995,7 @@ static void addEdgeToPath(PathPieces &path, /// which returns the element for ObjCForCollectionStmts. static const Stmt *getTerminatorCondition(const CFGBlock *B) { const Stmt *S = B->getTerminatorCondition(); - if (const ObjCForCollectionStmt *FS = - dyn_cast_or_null<ObjCForCollectionStmt>(S)) + if (const auto *FS = dyn_cast_or_null<ObjCForCollectionStmt>(S)) return FS->getElement(); return S; } @@ -1652,269 +1007,256 @@ static const char StrLoopRangeEmpty[] = static const char StrLoopCollectionEmpty[] = "Loop body skipped when collection is empty"; -static bool GenerateAlternateExtensivePathDiagnostic( - PathDiagnostic &PD, PathDiagnosticBuilder &PDB, const ExplodedNode *N, - LocationContextMap &LCM, - ArrayRef<std::unique_ptr<BugReporterVisitor>> visitors) { - - BugReport *report = PDB.getBugReport(); +static std::unique_ptr<FilesToLineNumsMap> +findExecutedLines(SourceManager &SM, const ExplodedNode *N); + +/// Generate diagnostics for the node \p N, +/// and write it into \p PD. +/// \p AddPathEdges Whether diagnostic consumer can generate path arrows +/// showing both row and column. +static void generatePathDiagnosticsForNode(const ExplodedNode *N, + PathDiagnostic &PD, + PathDiagnosticLocation &PrevLoc, + PathDiagnosticBuilder &PDB, + LocationContextMap &LCM, + StackDiagVector &CallStack, + InterestingExprs &IE, + bool AddPathEdges) { + ProgramPoint P = N->getLocation(); const SourceManager& SM = PDB.getSourceManager(); - StackDiagVector CallStack; - InterestingExprs IE; - PathDiagnosticLocation PrevLoc = PD.getLocation(); + // Have we encountered an entrance to a call? It may be + // the case that we have not encountered a matching + // call exit before this point. This means that the path + // terminated within the call itself. + if (auto CE = P.getAs<CallEnter>()) { + + if (AddPathEdges) { + // Add an edge to the start of the function. + const StackFrameContext *CalleeLC = CE->getCalleeContext(); + const Decl *D = CalleeLC->getDecl(); + // Add the edge only when the callee has body. We jump to the beginning + // of the *declaration*, however we expect it to be followed by the + // body. This isn't the case for autosynthesized property accessors in + // Objective-C. No need for a similar extra check for CallExit points + // because the exit edge comes from a statement (i.e. return), + // not from declaration. + if (D->hasBody()) + addEdgeToPath(PD.getActivePath(), PrevLoc, + PathDiagnosticLocation::createBegin(D, SM), CalleeLC); + } - const ExplodedNode *NextNode = N->getFirstPred(); - while (NextNode) { - N = NextNode; - NextNode = N->getFirstPred(); - ProgramPoint P = N->getLocation(); - - do { - // Have we encountered an entrance to a call? It may be - // the case that we have not encountered a matching - // call exit before this point. This means that the path - // terminated within the call itself. - if (Optional<CallEnter> CE = P.getAs<CallEnter>()) { - // Add an edge to the start of the function. - const StackFrameContext *CalleeLC = CE->getCalleeContext(); - const Decl *D = CalleeLC->getDecl(); - // Add the edge only when the callee has body. We jump to the beginning - // of the *declaration*, however we expect it to be followed by the - // body. This isn't the case for autosynthesized property accessors in - // Objective-C. No need for a similar extra check for CallExit points - // because the exit edge comes from a statement (i.e. return), - // not from declaration. - if (D->hasBody()) - addEdgeToPath(PD.getActivePath(), PrevLoc, - PathDiagnosticLocation::createBegin(D, SM), CalleeLC); + // Did we visit an entire call? + bool VisitedEntireCall = PD.isWithinCall(); + PD.popActivePath(); - // Did we visit an entire call? - bool VisitedEntireCall = PD.isWithinCall(); - PD.popActivePath(); + PathDiagnosticCallPiece *C; + if (VisitedEntireCall) { + C = cast<PathDiagnosticCallPiece>(PD.getActivePath().front().get()); + } else { + const Decl *Caller = CE->getLocationContext()->getDecl(); + C = PathDiagnosticCallPiece::construct(PD.getActivePath(), Caller); + + if (AddPathEdges) { + // Since we just transferred the path over to the call piece, + // reset the mapping from active to location context. + assert(PD.getActivePath().size() == 1 && + PD.getActivePath().front().get() == C); + LCM[&PD.getActivePath()] = nullptr; + } - PathDiagnosticCallPiece *C; - if (VisitedEntireCall) { - PathDiagnosticPiece *P = PD.getActivePath().front().get(); - C = cast<PathDiagnosticCallPiece>(P); - } else { - const Decl *Caller = CE->getLocationContext()->getDecl(); - C = PathDiagnosticCallPiece::construct(PD.getActivePath(), Caller); - - // Since we just transferred the path over to the call piece, - // reset the mapping from active to location context. - assert(PD.getActivePath().size() == 1 && - PD.getActivePath().front().get() == C); - LCM[&PD.getActivePath()] = nullptr; - - // Record the location context mapping for the path within - // the call. - assert(LCM[&C->path] == nullptr || - LCM[&C->path] == CE->getCalleeContext()); - LCM[&C->path] = CE->getCalleeContext(); - - // If this is the first item in the active path, record - // the new mapping from active path to location context. - const LocationContext *&NewLC = LCM[&PD.getActivePath()]; - if (!NewLC) - NewLC = N->getLocationContext(); - - PDB.LC = NewLC; - } - C->setCallee(*CE, SM); + // Record the location context mapping for the path within + // the call. + assert(LCM[&C->path] == nullptr || + LCM[&C->path] == CE->getCalleeContext()); + LCM[&C->path] = CE->getCalleeContext(); - // Update the previous location in the active path. - PrevLoc = C->getLocation(); + // If this is the first item in the active path, record + // the new mapping from active path to location context. + const LocationContext *&NewLC = LCM[&PD.getActivePath()]; + if (!NewLC) + NewLC = N->getLocationContext(); - if (!CallStack.empty()) { - assert(CallStack.back().first == C); - CallStack.pop_back(); - } - break; - } + PDB.LC = NewLC; + } + C->setCallee(*CE, SM); - // Query the location context here and the previous location - // as processing CallEnter may change the active path. - PDB.LC = N->getLocationContext(); + // Update the previous location in the active path. + PrevLoc = C->getLocation(); - // Record the mapping from the active path to the location - // context. - assert(!LCM[&PD.getActivePath()] || - LCM[&PD.getActivePath()] == PDB.LC); - LCM[&PD.getActivePath()] = PDB.LC; - - // Have we encountered an exit from a function call? - if (Optional<CallExitEnd> CE = P.getAs<CallExitEnd>()) { - const Stmt *S = CE->getCalleeContext()->getCallSite(); - // Propagate the interesting symbols accordingly. - if (const Expr *Ex = dyn_cast_or_null<Expr>(S)) { - reversePropagateIntererstingSymbols(*PDB.getBugReport(), IE, - N->getState().get(), Ex, - N->getLocationContext()); - } + if (!CallStack.empty()) { + assert(CallStack.back().first == C); + CallStack.pop_back(); + } + return; + } - // We are descending into a call (backwards). Construct - // a new call piece to contain the path pieces for that call. - auto C = PathDiagnosticCallPiece::construct(N, *CE, SM); - // Record the location context for this call piece. - LCM[&C->path] = CE->getCalleeContext(); + if (AddPathEdges) { + // Query the location context here and the previous location + // as processing CallEnter may change the active path. + PDB.LC = N->getLocationContext(); - // Add the edge to the return site. - addEdgeToPath(PD.getActivePath(), PrevLoc, C->callReturn, PDB.LC); - auto *P = C.get(); - PD.getActivePath().push_front(std::move(C)); - PrevLoc.invalidate(); + // Record the mapping from the active path to the location + // context. + assert(!LCM[&PD.getActivePath()] || LCM[&PD.getActivePath()] == PDB.LC); + LCM[&PD.getActivePath()] = PDB.LC; + } - // Make the contents of the call the active path for now. - PD.pushActivePath(&P->path); - CallStack.push_back(StackDiagPair(P, N)); - break; - } + // Have we encountered an exit from a function call? + if (Optional<CallExitEnd> CE = P.getAs<CallExitEnd>()) { - if (Optional<PostStmt> PS = P.getAs<PostStmt>()) { - // For expressions, make sure we propagate the - // interesting symbols correctly. - if (const Expr *Ex = PS->getStmtAs<Expr>()) - reversePropagateIntererstingSymbols(*PDB.getBugReport(), IE, - N->getState().get(), Ex, - N->getLocationContext()); - - // Add an edge. If this is an ObjCForCollectionStmt do - // not add an edge here as it appears in the CFG both - // as a terminator and as a terminator condition. - if (!isa<ObjCForCollectionStmt>(PS->getStmt())) { - PathDiagnosticLocation L = - PathDiagnosticLocation(PS->getStmt(), SM, PDB.LC); - addEdgeToPath(PD.getActivePath(), PrevLoc, L, PDB.LC); - } - break; + // We are descending into a call (backwards). Construct + // a new call piece to contain the path pieces for that call. + auto C = PathDiagnosticCallPiece::construct(N, *CE, SM); + // Record the mapping from call piece to LocationContext. + LCM[&C->path] = CE->getCalleeContext(); + + if (AddPathEdges) { + const Stmt *S = CE->getCalleeContext()->getCallSite(); + // Propagate the interesting symbols accordingly. + if (const auto *Ex = dyn_cast_or_null<Expr>(S)) { + reversePropagateIntererstingSymbols(*PDB.getBugReport(), IE, + N->getState().get(), Ex, + N->getLocationContext()); } + // Add the edge to the return site. + addEdgeToPath(PD.getActivePath(), PrevLoc, C->callReturn, PDB.LC); + PrevLoc.invalidate(); + } - // Block edges. - if (Optional<BlockEdge> BE = P.getAs<BlockEdge>()) { - // Does this represent entering a call? If so, look at propagating - // interesting symbols across call boundaries. - if (NextNode) { - const LocationContext *CallerCtx = NextNode->getLocationContext(); - const LocationContext *CalleeCtx = PDB.LC; - if (CallerCtx != CalleeCtx) { - reversePropagateInterestingSymbols(*PDB.getBugReport(), IE, - N->getState().get(), - CalleeCtx, CallerCtx); - } - } + auto *P = C.get(); + PD.getActivePath().push_front(std::move(C)); - // Are we jumping to the head of a loop? Add a special diagnostic. - if (const Stmt *Loop = BE->getSrc()->getLoopTarget()) { - PathDiagnosticLocation L(Loop, SM, PDB.LC); - const Stmt *Body = nullptr; - - if (const ForStmt *FS = dyn_cast<ForStmt>(Loop)) - Body = FS->getBody(); - else if (const WhileStmt *WS = dyn_cast<WhileStmt>(Loop)) - Body = WS->getBody(); - else if (const ObjCForCollectionStmt *OFS = - dyn_cast<ObjCForCollectionStmt>(Loop)) { - Body = OFS->getBody(); - } else if (const CXXForRangeStmt *FRS = - dyn_cast<CXXForRangeStmt>(Loop)) { - Body = FRS->getBody(); - } - // do-while statements are explicitly excluded here + // Make the contents of the call the active path for now. + PD.pushActivePath(&P->path); + CallStack.push_back(StackDiagPair(P, N)); + return; + } - auto p = std::make_shared<PathDiagnosticEventPiece>( - L, "Looping back to the head " - "of the loop"); - p->setPrunable(true); + if (auto PS = P.getAs<PostStmt>()) { + if (!AddPathEdges) + return; - addEdgeToPath(PD.getActivePath(), PrevLoc, p->getLocation(), PDB.LC); - PD.getActivePath().push_front(std::move(p)); + // For expressions, make sure we propagate the + // interesting symbols correctly. + if (const Expr *Ex = PS->getStmtAs<Expr>()) + reversePropagateIntererstingSymbols(*PDB.getBugReport(), IE, + N->getState().get(), Ex, + N->getLocationContext()); + + // Add an edge. If this is an ObjCForCollectionStmt do + // not add an edge here as it appears in the CFG both + // as a terminator and as a terminator condition. + if (!isa<ObjCForCollectionStmt>(PS->getStmt())) { + PathDiagnosticLocation L = + PathDiagnosticLocation(PS->getStmt(), SM, PDB.LC); + addEdgeToPath(PD.getActivePath(), PrevLoc, L, PDB.LC); + } - if (const CompoundStmt *CS = dyn_cast_or_null<CompoundStmt>(Body)) { - addEdgeToPath(PD.getActivePath(), PrevLoc, - PathDiagnosticLocation::createEndBrace(CS, SM), - PDB.LC); - } - } + } else if (auto BE = P.getAs<BlockEdge>()) { - const CFGBlock *BSrc = BE->getSrc(); - ParentMap &PM = PDB.getParentMap(); - - if (const Stmt *Term = BSrc->getTerminator()) { - // Are we jumping past the loop body without ever executing the - // loop (because the condition was false)? - if (isLoop(Term)) { - const Stmt *TermCond = getTerminatorCondition(BSrc); - bool IsInLoopBody = - isInLoopBody(PM, getStmtBeforeCond(PM, TermCond, N), Term); - - const char *str = nullptr; - - if (isJumpToFalseBranch(&*BE)) { - if (!IsInLoopBody) { - if (isa<ObjCForCollectionStmt>(Term)) { - str = StrLoopCollectionEmpty; - } else if (isa<CXXForRangeStmt>(Term)) { - str = StrLoopRangeEmpty; - } else { - str = StrLoopBodyZero; - } - } - } else { - str = StrEnteringLoop; - } + if (!AddPathEdges) { + generateMinimalDiagForBlockEdge(N, *BE, SM, PDB, PD); + return; + } - if (str) { - PathDiagnosticLocation L(TermCond ? TermCond : Term, SM, PDB.LC); - auto PE = std::make_shared<PathDiagnosticEventPiece>(L, str); - PE->setPrunable(true); - addEdgeToPath(PD.getActivePath(), PrevLoc, - PE->getLocation(), PDB.LC); - PD.getActivePath().push_front(std::move(PE)); - } - } else if (isa<BreakStmt>(Term) || isa<ContinueStmt>(Term) || - isa<GotoStmt>(Term)) { - PathDiagnosticLocation L(Term, SM, PDB.LC); - addEdgeToPath(PD.getActivePath(), PrevLoc, L, PDB.LC); - } - } - break; + // Does this represent entering a call? If so, look at propagating + // interesting symbols across call boundaries. + if (const ExplodedNode *NextNode = N->getFirstPred()) { + const LocationContext *CallerCtx = NextNode->getLocationContext(); + const LocationContext *CalleeCtx = PDB.LC; + if (CallerCtx != CalleeCtx && AddPathEdges) { + reversePropagateInterestingSymbols(*PDB.getBugReport(), IE, + N->getState().get(), + CalleeCtx, CallerCtx); } - } while (0); + } - if (!NextNode) - continue; + // Are we jumping to the head of a loop? Add a special diagnostic. + if (const Stmt *Loop = BE->getSrc()->getLoopTarget()) { + PathDiagnosticLocation L(Loop, SM, PDB.LC); + const Stmt *Body = nullptr; + + if (const auto *FS = dyn_cast<ForStmt>(Loop)) + Body = FS->getBody(); + else if (const auto *WS = dyn_cast<WhileStmt>(Loop)) + Body = WS->getBody(); + else if (const auto *OFS = dyn_cast<ObjCForCollectionStmt>(Loop)) { + Body = OFS->getBody(); + } else if (const auto *FRS = dyn_cast<CXXForRangeStmt>(Loop)) { + Body = FRS->getBody(); + } + // do-while statements are explicitly excluded here - // Add pieces from custom visitors. - llvm::FoldingSet<PathDiagnosticPiece> DeduplicationSet; - for (auto &V : visitors) { - if (auto p = V->VisitNode(N, NextNode, PDB, *report)) { - if (DeduplicationSet.GetOrInsertNode(p.get()) != p.get()) - continue; + auto p = std::make_shared<PathDiagnosticEventPiece>( + L, "Looping back to the head " + "of the loop"); + p->setPrunable(true); - addEdgeToPath(PD.getActivePath(), PrevLoc, p->getLocation(), PDB.LC); - updateStackPiecesWithMessage(*p, CallStack); - PD.getActivePath().push_front(std::move(p)); + addEdgeToPath(PD.getActivePath(), PrevLoc, p->getLocation(), PDB.LC); + PD.getActivePath().push_front(std::move(p)); + + if (const auto *CS = dyn_cast_or_null<CompoundStmt>(Body)) { + addEdgeToPath(PD.getActivePath(), PrevLoc, + PathDiagnosticLocation::createEndBrace(CS, SM), + PDB.LC); } } - } - // Add an edge to the start of the function. - // We'll prune it out later, but it helps make diagnostics more uniform. - const StackFrameContext *CalleeLC = PDB.LC->getCurrentStackFrame(); - const Decl *D = CalleeLC->getDecl(); - addEdgeToPath(PD.getActivePath(), PrevLoc, - PathDiagnosticLocation::createBegin(D, SM), - CalleeLC); + const CFGBlock *BSrc = BE->getSrc(); + ParentMap &PM = PDB.getParentMap(); + + if (const Stmt *Term = BSrc->getTerminator()) { + // Are we jumping past the loop body without ever executing the + // loop (because the condition was false)? + if (isLoop(Term)) { + const Stmt *TermCond = getTerminatorCondition(BSrc); + bool IsInLoopBody = + isInLoopBody(PM, getStmtBeforeCond(PM, TermCond, N), Term); + + const char *str = nullptr; + + if (isJumpToFalseBranch(&*BE)) { + if (!IsInLoopBody) { + if (isa<ObjCForCollectionStmt>(Term)) { + str = StrLoopCollectionEmpty; + } else if (isa<CXXForRangeStmt>(Term)) { + str = StrLoopRangeEmpty; + } else { + str = StrLoopBodyZero; + } + } + } else { + str = StrEnteringLoop; + } - return report->isValid(); + if (str) { + PathDiagnosticLocation L(TermCond ? TermCond : Term, SM, PDB.LC); + auto PE = std::make_shared<PathDiagnosticEventPiece>(L, str); + PE->setPrunable(true); + addEdgeToPath(PD.getActivePath(), PrevLoc, + PE->getLocation(), PDB.LC); + PD.getActivePath().push_front(std::move(PE)); + } + } else if (isa<BreakStmt>(Term) || isa<ContinueStmt>(Term) || + isa<GotoStmt>(Term)) { + PathDiagnosticLocation L(Term, SM, PDB.LC); + addEdgeToPath(PD.getActivePath(), PrevLoc, L, PDB.LC); + } + } + } } -static const Stmt *getLocStmt(PathDiagnosticLocation L) { - if (!L.isValid()) - return nullptr; - return L.asStmt(); +static std::unique_ptr<PathDiagnostic> +generateEmptyDiagnosticForReport(BugReport *R, SourceManager &SM) { + BugType &BT = R->getBugType(); + return llvm::make_unique<PathDiagnostic>( + R->getBugType().getCheckName(), R->getDeclWithIssue(), + R->getBugType().getName(), R->getDescription(), + R->getShortDescription(/*Fallback=*/false), BT.getCategory(), + R->getUniqueingLocation(), R->getUniqueingDecl(), + findExecutedLines(SM, R->getErrorNode())); } static const Stmt *getStmtParent(const Stmt *S, const ParentMap &PM) { @@ -1941,7 +1283,7 @@ static const Stmt *getStmtParent(const Stmt *S, const ParentMap &PM) { static bool isConditionForTerminator(const Stmt *S, const Stmt *Cond) { switch (S->getStmtClass()) { case Stmt::BinaryOperatorClass: { - const BinaryOperator *BO = cast<BinaryOperator>(S); + const auto *BO = cast<BinaryOperator>(S); if (!BO->isLogicalOp()) return false; return BO->getLHS() == Cond || BO->getRHS() == Cond; @@ -1963,7 +1305,7 @@ static bool isConditionForTerminator(const Stmt *S, const Stmt *Cond) { case Stmt::BinaryConditionalOperatorClass: return cast<BinaryConditionalOperator>(S)->getCond() == Cond; case Stmt::ConditionalOperatorClass: { - const ConditionalOperator *CO = cast<ConditionalOperator>(S); + const auto *CO = cast<ConditionalOperator>(S); return CO->getCond() == Cond || CO->getLHS() == Cond || CO->getRHS() == Cond; @@ -1971,7 +1313,7 @@ static bool isConditionForTerminator(const Stmt *S, const Stmt *Cond) { case Stmt::ObjCForCollectionStmtClass: return cast<ObjCForCollectionStmt>(S)->getElement() == Cond; case Stmt::CXXForRangeStmtClass: { - const CXXForRangeStmt *FRS = cast<CXXForRangeStmt>(S); + const auto *FRS = cast<CXXForRangeStmt>(S); return FRS->getCond() == Cond || FRS->getRangeInit() == Cond; } default: @@ -1980,16 +1322,15 @@ static bool isConditionForTerminator(const Stmt *S, const Stmt *Cond) { } static bool isIncrementOrInitInForLoop(const Stmt *S, const Stmt *FL) { - if (const ForStmt *FS = dyn_cast<ForStmt>(FL)) + if (const auto *FS = dyn_cast<ForStmt>(FL)) return FS->getInc() == S || FS->getInit() == S; - if (const CXXForRangeStmt *FRS = dyn_cast<CXXForRangeStmt>(FL)) + if (const auto *FRS = dyn_cast<CXXForRangeStmt>(FL)) return FRS->getInc() == S || FRS->getRangeStmt() == S || FRS->getLoopVarStmt() || FRS->getRangeInit() == S; return false; } -typedef llvm::DenseSet<const PathDiagnosticCallPiece *> - OptimizedCallsSet; +using OptimizedCallsSet = llvm::DenseSet<const PathDiagnosticCallPiece *>; /// Adds synthetic edges from top-level statements to their subexpressions. /// @@ -2001,8 +1342,7 @@ static void addContextEdges(PathPieces &pieces, SourceManager &SM, PathPieces::iterator Prev = pieces.end(); for (PathPieces::iterator I = pieces.begin(), E = Prev; I != E; Prev = I, ++I) { - PathDiagnosticControlFlowPiece *Piece = - dyn_cast<PathDiagnosticControlFlowPiece>(I->get()); + auto *Piece = dyn_cast<PathDiagnosticControlFlowPiece>(I->get()); if (!Piece) continue; @@ -2023,7 +1363,7 @@ static void addContextEdges(PathPieces &pieces, SourceManager &SM, // This is important for nested logical expressions (||, &&, ?:) where we // want to show all the levels of context. while (true) { - const Stmt *Dst = getLocStmt(Piece->getEndLocation()); + const Stmt *Dst = Piece->getEndLocation().getStmtOrNull(); // We are looking at an edge. Is the destination within a larger // expression? @@ -2046,9 +1386,11 @@ static void addContextEdges(PathPieces &pieces, SourceManager &SM, auto *PrevPiece = dyn_cast<PathDiagnosticControlFlowPiece>(Prev->get()); if (PrevPiece) { - if (const Stmt *PrevSrc = getLocStmt(PrevPiece->getStartLocation())) { + if (const Stmt *PrevSrc = + PrevPiece->getStartLocation().getStmtOrNull()) { const Stmt *PrevSrcParent = getStmtParent(PrevSrc, PM); - if (PrevSrcParent == getStmtParent(getLocStmt(DstContext), PM)) { + if (PrevSrcParent == + getStmtParent(DstContext.getStmtOrNull(), PM)) { PrevPiece->setEndLocation(DstContext); break; } @@ -2067,7 +1409,7 @@ static void addContextEdges(PathPieces &pieces, SourceManager &SM, } } -/// \brief Move edges from a branch condition to a branch target +/// Move edges from a branch condition to a branch target /// when the condition is simple. /// /// This restructures some of the work of addContextEdges. That function @@ -2077,17 +1419,15 @@ static void addContextEdges(PathPieces &pieces, SourceManager &SM, /// the branch to the branch condition, and (3) an edge from the branch /// condition to the branch target. We keep (1), but may wish to remove (2) /// and move the source of (3) to the branch if the branch condition is simple. -/// static void simplifySimpleBranches(PathPieces &pieces) { for (PathPieces::iterator I = pieces.begin(), E = pieces.end(); I != E; ++I) { - - auto *PieceI = dyn_cast<PathDiagnosticControlFlowPiece>(I->get()); + const auto *PieceI = dyn_cast<PathDiagnosticControlFlowPiece>(I->get()); if (!PieceI) continue; - const Stmt *s1Start = getLocStmt(PieceI->getStartLocation()); - const Stmt *s1End = getLocStmt(PieceI->getEndLocation()); + const Stmt *s1Start = PieceI->getStartLocation().getStmtOrNull(); + const Stmt *s1End = PieceI->getEndLocation().getStmtOrNull(); if (!s1Start || !s1End) continue; @@ -2102,7 +1442,7 @@ static void simplifySimpleBranches(PathPieces &pieces) { if (NextI == E) break; - auto *EV = dyn_cast<PathDiagnosticEventPiece>(NextI->get()); + const auto *EV = dyn_cast<PathDiagnosticEventPiece>(NextI->get()); if (EV) { StringRef S = EV->getString(); if (S == StrEnteringLoop || S == StrLoopBodyZero || @@ -2120,8 +1460,8 @@ static void simplifySimpleBranches(PathPieces &pieces) { if (!PieceNextI) continue; - const Stmt *s2Start = getLocStmt(PieceNextI->getStartLocation()); - const Stmt *s2End = getLocStmt(PieceNextI->getEndLocation()); + const Stmt *s2Start = PieceNextI->getStartLocation().getStmtOrNull(); + const Stmt *s2End = PieceNextI->getEndLocation().getStmtOrNull(); if (!s2Start || !s2End || s1End != s2Start) continue; @@ -2152,7 +1492,7 @@ static void simplifySimpleBranches(PathPieces &pieces) { static Optional<size_t> getLengthOnSingleLine(SourceManager &SM, SourceRange Range) { SourceRange ExpansionRange(SM.getExpansionLoc(Range.getBegin()), - SM.getExpansionRange(Range.getEnd()).second); + SM.getExpansionRange(Range.getEnd()).getEnd()); FileID FID = SM.getFileID(ExpansionRange.getBegin()); if (FID != SM.getFileID(ExpansionRange.getEnd())) @@ -2204,22 +1544,21 @@ static void removeContextCycles(PathPieces &Path, SourceManager &SM, ParentMap &PM) { for (PathPieces::iterator I = Path.begin(), E = Path.end(); I != E; ) { // Pattern match the current piece and its successor. - PathDiagnosticControlFlowPiece *PieceI = - dyn_cast<PathDiagnosticControlFlowPiece>(I->get()); + const auto *PieceI = dyn_cast<PathDiagnosticControlFlowPiece>(I->get()); if (!PieceI) { ++I; continue; } - const Stmt *s1Start = getLocStmt(PieceI->getStartLocation()); - const Stmt *s1End = getLocStmt(PieceI->getEndLocation()); + const Stmt *s1Start = PieceI->getStartLocation().getStmtOrNull(); + const Stmt *s1End = PieceI->getEndLocation().getStmtOrNull(); PathPieces::iterator NextI = I; ++NextI; if (NextI == E) break; - PathDiagnosticControlFlowPiece *PieceNextI = + const auto *PieceNextI = dyn_cast<PathDiagnosticControlFlowPiece>(NextI->get()); if (!PieceNextI) { @@ -2236,8 +1575,8 @@ static void removeContextCycles(PathPieces &Path, SourceManager &SM, } } - const Stmt *s2Start = getLocStmt(PieceNextI->getStartLocation()); - const Stmt *s2End = getLocStmt(PieceNextI->getEndLocation()); + const Stmt *s2Start = PieceNextI->getStartLocation().getStmtOrNull(); + const Stmt *s2End = PieceNextI->getEndLocation().getStmtOrNull(); if (s1Start && s2Start && s1Start == s2End && s2Start == s1End) { const size_t MAX_SHORT_LINE_LENGTH = 80; @@ -2256,10 +1595,8 @@ static void removeContextCycles(PathPieces &Path, SourceManager &SM, } } -/// \brief Return true if X is contained by Y. -static bool lexicalContains(ParentMap &PM, - const Stmt *X, - const Stmt *Y) { +/// Return true if X is contained by Y. +static bool lexicalContains(ParentMap &PM, const Stmt *X, const Stmt *Y) { while (X) { if (X == Y) return true; @@ -2269,24 +1606,21 @@ static bool lexicalContains(ParentMap &PM, } // Remove short edges on the same line less than 3 columns in difference. -static void removePunyEdges(PathPieces &path, - SourceManager &SM, +static void removePunyEdges(PathPieces &path, SourceManager &SM, ParentMap &PM) { - bool erased = false; for (PathPieces::iterator I = path.begin(), E = path.end(); I != E; erased ? I : ++I) { - erased = false; - auto *PieceI = dyn_cast<PathDiagnosticControlFlowPiece>(I->get()); + const auto *PieceI = dyn_cast<PathDiagnosticControlFlowPiece>(I->get()); if (!PieceI) continue; - const Stmt *start = getLocStmt(PieceI->getStartLocation()); - const Stmt *end = getLocStmt(PieceI->getEndLocation()); + const Stmt *start = PieceI->getStartLocation().getStmtOrNull(); + const Stmt *end = PieceI->getEndLocation().getStmtOrNull(); if (!start || !end) continue; @@ -2327,7 +1661,7 @@ static void removePunyEdges(PathPieces &path, static void removeIdenticalEvents(PathPieces &path) { for (PathPieces::iterator I = path.begin(), E = path.end(); I != E; ++I) { - auto *PieceI = dyn_cast<PathDiagnosticEventPiece>(I->get()); + const auto *PieceI = dyn_cast<PathDiagnosticEventPiece>(I->get()); if (!PieceI) continue; @@ -2336,7 +1670,7 @@ static void removeIdenticalEvents(PathPieces &path) { if (NextI == E) return; - auto *PieceNextI = dyn_cast<PathDiagnosticEventPiece>(NextI->get()); + const auto *PieceNextI = dyn_cast<PathDiagnosticEventPiece>(NextI->get()); if (!PieceNextI) continue; @@ -2377,8 +1711,8 @@ static bool optimizeEdges(PathPieces &path, SourceManager &SM, continue; } - const Stmt *s1Start = getLocStmt(PieceI->getStartLocation()); - const Stmt *s1End = getLocStmt(PieceI->getEndLocation()); + const Stmt *s1Start = PieceI->getStartLocation().getStmtOrNull(); + const Stmt *s1End = PieceI->getEndLocation().getStmtOrNull(); const Stmt *level1 = getStmtParent(s1Start, PM); const Stmt *level2 = getStmtParent(s1End, PM); @@ -2386,15 +1720,15 @@ static bool optimizeEdges(PathPieces &path, SourceManager &SM, if (NextI == E) break; - auto *PieceNextI = dyn_cast<PathDiagnosticControlFlowPiece>(NextI->get()); + const auto *PieceNextI = dyn_cast<PathDiagnosticControlFlowPiece>(NextI->get()); if (!PieceNextI) { ++I; continue; } - const Stmt *s2Start = getLocStmt(PieceNextI->getStartLocation()); - const Stmt *s2End = getLocStmt(PieceNextI->getEndLocation()); + const Stmt *s2Start = PieceNextI->getStartLocation().getStmtOrNull(); + const Stmt *s2End = PieceNextI->getEndLocation().getStmtOrNull(); const Stmt *level3 = getStmtParent(s2Start, PM); const Stmt *level4 = getStmtParent(s2End, PM); @@ -2412,7 +1746,6 @@ static bool optimizeEdges(PathPieces &path, SourceManager &SM, // // NOTE: this will be limited later in cases where we add barriers // to prevent this optimization. - // if (level1 && level1 == level2 && level1 == level3 && level1 == level4) { PieceI->setEndLocation(PieceNextI->getEndLocation()); path.erase(NextI); @@ -2427,7 +1760,6 @@ static bool optimizeEdges(PathPieces &path, SourceManager &SM, // // NOTE: this will be limited later in cases where we add barriers // to prevent this optimization. - // if (s1End && s1End == s2Start && level2) { bool removeEdge = false; // Remove edges into the increment or initialization of a @@ -2493,8 +1825,7 @@ static bool optimizeEdges(PathPieces &path, SourceManager &SM, // // (X -> element) if (s1End == s2Start) { - const ObjCForCollectionStmt *FS = - dyn_cast_or_null<ObjCForCollectionStmt>(level3); + const auto *FS = dyn_cast_or_null<ObjCForCollectionStmt>(level3); if (FS && FS->getCollection()->IgnoreParens() == s2Start && s2End == FS->getElement()) { PieceI->setEndLocation(PieceNextI->getEndLocation()); @@ -2532,8 +1863,7 @@ static bool optimizeEdges(PathPieces &path, SourceManager &SM, /// statement had an invalid source location), this function does nothing. // FIXME: We should just generate invalid edges anyway and have the optimizer // deal with them. -static void dropFunctionEntryEdge(PathPieces &Path, - LocationContextMap &LCM, +static void dropFunctionEntryEdge(PathPieces &Path, LocationContextMap &LCM, SourceManager &SM) { const auto *FirstEdge = dyn_cast<PathDiagnosticControlFlowPiece>(Path.front().get()); @@ -2548,11 +1878,134 @@ static void dropFunctionEntryEdge(PathPieces &Path, Path.pop_front(); } +using VisitorsDiagnosticsTy = llvm::DenseMap<const ExplodedNode *, + std::vector<std::shared_ptr<PathDiagnosticPiece>>>; + +/// This function is responsible for generating diagnostic pieces that are +/// *not* provided by bug report visitors. +/// These diagnostics may differ depending on the consumer's settings, +/// and are therefore constructed separately for each consumer. +/// +/// There are two path diagnostics generation modes: with adding edges (used +/// for plists) and without (used for HTML and text). +/// When edges are added (\p ActiveScheme is Extensive), +/// the path is modified to insert artificially generated +/// edges. +/// Otherwise, more detailed diagnostics is emitted for block edges, explaining +/// the transitions in words. +static std::unique_ptr<PathDiagnostic> generatePathDiagnosticForConsumer( + PathDiagnosticConsumer::PathGenerationScheme ActiveScheme, + PathDiagnosticBuilder &PDB, + const ExplodedNode *ErrorNode, + const VisitorsDiagnosticsTy &VisitorsDiagnostics) { + + bool GenerateDiagnostics = (ActiveScheme != PathDiagnosticConsumer::None); + bool AddPathEdges = (ActiveScheme == PathDiagnosticConsumer::Extensive); + SourceManager &SM = PDB.getSourceManager(); + BugReport *R = PDB.getBugReport(); + AnalyzerOptions &Opts = PDB.getBugReporter().getAnalyzerOptions(); + StackDiagVector CallStack; + InterestingExprs IE; + LocationContextMap LCM; + std::unique_ptr<PathDiagnostic> PD = generateEmptyDiagnosticForReport(R, SM); + + if (GenerateDiagnostics) { + auto EndNotes = VisitorsDiagnostics.find(ErrorNode); + std::shared_ptr<PathDiagnosticPiece> LastPiece; + if (EndNotes != VisitorsDiagnostics.end()) { + assert(!EndNotes->second.empty()); + LastPiece = EndNotes->second[0]; + } else { + LastPiece = BugReporterVisitor::getDefaultEndPath(PDB, ErrorNode, *R); + } + PD->setEndOfPath(LastPiece); + } + + PathDiagnosticLocation PrevLoc = PD->getLocation(); + const ExplodedNode *NextNode = ErrorNode->getFirstPred(); + while (NextNode) { + if (GenerateDiagnostics) + generatePathDiagnosticsForNode( + NextNode, *PD, PrevLoc, PDB, LCM, CallStack, IE, AddPathEdges); + + auto VisitorNotes = VisitorsDiagnostics.find(NextNode); + NextNode = NextNode->getFirstPred(); + if (!GenerateDiagnostics || VisitorNotes == VisitorsDiagnostics.end()) + continue; + + // This is a workaround due to inability to put shared PathDiagnosticPiece + // into a FoldingSet. + std::set<llvm::FoldingSetNodeID> DeduplicationSet; + + // Add pieces from custom visitors. + for (const auto &Note : VisitorNotes->second) { + llvm::FoldingSetNodeID ID; + Note->Profile(ID); + auto P = DeduplicationSet.insert(ID); + if (!P.second) + continue; + + if (AddPathEdges) + addEdgeToPath(PD->getActivePath(), PrevLoc, Note->getLocation(), + PDB.LC); + updateStackPiecesWithMessage(*Note, CallStack); + PD->getActivePath().push_front(Note); + } + } + + if (AddPathEdges) { + // Add an edge to the start of the function. + // We'll prune it out later, but it helps make diagnostics more uniform. + const StackFrameContext *CalleeLC = PDB.LC->getStackFrame(); + const Decl *D = CalleeLC->getDecl(); + addEdgeToPath(PD->getActivePath(), PrevLoc, + PathDiagnosticLocation::createBegin(D, SM), CalleeLC); + } + + if (!AddPathEdges && GenerateDiagnostics) + CompactPathDiagnostic(PD->getMutablePieces(), SM); + + // Finally, prune the diagnostic path of uninteresting stuff. + if (!PD->path.empty()) { + if (R->shouldPrunePath() && Opts.shouldPrunePaths()) { + bool stillHasNotes = + removeUnneededCalls(PD->getMutablePieces(), R, LCM); + assert(stillHasNotes); + (void)stillHasNotes; + } + + // Redirect all call pieces to have valid locations. + adjustCallLocations(PD->getMutablePieces()); + removePiecesWithInvalidLocations(PD->getMutablePieces()); + + if (AddPathEdges) { + + // Reduce the number of edges from a very conservative set + // to an aesthetically pleasing subset that conveys the + // necessary information. + OptimizedCallsSet OCS; + while (optimizeEdges(PD->getMutablePieces(), SM, OCS, LCM)) {} + + // Drop the very first function-entry edge. It's not really necessary + // for top-level functions. + dropFunctionEntryEdge(PD->getMutablePieces(), LCM, SM); + } + + // Remove messages that are basically the same, and edges that may not + // make sense. + // We have to do this after edge optimization in the Extensive mode. + removeRedundantMsgs(PD->getMutablePieces()); + removeEdgesToDefaultInitializers(PD->getMutablePieces()); + } + return PD; +} + //===----------------------------------------------------------------------===// // Methods for BugType and subclasses. //===----------------------------------------------------------------------===// -void BugType::anchor() { } + +void BugType::anchor() {} void BugType::FlushReports(BugReporter &BR) {} @@ -2570,14 +2023,17 @@ void BugReport::addVisitor(std::unique_ptr<BugReporterVisitor> visitor) { llvm::FoldingSetNodeID ID; visitor->Profile(ID); - void *InsertPos; - if (CallbacksSet.FindNodeOrInsertPos(ID, InsertPos)) + void *InsertPos = nullptr; + if (CallbacksSet.FindNodeOrInsertPos(ID, InsertPos)) { return; + } - CallbacksSet.InsertNode(visitor.get(), InsertPos); Callbacks.push_back(std::move(visitor)); - ++ConfigurationChangeToken; +} + +void BugReport::clearVisitors() { + Callbacks.clear(); } BugReport::~BugReport() { @@ -2595,7 +2051,7 @@ const Decl *BugReport::getDeclWithIssue() const { return nullptr; const LocationContext *LC = N->getLocationContext(); - return LC->getCurrentStackFrame()->getDecl(); + return LC->getStackFrame()->getDecl(); } void BugReport::Profile(llvm::FoldingSetNodeID& hash) const { @@ -2623,11 +2079,9 @@ void BugReport::markInteresting(SymbolRef sym) { if (!sym) return; - // If the symbol wasn't already in our set, note a configuration change. - if (getInterestingSymbols().insert(sym).second) - ++ConfigurationChangeToken; + getInterestingSymbols().insert(sym); - if (const SymbolMetadata *meta = dyn_cast<SymbolMetadata>(sym)) + if (const auto *meta = dyn_cast<SymbolMetadata>(sym)) getInterestingRegions().insert(meta->getRegion()); } @@ -2635,12 +2089,10 @@ void BugReport::markInteresting(const MemRegion *R) { if (!R) return; - // If the base region wasn't already in our set, note a configuration change. R = R->getBaseRegion(); - if (getInterestingRegions().insert(R).second) - ++ConfigurationChangeToken; + getInterestingRegions().insert(R); - if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(R)) + if (const auto *SR = dyn_cast<SymbolicRegion>(R)) getInterestingSymbols().insert(SR->getSymbol()); } @@ -2674,7 +2126,7 @@ bool BugReport::isInteresting(const MemRegion *R) { bool b = getInterestingRegions().count(R); if (b) return true; - if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(R)) + if (const auto *SR = dyn_cast<SymbolicRegion>(R)) return getInterestingSymbols().count(SR->getSymbol()); return false; } @@ -2734,7 +2186,7 @@ llvm::iterator_range<BugReport::ranges_iterator> BugReport::getRanges() { // If no custom ranges, add the range of the statement corresponding to // the error node. if (Ranges.empty()) { - if (const Expr *E = dyn_cast_or_null<Expr>(getStmt())) + if (const auto *E = dyn_cast_or_null<Expr>(getStmt())) addRange(E->getSourceRange()); else return llvm::make_range(ranges_iterator(), ranges_iterator()); @@ -2762,9 +2214,11 @@ PathDiagnosticLocation BugReport::getLocation(const SourceManager &SM) const { // Methods for BugReporter and subclasses. //===----------------------------------------------------------------------===// -BugReportEquivClass::~BugReportEquivClass() { } -GRBugReporter::~GRBugReporter() { } -BugReporterData::~BugReporterData() {} +BugReportEquivClass::~BugReportEquivClass() = default; + +GRBugReporter::~GRBugReporter() = default; + +BugReporterData::~BugReporterData() = default; ExplodedGraph &GRBugReporter::getGraph() { return Eng.getGraph(); } @@ -2775,11 +2229,8 @@ BugReporter::~BugReporter() { FlushReports(); // Free the bug reports we are tracking. - typedef std::vector<BugReportEquivClass *> ContTy; - for (ContTy::iterator I = EQClassesVector.begin(), E = EQClassesVector.end(); - I != E; ++I) { - delete *I; - } + for (const auto I : EQClassesVector) + delete I; } void BugReporter::FlushReports() { @@ -2791,18 +2242,13 @@ void BugReporter::FlushReports() { // FIXME: Only NSErrorChecker needs BugType's FlushReports. // Turn NSErrorChecker into a proper checker and remove this. SmallVector<const BugType *, 16> bugTypes(BugTypes.begin(), BugTypes.end()); - for (SmallVectorImpl<const BugType *>::iterator - I = bugTypes.begin(), E = bugTypes.end(); I != E; ++I) - const_cast<BugType*>(*I)->FlushReports(*this); + for (const auto I : bugTypes) + const_cast<BugType*>(I)->FlushReports(*this); // We need to flush reports in deterministic order to ensure the order // of the reports is consistent between runs. - typedef std::vector<BugReportEquivClass *> ContVecTy; - for (ContVecTy::iterator EI=EQClassesVector.begin(), EE=EQClassesVector.end(); - EI != EE; ++EI){ - BugReportEquivClass& EQ = **EI; - FlushReport(EQ); - } + for (const auto EQ : EQClassesVector) + FlushReport(*EQ); // BugReporter owns and deletes only BugTypes created implicitly through // EmitBasicReport. @@ -2819,6 +2265,7 @@ void BugReporter::FlushReports() { //===----------------------------------------------------------------------===// namespace { + /// A wrapper around a report graph, which contains only a single path, and its /// node maps. class ReportGraph { @@ -2833,10 +2280,12 @@ public: class TrimmedGraph { InterExplodedGraphMap InverseMap; - typedef llvm::DenseMap<const ExplodedNode *, unsigned> PriorityMapTy; + using PriorityMapTy = llvm::DenseMap<const ExplodedNode *, unsigned>; + PriorityMapTy PriorityMap; - typedef std::pair<const ExplodedNode *, size_t> NodeIndexPair; + using NodeIndexPair = std::pair<const ExplodedNode *, size_t>; + SmallVector<NodeIndexPair, 32> ReportNodes; std::unique_ptr<ExplodedGraph> G; @@ -2874,7 +2323,8 @@ public: bool popNextReportGraph(ReportGraph &GraphWrapper); }; -} + +} // namespace TrimmedGraph::TrimmedGraph(const ExplodedGraph *OriginalGraph, ArrayRef<const ExplodedNode *> Nodes) { @@ -2930,8 +2380,8 @@ TrimmedGraph::TrimmedGraph(const ExplodedGraph *OriginalGraph, } // Sort the error paths from longest to shortest. - std::sort(ReportNodes.begin(), ReportNodes.end(), - PriorityCompare<true>(PriorityMap)); + llvm::sort(ReportNodes.begin(), ReportNodes.end(), + PriorityCompare<true>(PriorityMap)); } bool TrimmedGraph::popNextReportGraph(ReportGraph &GraphWrapper) { @@ -2987,23 +2437,21 @@ bool TrimmedGraph::popNextReportGraph(ReportGraph &GraphWrapper) { return true; } - /// CompactPathDiagnostic - This function postprocesses a PathDiagnostic object /// and collapses PathDiagosticPieces that are expanded by macros. static void CompactPathDiagnostic(PathPieces &path, const SourceManager& SM) { - typedef std::vector< - std::pair<std::shared_ptr<PathDiagnosticMacroPiece>, SourceLocation>> - MacroStackTy; + using MacroStackTy = + std::vector< + std::pair<std::shared_ptr<PathDiagnosticMacroPiece>, SourceLocation>>; - typedef std::vector<std::shared_ptr<PathDiagnosticPiece>> PiecesTy; + using PiecesTy = std::vector<std::shared_ptr<PathDiagnosticPiece>>; MacroStackTy MacroStack; PiecesTy Pieces; for (PathPieces::const_iterator I = path.begin(), E = path.end(); - I!=E; ++I) { - - auto &piece = *I; + I != E; ++I) { + const auto &piece = *I; // Recursively compact calls. if (auto *call = dyn_cast<PathDiagnosticCallPiece>(&*piece)) { @@ -3082,43 +2530,69 @@ static void CompactPathDiagnostic(PathPieces &path, const SourceManager& SM) { path.insert(path.end(), Pieces.begin(), Pieces.end()); } -bool GRBugReporter::generatePathDiagnostic(PathDiagnostic& PD, - PathDiagnosticConsumer &PC, - ArrayRef<BugReport *> &bugReports) { - assert(!bugReports.empty()); +/// Generate notes from all visitors. +/// Notes associated with {@code ErrorNode} are generated using +/// {@code getEndPath}, and the rest are generated with {@code VisitNode}. +static std::unique_ptr<VisitorsDiagnosticsTy> +generateVisitorsDiagnostics(BugReport *R, const ExplodedNode *ErrorNode, + BugReporterContext &BRC) { + auto Notes = llvm::make_unique<VisitorsDiagnosticsTy>(); + BugReport::VisitorList visitors; - bool HasValid = false; - bool HasInvalid = false; - SmallVector<const ExplodedNode *, 32> errorNodes; - for (ArrayRef<BugReport*>::iterator I = bugReports.begin(), - E = bugReports.end(); I != E; ++I) { - if ((*I)->isValid()) { - HasValid = true; - errorNodes.push_back((*I)->getErrorNode()); - } else { - // Keep the errorNodes list in sync with the bugReports list. - HasInvalid = true; - errorNodes.push_back(nullptr); + // Run visitors on all nodes starting from the node *before* the last one. + // The last node is reserved for notes generated with {@code getEndPath}. + const ExplodedNode *NextNode = ErrorNode->getFirstPred(); + while (NextNode) { + + // At each iteration, move all visitors from report to visitor list. + for (BugReport::visitor_iterator I = R->visitor_begin(), + E = R->visitor_end(); + I != E; ++I) { + visitors.push_back(std::move(*I)); } - } + R->clearVisitors(); - // If all the reports have been marked invalid by a previous path generation, - // we're done. - if (!HasValid) - return false; + const ExplodedNode *Pred = NextNode->getFirstPred(); + if (!Pred) { + std::shared_ptr<PathDiagnosticPiece> LastPiece; + for (auto &V : visitors) { + V->finalizeVisitor(BRC, ErrorNode, *R); - typedef PathDiagnosticConsumer::PathGenerationScheme PathGenerationScheme; - PathGenerationScheme ActiveScheme = PC.getGenerationScheme(); + if (auto Piece = V->getEndPath(BRC, ErrorNode, *R)) { + assert(!LastPiece && + "There can only be one final piece in a diagnostic."); + LastPiece = std::move(Piece); + (*Notes)[ErrorNode].push_back(LastPiece); + } + } + break; + } - if (ActiveScheme == PathDiagnosticConsumer::Extensive) { - AnalyzerOptions &options = getAnalyzerOptions(); - if (options.getBooleanOption("path-diagnostics-alternate", true)) { - ActiveScheme = PathDiagnosticConsumer::AlternateExtensive; + for (auto &V : visitors) { + auto P = V->VisitNode(NextNode, Pred, BRC, *R); + if (P) + (*Notes)[NextNode].push_back(std::move(P)); } + + if (!R->isValid()) + break; + + NextNode = Pred; } - TrimmedGraph TrimG(&getGraph(), errorNodes); - ReportGraph ErrorGraph; + return Notes; +} + +/// Find a non-invalidated report for a given equivalence class, +/// and return together with a cache of visitors notes. +/// If none found, return a nullptr paired with an empty cache. +static +std::pair<BugReport*, std::unique_ptr<VisitorsDiagnosticsTy>> findValidReport( + TrimmedGraph &TrimG, + ReportGraph &ErrorGraph, + ArrayRef<BugReport *> &bugReports, + AnalyzerOptions &Opts, + GRBugReporter &Reporter) { while (TrimG.popNextReportGraph(ErrorGraph)) { // Find the BugReport with the original location. @@ -3126,125 +2600,85 @@ bool GRBugReporter::generatePathDiagnostic(PathDiagnostic& PD, BugReport *R = bugReports[ErrorGraph.Index]; assert(R && "No original report found for sliced graph."); assert(R->isValid() && "Report selected by trimmed graph marked invalid."); + const ExplodedNode *ErrorNode = ErrorGraph.ErrorNode; - // Start building the path diagnostic... - PathDiagnosticBuilder PDB(*this, R, ErrorGraph.BackMap, &PC); - const ExplodedNode *N = ErrorGraph.ErrorNode; + // Register refutation visitors first, if they mark the bug invalid no + // further analysis is required + R->addVisitor(llvm::make_unique<LikelyFalsePositiveSuppressionBRVisitor>()); // Register additional node visitors. R->addVisitor(llvm::make_unique<NilReceiverBRVisitor>()); R->addVisitor(llvm::make_unique<ConditionBRVisitor>()); - R->addVisitor(llvm::make_unique<LikelyFalsePositiveSuppressionBRVisitor>()); R->addVisitor(llvm::make_unique<CXXSelfAssignmentBRVisitor>()); - BugReport::VisitorList visitors; - unsigned origReportConfigToken, finalReportConfigToken; - LocationContextMap LCM; - - // While generating diagnostics, it's possible the visitors will decide - // new symbols and regions are interesting, or add other visitors based on - // the information they find. If they do, we need to regenerate the path - // based on our new report configuration. - do { - // Get a clean copy of all the visitors. - for (BugReport::visitor_iterator I = R->visitor_begin(), - E = R->visitor_end(); I != E; ++I) - visitors.push_back((*I)->clone()); - - // Clear out the active path from any previous work. - PD.resetPath(); - origReportConfigToken = R->getConfigurationChangeToken(); - - // Generate the very last diagnostic piece - the piece is visible before - // the trace is expanded. - std::unique_ptr<PathDiagnosticPiece> LastPiece; - for (BugReport::visitor_iterator I = visitors.begin(), E = visitors.end(); - I != E; ++I) { - if (std::unique_ptr<PathDiagnosticPiece> Piece = - (*I)->getEndPath(PDB, N, *R)) { - assert (!LastPiece && - "There can only be one final piece in a diagnostic."); - LastPiece = std::move(Piece); - } - } + BugReporterContext BRC(Reporter, ErrorGraph.BackMap); - if (ActiveScheme != PathDiagnosticConsumer::None) { - if (!LastPiece) - LastPiece = BugReporterVisitor::getDefaultEndPath(PDB, N, *R); - assert(LastPiece); - PD.setEndOfPath(std::move(LastPiece)); - } + // Run all visitors on a given graph, once. + std::unique_ptr<VisitorsDiagnosticsTy> visitorNotes = + generateVisitorsDiagnostics(R, ErrorNode, BRC); - // Make sure we get a clean location context map so we don't - // hold onto old mappings. - LCM.clear(); + if (R->isValid()) { + if (Opts.shouldCrosscheckWithZ3()) { + // If crosscheck is enabled, remove all visitors, add the refutation + // visitor and check again + R->clearVisitors(); + R->addVisitor(llvm::make_unique<FalsePositiveRefutationBRVisitor>()); - switch (ActiveScheme) { - case PathDiagnosticConsumer::AlternateExtensive: - GenerateAlternateExtensivePathDiagnostic(PD, PDB, N, LCM, visitors); - break; - case PathDiagnosticConsumer::Extensive: - GenerateExtensivePathDiagnostic(PD, PDB, N, LCM, visitors); - break; - case PathDiagnosticConsumer::Minimal: - GenerateMinimalPathDiagnostic(PD, PDB, N, LCM, visitors); - break; - case PathDiagnosticConsumer::None: - GenerateVisitorsOnlyPathDiagnostic(PD, PDB, N, visitors); - break; + // We don't overrite the notes inserted by other visitors because the + // refutation manager does not add any new note to the path + generateVisitorsDiagnostics(R, ErrorGraph.ErrorNode, BRC); } - // Clean up the visitors we used. - visitors.clear(); - - // Did anything change while generating this path? - finalReportConfigToken = R->getConfigurationChangeToken(); - } while (finalReportConfigToken != origReportConfigToken); - - if (!R->isValid()) - continue; - - // Finally, prune the diagnostic path of uninteresting stuff. - if (!PD.path.empty()) { - if (R->shouldPrunePath() && getAnalyzerOptions().shouldPrunePaths()) { - bool stillHasNotes = removeUnneededCalls(PD.getMutablePieces(), R, LCM); - assert(stillHasNotes); - (void)stillHasNotes; - } + // Check if the bug is still valid + if (R->isValid()) + return std::make_pair(R, std::move(visitorNotes)); + } + } - // Redirect all call pieces to have valid locations. - adjustCallLocations(PD.getMutablePieces()); - removePiecesWithInvalidLocations(PD.getMutablePieces()); + return std::make_pair(nullptr, llvm::make_unique<VisitorsDiagnosticsTy>()); +} - if (ActiveScheme == PathDiagnosticConsumer::AlternateExtensive) { - SourceManager &SM = getSourceManager(); +std::unique_ptr<DiagnosticForConsumerMapTy> +GRBugReporter::generatePathDiagnostics( + ArrayRef<PathDiagnosticConsumer *> consumers, + ArrayRef<BugReport *> &bugReports) { + assert(!bugReports.empty()); - // Reduce the number of edges from a very conservative set - // to an aesthetically pleasing subset that conveys the - // necessary information. - OptimizedCallsSet OCS; - while (optimizeEdges(PD.getMutablePieces(), SM, OCS, LCM)) {} + auto Out = llvm::make_unique<DiagnosticForConsumerMapTy>(); + bool HasValid = false; + SmallVector<const ExplodedNode *, 32> errorNodes; + for (const auto I : bugReports) { + if (I->isValid()) { + HasValid = true; + errorNodes.push_back(I->getErrorNode()); + } else { + // Keep the errorNodes list in sync with the bugReports list. + errorNodes.push_back(nullptr); + } + } - // Drop the very first function-entry edge. It's not really necessary - // for top-level functions. - dropFunctionEntryEdge(PD.getMutablePieces(), LCM, SM); - } + // If all the reports have been marked invalid by a previous path generation, + // we're done. + if (!HasValid) + return Out; - // Remove messages that are basically the same, and edges that may not - // make sense. - // We have to do this after edge optimization in the Extensive mode. - removeRedundantMsgs(PD.getMutablePieces()); - removeEdgesToDefaultInitializers(PD.getMutablePieces()); + TrimmedGraph TrimG(&getGraph(), errorNodes); + ReportGraph ErrorGraph; + auto ReportInfo = findValidReport(TrimG, ErrorGraph, bugReports, + getAnalyzerOptions(), *this); + BugReport *R = ReportInfo.first; + + if (R && R->isValid()) { + const ExplodedNode *ErrorNode = ErrorGraph.ErrorNode; + for (PathDiagnosticConsumer *PC : consumers) { + PathDiagnosticBuilder PDB(*this, R, ErrorGraph.BackMap, PC); + std::unique_ptr<PathDiagnostic> PD = generatePathDiagnosticForConsumer( + PC->getGenerationScheme(), PDB, ErrorNode, *ReportInfo.second); + (*Out)[PC] = std::move(PD); } - - // We found a report and didn't suppress it. - return true; } - // We suppressed all the reports in this equivalence class. - assert(!HasInvalid && "Inconsistent suppression"); - (void)HasInvalid; - return false; + return Out; } void BugReporter::Register(BugType *BT) { @@ -3294,20 +2728,21 @@ void BugReporter::emitReport(std::unique_ptr<BugReport> R) { EQ->AddReport(std::move(R)); } - //===----------------------------------------------------------------------===// // Emitting reports in equivalence classes. //===----------------------------------------------------------------------===// namespace { + struct FRIEC_WLItem { const ExplodedNode *N; ExplodedNode::const_succ_iterator I, E; FRIEC_WLItem(const ExplodedNode *n) - : N(n), I(N->succ_begin()), E(N->succ_end()) {} + : N(n), I(N->succ_begin()), E(N->succ_end()) {} }; -} + +} // namespace static const CFGBlock *findBlockForNode(const ExplodedNode *N) { ProgramPoint P = N->getLocation(); @@ -3397,7 +2832,6 @@ static bool isInevitablySinking(const ExplodedNode *N) { static BugReport * FindReportInEquivalenceClass(BugReportEquivClass& EQ, SmallVectorImpl<BugReport*> &bugReports) { - BugReportEquivClass::iterator I = EQ.begin(), E = EQ.end(); assert(I != E); BugType& BT = I->getBugType(); @@ -3407,10 +2841,10 @@ FindReportInEquivalenceClass(BugReportEquivClass& EQ, // to 'Nodes'. Any of the reports will serve as a "representative" report. if (!BT.isSuppressOnSink()) { BugReport *R = &*I; - for (BugReportEquivClass::iterator I=EQ.begin(), E=EQ.end(); I!=E; ++I) { - const ExplodedNode *N = I->getErrorNode(); + for (auto &I : EQ) { + const ExplodedNode *N = I.getErrorNode(); if (N) { - R = &*I; + R = &I; bugReports.push_back(R); } } @@ -3451,8 +2885,9 @@ FindReportInEquivalenceClass(BugReportEquivClass& EQ, // At this point we know that 'N' is not a sink and it has at least one // successor. Use a DFS worklist to find a non-sink end-of-path node. - typedef FRIEC_WLItem WLItem; - typedef SmallVector<WLItem, 10> DFSWorkList; + using WLItem = FRIEC_WLItem; + using DFSWorkList = SmallVector<WLItem, 10>; + llvm::DenseMap<const ExplodedNode *, unsigned> Visited; DFSWorkList WL; @@ -3502,90 +2937,166 @@ FindReportInEquivalenceClass(BugReportEquivClass& EQ, void BugReporter::FlushReport(BugReportEquivClass& EQ) { SmallVector<BugReport*, 10> bugReports; - BugReport *exampleReport = FindReportInEquivalenceClass(EQ, bugReports); - if (exampleReport) { - for (PathDiagnosticConsumer *PDC : getPathDiagnosticConsumers()) { - FlushReport(exampleReport, *PDC, bugReports); + BugReport *report = FindReportInEquivalenceClass(EQ, bugReports); + if (!report) + return; + + ArrayRef<PathDiagnosticConsumer*> Consumers = getPathDiagnosticConsumers(); + std::unique_ptr<DiagnosticForConsumerMapTy> Diagnostics = + generateDiagnosticForConsumerMap(report, Consumers, bugReports); + + for (auto &P : *Diagnostics) { + PathDiagnosticConsumer *Consumer = P.first; + std::unique_ptr<PathDiagnostic> &PD = P.second; + + // If the path is empty, generate a single step path with the location + // of the issue. + if (PD->path.empty()) { + PathDiagnosticLocation L = report->getLocation(getSourceManager()); + auto piece = llvm::make_unique<PathDiagnosticEventPiece>( + L, report->getDescription()); + for (SourceRange Range : report->getRanges()) + piece->addRange(Range); + PD->setEndOfPath(std::move(piece)); } - } -} -void BugReporter::FlushReport(BugReport *exampleReport, - PathDiagnosticConsumer &PD, - ArrayRef<BugReport*> bugReports) { + PathPieces &Pieces = PD->getMutablePieces(); + if (getAnalyzerOptions().shouldDisplayNotesAsEvents()) { + // For path diagnostic consumers that don't support extra notes, + // we may optionally convert those to path notes. + for (auto I = report->getNotes().rbegin(), + E = report->getNotes().rend(); I != E; ++I) { + PathDiagnosticNotePiece *Piece = I->get(); + auto ConvertedPiece = std::make_shared<PathDiagnosticEventPiece>( + Piece->getLocation(), Piece->getString()); + for (const auto &R: Piece->getRanges()) + ConvertedPiece->addRange(R); + + Pieces.push_front(std::move(ConvertedPiece)); + } + } else { + for (auto I = report->getNotes().rbegin(), + E = report->getNotes().rend(); I != E; ++I) + Pieces.push_front(*I); + } - // FIXME: Make sure we use the 'R' for the path that was actually used. - // Probably doesn't make a difference in practice. - BugType& BT = exampleReport->getBugType(); + // Get the meta data. + const BugReport::ExtraTextList &Meta = report->getExtraText(); + for (const auto &i : Meta) + PD->addMeta(i); - std::unique_ptr<PathDiagnostic> D(new PathDiagnostic( - exampleReport->getBugType().getCheckName(), - exampleReport->getDeclWithIssue(), exampleReport->getBugType().getName(), - exampleReport->getDescription(), - exampleReport->getShortDescription(/*Fallback=*/false), BT.getCategory(), - exampleReport->getUniqueingLocation(), - exampleReport->getUniqueingDecl())); + Consumer->HandlePathDiagnostic(std::move(PD)); + } +} - if (exampleReport->isPathSensitive()) { - // Generate the full path diagnostic, using the generation scheme - // specified by the PathDiagnosticConsumer. Note that we have to generate - // path diagnostics even for consumers which do not support paths, because - // the BugReporterVisitors may mark this bug as a false positive. - assert(!bugReports.empty()); +/// Insert all lines participating in the function signature \p Signature +/// into \p ExecutedLines. +static void populateExecutedLinesWithFunctionSignature( + const Decl *Signature, SourceManager &SM, + std::unique_ptr<FilesToLineNumsMap> &ExecutedLines) { + SourceRange SignatureSourceRange; + const Stmt* Body = Signature->getBody(); + if (const auto FD = dyn_cast<FunctionDecl>(Signature)) { + SignatureSourceRange = FD->getSourceRange(); + } else if (const auto OD = dyn_cast<ObjCMethodDecl>(Signature)) { + SignatureSourceRange = OD->getSourceRange(); + } else { + return; + } + SourceLocation Start = SignatureSourceRange.getBegin(); + SourceLocation End = Body ? Body->getSourceRange().getBegin() + : SignatureSourceRange.getEnd(); + unsigned StartLine = SM.getExpansionLineNumber(Start); + unsigned EndLine = SM.getExpansionLineNumber(End); - MaxBugClassSize.updateMax(bugReports.size()); + FileID FID = SM.getFileID(SM.getExpansionLoc(Start)); + for (unsigned Line = StartLine; Line <= EndLine; Line++) + ExecutedLines->operator[](FID.getHashValue()).insert(Line); +} - if (!generatePathDiagnostic(*D.get(), PD, bugReports)) - return; +static void populateExecutedLinesWithStmt( + const Stmt *S, SourceManager &SM, + std::unique_ptr<FilesToLineNumsMap> &ExecutedLines) { + SourceLocation Loc = S->getSourceRange().getBegin(); + SourceLocation ExpansionLoc = SM.getExpansionLoc(Loc); + FileID FID = SM.getFileID(ExpansionLoc); + unsigned LineNo = SM.getExpansionLineNumber(ExpansionLoc); + ExecutedLines->operator[](FID.getHashValue()).insert(LineNo); +} - MaxValidBugClassSize.updateMax(bugReports.size()); +/// \return all executed lines including function signatures on the path +/// starting from \p N. +static std::unique_ptr<FilesToLineNumsMap> +findExecutedLines(SourceManager &SM, const ExplodedNode *N) { + auto ExecutedLines = llvm::make_unique<FilesToLineNumsMap>(); - // Examine the report and see if the last piece is in a header. Reset the - // report location to the last piece in the main source file. - AnalyzerOptions &Opts = getAnalyzerOptions(); - if (Opts.shouldReportIssuesInMainSourceFile() && !Opts.AnalyzeAll) - D->resetDiagnosticLocationToMainFile(); - } - - // If the path is empty, generate a single step path with the location - // of the issue. - if (D->path.empty()) { - PathDiagnosticLocation L = exampleReport->getLocation(getSourceManager()); - auto piece = llvm::make_unique<PathDiagnosticEventPiece>( - L, exampleReport->getDescription()); - for (SourceRange Range : exampleReport->getRanges()) - piece->addRange(Range); - D->setEndOfPath(std::move(piece)); - } - - PathPieces &Pieces = D->getMutablePieces(); - if (getAnalyzerOptions().shouldDisplayNotesAsEvents()) { - // For path diagnostic consumers that don't support extra notes, - // we may optionally convert those to path notes. - for (auto I = exampleReport->getNotes().rbegin(), - E = exampleReport->getNotes().rend(); I != E; ++I) { - PathDiagnosticNotePiece *Piece = I->get(); - auto ConvertedPiece = std::make_shared<PathDiagnosticEventPiece>( - Piece->getLocation(), Piece->getString()); - for (const auto &R: Piece->getRanges()) - ConvertedPiece->addRange(R); + while (N) { + if (N->getFirstPred() == nullptr) { + // First node: show signature of the entrance point. + const Decl *D = N->getLocationContext()->getDecl(); + populateExecutedLinesWithFunctionSignature(D, SM, ExecutedLines); + } else if (auto CE = N->getLocationAs<CallEnter>()) { + // Inlined function: show signature. + const Decl* D = CE->getCalleeContext()->getDecl(); + populateExecutedLinesWithFunctionSignature(D, SM, ExecutedLines); + } else if (const Stmt *S = PathDiagnosticLocation::getStmt(N)) { + populateExecutedLinesWithStmt(S, SM, ExecutedLines); + + // Show extra context for some parent kinds. + const Stmt *P = N->getParentMap().getParent(S); + + // The path exploration can die before the node with the associated + // return statement is generated, but we do want to show the whole + // return. + if (const auto *RS = dyn_cast_or_null<ReturnStmt>(P)) { + populateExecutedLinesWithStmt(RS, SM, ExecutedLines); + P = N->getParentMap().getParent(RS); + } - Pieces.push_front(std::move(ConvertedPiece)); + if (P && (isa<SwitchCase>(P) || isa<LabelStmt>(P))) + populateExecutedLinesWithStmt(P, SM, ExecutedLines); } - } else { - for (auto I = exampleReport->getNotes().rbegin(), - E = exampleReport->getNotes().rend(); I != E; ++I) - Pieces.push_front(*I); + + N = N->getFirstPred(); } + return ExecutedLines; +} + +std::unique_ptr<DiagnosticForConsumerMapTy> +BugReporter::generateDiagnosticForConsumerMap( + BugReport *report, ArrayRef<PathDiagnosticConsumer *> consumers, + ArrayRef<BugReport *> bugReports) { - // Get the meta data. - const BugReport::ExtraTextList &Meta = exampleReport->getExtraText(); - for (BugReport::ExtraTextList::const_iterator i = Meta.begin(), - e = Meta.end(); i != e; ++i) { - D->addMeta(*i); + if (!report->isPathSensitive()) { + auto Out = llvm::make_unique<DiagnosticForConsumerMapTy>(); + for (auto *Consumer : consumers) + (*Out)[Consumer] = generateEmptyDiagnosticForReport(report, + getSourceManager()); + return Out; } - PD.HandlePathDiagnostic(std::move(D)); + // Generate the full path sensitive diagnostic, using the generation scheme + // specified by the PathDiagnosticConsumer. Note that we have to generate + // path diagnostics even for consumers which do not support paths, because + // the BugReporterVisitors may mark this bug as a false positive. + assert(!bugReports.empty()); + MaxBugClassSize.updateMax(bugReports.size()); + std::unique_ptr<DiagnosticForConsumerMapTy> Out = + generatePathDiagnostics(consumers, bugReports); + + if (Out->empty()) + return Out; + + MaxValidBugClassSize.updateMax(bugReports.size()); + + // Examine the report and see if the last piece is in a header. Reset the + // report location to the last piece in the main source file. + AnalyzerOptions &Opts = getAnalyzerOptions(); + for (auto const &P : *Out) + if (Opts.shouldReportIssuesInMainSourceFile() && !Opts.AnalyzeAll) + P.second->resetDiagnosticLocationToMainFile(); + + return Out; } void BugReporter::EmitBasicReport(const Decl *DeclWithIssue, @@ -3596,12 +3107,12 @@ void BugReporter::EmitBasicReport(const Decl *DeclWithIssue, EmitBasicReport(DeclWithIssue, Checker->getCheckName(), Name, Category, Str, Loc, Ranges); } + void BugReporter::EmitBasicReport(const Decl *DeclWithIssue, CheckName CheckName, StringRef name, StringRef category, StringRef str, PathDiagnosticLocation Loc, ArrayRef<SourceRange> Ranges) { - // 'BT' is owned by BugReporter. BugType *BT = getBugTypeForName(CheckName, name, category); auto R = llvm::make_unique<BugReport>(*BT, str, Loc); @@ -3622,84 +3133,3 @@ BugType *BugReporter::getBugTypeForName(CheckName CheckName, StringRef name, BT = new BugType(CheckName, name, category); return BT; } - -LLVM_DUMP_METHOD void PathPieces::dump() const { - unsigned index = 0; - for (PathPieces::const_iterator I = begin(), E = end(); I != E; ++I) { - llvm::errs() << "[" << index++ << "] "; - (*I)->dump(); - llvm::errs() << "\n"; - } -} - -LLVM_DUMP_METHOD void PathDiagnosticCallPiece::dump() const { - llvm::errs() << "CALL\n--------------\n"; - - if (const Stmt *SLoc = getLocStmt(getLocation())) - SLoc->dump(); - else if (const NamedDecl *ND = dyn_cast<NamedDecl>(getCallee())) - llvm::errs() << *ND << "\n"; - else - getLocation().dump(); -} - -LLVM_DUMP_METHOD void PathDiagnosticEventPiece::dump() const { - llvm::errs() << "EVENT\n--------------\n"; - llvm::errs() << getString() << "\n"; - llvm::errs() << " ---- at ----\n"; - getLocation().dump(); -} - -LLVM_DUMP_METHOD void PathDiagnosticControlFlowPiece::dump() const { - llvm::errs() << "CONTROL\n--------------\n"; - getStartLocation().dump(); - llvm::errs() << " ---- to ----\n"; - getEndLocation().dump(); -} - -LLVM_DUMP_METHOD void PathDiagnosticMacroPiece::dump() const { - llvm::errs() << "MACRO\n--------------\n"; - // FIXME: Print which macro is being invoked. -} - -LLVM_DUMP_METHOD void PathDiagnosticNotePiece::dump() const { - llvm::errs() << "NOTE\n--------------\n"; - llvm::errs() << getString() << "\n"; - llvm::errs() << " ---- at ----\n"; - getLocation().dump(); -} - -LLVM_DUMP_METHOD void PathDiagnosticLocation::dump() const { - if (!isValid()) { - llvm::errs() << "<INVALID>\n"; - return; - } - - switch (K) { - case RangeK: - // FIXME: actually print the range. - llvm::errs() << "<range>\n"; - break; - case SingleLocK: - asLocation().dump(); - llvm::errs() << "\n"; - break; - case StmtK: - if (S) - S->dump(); - else - llvm::errs() << "<NULL STMT>\n"; - break; - case DeclK: - if (const NamedDecl *ND = dyn_cast_or_null<NamedDecl>(D)) - llvm::errs() << *ND << "\n"; - else if (isa<BlockDecl>(D)) - // FIXME: Make this nicer. - llvm::errs() << "<block>\n"; - else if (D) - llvm::errs() << "<unknown decl>\n"; - else - llvm::errs() << "<NULL DECL>\n"; - break; - } -} diff --git a/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 972f4c5f3da2..c87bc685d8b9 100644 --- a/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -1,4 +1,4 @@ -// BugReporterVisitors.cpp - Helpers for reporting bugs -----------*- C++ -*--// +//===- BugReporterVisitors.cpp - Helpers for reporting bugs ---------------===// // // The LLVM Compiler Infrastructure // @@ -11,37 +11,83 @@ // enhance the diagnostics reported for a bug. // //===----------------------------------------------------------------------===// + #include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" +#include "clang/AST/DeclCXX.h" #include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/ExprObjC.h" +#include "clang/AST/Stmt.h" +#include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/Analysis/CFG.h" #include "clang/Analysis/CFGStmtMap.h" +#include "clang/Analysis/ProgramPoint.h" +#include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" +#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConstraintManager.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" +#include <cassert> +#include <deque> +#include <memory> +#include <string> +#include <utility> using namespace clang; using namespace ento; -using llvm::FoldingSetNodeID; - //===----------------------------------------------------------------------===// // Utility functions. //===----------------------------------------------------------------------===// bool bugreporter::isDeclRefExprToReference(const Expr *E) { - if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) { + if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) return DRE->getDecl()->getType()->isReferenceType(); - } return false; } +static const Expr *peelOffPointerArithmetic(const BinaryOperator *B) { + if (B->isAdditiveOp() && B->getType()->isPointerType()) { + if (B->getLHS()->getType()->isPointerType()) { + return B->getLHS(); + } else if (B->getRHS()->getType()->isPointerType()) { + return B->getRHS(); + } + } + return nullptr; +} + /// Given that expression S represents a pointer that would be dereferenced, /// try to find a sub-expression from which the pointer came from. /// This is used for tracking down origins of a null or undefined value: @@ -55,33 +101,27 @@ bool bugreporter::isDeclRefExprToReference(const Expr *E) { /// x->y.z ==> x (lvalue) /// foo()->y.z ==> foo() (rvalue) const Expr *bugreporter::getDerefExpr(const Stmt *S) { - const Expr *E = dyn_cast<Expr>(S); + const auto *E = dyn_cast<Expr>(S); if (!E) return nullptr; while (true) { - if (const CastExpr *CE = dyn_cast<CastExpr>(E)) { + if (const auto *CE = dyn_cast<CastExpr>(E)) { if (CE->getCastKind() == CK_LValueToRValue) { // This cast represents the load we're looking for. break; } E = CE->getSubExpr(); - } else if (const BinaryOperator *B = dyn_cast<BinaryOperator>(E)) { + } else if (const auto *B = dyn_cast<BinaryOperator>(E)) { // Pointer arithmetic: '*(x + 2)' -> 'x') etc. - if (B->getType()->isPointerType()) { - if (B->getLHS()->getType()->isPointerType()) { - E = B->getLHS(); - } else if (B->getRHS()->getType()->isPointerType()) { - E = B->getRHS(); - } else { - break; - } + if (const Expr *Inner = peelOffPointerArithmetic(B)) { + E = Inner; } else { // Probably more arithmetic can be pattern-matched here, // but for now give up. break; } - } else if (const UnaryOperator *U = dyn_cast<UnaryOperator>(E)) { + } else if (const auto *U = dyn_cast<UnaryOperator>(E)) { if (U->getOpcode() == UO_Deref || U->getOpcode() == UO_AddrOf || (U->isIncrementDecrementOp() && U->getType()->isPointerType())) { // Operators '*' and '&' don't actually mean anything. @@ -94,14 +134,16 @@ const Expr *bugreporter::getDerefExpr(const Stmt *S) { } } // Pattern match for a few useful cases: a[0], p->f, *p etc. - else if (const MemberExpr *ME = dyn_cast<MemberExpr>(E)) { + else if (const auto *ME = dyn_cast<MemberExpr>(E)) { E = ME->getBase(); - } else if (const ObjCIvarRefExpr *IvarRef = dyn_cast<ObjCIvarRefExpr>(E)) { + } else if (const auto *IvarRef = dyn_cast<ObjCIvarRefExpr>(E)) { E = IvarRef->getBase(); - } else if (const ArraySubscriptExpr *AE = dyn_cast<ArraySubscriptExpr>(E)) { + } else if (const auto *AE = dyn_cast<ArraySubscriptExpr>(E)) { E = AE->getBase(); - } else if (const ParenExpr *PE = dyn_cast<ParenExpr>(E)) { + } else if (const auto *PE = dyn_cast<ParenExpr>(E)) { E = PE->getSubExpr(); + } else if (const auto *EWC = dyn_cast<ExprWithCleanups>(E)) { + E = EWC->getSubExpr(); } else { // Other arbitrary stuff. break; @@ -111,7 +153,7 @@ const Expr *bugreporter::getDerefExpr(const Stmt *S) { // Special case: remove the final lvalue-to-rvalue cast, but do not recurse // deeper into the sub-expression. This way we return the lvalue from which // our pointer rvalue was loaded. - if (const ImplicitCastExpr *CE = dyn_cast<ImplicitCastExpr>(E)) + if (const auto *CE = dyn_cast<ImplicitCastExpr>(E)) if (CE->getCastKind() == CK_LValueToRValue) E = CE->getSubExpr(); @@ -120,14 +162,14 @@ const Expr *bugreporter::getDerefExpr(const Stmt *S) { const Stmt *bugreporter::GetDenomExpr(const ExplodedNode *N) { const Stmt *S = N->getLocationAs<PreStmt>()->getStmt(); - if (const BinaryOperator *BE = dyn_cast<BinaryOperator>(S)) + if (const auto *BE = dyn_cast<BinaryOperator>(S)) return BE->getRHS(); return nullptr; } const Stmt *bugreporter::GetRetValExpr(const ExplodedNode *N) { const Stmt *S = N->getLocationAs<PostStmt>()->getStmt(); - if (const ReturnStmt *RS = dyn_cast<ReturnStmt>(S)) + if (const auto *RS = dyn_cast<ReturnStmt>(S)) return RS->getRetValue(); return nullptr; } @@ -136,13 +178,18 @@ const Stmt *bugreporter::GetRetValExpr(const ExplodedNode *N) { // Definitions for bug reporter visitors. //===----------------------------------------------------------------------===// -std::unique_ptr<PathDiagnosticPiece> +std::shared_ptr<PathDiagnosticPiece> BugReporterVisitor::getEndPath(BugReporterContext &BRC, const ExplodedNode *EndPathNode, BugReport &BR) { return nullptr; } -std::unique_ptr<PathDiagnosticPiece> BugReporterVisitor::getDefaultEndPath( +void +BugReporterVisitor::finalizeVisitor(BugReporterContext &BRC, + const ExplodedNode *EndPathNode, + BugReport &BR) {} + +std::shared_ptr<PathDiagnosticPiece> BugReporterVisitor::getDefaultEndPath( BugReporterContext &BRC, const ExplodedNode *EndPathNode, BugReport &BR) { PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath(EndPathNode,BRC.getSourceManager()); @@ -151,16 +198,465 @@ std::unique_ptr<PathDiagnosticPiece> BugReporterVisitor::getDefaultEndPath( // Only add the statement itself as a range if we didn't specify any // special ranges for this report. - auto P = llvm::make_unique<PathDiagnosticEventPiece>( + auto P = std::make_shared<PathDiagnosticEventPiece>( L, BR.getDescription(), Ranges.begin() == Ranges.end()); for (SourceRange Range : Ranges) P->addRange(Range); - return std::move(P); + return P; +} + +/// \return name of the macro inside the location \p Loc. +static StringRef getMacroName(SourceLocation Loc, + BugReporterContext &BRC) { + return Lexer::getImmediateMacroName( + Loc, + BRC.getSourceManager(), + BRC.getASTContext().getLangOpts()); +} + +/// \return Whether given spelling location corresponds to an expansion +/// of a function-like macro. +static bool isFunctionMacroExpansion(SourceLocation Loc, + const SourceManager &SM) { + if (!Loc.isMacroID()) + return false; + while (SM.isMacroArgExpansion(Loc)) + Loc = SM.getImmediateExpansionRange(Loc).getBegin(); + std::pair<FileID, unsigned> TLInfo = SM.getDecomposedLoc(Loc); + SrcMgr::SLocEntry SE = SM.getSLocEntry(TLInfo.first); + const SrcMgr::ExpansionInfo &EInfo = SE.getExpansion(); + return EInfo.isFunctionMacroExpansion(); +} + +/// \return Whether \c RegionOfInterest was modified at \p N, +/// where \p ReturnState is a state associated with the return +/// from the current frame. +static bool wasRegionOfInterestModifiedAt( + const SubRegion *RegionOfInterest, + const ExplodedNode *N, + SVal ValueAfter) { + ProgramStateRef State = N->getState(); + ProgramStateManager &Mgr = N->getState()->getStateManager(); + + if (!N->getLocationAs<PostStore>() + && !N->getLocationAs<PostInitializer>() + && !N->getLocationAs<PostStmt>()) + return false; + + // Writing into region of interest. + if (auto PS = N->getLocationAs<PostStmt>()) + if (auto *BO = PS->getStmtAs<BinaryOperator>()) + if (BO->isAssignmentOp() && RegionOfInterest->isSubRegionOf( + N->getSVal(BO->getLHS()).getAsRegion())) + return true; + + // SVal after the state is possibly different. + SVal ValueAtN = N->getState()->getSVal(RegionOfInterest); + if (!Mgr.getSValBuilder().areEqual(State, ValueAtN, ValueAfter).isConstrainedTrue() && + (!ValueAtN.isUndef() || !ValueAfter.isUndef())) + return true; + + return false; } namespace { + +/// Put a diagnostic on return statement of all inlined functions +/// for which the region of interest \p RegionOfInterest was passed into, +/// but not written inside, and it has caused an undefined read or a null +/// pointer dereference outside. +class NoStoreFuncVisitor final : public BugReporterVisitor { + const SubRegion *RegionOfInterest; + static constexpr const char *DiagnosticsMsg = + "Returning without writing to '"; + + /// Frames writing into \c RegionOfInterest. + /// This visitor generates a note only if a function does not write into + /// a region of interest. This information is not immediately available + /// by looking at the node associated with the exit from the function + /// (usually the return statement). To avoid recomputing the same information + /// many times (going up the path for each node and checking whether the + /// region was written into) we instead lazily compute the + /// stack frames along the path which write into the region of interest. + llvm::SmallPtrSet<const StackFrameContext *, 32> FramesModifyingRegion; + llvm::SmallPtrSet<const StackFrameContext *, 32> FramesModifyingCalculated; + +public: + NoStoreFuncVisitor(const SubRegion *R) : RegionOfInterest(R) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int Tag = 0; + ID.AddPointer(&Tag); + } + + std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) override { + + const LocationContext *Ctx = N->getLocationContext(); + const StackFrameContext *SCtx = Ctx->getStackFrame(); + ProgramStateRef State = N->getState(); + auto CallExitLoc = N->getLocationAs<CallExitBegin>(); + + // No diagnostic if region was modified inside the frame. + if (!CallExitLoc) + return nullptr; + + CallEventRef<> Call = + BRC.getStateManager().getCallEventManager().getCaller(SCtx, State); + const PrintingPolicy &PP = BRC.getASTContext().getPrintingPolicy(); + const SourceManager &SM = BRC.getSourceManager(); + + // Region of interest corresponds to an IVar, exiting a method + // which could have written into that IVar, but did not. + if (const auto *MC = dyn_cast<ObjCMethodCall>(Call)) + if (const auto *IvarR = dyn_cast<ObjCIvarRegion>(RegionOfInterest)) + if (potentiallyWritesIntoIvar(Call->getRuntimeDefinition().getDecl(), + IvarR->getDecl()) && + !isRegionOfInterestModifiedInFrame(N)) + return notModifiedMemberDiagnostics( + Ctx, SM, PP, *CallExitLoc, Call, + MC->getReceiverSVal().getAsRegion()); + + if (const auto *CCall = dyn_cast<CXXConstructorCall>(Call)) { + const MemRegion *ThisRegion = CCall->getCXXThisVal().getAsRegion(); + if (RegionOfInterest->isSubRegionOf(ThisRegion) + && !CCall->getDecl()->isImplicit() + && !isRegionOfInterestModifiedInFrame(N)) + return notModifiedMemberDiagnostics(Ctx, SM, PP, *CallExitLoc, + CCall, ThisRegion); + } + + ArrayRef<ParmVarDecl *> parameters = getCallParameters(Call); + for (unsigned I = 0; I < Call->getNumArgs() && I < parameters.size(); ++I) { + const ParmVarDecl *PVD = parameters[I]; + SVal S = Call->getArgSVal(I); + unsigned IndirectionLevel = 1; + QualType T = PVD->getType(); + while (const MemRegion *R = S.getAsRegion()) { + if (RegionOfInterest->isSubRegionOf(R) + && !isPointerToConst(PVD->getType())) { + + if (isRegionOfInterestModifiedInFrame(N)) + return nullptr; + + return notModifiedParameterDiagnostics( + Ctx, SM, PP, *CallExitLoc, Call, PVD, R, IndirectionLevel); + } + QualType PT = T->getPointeeType(); + if (PT.isNull() || PT->isVoidType()) break; + S = State->getSVal(R, PT); + T = PT; + IndirectionLevel++; + } + } + + return nullptr; + } + +private: + + /// \return Whether the method declaration \p Parent + /// syntactically has a binary operation writing into the ivar \p Ivar. + bool potentiallyWritesIntoIvar(const Decl *Parent, + const ObjCIvarDecl *Ivar) { + using namespace ast_matchers; + if (!Parent || !Parent->getBody()) + return false; + StatementMatcher WriteIntoIvarM = binaryOperator( + hasOperatorName("="), hasLHS(ignoringParenImpCasts(objcIvarRefExpr( + hasDeclaration(equalsNode(Ivar)))))); + StatementMatcher ParentM = stmt(hasDescendant(WriteIntoIvarM)); + auto Matches = match(ParentM, *Parent->getBody(), Parent->getASTContext()); + return !Matches.empty(); + } + + /// Check and lazily calculate whether the region of interest is + /// modified in the stack frame to which \p N belongs. + /// The calculation is cached in FramesModifyingRegion. + bool isRegionOfInterestModifiedInFrame(const ExplodedNode *N) { + const LocationContext *Ctx = N->getLocationContext(); + const StackFrameContext *SCtx = Ctx->getStackFrame(); + if (!FramesModifyingCalculated.count(SCtx)) + findModifyingFrames(N); + return FramesModifyingRegion.count(SCtx); + } + + + /// Write to \c FramesModifyingRegion all stack frames along + /// the path in the current stack frame which modify \c RegionOfInterest. + void findModifyingFrames(const ExplodedNode *N) { + assert(N->getLocationAs<CallExitBegin>()); + ProgramStateRef LastReturnState = N->getState(); + SVal ValueAtReturn = LastReturnState->getSVal(RegionOfInterest); + const LocationContext *Ctx = N->getLocationContext(); + const StackFrameContext *OriginalSCtx = Ctx->getStackFrame(); + + do { + ProgramStateRef State = N->getState(); + auto CallExitLoc = N->getLocationAs<CallExitBegin>(); + if (CallExitLoc) { + LastReturnState = State; + ValueAtReturn = LastReturnState->getSVal(RegionOfInterest); + } + + FramesModifyingCalculated.insert( + N->getLocationContext()->getStackFrame()); + + if (wasRegionOfInterestModifiedAt(RegionOfInterest, N, ValueAtReturn)) { + const StackFrameContext *SCtx = N->getStackFrame(); + while (!SCtx->inTopFrame()) { + auto p = FramesModifyingRegion.insert(SCtx); + if (!p.second) + break; // Frame and all its parents already inserted. + SCtx = SCtx->getParent()->getStackFrame(); + } + } + + // Stop calculation at the call to the current function. + if (auto CE = N->getLocationAs<CallEnter>()) + if (CE->getCalleeContext() == OriginalSCtx) + break; + + N = N->getFirstPred(); + } while (N); + } + + /// Get parameters associated with runtime definition in order + /// to get the correct parameter name. + ArrayRef<ParmVarDecl *> getCallParameters(CallEventRef<> Call) { + // Use runtime definition, if available. + RuntimeDefinition RD = Call->getRuntimeDefinition(); + if (const auto *FD = dyn_cast_or_null<FunctionDecl>(RD.getDecl())) + return FD->parameters(); + + return Call->parameters(); + } + + /// \return whether \p Ty points to a const type, or is a const reference. + bool isPointerToConst(QualType Ty) { + return !Ty->getPointeeType().isNull() && + Ty->getPointeeType().getCanonicalType().isConstQualified(); + } + + /// \return Diagnostics piece for the member field not modified + /// in a given function. + std::shared_ptr<PathDiagnosticPiece> notModifiedMemberDiagnostics( + const LocationContext *Ctx, + const SourceManager &SM, + const PrintingPolicy &PP, + CallExitBegin &CallExitLoc, + CallEventRef<> Call, + const MemRegion *ArgRegion) { + const char *TopRegionName = isa<ObjCMethodCall>(Call) ? "self" : "this"; + SmallString<256> sbuf; + llvm::raw_svector_ostream os(sbuf); + os << DiagnosticsMsg; + bool out = prettyPrintRegionName(TopRegionName, "->", /*IsReference=*/true, + /*IndirectionLevel=*/1, ArgRegion, os, PP); + + // Return nothing if we have failed to pretty-print. + if (!out) + return nullptr; + + os << "'"; + PathDiagnosticLocation L = + getPathDiagnosticLocation(CallExitLoc.getReturnStmt(), SM, Ctx, Call); + return std::make_shared<PathDiagnosticEventPiece>(L, os.str()); + } + + /// \return Diagnostics piece for the parameter \p PVD not modified + /// in a given function. + /// \p IndirectionLevel How many times \c ArgRegion has to be dereferenced + /// before we get to the super region of \c RegionOfInterest + std::shared_ptr<PathDiagnosticPiece> + notModifiedParameterDiagnostics(const LocationContext *Ctx, + const SourceManager &SM, + const PrintingPolicy &PP, + CallExitBegin &CallExitLoc, + CallEventRef<> Call, + const ParmVarDecl *PVD, + const MemRegion *ArgRegion, + unsigned IndirectionLevel) { + PathDiagnosticLocation L = getPathDiagnosticLocation( + CallExitLoc.getReturnStmt(), SM, Ctx, Call); + SmallString<256> sbuf; + llvm::raw_svector_ostream os(sbuf); + os << DiagnosticsMsg; + bool IsReference = PVD->getType()->isReferenceType(); + const char *Sep = IsReference && IndirectionLevel == 1 ? "." : "->"; + bool Success = prettyPrintRegionName( + PVD->getQualifiedNameAsString().c_str(), + Sep, IsReference, IndirectionLevel, ArgRegion, os, PP); + + // Print the parameter name if the pretty-printing has failed. + if (!Success) + PVD->printQualifiedName(os); + os << "'"; + return std::make_shared<PathDiagnosticEventPiece>(L, os.str()); + } + + /// \return a path diagnostic location for the optionally + /// present return statement \p RS. + PathDiagnosticLocation getPathDiagnosticLocation(const ReturnStmt *RS, + const SourceManager &SM, + const LocationContext *Ctx, + CallEventRef<> Call) { + if (RS) + return PathDiagnosticLocation::createBegin(RS, SM, Ctx); + return PathDiagnosticLocation( + Call->getRuntimeDefinition().getDecl()->getSourceRange().getEnd(), SM); + } + + /// Pretty-print region \p ArgRegion starting from parent to \p os. + /// \return whether printing has succeeded + bool prettyPrintRegionName(StringRef TopRegionName, + StringRef Sep, + bool IsReference, + int IndirectionLevel, + const MemRegion *ArgRegion, + llvm::raw_svector_ostream &os, + const PrintingPolicy &PP) { + SmallVector<const MemRegion *, 5> Subregions; + const MemRegion *R = RegionOfInterest; + while (R != ArgRegion) { + if (!(isa<FieldRegion>(R) || isa<CXXBaseObjectRegion>(R) || + isa<ObjCIvarRegion>(R))) + return false; // Pattern-matching failed. + Subregions.push_back(R); + R = cast<SubRegion>(R)->getSuperRegion(); + } + bool IndirectReference = !Subregions.empty(); + + if (IndirectReference) + IndirectionLevel--; // Due to "->" symbol. + + if (IsReference) + IndirectionLevel--; // Due to reference semantics. + + bool ShouldSurround = IndirectReference && IndirectionLevel > 0; + + if (ShouldSurround) + os << "("; + for (int i = 0; i < IndirectionLevel; i++) + os << "*"; + os << TopRegionName; + if (ShouldSurround) + os << ")"; + + for (auto I = Subregions.rbegin(), E = Subregions.rend(); I != E; ++I) { + if (const auto *FR = dyn_cast<FieldRegion>(*I)) { + os << Sep; + FR->getDecl()->getDeclName().print(os, PP); + Sep = "."; + } else if (const auto *IR = dyn_cast<ObjCIvarRegion>(*I)) { + os << "->"; + IR->getDecl()->getDeclName().print(os, PP); + Sep = "."; + } else if (isa<CXXBaseObjectRegion>(*I)) { + continue; // Just keep going up to the base region. + } else { + llvm_unreachable("Previous check has missed an unexpected region"); + } + } + return true; + } +}; + +/// Suppress null-pointer-dereference bugs where dereferenced null was returned +/// the macro. +class MacroNullReturnSuppressionVisitor final : public BugReporterVisitor { + const SubRegion *RegionOfInterest; + const SVal ValueAtDereference; + + // Do not invalidate the reports where the value was modified + // after it got assigned to from the macro. + bool WasModified = false; + +public: + MacroNullReturnSuppressionVisitor(const SubRegion *R, + const SVal V) : RegionOfInterest(R), + ValueAtDereference(V) {} + + std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) override { + if (WasModified) + return nullptr; + + auto BugPoint = BR.getErrorNode()->getLocation().getAs<StmtPoint>(); + if (!BugPoint) + return nullptr; + + const SourceManager &SMgr = BRC.getSourceManager(); + if (auto Loc = matchAssignment(N, BRC)) { + if (isFunctionMacroExpansion(*Loc, SMgr)) { + std::string MacroName = getMacroName(*Loc, BRC); + SourceLocation BugLoc = BugPoint->getStmt()->getLocStart(); + if (!BugLoc.isMacroID() || getMacroName(BugLoc, BRC) != MacroName) + BR.markInvalid(getTag(), MacroName.c_str()); + } + } + + if (wasRegionOfInterestModifiedAt(RegionOfInterest, N, ValueAtDereference)) + WasModified = true; + + return nullptr; + } + + static void addMacroVisitorIfNecessary( + const ExplodedNode *N, const MemRegion *R, + bool EnableNullFPSuppression, BugReport &BR, + const SVal V) { + AnalyzerOptions &Options = N->getState()->getStateManager() + .getOwningEngine()->getAnalysisManager().options; + if (EnableNullFPSuppression && Options.shouldSuppressNullReturnPaths() + && V.getAs<Loc>()) + BR.addVisitor(llvm::make_unique<MacroNullReturnSuppressionVisitor>( + R->getAs<SubRegion>(), V)); + } + + void* getTag() const { + static int Tag = 0; + return static_cast<void *>(&Tag); + } + + void Profile(llvm::FoldingSetNodeID &ID) const override { + ID.AddPointer(getTag()); + } + +private: + /// \return Source location of right hand side of an assignment + /// into \c RegionOfInterest, empty optional if none found. + Optional<SourceLocation> matchAssignment(const ExplodedNode *N, + BugReporterContext &BRC) { + const Stmt *S = PathDiagnosticLocation::getStmt(N); + ProgramStateRef State = N->getState(); + auto *LCtx = N->getLocationContext(); + if (!S) + return None; + + if (const auto *DS = dyn_cast<DeclStmt>(S)) { + if (const auto *VD = dyn_cast<VarDecl>(DS->getSingleDecl())) + if (const Expr *RHS = VD->getInit()) + if (RegionOfInterest->isSubRegionOf( + State->getLValue(VD, LCtx).getAsRegion())) + return RHS->getLocStart(); + } else if (const auto *BO = dyn_cast<BinaryOperator>(S)) { + const MemRegion *R = N->getSVal(BO->getLHS()).getAsRegion(); + const Expr *RHS = BO->getRHS(); + if (BO->isAssignmentOp() && RegionOfInterest->isSubRegionOf(R)) { + return RHS->getLocStart(); + } + } + return None; + } +}; + /// Emits an extra note at the return statement of an interesting stack frame. /// /// The returned value is marked as an interesting value, and if it's null, @@ -168,19 +664,20 @@ namespace { /// /// This visitor is intended to be used when another visitor discovers that an /// interesting value comes from an inlined function call. -class ReturnVisitor : public BugReporterVisitorImpl<ReturnVisitor> { +class ReturnVisitor : public BugReporterVisitor { const StackFrameContext *StackFrame; enum { Initial, MaybeUnsuppress, Satisfied - } Mode; + } Mode = Initial; bool EnableNullFPSuppression; + bool ShouldInvalidate = true; public: ReturnVisitor(const StackFrameContext *Frame, bool Suppressed) - : StackFrame(Frame), Mode(Initial), EnableNullFPSuppression(Suppressed) {} + : StackFrame(Frame), EnableNullFPSuppression(Suppressed) {} static void *getTag() { static int Tag = 0; @@ -235,7 +732,7 @@ public: // Check the return value. ProgramStateRef State = Node->getState(); - SVal RetVal = State->getSVal(S, Node->getLocationContext()); + SVal RetVal = Node->getSVal(S); // Handle cases where a reference is returned and then immediately used. if (cast<Expr>(S)->isGLValue()) @@ -274,7 +771,7 @@ public: if (!SP) return nullptr; - const ReturnStmt *Ret = dyn_cast<ReturnStmt>(SP->getStmt()); + const auto *Ret = dyn_cast<ReturnStmt>(SP->getStmt()); if (!Ret) return nullptr; @@ -329,8 +826,7 @@ public: // If we have counter-suppression enabled, make sure we keep visiting // future nodes. We want to emit a path note as well, in case // the report is resurrected as valid later on. - ExprEngine &Eng = BRC.getBugReporter().getEngine(); - AnalyzerOptions &Options = Eng.getAnalysisManager().options; + AnalyzerOptions &Options = BRC.getAnalyzerOptions(); if (EnableNullFPSuppression && hasCounterSuppression(Options)) Mode = MaybeUnsuppress; @@ -352,8 +848,8 @@ public: } } else { // FIXME: We should have a more generalized location printing mechanism. - if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(RetE)) - if (const DeclaratorDecl *DD = dyn_cast<DeclaratorDecl>(DR->getDecl())) + if (const auto *DR = dyn_cast<DeclRefExpr>(RetE)) + if (const auto *DD = dyn_cast<DeclaratorDecl>(DR->getDecl())) Out << " (loaded from '" << *DD << "')"; } @@ -368,8 +864,7 @@ public: visitNodeMaybeUnsuppress(const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) { #ifndef NDEBUG - ExprEngine &Eng = BRC.getBugReporter().getEngine(); - AnalyzerOptions &Options = Eng.getAnalysisManager().options; + AnalyzerOptions &Options = BRC.getAnalyzerOptions(); assert(hasCounterSuppression(Options)); #endif @@ -406,7 +901,7 @@ public: if (bugreporter::trackNullOrUndefValue(N, ArgE, BR, /*IsArg=*/true, EnableNullFPSuppression)) - BR.removeInvalidation(ReturnVisitor::getTag(), StackFrame); + ShouldInvalidate = false; // If we /can't/ track the null pointer, we should err on the side of // false negatives, and continue towards marking this report invalid. @@ -432,18 +927,16 @@ public: llvm_unreachable("Invalid visit mode!"); } - std::unique_ptr<PathDiagnosticPiece> getEndPath(BugReporterContext &BRC, - const ExplodedNode *N, - BugReport &BR) override { - if (EnableNullFPSuppression) + void finalizeVisitor(BugReporterContext &BRC, const ExplodedNode *N, + BugReport &BR) override { + if (EnableNullFPSuppression && ShouldInvalidate) BR.markInvalid(ReturnVisitor::getTag(), StackFrame); - return nullptr; } }; -} // end anonymous namespace +} // namespace -void FindLastStoreBRVisitor ::Profile(llvm::FoldingSetNodeID &ID) const { +void FindLastStoreBRVisitor::Profile(llvm::FoldingSetNodeID &ID) const { static int tag = 0; ID.AddPointer(&tag); ID.AddPointer(R); @@ -466,7 +959,7 @@ static bool isInitializationOfVar(const ExplodedNode *N, const VarRegion *VR) { return false; const MemSpaceRegion *VarSpace = VR->getMemorySpace(); - const StackSpaceRegion *FrameSpace = dyn_cast<StackSpaceRegion>(VarSpace); + const auto *FrameSpace = dyn_cast<StackSpaceRegion>(VarSpace); if (!FrameSpace) { // If we ever directly evaluate global DeclStmts, this assertion will be // invalid, but this still seems preferable to silently accepting an @@ -477,14 +970,131 @@ static bool isInitializationOfVar(const ExplodedNode *N, const VarRegion *VR) { assert(VR->getDecl()->hasLocalStorage()); const LocationContext *LCtx = N->getLocationContext(); - return FrameSpace->getStackFrame() == LCtx->getCurrentStackFrame(); + return FrameSpace->getStackFrame() == LCtx->getStackFrame(); +} + +/// Show diagnostics for initializing or declaring a region \p R with a bad value. +static void showBRDiagnostics(const char *action, llvm::raw_svector_ostream &os, + const MemRegion *R, SVal V, const DeclStmt *DS) { + if (R->canPrintPretty()) { + R->printPretty(os); + os << " "; + } + + if (V.getAs<loc::ConcreteInt>()) { + bool b = false; + if (R->isBoundable()) { + if (const auto *TR = dyn_cast<TypedValueRegion>(R)) { + if (TR->getValueType()->isObjCObjectPointerType()) { + os << action << "nil"; + b = true; + } + } + } + if (!b) + os << action << "a null pointer value"; + + } else if (auto CVal = V.getAs<nonloc::ConcreteInt>()) { + os << action << CVal->getValue(); + } else if (DS) { + if (V.isUndef()) { + if (isa<VarRegion>(R)) { + const auto *VD = cast<VarDecl>(DS->getSingleDecl()); + if (VD->getInit()) { + os << (R->canPrintPretty() ? "initialized" : "Initializing") + << " to a garbage value"; + } else { + os << (R->canPrintPretty() ? "declared" : "Declaring") + << " without an initial value"; + } + } + } else { + os << (R->canPrintPretty() ? "initialized" : "Initialized") + << " here"; + } + } +} + +/// Display diagnostics for passing bad region as a parameter. +static void showBRParamDiagnostics(llvm::raw_svector_ostream& os, + const VarRegion *VR, + SVal V) { + const auto *Param = cast<ParmVarDecl>(VR->getDecl()); + + os << "Passing "; + + if (V.getAs<loc::ConcreteInt>()) { + if (Param->getType()->isObjCObjectPointerType()) + os << "nil object reference"; + else + os << "null pointer value"; + } else if (V.isUndef()) { + os << "uninitialized value"; + } else if (auto CI = V.getAs<nonloc::ConcreteInt>()) { + os << "the value " << CI->getValue(); + } else { + os << "value"; + } + + // Printed parameter indexes are 1-based, not 0-based. + unsigned Idx = Param->getFunctionScopeIndex() + 1; + os << " via " << Idx << llvm::getOrdinalSuffix(Idx) << " parameter"; + if (VR->canPrintPretty()) { + os << " "; + VR->printPretty(os); + } +} + +/// Show default diagnostics for storing bad region. +static void showBRDefaultDiagnostics(llvm::raw_svector_ostream& os, + const MemRegion *R, + SVal V) { + if (V.getAs<loc::ConcreteInt>()) { + bool b = false; + if (R->isBoundable()) { + if (const auto *TR = dyn_cast<TypedValueRegion>(R)) { + if (TR->getValueType()->isObjCObjectPointerType()) { + os << "nil object reference stored"; + b = true; + } + } + } + if (!b) { + if (R->canPrintPretty()) + os << "Null pointer value stored"; + else + os << "Storing null pointer value"; + } + + } else if (V.isUndef()) { + if (R->canPrintPretty()) + os << "Uninitialized value stored"; + else + os << "Storing uninitialized value"; + + } else if (auto CV = V.getAs<nonloc::ConcreteInt>()) { + if (R->canPrintPretty()) + os << "The value " << CV->getValue() << " is assigned"; + else + os << "Assigning " << CV->getValue(); + + } else { + if (R->canPrintPretty()) + os << "Value assigned"; + else + os << "Assigning value"; + } + + if (R->canPrintPretty()) { + os << " to "; + R->printPretty(os); + } } std::shared_ptr<PathDiagnosticPiece> FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, const ExplodedNode *Pred, BugReporterContext &BRC, BugReport &BR) { - if (Satisfied) return nullptr; @@ -493,7 +1103,7 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, bool IsParam = false; // First see if we reached the declaration of the region. - if (const VarRegion *VR = dyn_cast<VarRegion>(R)) { + if (const auto *VR = dyn_cast<VarRegion>(R)) { if (isInitializationOfVar(Pred, VR)) { StoreSite = Pred; InitE = VR->getDecl()->getInit(); @@ -539,8 +1149,8 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, // 'this' should never be NULL, but this visitor isn't just for NULL and // UndefinedVal.) if (Optional<CallEnter> CE = Succ->getLocationAs<CallEnter>()) { - if (const VarRegion *VR = dyn_cast<VarRegion>(R)) { - const ParmVarDecl *Param = cast<ParmVarDecl>(VR->getDecl()); + if (const auto *VR = dyn_cast<VarRegion>(R)) { + const auto *Param = cast<ParmVarDecl>(VR->getDecl()); ProgramStateManager &StateMgr = BRC.getStateManager(); CallEventManager &CallMgr = StateMgr.getCallEventManager(); @@ -554,7 +1164,7 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, // If this is a CXXTempObjectRegion, the Expr responsible for its creation // is wrapped inside of it. - if (const CXXTempObjectRegion *TmpR = dyn_cast<CXXTempObjectRegion>(R)) + if (const auto *TmpR = dyn_cast<CXXTempObjectRegion>(R)) InitE = TmpR->getExpr(); } @@ -584,8 +1194,8 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, if (Optional<PostStmt> PS = StoreSite->getLocationAs<PostStmt>()) { const Stmt *S = PS->getStmt(); const char *action = nullptr; - const DeclStmt *DS = dyn_cast<DeclStmt>(S); - const VarRegion *VR = dyn_cast<VarRegion>(R); + const auto *DS = dyn_cast<DeclStmt>(S); + const auto *VR = dyn_cast<VarRegion>(R); if (DS) { action = R->canPrintPretty() ? "initialized to " : @@ -596,8 +1206,8 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, if (VR) { // See if we can get the BlockVarRegion. ProgramStateRef State = StoreSite->getState(); - SVal V = State->getSVal(S, PS->getLocationContext()); - if (const BlockDataRegion *BDR = + SVal V = StoreSite->getSVal(S); + if (const auto *BDR = dyn_cast_or_null<BlockDataRegion>(V.getAsRegion())) { if (const VarRegion *OriginalR = BDR->getOriginalRegion(VR)) { if (Optional<KnownSVal> KV = @@ -608,122 +1218,16 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, } } } + if (action) + showBRDiagnostics(action, os, R, V, DS); - if (action) { - if (R->canPrintPretty()) { - R->printPretty(os); - os << " "; - } - - if (V.getAs<loc::ConcreteInt>()) { - bool b = false; - if (R->isBoundable()) { - if (const TypedValueRegion *TR = dyn_cast<TypedValueRegion>(R)) { - if (TR->getValueType()->isObjCObjectPointerType()) { - os << action << "nil"; - b = true; - } - } - } - - if (!b) - os << action << "a null pointer value"; - } else if (Optional<nonloc::ConcreteInt> CVal = - V.getAs<nonloc::ConcreteInt>()) { - os << action << CVal->getValue(); - } - else if (DS) { - if (V.isUndef()) { - if (isa<VarRegion>(R)) { - const VarDecl *VD = cast<VarDecl>(DS->getSingleDecl()); - if (VD->getInit()) { - os << (R->canPrintPretty() ? "initialized" : "Initializing") - << " to a garbage value"; - } else { - os << (R->canPrintPretty() ? "declared" : "Declaring") - << " without an initial value"; - } - } - } - else { - os << (R->canPrintPretty() ? "initialized" : "Initialized") - << " here"; - } - } - } } else if (StoreSite->getLocation().getAs<CallEnter>()) { - if (const VarRegion *VR = dyn_cast<VarRegion>(R)) { - const ParmVarDecl *Param = cast<ParmVarDecl>(VR->getDecl()); - - os << "Passing "; - - if (V.getAs<loc::ConcreteInt>()) { - if (Param->getType()->isObjCObjectPointerType()) - os << "nil object reference"; - else - os << "null pointer value"; - } else if (V.isUndef()) { - os << "uninitialized value"; - } else if (Optional<nonloc::ConcreteInt> CI = - V.getAs<nonloc::ConcreteInt>()) { - os << "the value " << CI->getValue(); - } else { - os << "value"; - } - - // Printed parameter indexes are 1-based, not 0-based. - unsigned Idx = Param->getFunctionScopeIndex() + 1; - os << " via " << Idx << llvm::getOrdinalSuffix(Idx) << " parameter"; - if (R->canPrintPretty()) { - os << " "; - R->printPretty(os); - } - } + if (const auto *VR = dyn_cast<VarRegion>(R)) + showBRParamDiagnostics(os, VR, V); } - if (os.str().empty()) { - if (V.getAs<loc::ConcreteInt>()) { - bool b = false; - if (R->isBoundable()) { - if (const TypedValueRegion *TR = dyn_cast<TypedValueRegion>(R)) { - if (TR->getValueType()->isObjCObjectPointerType()) { - os << "nil object reference stored"; - b = true; - } - } - } - if (!b) { - if (R->canPrintPretty()) - os << "Null pointer value stored"; - else - os << "Storing null pointer value"; - } - - } else if (V.isUndef()) { - if (R->canPrintPretty()) - os << "Uninitialized value stored"; - else - os << "Storing uninitialized value"; - - } else if (Optional<nonloc::ConcreteInt> CV = - V.getAs<nonloc::ConcreteInt>()) { - if (R->canPrintPretty()) - os << "The value " << CV->getValue() << " is assigned"; - else - os << "Assigning " << CV->getValue(); - - } else { - if (R->canPrintPretty()) - os << "Value assigned"; - else - os << "Assigning value"; - } - - if (R->canPrintPretty()) { - os << " to "; - R->printPretty(os); - } - } + if (os.str().empty()) + showBRDefaultDiagnostics(os, R, V); // Construct a new PathDiagnosticPiece. ProgramPoint P = StoreSite->getLocation(); @@ -778,7 +1282,6 @@ TrackConstraintBRVisitor::VisitNode(const ExplodedNode *N, // Check if in the previous state it was feasible for this constraint // to *not* be true. if (isUnderconstrained(PrevN)) { - IsSatisfied = true; // As a sanity check, make sure that the negation of the constraint @@ -816,20 +1319,20 @@ TrackConstraintBRVisitor::VisitNode(const ExplodedNode *N, SuppressInlineDefensiveChecksVisitor:: SuppressInlineDefensiveChecksVisitor(DefinedSVal Value, const ExplodedNode *N) - : V(Value), IsSatisfied(false), IsTrackingTurnedOn(false) { - - // Check if the visitor is disabled. - SubEngine *Eng = N->getState()->getStateManager().getOwningEngine(); - assert(Eng && "Cannot file a bug report without an owning engine"); - AnalyzerOptions &Options = Eng->getAnalysisManager().options; - if (!Options.shouldSuppressInlinedDefensiveChecks()) - IsSatisfied = true; + : V(Value) { + // Check if the visitor is disabled. + SubEngine *Eng = N->getState()->getStateManager().getOwningEngine(); + assert(Eng && "Cannot file a bug report without an owning engine"); + AnalyzerOptions &Options = Eng->getAnalysisManager().options; + if (!Options.shouldSuppressInlinedDefensiveChecks()) + IsSatisfied = true; - assert(N->getState()->isNull(V).isConstrainedTrue() && - "The visitor only tracks the cases where V is constrained to 0"); + assert(N->getState()->isNull(V).isConstrainedTrue() && + "The visitor only tracks the cases where V is constrained to 0"); } -void SuppressInlineDefensiveChecksVisitor::Profile(FoldingSetNodeID &ID) const { +void SuppressInlineDefensiveChecksVisitor::Profile( + llvm::FoldingSetNodeID &ID) const { static int id = 0; ID.AddPointer(&id); ID.Add(V); @@ -878,10 +1381,6 @@ SuppressInlineDefensiveChecksVisitor::VisitNode(const ExplodedNode *Succ, if (!BugPoint) return nullptr; - SourceLocation BugLoc = BugPoint->getStmt()->getLocStart(); - if (BugLoc.isMacroID()) - return nullptr; - ProgramPoint CurPoint = Succ->getLocation(); const Stmt *CurTerminatorStmt = nullptr; if (auto BE = CurPoint.getAs<BlockEdge>()) { @@ -902,14 +1401,14 @@ SuppressInlineDefensiveChecksVisitor::VisitNode(const ExplodedNode *Succ, SourceLocation TerminatorLoc = CurTerminatorStmt->getLocStart(); if (TerminatorLoc.isMacroID()) { - const SourceManager &SMgr = BRC.getSourceManager(); - std::pair<FileID, unsigned> TLInfo = SMgr.getDecomposedLoc(TerminatorLoc); - SrcMgr::SLocEntry SE = SMgr.getSLocEntry(TLInfo.first); - const SrcMgr::ExpansionInfo &EInfo = SE.getExpansion(); - if (EInfo.isFunctionMacroExpansion()) { + SourceLocation BugLoc = BugPoint->getStmt()->getLocStart(); + + // Suppress reports unless we are in that same macro. + if (!BugLoc.isMacroID() || + getMacroName(BugLoc, BRC) != getMacroName(TerminatorLoc, BRC)) { BR.markInvalid("Suppress Macro IDC", CurLC); - return nullptr; } + return nullptr; } } return nullptr; @@ -917,8 +1416,8 @@ SuppressInlineDefensiveChecksVisitor::VisitNode(const ExplodedNode *Succ, static const MemRegion *getLocationRegionIfReference(const Expr *E, const ExplodedNode *N) { - if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(E)) { - if (const VarDecl *VD = dyn_cast<VarDecl>(DR->getDecl())) { + if (const auto *DR = dyn_cast<DeclRefExpr>(E)) { + if (const auto *VD = dyn_cast<VarDecl>(DR->getDecl())) { if (!VD->getType()->isReferenceType()) return nullptr; ProgramStateManager &StateMgr = N->getState()->getStateManager(); @@ -939,12 +1438,12 @@ static const MemRegion *getLocationRegionIfReference(const Expr *E, static const Expr *peelOffOuterExpr(const Expr *Ex, const ExplodedNode *N) { Ex = Ex->IgnoreParenCasts(); - if (const ExprWithCleanups *EWC = dyn_cast<ExprWithCleanups>(Ex)) + if (const auto *EWC = dyn_cast<ExprWithCleanups>(Ex)) return peelOffOuterExpr(EWC->getSubExpr(), N); - if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(Ex)) + if (const auto *OVE = dyn_cast<OpaqueValueExpr>(Ex)) return peelOffOuterExpr(OVE->getSourceExpr(), N); - if (auto *POE = dyn_cast<PseudoObjectExpr>(Ex)) { - auto *PropRef = dyn_cast<ObjCPropertyRefExpr>(POE->getSyntacticForm()); + if (const auto *POE = dyn_cast<PseudoObjectExpr>(Ex)) { + const auto *PropRef = dyn_cast<ObjCPropertyRefExpr>(POE->getSyntacticForm()); if (PropRef && PropRef->isMessagingGetter()) { const Expr *GetterMessageSend = POE->getSemanticExpr(POE->getNumSemanticExprs() - 1); @@ -954,7 +1453,7 @@ static const Expr *peelOffOuterExpr(const Expr *Ex, } // Peel off the ternary operator. - if (const ConditionalOperator *CO = dyn_cast<ConditionalOperator>(Ex)) { + if (const auto *CO = dyn_cast<ConditionalOperator>(Ex)) { // Find a node where the branching occurred and find out which branch // we took (true/false) by looking at the ExplodedGraph. const ExplodedNode *NI = N; @@ -975,6 +1474,68 @@ static const Expr *peelOffOuterExpr(const Expr *Ex, NI = NI->getFirstPred(); } while (NI); } + + if (auto *BO = dyn_cast<BinaryOperator>(Ex)) + if (const Expr *SubEx = peelOffPointerArithmetic(BO)) + return peelOffOuterExpr(SubEx, N); + + return Ex; +} + +/// Walk through nodes until we get one that matches the statement exactly. +/// Alternately, if we hit a known lvalue for the statement, we know we've +/// gone too far (though we can likely track the lvalue better anyway). +static const ExplodedNode* findNodeForStatement(const ExplodedNode *N, + const Stmt *S, + const Expr *Inner) { + do { + const ProgramPoint &pp = N->getLocation(); + if (auto ps = pp.getAs<StmtPoint>()) { + if (ps->getStmt() == S || ps->getStmt() == Inner) + break; + } else if (auto CEE = pp.getAs<CallExitEnd>()) { + if (CEE->getCalleeContext()->getCallSite() == S || + CEE->getCalleeContext()->getCallSite() == Inner) + break; + } + N = N->getFirstPred(); + } while (N); + return N; +} + +/// Find the ExplodedNode where the lvalue (the value of 'Ex') +/// was computed. +static const ExplodedNode* findNodeForExpression(const ExplodedNode *N, + const Expr *Inner) { + while (N) { + if (auto P = N->getLocation().getAs<PostStmt>()) { + if (P->getStmt() == Inner) + break; + } + N = N->getFirstPred(); + } + assert(N && "Unable to find the lvalue node."); + return N; +} + +/// Performing operator `&' on an lvalue expression is essentially a no-op. +/// Then, if we are taking addresses of fields or elements, these are also +/// unlikely to matter. +static const Expr* peelOfOuterAddrOf(const Expr* Ex) { + Ex = Ex->IgnoreParenCasts(); + + // FIXME: There's a hack in our Store implementation that always computes + // field offsets around null pointers as if they are always equal to 0. + // The idea here is to report accesses to fields as null dereferences + // even though the pointer value that's being dereferenced is actually + // the offset of the field rather than exactly 0. + // See the FIXME in StoreManager's getLValueFieldOrIvar() method. + // This code interacts heavily with this hack; otherwise the value + // would not be null at all for most fields, so we'd be unable to track it. + if (const auto *Op = dyn_cast<UnaryOperator>(Ex)) + if (Op->getOpcode() == UO_AddrOf && Op->getSubExpr()->isLValue()) + if (const Expr *DerefEx = bugreporter::getDerefExpr(Op->getSubExpr())) + return DerefEx; return Ex; } @@ -985,52 +1546,23 @@ bool bugreporter::trackNullOrUndefValue(const ExplodedNode *N, if (!S || !N) return false; - if (const Expr *Ex = dyn_cast<Expr>(S)) + if (const auto *Ex = dyn_cast<Expr>(S)) S = peelOffOuterExpr(Ex, N); const Expr *Inner = nullptr; - if (const Expr *Ex = dyn_cast<Expr>(S)) { + if (const auto *Ex = dyn_cast<Expr>(S)) { + Ex = peelOfOuterAddrOf(Ex); Ex = Ex->IgnoreParenCasts(); - // Performing operator `&' on an lvalue expression is essentially a no-op. - // Then, if we are taking addresses of fields or elements, these are also - // unlikely to matter. - // FIXME: There's a hack in our Store implementation that always computes - // field offsets around null pointers as if they are always equal to 0. - // The idea here is to report accesses to fields as null dereferences - // even though the pointer value that's being dereferenced is actually - // the offset of the field rather than exactly 0. - // See the FIXME in StoreManager's getLValueFieldOrIvar() method. - // This code interacts heavily with this hack; otherwise the value - // would not be null at all for most fields, so we'd be unable to track it. - if (const auto *Op = dyn_cast<UnaryOperator>(Ex)) - if (Op->getOpcode() == UO_AddrOf && Op->getSubExpr()->isLValue()) - if (const Expr *DerefEx = getDerefExpr(Op->getSubExpr())) - Ex = DerefEx; - - if (Ex && (ExplodedGraph::isInterestingLValueExpr(Ex) || CallEvent::isCallStmt(Ex))) + if (Ex && (ExplodedGraph::isInterestingLValueExpr(Ex) + || CallEvent::isCallStmt(Ex))) Inner = Ex; } if (IsArg && !Inner) { assert(N->getLocation().getAs<CallEnter>() && "Tracking arg but not at call"); } else { - // Walk through nodes until we get one that matches the statement exactly. - // Alternately, if we hit a known lvalue for the statement, we know we've - // gone too far (though we can likely track the lvalue better anyway). - do { - const ProgramPoint &pp = N->getLocation(); - if (Optional<StmtPoint> ps = pp.getAs<StmtPoint>()) { - if (ps->getStmt() == S || ps->getStmt() == Inner) - break; - } else if (Optional<CallExitEnd> CEE = pp.getAs<CallExitEnd>()) { - if (CEE->getCalleeContext()->getCallSite() == S || - CEE->getCalleeContext()->getCallSite() == Inner) - break; - } - N = N->getFirstPred(); - } while (N); - + N = findNodeForStatement(N, S, Inner); if (!N) return false; } @@ -1041,51 +1573,43 @@ bool bugreporter::trackNullOrUndefValue(const ExplodedNode *N, // At this point in the path, the receiver should be live since we are at the // message send expr. If it is nil, start tracking it. if (const Expr *Receiver = NilReceiverBRVisitor::getNilReceiver(S, N)) - trackNullOrUndefValue(N, Receiver, report, false, EnableNullFPSuppression); - + trackNullOrUndefValue(N, Receiver, report, /* IsArg=*/ false, + EnableNullFPSuppression); // See if the expression we're interested refers to a variable. // If so, we can track both its contents and constraints on its value. if (Inner && ExplodedGraph::isInterestingLValueExpr(Inner)) { - const MemRegion *R = nullptr; - - // Find the ExplodedNode where the lvalue (the value of 'Ex') - // was computed. We need this for getting the location value. - const ExplodedNode *LVNode = N; - while (LVNode) { - if (Optional<PostStmt> P = LVNode->getLocation().getAs<PostStmt>()) { - if (P->getStmt() == Inner) - break; - } - LVNode = LVNode->getFirstPred(); - } - assert(LVNode && "Unable to find the lvalue node."); + const ExplodedNode *LVNode = findNodeForExpression(N, Inner); ProgramStateRef LVState = LVNode->getState(); - SVal LVal = LVState->getSVal(Inner, LVNode->getLocationContext()); - - if (LVState->isNull(LVal).isConstrainedTrue()) { - // In case of C++ references, we want to differentiate between a null - // reference and reference to null pointer. - // If the LVal is null, check if we are dealing with null reference. - // For those, we want to track the location of the reference. - if (const MemRegion *RR = getLocationRegionIfReference(Inner, N)) - R = RR; - } else { - R = LVState->getSVal(Inner, LVNode->getLocationContext()).getAsRegion(); - - // If this is a C++ reference to a null pointer, we are tracking the - // pointer. In addition, we should find the store at which the reference - // got initialized. - if (const MemRegion *RR = getLocationRegionIfReference(Inner, N)) { - if (Optional<KnownSVal> KV = LVal.getAs<KnownSVal>()) - report.addVisitor(llvm::make_unique<FindLastStoreBRVisitor>( + SVal LVal = LVNode->getSVal(Inner); + + const MemRegion *RR = getLocationRegionIfReference(Inner, N); + bool LVIsNull = LVState->isNull(LVal).isConstrainedTrue(); + + // If this is a C++ reference to a null pointer, we are tracking the + // pointer. In addition, we should find the store at which the reference + // got initialized. + if (RR && !LVIsNull) { + if (auto KV = LVal.getAs<KnownSVal>()) + report.addVisitor(llvm::make_unique<FindLastStoreBRVisitor>( *KV, RR, EnableNullFPSuppression)); - } } + // In case of C++ references, we want to differentiate between a null + // reference and reference to null pointer. + // If the LVal is null, check if we are dealing with null reference. + // For those, we want to track the location of the reference. + const MemRegion *R = (RR && LVIsNull) ? RR : + LVNode->getSVal(Inner).getAsRegion(); + if (R) { // Mark both the variable region and its contents as interesting. SVal V = LVState->getRawSVal(loc::MemRegionVal(R)); + report.addVisitor( + llvm::make_unique<NoStoreFuncVisitor>(cast<SubRegion>(R))); + + MacroNullReturnSuppressionVisitor::addMacroVisitorIfNecessary( + N, R, EnableNullFPSuppression, report, V); report.markInteresting(R); report.markInteresting(V); @@ -1094,21 +1618,21 @@ bool bugreporter::trackNullOrUndefValue(const ExplodedNode *N, // If the contents are symbolic, find out when they became null. if (V.getAsLocSymbol(/*IncludeBaseRegions*/ true)) report.addVisitor(llvm::make_unique<TrackConstraintBRVisitor>( - V.castAs<DefinedSVal>(), false)); + V.castAs<DefinedSVal>(), false)); // Add visitor, which will suppress inline defensive checks. - if (Optional<DefinedSVal> DV = V.getAs<DefinedSVal>()) { + if (auto DV = V.getAs<DefinedSVal>()) { if (!DV->isZeroConstant() && LVState->isNull(*DV).isConstrainedTrue() && EnableNullFPSuppression) { report.addVisitor( llvm::make_unique<SuppressInlineDefensiveChecksVisitor>(*DV, - LVNode)); + LVNode)); } } - if (Optional<KnownSVal> KV = V.getAs<KnownSVal>()) + if (auto KV = V.getAs<KnownSVal>()) report.addVisitor(llvm::make_unique<FindLastStoreBRVisitor>( - *KV, R, EnableNullFPSuppression)); + *KV, R, EnableNullFPSuppression)); return true; } } @@ -1119,7 +1643,7 @@ bool bugreporter::trackNullOrUndefValue(const ExplodedNode *N, // If the value came from an inlined function call, we should at least make // sure that function isn't pruned in our output. - if (const Expr *E = dyn_cast<Expr>(S)) + if (const auto *E = dyn_cast<Expr>(S)) S = E->IgnoreParenCasts(); ReturnVisitor::addVisitorIfNecessary(N, S, report, EnableNullFPSuppression); @@ -1128,40 +1652,40 @@ bool bugreporter::trackNullOrUndefValue(const ExplodedNode *N, // base value that was dereferenced. // assert(!V.isUnknownOrUndef()); // Is it a symbolic value? - if (Optional<loc::MemRegionVal> L = V.getAs<loc::MemRegionVal>()) { + if (auto L = V.getAs<loc::MemRegionVal>()) { + report.addVisitor(llvm::make_unique<UndefOrNullArgVisitor>(L->getRegion())); + // At this point we are dealing with the region's LValue. // However, if the rvalue is a symbolic region, we should track it as well. // Try to use the correct type when looking up the value. SVal RVal; - if (const Expr *E = dyn_cast<Expr>(S)) + if (const auto *E = dyn_cast<Expr>(S)) RVal = state->getRawSVal(L.getValue(), E->getType()); else RVal = state->getSVal(L->getRegion()); - report.addVisitor(llvm::make_unique<UndefOrNullArgVisitor>(L->getRegion())); - if (Optional<KnownSVal> KV = RVal.getAs<KnownSVal>()) + if (auto KV = RVal.getAs<KnownSVal>()) report.addVisitor(llvm::make_unique<FindLastStoreBRVisitor>( - *KV, L->getRegion(), EnableNullFPSuppression)); + *KV, L->getRegion(), EnableNullFPSuppression)); const MemRegion *RegionRVal = RVal.getAsRegion(); if (RegionRVal && isa<SymbolicRegion>(RegionRVal)) { report.markInteresting(RegionRVal); report.addVisitor(llvm::make_unique<TrackConstraintBRVisitor>( - loc::MemRegionVal(RegionRVal), false)); + loc::MemRegionVal(RegionRVal), false)); } } - return true; } const Expr *NilReceiverBRVisitor::getNilReceiver(const Stmt *S, const ExplodedNode *N) { - const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S); + const auto *ME = dyn_cast<ObjCMessageExpr>(S); if (!ME) return nullptr; if (const Expr *Receiver = ME->getInstanceReceiver()) { ProgramStateRef state = N->getState(); - SVal V = state->getSVal(Receiver, N->getLocationContext()); + SVal V = N->getSVal(Receiver); if (state->isNull(V).isConstrainedTrue()) return Receiver; } @@ -1184,7 +1708,7 @@ NilReceiverBRVisitor::VisitNode(const ExplodedNode *N, llvm::SmallString<256> Buf; llvm::raw_svector_ostream OS(Buf); - if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) { + if (const auto *ME = dyn_cast<ObjCMessageExpr>(S)) { OS << "'"; ME->getSelector().print(OS); OS << "' not called"; @@ -1217,16 +1741,15 @@ void FindLastStoreBRVisitor::registerStatementVarDecls(BugReport &BR, const Stmt *Head = WorkList.front(); WorkList.pop_front(); - ProgramStateRef state = N->getState(); - ProgramStateManager &StateMgr = state->getStateManager(); + ProgramStateManager &StateMgr = N->getState()->getStateManager(); - if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(Head)) { - if (const VarDecl *VD = dyn_cast<VarDecl>(DR->getDecl())) { + if (const auto *DR = dyn_cast<DeclRefExpr>(Head)) { + if (const auto *VD = dyn_cast<VarDecl>(DR->getDecl())) { const VarRegion *R = StateMgr.getRegionManager().getVarRegion(VD, N->getLocationContext()); // What did we load? - SVal V = state->getSVal(S, N->getLocationContext()); + SVal V = N->getSVal(S); if (V.getAs<loc::ConcreteInt>() || V.getAs<nonloc::ConcreteInt>()) { // Register a new visitor with the BugReport. @@ -1267,7 +1790,6 @@ std::shared_ptr<PathDiagnosticPiece> ConditionBRVisitor::VisitNodeImpl(const ExplodedNode *N, const ExplodedNode *Prev, BugReporterContext &BRC, BugReport &BR) { - ProgramPoint progPoint = N->getLocation(); ProgramStateRef CurrentState = N->getState(); ProgramStateRef PrevState = Prev->getState(); @@ -1289,11 +1811,8 @@ ConditionBRVisitor::VisitNodeImpl(const ExplodedNode *N, } if (Optional<PostStmt> PS = progPoint.getAs<PostStmt>()) { - // FIXME: Assuming that BugReporter is a GRBugReporter is a layering - // violation. const std::pair<const ProgramPointTag *, const ProgramPointTag *> &tags = - cast<GRBugReporter>(BRC.getBugReporter()). - getEngine().geteagerlyAssumeBinOpBifurcationTags(); + ExprEngine::geteagerlyAssumeBinOpBifurcationTags(); const ProgramPointTag *tag = PS->getTag(); if (tag == tags.first) @@ -1389,7 +1908,7 @@ ConditionBRVisitor::VisitTrueTest(const Expr *Cond, bool tookTrue, return P; break; case Stmt::UnaryOperatorClass: { - const UnaryOperator *UO = cast<UnaryOperator>(CondTmp); + const auto *UO = cast<UnaryOperator>(CondTmp); if (UO->getOpcode() == UO_LNot) { tookTrueTmp = !tookTrueTmp; CondTmp = UO->getSubExpr(); @@ -1432,7 +1951,6 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex, isa<CXXBoolLiteralExpr>(Ex) || isa<IntegerLiteral>(Ex) || isa<FloatingLiteral>(Ex))) { - StringRef StartName = Lexer::getImmediateMacroNameForDiagnostics(LocStart, BRC.getSourceManager(), BRC.getASTContext().getLangOpts()); StringRef EndName = Lexer::getImmediateMacroNameForDiagnostics(LocEnd, @@ -1463,7 +1981,7 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex, } } - if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(Ex)) { + if (const auto *DR = dyn_cast<DeclRefExpr>(Ex)) { const bool quotes = isa<VarDecl>(DR->getDecl()); if (quotes) { Out << '\''; @@ -1487,7 +2005,7 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex, return quotes; } - if (const IntegerLiteral *IL = dyn_cast<IntegerLiteral>(Ex)) { + if (const auto *IL = dyn_cast<IntegerLiteral>(Ex)) { QualType OriginalTy = OriginalExpr->getType(); if (OriginalTy->isPointerType()) { if (IL->getValue() == 0) { @@ -1513,7 +2031,6 @@ std::shared_ptr<PathDiagnosticPiece> ConditionBRVisitor::VisitTrueTest(const Expr *Cond, const BinaryOperator *BExpr, const bool tookTrue, BugReporterContext &BRC, BugReport &R, const ExplodedNode *N) { - bool shouldInvert = false; Optional<bool> shouldPrune; @@ -1618,8 +2135,8 @@ std::shared_ptr<PathDiagnosticPiece> ConditionBRVisitor::VisitConditionVariable( PathDiagnosticLocation Loc(CondVarExpr, BRC.getSourceManager(), LCtx); auto event = std::make_shared<PathDiagnosticEventPiece>(Loc, Out.str()); - if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(CondVarExpr)) { - if (const VarDecl *VD = dyn_cast<VarDecl>(DR->getDecl())) { + if (const auto *DR = dyn_cast<DeclRefExpr>(CondVarExpr)) { + if (const auto *VD = dyn_cast<VarDecl>(DR->getDecl())) { const ProgramState *state = N->getState().get(); if (const MemRegion *R = state->getLValue(VD, LCtx).getAsRegion()) { if (report.isInteresting(R)) @@ -1635,8 +2152,7 @@ std::shared_ptr<PathDiagnosticPiece> ConditionBRVisitor::VisitTrueTest(const Expr *Cond, const DeclRefExpr *DR, const bool tookTrue, BugReporterContext &BRC, BugReport &report, const ExplodedNode *N) { - - const VarDecl *VD = dyn_cast<VarDecl>(DR->getDecl()); + const auto *VD = dyn_cast<VarDecl>(DR->getDecl()); if (!VD) return nullptr; @@ -1684,14 +2200,11 @@ bool ConditionBRVisitor::isPieceMessageGeneric( Piece->getString() == GenericFalseMessage; } -std::unique_ptr<PathDiagnosticPiece> -LikelyFalsePositiveSuppressionBRVisitor::getEndPath(BugReporterContext &BRC, - const ExplodedNode *N, - BugReport &BR) { +void LikelyFalsePositiveSuppressionBRVisitor::finalizeVisitor( + BugReporterContext &BRC, const ExplodedNode *N, BugReport &BR) { // Here we suppress false positives coming from system headers. This list is // based on known issues. - ExprEngine &Eng = BRC.getBugReporter().getEngine(); - AnalyzerOptions &Options = Eng.getAnalysisManager().options; + AnalyzerOptions &Options = BRC.getAnalyzerOptions(); const Decl *D = N->getLocationContext()->getDecl(); if (AnalysisDeclContext::isInStdNamespace(D)) { @@ -1701,8 +2214,7 @@ LikelyFalsePositiveSuppressionBRVisitor::getEndPath(BugReporterContext &BRC, // TR1, Boost, or llvm/ADT. if (Options.shouldSuppressFromCXXStandardLibrary()) { BR.markInvalid(getTag(), nullptr); - return nullptr; - + return; } else { // If the complete 'std' suppression is not enabled, suppress reports // from the 'std' namespace that are known to produce false positives. @@ -1710,27 +2222,27 @@ LikelyFalsePositiveSuppressionBRVisitor::getEndPath(BugReporterContext &BRC, // The analyzer issues a false use-after-free when std::list::pop_front // or std::list::pop_back are called multiple times because we cannot // reason about the internal invariants of the data structure. - if (const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(D)) { + if (const auto *MD = dyn_cast<CXXMethodDecl>(D)) { const CXXRecordDecl *CD = MD->getParent(); if (CD->getName() == "list") { BR.markInvalid(getTag(), nullptr); - return nullptr; + return; } } // The analyzer issues a false positive when the constructor of // std::__independent_bits_engine from algorithms is used. - if (const CXXConstructorDecl *MD = dyn_cast<CXXConstructorDecl>(D)) { + if (const auto *MD = dyn_cast<CXXConstructorDecl>(D)) { const CXXRecordDecl *CD = MD->getParent(); if (CD->getName() == "__independent_bits_engine") { BR.markInvalid(getTag(), nullptr); - return nullptr; + return; } } for (const LocationContext *LCtx = N->getLocationContext(); LCtx; LCtx = LCtx->getParent()) { - const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(LCtx->getDecl()); + const auto *MD = dyn_cast<CXXMethodDecl>(LCtx->getDecl()); if (!MD) continue; @@ -1743,7 +2255,7 @@ LikelyFalsePositiveSuppressionBRVisitor::getEndPath(BugReporterContext &BRC, // data structure. if (CD->getName() == "basic_string") { BR.markInvalid(getTag(), nullptr); - return nullptr; + return; } // The analyzer issues a false positive on @@ -1751,7 +2263,7 @@ LikelyFalsePositiveSuppressionBRVisitor::getEndPath(BugReporterContext &BRC, // because it does not reason properly about temporary destructors. if (CD->getName() == "shared_ptr") { BR.markInvalid(getTag(), nullptr); - return nullptr; + return; } } } @@ -1765,18 +2277,15 @@ LikelyFalsePositiveSuppressionBRVisitor::getEndPath(BugReporterContext &BRC, Loc = Loc.getSpellingLoc(); if (SM.getFilename(Loc).endswith("sys/queue.h")) { BR.markInvalid(getTag(), nullptr); - return nullptr; + return; } } - - return nullptr; } std::shared_ptr<PathDiagnosticPiece> UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) { - ProgramStateRef State = N->getState(); ProgramPoint ProgLoc = N->getLocation(); @@ -1789,18 +2298,17 @@ UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, CallEventManager &CEMgr = BRC.getStateManager().getCallEventManager(); CallEventRef<> Call = CEMgr.getCaller(CEnter->getCalleeContext(), State); unsigned Idx = 0; - ArrayRef<ParmVarDecl*> parms = Call->parameters(); + ArrayRef<ParmVarDecl *> parms = Call->parameters(); - for (ArrayRef<ParmVarDecl*>::iterator I = parms.begin(), E = parms.end(); - I != E; ++I, ++Idx) { + for (const auto ParamDecl : parms) { const MemRegion *ArgReg = Call->getArgSVal(Idx).getAsRegion(); + ++Idx; // Are we tracking the argument or its subregion? - if ( !ArgReg || (ArgReg != R && !R->isSubRegionOf(ArgReg->StripCasts()))) + if ( !ArgReg || !R->isSubRegionOf(ArgReg->StripCasts())) continue; // Check the function parameter type. - const ParmVarDecl *ParamDecl = *I; assert(ParamDecl && "Formal parameter has no decl?"); QualType T = ParamDecl->getType(); @@ -1832,7 +2340,7 @@ CXXSelfAssignmentBRVisitor::VisitNode(const ExplodedNode *Succ, if (Satisfied) return nullptr; - auto Edge = Succ->getLocation().getAs<BlockEdge>(); + const auto Edge = Succ->getLocation().getAs<BlockEdge>(); if (!Edge.hasValue()) return nullptr; @@ -1859,7 +2367,7 @@ CXXSelfAssignmentBRVisitor::VisitNode(const ExplodedNode *Succ, const auto Param = State->getSVal(State->getRegion(Met->getParamDecl(0), LCtx)); const auto This = - State->getSVal(SVB.getCXXThis(Met, LCtx->getCurrentStackFrame())); + State->getSVal(SVB.getCXXThis(Met, LCtx->getStackFrame())); auto L = PathDiagnosticLocation::create(Met, BRC.getSourceManager()); @@ -1877,3 +2385,82 @@ CXXSelfAssignmentBRVisitor::VisitNode(const ExplodedNode *Succ, return std::move(Piece); } + +std::shared_ptr<PathDiagnosticPiece> +TaintBugVisitor::VisitNode(const ExplodedNode *N, const ExplodedNode *PrevN, + BugReporterContext &BRC, BugReport &BR) { + + // Find the ExplodedNode where the taint was first introduced + if (!N->getState()->isTainted(V) || PrevN->getState()->isTainted(V)) + return nullptr; + + const Stmt *S = PathDiagnosticLocation::getStmt(N); + if (!S) + return nullptr; + + const LocationContext *NCtx = N->getLocationContext(); + PathDiagnosticLocation L = + PathDiagnosticLocation::createBegin(S, BRC.getSourceManager(), NCtx); + if (!L.isValid() || !L.asLocation().isValid()) + return nullptr; + + return std::make_shared<PathDiagnosticEventPiece>(L, "Taint originated here"); +} + +FalsePositiveRefutationBRVisitor::FalsePositiveRefutationBRVisitor() + : Constraints(ConstraintRangeTy::Factory().getEmptyMap()) {} + +void FalsePositiveRefutationBRVisitor::finalizeVisitor( + BugReporterContext &BRC, const ExplodedNode *EndPathNode, BugReport &BR) { + // Collect new constraints + VisitNode(EndPathNode, nullptr, BRC, BR); + + // Create a refutation manager + std::unique_ptr<SMTSolver> RefutationSolver = CreateZ3Solver(); + ASTContext &Ctx = BRC.getASTContext(); + + // Add constraints to the solver + for (const auto &I : Constraints) { + SymbolRef Sym = I.first; + + SMTExprRef Constraints = RefutationSolver->fromBoolean(false); + for (const auto &Range : I.second) { + Constraints = RefutationSolver->mkOr( + Constraints, + RefutationSolver->getRangeExpr(Ctx, Sym, Range.From(), Range.To(), + /*InRange=*/true)); + } + RefutationSolver->addConstraint(Constraints); + } + + // And check for satisfiability + if (RefutationSolver->check().isConstrainedFalse()) + BR.markInvalid("Infeasible constraints", EndPathNode->getLocationContext()); +} + +std::shared_ptr<PathDiagnosticPiece> +FalsePositiveRefutationBRVisitor::VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) { + // Collect new constraints + const ConstraintRangeTy &NewCs = N->getState()->get<ConstraintRange>(); + ConstraintRangeTy::Factory &CF = + N->getState()->get_context<ConstraintRange>(); + + // Add constraints if we don't have them yet + for (auto const &C : NewCs) { + const SymbolRef &Sym = C.first; + if (!Constraints.contains(Sym)) { + Constraints = CF.add(Constraints, Sym, C.second); + } + } + + return nullptr; +} + +void FalsePositiveRefutationBRVisitor::Profile( + llvm::FoldingSetNodeID &ID) const { + static int Tag = 0; + ID.AddPointer(&Tag); +} diff --git a/lib/StaticAnalyzer/Core/CMakeLists.txt b/lib/StaticAnalyzer/Core/CMakeLists.txt index 5ac4f942f373..de994b598e59 100644 --- a/lib/StaticAnalyzer/Core/CMakeLists.txt +++ b/lib/StaticAnalyzer/Core/CMakeLists.txt @@ -13,7 +13,6 @@ add_clang_library(clangStaticAnalyzerCore AnalyzerOptions.cpp BasicValueFactory.cpp BlockCounter.cpp - IssueHash.cpp BugReporter.cpp BugReporterVisitors.cpp CallEvent.cpp @@ -35,6 +34,7 @@ add_clang_library(clangStaticAnalyzerCore ExprEngineObjC.cpp FunctionSummary.cpp HTMLDiagnostics.cpp + IssueHash.cpp LoopUnrolling.cpp LoopWidening.cpp MemRegion.cpp @@ -48,9 +48,11 @@ add_clang_library(clangStaticAnalyzerCore SVals.cpp SimpleConstraintManager.cpp SimpleSValBuilder.cpp + SMTConstraintManager.cpp Store.cpp SubEngine.cpp SymbolManager.cpp + WorkList.cpp Z3ConstraintManager.cpp LINK_LIBS @@ -58,6 +60,7 @@ add_clang_library(clangStaticAnalyzerCore clangASTMatchers clangAnalysis clangBasic + clangCrossTU clangLex clangRewrite ${Z3_LINK_FILES} diff --git a/lib/StaticAnalyzer/Core/CallEvent.cpp b/lib/StaticAnalyzer/Core/CallEvent.cpp index 776369be9dba..8db7b06f186d 100644 --- a/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -1,4 +1,4 @@ -//===- Calls.cpp - Wrapper for all function and method calls ------*- C++ -*--// +//===- CallEvent.cpp - Wrapper for all function and method calls ----------===// // // The LLVM Compiler Infrastructure // @@ -14,14 +14,52 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/ExprObjC.h" #include "clang/AST/ParentMap.h" +#include "clang/AST/Stmt.h" +#include "clang/AST/Type.h" +#include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/Analysis/CFG.h" #include "clang/Analysis/ProgramPoint.h" +#include "clang/CrossTU/CrossTranslationUnit.h" +#include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/Specifiers.h" +#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/Store.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" -#include "llvm/Support/raw_ostream.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Compiler.h" #include "llvm/Support/Debug.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/raw_ostream.h" +#include <cassert> +#include <utility> #define DEBUG_TYPE "static-analyzer-call-event" @@ -29,11 +67,13 @@ using namespace clang; using namespace ento; QualType CallEvent::getResultType() const { + ASTContext &Ctx = getState()->getStateManager().getContext(); const Expr *E = getOriginExpr(); - assert(E && "Calls without origin expressions do not have results"); - QualType ResultTy = E->getType(); + if (!E) + return Ctx.VoidTy; + assert(E); - ASTContext &Ctx = getState()->getStateManager().getContext(); + QualType ResultTy = E->getType(); // A function that returns a reference to 'int' will have a result type // of simply 'int'. Check the origin expr's value kind to recover the @@ -78,7 +118,7 @@ static bool isCallback(QualType T) { } static bool isVoidPointerToNonConst(QualType T) { - if (const PointerType *PT = T->getAs<PointerType>()) { + if (const auto *PT = T->getAs<PointerType>()) { QualType PointeeTy = PT->getPointeeType(); if (PointeeTy.isConstQualified()) return false; @@ -119,14 +159,14 @@ bool CallEvent::hasVoidPointerToNonConstArg() const { } bool CallEvent::isGlobalCFunction(StringRef FunctionName) const { - const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(getDecl()); + const auto *FD = dyn_cast_or_null<FunctionDecl>(getDecl()); if (!FD) return false; return CheckerContext::isCLibraryFunction(FD, FunctionName); } -/// \brief Returns true if a type is a pointer-to-const or reference-to-const +/// Returns true if a type is a pointer-to-const or reference-to-const /// with no further indirection. static bool isPointerToConst(QualType Ty) { QualType PointeeTy = Ty->getPointeeType(); @@ -235,7 +275,7 @@ SVal CallEvent::getArgSVal(unsigned Index) const { SourceRange CallEvent::getArgSourceRange(unsigned Index) const { const Expr *ArgE = getArgExpr(Index); if (!ArgE) - return SourceRange(); + return {}; return ArgE->getSourceRange(); } @@ -266,7 +306,6 @@ void CallEvent::dump(raw_ostream &Out) const { Out << "Unknown call (type " << getKind() << ")"; } - bool CallEvent::isCallStmt(const Stmt *S) { return isa<CallExpr>(S) || isa<ObjCMessageExpr>(S) || isa<CXXConstructExpr>(S) @@ -275,11 +314,11 @@ bool CallEvent::isCallStmt(const Stmt *S) { QualType CallEvent::getDeclaredResultType(const Decl *D) { assert(D); - if (const FunctionDecl* FD = dyn_cast<FunctionDecl>(D)) + if (const auto *FD = dyn_cast<FunctionDecl>(D)) return FD->getReturnType(); - if (const ObjCMethodDecl* MD = dyn_cast<ObjCMethodDecl>(D)) + if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) return MD->getReturnType(); - if (const BlockDecl *BD = dyn_cast<BlockDecl>(D)) { + if (const auto *BD = dyn_cast<BlockDecl>(D)) { // Blocks are difficult because the return type may not be stored in the // BlockDecl itself. The AST should probably be enhanced, but for now we // just do what we can. @@ -296,7 +335,7 @@ QualType CallEvent::getDeclaredResultType(const Decl *D) { return Ty; } - return QualType(); + return {}; } llvm_unreachable("unknown callable kind"); @@ -305,11 +344,11 @@ QualType CallEvent::getDeclaredResultType(const Decl *D) { bool CallEvent::isVariadic(const Decl *D) { assert(D); - if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) + if (const auto *FD = dyn_cast<FunctionDecl>(D)) return FD->isVariadic(); - if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) + if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) return MD->isVariadic(); - if (const BlockDecl *BD = dyn_cast<BlockDecl>(D)) + if (const auto *BD = dyn_cast<BlockDecl>(D)) return BD->isVariadic(); llvm_unreachable("unknown callable kind"); @@ -350,32 +389,53 @@ ArrayRef<ParmVarDecl*> AnyFunctionCall::parameters() const { RuntimeDefinition AnyFunctionCall::getRuntimeDefinition() const { const FunctionDecl *FD = getDecl(); + if (!FD) + return {}; + // Note that the AnalysisDeclContext will have the FunctionDecl with // the definition (if one exists). - if (FD) { - AnalysisDeclContext *AD = - getLocationContext()->getAnalysisDeclContext()-> - getManager()->getContext(FD); - bool IsAutosynthesized; - Stmt* Body = AD->getBody(IsAutosynthesized); - DEBUG({ - if (IsAutosynthesized) - llvm::dbgs() << "Using autosynthesized body for " << FD->getName() - << "\n"; - }); - if (Body) { - const Decl* Decl = AD->getDecl(); - return RuntimeDefinition(Decl); - } + AnalysisDeclContext *AD = + getLocationContext()->getAnalysisDeclContext()-> + getManager()->getContext(FD); + bool IsAutosynthesized; + Stmt* Body = AD->getBody(IsAutosynthesized); + LLVM_DEBUG({ + if (IsAutosynthesized) + llvm::dbgs() << "Using autosynthesized body for " << FD->getName() + << "\n"; + }); + if (Body) { + const Decl* Decl = AD->getDecl(); + return RuntimeDefinition(Decl); + } + + SubEngine *Engine = getState()->getStateManager().getOwningEngine(); + AnalyzerOptions &Opts = Engine->getAnalysisManager().options; + + // Try to get CTU definition only if CTUDir is provided. + if (!Opts.naiveCTUEnabled()) + return {}; + + cross_tu::CrossTranslationUnitContext &CTUCtx = + *Engine->getCrossTranslationUnitContext(); + llvm::Expected<const FunctionDecl *> CTUDeclOrError = + CTUCtx.getCrossTUDefinition(FD, Opts.getCTUDir(), Opts.getCTUIndexName()); + + if (!CTUDeclOrError) { + handleAllErrors(CTUDeclOrError.takeError(), + [&](const cross_tu::IndexError &IE) { + CTUCtx.emitCrossTUDiagnostics(IE); + }); + return {}; } - return RuntimeDefinition(); + return RuntimeDefinition(*CTUDeclOrError); } void AnyFunctionCall::getInitialStackFrameContents( const StackFrameContext *CalleeCtx, BindingsTy &Bindings) const { - const FunctionDecl *D = cast<FunctionDecl>(CalleeCtx->getDecl()); + const auto *D = cast<FunctionDecl>(CalleeCtx->getDecl()); SValBuilder &SVB = getState()->getStateManager().getSValBuilder(); addParameterValuesToBindings(CalleeCtx, Bindings, SVB, *this, D->parameters()); @@ -442,7 +502,6 @@ bool AnyFunctionCall::argumentsMayEscape() const { return false; } - const FunctionDecl *SimpleFunctionCall::getDecl() const { const FunctionDecl *D = getOriginExpr()->getDirectCallee(); if (D) @@ -451,9 +510,8 @@ const FunctionDecl *SimpleFunctionCall::getDecl() const { return getSVal(getOriginExpr()->getCallee()).getAsFunctionDecl(); } - const FunctionDecl *CXXInstanceCall::getDecl() const { - const CallExpr *CE = cast_or_null<CallExpr>(getOriginExpr()); + const auto *CE = cast_or_null<CallExpr>(getOriginExpr()); if (!CE) return AnyFunctionCall::getDecl(); @@ -470,15 +528,20 @@ void CXXInstanceCall::getExtraInvalidatedValues( Values.push_back(ThisVal); // Don't invalidate if the method is const and there are no mutable fields. - if (const CXXMethodDecl *D = cast_or_null<CXXMethodDecl>(getDecl())) { + if (const auto *D = cast_or_null<CXXMethodDecl>(getDecl())) { if (!D->isConst()) return; // Get the record decl for the class of 'This'. D->getParent() may return a // 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 CXXRecordDecl *ParentRecord = Ex->getType()->getAsCXXRecordDecl(); - if (!ParentRecord || ParentRecord->hasMutableFields()) + QualType T = Ex->getType(); + if (T->isPointerType()) // Arrow or implicit-this syntax? + T = T->getPointeeType(); + const CXXRecordDecl *ParentRecord = T->getAsCXXRecordDecl(); + assert(ParentRecord); + if (ParentRecord->hasMutableFields()) return; // Preserve CXXThis. const MemRegion *ThisRegion = ThisVal.getAsRegion(); @@ -501,27 +564,26 @@ SVal CXXInstanceCall::getCXXThisVal() const { return ThisVal; } - RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const { // Do we have a decl at all? const Decl *D = getDecl(); if (!D) - return RuntimeDefinition(); + return {}; // If the method is non-virtual, we know we can inline it. - const CXXMethodDecl *MD = cast<CXXMethodDecl>(D); + const auto *MD = cast<CXXMethodDecl>(D); if (!MD->isVirtual()) return AnyFunctionCall::getRuntimeDefinition(); // Do we know the implicit 'this' object being called? const MemRegion *R = getCXXThisVal().getAsRegion(); if (!R) - return RuntimeDefinition(); + return {}; // Do we know anything about the type of 'this'? DynamicTypeInfo DynType = getDynamicTypeInfo(getState(), R); if (!DynType.isValid()) - return RuntimeDefinition(); + return {}; // Is the type a C++ class? (This is mostly a defensive check.) QualType RegionType = DynType.getType()->getPointeeType(); @@ -529,7 +591,7 @@ RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const { const CXXRecordDecl *RD = RegionType->getAsCXXRecordDecl(); if (!RD || !RD->hasDefinition()) - return RuntimeDefinition(); + return {}; // Find the decl for this method in that class. const CXXMethodDecl *Result = MD->getCorrespondingMethodInClass(RD, true); @@ -547,13 +609,13 @@ RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const { // this is fixed. <rdar://problem/12287087> //assert(!MD->getParent()->isDerivedFrom(RD) && "Bad DynamicTypeInfo"); - return RuntimeDefinition(); + return {}; } // Does the decl that we found have an implementation? const FunctionDecl *Definition; if (!Result->hasBody(Definition)) - return RuntimeDefinition(); + return {}; // We found a definition. If we're not sure that this devirtualization is // actually what will happen at runtime, make sure to provide the region so @@ -574,7 +636,7 @@ void CXXInstanceCall::getInitialStackFrameContents( ProgramStateManager &StateMgr = getState()->getStateManager(); SValBuilder &SVB = StateMgr.getSValBuilder(); - const CXXMethodDecl *MD = cast<CXXMethodDecl>(CalleeCtx->getDecl()); + const auto *MD = cast<CXXMethodDecl>(CalleeCtx->getDecl()); Loc ThisLoc = SVB.getCXXThis(MD, CalleeCtx); // If we devirtualized to a different member function, we need to make sure @@ -587,7 +649,15 @@ void CXXInstanceCall::getInitialStackFrameContents( // FIXME: CallEvent maybe shouldn't be directly accessing StoreManager. bool Failed; ThisVal = StateMgr.getStoreManager().attemptDownCast(ThisVal, Ty, Failed); - assert(!Failed && "Calling an incorrectly devirtualized method"); + if (Failed) { + // We might have suffered some sort of placement new earlier, so + // we're constructing in a completely unexpected storage. + // Fall back to a generic pointer cast for this-value. + const CXXMethodDecl *StaticMD = cast<CXXMethodDecl>(getDecl()); + const CXXRecordDecl *StaticClass = StaticMD->getParent(); + QualType StaticTy = Ctx.getPointerType(Ctx.getRecordType(StaticClass)); + ThisVal = SVB.evalCast(ThisVal, Ty, StaticTy); + } } if (!ThisVal.isUnknown()) @@ -595,8 +665,6 @@ void CXXInstanceCall::getInitialStackFrameContents( } } - - const Expr *CXXMemberCall::getCXXThisExpr() const { return getOriginExpr()->getImplicitObjectArgument(); } @@ -606,19 +674,17 @@ RuntimeDefinition CXXMemberCall::getRuntimeDefinition() const { // id-expression in the class member access expression is a qualified-id, // that function is called. Otherwise, its final overrider in the dynamic type // of the object expression is called. - if (const MemberExpr *ME = dyn_cast<MemberExpr>(getOriginExpr()->getCallee())) + if (const auto *ME = dyn_cast<MemberExpr>(getOriginExpr()->getCallee())) if (ME->hasQualifier()) return AnyFunctionCall::getRuntimeDefinition(); return CXXInstanceCall::getRuntimeDefinition(); } - const Expr *CXXMemberOperatorCall::getCXXThisExpr() const { return getOriginExpr()->getArg(0); } - const BlockDataRegion *BlockCall::getBlockRegion() const { const Expr *Callee = getOriginExpr()->getCallee(); const MemRegion *DataReg = getSVal(Callee).getAsRegion(); @@ -663,7 +729,6 @@ void BlockCall::getInitialStackFrameContents(const StackFrameContext *CalleeCtx, Params); } - SVal CXXConstructorCall::getCXXThisVal() const { if (Data) return loc::MemRegionVal(static_cast<const MemRegion *>(Data)); @@ -672,8 +737,13 @@ SVal CXXConstructorCall::getCXXThisVal() const { void CXXConstructorCall::getExtraInvalidatedValues(ValueList &Values, RegionAndSymbolInvalidationTraits *ETraits) const { - if (Data) - Values.push_back(loc::MemRegionVal(static_cast<const MemRegion *>(Data))); + if (Data) { + loc::MemRegionVal MV(static_cast<const MemRegion *>(Data)); + if (SymbolRef Sym = MV.getAsSymbol(true)) + ETraits->setTrait(Sym, + RegionAndSymbolInvalidationTraits::TK_SuppressEscape); + Values.push_back(MV); + } } void CXXConstructorCall::getInitialStackFrameContents( @@ -684,7 +754,7 @@ void CXXConstructorCall::getInitialStackFrameContents( SVal ThisVal = getCXXThisVal(); if (!ThisVal.isUnknown()) { SValBuilder &SVB = getState()->getStateManager().getSValBuilder(); - const CXXMethodDecl *MD = cast<CXXMethodDecl>(CalleeCtx->getDecl()); + const auto *MD = cast<CXXMethodDecl>(CalleeCtx->getDecl()); Loc ThisLoc = SVB.getCXXThis(MD, CalleeCtx); Bindings.push_back(std::make_pair(ThisLoc, ThisVal)); } @@ -785,7 +855,7 @@ SourceRange ObjCMethodCall::getSourceRange() const { llvm_unreachable("unknown message kind"); } -typedef llvm::PointerIntPair<const PseudoObjectExpr *, 2> ObjCMessageDataTy; +using ObjCMessageDataTy = llvm::PointerIntPair<const PseudoObjectExpr *, 2>; const PseudoObjectExpr *ObjCMethodCall::getContainingPseudoObjectExpr() const { assert(Data && "Lazy lookup not yet performed."); @@ -799,7 +869,7 @@ getSyntacticFromForPseudoObjectExpr(const PseudoObjectExpr *POE) { // This handles the funny case of assigning to the result of a getter. // This can happen if the getter returns a non-const reference. - if (const BinaryOperator *BO = dyn_cast<BinaryOperator>(Syntactic)) + if (const auto *BO = dyn_cast<BinaryOperator>(Syntactic)) Syntactic = BO->getLHS(); return Syntactic; @@ -807,13 +877,12 @@ getSyntacticFromForPseudoObjectExpr(const PseudoObjectExpr *POE) { ObjCMessageKind ObjCMethodCall::getMessageKind() const { if (!Data) { - // Find the parent, ignoring implicit casts. ParentMap &PM = getLocationContext()->getParentMap(); const Stmt *S = PM.getParentIgnoreParenCasts(getOriginExpr()); // Check if parent is a PseudoObjectExpr. - if (const PseudoObjectExpr *POE = dyn_cast_or_null<PseudoObjectExpr>(S)) { + if (const auto *POE = dyn_cast_or_null<PseudoObjectExpr>(S)) { const Expr *Syntactic = getSyntacticFromForPseudoObjectExpr(POE); ObjCMessageKind K; @@ -875,15 +944,14 @@ const ObjCPropertyDecl *ObjCMethodCall::getAccessedProperty() const { bool ObjCMethodCall::canBeOverridenInSubclass(ObjCInterfaceDecl *IDecl, Selector Sel) const { assert(IDecl); - const SourceManager &SM = - getState()->getStateManager().getContext().getSourceManager(); - + AnalysisManager &AMgr = + getState()->getStateManager().getOwningEngine()->getAnalysisManager(); // If the class interface is declared inside the main file, assume it is not // subcassed. // TODO: It could actually be subclassed if the subclass is private as well. // This is probably very rare. SourceLocation InterfLoc = IDecl->getEndOfDefinitionLoc(); - if (InterfLoc.isValid() && SM.isInMainFile(InterfLoc)) + if (InterfLoc.isValid() && AMgr.isInCodeFile(InterfLoc)) return false; // Assume that property accessors are not overridden. @@ -905,7 +973,7 @@ bool ObjCMethodCall::canBeOverridenInSubclass(ObjCInterfaceDecl *IDecl, return false; // If outside the main file, - if (D->getLocation().isValid() && !SM.isInMainFile(D->getLocation())) + if (D->getLocation().isValid() && !AMgr.isInCodeFile(D->getLocation())) return true; if (D->isOverriding()) { @@ -965,7 +1033,6 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { Selector Sel = E->getSelector(); if (E->isInstanceMessage()) { - // Find the receiver type. const ObjCObjectPointerType *ReceiverT = nullptr; bool CanBeSubClassed = false; @@ -980,13 +1047,13 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { } else { Receiver = getReceiverSVal().getAsRegion(); if (!Receiver) - return RuntimeDefinition(); + return {}; DynamicTypeInfo DTI = getDynamicTypeInfo(getState(), Receiver); if (!DTI.isValid()) { assert(isa<AllocaRegion>(Receiver) && "Unhandled untyped region class!"); - return RuntimeDefinition(); + return {}; } QualType DynType = DTI.getType(); @@ -1041,11 +1108,9 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { // need to revisit this someday. In terms of memory, this table // stays around until clang quits, which also may be bad if we // need to release memory. - typedef std::pair<const ObjCInterfaceDecl*, Selector> - PrivateMethodKey; - typedef llvm::DenseMap<PrivateMethodKey, - Optional<const ObjCMethodDecl *> > - PrivateMethodCache; + using PrivateMethodKey = std::pair<const ObjCInterfaceDecl *, Selector>; + using PrivateMethodCache = + llvm::DenseMap<PrivateMethodKey, Optional<const ObjCMethodDecl *>>; static PrivateMethodCache PMC; Optional<const ObjCMethodDecl *> &Val = PMC[std::make_pair(IDecl, Sel)]; @@ -1090,7 +1155,6 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { else return RuntimeDefinition(MD, nullptr); } - } else { // This is a class method. // If we have type info for the receiver class, we are calling via @@ -1101,7 +1165,7 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { } } - return RuntimeDefinition(); + return {}; } bool ObjCMethodCall::argumentsMayEscape() const { @@ -1118,7 +1182,7 @@ bool ObjCMethodCall::argumentsMayEscape() const { void ObjCMethodCall::getInitialStackFrameContents( const StackFrameContext *CalleeCtx, BindingsTy &Bindings) const { - const ObjCMethodDecl *D = cast<ObjCMethodDecl>(CalleeCtx->getDecl()); + const auto *D = cast<ObjCMethodDecl>(CalleeCtx->getDecl()); SValBuilder &SVB = getState()->getStateManager().getSValBuilder(); addParameterValuesToBindings(CalleeCtx, Bindings, SVB, *this, D->parameters()); @@ -1135,12 +1199,12 @@ void ObjCMethodCall::getInitialStackFrameContents( CallEventRef<> CallEventManager::getSimpleCall(const CallExpr *CE, ProgramStateRef State, const LocationContext *LCtx) { - if (const CXXMemberCallExpr *MCE = dyn_cast<CXXMemberCallExpr>(CE)) + if (const auto *MCE = dyn_cast<CXXMemberCallExpr>(CE)) return create<CXXMemberCall>(MCE, State, LCtx); - if (const CXXOperatorCallExpr *OpCE = dyn_cast<CXXOperatorCallExpr>(CE)) { + if (const auto *OpCE = dyn_cast<CXXOperatorCallExpr>(CE)) { const FunctionDecl *DirectCallee = OpCE->getDirectCallee(); - if (const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(DirectCallee)) + if (const auto *MD = dyn_cast<CXXMethodDecl>(DirectCallee)) if (MD->isInstance()) return create<CXXMemberOperatorCall>(OpCE, State, LCtx); @@ -1153,12 +1217,11 @@ CallEventManager::getSimpleCall(const CallExpr *CE, ProgramStateRef State, return create<SimpleFunctionCall>(CE, State, LCtx); } - CallEventRef<> CallEventManager::getCaller(const StackFrameContext *CalleeCtx, ProgramStateRef State) { const LocationContext *ParentCtx = CalleeCtx->getParent(); - const LocationContext *CallerCtx = ParentCtx->getCurrentStackFrame(); + const LocationContext *CallerCtx = ParentCtx->getStackFrame(); assert(CallerCtx && "This should not be used for top-level stack frames"); const Stmt *CallSite = CalleeCtx->getCallSite(); @@ -1171,7 +1234,7 @@ CallEventManager::getCaller(const StackFrameContext *CalleeCtx, case Stmt::CXXConstructExprClass: case Stmt::CXXTemporaryObjectExprClass: { SValBuilder &SVB = State->getStateManager().getSValBuilder(); - const CXXMethodDecl *Ctor = cast<CXXMethodDecl>(CalleeCtx->getDecl()); + const auto *Ctor = cast<CXXMethodDecl>(CalleeCtx->getDecl()); Loc ThisPtr = SVB.getCXXThis(Ctor, CalleeCtx); SVal ThisVal = State->getSVal(ThisPtr); @@ -1192,12 +1255,11 @@ CallEventManager::getCaller(const StackFrameContext *CalleeCtx, // destructors, though this could change in the future. const CFGBlock *B = CalleeCtx->getCallSiteBlock(); CFGElement E = (*B)[CalleeCtx->getIndex()]; - assert(E.getAs<CFGImplicitDtor>() && + assert((E.getAs<CFGImplicitDtor>() || E.getAs<CFGTemporaryDtor>()) && "All other CFG elements should have exprs"); - assert(!E.getAs<CFGTemporaryDtor>() && "We don't handle temporaries yet"); SValBuilder &SVB = State->getStateManager().getSValBuilder(); - const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CalleeCtx->getDecl()); + const auto *Dtor = cast<CXXDestructorDecl>(CalleeCtx->getDecl()); Loc ThisPtr = SVB.getCXXThis(Dtor, CalleeCtx); SVal ThisVal = State->getSVal(ThisPtr); @@ -1205,7 +1267,7 @@ CallEventManager::getCaller(const StackFrameContext *CalleeCtx, if (Optional<CFGAutomaticObjDtor> AutoDtor = E.getAs<CFGAutomaticObjDtor>()) Trigger = AutoDtor->getTriggerStmt(); else if (Optional<CFGDeleteDtor> DeleteDtor = E.getAs<CFGDeleteDtor>()) - Trigger = cast<Stmt>(DeleteDtor->getDeleteExpr()); + Trigger = DeleteDtor->getDeleteExpr(); else Trigger = Dtor->getBody(); diff --git a/lib/StaticAnalyzer/Core/CheckerContext.cpp b/lib/StaticAnalyzer/Core/CheckerContext.cpp index 61cbf3854bb2..6cf931abbddd 100644 --- a/lib/StaticAnalyzer/Core/CheckerContext.cpp +++ b/lib/StaticAnalyzer/Core/CheckerContext.cpp @@ -20,9 +20,8 @@ using namespace clang; using namespace ento; const FunctionDecl *CheckerContext::getCalleeDecl(const CallExpr *CE) const { - ProgramStateRef State = getState(); const Expr *Callee = CE->getCallee(); - SVal L = State->getSVal(Callee, Pred->getLocationContext()); + SVal L = Pred->getSVal(Callee); return L.getAsFunctionDecl(); } diff --git a/lib/StaticAnalyzer/Core/CheckerHelpers.cpp b/lib/StaticAnalyzer/Core/CheckerHelpers.cpp index ed41914ebd05..b9facffcc8b5 100644 --- a/lib/StaticAnalyzer/Core/CheckerHelpers.cpp +++ b/lib/StaticAnalyzer/Core/CheckerHelpers.cpp @@ -15,8 +15,12 @@ #include "clang/AST/Decl.h" #include "clang/AST/Expr.h" +namespace clang { + +namespace ento { + // Recursively find any substatements containing macros -bool clang::ento::containsMacro(const Stmt *S) { +bool containsMacro(const Stmt *S) { if (S->getLocStart().isMacroID()) return true; @@ -31,7 +35,7 @@ bool clang::ento::containsMacro(const Stmt *S) { } // Recursively find any substatements containing enum constants -bool clang::ento::containsEnum(const Stmt *S) { +bool containsEnum(const Stmt *S) { const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(S); if (DR && isa<EnumConstantDecl>(DR->getDecl())) @@ -45,7 +49,7 @@ bool clang::ento::containsEnum(const Stmt *S) { } // Recursively find any substatements containing static vars -bool clang::ento::containsStaticLocal(const Stmt *S) { +bool containsStaticLocal(const Stmt *S) { const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(S); if (DR) @@ -61,7 +65,7 @@ bool clang::ento::containsStaticLocal(const Stmt *S) { } // Recursively find any substatements containing __builtin_offsetof -bool clang::ento::containsBuiltinOffsetOf(const Stmt *S) { +bool containsBuiltinOffsetOf(const Stmt *S) { if (isa<OffsetOfExpr>(S)) return true; @@ -74,7 +78,7 @@ bool clang::ento::containsBuiltinOffsetOf(const Stmt *S) { // Extract lhs and rhs from assignment statement std::pair<const clang::VarDecl *, const clang::Expr *> -clang::ento::parseAssignment(const Stmt *S) { +parseAssignment(const Stmt *S) { const VarDecl *VD = nullptr; const Expr *RHS = nullptr; @@ -94,3 +98,18 @@ clang::ento::parseAssignment(const Stmt *S) { return std::make_pair(VD, RHS); } + +Nullability getNullabilityAnnotation(QualType Type) { + const auto *AttrType = Type->getAs<AttributedType>(); + if (!AttrType) + return Nullability::Unspecified; + if (AttrType->getAttrKind() == AttributedType::attr_nullable) + return Nullability::Nullable; + else if (AttrType->getAttrKind() == AttributedType::attr_nonnull) + return Nullability::Nonnull; + return Nullability::Unspecified; +} + + +} // end namespace ento +} // end namespace clang diff --git a/lib/StaticAnalyzer/Core/CheckerManager.cpp b/lib/StaticAnalyzer/Core/CheckerManager.cpp index 49f3edef2a2d..712872a15d8a 100644 --- a/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -1,4 +1,4 @@ -//===--- CheckerManager.cpp - Static Analyzer Checker Manager -------------===// +//===- CheckerManager.cpp - Static Analyzer Checker Manager ---------------===// // // The LLVM Compiler Infrastructure // @@ -13,10 +13,20 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/AST/DeclBase.h" +#include "clang/AST/Stmt.h" #include "clang/Analysis/ProgramPoint.h" +#include "clang/Basic/LLVM.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" +#include <cassert> +#include <vector> using namespace clang; using namespace ento; @@ -43,9 +53,9 @@ void CheckerManager::finishedCheckerRegistration() { #ifndef NDEBUG // Make sure that for every event that has listeners, there is at least // one dispatcher registered for it. - for (llvm::DenseMap<EventTag, EventInfo>::iterator - I = Events.begin(), E = Events.end(); I != E; ++I) - assert(I->second.HasDispatcher && "No dispatcher registered for an event"); + for (const auto &Event : Events) + assert(Event.second.HasDispatcher && + "No dispatcher registered for an event"); #endif } @@ -65,25 +75,22 @@ void CheckerManager::runCheckersOnASTDecl(const Decl *D, AnalysisManager& mgr, } else { // Find the checkers that should run for this Decl and cache them. checkers = &CachedDeclCheckersMap[DeclKind]; - for (unsigned i = 0, e = DeclCheckers.size(); i != e; ++i) { - DeclCheckerInfo &info = DeclCheckers[i]; + for (const auto &info : DeclCheckers) if (info.IsForDeclFn(D)) checkers->push_back(info.CheckFn); - } } assert(checkers); - for (CachedDeclCheckers::iterator - I = checkers->begin(), E = checkers->end(); I != E; ++I) - (*I)(D, mgr, BR); + for (const auto checker : *checkers) + checker(D, mgr, BR); } void CheckerManager::runCheckersOnASTBody(const Decl *D, AnalysisManager& mgr, BugReporter &BR) { assert(D && D->hasBody()); - for (unsigned i = 0, e = BodyCheckers.size(); i != e; ++i) - BodyCheckers[i](D, mgr, BR); + for (const auto BodyChecker : BodyCheckers) + BodyChecker(D, mgr, BR); } //===----------------------------------------------------------------------===// @@ -118,10 +125,8 @@ static void expandGraphWithCheckers(CHECK_CTX checkCtx, } NodeBuilder B(*PrevSet, *CurrSet, BldrCtx); - for (ExplodedNodeSet::iterator NI = PrevSet->begin(), NE = PrevSet->end(); - NI != NE; ++NI) { - checkCtx.runChecker(*I, B, *NI); - } + for (const auto &NI : *PrevSet) + checkCtx.runChecker(*I, B, NI); // If all the produced transitions are sinks, stop. if (CurrSet->empty()) @@ -133,21 +138,23 @@ static void expandGraphWithCheckers(CHECK_CTX checkCtx, } namespace { + struct CheckStmtContext { - typedef SmallVectorImpl<CheckerManager::CheckStmtFunc> CheckersTy; + using CheckersTy = SmallVectorImpl<CheckerManager::CheckStmtFunc>; + bool IsPreVisit; const CheckersTy &Checkers; const Stmt *S; ExprEngine &Eng; bool WasInlined; - CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } - CheckersTy::const_iterator checkers_end() { return Checkers.end(); } - CheckStmtContext(bool isPreVisit, const CheckersTy &checkers, const Stmt *s, ExprEngine &eng, bool wasInlined = false) - : IsPreVisit(isPreVisit), Checkers(checkers), S(s), Eng(eng), - WasInlined(wasInlined) {} + : IsPreVisit(isPreVisit), Checkers(checkers), S(s), Eng(eng), + WasInlined(wasInlined) {} + + CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } + CheckersTy::const_iterator checkers_end() { return Checkers.end(); } void runChecker(CheckerManager::CheckStmtFunc checkFn, NodeBuilder &Bldr, ExplodedNode *Pred) { @@ -160,9 +167,10 @@ namespace { checkFn(S, C); } }; -} -/// \brief Run checkers for visiting Stmts. +} // namespace + +/// Run checkers for visiting Stmts. void CheckerManager::runCheckersForStmt(bool isPreVisit, ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, @@ -175,8 +183,9 @@ void CheckerManager::runCheckersForStmt(bool isPreVisit, } namespace { + struct CheckObjCMessageContext { - typedef std::vector<CheckerManager::CheckObjCMessageFunc> CheckersTy; + using CheckersTy = std::vector<CheckerManager::CheckObjCMessageFunc>; ObjCMessageVisitKind Kind; bool WasInlined; @@ -184,19 +193,18 @@ namespace { const ObjCMethodCall &Msg; ExprEngine &Eng; - CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } - CheckersTy::const_iterator checkers_end() { return Checkers.end(); } - CheckObjCMessageContext(ObjCMessageVisitKind visitKind, const CheckersTy &checkers, const ObjCMethodCall &msg, ExprEngine &eng, bool wasInlined) - : Kind(visitKind), WasInlined(wasInlined), Checkers(checkers), - Msg(msg), Eng(eng) { } + : Kind(visitKind), WasInlined(wasInlined), Checkers(checkers), Msg(msg), + Eng(eng) {} + + CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } + CheckersTy::const_iterator checkers_end() { return Checkers.end(); } void runChecker(CheckerManager::CheckObjCMessageFunc checkFn, NodeBuilder &Bldr, ExplodedNode *Pred) { - bool IsPreVisit; switch (Kind) { @@ -215,9 +223,10 @@ namespace { checkFn(*Msg.cloneWithState<ObjCMethodCall>(Pred->getState()), C); } }; -} -/// \brief Run checkers for visiting obj-c messages. +} // namespace + +/// Run checkers for visiting obj-c messages. void CheckerManager::runCheckersForObjCMessage(ObjCMessageVisitKind visitKind, ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, @@ -242,24 +251,27 @@ CheckerManager::getObjCMessageCheckers(ObjCMessageVisitKind Kind) { } llvm_unreachable("Unknown Kind"); } + namespace { + // FIXME: This has all the same signatures as CheckObjCMessageContext. // Is there a way we can merge the two? struct CheckCallContext { - typedef std::vector<CheckerManager::CheckCallFunc> CheckersTy; + using CheckersTy = std::vector<CheckerManager::CheckCallFunc>; + bool IsPreVisit, WasInlined; const CheckersTy &Checkers; const CallEvent &Call; ExprEngine &Eng; - CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } - CheckersTy::const_iterator checkers_end() { return Checkers.end(); } - CheckCallContext(bool isPreVisit, const CheckersTy &checkers, const CallEvent &call, ExprEngine &eng, bool wasInlined) - : IsPreVisit(isPreVisit), WasInlined(wasInlined), Checkers(checkers), - Call(call), Eng(eng) { } + : IsPreVisit(isPreVisit), WasInlined(wasInlined), Checkers(checkers), + Call(call), Eng(eng) {} + + CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } + CheckersTy::const_iterator checkers_end() { return Checkers.end(); } void runChecker(CheckerManager::CheckCallFunc checkFn, NodeBuilder &Bldr, ExplodedNode *Pred) { @@ -269,9 +281,10 @@ namespace { checkFn(*Call.cloneWithState(Pred->getState()), C); } }; -} -/// \brief Run checkers for visiting an abstract call event. +} // namespace + +/// Run checkers for visiting an abstract call event. void CheckerManager::runCheckersForCallEvent(bool isPreVisit, ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, @@ -286,8 +299,10 @@ void CheckerManager::runCheckersForCallEvent(bool isPreVisit, } namespace { + struct CheckLocationContext { - typedef std::vector<CheckerManager::CheckLocationFunc> CheckersTy; + using CheckersTy = std::vector<CheckerManager::CheckLocationFunc>; + const CheckersTy &Checkers; SVal Loc; bool IsLoad; @@ -295,15 +310,15 @@ namespace { const Stmt *BoundEx; ExprEngine &Eng; - CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } - CheckersTy::const_iterator checkers_end() { return Checkers.end(); } - CheckLocationContext(const CheckersTy &checkers, SVal loc, bool isLoad, const Stmt *NodeEx, const Stmt *BoundEx, ExprEngine &eng) - : Checkers(checkers), Loc(loc), IsLoad(isLoad), NodeEx(NodeEx), - BoundEx(BoundEx), Eng(eng) {} + : Checkers(checkers), Loc(loc), IsLoad(isLoad), NodeEx(NodeEx), + BoundEx(BoundEx), Eng(eng) {} + + CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } + CheckersTy::const_iterator checkers_end() { return Checkers.end(); } void runChecker(CheckerManager::CheckLocationFunc checkFn, NodeBuilder &Bldr, ExplodedNode *Pred) { @@ -317,9 +332,10 @@ namespace { checkFn(Loc, IsLoad, BoundEx, C); } }; -} -/// \brief Run checkers for load/store of a location. +} // namespace + +/// Run checkers for load/store of a location. void CheckerManager::runCheckersForLocation(ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, @@ -333,8 +349,10 @@ void CheckerManager::runCheckersForLocation(ExplodedNodeSet &Dst, } namespace { + struct CheckBindContext { - typedef std::vector<CheckerManager::CheckBindFunc> CheckersTy; + using CheckersTy = std::vector<CheckerManager::CheckBindFunc>; + const CheckersTy &Checkers; SVal Loc; SVal Val; @@ -342,13 +360,13 @@ namespace { ExprEngine &Eng; const ProgramPoint &PP; - CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } - CheckersTy::const_iterator checkers_end() { return Checkers.end(); } - CheckBindContext(const CheckersTy &checkers, SVal loc, SVal val, const Stmt *s, ExprEngine &eng, const ProgramPoint &pp) - : Checkers(checkers), Loc(loc), Val(val), S(s), Eng(eng), PP(pp) {} + : Checkers(checkers), Loc(loc), Val(val), S(s), Eng(eng), PP(pp) {} + + CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } + CheckersTy::const_iterator checkers_end() { return Checkers.end(); } void runChecker(CheckerManager::CheckBindFunc checkFn, NodeBuilder &Bldr, ExplodedNode *Pred) { @@ -358,9 +376,10 @@ namespace { checkFn(Loc, Val, S, C); } }; -} -/// \brief Run checkers for binding of a value to a location. +} // namespace + +/// Run checkers for binding of a value to a location. void CheckerManager::runCheckersForBind(ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, SVal location, SVal val, @@ -373,24 +392,26 @@ void CheckerManager::runCheckersForBind(ExplodedNodeSet &Dst, void CheckerManager::runCheckersForEndAnalysis(ExplodedGraph &G, BugReporter &BR, ExprEngine &Eng) { - for (unsigned i = 0, e = EndAnalysisCheckers.size(); i != e; ++i) - EndAnalysisCheckers[i](G, BR, Eng); + for (const auto EndAnalysisChecker : EndAnalysisCheckers) + EndAnalysisChecker(G, BR, Eng); } namespace { + struct CheckBeginFunctionContext { - typedef std::vector<CheckerManager::CheckBeginFunctionFunc> CheckersTy; + using CheckersTy = std::vector<CheckerManager::CheckBeginFunctionFunc>; + const CheckersTy &Checkers; ExprEngine &Eng; const ProgramPoint &PP; - CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } - CheckersTy::const_iterator checkers_end() { return Checkers.end(); } - CheckBeginFunctionContext(const CheckersTy &Checkers, ExprEngine &Eng, const ProgramPoint &PP) : Checkers(Checkers), Eng(Eng), PP(PP) {} + CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } + CheckersTy::const_iterator checkers_end() { return Checkers.end(); } + void runChecker(CheckerManager::CheckBeginFunctionFunc checkFn, NodeBuilder &Bldr, ExplodedNode *Pred) { const ProgramPoint &L = PP.withTag(checkFn.Checker); @@ -399,7 +420,8 @@ struct CheckBeginFunctionContext { checkFn(C); } }; -} + +} // namespace void CheckerManager::runCheckersForBeginFunction(ExplodedNodeSet &Dst, const BlockEdge &L, @@ -411,42 +433,42 @@ void CheckerManager::runCheckersForBeginFunction(ExplodedNodeSet &Dst, expandGraphWithCheckers(C, Dst, Src); } -/// \brief Run checkers for end of path. +/// Run checkers for end of path. // Note, We do not chain the checker output (like in expandGraphWithCheckers) // for this callback since end of path nodes are expected to be final. void CheckerManager::runCheckersForEndFunction(NodeBuilderContext &BC, ExplodedNodeSet &Dst, ExplodedNode *Pred, - ExprEngine &Eng) { - + ExprEngine &Eng, + const ReturnStmt *RS) { // We define the builder outside of the loop bacause if at least one checkers // creates a sucsessor for Pred, we do not need to generate an // autotransition for it. NodeBuilder Bldr(Pred, Dst, BC); - for (unsigned i = 0, e = EndFunctionCheckers.size(); i != e; ++i) { - CheckEndFunctionFunc checkFn = EndFunctionCheckers[i]; - + for (const auto checkFn : EndFunctionCheckers) { const ProgramPoint &L = BlockEntrance(BC.Block, Pred->getLocationContext(), checkFn.Checker); CheckerContext C(Bldr, Eng, Pred, L); - checkFn(C); + checkFn(RS, C); } } namespace { + struct CheckBranchConditionContext { - typedef std::vector<CheckerManager::CheckBranchConditionFunc> CheckersTy; + using CheckersTy = std::vector<CheckerManager::CheckBranchConditionFunc>; + const CheckersTy &Checkers; const Stmt *Condition; ExprEngine &Eng; - CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } - CheckersTy::const_iterator checkers_end() { return Checkers.end(); } - CheckBranchConditionContext(const CheckersTy &checkers, const Stmt *Cond, ExprEngine &eng) - : Checkers(checkers), Condition(Cond), Eng(eng) {} + : Checkers(checkers), Condition(Cond), Eng(eng) {} + + CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } + CheckersTy::const_iterator checkers_end() { return Checkers.end(); } void runChecker(CheckerManager::CheckBranchConditionFunc checkFn, NodeBuilder &Bldr, ExplodedNode *Pred) { @@ -456,9 +478,10 @@ namespace { checkFn(Condition, C); } }; -} -/// \brief Run checkers for branch condition. +} // namespace + +/// Run checkers for branch condition. void CheckerManager::runCheckersForBranchCondition(const Stmt *Condition, ExplodedNodeSet &Dst, ExplodedNode *Pred, @@ -469,29 +492,69 @@ void CheckerManager::runCheckersForBranchCondition(const Stmt *Condition, expandGraphWithCheckers(C, Dst, Src); } -/// \brief Run checkers for live symbols. +namespace { + + struct CheckNewAllocatorContext { + using CheckersTy = std::vector<CheckerManager::CheckNewAllocatorFunc>; + + const CheckersTy &Checkers; + const CXXNewExpr *NE; + SVal Target; + bool WasInlined; + ExprEngine &Eng; + + CheckNewAllocatorContext(const CheckersTy &Checkers, const CXXNewExpr *NE, + SVal Target, bool WasInlined, ExprEngine &Eng) + : Checkers(Checkers), NE(NE), Target(Target), WasInlined(WasInlined), + Eng(Eng) {} + + CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } + CheckersTy::const_iterator checkers_end() { return Checkers.end(); } + + void runChecker(CheckerManager::CheckNewAllocatorFunc checkFn, + NodeBuilder &Bldr, ExplodedNode *Pred) { + ProgramPoint L = PostAllocatorCall(NE, Pred->getLocationContext()); + CheckerContext C(Bldr, Eng, Pred, L, WasInlined); + checkFn(NE, Target, C); + } + }; + +} // namespace + +void CheckerManager::runCheckersForNewAllocator( + const CXXNewExpr *NE, SVal Target, ExplodedNodeSet &Dst, ExplodedNode *Pred, + ExprEngine &Eng, bool WasInlined) { + ExplodedNodeSet Src; + Src.insert(Pred); + CheckNewAllocatorContext C(NewAllocatorCheckers, NE, Target, WasInlined, Eng); + expandGraphWithCheckers(C, Dst, Src); +} + +/// Run checkers for live symbols. void CheckerManager::runCheckersForLiveSymbols(ProgramStateRef state, SymbolReaper &SymReaper) { - for (unsigned i = 0, e = LiveSymbolsCheckers.size(); i != e; ++i) - LiveSymbolsCheckers[i](state, SymReaper); + for (const auto LiveSymbolsChecker : LiveSymbolsCheckers) + LiveSymbolsChecker(state, SymReaper); } namespace { + struct CheckDeadSymbolsContext { - typedef std::vector<CheckerManager::CheckDeadSymbolsFunc> CheckersTy; + using CheckersTy = std::vector<CheckerManager::CheckDeadSymbolsFunc>; + const CheckersTy &Checkers; SymbolReaper &SR; const Stmt *S; ExprEngine &Eng; ProgramPoint::Kind ProgarmPointKind; - CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } - CheckersTy::const_iterator checkers_end() { return Checkers.end(); } - CheckDeadSymbolsContext(const CheckersTy &checkers, SymbolReaper &sr, const Stmt *s, ExprEngine &eng, ProgramPoint::Kind K) - : Checkers(checkers), SR(sr), S(s), Eng(eng), ProgarmPointKind(K) { } + : Checkers(checkers), SR(sr), S(s), Eng(eng), ProgarmPointKind(K) {} + + CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } + CheckersTy::const_iterator checkers_end() { return Checkers.end(); } void runChecker(CheckerManager::CheckDeadSymbolsFunc checkFn, NodeBuilder &Bldr, ExplodedNode *Pred) { @@ -505,9 +568,10 @@ namespace { checkFn(SR, C); } }; -} -/// \brief Run checkers for dead symbols. +} // namespace + +/// Run checkers for dead symbols. void CheckerManager::runCheckersForDeadSymbols(ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, SymbolReaper &SymReaper, @@ -518,7 +582,7 @@ void CheckerManager::runCheckersForDeadSymbols(ExplodedNodeSet &Dst, expandGraphWithCheckers(C, Dst, Src); } -/// \brief Run checkers for region changes. +/// Run checkers for region changes. ProgramStateRef CheckerManager::runCheckersForRegionChanges(ProgramStateRef state, const InvalidatedSymbols *invalidated, @@ -526,19 +590,18 @@ CheckerManager::runCheckersForRegionChanges(ProgramStateRef state, ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx, const CallEvent *Call) { - for (unsigned i = 0, e = RegionChangesCheckers.size(); i != e; ++i) { + for (const auto RegionChangesChecker : RegionChangesCheckers) { // If any checker declares the state infeasible (or if it starts that way), // bail out. if (!state) return nullptr; - state = RegionChangesCheckers[i](state, invalidated, - ExplicitRegions, Regions, - LCtx, Call); + state = RegionChangesChecker(state, invalidated, ExplicitRegions, Regions, + LCtx, Call); } return state; } -/// \brief Run checkers to process symbol escape event. +/// Run checkers to process symbol escape event. ProgramStateRef CheckerManager::runCheckersForPointerEscape(ProgramStateRef State, const InvalidatedSymbols &Escaped, @@ -549,58 +612,55 @@ CheckerManager::runCheckersForPointerEscape(ProgramStateRef State, (Kind != PSK_DirectEscapeOnCall && Kind != PSK_IndirectEscapeOnCall)) && "Call must not be NULL when escaping on call"); - for (unsigned i = 0, e = PointerEscapeCheckers.size(); i != e; ++i) { - // If any checker declares the state infeasible (or if it starts that - // way), bail out. - if (!State) - return nullptr; - State = PointerEscapeCheckers[i](State, Escaped, Call, Kind, ETraits); - } + for (const auto PointerEscapeChecker : PointerEscapeCheckers) { + // If any checker declares the state infeasible (or if it starts that + // way), bail out. + if (!State) + return nullptr; + State = PointerEscapeChecker(State, Escaped, Call, Kind, ETraits); + } return State; } -/// \brief Run checkers for handling assumptions on symbolic values. +/// Run checkers for handling assumptions on symbolic values. ProgramStateRef CheckerManager::runCheckersForEvalAssume(ProgramStateRef state, SVal Cond, bool Assumption) { - for (unsigned i = 0, e = EvalAssumeCheckers.size(); i != e; ++i) { + for (const auto EvalAssumeChecker : EvalAssumeCheckers) { // If any checker declares the state infeasible (or if it starts that way), // bail out. if (!state) return nullptr; - state = EvalAssumeCheckers[i](state, Cond, Assumption); + state = EvalAssumeChecker(state, Cond, Assumption); } return state; } -/// \brief Run checkers for evaluating a call. +/// Run checkers for evaluating a call. /// Only one checker will evaluate the call. void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, const CallEvent &Call, ExprEngine &Eng) { const CallExpr *CE = cast<CallExpr>(Call.getOriginExpr()); - for (ExplodedNodeSet::iterator - NI = Src.begin(), NE = Src.end(); NI != NE; ++NI) { - ExplodedNode *Pred = *NI; + for (const auto Pred : Src) { bool anyEvaluated = false; ExplodedNodeSet checkDst; NodeBuilder B(Pred, checkDst, Eng.getBuilderContext()); // Check if any of the EvalCall callbacks can evaluate the call. - for (std::vector<EvalCallFunc>::iterator - EI = EvalCallCheckers.begin(), EE = EvalCallCheckers.end(); - EI != EE; ++EI) { + for (const auto EvalCallChecker : EvalCallCheckers) { ProgramPoint::Kind K = ProgramPoint::PostStmtKind; - const ProgramPoint &L = ProgramPoint::getProgramPoint(CE, K, - Pred->getLocationContext(), EI->Checker); + const ProgramPoint &L = + ProgramPoint::getProgramPoint(CE, K, Pred->getLocationContext(), + EvalCallChecker.Checker); bool evaluated = false; { // CheckerContext generates transitions(populates checkDest) on // destruction, so introduce the scope to make sure it gets properly // populated. CheckerContext C(B, Eng, Pred, L); - evaluated = (*EI)(CE, C); + evaluated = EvalCallChecker(CE, C); } assert(!(evaluated && anyEvaluated) && "There are more than one checkers evaluating the call"); @@ -621,21 +681,20 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, } } -/// \brief Run checkers for the entire Translation Unit. +/// Run checkers for the entire Translation Unit. void CheckerManager::runCheckersOnEndOfTranslationUnit( const TranslationUnitDecl *TU, AnalysisManager &mgr, BugReporter &BR) { - for (unsigned i = 0, e = EndOfTranslationUnitCheckers.size(); i != e; ++i) - EndOfTranslationUnitCheckers[i](TU, mgr, BR); + for (const auto EndOfTranslationUnitChecker : EndOfTranslationUnitCheckers) + EndOfTranslationUnitChecker(TU, mgr, BR); } void CheckerManager::runCheckersForPrintState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) { - for (llvm::DenseMap<CheckerTag, CheckerRef>::iterator - I = CheckerTags.begin(), E = CheckerTags.end(); I != E; ++I) - I->second->printState(Out, State, NL, Sep); + for (const auto &CheckerTag : CheckerTags) + CheckerTag.second->printState(Out, State, NL, Sep); } //===----------------------------------------------------------------------===// @@ -661,6 +720,7 @@ void CheckerManager::_registerForPreStmt(CheckStmtFunc checkfn, StmtCheckerInfo info = { checkfn, isForStmtFn, /*IsPreVisit*/true }; StmtCheckers.push_back(info); } + void CheckerManager::_registerForPostStmt(CheckStmtFunc checkfn, HandlesStmtFunc isForStmtFn) { StmtCheckerInfo info = { checkfn, isForStmtFn, /*IsPreVisit*/false }; @@ -711,6 +771,10 @@ void CheckerManager::_registerForBranchCondition( BranchConditionCheckers.push_back(checkfn); } +void CheckerManager::_registerForNewAllocator(CheckNewAllocatorFunc checkfn) { + NewAllocatorCheckers.push_back(checkfn); +} + void CheckerManager::_registerForLiveSymbols(CheckLiveSymbolsFunc checkfn) { LiveSymbolsCheckers.push_back(checkfn); } @@ -760,15 +824,13 @@ CheckerManager::getCachedStmtCheckersFor(const Stmt *S, bool isPreVisit) { // Find the checkers that should run for this Stmt and cache them. CachedStmtCheckers &Checkers = CachedStmtCheckersMap[Key]; - for (unsigned i = 0, e = StmtCheckers.size(); i != e; ++i) { - StmtCheckerInfo &Info = StmtCheckers[i]; + for (const auto &Info : StmtCheckers) if (Info.IsPreVisit == isPreVisit && Info.IsForStmtFn(S)) Checkers.push_back(Info.CheckFn); - } return Checkers; } CheckerManager::~CheckerManager() { - for (unsigned i = 0, e = CheckerDtors.size(); i != e; ++i) - CheckerDtors[i](); + for (const auto CheckerDtor : CheckerDtors) + CheckerDtor(); } diff --git a/lib/StaticAnalyzer/Core/CheckerRegistry.cpp b/lib/StaticAnalyzer/Core/CheckerRegistry.cpp index c9cb189a5b72..645845ec2181 100644 --- a/lib/StaticAnalyzer/Core/CheckerRegistry.cpp +++ b/lib/StaticAnalyzer/Core/CheckerRegistry.cpp @@ -1,4 +1,4 @@ -//===--- CheckerRegistry.cpp - Maintains all available checkers -*- C++ -*-===// +//===- CheckerRegistry.cpp - Maintains all available checkers -------------===// // // The LLVM Compiler Infrastructure // @@ -9,18 +9,26 @@ #include "clang/StaticAnalyzer/Core/CheckerRegistry.h" #include "clang/Basic/Diagnostic.h" +#include "clang/Basic/LLVM.h" #include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/CheckerOptInfo.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SetVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/raw_ostream.h" +#include <algorithm> +#include <cstddef> +#include <tuple> using namespace clang; using namespace ento; static const char PackageSeparator = '.'; -typedef llvm::SetVector<const CheckerRegistry::CheckerInfo *> CheckerInfoSet; +using CheckerInfoSet = llvm::SetVector<const CheckerRegistry::CheckerInfo *>; static bool checkerNameLT(const CheckerRegistry::CheckerInfo &a, const CheckerRegistry::CheckerInfo &b) { @@ -50,8 +58,7 @@ static void collectCheckers(const CheckerRegistry::CheckerInfoList &checkers, // Use a binary search to find the possible start of the package. CheckerRegistry::CheckerInfo packageInfo(nullptr, opt.getName(), ""); auto end = checkers.cend(); - CheckerRegistry::CheckerInfoList::const_iterator i = - std::lower_bound(checkers.cbegin(), end, packageInfo, checkerNameLT); + auto i = std::lower_bound(checkers.cbegin(), end, packageInfo, checkerNameLT); // If we didn't even find a possible package, give up. if (i == end) @@ -73,12 +80,11 @@ static void collectCheckers(const CheckerRegistry::CheckerInfoList &checkers, size = packageSize->getValue(); // Step through all the checkers in the package. - for (auto checkEnd = i+size; i != checkEnd; ++i) { + for (auto checkEnd = i+size; i != checkEnd; ++i) if (opt.isEnabled()) collected.insert(&*i); else collected.remove(&*i); - } } void CheckerRegistry::addChecker(InitializationFunction fn, StringRef name, @@ -97,42 +103,38 @@ void CheckerRegistry::addChecker(InitializationFunction fn, StringRef name, void CheckerRegistry::initializeManager(CheckerManager &checkerMgr, SmallVectorImpl<CheckerOptInfo> &opts) const { // Sort checkers for efficient collection. - std::sort(Checkers.begin(), Checkers.end(), checkerNameLT); + llvm::sort(Checkers.begin(), Checkers.end(), checkerNameLT); // Collect checkers enabled by the options. CheckerInfoSet enabledCheckers; - for (SmallVectorImpl<CheckerOptInfo>::iterator - i = opts.begin(), e = opts.end(); i != e; ++i) { - collectCheckers(Checkers, Packages, *i, enabledCheckers); - } + for (auto &i : opts) + collectCheckers(Checkers, Packages, i, enabledCheckers); // Initialize the CheckerManager with all enabled checkers. - for (CheckerInfoSet::iterator - i = enabledCheckers.begin(), e = enabledCheckers.end(); i != e; ++i) { - checkerMgr.setCurrentCheckName(CheckName((*i)->FullName)); - (*i)->Initialize(checkerMgr); + for (const auto *i :enabledCheckers) { + checkerMgr.setCurrentCheckName(CheckName(i->FullName)); + i->Initialize(checkerMgr); } } void CheckerRegistry::validateCheckerOptions(const AnalyzerOptions &opts, DiagnosticsEngine &diags) const { - for (auto &config : opts.Config) { + for (const auto &config : opts.Config) { size_t pos = config.getKey().find(':'); if (pos == StringRef::npos) continue; bool hasChecker = false; StringRef checkerName = config.getKey().substr(0, pos); - for (auto &checker : Checkers) { + for (const auto &checker : Checkers) { if (checker.FullName.startswith(checkerName) && (checker.FullName.size() == pos || checker.FullName[pos] == '.')) { hasChecker = true; break; } } - if (!hasChecker) { + if (!hasChecker) diags.Report(diag::err_unknown_analyzer_checker) << checkerName; - } } } @@ -141,7 +143,7 @@ void CheckerRegistry::printHelp(raw_ostream &out, // FIXME: Alphabetical sort puts 'experimental' in the middle. // Would it be better to name it '~experimental' or something else // that's ASCIIbetically last? - std::sort(Checkers.begin(), Checkers.end(), checkerNameLT); + llvm::sort(Checkers.begin(), Checkers.end(), checkerNameLT); // FIXME: Print available packages. @@ -149,28 +151,26 @@ void CheckerRegistry::printHelp(raw_ostream &out, // Find the maximum option length. size_t optionFieldWidth = 0; - for (CheckerInfoList::const_iterator i = Checkers.begin(), e = Checkers.end(); - i != e; ++i) { + for (const auto &i : Checkers) { // Limit the amount of padding we are willing to give up for alignment. // Package.Name Description [Hidden] - size_t nameLength = i->FullName.size(); + size_t nameLength = i.FullName.size(); if (nameLength <= maxNameChars) optionFieldWidth = std::max(optionFieldWidth, nameLength); } const size_t initialPad = 2; - for (CheckerInfoList::const_iterator i = Checkers.begin(), e = Checkers.end(); - i != e; ++i) { - out.indent(initialPad) << i->FullName; + for (const auto &i : Checkers) { + out.indent(initialPad) << i.FullName; - int pad = optionFieldWidth - i->FullName.size(); + int pad = optionFieldWidth - i.FullName.size(); // Break on long option names. if (pad < 0) { out << '\n'; pad = optionFieldWidth + initialPad; } - out.indent(pad + 2) << i->Desc; + out.indent(pad + 2) << i.Desc; out << '\n'; } @@ -178,19 +178,13 @@ void CheckerRegistry::printHelp(raw_ostream &out, void CheckerRegistry::printList( raw_ostream &out, SmallVectorImpl<CheckerOptInfo> &opts) const { - std::sort(Checkers.begin(), Checkers.end(), checkerNameLT); + llvm::sort(Checkers.begin(), Checkers.end(), checkerNameLT); // Collect checkers enabled by the options. CheckerInfoSet enabledCheckers; - for (SmallVectorImpl<CheckerOptInfo>::iterator i = opts.begin(), - e = opts.end(); - i != e; ++i) { - collectCheckers(Checkers, Packages, *i, enabledCheckers); - } + for (auto &i : opts) + collectCheckers(Checkers, Packages, i, enabledCheckers); - for (CheckerInfoSet::const_iterator i = enabledCheckers.begin(), - e = enabledCheckers.end(); - i != e; ++i) { - out << (*i)->FullName << '\n'; - } + for (const auto *i : enabledCheckers) + out << i->FullName << '\n'; } diff --git a/lib/StaticAnalyzer/Core/ConstraintManager.cpp b/lib/StaticAnalyzer/Core/ConstraintManager.cpp index 8de2b0e8d271..ef9c44c51be4 100644 --- a/lib/StaticAnalyzer/Core/ConstraintManager.cpp +++ b/lib/StaticAnalyzer/Core/ConstraintManager.cpp @@ -1,4 +1,4 @@ -//== ConstraintManager.cpp - Constraints on symbolic values -----*- C++ -*--==// +//===- ConstraintManager.cpp - Constraints on symbolic values. ------------===// // // The LLVM Compiler Infrastructure // @@ -11,12 +11,17 @@ // //===----------------------------------------------------------------------===// +#include "clang/StaticAnalyzer/Core/PathSensitive/ConstraintManager.h" +#include "clang/AST/Type.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" using namespace clang; using namespace ento; -ConstraintManager::~ConstraintManager() {} +ConstraintManager::~ConstraintManager() = default; static DefinedSVal getLocFromSymbol(const ProgramStateRef &State, SymbolRef Sym) { @@ -35,5 +40,5 @@ ConditionTruthVal ConstraintManager::checkNull(ProgramStateRef State, return ConditionTruthVal(false); if (!P.first && P.second) return ConditionTruthVal(true); - return ConditionTruthVal(); + return {}; } diff --git a/lib/StaticAnalyzer/Core/CoreEngine.cpp b/lib/StaticAnalyzer/Core/CoreEngine.cpp index e2e9ddf5048e..c17b6aae37e2 100644 --- a/lib/StaticAnalyzer/Core/CoreEngine.cpp +++ b/lib/StaticAnalyzer/Core/CoreEngine.cpp @@ -1,4 +1,4 @@ -//==- CoreEngine.cpp - Path-Sensitive Dataflow Engine ------------*- C++ -*-// +//===- CoreEngine.cpp - Path-Sensitive Dataflow Engine --------------------===// // // The LLVM Compiler Infrastructure // @@ -15,11 +15,27 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" +#include "clang/AST/Stmt.h" #include "clang/AST/StmtCXX.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" +#include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/Analysis/CFG.h" +#include "clang/Analysis/ProgramPoint.h" +#include "clang/Basic/LLVM.h" +#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/BlockCounter.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/FunctionSummary.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/WorkList.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/Statistic.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" +#include <algorithm> +#include <cassert> +#include <memory> +#include <utility> using namespace clang; using namespace ento; @@ -34,146 +50,42 @@ STATISTIC(NumPathsExplored, "The # of paths explored by the analyzer."); //===----------------------------------------------------------------------===// -// Worklist classes for exploration of reachable states. +// Core analysis engine. //===----------------------------------------------------------------------===// -WorkList::Visitor::~Visitor() {} - -namespace { -class DFS : public WorkList { - SmallVector<WorkListUnit,20> Stack; -public: - bool hasWork() const override { - return !Stack.empty(); - } - - void enqueue(const WorkListUnit& U) override { - Stack.push_back(U); - } - - WorkListUnit dequeue() override { - assert (!Stack.empty()); - const WorkListUnit& U = Stack.back(); - Stack.pop_back(); // This technically "invalidates" U, but we are fine. - return U; - } - - bool visitItemsInWorkList(Visitor &V) override { - for (SmallVectorImpl<WorkListUnit>::iterator - I = Stack.begin(), E = Stack.end(); I != E; ++I) { - if (V.visit(*I)) - return true; - } - return false; - } -}; - -class BFS : public WorkList { - std::deque<WorkListUnit> Queue; -public: - bool hasWork() const override { - return !Queue.empty(); - } - - void enqueue(const WorkListUnit& U) override { - Queue.push_back(U); - } - - WorkListUnit dequeue() override { - WorkListUnit U = Queue.front(); - Queue.pop_front(); - return U; - } - - bool visitItemsInWorkList(Visitor &V) override { - for (std::deque<WorkListUnit>::iterator - I = Queue.begin(), E = Queue.end(); I != E; ++I) { - if (V.visit(*I)) - return true; - } - return false; +static std::unique_ptr<WorkList> generateWorkList(AnalyzerOptions &Opts) { + switch (Opts.getExplorationStrategy()) { + case AnalyzerOptions::ExplorationStrategyKind::DFS: + return WorkList::makeDFS(); + case AnalyzerOptions::ExplorationStrategyKind::BFS: + return WorkList::makeBFS(); + case AnalyzerOptions::ExplorationStrategyKind::BFSBlockDFSContents: + return WorkList::makeBFSBlockDFSContents(); + case AnalyzerOptions::ExplorationStrategyKind::UnexploredFirst: + return WorkList::makeUnexploredFirst(); + case AnalyzerOptions::ExplorationStrategyKind::UnexploredFirstQueue: + return WorkList::makeUnexploredFirstPriorityQueue(); + default: + llvm_unreachable("Unexpected case"); } -}; - -} // end anonymous namespace - -// Place the dstor for WorkList here because it contains virtual member -// functions, and we the code for the dstor generated in one compilation unit. -WorkList::~WorkList() {} - -WorkList *WorkList::makeDFS() { return new DFS(); } -WorkList *WorkList::makeBFS() { return new BFS(); } - -namespace { - class BFSBlockDFSContents : public WorkList { - std::deque<WorkListUnit> Queue; - SmallVector<WorkListUnit,20> Stack; - public: - bool hasWork() const override { - return !Queue.empty() || !Stack.empty(); - } - - void enqueue(const WorkListUnit& U) override { - if (U.getNode()->getLocation().getAs<BlockEntrance>()) - Queue.push_front(U); - else - Stack.push_back(U); - } - - WorkListUnit dequeue() override { - // Process all basic blocks to completion. - if (!Stack.empty()) { - const WorkListUnit& U = Stack.back(); - Stack.pop_back(); // This technically "invalidates" U, but we are fine. - return U; - } - - assert(!Queue.empty()); - // Don't use const reference. The subsequent pop_back() might make it - // unsafe. - WorkListUnit U = Queue.front(); - Queue.pop_front(); - return U; - } - bool visitItemsInWorkList(Visitor &V) override { - for (SmallVectorImpl<WorkListUnit>::iterator - I = Stack.begin(), E = Stack.end(); I != E; ++I) { - if (V.visit(*I)) - return true; - } - for (std::deque<WorkListUnit>::iterator - I = Queue.begin(), E = Queue.end(); I != E; ++I) { - if (V.visit(*I)) - return true; - } - return false; - } - - }; -} // end anonymous namespace - -WorkList* WorkList::makeBFSBlockDFSContents() { - return new BFSBlockDFSContents(); } -//===----------------------------------------------------------------------===// -// Core analysis engine. -//===----------------------------------------------------------------------===// +CoreEngine::CoreEngine(SubEngine &subengine, FunctionSummariesTy *FS, + AnalyzerOptions &Opts) + : SubEng(subengine), WList(generateWorkList(Opts)), + BCounterFactory(G.getAllocator()), FunctionSummaries(FS) {} /// ExecuteWorkList - Run the worklist algorithm for a maximum number of steps. bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned Steps, ProgramStateRef InitState) { - if (G.num_roots() == 0) { // Initialize the analysis by constructing // the root if none exists. const CFGBlock *Entry = &(L->getCFG()->getEntry()); - assert (Entry->empty() && - "Entry block must be empty."); + assert(Entry->empty() && "Entry block must be empty."); - assert (Entry->succ_size() == 1 && - "Entry block must have 1 successor."); + assert(Entry->succ_size() == 1 && "Entry block must have 1 successor."); // Mark the entry block as visited. FunctionSummaries->markVisitedBasicBlock(Entry->getBlockID(), @@ -195,7 +107,7 @@ bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned Steps, bool IsNew; ExplodedNode *Node = G.getNode(StartLoc, InitState, false, &IsNew); - assert (IsNew); + assert(IsNew); G.addRoot(Node); NodeBuilderContext BuilderCtx(*this, StartLoc.getDst(), Node); @@ -251,13 +163,12 @@ void CoreEngine::dispatchWorkItem(ExplodedNode* Pred, ProgramPoint Loc, break; case ProgramPoint::BlockExitKind: - assert (false && "BlockExit location never occur in forward analysis."); + assert(false && "BlockExit location never occur in forward analysis."); break; - case ProgramPoint::CallEnterKind: { + case ProgramPoint::CallEnterKind: HandleCallEnter(Loc.castAs<CallEnter>(), Pred); break; - } case ProgramPoint::CallExitBeginKind: SubEng.processCallExit(Pred); @@ -275,7 +186,8 @@ void CoreEngine::dispatchWorkItem(ExplodedNode* Pred, ProgramPoint Loc, Loc.getAs<PostInitializer>() || Loc.getAs<PostImplicitCall>() || Loc.getAs<CallExitEnd>() || - Loc.getAs<LoopExit>()); + Loc.getAs<LoopExit>() || + Loc.getAs<PostAllocatorCall>()); HandlePostStmt(WU.getBlock(), WU.getIndex(), Pred); break; } @@ -294,7 +206,6 @@ bool CoreEngine::ExecuteWorkListWithInitialState(const LocationContext *L, } void CoreEngine::HandleBlockEdge(const BlockEdge &L, ExplodedNode *Pred) { - const CFGBlock *Blk = L.getDst(); NodeBuilderContext BuilderCtx(*this, Blk, Pred); @@ -306,18 +217,14 @@ void CoreEngine::HandleBlockEdge(const BlockEdge &L, ExplodedNode *Pred) { // Check if we are entering the EXIT block. if (Blk == &(L.getLocationContext()->getCFG()->getExit())) { - - assert (L.getLocationContext()->getCFG()->getExit().size() == 0 - && "EXIT block cannot contain Stmts."); + assert(L.getLocationContext()->getCFG()->getExit().empty() && + "EXIT block cannot contain Stmts."); // Get return statement.. const ReturnStmt *RS = nullptr; if (!L.getSrc()->empty()) { if (Optional<CFGStmt> LastStmt = L.getSrc()->back().getAs<CFGStmt>()) { - if ((RS = dyn_cast<ReturnStmt>(LastStmt->getStmt()))) { - if (!RS->getRetValue()) - RS = nullptr; - } + RS = dyn_cast<ReturnStmt>(LastStmt->getStmt()); } } @@ -345,12 +252,11 @@ void CoreEngine::HandleBlockEdge(const BlockEdge &L, ExplodedNode *Pred) { void CoreEngine::HandleBlockEntrance(const BlockEntrance &L, ExplodedNode *Pred) { - // Increment the block counter. const LocationContext *LC = Pred->getLocationContext(); unsigned BlockId = L.getBlock()->getBlockID(); BlockCounter Counter = WList->getBlockCounter(); - Counter = BCounterFactory.IncrementCount(Counter, LC->getCurrentStackFrame(), + Counter = BCounterFactory.IncrementCount(Counter, LC->getStackFrame(), BlockId); WList->setBlockCounter(Counter); @@ -364,7 +270,6 @@ void CoreEngine::HandleBlockEntrance(const BlockEntrance &L, } void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { - if (const Stmt *Term = B->getTerminator()) { switch (Term->getStmtClass()) { default: @@ -397,7 +302,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { HandleBranch(cast<ChooseExpr>(Term)->getCond(), Term, B, Pred); return; - case Stmt::CXXTryStmtClass: { + case Stmt::CXXTryStmtClass: // Generate a node for each of the successors. // Our logic for EH analysis can certainly be improved. for (CFGBlock::const_succ_iterator it = B->succ_begin(), @@ -408,7 +313,6 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { } } return; - } case Stmt::DoStmtClass: HandleBranch(cast<DoStmt>(Term)->getCond(), Term, B, Pred); @@ -433,7 +337,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { case Stmt::IndirectGotoStmtClass: { // Only 1 successor: the indirect goto dispatch block. - assert (B->succ_size() == 1); + assert(B->succ_size() == 1); IndirectGotoNodeBuilder builder(Pred, B, cast<IndirectGotoStmt>(Term)->getTarget(), @@ -443,7 +347,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { return; } - case Stmt::ObjCForCollectionStmtClass: { + case Stmt::ObjCForCollectionStmtClass: // In the case of ObjCForCollectionStmt, it appears twice in a CFG: // // (1) inside a basic block, which represents the binding of the @@ -456,7 +360,6 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { // contain nil elements. HandleBranch(Term, Term, B, Pred); return; - } case Stmt::SwitchStmtClass: { SwitchNodeBuilder builder(Pred, B, cast<SwitchStmt>(Term)->getCond(), @@ -472,8 +375,8 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { } } - assert (B->succ_size() == 1 && - "Blocks with no terminator should have at most 1 successor."); + assert(B->succ_size() == 1 && + "Blocks with no terminator should have at most 1 successor."); generateNode(BlockEdge(B, *(B->succ_begin()), Pred->getLocationContext()), Pred->State, Pred); @@ -518,9 +421,8 @@ void CoreEngine::HandleStaticInit(const DeclStmt *DS, const CFGBlock *B, enqueue(Dst); } - void CoreEngine::HandlePostStmt(const CFGBlock *B, unsigned StmtIdx, - ExplodedNode *Pred) { + ExplodedNode *Pred) { assert(B); assert(!B->empty()); @@ -537,14 +439,13 @@ void CoreEngine::HandlePostStmt(const CFGBlock *B, unsigned StmtIdx, void CoreEngine::generateNode(const ProgramPoint &Loc, ProgramStateRef State, ExplodedNode *Pred) { - bool IsNew; ExplodedNode *Node = G.getNode(Loc, State, false, &IsNew); if (Pred) Node->addPredecessor(Pred, G); // Link 'Node' with its predecessor. else { - assert (IsNew); + assert(IsNew); G.addRoot(Node); // 'Node' has no predecessor. Make it a root. } @@ -555,7 +456,7 @@ void CoreEngine::generateNode(const ProgramPoint &Loc, void CoreEngine::enqueueStmtNode(ExplodedNode *N, const CFGBlock *Block, unsigned Idx) { assert(Block); - assert (!N->isSink()); + assert(!N->isSink()); // Check if this node entered a callee. if (N->getLocation().getAs<CallEnter>()) { @@ -605,8 +506,7 @@ void CoreEngine::enqueueStmtNode(ExplodedNode *N, ExplodedNode *CoreEngine::generateCallExitBeginNode(ExplodedNode *N, const ReturnStmt *RS) { // Create a CallExitBegin node and enqueue it. - const StackFrameContext *LocCtx - = cast<StackFrameContext>(N->getLocationContext()); + const auto *LocCtx = cast<StackFrameContext>(N->getLocationContext()); // Use the callee location context. CallExitBegin Loc(LocCtx, RS); @@ -617,40 +517,33 @@ ExplodedNode *CoreEngine::generateCallExitBeginNode(ExplodedNode *N, return isNew ? Node : nullptr; } - void CoreEngine::enqueue(ExplodedNodeSet &Set) { - for (ExplodedNodeSet::iterator I = Set.begin(), - E = Set.end(); I != E; ++I) { - WList->enqueue(*I); - } + for (const auto I : Set) + WList->enqueue(I); } void CoreEngine::enqueue(ExplodedNodeSet &Set, const CFGBlock *Block, unsigned Idx) { - for (ExplodedNodeSet::iterator I = Set.begin(), - E = Set.end(); I != E; ++I) { - enqueueStmtNode(*I, Block, Idx); - } + for (const auto I : Set) + enqueueStmtNode(I, Block, Idx); } void CoreEngine::enqueueEndOfFunction(ExplodedNodeSet &Set, const ReturnStmt *RS) { - for (ExplodedNodeSet::iterator I = Set.begin(), E = Set.end(); I != E; ++I) { - ExplodedNode *N = *I; + for (auto I : Set) { // If we are in an inlined call, generate CallExitBegin node. - if (N->getLocationContext()->getParent()) { - N = generateCallExitBeginNode(N, RS); - if (N) - WList->enqueue(N); + if (I->getLocationContext()->getParent()) { + I = generateCallExitBeginNode(I, RS); + if (I) + WList->enqueue(I); } else { // TODO: We should run remove dead bindings here. - G.addEndOfPath(N); + G.addEndOfPath(I); NumPathsExplored++; } } } - -void NodeBuilder::anchor() { } +void NodeBuilder::anchor() {} ExplodedNode* NodeBuilder::generateNodeImpl(const ProgramPoint &Loc, ProgramStateRef State, @@ -671,16 +564,15 @@ ExplodedNode* NodeBuilder::generateNodeImpl(const ProgramPoint &Loc, return N; } -void NodeBuilderWithSinks::anchor() { } +void NodeBuilderWithSinks::anchor() {} StmtNodeBuilder::~StmtNodeBuilder() { if (EnclosingBldr) - for (ExplodedNodeSet::iterator I = Frontier.begin(), - E = Frontier.end(); I != E; ++I ) - EnclosingBldr->addNodes(*I); + for (const auto I : Frontier) + EnclosingBldr->addNodes(I); } -void BranchNodeBuilder::anchor() { } +void BranchNodeBuilder::anchor() {} ExplodedNode *BranchNodeBuilder::generateNode(ProgramStateRef State, bool branch, @@ -714,11 +606,9 @@ IndirectGotoNodeBuilder::generateNode(const iterator &I, return Succ; } - ExplodedNode* SwitchNodeBuilder::generateCaseStmtNode(const iterator &I, ProgramStateRef St) { - bool IsNew; ExplodedNode *Succ = Eng.G.getNode(BlockEdge(Src, I.getBlock(), Pred->getLocationContext()), @@ -731,7 +621,6 @@ SwitchNodeBuilder::generateCaseStmtNode(const iterator &I, return Succ; } - ExplodedNode* SwitchNodeBuilder::generateDefaultCaseNode(ProgramStateRef St, bool IsSink) { diff --git a/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp b/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp index a01ff36a8aae..530933916889 100644 --- a/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp +++ b/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp @@ -1,4 +1,4 @@ -//==- DynamicTypeMap.cpp - Dynamic Type Info related APIs ----------*- C++ -*-// +//===- DynamicTypeMap.cpp - Dynamic Type Info related APIs ----------------===// // // The LLVM Compiler Infrastructure // @@ -14,6 +14,13 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/Basic/LLVM.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/raw_ostream.h" +#include <cassert> namespace clang { namespace ento { @@ -28,15 +35,15 @@ DynamicTypeInfo getDynamicTypeInfo(ProgramStateRef State, return *GDMType; // Otherwise, fall back to what we know about the region. - if (const TypedRegion *TR = dyn_cast<TypedRegion>(Reg)) + if (const auto *TR = dyn_cast<TypedRegion>(Reg)) return DynamicTypeInfo(TR->getLocationType(), /*CanBeSubclass=*/false); - if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(Reg)) { + if (const auto *SR = dyn_cast<SymbolicRegion>(Reg)) { SymbolRef Sym = SR->getSymbol(); return DynamicTypeInfo(Sym->getType()); } - return DynamicTypeInfo(); + return {}; } ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *Reg, @@ -47,5 +54,28 @@ ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *Reg, return NewState; } +void printDynamicTypeInfo(ProgramStateRef State, raw_ostream &Out, + const char *NL, const char *Sep) { + bool First = true; + for (const auto &I : State->get<DynamicTypeMap>()) { + if (First) { + Out << NL << "Dynamic types of regions:" << NL; + First = false; + } + const MemRegion *MR = I.first; + const DynamicTypeInfo &DTI = I.second; + Out << MR << " : "; + if (DTI.isValid()) { + Out << DTI.getType()->getPointeeType().getAsString(); + if (DTI.canBeASubClass()) { + Out << " (or its subclass)"; + } + } else { + Out << "Invalid type info"; + } + Out << NL; + } +} + } // namespace ento } // namespace clang diff --git a/lib/StaticAnalyzer/Core/Environment.cpp b/lib/StaticAnalyzer/Core/Environment.cpp index c6acb9d1851c..eccaee292c40 100644 --- a/lib/StaticAnalyzer/Core/Environment.cpp +++ b/lib/StaticAnalyzer/Core/Environment.cpp @@ -1,4 +1,4 @@ -//== Environment.cpp - Map from Stmt* to Locations/Values -------*- C++ -*--==// +//===- Environment.cpp - Map from Stmt* to Locations/Values ---------------===// // // The LLVM Compiler Infrastructure // @@ -11,12 +11,25 @@ // //===----------------------------------------------------------------------===// +#include "clang/StaticAnalyzer/Core/PathSensitive/Environment.h" +#include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" -#include "clang/AST/ExprObjC.h" +#include "clang/AST/PrettyPrinter.h" +#include "clang/AST/Stmt.h" #include "clang/Analysis/AnalysisDeclContext.h" -#include "clang/Analysis/CFG.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/LangOptions.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include "llvm/ADT/ImmutableMap.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" +#include <cassert> using namespace clang; using namespace ento; @@ -46,16 +59,16 @@ static const Expr *ignoreTransparentExprs(const Expr *E) { } static const Stmt *ignoreTransparentExprs(const Stmt *S) { - if (const Expr *E = dyn_cast<Expr>(S)) + if (const auto *E = dyn_cast<Expr>(S)) return ignoreTransparentExprs(E); return S; } EnvironmentEntry::EnvironmentEntry(const Stmt *S, const LocationContext *L) - : std::pair<const Stmt *, - const StackFrameContext *>(ignoreTransparentExprs(S), - L ? L->getCurrentStackFrame() - : nullptr) {} + : std::pair<const Stmt *, + const StackFrameContext *>(ignoreTransparentExprs(S), + L ? L->getStackFrame() + : nullptr) {} SVal Environment::lookupExpr(const EnvironmentEntry &E) const { const SVal* X = ExprBindings.lookup(E); @@ -95,7 +108,7 @@ SVal Environment::getSVal(const EnvironmentEntry &Entry, return svalBuilder.getConstantVal(cast<Expr>(S)).getValue(); case Stmt::ReturnStmtClass: { - const ReturnStmt *RS = cast<ReturnStmt>(S); + const auto *RS = cast<ReturnStmt>(S); if (const Expr *RE = RS->getRetValue()) return getSVal(EnvironmentEntry(RE, LCtx), svalBuilder); return UndefinedVal(); @@ -121,20 +134,25 @@ Environment EnvironmentManager::bindExpr(Environment Env, } namespace { + class MarkLiveCallback final : public SymbolVisitor { SymbolReaper &SymReaper; + public: MarkLiveCallback(SymbolReaper &symreaper) : SymReaper(symreaper) {} + bool VisitSymbol(SymbolRef sym) override { SymReaper.markLive(sym); return true; } + bool VisitMemRegion(const MemRegion *R) override { SymReaper.markLive(R); return true; } }; -} // end anonymous namespace + +} // namespace // removeDeadBindings: // - Remove subexpression bindings. @@ -147,7 +165,6 @@ Environment EnvironmentManager::removeDeadBindings(Environment Env, SymbolReaper &SymReaper, ProgramStateRef ST) { - // We construct a new Environment object entirely, as this is cheaper than // individually removing all the subexpression bindings (which will greatly // outnumber block-level expression bindings). @@ -156,14 +173,13 @@ EnvironmentManager::removeDeadBindings(Environment Env, MarkLiveCallback CB(SymReaper); ScanReachableSymbols RSScaner(ST, CB); - llvm::ImmutableMapRef<EnvironmentEntry,SVal> + llvm::ImmutableMapRef<EnvironmentEntry, SVal> EBMapRef(NewEnv.ExprBindings.getRootWithoutRetain(), F.getTreeFactory()); // Iterate over the block-expr bindings. for (Environment::iterator I = Env.begin(), E = Env.end(); I != E; ++I) { - const EnvironmentEntry &BlkExpr = I.getKey(); const SVal &X = I.getData(); @@ -186,28 +202,41 @@ EnvironmentManager::removeDeadBindings(Environment Env, } void Environment::print(raw_ostream &Out, const char *NL, - const char *Sep) const { - bool isFirst = true; + const char *Sep, const LocationContext *WithLC) const { + if (ExprBindings.isEmpty()) + return; + + if (!WithLC) { + // Find the freshest location context. + llvm::SmallPtrSet<const LocationContext *, 16> FoundContexts; + for (auto I : *this) { + const LocationContext *LC = I.first.getLocationContext(); + if (FoundContexts.count(LC) == 0) { + // This context is fresher than all other contexts so far. + WithLC = LC; + for (const LocationContext *LCI = LC; LCI; LCI = LCI->getParent()) + FoundContexts.insert(LCI); + } + } + } - for (Environment::iterator I = begin(), E = end(); I != E; ++I) { - const EnvironmentEntry &En = I.getKey(); + assert(WithLC); - if (isFirst) { - Out << NL << NL - << "Expressions:" - << NL; - isFirst = false; - } else { - Out << NL; - } + LangOptions LO; // FIXME. + PrintingPolicy PP(LO); - const Stmt *S = En.getStmt(); - assert(S != nullptr && "Expected non-null Stmt"); + Out << NL << NL << "Expressions by stack frame:" << NL; + WithLC->dumpStack(Out, "", NL, Sep, [&](const LocationContext *LC) { + for (auto I : ExprBindings) { + if (I.first.getLocationContext() != LC) + continue; - Out << " (" << (const void*) En.getLocationContext() << ',' - << (const void*) S << ") "; - LangOptions LO; // FIXME. - S->printPretty(Out, nullptr, PrintingPolicy(LO)); - Out << " : " << I.getData(); - } + const Stmt *S = I.first.getStmt(); + assert(S != nullptr && "Expected non-null Stmt"); + + Out << "(" << (const void *)LC << ',' << (const void *)S << ") "; + S->printPretty(Out, nullptr, PP); + Out << " : " << I.second << NL; + } + }); } diff --git a/lib/StaticAnalyzer/Core/ExplodedGraph.cpp b/lib/StaticAnalyzer/Core/ExplodedGraph.cpp index 3bc8e09333b9..ece103d9d09a 100644 --- a/lib/StaticAnalyzer/Core/ExplodedGraph.cpp +++ b/lib/StaticAnalyzer/Core/ExplodedGraph.cpp @@ -1,4 +1,4 @@ -//=-- ExplodedGraph.cpp - Local, Path-Sens. "Exploded Graph" -*- C++ -*------=// +//===- ExplodedGraph.cpp - Local, Path-Sens. "Exploded Graph" -------------===// // // The LLVM Compiler Infrastructure // @@ -13,13 +13,24 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprObjC.h" #include "clang/AST/ParentMap.h" #include "clang/AST/Stmt.h" +#include "clang/Analysis/ProgramPoint.h" +#include "clang/Analysis/Support/BumpVector.h" +#include "clang/Basic/LLVM.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" #include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/PointerUnion.h" #include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/Statistic.h" +#include "llvm/Support/Casting.h" +#include <cassert> +#include <memory> using namespace clang; using namespace ento; @@ -29,7 +40,7 @@ using namespace ento; //===----------------------------------------------------------------------===// // An out of line virtual method to provide a home for the class vtable. -ExplodedNode::Auditor::~Auditor() {} +ExplodedNode::Auditor::~Auditor() = default; #ifndef NDEBUG static ExplodedNode::Auditor* NodeAuditor = nullptr; @@ -45,10 +56,9 @@ void ExplodedNode::SetAuditor(ExplodedNode::Auditor* A) { // Cleanup. //===----------------------------------------------------------------------===// -ExplodedGraph::ExplodedGraph() - : NumNodes(0), ReclaimNodeInterval(0) {} +ExplodedGraph::ExplodedGraph() = default; -ExplodedGraph::~ExplodedGraph() {} +ExplodedGraph::~ExplodedGraph() = default; //===----------------------------------------------------------------------===// // Node reclamation. @@ -187,12 +197,9 @@ void ExplodedGraph::reclaimRecentlyAllocatedNodes() { return; ReclaimCounter = ReclaimNodeInterval; - for (NodeVector::iterator it = ChangedNodes.begin(), et = ChangedNodes.end(); - it != et; ++it) { - ExplodedNode *node = *it; + for (const auto node : ChangedNodes) if (shouldCollect(node)) collectNode(node); - } ChangedNodes.clear(); } @@ -210,11 +217,11 @@ void ExplodedGraph::reclaimRecentlyAllocatedNodes() { // 2. The group is empty, in which case the storage value is null. // 3. The group contains a single node. // 4. The group contains more than one node. -typedef BumpVector<ExplodedNode *> ExplodedNodeVector; -typedef llvm::PointerUnion<ExplodedNode *, ExplodedNodeVector *> GroupStorage; +using ExplodedNodeVector = BumpVector<ExplodedNode *>; +using GroupStorage = llvm::PointerUnion<ExplodedNode *, ExplodedNodeVector *>; void ExplodedNode::addPredecessor(ExplodedNode *V, ExplodedGraph &G) { - assert (!V->isSink()); + assert(!V->isSink()); Preds.addNode(V, G); V->Succs.addNode(this, G); #ifndef NDEBUG @@ -346,25 +353,22 @@ std::unique_ptr<ExplodedGraph> ExplodedGraph::trim(ArrayRef<const NodeTy *> Sinks, InterExplodedGraphMap *ForwardMap, InterExplodedGraphMap *InverseMap) const { - if (Nodes.empty()) return nullptr; - typedef llvm::DenseSet<const ExplodedNode*> Pass1Ty; + using Pass1Ty = llvm::DenseSet<const ExplodedNode *>; Pass1Ty Pass1; - typedef InterExplodedGraphMap Pass2Ty; + using Pass2Ty = InterExplodedGraphMap; InterExplodedGraphMap Pass2Scratch; Pass2Ty &Pass2 = ForwardMap ? *ForwardMap : Pass2Scratch; SmallVector<const ExplodedNode*, 10> WL1, WL2; // ===- Pass 1 (reverse DFS) -=== - for (ArrayRef<const NodeTy *>::iterator I = Sinks.begin(), E = Sinks.end(); - I != E; ++I) { - if (*I) - WL1.push_back(*I); - } + for (const auto Sink : Sinks) + if (Sink) + WL1.push_back(Sink); // Process the first worklist until it is empty. while (!WL1.empty()) { @@ -445,4 +449,3 @@ ExplodedGraph::trim(ArrayRef<const NodeTy *> Sinks, return G; } - diff --git a/lib/StaticAnalyzer/Core/ExprEngine.cpp b/lib/StaticAnalyzer/Core/ExprEngine.cpp index 3be37e7ae301..188316c096e3 100644 --- a/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1,4 +1,4 @@ -//=-- ExprEngine.cpp - Path-Sensitive Expression-Level Dataflow ---*- C++ -*-= +//===- ExprEngine.cpp - Path-Sensitive Expression-Level Dataflow ----------===// // // The LLVM Compiler Infrastructure // @@ -15,31 +15,75 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "PrettyStackTraceLocationContext.h" -#include "clang/AST/CharUnits.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/ExprObjC.h" #include "clang/AST/ParentMap.h" -#include "clang/Analysis/CFGStmtMap.h" +#include "clang/AST/PrettyPrinter.h" +#include "clang/AST/Stmt.h" #include "clang/AST/StmtCXX.h" #include "clang/AST/StmtObjC.h" -#include "clang/Basic/Builtins.h" +#include "clang/AST/Type.h" +#include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/Analysis/CFG.h" +#include "clang/Analysis/ConstructionContext.h" +#include "clang/Analysis/ProgramPoint.h" +#include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/LangOptions.h" #include "clang/Basic/PrettyStackTrace.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" +#include "clang/Basic/Specifiers.h" +#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/LoopWidening.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ConstraintManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" #include "clang/StaticAnalyzer/Core/PathSensitive/LoopUnrolling.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/LoopWidening.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/Store.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/ImmutableMap.h" +#include "llvm/ADT/ImmutableSet.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Statistic.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/DOTGraphTraits.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/GraphWriter.h" #include "llvm/Support/SaveAndRestore.h" #include "llvm/Support/raw_ostream.h" - -#ifndef NDEBUG -#include "llvm/Support/GraphWriter.h" -#endif +#include <cassert> +#include <cstdint> +#include <memory> +#include <string> +#include <tuple> +#include <utility> +#include <vector> using namespace clang; using namespace ento; -using llvm::APSInt; #define DEBUG_TYPE "ExprEngine" @@ -54,14 +98,100 @@ STATISTIC(NumMaxBlockCountReachedInInlined, STATISTIC(NumTimesRetriedWithoutInlining, "The # of times we re-evaluated a call without inlining"); -typedef std::pair<const CXXBindTemporaryExpr *, const StackFrameContext *> - CXXBindTemporaryContext; -// Keeps track of whether CXXBindTemporaryExpr nodes have been evaluated. -// The StackFrameContext assures that nested calls due to inlined recursive -// functions do not interfere. -REGISTER_TRAIT_WITH_PROGRAMSTATE(InitializedTemporariesSet, - llvm::ImmutableSet<CXXBindTemporaryContext>) +//===----------------------------------------------------------------------===// +// Internal program state traits. +//===----------------------------------------------------------------------===// + +// When modeling a C++ constructor, for a variety of reasons we need to track +// the location of the object for the duration of its ConstructionContext. +// ObjectsUnderConstruction maps statements within the construction context +// to the object's location, so that on every such statement the location +// could have been retrieved. + +/// ConstructedObjectKey is used for being able to find the path-sensitive +/// memory region of a freshly constructed object while modeling the AST node +/// that syntactically represents the object that is being constructed. +/// Semantics of such nodes may sometimes require access to the region that's +/// not otherwise present in the program state, or to the very fact that +/// the construction context was present and contained references to these +/// AST nodes. +class ConstructedObjectKey { + typedef std::pair< + llvm::PointerUnion<const Stmt *, const CXXCtorInitializer *>, + const LocationContext *> ConstructedObjectKeyImpl; + + ConstructedObjectKeyImpl Impl; + + const void *getAnyASTNodePtr() const { + if (const Stmt *S = getStmt()) + return S; + else + return getCXXCtorInitializer(); + } + +public: + ConstructedObjectKey( + llvm::PointerUnion<const Stmt *, const CXXCtorInitializer *> P, + const LocationContext *LC) + : Impl(P, LC) { + // This is the full list of statements that require additional actions when + // encountered. This list may be expanded when new actions are implemented. + assert(getCXXCtorInitializer() || isa<DeclStmt>(getStmt()) || + isa<CXXNewExpr>(getStmt()) || isa<CXXBindTemporaryExpr>(getStmt()) || + isa<MaterializeTemporaryExpr>(getStmt()) || + isa<CXXConstructExpr>(getStmt())); + } + + const Stmt *getStmt() const { + return Impl.first.dyn_cast<const Stmt *>(); + } + + const CXXCtorInitializer *getCXXCtorInitializer() const { + return Impl.first.dyn_cast<const CXXCtorInitializer *>(); + } + + const LocationContext *getLocationContext() const { + return Impl.second; + } + + void print(llvm::raw_ostream &OS, PrinterHelper *Helper, PrintingPolicy &PP) { + OS << '(' << getLocationContext() << ',' << getAnyASTNodePtr() << ") "; + if (const Stmt *S = getStmt()) { + S->printPretty(OS, Helper, PP); + } else { + const CXXCtorInitializer *I = getCXXCtorInitializer(); + OS << I->getAnyMember()->getNameAsString(); + } + } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddPointer(Impl.first.getOpaqueValue()); + ID.AddPointer(Impl.second); + } + + bool operator==(const ConstructedObjectKey &RHS) const { + return Impl == RHS.Impl; + } + + bool operator<(const ConstructedObjectKey &RHS) const { + return Impl < RHS.Impl; + } +}; + +typedef llvm::ImmutableMap<ConstructedObjectKey, SVal> + ObjectsUnderConstructionMap; +REGISTER_TRAIT_WITH_PROGRAMSTATE(ObjectsUnderConstruction, + ObjectsUnderConstructionMap) + +// Additionally, track a set of destructors that correspond to elided +// constructors when copy elision occurs. +typedef std::pair<const CXXBindTemporaryExpr *, const LocationContext *> + ElidedDestructorItem; +typedef llvm::ImmutableSet<ElidedDestructorItem> + ElidedDestructorSet; +REGISTER_TRAIT_WITH_PROGRAMSTATE(ElidedDestructors, + ElidedDestructorSet) //===----------------------------------------------------------------------===// // Engine construction and deletion. @@ -69,25 +199,21 @@ REGISTER_TRAIT_WITH_PROGRAMSTATE(InitializedTemporariesSet, static const char* TagProviderName = "ExprEngine"; -ExprEngine::ExprEngine(AnalysisManager &mgr, bool gcEnabled, +ExprEngine::ExprEngine(cross_tu::CrossTranslationUnitContext &CTU, + AnalysisManager &mgr, bool gcEnabled, SetOfConstDecls *VisitedCalleesIn, FunctionSummariesTy *FS, InliningModes HowToInlineIn) - : AMgr(mgr), - AnalysisDeclContexts(mgr.getAnalysisDeclContextManager()), - Engine(*this, FS), - G(Engine.getGraph()), - StateMgr(getContext(), mgr.getStoreManagerCreator(), - mgr.getConstraintManagerCreator(), G.getAllocator(), - this), - SymMgr(StateMgr.getSymbolManager()), - svalBuilder(StateMgr.getSValBuilder()), - currStmtIdx(0), currBldrCtx(nullptr), - ObjCNoRet(mgr.getASTContext()), - ObjCGCEnabled(gcEnabled), BR(mgr, *this), - VisitedCallees(VisitedCalleesIn), - HowToInline(HowToInlineIn) -{ + : CTU(CTU), AMgr(mgr), + AnalysisDeclContexts(mgr.getAnalysisDeclContextManager()), + Engine(*this, FS, mgr.getAnalyzerOptions()), G(Engine.getGraph()), + StateMgr(getContext(), mgr.getStoreManagerCreator(), + mgr.getConstraintManagerCreator(), G.getAllocator(), + this), + SymMgr(StateMgr.getSymbolManager()), + svalBuilder(StateMgr.getSValBuilder()), ObjCNoRet(mgr.getASTContext()), + ObjCGCEnabled(gcEnabled), BR(mgr, *this), + VisitedCallees(VisitedCalleesIn), HowToInline(HowToInlineIn) { unsigned TrimInterval = mgr.options.getGraphTrimInterval(); if (TrimInterval != 0) { // Enable eager node reclaimation when constructing the ExplodedGraph. @@ -111,8 +237,7 @@ ProgramStateRef ExprEngine::getInitialState(const LocationContext *InitLoc) { // FIXME: It would be nice if we had a more general mechanism to add // such preconditions. Some day. do { - - if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) { + if (const auto *FD = dyn_cast<FunctionDecl>(D)) { // Precondition: the first argument of 'main' is an integer guaranteed // to be > 0. const IdentifierInfo *II = FD->getIdentifier(); @@ -121,7 +246,7 @@ ProgramStateRef ExprEngine::getInitialState(const LocationContext *InitLoc) { const ParmVarDecl *PD = FD->getParamDecl(0); QualType T = PD->getType(); - const BuiltinType *BT = dyn_cast<BuiltinType>(T); + const auto *BT = dyn_cast<BuiltinType>(T); if (!BT || !BT->isInteger()) break; @@ -145,9 +270,9 @@ ProgramStateRef ExprEngine::getInitialState(const LocationContext *InitLoc) { } break; } - while (0); + while (false); - if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) { + if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) { // Precondition: 'self' is always non-null upon entry to an Objective-C // method. const ImplicitParamDecl *SelfD = MD->getSelfDecl(); @@ -161,12 +286,12 @@ ProgramStateRef ExprEngine::getInitialState(const LocationContext *InitLoc) { } } - if (const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(D)) { + if (const auto *MD = dyn_cast<CXXMethodDecl>(D)) { if (!MD->isStatic()) { // Precondition: 'this' is always non-null upon entry to the // top-level function. This is our starting assumption for // analyzing an "open" program. - const StackFrameContext *SFC = InitLoc->getCurrentStackFrame(); + const StackFrameContext *SFC = InitLoc->getStackFrame(); if (SFC->getParent() == nullptr) { loc::MemRegionVal L = svalBuilder.getCXXThis(MD, SFC); SVal V = state->getSVal(L); @@ -237,17 +362,30 @@ ExprEngine::createTemporaryRegionIfNeeded(ProgramStateRef State, const Expr *Init = InitWithAdjustments->skipRValueSubobjectAdjustments( CommaLHSs, Adjustments); + // Take the region for Init, i.e. for the whole object. If we do not remember + // the region in which the object originally was constructed, come up with + // a new temporary region out of thin air and copy the contents of the object + // (which are currently present in the Environment, because Init is an rvalue) + // into that region. This is not correct, but it is better than nothing. const TypedValueRegion *TR = nullptr; - if (const MaterializeTemporaryExpr *MT = - dyn_cast<MaterializeTemporaryExpr>(Result)) { - StorageDuration SD = MT->getStorageDuration(); - // If this object is bound to a reference with static storage duration, we - // put it in a different region to prevent "address leakage" warnings. - if (SD == SD_Static || SD == SD_Thread) - TR = MRMgr.getCXXStaticTempObjectRegion(Init); - } - if (!TR) + if (const auto *MT = dyn_cast<MaterializeTemporaryExpr>(Result)) { + if (Optional<SVal> V = getObjectUnderConstruction(State, MT, LC)) { + State = finishObjectConstruction(State, MT, LC); + State = State->BindExpr(Result, LC, *V); + return State; + } else { + StorageDuration SD = MT->getStorageDuration(); + // If this object is bound to a reference with static storage duration, we + // put it in a different region to prevent "address leakage" warnings. + if (SD == SD_Static || SD == SD_Thread) { + TR = MRMgr.getCXXStaticTempObjectRegion(Init); + } else { + TR = MRMgr.getCXXTempObjectRegion(Init, LC); + } + } + } else { TR = MRMgr.getCXXTempObjectRegion(Init, LC); + } SVal Reg = loc::MemRegionVal(TR); SVal BaseReg = Reg; @@ -264,7 +402,9 @@ ExprEngine::createTemporaryRegionIfNeeded(ProgramStateRef State, break; case SubobjectAdjustment::MemberPointerAdjustment: // FIXME: Unimplemented. - State = State->bindDefault(Reg, UnknownVal(), LC); + State = State->invalidateRegions(Reg, InitWithAdjustments, + currBldrCtx->blockCount(), LC, true, + nullptr, nullptr, nullptr); return State; } } @@ -283,7 +423,8 @@ ExprEngine::createTemporaryRegionIfNeeded(ProgramStateRef State, currBldrCtx->blockCount()); State = State->bindLoc(BaseReg.castAs<Loc>(), InitVal, LC, false); - // Then we'd need to take the value that certainly exists and bind it over. + // Then we'd need to take the value that certainly exists and bind it + // over. if (InitValWithAdjustments.isUnknown()) { // Try to recover some path sensitivity in case we couldn't // compute the value. @@ -308,6 +449,79 @@ ExprEngine::createTemporaryRegionIfNeeded(ProgramStateRef State, return State; } +ProgramStateRef ExprEngine::addObjectUnderConstruction( + ProgramStateRef State, + llvm::PointerUnion<const Stmt *, const CXXCtorInitializer *> P, + const LocationContext *LC, SVal V) { + ConstructedObjectKey Key(P, LC->getStackFrame()); + // FIXME: Currently the state might already contain the marker due to + // incorrect handling of temporaries bound to default parameters. + assert(!State->get<ObjectsUnderConstruction>(Key) || + isa<CXXBindTemporaryExpr>(Key.getStmt())); + return State->set<ObjectsUnderConstruction>(Key, V); +} + +Optional<SVal> ExprEngine::getObjectUnderConstruction( + ProgramStateRef State, + llvm::PointerUnion<const Stmt *, const CXXCtorInitializer *> P, + const LocationContext *LC) { + ConstructedObjectKey Key(P, LC->getStackFrame()); + return Optional<SVal>::create(State->get<ObjectsUnderConstruction>(Key)); +} + +ProgramStateRef ExprEngine::finishObjectConstruction( + ProgramStateRef State, + llvm::PointerUnion<const Stmt *, const CXXCtorInitializer *> P, + const LocationContext *LC) { + ConstructedObjectKey Key(P, LC->getStackFrame()); + assert(State->contains<ObjectsUnderConstruction>(Key)); + return State->remove<ObjectsUnderConstruction>(Key); +} + +ProgramStateRef ExprEngine::elideDestructor(ProgramStateRef State, + const CXXBindTemporaryExpr *BTE, + const LocationContext *LC) { + ElidedDestructorItem I(BTE, LC); + assert(!State->contains<ElidedDestructors>(I)); + return State->add<ElidedDestructors>(I); +} + +ProgramStateRef +ExprEngine::cleanupElidedDestructor(ProgramStateRef State, + const CXXBindTemporaryExpr *BTE, + const LocationContext *LC) { + ElidedDestructorItem I(BTE, LC); + assert(State->contains<ElidedDestructors>(I)); + return State->remove<ElidedDestructors>(I); +} + +bool ExprEngine::isDestructorElided(ProgramStateRef State, + const CXXBindTemporaryExpr *BTE, + const LocationContext *LC) { + ElidedDestructorItem I(BTE, LC); + return State->contains<ElidedDestructors>(I); +} + +bool ExprEngine::areAllObjectsFullyConstructed(ProgramStateRef State, + const LocationContext *FromLC, + const LocationContext *ToLC) { + const LocationContext *LC = FromLC; + while (LC != ToLC) { + assert(LC && "ToLC must be a parent of FromLC!"); + for (auto I : State->get<ObjectsUnderConstruction>()) + if (I.first.getLocationContext() == LC) + return false; + + for (auto I: State->get<ElidedDestructors>()) + if (I.second == LC) + return false; + + LC = LC->getParent(); + } + return true; +} + + //===----------------------------------------------------------------------===// // Top-level transfer function logic (Dispatcher). //===----------------------------------------------------------------------===// @@ -331,8 +545,44 @@ ExprEngine::processRegionChanges(ProgramStateRef state, LCtx, Call); } +static void printObjectsUnderConstructionForContext(raw_ostream &Out, + ProgramStateRef State, + const char *NL, + const char *Sep, + const LocationContext *LC) { + PrintingPolicy PP = + LC->getAnalysisDeclContext()->getASTContext().getPrintingPolicy(); + for (auto I : State->get<ObjectsUnderConstruction>()) { + ConstructedObjectKey Key = I.first; + SVal Value = I.second; + if (Key.getLocationContext() != LC) + continue; + Key.print(Out, nullptr, PP); + Out << " : " << Value << NL; + } + + for (auto I : State->get<ElidedDestructors>()) { + if (I.second != LC) + continue; + Out << '(' << I.second << ',' << (const void *)I.first << ") "; + I.first->printPretty(Out, nullptr, PP); + Out << " : (constructor elided)" << NL; + } +} + void ExprEngine::printState(raw_ostream &Out, ProgramStateRef State, - const char *NL, const char *Sep) { + const char *NL, const char *Sep, + const LocationContext *LCtx) { + if (LCtx) { + if (!State->get<ObjectsUnderConstruction>().isEmpty()) { + Out << Sep << "Objects under construction:" << NL; + + LCtx->dumpStack(Out, "", NL, Sep, [&](const LocationContext *LC) { + printObjectsUnderConstructionForContext(Out, State, NL, Sep, LC); + }); + } + } + getCheckerManager().runCheckersForPrintState(Out, State, NL, Sep); } @@ -348,10 +598,12 @@ void ExprEngine::processCFGElement(const CFGElement E, ExplodedNode *Pred, switch (E.getKind()) { case CFGElement::Statement: - ProcessStmt(const_cast<Stmt*>(E.castAs<CFGStmt>().getStmt()), Pred); + case CFGElement::Constructor: + case CFGElement::CXXRecordTypedCall: + ProcessStmt(E.castAs<CFGStmt>().getStmt(), Pred); return; case CFGElement::Initializer: - ProcessInitializer(E.castAs<CFGInitializer>().getInitializer(), Pred); + ProcessInitializer(E.castAs<CFGInitializer>(), Pred); return; case CFGElement::NewAllocator: ProcessNewAllocator(E.castAs<CFGNewAllocator>().getAllocatorExpr(), @@ -368,15 +620,16 @@ void ExprEngine::processCFGElement(const CFGElement E, ExplodedNode *Pred, ProcessLoopExit(E.castAs<CFGLoopExit>().getLoopStmt(), Pred); return; case CFGElement::LifetimeEnds: + case CFGElement::ScopeBegin: + case CFGElement::ScopeEnd: return; } } static bool shouldRemoveDeadBindings(AnalysisManager &AMgr, - const CFGStmt S, + const Stmt *S, const ExplodedNode *Pred, const LocationContext *LC) { - // Are we never purging state values? if (AMgr.options.AnalysisPurgeOpt == PurgeNone) return false; @@ -386,17 +639,17 @@ static bool shouldRemoveDeadBindings(AnalysisManager &AMgr, return true; // Is this on a non-expression? - if (!isa<Expr>(S.getStmt())) + if (!isa<Expr>(S)) return true; // Run before processing a call. - if (CallEvent::isCallStmt(S.getStmt())) + if (CallEvent::isCallStmt(S)) return true; // Is this an expression that is consumed by another expression? If so, // postpone cleaning out the state. ParentMap &PM = LC->getAnalysisDeclContext()->getParentMap(); - return !PM.isConsumedExpr(cast<Expr>(S.getStmt())); + return !PM.isConsumedExpr(cast<Expr>(S)); } void ExprEngine::removeDead(ExplodedNode *Pred, ExplodedNodeSet &Out, @@ -426,9 +679,16 @@ void ExprEngine::removeDead(ExplodedNode *Pred, ExplodedNodeSet &Out, LC = LC->getParent(); } - const StackFrameContext *SFC = LC ? LC->getCurrentStackFrame() : nullptr; + const StackFrameContext *SFC = LC ? LC->getStackFrame() : nullptr; SymbolReaper SymReaper(SFC, ReferenceStmt, SymMgr, getStoreManager()); + for (auto I : CleanedState->get<ObjectsUnderConstruction>()) { + if (SymbolRef Sym = I.second.getAsSymbol()) + SymReaper.markLive(Sym); + if (const MemRegion *MR = I.second.getAsRegion()) + SymReaper.markLive(MR); + } + getCheckerManager().runCheckersForLiveSymbols(CleanedState, SymReaper); // Create a state in which dead bindings are removed from the environment @@ -457,9 +717,8 @@ void ExprEngine::removeDead(ExplodedNode *Pred, ExplodedNodeSet &Out, // environment, the store, and the constraints cleaned up but have the // user-supplied states as the predecessors. StmtNodeBuilder Bldr(CheckedSet, Out, *currBldrCtx); - for (ExplodedNodeSet::const_iterator - I = CheckedSet.begin(), E = CheckedSet.end(); I != E; ++I) { - ProgramStateRef CheckerState = (*I)->getState(); + for (const auto I : CheckedSet) { + ProgramStateRef CheckerState = I->getState(); // The constraint manager has not been cleaned up yet, so clean up now. CheckerState = getConstraintManager().removeDeadBindings(CheckerState, @@ -476,35 +735,34 @@ void ExprEngine::removeDead(ExplodedNode *Pred, ExplodedNodeSet &Out, // generate a transition to that state. ProgramStateRef CleanedCheckerSt = StateMgr.getPersistentStateWithGDM(CleanedState, CheckerState); - Bldr.generateNode(DiagnosticStmt, *I, CleanedCheckerSt, &cleanupTag, K); + Bldr.generateNode(DiagnosticStmt, I, CleanedCheckerSt, &cleanupTag, K); } } } -void ExprEngine::ProcessStmt(const CFGStmt S, - ExplodedNode *Pred) { +void ExprEngine::ProcessStmt(const Stmt *currStmt, ExplodedNode *Pred) { // Reclaim any unnecessary nodes in the ExplodedGraph. G.reclaimRecentlyAllocatedNodes(); - const Stmt *currStmt = S.getStmt(); PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(), currStmt->getLocStart(), "Error evaluating statement"); // Remove dead bindings and symbols. ExplodedNodeSet CleanedStates; - if (shouldRemoveDeadBindings(AMgr, S, Pred, Pred->getLocationContext())){ - removeDead(Pred, CleanedStates, currStmt, Pred->getLocationContext()); + if (shouldRemoveDeadBindings(AMgr, currStmt, Pred, + Pred->getLocationContext())) { + removeDead(Pred, CleanedStates, currStmt, + Pred->getLocationContext()); } else CleanedStates.Add(Pred); // Visit the statement. ExplodedNodeSet Dst; - for (ExplodedNodeSet::iterator I = CleanedStates.begin(), - E = CleanedStates.end(); I != E; ++I) { + for (const auto I : CleanedStates) { ExplodedNodeSet DstI; // Visit the statement. - Visit(currStmt, *I, DstI); + Visit(currStmt, I, DstI); Dst.insert(DstI); } @@ -530,36 +788,38 @@ void ExprEngine::ProcessLoopExit(const Stmt* S, ExplodedNode *Pred) { Engine.enqueue(Dst, currBldrCtx->getBlock(), currStmtIdx); } -void ExprEngine::ProcessInitializer(const CFGInitializer Init, +void ExprEngine::ProcessInitializer(const CFGInitializer CFGInit, ExplodedNode *Pred) { - const CXXCtorInitializer *BMI = Init.getInitializer(); + const CXXCtorInitializer *BMI = CFGInit.getInitializer(); + const Expr *Init = BMI->getInit()->IgnoreImplicit(); + const LocationContext *LC = Pred->getLocationContext(); PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(), BMI->getSourceLocation(), "Error evaluating initializer"); // We don't clean up dead bindings here. - const StackFrameContext *stackFrame = - cast<StackFrameContext>(Pred->getLocationContext()); - const CXXConstructorDecl *decl = - cast<CXXConstructorDecl>(stackFrame->getDecl()); + const auto *stackFrame = cast<StackFrameContext>(Pred->getLocationContext()); + const auto *decl = cast<CXXConstructorDecl>(stackFrame->getDecl()); ProgramStateRef State = Pred->getState(); SVal thisVal = State->getSVal(svalBuilder.getCXXThis(decl, stackFrame)); - ExplodedNodeSet Tmp(Pred); + ExplodedNodeSet Tmp; SVal FieldLoc; // Evaluate the initializer, if necessary if (BMI->isAnyMemberInitializer()) { // Constructors build the object directly in the field, // but non-objects must be copied in from the initializer. - if (auto *CtorExpr = findDirectConstructorForCurrentCFGElement()) { - assert(BMI->getInit()->IgnoreImplicit() == CtorExpr); - (void)CtorExpr; + if (getObjectUnderConstruction(State, BMI, LC)) { // The field was directly constructed, so there is no need to bind. + // But we still need to stop tracking the object under construction. + State = finishObjectConstruction(State, BMI, LC); + NodeBuilder Bldr(Pred, Tmp, *currBldrCtx); + PostStore PS(Init, LC, /*Loc*/ nullptr, /*tag*/ nullptr); + Bldr.generateNode(PS, State, Pred); } else { - const Expr *Init = BMI->getInit()->IgnoreImplicit(); const ValueDecl *Field; if (BMI->isIndirectMemberInitializer()) { Field = BMI->getIndirectMember(); @@ -593,15 +853,12 @@ void ExprEngine::ProcessInitializer(const CFGInitializer Init, InitVal = State->getSVal(BMI->getInit(), stackFrame); } - assert(Tmp.size() == 1 && "have not generated any new nodes yet"); - assert(*Tmp.begin() == Pred && "have not generated any new nodes yet"); - Tmp.clear(); - PostInitializer PP(BMI, FieldLoc.getAsRegion(), stackFrame); evalBind(Tmp, Init, Pred, FieldLoc, InitVal, /*isInit=*/true, &PP); } } else { assert(BMI->isBaseInitializer() || BMI->isDelegatingInitializer()); + Tmp.insert(Pred); // We already did all the work when visiting the CXXConstructExpr. } @@ -610,9 +867,9 @@ void ExprEngine::ProcessInitializer(const CFGInitializer Init, PostInitializer PP(BMI, FieldLoc.getAsRegion(), stackFrame); ExplodedNodeSet Dst; NodeBuilder Bldr(Tmp, Dst, *currBldrCtx); - for (ExplodedNodeSet::iterator I = Tmp.begin(), E = Tmp.end(); I != E; ++I) { - ExplodedNode *N = *I; - Bldr.generateNode(PP, N->getState(), N); + for (const auto I : Tmp) { + ProgramStateRef State = I->getState(); + Bldr.generateNode(PP, State, I); } // Enqueue the new nodes onto the work list. @@ -688,8 +945,15 @@ void ExprEngine::ProcessAutomaticObjDtor(const CFGAutomaticObjDtor Dtor, varType = cast<TypedValueRegion>(Region)->getValueType(); } + // FIXME: We need to run the same destructor on every element of the array. + // This workaround will just run the first destructor (which will still + // invalidate the entire array). + EvalCallOptions CallOpts; + Region = makeZeroElementRegion(state, loc::MemRegionVal(Region), varType, + CallOpts.IsArrayCtorOrDtor).getAsRegion(); + VisitCXXDestructor(varType, Region, Dtor.getTriggerStmt(), /*IsBase=*/ false, - Pred, Dst); + Pred, Dst, CallOpts); } void ExprEngine::ProcessDeleteDtor(const CFGDeleteDtor Dtor, @@ -699,12 +963,12 @@ void ExprEngine::ProcessDeleteDtor(const CFGDeleteDtor Dtor, const LocationContext *LCtx = Pred->getLocationContext(); const CXXDeleteExpr *DE = Dtor.getDeleteExpr(); const Stmt *Arg = DE->getArgument(); + QualType DTy = DE->getDestroyedType(); SVal ArgVal = State->getSVal(Arg, LCtx); // If the argument to delete is known to be a null value, // don't run destructor. if (State->isNull(ArgVal).isConstrainedTrue()) { - QualType DTy = DE->getDestroyedType(); QualType BTy = getContext().getBaseElementType(DTy); const CXXRecordDecl *RD = BTy->getAsCXXRecordDecl(); const CXXDestructorDecl *Dtor = RD->getDestructor(); @@ -715,19 +979,30 @@ void ExprEngine::ProcessDeleteDtor(const CFGDeleteDtor Dtor, return; } - VisitCXXDestructor(DE->getDestroyedType(), - ArgVal.getAsRegion(), - DE, /*IsBase=*/ false, - Pred, Dst); + EvalCallOptions CallOpts; + const MemRegion *ArgR = ArgVal.getAsRegion(); + if (DE->isArrayForm()) { + // FIXME: We need to run the same destructor on every element of the array. + // This workaround will just run the first destructor (which will still + // invalidate the entire array). + CallOpts.IsArrayCtorOrDtor = true; + // Yes, it may even be a multi-dimensional array. + while (const auto *AT = getContext().getAsArrayType(DTy)) + DTy = AT->getElementType(); + if (ArgR) + ArgR = getStoreManager().GetElementZeroRegion(cast<SubRegion>(ArgR), DTy); + } + + VisitCXXDestructor(DTy, ArgR, DE, /*IsBase=*/false, Pred, Dst, CallOpts); } void ExprEngine::ProcessBaseDtor(const CFGBaseDtor D, ExplodedNode *Pred, ExplodedNodeSet &Dst) { const LocationContext *LCtx = Pred->getLocationContext(); - const CXXDestructorDecl *CurDtor = cast<CXXDestructorDecl>(LCtx->getDecl()); + const auto *CurDtor = cast<CXXDestructorDecl>(LCtx->getDecl()); Loc ThisPtr = getSValBuilder().getCXXThis(CurDtor, - LCtx->getCurrentStackFrame()); + LCtx->getStackFrame()); SVal ThisVal = Pred->getState()->getSVal(ThisPtr); // Create the base object region. @@ -737,51 +1012,94 @@ void ExprEngine::ProcessBaseDtor(const CFGBaseDtor D, Base->isVirtual()); VisitCXXDestructor(BaseTy, BaseVal.castAs<loc::MemRegionVal>().getRegion(), - CurDtor->getBody(), /*IsBase=*/ true, Pred, Dst); + CurDtor->getBody(), /*IsBase=*/ true, Pred, Dst, {}); } void ExprEngine::ProcessMemberDtor(const CFGMemberDtor D, ExplodedNode *Pred, ExplodedNodeSet &Dst) { const FieldDecl *Member = D.getFieldDecl(); + QualType T = Member->getType(); ProgramStateRef State = Pred->getState(); const LocationContext *LCtx = Pred->getLocationContext(); - const CXXDestructorDecl *CurDtor = cast<CXXDestructorDecl>(LCtx->getDecl()); + const auto *CurDtor = cast<CXXDestructorDecl>(LCtx->getDecl()); Loc ThisVal = getSValBuilder().getCXXThis(CurDtor, - LCtx->getCurrentStackFrame()); + LCtx->getStackFrame()); SVal FieldVal = State->getLValue(Member, State->getSVal(ThisVal).castAs<Loc>()); - VisitCXXDestructor(Member->getType(), - FieldVal.castAs<loc::MemRegionVal>().getRegion(), - CurDtor->getBody(), /*IsBase=*/false, Pred, Dst); + // FIXME: We need to run the same destructor on every element of the array. + // This workaround will just run the first destructor (which will still + // invalidate the entire array). + EvalCallOptions CallOpts; + FieldVal = makeZeroElementRegion(State, FieldVal, T, + CallOpts.IsArrayCtorOrDtor); + + VisitCXXDestructor(T, FieldVal.castAs<loc::MemRegionVal>().getRegion(), + CurDtor->getBody(), /*IsBase=*/false, Pred, Dst, CallOpts); } void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D, ExplodedNode *Pred, ExplodedNodeSet &Dst) { - ExplodedNodeSet CleanDtorState; - StmtNodeBuilder StmtBldr(Pred, CleanDtorState, *currBldrCtx); + const CXXBindTemporaryExpr *BTE = D.getBindTemporaryExpr(); ProgramStateRef State = Pred->getState(); - if (State->contains<InitializedTemporariesSet>( - std::make_pair(D.getBindTemporaryExpr(), Pred->getStackFrame()))) { + const LocationContext *LC = Pred->getLocationContext(); + const MemRegion *MR = nullptr; + + if (Optional<SVal> V = + getObjectUnderConstruction(State, D.getBindTemporaryExpr(), + Pred->getLocationContext())) { // FIXME: Currently we insert temporary destructors for default parameters, - // but we don't insert the constructors. - State = State->remove<InitializedTemporariesSet>( - std::make_pair(D.getBindTemporaryExpr(), Pred->getStackFrame())); + // but we don't insert the constructors, so the entry in + // ObjectsUnderConstruction may be missing. + State = finishObjectConstruction(State, D.getBindTemporaryExpr(), + Pred->getLocationContext()); + MR = V->getAsRegion(); } + + // If copy elision has occured, and the constructor corresponding to the + // destructor was elided, we need to skip the destructor as well. + if (isDestructorElided(State, BTE, LC)) { + State = cleanupElidedDestructor(State, BTE, LC); + NodeBuilder Bldr(Pred, Dst, *currBldrCtx); + PostImplicitCall PP(D.getDestructorDecl(getContext()), + D.getBindTemporaryExpr()->getLocStart(), + Pred->getLocationContext()); + Bldr.generateNode(PP, State, Pred); + return; + } + + ExplodedNodeSet CleanDtorState; + StmtNodeBuilder StmtBldr(Pred, CleanDtorState, *currBldrCtx); StmtBldr.generateNode(D.getBindTemporaryExpr(), Pred, State); - QualType varType = D.getBindTemporaryExpr()->getSubExpr()->getType(); + QualType T = D.getBindTemporaryExpr()->getSubExpr()->getType(); // FIXME: Currently CleanDtorState can be empty here due to temporaries being // bound to default parameters. assert(CleanDtorState.size() <= 1); ExplodedNode *CleanPred = CleanDtorState.empty() ? Pred : *CleanDtorState.begin(); - // FIXME: Inlining of temporary destructors is not supported yet anyway, so - // we just put a NULL region for now. This will need to be changed later. - VisitCXXDestructor(varType, nullptr, D.getBindTemporaryExpr(), - /*IsBase=*/false, CleanPred, Dst); + + EvalCallOptions CallOpts; + CallOpts.IsTemporaryCtorOrDtor = true; + if (!MR) { + CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true; + + // If we have no MR, we still need to unwrap the array to avoid destroying + // the whole array at once. Regardless, we'd eventually need to model array + // destructors properly, element-by-element. + while (const ArrayType *AT = getContext().getAsArrayType(T)) { + T = AT->getElementType(); + CallOpts.IsArrayCtorOrDtor = true; + } + } else { + // We'd eventually need to makeZeroElementRegion() trick here, + // but for now we don't have the respective construction contexts, + // so MR would always be null in this case. Do nothing for now. + } + VisitCXXDestructor(T, MR, D.getBindTemporaryExpr(), + /*IsBase=*/false, CleanPred, Dst, CallOpts); } void ExprEngine::processCleanupTemporaryBranch(const CXXBindTemporaryExpr *BTE, @@ -791,19 +1109,23 @@ void ExprEngine::processCleanupTemporaryBranch(const CXXBindTemporaryExpr *BTE, const CFGBlock *DstT, const CFGBlock *DstF) { BranchNodeBuilder TempDtorBuilder(Pred, Dst, BldCtx, DstT, DstF); - if (Pred->getState()->contains<InitializedTemporariesSet>( - std::make_pair(BTE, Pred->getStackFrame()))) { + ProgramStateRef State = Pred->getState(); + const LocationContext *LC = Pred->getLocationContext(); + if (getObjectUnderConstruction(State, BTE, LC)) { TempDtorBuilder.markInfeasible(false); - TempDtorBuilder.generateNode(Pred->getState(), true, Pred); + TempDtorBuilder.generateNode(State, true, Pred); } else { TempDtorBuilder.markInfeasible(true); - TempDtorBuilder.generateNode(Pred->getState(), false, Pred); + TempDtorBuilder.generateNode(State, false, Pred); } } void ExprEngine::VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *BTE, ExplodedNodeSet &PreVisit, ExplodedNodeSet &Dst) { + // This is a fallback solution in case we didn't have a construction + // context when we were constructing the temporary. Otherwise the map should + // have been populated there. if (!getAnalysisManager().options.includeTemporaryDtorsInCFG()) { // In case we don't have temporary destructors in the CFG, do not mark // the initialization - we would otherwise never clean it up. @@ -813,34 +1135,39 @@ void ExprEngine::VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *BTE, StmtNodeBuilder StmtBldr(PreVisit, Dst, *currBldrCtx); for (ExplodedNode *Node : PreVisit) { ProgramStateRef State = Node->getState(); - - if (!State->contains<InitializedTemporariesSet>( - std::make_pair(BTE, Node->getStackFrame()))) { - // FIXME: Currently the state might already contain the marker due to + const LocationContext *LC = Node->getLocationContext(); + if (!getObjectUnderConstruction(State, BTE, LC)) { + // FIXME: Currently the state might also already contain the marker due to // incorrect handling of temporaries bound to default parameters; for // those, we currently skip the CXXBindTemporaryExpr but rely on adding // temporary destructor nodes. - State = State->add<InitializedTemporariesSet>( - std::make_pair(BTE, Node->getStackFrame())); + State = addObjectUnderConstruction(State, BTE, LC, UnknownVal()); } StmtBldr.generateNode(BTE, Node, State); } } -namespace { -class CollectReachableSymbolsCallback final : public SymbolVisitor { - InvalidatedSymbols Symbols; +ProgramStateRef ExprEngine::escapeValue(ProgramStateRef State, SVal V, + PointerEscapeKind K) const { + class CollectReachableSymbolsCallback final : public SymbolVisitor { + InvalidatedSymbols Symbols; -public: - explicit CollectReachableSymbolsCallback(ProgramStateRef State) {} - const InvalidatedSymbols &getSymbols() const { return Symbols; } + public: + explicit CollectReachableSymbolsCallback(ProgramStateRef State) {} - bool VisitSymbol(SymbolRef Sym) override { - Symbols.insert(Sym); - return true; - } -}; -} // end anonymous namespace + const InvalidatedSymbols &getSymbols() const { return Symbols; } + + bool VisitSymbol(SymbolRef Sym) override { + Symbols.insert(Sym); + return true; + } + }; + + const CollectReachableSymbolsCallback &Scanner = + State->scanReachableSymbols<CollectReachableSymbolsCallback>(V); + return getCheckerManager().runCheckersForPointerEscape( + State, Scanner.getSymbols(), /*CallEvent*/ nullptr, K, nullptr); +} void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, ExplodedNodeSet &DstTop) { @@ -930,8 +1257,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::OMPTargetTeamsDistributeParallelForDirectiveClass: case Stmt::OMPTargetTeamsDistributeParallelForSimdDirectiveClass: case Stmt::OMPTargetTeamsDistributeSimdDirectiveClass: - case Stmt::CapturedStmtClass: - { + case Stmt::CapturedStmtClass: { const ExplodedNode *node = Bldr.generateSink(S, Pred, Pred->getState()); Engine.addAbortedBlock(node, currBldrCtx->getBlock()); break; @@ -1026,6 +1352,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::AddrLabelExprClass: case Stmt::AttributedStmtClass: case Stmt::IntegerLiteralClass: + case Stmt::FixedPointLiteralClass: case Stmt::CharacterLiteralClass: case Stmt::ImplicitValueInitExprClass: case Stmt::CXXScalarValueInitExprClass: @@ -1060,16 +1387,15 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, StmtNodeBuilder Bldr2(PreVisit, Tmp, *currBldrCtx); const Expr *ArgE; - if (const CXXDefaultArgExpr *DefE = dyn_cast<CXXDefaultArgExpr>(S)) + if (const auto *DefE = dyn_cast<CXXDefaultArgExpr>(S)) ArgE = DefE->getExpr(); - else if (const CXXDefaultInitExpr *DefE = dyn_cast<CXXDefaultInitExpr>(S)) + else if (const auto *DefE = dyn_cast<CXXDefaultInitExpr>(S)) ArgE = DefE->getExpr(); else llvm_unreachable("unknown constant wrapper kind"); bool IsTemporary = false; - if (const MaterializeTemporaryExpr *MTE = - dyn_cast<MaterializeTemporaryExpr>(ArgE)) { + if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(ArgE)) { ArgE = MTE->GetTemporaryExpr(); IsTemporary = true; } @@ -1079,15 +1405,14 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, ConstantVal = UnknownVal(); const LocationContext *LCtx = Pred->getLocationContext(); - for (ExplodedNodeSet::iterator I = PreVisit.begin(), E = PreVisit.end(); - I != E; ++I) { - ProgramStateRef State = (*I)->getState(); + for (const auto I : PreVisit) { + ProgramStateRef State = I->getState(); State = State->BindExpr(S, LCtx, *ConstantVal); if (IsTemporary) State = createTemporaryRegionIfNeeded(State, LCtx, cast<Expr>(S), cast<Expr>(S)); - Bldr2.generateNode(S, *I, State); + Bldr2.generateNode(S, I, State); } getCheckerManager().runCheckersForPostStmt(Dst, Tmp, S, *this); @@ -1108,12 +1433,10 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, ExplodedNodeSet Tmp; StmtNodeBuilder Bldr2(preVisit, Tmp, *currBldrCtx); - const Expr *Ex = cast<Expr>(S); + const auto *Ex = cast<Expr>(S); QualType resultType = Ex->getType(); - for (ExplodedNodeSet::iterator it = preVisit.begin(), et = preVisit.end(); - it != et; ++it) { - ExplodedNode *N = *it; + for (const auto N : preVisit) { const LocationContext *LCtx = N->getLocationContext(); SVal result = svalBuilder.conjureSymbolVal(nullptr, Ex, LCtx, resultType, @@ -1127,17 +1450,8 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, ->getType()->isRecordType())) for (auto Child : Ex->children()) { assert(Child); - SVal Val = State->getSVal(Child, LCtx); - - CollectReachableSymbolsCallback Scanner = - State->scanReachableSymbols<CollectReachableSymbolsCallback>( - Val); - const InvalidatedSymbols &EscapedSymbols = Scanner.getSymbols(); - - State = getCheckerManager().runCheckersForPointerEscape( - State, EscapedSymbols, - /*CallEvent*/ nullptr, PSK_EscapeOther, nullptr); + State = escapeValue(State, Val, PSK_EscapeOther); } Bldr2.generateNode(S, N, State); @@ -1184,7 +1498,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, break; case Stmt::BinaryOperatorClass: { - const BinaryOperator* B = cast<BinaryOperator>(S); + const auto *B = cast<BinaryOperator>(S); if (B->isLogicalOp()) { Bldr.takeNodes(Pred); VisitLogicalExpr(B, Pred, Dst); @@ -1216,12 +1530,12 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, } case Stmt::CXXOperatorCallExprClass: { - const CXXOperatorCallExpr *OCE = cast<CXXOperatorCallExpr>(S); + const auto *OCE = cast<CXXOperatorCallExpr>(S); // For instance method operators, make sure the 'this' argument has a // valid region. const Decl *Callee = OCE->getCalleeDecl(); - if (const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Callee)) { + if (const auto *MD = dyn_cast_or_null<CXXMethodDecl>(Callee)) { if (MD->isInstance()) { ProgramStateRef State = Pred->getState(); const LocationContext *LCtx = Pred->getLocationContext(); @@ -1239,34 +1553,38 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, // FALLTHROUGH LLVM_FALLTHROUGH; } + case Stmt::CallExprClass: case Stmt::CXXMemberCallExprClass: - case Stmt::UserDefinedLiteralClass: { + case Stmt::UserDefinedLiteralClass: Bldr.takeNodes(Pred); VisitCallExpr(cast<CallExpr>(S), Pred, Dst); Bldr.addNodes(Dst); break; - } - case Stmt::CXXCatchStmtClass: { + case Stmt::CXXCatchStmtClass: Bldr.takeNodes(Pred); VisitCXXCatchStmt(cast<CXXCatchStmt>(S), Pred, Dst); Bldr.addNodes(Dst); break; - } case Stmt::CXXTemporaryObjectExprClass: - case Stmt::CXXConstructExprClass: { + case Stmt::CXXConstructExprClass: Bldr.takeNodes(Pred); VisitCXXConstructExpr(cast<CXXConstructExpr>(S), Pred, Dst); Bldr.addNodes(Dst); break; - } case Stmt::CXXNewExprClass: { Bldr.takeNodes(Pred); + + ExplodedNodeSet PreVisit; + getCheckerManager().runCheckersForPreStmt(PreVisit, Pred, S, *this); + ExplodedNodeSet PostVisit; - VisitCXXNewExpr(cast<CXXNewExpr>(S), Pred, PostVisit); + for (const auto i : PreVisit) + VisitCXXNewExpr(cast<CXXNewExpr>(S), i, PostVisit); + getCheckerManager().runCheckersForPostStmt(Dst, PostVisit, S, *this); Bldr.addNodes(Dst); break; @@ -1275,12 +1593,11 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::CXXDeleteExprClass: { Bldr.takeNodes(Pred); ExplodedNodeSet PreVisit; - const CXXDeleteExpr *CDE = cast<CXXDeleteExpr>(S); + const auto *CDE = cast<CXXDeleteExpr>(S); getCheckerManager().runCheckersForPreStmt(PreVisit, Pred, S, *this); - for (ExplodedNodeSet::iterator i = PreVisit.begin(), - e = PreVisit.end(); i != e ; ++i) - VisitCXXDeleteExpr(CDE, *i, Dst); + for (const auto i : PreVisit) + VisitCXXDeleteExpr(CDE, i, Dst); Bldr.addNodes(Dst); break; @@ -1290,7 +1607,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::ChooseExprClass: { // __builtin_choose_expr Bldr.takeNodes(Pred); - const ChooseExpr *C = cast<ChooseExpr>(S); + const auto *C = cast<ChooseExpr>(S); VisitGuardedExpr(C, C->getLHS(), C->getRHS(), Pred, Dst); Bldr.addNodes(Dst); break; @@ -1311,8 +1628,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::BinaryConditionalOperatorClass: case Stmt::ConditionalOperatorClass: { // '?' operator Bldr.takeNodes(Pred); - const AbstractConditionalOperator *C - = cast<AbstractConditionalOperator>(S); + const auto *C = cast<AbstractConditionalOperator>(S); VisitGuardedExpr(C, C->getTrueExpr(), C->getFalseExpr(), Pred, Dst); Bldr.addNodes(Dst); break; @@ -1326,7 +1642,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::DeclRefExprClass: { Bldr.takeNodes(Pred); - const DeclRefExpr *DE = cast<DeclRefExpr>(S); + const auto *DE = cast<DeclRefExpr>(S); VisitCommonDeclRefExpr(DE, DE->getDecl(), Pred, Dst); Bldr.addNodes(Dst); break; @@ -1347,7 +1663,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::CXXFunctionalCastExprClass: case Stmt::ObjCBridgedCastExprClass: { Bldr.takeNodes(Pred); - const CastExpr *C = cast<CastExpr>(S); + const auto *C = cast<CastExpr>(S); ExplodedNodeSet dstExpr; VisitCast(C, C->getSubExpr(), Pred, dstExpr); @@ -1359,14 +1675,12 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Expr::MaterializeTemporaryExprClass: { Bldr.takeNodes(Pred); - const MaterializeTemporaryExpr *MTE = cast<MaterializeTemporaryExpr>(S); + const auto *MTE = cast<MaterializeTemporaryExpr>(S); ExplodedNodeSet dstPrevisit; getCheckerManager().runCheckersForPreStmt(dstPrevisit, Pred, MTE, *this); ExplodedNodeSet dstExpr; - for (ExplodedNodeSet::iterator i = dstPrevisit.begin(), - e = dstPrevisit.end(); i != e ; ++i) { - CreateCXXTemporaryObject(MTE, *i, dstExpr); - } + for (const auto i : dstPrevisit) + CreateCXXTemporaryObject(MTE, i, dstExpr); getCheckerManager().runCheckersForPostStmt(Dst, dstExpr, MTE, *this); Bldr.addNodes(Dst); break; @@ -1421,11 +1735,19 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, Bldr.addNodes(Dst); break; - case Stmt::OffsetOfExprClass: + case Stmt::OffsetOfExprClass: { Bldr.takeNodes(Pred); - VisitOffsetOfExpr(cast<OffsetOfExpr>(S), Pred, Dst); + ExplodedNodeSet PreVisit; + getCheckerManager().runCheckersForPreStmt(PreVisit, Pred, S, *this); + + ExplodedNodeSet PostVisit; + for (const auto Node : PreVisit) + VisitOffsetOfExpr(cast<OffsetOfExpr>(S), Node, PostVisit); + + getCheckerManager().runCheckersForPostStmt(Dst, PostVisit, S, *this); Bldr.addNodes(Dst); break; + } case Stmt::UnaryExprOrTypeTraitExprClass: Bldr.takeNodes(Pred); @@ -1435,7 +1757,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, break; case Stmt::StmtExprClass: { - const StmtExpr *SE = cast<StmtExpr>(S); + const auto *SE = cast<StmtExpr>(S); if (SE->getSubStmt()->body_empty()) { // Empty statement expression. @@ -1444,7 +1766,8 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, break; } - if (Expr *LastExpr = dyn_cast<Expr>(*SE->getSubStmt()->body_rbegin())) { + if (const auto *LastExpr = + dyn_cast<Expr>(*SE->getSubStmt()->body_rbegin())) { ProgramStateRef state = Pred->getState(); Bldr.generateNode(SE, Pred, state->BindExpr(SE, Pred->getLocationContext(), @@ -1456,7 +1779,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::UnaryOperatorClass: { Bldr.takeNodes(Pred); - const UnaryOperator *U = cast<UnaryOperator>(S); + const auto *U = cast<UnaryOperator>(S); if (AMgr.options.eagerlyAssumeBinOpBifurcation && (U->getOpcode() == UO_LNot)) { ExplodedNodeSet Tmp; VisitUnaryOperator(U, Pred, Tmp); @@ -1471,7 +1794,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::PseudoObjectExprClass: { Bldr.takeNodes(Pred); ProgramStateRef state = Pred->getState(); - const PseudoObjectExpr *PE = cast<PseudoObjectExpr>(S); + const auto *PE = cast<PseudoObjectExpr>(S); if (const Expr *Result = PE->getResultExpr()) { SVal V = state->getSVal(Result, Pred->getLocationContext()); Bldr.generateNode(S, Pred, @@ -1490,8 +1813,8 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, bool ExprEngine::replayWithoutInlining(ExplodedNode *N, const LocationContext *CalleeLC) { - const StackFrameContext *CalleeSF = CalleeLC->getCurrentStackFrame(); - const StackFrameContext *CallerSF = CalleeSF->getParent()->getCurrentStackFrame(); + const StackFrameContext *CalleeSF = CalleeLC->getStackFrame(); + const StackFrameContext *CallerSF = CalleeSF->getParent()->getStackFrame(); assert(CalleeSF && CallerSF); ExplodedNode *BeforeProcessingCall = nullptr; const Stmt *CE = CalleeSF->getCallSite(); @@ -1503,7 +1826,7 @@ bool ExprEngine::replayWithoutInlining(ExplodedNode *N, N = N->pred_empty() ? nullptr : *(N->pred_begin()); // Skip the nodes corresponding to the inlined code. - if (L.getLocationContext()->getCurrentStackFrame() != CallerSF) + if (L.getStackFrame() != CallerSF) continue; // We reached the caller. Find the node right before we started // processing the call. @@ -1602,10 +1925,10 @@ void ExprEngine::processCFGBlockEntrance(const BlockEdge &L, // Check if we stopped at the top level function or not. // Root node should have the location context of the top most function. const LocationContext *CalleeLC = Pred->getLocation().getLocationContext(); - const LocationContext *CalleeSF = CalleeLC->getCurrentStackFrame(); + const LocationContext *CalleeSF = CalleeLC->getStackFrame(); const LocationContext *RootLC = (*G.roots_begin())->getLocation().getLocationContext(); - if (RootLC->getCurrentStackFrame() != CalleeSF) { + if (RootLC->getStackFrame() != CalleeSF) { Engine.FunctionSummaries->markReachedMaxBlockCount(CalleeSF->getDecl()); // Re-run the call evaluation without inlining it, by storing the @@ -1639,14 +1962,14 @@ static SVal RecoverCastedSymbol(ProgramStateManager& StateMgr, const LocationContext *LCtx, ASTContext &Ctx) { - const Expr *Ex = dyn_cast<Expr>(Condition); + const auto *Ex = dyn_cast<Expr>(Condition); if (!Ex) return UnknownVal(); uint64_t bits = 0; bool bitsInit = false; - while (const CastExpr *CE = dyn_cast<CastExpr>(Ex)) { + while (const auto *CE = dyn_cast<CastExpr>(Ex)) { QualType T = CE->getType(); if (!T->isIntegralOrEnumerationType()) @@ -1674,7 +1997,7 @@ static SVal RecoverCastedSymbol(ProgramStateManager& StateMgr, #ifndef NDEBUG static const Stmt *getRightmostLeaf(const Stmt *Condition) { while (Condition) { - const BinaryOperator *BO = dyn_cast<BinaryOperator>(Condition); + const auto *BO = dyn_cast<BinaryOperator>(Condition); if (!BO || !BO->isLogicalOp()) { return Condition; } @@ -1700,10 +2023,10 @@ static const Stmt *getRightmostLeaf(const Stmt *Condition) { // space. static const Stmt *ResolveCondition(const Stmt *Condition, const CFGBlock *B) { - if (const Expr *Ex = dyn_cast<Expr>(Condition)) + if (const auto *Ex = dyn_cast<Expr>(Condition)) Condition = Ex->IgnoreParens(); - const BinaryOperator *BO = dyn_cast<BinaryOperator>(Condition); + const auto *BO = dyn_cast<BinaryOperator>(Condition); if (!BO || !BO->isLogicalOp()) return Condition; @@ -1751,7 +2074,7 @@ void ExprEngine::processBranch(const Stmt *Condition, const Stmt *Term, return; } - if (const Expr *Ex = dyn_cast<Expr>(Condition)) + if (const auto *Ex = dyn_cast<Expr>(Condition)) Condition = Ex->IgnoreParens(); Condition = ResolveCondition(Condition, BldCtx.getBlock()); @@ -1767,10 +2090,7 @@ void ExprEngine::processBranch(const Stmt *Condition, const Stmt *Term, return; BranchNodeBuilder builder(CheckersOutSet, Dst, BldCtx, DstT, DstF); - for (NodeBuilder::iterator I = CheckersOutSet.begin(), - E = CheckersOutSet.end(); E != I; ++I) { - ExplodedNode *PredI = *I; - + for (const auto PredI : CheckersOutSet) { if (PredI->isSink()) continue; @@ -1779,7 +2099,7 @@ void ExprEngine::processBranch(const Stmt *Condition, const Stmt *Term, if (X.isUnknownOrUndef()) { // Give it a chance to recover from unknown. - if (const Expr *Ex = dyn_cast<Expr>(Condition)) { + 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. @@ -1836,13 +2156,13 @@ REGISTER_TRAIT_WITH_PROGRAMSTATE(InitializedGlobalsSet, void ExprEngine::processStaticInitializer(const DeclStmt *DS, NodeBuilderContext &BuilderCtx, ExplodedNode *Pred, - clang::ento::ExplodedNodeSet &Dst, + ExplodedNodeSet &Dst, const CFGBlock *DstT, const CFGBlock *DstF) { PrettyStackTraceLocationContext CrashInfo(Pred->getLocationContext()); currBldrCtx = &BuilderCtx; - const VarDecl *VD = cast<VarDecl>(DS->getSingleDecl()); + const auto *VD = cast<VarDecl>(DS->getSingleDecl()); ProgramStateRef state = Pred->getState(); bool initHasRun = state->contains<InitializedGlobalsSet>(VD); BranchNodeBuilder builder(Pred, Dst, BuilderCtx, DstT, DstF); @@ -1860,7 +2180,6 @@ void ExprEngine::processStaticInitializer(const DeclStmt *DS, /// processIndirectGoto - Called by CoreEngine. Used to generate successor /// nodes by processing the 'effects' of a computed goto jump. void ExprEngine::processIndirectGoto(IndirectGotoNodeBuilder &builder) { - ProgramStateRef state = builder.getState(); SVal V = state->getSVal(builder.getTarget(), builder.getLocationContext()); @@ -1871,7 +2190,7 @@ void ExprEngine::processIndirectGoto(IndirectGotoNodeBuilder &builder) { // (3) We have no clue about the label. Dispatch to all targets. // - typedef IndirectGotoNodeBuilder::iterator iterator; + using iterator = IndirectGotoNodeBuilder::iterator; if (Optional<loc::GotoLabel> LV = V.getAs<loc::GotoLabel>()) { const LabelDecl *L = LV->getLabel(); @@ -1897,26 +2216,10 @@ void ExprEngine::processIndirectGoto(IndirectGotoNodeBuilder &builder) { // This is really a catch-all. We don't support symbolics yet. // FIXME: Implement dispatch for symbolic pointers. - for (iterator I=builder.begin(), E=builder.end(); I != E; ++I) + for (iterator I = builder.begin(), E = builder.end(); I != E; ++I) builder.generateNode(I, state); } -#if 0 -static bool stackFrameDoesNotContainInitializedTemporaries(ExplodedNode &Pred) { - const StackFrameContext* Frame = Pred.getStackFrame(); - const llvm::ImmutableSet<CXXBindTemporaryContext> &Set = - Pred.getState()->get<InitializedTemporariesSet>(); - return std::find_if(Set.begin(), Set.end(), - [&](const CXXBindTemporaryContext &Ctx) { - if (Ctx.second == Frame) { - Ctx.first->dump(); - llvm::errs() << "\n"; - } - return Ctx.second == Frame; - }) == Set.end(); -} -#endif - void ExprEngine::processBeginOfFunction(NodeBuilderContext &BC, ExplodedNode *Pred, ExplodedNodeSet &Dst, @@ -1930,9 +2233,59 @@ void ExprEngine::processBeginOfFunction(NodeBuilderContext &BC, void ExprEngine::processEndOfFunction(NodeBuilderContext& BC, ExplodedNode *Pred, const ReturnStmt *RS) { - // FIXME: Assert that stackFrameDoesNotContainInitializedTemporaries(*Pred)). - // We currently cannot enable this assert, as lifetime extended temporaries - // are not modelled correctly. + // FIXME: We currently cannot assert that temporaries are clear, because + // lifetime extended temporaries are not always modelled correctly. In some + // cases when we materialize the temporary, we do + // createTemporaryRegionIfNeeded(), and the region changes, and also the + // respective destructor becomes automatic from temporary. So for now clean up + // the state manually before asserting. Ideally, the code above the assertion + // should go away, but the assertion should remain. + { + ExplodedNodeSet CleanUpObjects; + NodeBuilder Bldr(Pred, CleanUpObjects, BC); + ProgramStateRef State = Pred->getState(); + const LocationContext *FromLC = Pred->getLocationContext(); + const LocationContext *ToLC = FromLC->getStackFrame()->getParent(); + const LocationContext *LC = FromLC; + while (LC != ToLC) { + assert(LC && "ToLC must be a parent of FromLC!"); + for (auto I : State->get<ObjectsUnderConstruction>()) + if (I.first.getLocationContext() == LC) { + // The comment above only pardons us for not cleaning up a + // CXXBindTemporaryExpr. If any other statements are found here, + // it must be a separate problem. + assert(isa<CXXBindTemporaryExpr>(I.first.getStmt())); + State = State->remove<ObjectsUnderConstruction>(I.first); + // Also cleanup the elided destructor info. + ElidedDestructorItem Item( + cast<CXXBindTemporaryExpr>(I.first.getStmt()), + I.first.getLocationContext()); + State = State->remove<ElidedDestructors>(Item); + } + + // Also suppress the assertion for elided destructors when temporary + // destructors are not provided at all by the CFG, because there's no + // good place to clean them up. + if (!AMgr.getAnalyzerOptions().includeTemporaryDtorsInCFG()) + for (auto I : State->get<ElidedDestructors>()) + if (I.second == LC) + State = State->remove<ElidedDestructors>(I); + + LC = LC->getParent(); + } + if (State != Pred->getState()) { + Pred = Bldr.generateNode(Pred->getLocation(), State, Pred); + if (!Pred) { + // The node with clean temporaries already exists. We might have reached + // it on a path on which we initialize different temporaries. + return; + } + } + } + assert(areAllObjectsFullyConstructed(Pred->getState(), + Pred->getLocationContext(), + Pred->getStackFrame()->getParent())); + PrettyStackTraceLocationContext CrashInfo(Pred->getLocationContext()); StateMgr.EndPath(Pred->getState()); @@ -1943,12 +2296,10 @@ void ExprEngine::processEndOfFunction(NodeBuilderContext& BC, removeDeadOnEndOfFunction(BC, Pred, AfterRemovedDead); // Notify checkers. - for (ExplodedNodeSet::iterator I = AfterRemovedDead.begin(), - E = AfterRemovedDead.end(); I != E; ++I) { - getCheckerManager().runCheckersForEndFunction(BC, Dst, *I, *this); - } + for (const auto I : AfterRemovedDead) + getCheckerManager().runCheckersForEndFunction(BC, Dst, I, *this, RS); } else { - getCheckerManager().runCheckersForEndFunction(BC, Dst, Pred, *this); + getCheckerManager().runCheckersForEndFunction(BC, Dst, Pred, *this, RS); } Engine.enqueueEndOfFunction(Dst, RS); @@ -1957,7 +2308,8 @@ void ExprEngine::processEndOfFunction(NodeBuilderContext& BC, /// ProcessSwitch - Called by CoreEngine. Used to generate successor /// nodes by processing the 'effects' of a switch statement. void ExprEngine::processSwitch(SwitchNodeBuilder& builder) { - typedef SwitchNodeBuilder::iterator iterator; + using iterator = SwitchNodeBuilder::iterator; + ProgramStateRef state = builder.getState(); const Expr *CondE = builder.getCondition(); SVal CondV_untested = state->getSVal(CondE, builder.getLocationContext()); @@ -2046,16 +2398,16 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D, ProgramStateRef state = Pred->getState(); const LocationContext *LCtx = Pred->getLocationContext(); - if (const VarDecl *VD = dyn_cast<VarDecl>(D)) { + if (const auto *VD = dyn_cast<VarDecl>(D)) { // C permits "extern void v", and if you cast the address to a valid type, // you can even do things with it. We simply pretend assert(Ex->isGLValue() || VD->getType()->isVoidType()); const LocationContext *LocCtxt = Pred->getLocationContext(); const Decl *D = LocCtxt->getDecl(); - const auto *MD = D ? dyn_cast<CXXMethodDecl>(D) : nullptr; + const auto *MD = dyn_cast_or_null<CXXMethodDecl>(D); const auto *DeclRefEx = dyn_cast<DeclRefExpr>(Ex); - SVal V; - bool IsReference; + Optional<std::pair<SVal, QualType>> VInfo; + if (AMgr.options.shouldInlineLambdas() && DeclRefEx && DeclRefEx->refersToEnclosingVariableOrCapture() && MD && MD->getParent()->isLambda()) { @@ -2064,25 +2416,23 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D, llvm::DenseMap<const VarDecl *, FieldDecl *> LambdaCaptureFields; FieldDecl *LambdaThisCaptureField; CXXRec->getCaptureFields(LambdaCaptureFields, LambdaThisCaptureField); - const FieldDecl *FD = LambdaCaptureFields[VD]; - if (!FD) { - // When a constant is captured, sometimes no corresponding field is - // created in the lambda object. - assert(VD->getType().isConstQualified()); - V = state->getLValue(VD, LocCtxt); - IsReference = false; - } else { + + // Sema follows a sequence of complex rules to determine whether the + // variable should be captured. + if (const FieldDecl *FD = LambdaCaptureFields[VD]) { Loc CXXThis = - svalBuilder.getCXXThis(MD, LocCtxt->getCurrentStackFrame()); + svalBuilder.getCXXThis(MD, LocCtxt->getStackFrame()); SVal CXXThisVal = state->getSVal(CXXThis); - V = state->getLValue(FD, CXXThisVal); - IsReference = FD->getType()->isReferenceType(); + VInfo = std::make_pair(state->getLValue(FD, CXXThisVal), FD->getType()); } - } else { - V = state->getLValue(VD, LocCtxt); - IsReference = VD->getType()->isReferenceType(); } + if (!VInfo) + VInfo = std::make_pair(state->getLValue(VD, LocCtxt), VD->getType()); + + SVal V = VInfo->first; + bool IsReference = VInfo->second->isReferenceType(); + // For references, the 'lvalue' is the pointer address stored in the // reference region. if (IsReference) { @@ -2096,13 +2446,13 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D, ProgramPoint::PostLValueKind); return; } - if (const EnumConstantDecl *ED = dyn_cast<EnumConstantDecl>(D)) { + if (const auto *ED = dyn_cast<EnumConstantDecl>(D)) { assert(!Ex->isGLValue()); SVal V = svalBuilder.makeIntVal(ED->getInitVal()); Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, V)); return; } - if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) { + if (const auto *FD = dyn_cast<FunctionDecl>(D)) { SVal V = svalBuilder.getFunctionPointer(FD); Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, V), nullptr, ProgramPoint::PostLValueKind); @@ -2118,7 +2468,12 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D, currBldrCtx->blockCount()); state = state->assume(V.castAs<DefinedOrUnknownSVal>(), true); Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, V), nullptr, - ProgramPoint::PostLValueKind); + ProgramPoint::PostLValueKind); + return; + } + if (isa<BindingDecl>(D)) { + // FIXME: proper support for bound declarations. + // For now, let's just prevent crashing. return; } @@ -2151,9 +2506,17 @@ void ExprEngine::VisitArraySubscriptExpr(const ArraySubscriptExpr *A, ProgramStateRef state = Node->getState(); if (IsGLValueLike) { - SVal V = state->getLValue(A->getType(), - state->getSVal(Idx, LCtx), - state->getSVal(Base, LCtx)); + QualType T = A->getType(); + + // One of the forbidden LValue types! We still need to have sensible + // symbolic locations to represent this stuff. Note that arithmetic on + // void pointers is a GCC extension. + if (T->isVoidType()) + T = getContext().CharTy; + + SVal V = state->getLValue(T, + state->getSVal(Idx, LCtx), + state->getSVal(Base, LCtx)); Bldr.generateNode(A, Node, state->BindExpr(A, LCtx, V), nullptr, ProgramPoint::PostLValueKind); } else if (IsVectorType) { @@ -2171,41 +2534,36 @@ a vector and not a forbidden lvalue type"); /// VisitMemberExpr - Transfer function for member expressions. void ExprEngine::VisitMemberExpr(const MemberExpr *M, ExplodedNode *Pred, ExplodedNodeSet &Dst) { - // FIXME: Prechecks eventually go in ::Visit(). ExplodedNodeSet CheckedSet; getCheckerManager().runCheckersForPreStmt(CheckedSet, Pred, M, *this); - ExplodedNodeSet EvalSet; - ValueDecl *Member = M->getMemberDecl(); + ExplodedNodeSet EvalSet; + ValueDecl *Member = M->getMemberDecl(); // Handle static member variables and enum constants accessed via // member syntax. - if (isa<VarDecl>(Member) || isa<EnumConstantDecl>(Member)) { - ExplodedNodeSet Dst; - for (ExplodedNodeSet::iterator I = CheckedSet.begin(), E = CheckedSet.end(); - I != E; ++I) { - VisitCommonDeclRefExpr(M, Member, Pred, EvalSet); - } + if (isa<VarDecl>(Member) || isa<EnumConstantDecl>(Member)) { + for (const auto I : CheckedSet) + VisitCommonDeclRefExpr(M, Member, I, EvalSet); } else { StmtNodeBuilder Bldr(CheckedSet, EvalSet, *currBldrCtx); ExplodedNodeSet Tmp; - for (ExplodedNodeSet::iterator I = CheckedSet.begin(), E = CheckedSet.end(); - I != E; ++I) { - ProgramStateRef state = (*I)->getState(); - const LocationContext *LCtx = (*I)->getLocationContext(); + for (const auto I : CheckedSet) { + ProgramStateRef state = I->getState(); + const LocationContext *LCtx = I->getLocationContext(); Expr *BaseExpr = M->getBase(); // Handle C++ method calls. - if (const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(Member)) { + if (const auto *MD = dyn_cast<CXXMethodDecl>(Member)) { if (MD->isInstance()) state = createTemporaryRegionIfNeeded(state, LCtx, BaseExpr); SVal MDVal = svalBuilder.getFunctionPointer(MD); state = state->BindExpr(M, LCtx, MDVal); - Bldr.generateNode(M, *I, state); + Bldr.generateNode(M, I, state); continue; } @@ -2213,7 +2571,7 @@ void ExprEngine::VisitMemberExpr(const MemberExpr *M, ExplodedNode *Pred, state = createTemporaryRegionIfNeeded(state, LCtx, BaseExpr); SVal baseExprVal = state->getSVal(BaseExpr, LCtx); - FieldDecl *field = cast<FieldDecl>(Member); + const auto *field = cast<FieldDecl>(Member); SVal L = state->getLValue(field, baseExprVal); if (M->isGLValue() || M->getType()->isArrayType()) { @@ -2223,8 +2581,8 @@ void ExprEngine::VisitMemberExpr(const MemberExpr *M, ExplodedNode *Pred, // pointers as soon as they are used. if (!M->isGLValue()) { assert(M->getType()->isArrayType()); - const ImplicitCastExpr *PE = - dyn_cast<ImplicitCastExpr>((*I)->getParentMap().getParentIgnoreParens(M)); + const auto *PE = + dyn_cast<ImplicitCastExpr>(I->getParentMap().getParentIgnoreParens(M)); if (!PE || PE->getCastKind() != CK_ArrayToPointerDecay) { llvm_unreachable("should always be wrapped in ArrayToPointerDecay"); } @@ -2237,11 +2595,11 @@ void ExprEngine::VisitMemberExpr(const MemberExpr *M, ExplodedNode *Pred, L = UnknownVal(); } - Bldr.generateNode(M, *I, state->BindExpr(M, LCtx, L), nullptr, + Bldr.generateNode(M, I, state->BindExpr(M, LCtx, L), nullptr, ProgramPoint::PostLValueKind); } else { - Bldr.takeNodes(*I); - evalLoad(Tmp, M, M, *I, state, L); + Bldr.takeNodes(I); + evalLoad(Tmp, M, M, I, state, L); Bldr.addNodes(Tmp); } } @@ -2261,10 +2619,9 @@ void ExprEngine::VisitAtomicExpr(const AtomicExpr *AE, ExplodedNode *Pred, ExplodedNodeSet AfterInvalidateSet; StmtNodeBuilder Bldr(AfterPreSet, AfterInvalidateSet, *currBldrCtx); - for (ExplodedNodeSet::iterator I = AfterPreSet.begin(), E = AfterPreSet.end(); - I != E; ++I) { - ProgramStateRef State = (*I)->getState(); - const LocationContext *LCtx = (*I)->getLocationContext(); + for (const auto I : AfterPreSet) { + ProgramStateRef State = I->getState(); + const LocationContext *LCtx = I->getLocationContext(); SmallVector<SVal, 8> ValuesToInvalidate; for (unsigned SI = 0, Count = AE->getNumSubExprs(); SI != Count; SI++) { @@ -2281,7 +2638,7 @@ void ExprEngine::VisitAtomicExpr(const AtomicExpr *AE, ExplodedNode *Pred, SVal ResultVal = UnknownVal(); State = State->BindExpr(AE, LCtx, ResultVal); - Bldr.generateNode(AE, *I, State, nullptr, + Bldr.generateNode(AE, I, State, nullptr, ProgramPoint::PostStmtKind); } @@ -2324,15 +2681,7 @@ ProgramStateRef ExprEngine::processPointerEscapedOnBind(ProgramStateRef State, // Otherwise, find all symbols referenced by 'val' that we are tracking // and stop tracking them. - CollectReachableSymbolsCallback Scanner = - State->scanReachableSymbols<CollectReachableSymbolsCallback>(Val); - const InvalidatedSymbols &EscapedSymbols = Scanner.getSymbols(); - State = getCheckerManager().runCheckersForPointerEscape(State, - EscapedSymbols, - /*CallEvent*/ nullptr, - PSK_EscapeOnBind, - nullptr); - + State = escapeValue(State, Val, PSK_EscapeOnBind); return State; } @@ -2343,7 +2692,6 @@ ExprEngine::notifyCheckersOfPointerEscape(ProgramStateRef State, ArrayRef<const MemRegion *> Regions, const CallEvent *Call, RegionAndSymbolInvalidationTraits &ITraits) { - if (!Invalidated || Invalidated->empty()) return State; @@ -2357,16 +2705,13 @@ ExprEngine::notifyCheckersOfPointerEscape(ProgramStateRef State, // If the symbols were invalidated by a call, we want to find out which ones // were invalidated directly due to being arguments to the call. InvalidatedSymbols SymbolsDirectlyInvalidated; - for (ArrayRef<const MemRegion *>::iterator I = ExplicitRegions.begin(), - E = ExplicitRegions.end(); I != E; ++I) { - if (const SymbolicRegion *R = (*I)->StripCasts()->getAs<SymbolicRegion>()) + for (const auto I : ExplicitRegions) { + if (const SymbolicRegion *R = I->StripCasts()->getAs<SymbolicRegion>()) SymbolsDirectlyInvalidated.insert(R->getSymbol()); } InvalidatedSymbols SymbolsIndirectlyInvalidated; - for (InvalidatedSymbols::const_iterator I=Invalidated->begin(), - E = Invalidated->end(); I!=E; ++I) { - SymbolRef sym = *I; + for (const auto &sym : *Invalidated) { if (SymbolsDirectlyInvalidated.count(sym)) continue; SymbolsIndirectlyInvalidated.insert(sym); @@ -2390,7 +2735,6 @@ void ExprEngine::evalBind(ExplodedNodeSet &Dst, const Stmt *StoreE, ExplodedNode *Pred, SVal location, SVal Val, bool atDeclInit, const ProgramPoint *PP) { - const LocationContext *LC = Pred->getLocationContext(); PostStmt PS(StoreE, LC); if (!PP) @@ -2414,9 +2758,7 @@ void ExprEngine::evalBind(ExplodedNodeSet &Dst, const Stmt *StoreE, return; } - for (ExplodedNodeSet::iterator I = CheckedSet.begin(), E = CheckedSet.end(); - I!=E; ++I) { - ExplodedNode *PredI = *I; + for (const auto PredI : CheckedSet) { ProgramStateRef state = PredI->getState(); state = processPointerEscapedOnBind(state, location, Val, LC); @@ -2465,8 +2807,8 @@ void ExprEngine::evalStore(ExplodedNodeSet &Dst, const Expr *AssignE, if (location.isUndef()) return; - for (ExplodedNodeSet::iterator NI=Tmp.begin(), NE=Tmp.end(); NI!=NE; ++NI) - evalBind(Dst, StoreE, *NI, location, Val, false); + for (const auto I : Tmp) + evalBind(Dst, StoreE, I, location, Val, false); } void ExprEngine::evalLoad(ExplodedNodeSet &Dst, @@ -2476,46 +2818,8 @@ void ExprEngine::evalLoad(ExplodedNodeSet &Dst, ProgramStateRef state, SVal location, const ProgramPointTag *tag, - QualType LoadTy) -{ + QualType LoadTy) { assert(!location.getAs<NonLoc>() && "location cannot be a NonLoc."); - - // Are we loading from a region? This actually results in two loads; one - // to fetch the address of the referenced value and one to fetch the - // referenced value. - if (const TypedValueRegion *TR = - dyn_cast_or_null<TypedValueRegion>(location.getAsRegion())) { - - QualType ValTy = TR->getValueType(); - if (const ReferenceType *RT = ValTy->getAs<ReferenceType>()) { - static SimpleProgramPointTag - loadReferenceTag(TagProviderName, "Load Reference"); - ExplodedNodeSet Tmp; - evalLoadCommon(Tmp, NodeEx, BoundEx, Pred, state, - location, &loadReferenceTag, - getContext().getPointerType(RT->getPointeeType())); - - // Perform the load from the referenced value. - for (ExplodedNodeSet::iterator I=Tmp.begin(), E=Tmp.end() ; I!=E; ++I) { - state = (*I)->getState(); - location = state->getSVal(BoundEx, (*I)->getLocationContext()); - evalLoadCommon(Dst, NodeEx, BoundEx, *I, state, location, tag, LoadTy); - } - return; - } - } - - evalLoadCommon(Dst, NodeEx, BoundEx, Pred, state, location, tag, LoadTy); -} - -void ExprEngine::evalLoadCommon(ExplodedNodeSet &Dst, - const Expr *NodeEx, - const Expr *BoundEx, - ExplodedNode *Pred, - ProgramStateRef state, - SVal location, - const ProgramPointTag *tag, - QualType LoadTy) { assert(NodeEx); assert(BoundEx); // Evaluate the location (checks for bad dereferences). @@ -2529,9 +2833,9 @@ void ExprEngine::evalLoadCommon(ExplodedNodeSet &Dst, return; // Proceed with the load. - for (ExplodedNodeSet::iterator NI=Tmp.begin(), NE=Tmp.end(); NI!=NE; ++NI) { - state = (*NI)->getState(); - const LocationContext *LCtx = (*NI)->getLocationContext(); + for (const auto I : Tmp) { + state = I->getState(); + const LocationContext *LCtx = I->getLocationContext(); SVal V = UnknownVal(); if (location.isValid()) { @@ -2540,7 +2844,7 @@ void ExprEngine::evalLoadCommon(ExplodedNodeSet &Dst, V = state->getSVal(location.castAs<Loc>(), LoadTy); } - Bldr.generateNode(NodeEx, *NI, state->BindExpr(BoundEx, LCtx, V), tag, + Bldr.generateNode(NodeEx, I, state->BindExpr(BoundEx, LCtx, V), tag, ProgramPoint::PostLoadKind); } } @@ -2597,8 +2901,7 @@ void ExprEngine::evalEagerlyAssumeBinOpBifurcation(ExplodedNodeSet &Dst, const Expr *Ex) { StmtNodeBuilder Bldr(Src, Dst, *currBldrCtx); - for (ExplodedNodeSet::iterator I=Src.begin(), E=Src.end(); I!=E; ++I) { - ExplodedNode *Pred = *I; + for (const auto Pred : Src) { // Test if the previous node was as the same expression. This can happen // when the expression fails to evaluate to anything meaningful and // (as an optimization) we don't generate a node. @@ -2648,7 +2951,7 @@ void ExprEngine::VisitGCCAsmStmt(const GCCAsmStmt *A, ExplodedNode *Pred, for (const Expr *O : A->outputs()) { SVal X = state->getSVal(O, Pred->getLocationContext()); - assert (!X.getAs<NonLoc>()); // Should be an Lval, or unknown, undef. + assert(!X.getAs<NonLoc>()); // Should be an Lval, or unknown, undef. if (Optional<Loc> LV = X.getAs<Loc>()) state = state->bindLoc(*LV, UnknownVal(), Pred->getLocationContext()); @@ -2672,16 +2975,15 @@ static ExprEngine* GraphPrintCheckerState; static SourceManager* GraphPrintSourceManager; namespace llvm { -template<> -struct DOTGraphTraits<ExplodedNode*> : - public DefaultDOTGraphTraits { - DOTGraphTraits (bool isSimple=false) : DefaultDOTGraphTraits(isSimple) {} +template<> +struct DOTGraphTraits<ExplodedNode*> : public DefaultDOTGraphTraits { + DOTGraphTraits (bool isSimple = false) : DefaultDOTGraphTraits(isSimple) {} // FIXME: Since we do not cache error nodes in ExprEngine now, this does not // work. static std::string getNodeAttributes(const ExplodedNode *N, void*) { - return ""; + return {}; } // De-duplicate some source location pretty-printing. @@ -2694,15 +2996,8 @@ struct DOTGraphTraits<ExplodedNode*> : << "\\l"; } } - static void printLocation2(raw_ostream &Out, SourceLocation SLoc) { - if (SLoc.isFileID() && GraphPrintSourceManager->isInMainFile(SLoc)) - Out << "line " << GraphPrintSourceManager->getExpansionLineNumber(SLoc); - else - SLoc.print(Out, *GraphPrintSourceManager); - } static std::string getNodeLabel(const ExplodedNode *N, void*){ - std::string sbuf; llvm::raw_string_ostream Out(sbuf); @@ -2710,14 +3005,13 @@ struct DOTGraphTraits<ExplodedNode*> : ProgramPoint Loc = N->getLocation(); switch (Loc.getKind()) { - case ProgramPoint::BlockEntranceKind: { + case ProgramPoint::BlockEntranceKind: Out << "Block Entrance: B" << Loc.castAs<BlockEntrance>().getBlock()->getBlockID(); break; - } case ProgramPoint::BlockExitKind: - assert (false); + assert(false); break; case ProgramPoint::CallEnterKind: @@ -2808,7 +3102,7 @@ struct DOTGraphTraits<ExplodedNode*> : const Stmt *Label = E.getDst()->getLabel(); if (Label) { - if (const CaseStmt *C = dyn_cast<CaseStmt>(Label)) { + if (const auto *C = dyn_cast<CaseStmt>(Label)) { Out << "\\lcase "; LangOptions LO; // FIXME. if (C->getLHS()) @@ -2822,7 +3116,7 @@ struct DOTGraphTraits<ExplodedNode*> : Out << ":"; } else { - assert (isa<DefaultStmt>(Label)); + assert(isa<DefaultStmt>(Label)); Out << "\\ldefault:"; } } @@ -2863,6 +3157,8 @@ struct DOTGraphTraits<ExplodedNode*> : Out << "\\lPostStore\\l"; else if (Loc.getAs<PostLValue>()) Out << "\\lPostLValue\\l"; + else if (Loc.getAs<PostAllocatorCall>()) + Out << "\\lPostAllocatorCall\\l"; break; } @@ -2872,40 +3168,7 @@ struct DOTGraphTraits<ExplodedNode*> : Out << "\\|StateID: " << (const void*) state.get() << " NodeID: " << (const void*) N << "\\|"; - // Analysis stack backtrace. - Out << "Location context stack (from current to outer):\\l"; - const LocationContext *LC = Loc.getLocationContext(); - unsigned Idx = 0; - for (; LC; LC = LC->getParent(), ++Idx) { - Out << Idx << ". (" << (const void *)LC << ") "; - switch (LC->getKind()) { - case LocationContext::StackFrame: - if (const NamedDecl *D = dyn_cast<NamedDecl>(LC->getDecl())) - Out << "Calling " << D->getQualifiedNameAsString(); - else - Out << "Calling anonymous code"; - if (const Stmt *S = cast<StackFrameContext>(LC)->getCallSite()) { - Out << " at "; - printLocation2(Out, S->getLocStart()); - } - break; - case LocationContext::Block: - Out << "Invoking block"; - if (const Decl *D = cast<BlockInvocationContext>(LC)->getBlockDecl()) { - Out << " defined at "; - printLocation2(Out, D->getLocStart()); - } - break; - case LocationContext::Scope: - Out << "Entering scope"; - // FIXME: Add more info once ScopeContext is activated. - break; - } - Out << "\\l"; - } - Out << "\\l"; - - state->printDOT(Out); + state->printDOT(Out, N->getLocationContext()); Out << "\\l"; @@ -2916,23 +3179,24 @@ struct DOTGraphTraits<ExplodedNode*> : return Out.str(); } }; -} // end llvm namespace + +} // namespace llvm #endif void ExprEngine::ViewGraph(bool trim) { #ifndef NDEBUG if (trim) { - std::vector<const ExplodedNode*> Src; + std::vector<const ExplodedNode *> Src; // Flush any outstanding reports to make sure we cover all the nodes. // This does not cause them to get displayed. - for (BugReporter::iterator I=BR.begin(), E=BR.end(); I!=E; ++I) - const_cast<BugType*>(*I)->FlushReports(BR); + for (const auto I : BR) + const_cast<BugType *>(I)->FlushReports(BR); // Iterate through the reports and get their nodes. for (BugReporter::EQClasses_iterator EI = BR.EQClasses_begin(), EE = BR.EQClasses_end(); EI != EE; ++EI) { - ExplodedNode *N = const_cast<ExplodedNode*>(EI->begin()->getErrorNode()); + const auto *N = const_cast<ExplodedNode *>(EI->begin()->getErrorNode()); if (N) Src.push_back(N); } diff --git a/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/lib/StaticAnalyzer/Core/ExprEngineC.cpp index 3e7a50365f50..c7b1a9ac82f0 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -20,7 +20,7 @@ using namespace clang; using namespace ento; using llvm::APSInt; -/// \brief Optionally conjure and return a symbol for offset when processing +/// Optionally conjure and return a symbol for offset when processing /// an expression \p Expression. /// If \p Other is a location, conjure a symbol for \p Symbol /// (offset) if it is unknown so that memory arithmetic always @@ -257,13 +257,23 @@ ProgramStateRef ExprEngine::handleLValueBitCast( ProgramStateRef state, const Expr* Ex, const LocationContext* LCtx, QualType T, QualType ExTy, const CastExpr* CastE, StmtNodeBuilder& Bldr, ExplodedNode* Pred) { + if (T->isLValueReferenceType()) { + assert(!CastE->getType()->isLValueReferenceType()); + ExTy = getContext().getLValueReferenceType(ExTy); + } else if (T->isRValueReferenceType()) { + assert(!CastE->getType()->isRValueReferenceType()); + ExTy = getContext().getRValueReferenceType(ExTy); + } // Delegate to SValBuilder to process. - SVal V = state->getSVal(Ex, LCtx); - V = svalBuilder.evalCast(V, T, ExTy); + SVal OrigV = state->getSVal(Ex, LCtx); + SVal V = svalBuilder.evalCast(OrigV, T, ExTy); // Negate the result if we're treating the boolean as a signed i1 if (CastE->getCastKind() == CK_BooleanToSignedIntegral) V = evalMinus(V); state = state->BindExpr(CastE, LCtx, V); + if (V.isUnknown() && !OrigV.isUnknown()) { + state = escapeValue(state, OrigV, PSK_EscapeOther); + } Bldr.generateNode(CastE, Pred, state); return state; @@ -580,24 +590,12 @@ void ExprEngine::VisitDeclStmt(const DeclStmt *DS, ExplodedNode *Pred, SVal InitVal = state->getSVal(InitEx, LC); assert(DS->isSingleDecl()); - if (auto *CtorExpr = findDirectConstructorForCurrentCFGElement()) { - assert(InitEx->IgnoreImplicit() == CtorExpr); - (void)CtorExpr; + if (getObjectUnderConstruction(state, DS, LC)) { + state = finishObjectConstruction(state, DS, LC); // We constructed the object directly in the variable. // No need to bind anything. B.generateNode(DS, UpdatedN, state); } else { - // We bound the temp obj region to the CXXConstructExpr. Now recover - // the lazy compound value when the variable is not a reference. - if (AMgr.getLangOpts().CPlusPlus && VD->getType()->isRecordType() && - !VD->getType()->isReferenceType()) { - if (Optional<loc::MemRegionVal> M = - InitVal.getAs<loc::MemRegionVal>()) { - InitVal = state->getSVal(M->getRegion()); - assert(InitVal.getAs<nonloc::LazyCompoundVal>()); - } - } - // Recover some path-sensitivity if a scalar value evaluated to // UnknownVal. if (InitVal.isUnknown()) { @@ -760,7 +758,11 @@ void ExprEngine::VisitGuardedExpr(const Expr *Ex, for (const ExplodedNode *N = Pred ; N ; N = *N->pred_begin()) { ProgramPoint PP = N->getLocation(); if (PP.getAs<PreStmtPurgeDeadSymbols>() || PP.getAs<BlockEntrance>()) { - assert(N->pred_size() == 1); + // If the state N has multiple predecessors P, it means that successors + // of P are all equivalent. + // In turn, that means that all nodes at P are equivalent in terms + // of observable behavior at N, and we can follow any of them. + // FIXME: a more robust solution which does not walk up the tree. continue; } SrcBlock = PP.castAs<BlockEdge>().getSrc(); @@ -1062,6 +1064,7 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U, // constant value. If the UnaryOperator has location type, create the // constant with int type and pointer width. SVal RHS; + SVal Result; if (U->getType()->isAnyPointerType()) RHS = svalBuilder.makeArrayIndex(1); @@ -1070,7 +1073,14 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U, else RHS = UnknownVal(); - SVal Result = evalBinOp(state, Op, V2, RHS, U->getType()); + // The use of an operand of type bool with the ++ operators is deprecated + // but valid until C++17. And if the operand of the ++ operator is of type + // bool, it is set to true until C++17. Note that for '_Bool', it is also + // set to true when it encounters ++ operator. + if (U->getType()->isBooleanType() && U->isIncrementOp()) + Result = svalBuilder.makeTruthVal(true, U->getType()); + else + Result = evalBinOp(state, Op, V2, RHS, U->getType()); // Conjure a new symbol if necessary to recover precision. if (Result.isUnknown()){ @@ -1092,7 +1102,6 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U, Constraint = svalBuilder.evalEQ(state, SymVal, svalBuilder.makeZeroVal(U->getType())); - state = state->assume(Constraint, false); assert(state); } diff --git a/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index dad93111966f..dc124fc3ff2d 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" +#include "clang/Analysis/ConstructionContext.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/StmtCXX.h" #include "clang/AST/ParentMap.h" @@ -41,19 +42,30 @@ void ExprEngine::performTrivialCopy(NodeBuilder &Bldr, ExplodedNode *Pred, const CallEvent &Call) { SVal ThisVal; bool AlwaysReturnsLValue; + const CXXRecordDecl *ThisRD = nullptr; if (const CXXConstructorCall *Ctor = dyn_cast<CXXConstructorCall>(&Call)) { assert(Ctor->getDecl()->isTrivial()); assert(Ctor->getDecl()->isCopyOrMoveConstructor()); ThisVal = Ctor->getCXXThisVal(); + ThisRD = Ctor->getDecl()->getParent(); AlwaysReturnsLValue = false; } else { assert(cast<CXXMethodDecl>(Call.getDecl())->isTrivial()); assert(cast<CXXMethodDecl>(Call.getDecl())->getOverloadedOperator() == OO_Equal); ThisVal = cast<CXXInstanceCall>(Call).getCXXThisVal(); + ThisRD = cast<CXXMethodDecl>(Call.getDecl())->getParent(); AlwaysReturnsLValue = true; } + assert(ThisRD); + if (ThisRD->isEmpty()) { + // Do nothing for empty classes. Otherwise it'd retrieve an UnknownVal + // and bind it and RegionStore would think that the actual value + // in this region at this offset is unknown. + return; + } + const LocationContext *LCtx = Pred->getLocationContext(); ExplodedNodeSet Dst; @@ -84,52 +96,50 @@ void ExprEngine::performTrivialCopy(NodeBuilder &Bldr, ExplodedNode *Pred, } -/// Returns a region representing the first element of a (possibly -/// multi-dimensional) array. -/// -/// On return, \p Ty will be set to the base type of the array. -/// -/// If the type is not an array type at all, the original value is returned. -static SVal makeZeroElementRegion(ProgramStateRef State, SVal LValue, - QualType &Ty) { +SVal ExprEngine::makeZeroElementRegion(ProgramStateRef State, SVal LValue, + QualType &Ty, bool &IsArray) { SValBuilder &SVB = State->getStateManager().getSValBuilder(); ASTContext &Ctx = SVB.getContext(); while (const ArrayType *AT = Ctx.getAsArrayType(Ty)) { Ty = AT->getElementType(); LValue = State->getLValue(Ty, SVB.makeZeroArrayIndex(), LValue); + IsArray = true; } return LValue; } +std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( + const Expr *E, ProgramStateRef State, const LocationContext *LCtx, + const ConstructionContext *CC, EvalCallOptions &CallOpts) { + MemRegionManager &MRMgr = getSValBuilder().getRegionManager(); -const MemRegion * -ExprEngine::getRegionForConstructedObject(const CXXConstructExpr *CE, - ExplodedNode *Pred) { - const LocationContext *LCtx = Pred->getLocationContext(); - ProgramStateRef State = Pred->getState(); - - // See if we're constructing an existing region by looking at the next - // element in the CFG. - - if (auto Elem = findElementDirectlyInitializedByCurrentConstructor()) { - if (Optional<CFGStmt> StmtElem = Elem->getAs<CFGStmt>()) { - auto *DS = cast<DeclStmt>(StmtElem->getStmt()); - if (const auto *Var = dyn_cast<VarDecl>(DS->getSingleDecl())) { - if (Var->getInit() && Var->getInit()->IgnoreImplicit() == CE) { - SVal LValue = State->getLValue(Var, LCtx); - QualType Ty = Var->getType(); - LValue = makeZeroElementRegion(State, LValue, Ty); - return LValue.getAsRegion(); - } - } - } else if (Optional<CFGInitializer> InitElem = Elem->getAs<CFGInitializer>()) { - const CXXCtorInitializer *Init = InitElem->getInitializer(); + // See if we're constructing an existing region by looking at the + // current construction context. + if (CC) { + switch (CC->getKind()) { + case ConstructionContext::CXX17ElidedCopyVariableKind: + case ConstructionContext::SimpleVariableKind: { + const auto *DSCC = cast<VariableConstructionContext>(CC); + const auto *DS = DSCC->getDeclStmt(); + const auto *Var = cast<VarDecl>(DS->getSingleDecl()); + SVal LValue = State->getLValue(Var, LCtx); + QualType Ty = Var->getType(); + LValue = + makeZeroElementRegion(State, LValue, Ty, CallOpts.IsArrayCtorOrDtor); + State = + addObjectUnderConstruction(State, DSCC->getDeclStmt(), LCtx, LValue); + return std::make_pair(State, LValue); + } + case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind: + 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 = - getSValBuilder().getCXXThis(CurCtor, LCtx->getCurrentStackFrame()); + getSValBuilder().getCXXThis(CurCtor, LCtx->getStackFrame()); SVal ThisVal = State->getSVal(ThisPtr); const ValueDecl *Field; @@ -143,92 +153,134 @@ ExprEngine::getRegionForConstructedObject(const CXXConstructExpr *CE, } QualType Ty = Field->getType(); - FieldVal = makeZeroElementRegion(State, FieldVal, Ty); - return FieldVal.getAsRegion(); + FieldVal = makeZeroElementRegion(State, FieldVal, Ty, + CallOpts.IsArrayCtorOrDtor); + State = addObjectUnderConstruction(State, Init, LCtx, FieldVal); + return std::make_pair(State, FieldVal); } - - // FIXME: This will eventually need to handle new-expressions as well. - // Don't forget to update the pre-constructor initialization code in - // ExprEngine::VisitCXXConstructExpr. - } - // If we couldn't find an existing region to construct into, assume we're - // constructing a temporary. - MemRegionManager &MRMgr = getSValBuilder().getRegionManager(); - return MRMgr.getCXXTempObjectRegion(CE, LCtx); -} - -/// Returns true if the initializer for \Elem can be a direct -/// constructor. -static bool canHaveDirectConstructor(CFGElement Elem){ - // DeclStmts and CXXCtorInitializers for fields can be directly constructed. - - if (Optional<CFGStmt> StmtElem = Elem.getAs<CFGStmt>()) { - if (isa<DeclStmt>(StmtElem->getStmt())) { - return true; + case ConstructionContext::NewAllocatedObjectKind: { + if (AMgr.getAnalyzerOptions().mayInlineCXXAllocator()) { + const auto *NECC = cast<NewAllocatedObjectConstructionContext>(CC); + const auto *NE = NECC->getCXXNewExpr(); + SVal V = *getObjectUnderConstruction(State, NE, LCtx); + if (const SubRegion *MR = + dyn_cast_or_null<SubRegion>(V.getAsRegion())) { + if (NE->isArray()) { + // TODO: In fact, we need to call the constructor for every + // allocated element, not just the first one! + CallOpts.IsArrayCtorOrDtor = true; + return std::make_pair( + State, loc::MemRegionVal(getStoreManager().GetElementZeroRegion( + MR, NE->getType()->getPointeeType()))); + } + return std::make_pair(State, V); + } + // TODO: Detect when the allocator returns a null pointer. + // Constructor shall not be called in this case. + } + break; } - } - - if (Elem.getKind() == CFGElement::Initializer) { - return true; - } - - return false; -} + case ConstructionContext::SimpleReturnedValueKind: + case ConstructionContext::CXX17ElidedCopyReturnedValueKind: { + // The temporary is to be managed by the parent stack frame. + // So build it in the parent stack frame if we're not in the + // top frame of the analysis. + const StackFrameContext *SFC = LCtx->getStackFrame(); + if (const LocationContext *CallerLCtx = SFC->getParent()) { + auto RTC = (*SFC->getCallSiteBlock())[SFC->getIndex()] + .getAs<CFGCXXRecordTypedCall>(); + if (!RTC) { + // We were unable to find the correct construction context for the + // call in the parent stack frame. This is equivalent to not being + // able to find construction context at all. + break; + } + return prepareForObjectConstruction( + cast<Expr>(SFC->getCallSite()), State, CallerLCtx, + RTC->getConstructionContext(), CallOpts); + } else { + // We are on the top frame of the analysis. + // TODO: What exactly happens when we are? Does the temporary object + // live long enough in the region store in this case? Would checkers + // think that this object immediately goes out of scope? + CallOpts.IsTemporaryCtorOrDtor = true; + SVal V = loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)); + return std::make_pair(State, V); + } + llvm_unreachable("Unhandled return value construction context!"); + } + case ConstructionContext::ElidedTemporaryObjectKind: { + assert(AMgr.getAnalyzerOptions().shouldElideConstructors()); + const auto *TCC = cast<ElidedTemporaryObjectConstructionContext>(CC); + const CXXBindTemporaryExpr *BTE = TCC->getCXXBindTemporaryExpr(); + const MaterializeTemporaryExpr *MTE = TCC->getMaterializedTemporaryExpr(); + const CXXConstructExpr *CE = TCC->getConstructorAfterElision(); + + // Support pre-C++17 copy elision. We'll have the elidable copy + // constructor in the AST and in the CFG, but we'll skip it + // and construct directly into the final object. This call + // also sets the CallOpts flags for us. + SVal V; + std::tie(State, V) = prepareForObjectConstruction( + CE, State, LCtx, TCC->getConstructionContextAfterElision(), CallOpts); + + // Remember that we've elided the constructor. + State = addObjectUnderConstruction(State, CE, LCtx, V); + + // Remember that we've elided the destructor. + if (BTE) + State = elideDestructor(State, BTE, LCtx); + + // Instead of materialization, shamelessly return + // the final object destination. + if (MTE) + State = addObjectUnderConstruction(State, MTE, LCtx, V); + + return std::make_pair(State, V); + } + case ConstructionContext::SimpleTemporaryObjectKind: { + const auto *TCC = cast<SimpleTemporaryObjectConstructionContext>(CC); + const CXXBindTemporaryExpr *BTE = TCC->getCXXBindTemporaryExpr(); + const MaterializeTemporaryExpr *MTE = TCC->getMaterializedTemporaryExpr(); + SVal V = UnknownVal(); + + if (MTE) { + if (const ValueDecl *VD = MTE->getExtendingDecl()) { + assert(MTE->getStorageDuration() != SD_FullExpression); + if (!VD->getType()->isReferenceType()) { + // We're lifetime-extended by a surrounding aggregate. + // Automatic destructors aren't quite working in this case + // on the CFG side. We should warn the caller about that. + // FIXME: Is there a better way to retrieve this information from + // the MaterializeTemporaryExpr? + CallOpts.IsTemporaryLifetimeExtendedViaAggregate = true; + } + } -Optional<CFGElement> -ExprEngine::findElementDirectlyInitializedByCurrentConstructor() { - const NodeBuilderContext &CurrBldrCtx = getBuilderContext(); - // See if we're constructing an existing region by looking at the next - // element in the CFG. - const CFGBlock *B = CurrBldrCtx.getBlock(); - assert(isa<CXXConstructExpr>(((*B)[currStmtIdx]).castAs<CFGStmt>().getStmt())); - unsigned int NextStmtIdx = currStmtIdx + 1; - if (NextStmtIdx >= B->size()) - return None; - - CFGElement Next = (*B)[NextStmtIdx]; - - // Is this a destructor? If so, we might be in the middle of an assignment - // to a local or member: look ahead one more element to see what we find. - while (Next.getAs<CFGImplicitDtor>() && NextStmtIdx + 1 < B->size()) { - ++NextStmtIdx; - Next = (*B)[NextStmtIdx]; - } + if (MTE->getStorageDuration() == SD_Static || + MTE->getStorageDuration() == SD_Thread) + V = loc::MemRegionVal(MRMgr.getCXXStaticTempObjectRegion(E)); + } - if (canHaveDirectConstructor(Next)) - return Next; + if (V.isUnknown()) + V = loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)); - return None; -} + if (BTE) + State = addObjectUnderConstruction(State, BTE, LCtx, V); -const CXXConstructExpr * -ExprEngine::findDirectConstructorForCurrentCFGElement() { - // Go backward in the CFG to see if the previous element (ignoring - // destructors) was a CXXConstructExpr. If so, that constructor - // was constructed directly into an existing region. - // This process is essentially the inverse of that performed in - // findElementDirectlyInitializedByCurrentConstructor(). - if (currStmtIdx == 0) - return nullptr; - - const CFGBlock *B = getBuilderContext().getBlock(); - assert(canHaveDirectConstructor((*B)[currStmtIdx])); - - unsigned int PreviousStmtIdx = currStmtIdx - 1; - CFGElement Previous = (*B)[PreviousStmtIdx]; - - while (Previous.getAs<CFGImplicitDtor>() && PreviousStmtIdx > 0) { - --PreviousStmtIdx; - Previous = (*B)[PreviousStmtIdx]; - } + if (MTE) + State = addObjectUnderConstruction(State, MTE, LCtx, V); - if (Optional<CFGStmt> PrevStmtElem = Previous.getAs<CFGStmt>()) { - if (auto *CtorExpr = dyn_cast<CXXConstructExpr>(PrevStmtElem->getStmt())) { - return CtorExpr; + CallOpts.IsTemporaryCtorOrDtor = true; + return std::make_pair(State, V); + } } } - - return nullptr; + // If we couldn't find an existing region to construct into, assume we're + // constructing a temporary. Notify the caller of our failure. + CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true; + return std::make_pair( + State, loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx))); } void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, @@ -237,21 +289,41 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, const LocationContext *LCtx = Pred->getLocationContext(); ProgramStateRef State = Pred->getState(); - const MemRegion *Target = nullptr; + SVal Target = UnknownVal(); + + if (Optional<SVal> ElidedTarget = + getObjectUnderConstruction(State, CE, LCtx)) { + // We've previously modeled an elidable constructor by pretending that it in + // fact constructs into the correct target. This constructor can therefore + // be skipped. + Target = *ElidedTarget; + StmtNodeBuilder Bldr(Pred, destNodes, *currBldrCtx); + State = finishObjectConstruction(State, CE, LCtx); + if (auto L = Target.getAs<Loc>()) + State = State->BindExpr(CE, LCtx, State->getSVal(*L, CE->getType())); + Bldr.generateNode(CE, Pred, State); + return; + } // FIXME: Handle arrays, which run the same constructor for every element. // For now, we just run the first constructor (which should still invalidate // the entire array). + EvalCallOptions CallOpts; + auto C = getCurrentCFGElement().getAs<CFGConstructor>(); + assert(C || getCurrentCFGElement().getAs<CFGStmt>()); + const ConstructionContext *CC = C ? C->getConstructionContext() : nullptr; + switch (CE->getConstructionKind()) { case CXXConstructExpr::CK_Complete: { - Target = getRegionForConstructedObject(CE, Pred); + std::tie(State, Target) = + prepareForObjectConstruction(CE, State, LCtx, CC, CallOpts); break; } case CXXConstructExpr::CK_VirtualBase: // Make sure we are not calling virtual base class initializers twice. // Only the most-derived object should initialize virtual base classes. - if (const Stmt *Outer = LCtx->getCurrentStackFrame()->getCallSite()) { + if (const Stmt *Outer = LCtx->getStackFrame()->getCallSite()) { const CXXConstructExpr *OuterCtor = dyn_cast<CXXConstructExpr>(Outer); if (OuterCtor) { switch (OuterCtor->getConstructionKind()) { @@ -281,48 +353,59 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, // otherwise always available during construction. if (dyn_cast_or_null<InitListExpr>(LCtx->getParentMap().getParent(CE))) { MemRegionManager &MRMgr = getSValBuilder().getRegionManager(); - Target = MRMgr.getCXXTempObjectRegion(CE, LCtx); + Target = loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(CE, LCtx)); + CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true; break; } // FALLTHROUGH case CXXConstructExpr::CK_Delegating: { const CXXMethodDecl *CurCtor = cast<CXXMethodDecl>(LCtx->getDecl()); Loc ThisPtr = getSValBuilder().getCXXThis(CurCtor, - LCtx->getCurrentStackFrame()); + LCtx->getStackFrame()); SVal ThisVal = State->getSVal(ThisPtr); if (CE->getConstructionKind() == CXXConstructExpr::CK_Delegating) { - Target = ThisVal.getAsRegion(); + Target = ThisVal; } else { // Cast to the base type. bool IsVirtual = (CE->getConstructionKind() == CXXConstructExpr::CK_VirtualBase); SVal BaseVal = getStoreManager().evalDerivedToBase(ThisVal, CE->getType(), IsVirtual); - Target = BaseVal.getAsRegion(); + Target = BaseVal; } break; } } + if (State != Pred->getState()) { + static SimpleProgramPointTag T("ExprEngine", + "Prepare for object construction"); + ExplodedNodeSet DstPrepare; + StmtNodeBuilder BldrPrepare(Pred, DstPrepare, *currBldrCtx); + BldrPrepare.generateNode(CE, Pred, State, &T, ProgramPoint::PreStmtKind); + assert(DstPrepare.size() <= 1); + if (DstPrepare.size() == 0) + return; + Pred = *BldrPrepare.begin(); + } + CallEventManager &CEMgr = getStateManager().getCallEventManager(); CallEventRef<CXXConstructorCall> Call = - CEMgr.getCXXConstructorCall(CE, Target, State, LCtx); + CEMgr.getCXXConstructorCall(CE, Target.getAsRegion(), State, LCtx); ExplodedNodeSet DstPreVisit; getCheckerManager().runCheckersForPreStmt(DstPreVisit, Pred, CE, *this); + // FIXME: Is it possible and/or useful to do this before PreStmt? ExplodedNodeSet PreInitialized; { StmtNodeBuilder Bldr(DstPreVisit, PreInitialized, *currBldrCtx); - if (CE->requiresZeroInitialization()) { - // Type of the zero doesn't matter. - SVal ZeroVal = svalBuilder.makeZeroVal(getContext().CharTy); - - for (ExplodedNodeSet::iterator I = DstPreVisit.begin(), - E = DstPreVisit.end(); - I != E; ++I) { - ProgramStateRef State = (*I)->getState(); + for (ExplodedNodeSet::iterator I = DstPreVisit.begin(), + E = DstPreVisit.end(); + I != E; ++I) { + ProgramStateRef State = (*I)->getState(); + if (CE->requiresZeroInitialization()) { // FIXME: Once we properly handle constructors in new-expressions, we'll // need to invalidate the region before setting a default value, to make // sure there aren't any lingering bindings around. This probably needs @@ -335,10 +418,11 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, // actually make things worse. Placement new makes this tricky as well, // since it's then possible to be initializing one part of a multi- // dimensional array. - State = State->bindDefault(loc::MemRegionVal(Target), ZeroVal, LCtx); - Bldr.generateNode(CE, *I, State, /*tag=*/nullptr, - ProgramPoint::PreStmtKind); + State = State->bindDefaultZero(Target, LCtx); } + + Bldr.generateNode(CE, *I, State, /*tag=*/nullptr, + ProgramPoint::PreStmtKind); } } @@ -349,10 +433,9 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, ExplodedNodeSet DstEvaluated; StmtNodeBuilder Bldr(DstPreCall, DstEvaluated, *currBldrCtx); - bool IsArray = isa<ElementRegion>(Target); if (CE->getConstructor()->isTrivial() && CE->getConstructor()->isCopyOrMoveConstructor() && - !IsArray) { + !CallOpts.IsArrayCtorOrDtor) { // FIXME: Handle other kinds of trivial constructors as well. for (ExplodedNodeSet::iterator I = DstPreCall.begin(), E = DstPreCall.end(); I != E; ++I) @@ -361,10 +444,10 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, } else { for (ExplodedNodeSet::iterator I = DstPreCall.begin(), E = DstPreCall.end(); I != E; ++I) - defaultEvalCall(Bldr, *I, *Call); + defaultEvalCall(Bldr, *I, *Call, CallOpts); } - // If the CFG was contructed without elements for temporary destructors + // If the CFG was constructed without elements for temporary destructors // and the just-called constructor created a temporary object then // stop exploration if the temporary object has a noreturn constructor. // This can lose coverage because the destructor, if it were present @@ -373,20 +456,30 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, // paths when no-return temporary destructors are used for assertions. const AnalysisDeclContext *ADC = LCtx->getAnalysisDeclContext(); if (!ADC->getCFGBuildOptions().AddTemporaryDtors) { - const MemRegion *Target = Call->getCXXThisVal().getAsRegion(); - if (Target && isa<CXXTempObjectRegion>(Target) && - Call->getDecl()->getParent()->isAnyDestructorNoReturn()) { + const MemRegion *Target = Call->getCXXThisVal().getAsRegion(); + if (Target && isa<CXXTempObjectRegion>(Target) && + Call->getDecl()->getParent()->isAnyDestructorNoReturn()) { + + // If we've inlined the constructor, then DstEvaluated would be empty. + // In this case we still want a sink, which could be implemented + // in processCallExit. But we don't have that implemented at the moment, + // so if you hit this assertion, see if you can avoid inlining + // the respective constructor when analyzer-config cfg-temporary-dtors + // is set to false. + // Otherwise there's nothing wrong with inlining such constructor. + assert(!DstEvaluated.empty() && + "We should not have inlined this constructor!"); for (ExplodedNode *N : DstEvaluated) { Bldr.generateSink(CE, N, N->getState()); } - // There is no need to run the PostCall and PostStmtchecker + // There is no need to run the PostCall and PostStmt checker // callbacks because we just generated sinks on all nodes in th // frontier. return; } - } + } ExplodedNodeSet DstPostCall; getCheckerManager().runCheckersForPostCall(DstPostCall, DstEvaluated, @@ -399,19 +492,11 @@ void ExprEngine::VisitCXXDestructor(QualType ObjectType, const Stmt *S, bool IsBaseDtor, ExplodedNode *Pred, - ExplodedNodeSet &Dst) { + ExplodedNodeSet &Dst, + const EvalCallOptions &CallOpts) { const LocationContext *LCtx = Pred->getLocationContext(); ProgramStateRef State = Pred->getState(); - // FIXME: We need to run the same destructor on every element of the array. - // This workaround will just run the first destructor (which will still - // invalidate the entire array). - SVal DestVal = UnknownVal(); - if (Dest) - DestVal = loc::MemRegionVal(Dest); - DestVal = makeZeroElementRegion(State, DestVal, ObjectType); - Dest = DestVal.getAsRegion(); - const CXXRecordDecl *RecordDecl = ObjectType->getAsCXXRecordDecl(); assert(RecordDecl && "Only CXXRecordDecls should have destructors"); const CXXDestructorDecl *DtorDecl = RecordDecl->getDestructor(); @@ -432,7 +517,7 @@ void ExprEngine::VisitCXXDestructor(QualType ObjectType, StmtNodeBuilder Bldr(DstPreCall, DstInvalidated, *currBldrCtx); for (ExplodedNodeSet::iterator I = DstPreCall.begin(), E = DstPreCall.end(); I != E; ++I) - defaultEvalCall(Bldr, *I, *Call); + defaultEvalCall(Bldr, *I, *Call, CallOpts); ExplodedNodeSet DstPostCall; getCheckerManager().runCheckersForPostCall(Dst, DstInvalidated, @@ -455,15 +540,58 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE, getCheckerManager().runCheckersForPreCall(DstPreCall, Pred, *Call, *this); - ExplodedNodeSet DstInvalidated; - StmtNodeBuilder Bldr(DstPreCall, DstInvalidated, *currBldrCtx); - for (ExplodedNodeSet::iterator I = DstPreCall.begin(), E = DstPreCall.end(); - I != E; ++I) - defaultEvalCall(Bldr, *I, *Call); - getCheckerManager().runCheckersForPostCall(Dst, DstInvalidated, - *Call, *this); -} + ExplodedNodeSet DstPostCall; + StmtNodeBuilder CallBldr(DstPreCall, DstPostCall, *currBldrCtx); + for (auto I : DstPreCall) { + // FIXME: Provide evalCall for checkers? + defaultEvalCall(CallBldr, I, *Call); + } + // If the call is inlined, DstPostCall will be empty and we bail out now. + + // Store return value of operator new() for future use, until the actual + // CXXNewExpr gets processed. + ExplodedNodeSet DstPostValue; + StmtNodeBuilder ValueBldr(DstPostCall, DstPostValue, *currBldrCtx); + for (auto I : DstPostCall) { + // FIXME: Because CNE serves as the "call site" for the allocator (due to + // lack of a better expression in the AST), the conjured return value symbol + // is going to be of the same type (C++ object pointer type). Technically + // this is not correct because the operator new's prototype always says that + // it returns a 'void *'. So we should change the type of the symbol, + // and then evaluate the cast over the symbolic pointer from 'void *' to + // the object pointer type. But without changing the symbol's type it + // is breaking too much to evaluate the no-op symbolic cast over it, so we + // skip it for now. + ProgramStateRef State = I->getState(); + SVal RetVal = State->getSVal(CNE, LCtx); + + // If this allocation function is not declared as non-throwing, failures + // /must/ be signalled by exceptions, and thus the return value will never + // be NULL. -fno-exceptions does not influence this semantics. + // FIXME: GCC has a -fcheck-new option, which forces it to consider the case + // where new can return NULL. If we end up supporting that option, we can + // consider adding a check for it here. + // C++11 [basic.stc.dynamic.allocation]p3. + if (const FunctionDecl *FD = CNE->getOperatorNew()) { + QualType Ty = FD->getType(); + if (const auto *ProtoType = Ty->getAs<FunctionProtoType>()) + if (!ProtoType->isNothrow()) + State = State->assume(RetVal.castAs<DefinedOrUnknownSVal>(), true); + } + ValueBldr.generateNode( + CNE, I, addObjectUnderConstruction(State, CNE, LCtx, RetVal)); + } + + ExplodedNodeSet DstPostPostCallCallback; + getCheckerManager().runCheckersForPostCall(DstPostPostCallCallback, + DstPostValue, *Call, *this); + for (auto I : DstPostPostCallCallback) { + getCheckerManager().runCheckersForNewAllocator( + CNE, *getObjectUnderConstruction(I->getState(), CNE, LCtx), Dst, I, + *this); + } +} void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, ExplodedNodeSet &Dst) { @@ -474,69 +602,74 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, unsigned blockCount = currBldrCtx->blockCount(); const LocationContext *LCtx = Pred->getLocationContext(); - DefinedOrUnknownSVal symVal = UnknownVal(); + SVal symVal = UnknownVal(); FunctionDecl *FD = CNE->getOperatorNew(); - bool IsStandardGlobalOpNewFunction = false; - if (FD && !isa<CXXMethodDecl>(FD) && !FD->isVariadic()) { - if (FD->getNumParams() == 2) { - QualType T = FD->getParamDecl(1)->getType(); - if (const IdentifierInfo *II = T.getBaseTypeIdentifier()) - // NoThrow placement new behaves as a standard new. - IsStandardGlobalOpNewFunction = II->getName().equals("nothrow_t"); - } - else - // Placement forms are considered non-standard. - IsStandardGlobalOpNewFunction = (FD->getNumParams() == 1); + bool IsStandardGlobalOpNewFunction = + FD->isReplaceableGlobalAllocationFunction(); + + ProgramStateRef State = Pred->getState(); + + // Retrieve the stored operator new() return value. + if (AMgr.getAnalyzerOptions().mayInlineCXXAllocator()) { + symVal = *getObjectUnderConstruction(State, CNE, LCtx); + State = finishObjectConstruction(State, CNE, LCtx); } // We assume all standard global 'operator new' functions allocate memory in // heap. We realize this is an approximation that might not correctly model // a custom global allocator. - if (IsStandardGlobalOpNewFunction) - symVal = svalBuilder.getConjuredHeapSymbolVal(CNE, LCtx, blockCount); - else - symVal = svalBuilder.conjureSymbolVal(nullptr, CNE, LCtx, CNE->getType(), - blockCount); + if (symVal.isUnknown()) { + if (IsStandardGlobalOpNewFunction) + symVal = svalBuilder.getConjuredHeapSymbolVal(CNE, LCtx, blockCount); + else + symVal = svalBuilder.conjureSymbolVal(nullptr, CNE, LCtx, CNE->getType(), + blockCount); + } - ProgramStateRef State = Pred->getState(); CallEventManager &CEMgr = getStateManager().getCallEventManager(); CallEventRef<CXXAllocatorCall> Call = CEMgr.getCXXAllocatorCall(CNE, State, LCtx); - // Invalidate placement args. - // FIXME: Once we figure out how we want allocators to work, - // we should be using the usual pre-/(default-)eval-/post-call checks here. - State = Call->invalidateRegions(blockCount); - if (!State) - return; + if (!AMgr.getAnalyzerOptions().mayInlineCXXAllocator()) { + // Invalidate placement args. + // FIXME: Once we figure out how we want allocators to work, + // we should be using the usual pre-/(default-)eval-/post-call checks here. + State = Call->invalidateRegions(blockCount); + if (!State) + return; - // If this allocation function is not declared as non-throwing, failures - // /must/ be signalled by exceptions, and thus the return value will never be - // NULL. -fno-exceptions does not influence this semantics. - // FIXME: GCC has a -fcheck-new option, which forces it to consider the case - // where new can return NULL. If we end up supporting that option, we can - // consider adding a check for it here. - // C++11 [basic.stc.dynamic.allocation]p3. - if (FD) { - QualType Ty = FD->getType(); - if (const FunctionProtoType *ProtoType = Ty->getAs<FunctionProtoType>()) - if (!ProtoType->isNothrow(getContext())) - State = State->assume(symVal, true); + // If this allocation function is not declared as non-throwing, failures + // /must/ be signalled by exceptions, and thus the return value will never + // be NULL. -fno-exceptions does not influence this semantics. + // FIXME: GCC has a -fcheck-new option, which forces it to consider the case + // where new can return NULL. If we end up supporting that option, we can + // consider adding a check for it here. + // C++11 [basic.stc.dynamic.allocation]p3. + if (FD) { + QualType Ty = FD->getType(); + if (const auto *ProtoType = Ty->getAs<FunctionProtoType>()) + if (!ProtoType->isNothrow()) + if (auto dSymVal = symVal.getAs<DefinedOrUnknownSVal>()) + State = State->assume(*dSymVal, true); + } } StmtNodeBuilder Bldr(Pred, Dst, *currBldrCtx); + SVal Result = symVal; + if (CNE->isArray()) { // FIXME: allocating an array requires simulating the constructors. // For now, just return a symbolicated region. - const SubRegion *NewReg = - symVal.castAs<loc::MemRegionVal>().getRegionAs<SubRegion>(); - QualType ObjTy = CNE->getType()->getAs<PointerType>()->getPointeeType(); - const ElementRegion *EleReg = - getStoreManager().GetElementZeroRegion(NewReg, ObjTy); - State = State->BindExpr(CNE, Pred->getLocationContext(), - loc::MemRegionVal(EleReg)); + if (const SubRegion *NewReg = + dyn_cast_or_null<SubRegion>(symVal.getAsRegion())) { + QualType ObjTy = CNE->getType()->getAs<PointerType>()->getPointeeType(); + const ElementRegion *EleReg = + getStoreManager().GetElementZeroRegion(NewReg, ObjTy); + Result = loc::MemRegionVal(EleReg); + } + State = State->BindExpr(CNE, Pred->getLocationContext(), Result); Bldr.generateNode(CNE, Pred, State); return; } @@ -545,7 +678,6 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, // CXXNewExpr, we need to make sure that the constructed object is not // immediately invalidated here. (The placement call should happen before // the constructor call anyway.) - SVal Result = symVal; if (FD && FD->isReservedGlobalPlacementOperator()) { // Non-array placement new should always return the placement location. SVal PlacementLoc = State->getSVal(CNE->getPlacementArg(0), LCtx); diff --git a/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index caf86b26b66d..3ee67f3d6882 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -15,8 +15,8 @@ #include "PrettyStackTraceLocationContext.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/DeclCXX.h" -#include "clang/AST/ParentMap.h" #include "clang/Analysis/Analyses/LiveVariables.h" +#include "clang/Analysis/ConstructionContext.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "llvm/ADT/SmallSet.h" @@ -74,15 +74,14 @@ static std::pair<const Stmt*, const CFGBlock*> getLastStmt(const ExplodedNode *Node) { const Stmt *S = nullptr; const CFGBlock *Blk = nullptr; - const StackFrameContext *SF = - Node->getLocation().getLocationContext()->getCurrentStackFrame(); + const StackFrameContext *SF = Node->getStackFrame(); // Back up through the ExplodedGraph until we reach a statement node in this // stack frame. while (Node) { const ProgramPoint &PP = Node->getLocation(); - if (PP.getLocationContext()->getCurrentStackFrame() == SF) { + if (PP.getStackFrame() == SF) { if (Optional<StmtPoint> SP = PP.getAs<StmtPoint>()) { S = SP->getStmt(); break; @@ -121,7 +120,7 @@ static std::pair<const Stmt*, /// Adjusts a return value when the called function's return type does not /// match the caller's expression type. This can happen when a dynamic call -/// is devirtualized, and the overridding method has a covariant (more specific) +/// is devirtualized, and the overriding method has a covariant (more specific) /// return type than the parent's method. For C++ objects, this means we need /// to add base casts. static SVal adjustReturnValue(SVal V, QualType ExpectedTy, QualType ActualTy, @@ -193,23 +192,6 @@ static bool wasDifferentDeclUsedForInlining(CallEventRef<> Call, return RuntimeCallee->getCanonicalDecl() != StaticDecl->getCanonicalDecl(); } -/// Returns true if the CXXConstructExpr \p E was intended to construct a -/// prvalue for the region in \p V. -/// -/// Note that we can't just test for rvalue vs. glvalue because -/// CXXConstructExprs embedded in DeclStmts and initializers are considered -/// rvalues by the AST, and the analyzer would like to treat them as lvalues. -static bool isTemporaryPRValue(const CXXConstructExpr *E, SVal V) { - if (E->isGLValue()) - return false; - - const MemRegion *MR = V.getAsRegion(); - if (!MR) - return false; - - return isa<CXXTempObjectRegion>(MR); -} - /// The call exit is simulated with a sequence of nodes, which occur between /// CallExitBegin and CallExitEnd. The following operations occur between the /// two program points: @@ -221,13 +203,12 @@ static bool isTemporaryPRValue(const CXXConstructExpr *E, SVal V) { void ExprEngine::processCallExit(ExplodedNode *CEBNode) { // Step 1 CEBNode was generated before the call. PrettyStackTraceLocationContext CrashInfo(CEBNode->getLocationContext()); - const StackFrameContext *calleeCtx = - CEBNode->getLocationContext()->getCurrentStackFrame(); + const StackFrameContext *calleeCtx = CEBNode->getStackFrame(); // The parent context might not be a stack frame, so make sure we // look up the first enclosing stack frame. const StackFrameContext *callerCtx = - calleeCtx->getParent()->getCurrentStackFrame(); + calleeCtx->getParent()->getStackFrame(); const Stmt *CE = calleeCtx->getCallSite(); ProgramStateRef state = CEBNode->getState(); @@ -269,13 +250,24 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { loc::MemRegionVal This = svalBuilder.getCXXThis(CCE->getConstructor()->getParent(), calleeCtx); SVal ThisV = state->getSVal(This); - - // If the constructed object is a temporary prvalue, get its bindings. - if (isTemporaryPRValue(CCE, ThisV)) - ThisV = state->getSVal(ThisV.castAs<Loc>()); - + ThisV = state->getSVal(ThisV.castAs<Loc>()); state = state->BindExpr(CCE, callerCtx, ThisV); } + + if (const auto *CNE = dyn_cast<CXXNewExpr>(CE)) { + // We are currently evaluating a CXXNewAllocator CFGElement. It takes a + // while to reach the actual CXXNewExpr element from here, so keep the + // region for later use. + // Additionally cast the return value of the inlined operator new + // (which is of type 'void *') to the correct object type. + SVal AllocV = state->getSVal(CNE, callerCtx); + AllocV = svalBuilder.evalCast( + AllocV, CNE->getType(), + getContext().getPointerType(getContext().VoidTy)); + + state = addObjectUnderConstruction(state, CNE, calleeCtx->getParent(), + AllocV); + } } // Step 3: BindedRetNode -> CleanedNodes @@ -315,6 +307,7 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { CallExitEnd Loc(calleeCtx, callerCtx); bool isNew; ProgramStateRef CEEState = (*I == CEBNode) ? state : (*I)->getState(); + ExplodedNode *CEENode = G.getNode(Loc, CEEState, false, &isNew); CEENode->addPredecessor(*I, G); if (!isNew) @@ -331,16 +324,32 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { CallEventRef<> UpdatedCall = Call.cloneWithState(CEEState); ExplodedNodeSet DstPostCall; - getCheckerManager().runCheckersForPostCall(DstPostCall, CEENode, - *UpdatedCall, *this, - /*WasInlined=*/true); - + if (const CXXNewExpr *CNE = dyn_cast_or_null<CXXNewExpr>(CE)) { + ExplodedNodeSet DstPostPostCallCallback; + getCheckerManager().runCheckersForPostCall(DstPostPostCallCallback, + CEENode, *UpdatedCall, *this, + /*WasInlined=*/true); + for (auto I : DstPostPostCallCallback) { + getCheckerManager().runCheckersForNewAllocator( + CNE, + *getObjectUnderConstruction(I->getState(), CNE, + calleeCtx->getParent()), + DstPostCall, I, *this, + /*WasInlined=*/true); + } + } else { + getCheckerManager().runCheckersForPostCall(DstPostCall, CEENode, + *UpdatedCall, *this, + /*WasInlined=*/true); + } ExplodedNodeSet Dst; if (const ObjCMethodCall *Msg = dyn_cast<ObjCMethodCall>(Call)) { getCheckerManager().runCheckersForPostObjCMessage(Dst, DstPostCall, *Msg, *this, /*WasInlined=*/true); - } else if (CE) { + } else if (CE && + !(isa<CXXNewExpr>(CE) && // Called when visiting CXXNewExpr. + AMgr.getAnalyzerOptions().mayInlineCXXAllocator())) { getCheckerManager().runCheckersForPostStmt(Dst, DstPostCall, CE, *this, /*WasInlined=*/true); } else { @@ -407,7 +416,7 @@ bool ExprEngine::inlineCall(const CallEvent &Call, const Decl *D, assert(D); const LocationContext *CurLC = Pred->getLocationContext(); - const StackFrameContext *CallerSFC = CurLC->getCurrentStackFrame(); + const StackFrameContext *CallerSFC = CurLC->getStackFrame(); const LocationContext *ParentOfCallee = CallerSFC; if (Call.getKind() == CE_Block && !cast<BlockCall>(Call).isConversionFromLambda()) { @@ -542,19 +551,45 @@ ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call, } } else if (const CXXConstructorCall *C = dyn_cast<CXXConstructorCall>(&Call)){ SVal ThisV = C->getCXXThisVal(); - - // If the constructed object is a temporary prvalue, get its bindings. - if (isTemporaryPRValue(cast<CXXConstructExpr>(E), ThisV)) - ThisV = State->getSVal(ThisV.castAs<Loc>()); - + ThisV = State->getSVal(ThisV.castAs<Loc>()); return State->BindExpr(E, LCtx, ThisV); } - // Conjure a symbol if the return value is unknown. + SVal R; QualType ResultTy = Call.getResultType(); - SValBuilder &SVB = getSValBuilder(); unsigned Count = currBldrCtx->blockCount(); - SVal R = SVB.conjureSymbolVal(nullptr, E, LCtx, ResultTy, Count); + if (auto RTC = getCurrentCFGElement().getAs<CFGCXXRecordTypedCall>()) { + // Conjure a temporary if the function returns an object by value. + SVal Target; + assert(RTC->getStmt() == Call.getOriginExpr()); + EvalCallOptions CallOpts; // FIXME: We won't really need those. + std::tie(State, Target) = + prepareForObjectConstruction(Call.getOriginExpr(), State, LCtx, + RTC->getConstructionContext(), CallOpts); + assert(Target.getAsRegion()); + // Invalidate the region so that it didn't look uninitialized. Don't notify + // the checkers. + State = State->invalidateRegions(Target.getAsRegion(), E, Count, LCtx, + /* CausedByPointerEscape=*/false, nullptr, + &Call, nullptr); + + R = State->getSVal(Target.castAs<Loc>(), E->getType()); + } else { + // Conjure a symbol if the return value is unknown. + + // See if we need to conjure a heap pointer instead of + // a regular unknown pointer. + bool IsHeapPointer = false; + if (const auto *CNE = dyn_cast<CXXNewExpr>(E)) + if (CNE->getOperatorNew()->isReplaceableGlobalAllocationFunction()) { + // FIXME: Delegate this to evalCall in MallocChecker? + IsHeapPointer = true; + } + + R = IsHeapPointer ? svalBuilder.getConjuredHeapSymbolVal(E, LCtx, Count) + : svalBuilder.conjureSymbolVal(nullptr, E, LCtx, ResultTy, + Count); + } return State->BindExpr(E, LCtx, R); } @@ -570,17 +605,12 @@ void ExprEngine::conservativeEvalCall(const CallEvent &Call, NodeBuilder &Bldr, Bldr.generateNode(Call.getProgramPoint(), State, Pred); } -enum CallInlinePolicy { - CIP_Allowed, - CIP_DisallowedOnce, - CIP_DisallowedAlways -}; - -static CallInlinePolicy mayInlineCallKind(const CallEvent &Call, - const ExplodedNode *Pred, - AnalyzerOptions &Opts) { +ExprEngine::CallInlinePolicy +ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, + AnalyzerOptions &Opts, + const ExprEngine::EvalCallOptions &CallOpts) { const LocationContext *CurLC = Pred->getLocationContext(); - const StackFrameContext *CallerSFC = CurLC->getCurrentStackFrame(); + const StackFrameContext *CallerSFC = CurLC->getStackFrame(); switch (Call.getKind()) { case CE_Function: case CE_Block: @@ -596,22 +626,24 @@ static CallInlinePolicy mayInlineCallKind(const CallEvent &Call, const CXXConstructorCall &Ctor = cast<CXXConstructorCall>(Call); + const CXXConstructExpr *CtorExpr = Ctor.getOriginExpr(); + + auto CCE = getCurrentCFGElement().getAs<CFGConstructor>(); + const ConstructionContext *CC = CCE ? CCE->getConstructionContext() + : nullptr; + + if (CC && isa<NewAllocatedObjectConstructionContext>(CC) && + !Opts.mayInlineCXXAllocator()) + return CIP_DisallowedOnce; + // FIXME: We don't handle constructors or destructors for arrays properly. // Even once we do, we still need to be careful about implicitly-generated // initializers for array fields in default move/copy constructors. - const MemRegion *Target = Ctor.getCXXThisVal().getAsRegion(); - if (Target && isa<ElementRegion>(Target)) + // We still allow construction into ElementRegion targets when they don't + // represent array elements. + if (CallOpts.IsArrayCtorOrDtor) return CIP_DisallowedOnce; - // FIXME: This is a hack. We don't use the correct region for a new - // expression, so if we inline the constructor its result will just be - // thrown away. This short-term hack is tracked in <rdar://problem/12180598> - // and the longer-term possible fix is discussed in PR12014. - const CXXConstructExpr *CtorExpr = Ctor.getOriginExpr(); - if (const Stmt *Parent = CurLC->getParentMap().getParent(CtorExpr)) - if (isa<CXXNewExpr>(Parent)) - return CIP_DisallowedOnce; - // Inlining constructors requires including initializers in the CFG. const AnalysisDeclContext *ADC = CallerSFC->getAnalysisDeclContext(); assert(ADC->getCFGBuildOptions().AddInitializers && "No CFG initializers"); @@ -626,12 +658,25 @@ static CallInlinePolicy mayInlineCallKind(const CallEvent &Call, if (!Opts.mayInlineCXXMemberFunction(CIMK_Destructors)) return CIP_DisallowedAlways; - // FIXME: This is a hack. We don't handle temporary destructors - // right now, so we shouldn't inline their constructors. - if (CtorExpr->getConstructionKind() == CXXConstructExpr::CK_Complete) - if (!Target || !isa<DeclRegion>(Target)) + if (CtorExpr->getConstructionKind() == CXXConstructExpr::CK_Complete) { + // If we don't handle temporary destructors, we shouldn't inline + // their constructors. + if (CallOpts.IsTemporaryCtorOrDtor && + !Opts.includeTemporaryDtorsInCFG()) + return CIP_DisallowedOnce; + + // If we did not find the correct this-region, it would be pointless + // to inline the constructor. Instead we will simply invalidate + // the fake temporary target. + if (CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion) return CIP_DisallowedOnce; + // If the temporary is lifetime-extended by binding it to a reference-type + // field within an aggregate, automatic destructors don't work properly. + if (CallOpts.IsTemporaryLifetimeExtendedViaAggregate) + return CIP_DisallowedOnce; + } + break; } case CE_CXXDestructor: { @@ -643,13 +688,19 @@ static CallInlinePolicy mayInlineCallKind(const CallEvent &Call, assert(ADC->getCFGBuildOptions().AddImplicitDtors && "No CFG destructors"); (void)ADC; - const CXXDestructorCall &Dtor = cast<CXXDestructorCall>(Call); - // FIXME: We don't handle constructors or destructors for arrays properly. - const MemRegion *Target = Dtor.getCXXThisVal().getAsRegion(); - if (Target && isa<ElementRegion>(Target)) + if (CallOpts.IsArrayCtorOrDtor) + return CIP_DisallowedOnce; + + // Allow disabling temporary destructor inlining with a separate option. + if (CallOpts.IsTemporaryCtorOrDtor && !Opts.mayInlineCXXTemporaryDtors()) return CIP_DisallowedOnce; + // If we did not find the correct this-region, it would be pointless + // to inline the destructor. Instead we will simply invalidate + // the fake temporary target. + if (CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion) + return CIP_DisallowedOnce; break; } case CE_CXXAllocator: @@ -731,8 +782,9 @@ static bool isCXXSharedPtrDtor(const FunctionDecl *FD) { /// This checks static properties of the function, such as its signature and /// CFG, to determine whether the analyzer should ever consider inlining it, /// in any context. -static bool mayInlineDecl(AnalysisDeclContext *CalleeADC, - AnalyzerOptions &Opts) { +static bool mayInlineDecl(AnalysisManager &AMgr, + AnalysisDeclContext *CalleeADC) { + AnalyzerOptions &Opts = AMgr.getAnalyzerOptions(); // FIXME: Do not inline variadic calls. if (CallEvent::isVariadic(CalleeADC->getDecl())) return false; @@ -755,7 +807,7 @@ static bool mayInlineDecl(AnalysisDeclContext *CalleeADC, // Conditionally control the inlining of methods on objects that look // like C++ containers. if (!Opts.mayInlineCXXContainerMethods()) - if (!Ctx.getSourceManager().isInMainFile(FD->getLocation())) + if (!AMgr.isInCodeFile(FD->getLocation())) if (isContainerMethod(Ctx, FD)) return false; @@ -788,7 +840,8 @@ static bool mayInlineDecl(AnalysisDeclContext *CalleeADC, } bool ExprEngine::shouldInlineCall(const CallEvent &Call, const Decl *D, - const ExplodedNode *Pred) { + const ExplodedNode *Pred, + const EvalCallOptions &CallOpts) { if (!D) return false; @@ -797,14 +850,6 @@ bool ExprEngine::shouldInlineCall(const CallEvent &Call, const Decl *D, AnalysisDeclContextManager &ADCMgr = AMgr.getAnalysisDeclContextManager(); AnalysisDeclContext *CalleeADC = ADCMgr.getContext(D); - // Temporary object destructor processing is currently broken, so we never - // inline them. - // FIXME: Remove this once temp destructors are working. - if (isa<CXXDestructorCall>(Call)) { - if ((*currBldrCtx->getBlock())[currStmtIdx].getAs<CFGTemporaryDtor>()) - return false; - } - // The auto-synthesized bodies are essential to inline as they are // usually small and commonly used. Note: we should do this check early on to // ensure we always inline these calls. @@ -823,7 +868,7 @@ bool ExprEngine::shouldInlineCall(const CallEvent &Call, const Decl *D, } else { // We haven't actually checked the static properties of this function yet. // Do that now, and record our decision in the function summaries. - if (mayInlineDecl(CalleeADC, Opts)) { + if (mayInlineDecl(getAnalysisManager(), CalleeADC)) { Engine.FunctionSummaries->markMayInline(D); } else { Engine.FunctionSummaries->markShouldNotInline(D); @@ -835,7 +880,7 @@ bool ExprEngine::shouldInlineCall(const CallEvent &Call, const Decl *D, // FIXME: this checks both static and dynamic properties of the call, which // means we're redoing a bit of work that could be cached in the function // summary. - CallInlinePolicy CIP = mayInlineCallKind(Call, Pred, Opts); + CallInlinePolicy CIP = mayInlineCallKind(Call, Pred, Opts, CallOpts); if (CIP != CIP_Allowed) { if (CIP == CIP_DisallowedAlways) { assert(!MayInline.hasValue() || MayInline.getValue()); @@ -887,7 +932,8 @@ static bool isTrivialObjectAssignment(const CallEvent &Call) { } void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred, - const CallEvent &CallTemplate) { + const CallEvent &CallTemplate, + const EvalCallOptions &CallOpts) { // Make sure we have the most recent state attached to the call. ProgramStateRef State = Pred->getState(); CallEventRef<> Call = CallTemplate.cloneWithState(State); @@ -910,7 +956,7 @@ void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred, } else { RuntimeDefinition RD = Call->getRuntimeDefinition(); const Decl *D = RD.getDecl(); - if (shouldInlineCall(*Call, D, Pred)) { + if (shouldInlineCall(*Call, D, Pred, CallOpts)) { if (RD.mayHaveOtherDefinitions()) { AnalyzerOptions &Options = getAnalysisManager().options; diff --git a/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp b/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp index f5e64f4a5a8c..d76b9cbcfaca 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp @@ -42,6 +42,47 @@ void ExprEngine::VisitObjCAtSynchronizedStmt(const ObjCAtSynchronizedStmt *S, getCheckerManager().runCheckersForPreStmt(Dst, Pred, S, *this); } +/// Generate a node in \p Bldr for an iteration statement using ObjC +/// for-loop iterator. +static void populateObjCForDestinationSet( + ExplodedNodeSet &dstLocation, SValBuilder &svalBuilder, + const ObjCForCollectionStmt *S, const Stmt *elem, SVal elementV, + SymbolManager &SymMgr, const NodeBuilderContext *currBldrCtx, + StmtNodeBuilder &Bldr, bool hasElements) { + + for (ExplodedNode *Pred : dstLocation) { + 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); + + if (auto MV = elementV.getAs<loc::MemRegionVal>()) + if (const auto *R = dyn_cast<TypedValueRegion>(MV->getRegion())) { + // FIXME: The proper thing to do is to really iterate over the + // container. We will do this with dispatch logic to the store. + // For now, just 'conjure' up a symbolic value. + QualType T = R->getValueType(); + assert(Loc::isLocType(T)); + + SVal V; + if (hasElements) { + SymbolRef Sym = SymMgr.conjureSymbol(elem, LCtx, T, + currBldrCtx->blockCount()); + V = svalBuilder.makeLoc(Sym); + } else { + V = svalBuilder.makeIntVal(0, T); + } + + nextState = nextState->bindLoc(elementV, V, LCtx); + } + + Bldr.generateNode(S, Pred, nextState); + } +} + void ExprEngine::VisitObjCForCollectionStmt(const ObjCForCollectionStmt *S, ExplodedNode *Pred, ExplodedNodeSet &Dst) { @@ -72,60 +113,35 @@ void ExprEngine::VisitObjCForCollectionStmt(const ObjCForCollectionStmt *S, // result in state splitting. const Stmt *elem = S->getElement(); + const Stmt *collection = S->getCollection(); ProgramStateRef state = Pred->getState(); - SVal elementV; + SVal collectionV = state->getSVal(collection, Pred->getLocationContext()); - if (const DeclStmt *DS = dyn_cast<DeclStmt>(elem)) { + SVal elementV; + if (const auto *DS = dyn_cast<DeclStmt>(elem)) { const VarDecl *elemD = cast<VarDecl>(DS->getSingleDecl()); assert(elemD->getInit() == nullptr); elementV = state->getLValue(elemD, Pred->getLocationContext()); - } - else { + } else { elementV = state->getSVal(elem, Pred->getLocationContext()); } + bool isContainerNull = state->isNull(collectionV).isConstrainedTrue(); + ExplodedNodeSet dstLocation; evalLocation(dstLocation, S, elem, Pred, state, elementV, nullptr, false); ExplodedNodeSet Tmp; StmtNodeBuilder Bldr(Pred, Tmp, *currBldrCtx); - for (ExplodedNodeSet::iterator NI = dstLocation.begin(), - NE = dstLocation.end(); NI!=NE; ++NI) { - Pred = *NI; - ProgramStateRef state = Pred->getState(); - const LocationContext *LCtx = Pred->getLocationContext(); - - // Handle the case where the container still has elements. - SVal TrueV = svalBuilder.makeTruthVal(1); - ProgramStateRef hasElems = state->BindExpr(S, LCtx, TrueV); - - // Handle the case where the container has no elements. - SVal FalseV = svalBuilder.makeTruthVal(0); - ProgramStateRef noElems = state->BindExpr(S, LCtx, FalseV); + if (!isContainerNull) + populateObjCForDestinationSet(dstLocation, svalBuilder, S, elem, elementV, + SymMgr, currBldrCtx, Bldr, + /*hasElements=*/true); - if (Optional<loc::MemRegionVal> MV = elementV.getAs<loc::MemRegionVal>()) - if (const TypedValueRegion *R = - dyn_cast<TypedValueRegion>(MV->getRegion())) { - // FIXME: The proper thing to do is to really iterate over the - // container. We will do this with dispatch logic to the store. - // For now, just 'conjure' up a symbolic value. - QualType T = R->getValueType(); - assert(Loc::isLocType(T)); - SymbolRef Sym = SymMgr.conjureSymbol(elem, LCtx, T, - currBldrCtx->blockCount()); - SVal V = svalBuilder.makeLoc(Sym); - hasElems = hasElems->bindLoc(elementV, V, LCtx); - - // Bind the location to 'nil' on the false branch. - SVal nilV = svalBuilder.makeIntVal(0, T); - noElems = noElems->bindLoc(elementV, nilV, LCtx); - } - - // Create the new nodes. - Bldr.generateNode(S, Pred, hasElems); - Bldr.generateNode(S, Pred, noElems); - } + populateObjCForDestinationSet(dstLocation, svalBuilder, S, elem, elementV, + SymMgr, currBldrCtx, Bldr, + /*hasElements=*/false); // Finally, run any custom checkers. // FIXME: Eventually all pre- and post-checks should live in VisitStmt. diff --git a/lib/StaticAnalyzer/Core/FunctionSummary.cpp b/lib/StaticAnalyzer/Core/FunctionSummary.cpp index c21735b8b882..94edd84d15d2 100644 --- a/lib/StaticAnalyzer/Core/FunctionSummary.cpp +++ b/lib/StaticAnalyzer/Core/FunctionSummary.cpp @@ -1,4 +1,4 @@ -//== FunctionSummary.cpp - Stores summaries of functions. ----------*- C++ -*-// +//===- FunctionSummary.cpp - Stores summaries of functions. ---------------===// // // The LLVM Compiler Infrastructure // @@ -12,21 +12,20 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/PathSensitive/FunctionSummary.h" + using namespace clang; using namespace ento; unsigned FunctionSummariesTy::getTotalNumBasicBlocks() { unsigned Total = 0; - for (MapTy::iterator I = Map.begin(), E = Map.end(); I != E; ++I) { - Total += I->second.TotalBasicBlocks; - } + for (const auto &I : Map) + Total += I.second.TotalBasicBlocks; return Total; } unsigned FunctionSummariesTy::getTotalNumVisitedBasicBlocks() { unsigned Total = 0; - for (MapTy::iterator I = Map.begin(), E = Map.end(); I != E; ++I) { - Total += I->second.VisitedBasicBlocks.count(); - } + for (const auto &I : Map) + Total += I.second.VisitedBasicBlocks.count(); return Total; } diff --git a/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp index ebf1487d4bfc..d5e5f96dee0f 100644 --- a/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ b/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -1,4 +1,4 @@ -//===--- HTMLDiagnostics.cpp - HTML Diagnostics for Paths ----*- C++ -*-===// +//===- HTMLDiagnostics.cpp - HTML Diagnostics for Paths -------------------===// // // The LLVM Compiler Infrastructure // @@ -11,24 +11,43 @@ // //===----------------------------------------------------------------------===// -#include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" +#include "clang/AST/Stmt.h" #include "clang/Basic/FileManager.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" #include "clang/Lex/Preprocessor.h" +#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/BugReporter/PathDiagnostic.h" -#include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/IssueHash.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/iterator_range.h" +#include "llvm/Support/Casting.h" #include "llvm/Support/Errc.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" +#include <algorithm> +#include <cassert> +#include <map> +#include <memory> +#include <set> #include <sstream> +#include <string> +#include <system_error> +#include <utility> +#include <vector> using namespace clang; using namespace ento; @@ -41,15 +60,19 @@ namespace { class HTMLDiagnostics : public PathDiagnosticConsumer { std::string Directory; - bool createdDir, noDir; + bool createdDir = false; + bool noDir = false; const Preprocessor &PP; AnalyzerOptions &AnalyzerOpts; const bool SupportsCrossFileDiagnostics; + public: HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, const std::string& prefix, const Preprocessor &pp, - bool supportsMultipleFiles); + bool supportsMultipleFiles) + : Directory(prefix), PP(pp), AnalyzerOpts(AnalyzerOpts), + SupportsCrossFileDiagnostics(supportsMultipleFiles) {} ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); } @@ -94,20 +117,13 @@ public: /// \return Javascript for navigating the HTML report using j/k keys. std::string generateKeyboardNavigationJavascript(); -}; -} // end anonymous namespace +private: + /// \return Javascript for displaying shortcuts help; + std::string showHelpJavascript(); +}; -HTMLDiagnostics::HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, - const std::string& prefix, - const Preprocessor &pp, - bool supportsMultipleFiles) - : Directory(prefix), - createdDir(false), - noDir(false), - PP(pp), - AnalyzerOpts(AnalyzerOpts), - SupportsCrossFileDiagnostics(supportsMultipleFiles) {} +} // namespace void ento::createHTMLDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, @@ -130,24 +146,19 @@ void ento::createHTMLSingleFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, void HTMLDiagnostics::FlushDiagnosticsImpl( std::vector<const PathDiagnostic *> &Diags, FilesMade *filesMade) { - for (std::vector<const PathDiagnostic *>::iterator it = Diags.begin(), - et = Diags.end(); it != et; ++it) { - ReportDiag(**it, filesMade); - } + for (const auto Diag : Diags) + ReportDiag(*Diag, filesMade); } void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, FilesMade *filesMade) { - // Create the HTML directory if it is missing. if (!createdDir) { createdDir = true; if (std::error_code ec = llvm::sys::fs::create_directories(Directory)) { llvm::errs() << "warning: could not create directory '" << Directory << "': " << ec.message() << '\n'; - noDir = true; - return; } } @@ -174,7 +185,7 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, SmallString<128> declName("unknown"); int offsetDecl = 0; if (const Decl *DeclWithIssue = D.getDeclWithIssue()) { - if (const NamedDecl *ND = dyn_cast<NamedDecl>(DeclWithIssue)) + if (const auto *ND = dyn_cast<NamedDecl>(DeclWithIssue)) declName = ND->getDeclName().getAsString(); if (const Stmt *Body = DeclWithIssue->getBody()) { @@ -212,7 +223,6 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, << "': " << EC.message() << '\n'; return; } - } else { int i = 1; std::error_code EC; @@ -228,10 +238,8 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, << "-" << i << ".html"; llvm::sys::path::append(Model, Directory, filename.str()); - EC = llvm::sys::fs::openFileForWrite(Model, - FD, - llvm::sys::fs::F_RW | - llvm::sys::fs::F_Excl); + EC = llvm::sys::fs::openFileForReadWrite( + Model, FD, llvm::sys::fs::CD_CreateNew, llvm::sys::fs::OF_None); if (EC && EC != llvm::errc::file_exists) { llvm::errs() << "warning: could not create file '" << Model << "': " << EC.message() << '\n'; @@ -253,7 +261,6 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R, const SourceManager& SMgr, const PathPieces& path, const char *declName) { - // Rewrite source files as HTML for every new file the path crosses std::vector<FileID> FileIDs; for (auto I : path) { @@ -309,10 +316,12 @@ std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R, const RewriteBuffer *Buf = R.getRewriteBufferFor(FileIDs[0]); if (!Buf) - return ""; + return {}; // Add CSS, header, and footer. - const FileEntry* Entry = SMgr.getFileEntryForID(FileIDs[0]); + FileID FID = + path.back()->getLocation().asLocation().getExpansionLoc().getFileID(); + const FileEntry* Entry = SMgr.getFileEntryForID(FID); FinalizeHTML(D, R, SMgr, path, FileIDs[0], Entry, declName); std::string file; @@ -323,6 +332,114 @@ std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R, return os.str(); } +/// Write executed lines from \p D in JSON format into \p os. +static void serializeExecutedLines( + const PathDiagnostic &D, + const PathPieces &path, + llvm::raw_string_ostream &os) { + // Copy executed lines from path diagnostics. + std::map<unsigned, std::set<unsigned>> ExecutedLines; + for (auto I = D.executedLines_begin(), + E = D.executedLines_end(); I != E; ++I) { + std::set<unsigned> &LinesInFile = ExecutedLines[I->first]; + for (unsigned LineNo : I->second) { + LinesInFile.insert(LineNo); + } + } + + // We need to include all lines for which any kind of diagnostics appears. + for (const auto &P : path) { + FullSourceLoc Loc = P->getLocation().asLocation().getExpansionLoc(); + FileID FID = Loc.getFileID(); + unsigned LineNo = Loc.getLineNumber(); + ExecutedLines[FID.getHashValue()].insert(LineNo); + } + + os << "var relevant_lines = {"; + for (auto I = ExecutedLines.begin(), + E = ExecutedLines.end(); I != E; ++I) { + if (I != ExecutedLines.begin()) + os << ", "; + + os << "\"" << I->first << "\": {"; + for (unsigned LineNo : I->second) { + if (LineNo != *(I->second.begin())) + os << ", "; + + os << "\"" << LineNo << "\": 1"; + } + os << "}"; + } + + os << "};"; +} + +/// \return JavaScript for an option to only show relevant lines. +static std::string showRelevantLinesJavascript( + const PathDiagnostic &D, const PathPieces &path) { + std::string s; + llvm::raw_string_ostream os(s); + os << "<script type='text/javascript'>\n"; + serializeExecutedLines(D, path, os); + os << R"<<<( + +var filterCounterexample = function (hide) { + var tables = document.getElementsByClassName("code"); + for (var t=0; t<tables.length; t++) { + var table = tables[t]; + var file_id = table.getAttribute("data-fileid"); + var lines_in_fid = relevant_lines[file_id]; + if (!lines_in_fid) { + lines_in_fid = {}; + } + var lines = table.getElementsByClassName("codeline"); + for (var i=0; i<lines.length; i++) { + var el = lines[i]; + var lineNo = el.getAttribute("data-linenumber"); + if (!lines_in_fid[lineNo]) { + if (hide) { + el.setAttribute("hidden", ""); + } else { + el.removeAttribute("hidden"); + } + } + } + } +} + +window.addEventListener("keydown", function (event) { + if (event.defaultPrevented) { + return; + } + if (event.key == "S") { + var checked = document.getElementsByName("showCounterexample")[0].checked; + filterCounterexample(!checked); + document.getElementsByName("showCounterexample")[0].checked = !checked; + } else { + return; + } + event.preventDefault(); +}, true); + +document.addEventListener("DOMContentLoaded", function() { + document.querySelector('input[name="showCounterexample"]').onchange= + function (event) { + filterCounterexample(this.checked); + }; +}); +</script> + +<form> + <input type="checkbox" name="showCounterexample" id="showCounterexample" /> + <label for="showCounterexample"> + Show only relevant lines + </label> +</form> +)<<<"; + + return os.str(); +} + void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, const SourceManager& SMgr, const PathPieces& path, FileID FID, const FileEntry *Entry, const char *declName) { @@ -340,9 +457,15 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, int LineNumber = path.back()->getLocation().asLocation().getExpansionLineNumber(); int ColumnNumber = path.back()->getLocation().asLocation().getExpansionColumnNumber(); + R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), showHelpJavascript()); + R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), generateKeyboardNavigationJavascript()); + // Checkbox and javascript for filtering the output to the counterexample. + R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), + showRelevantLinesJavascript(D, path)); + // Add the name of the file as an <h1> tag. { std::string s; @@ -379,8 +502,8 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, // Output any other meta data. - for (PathDiagnostic::meta_iterator I=D.meta_begin(), E=D.meta_end(); - I!=E; ++I) { + for (PathDiagnostic::meta_iterator I = D.meta_begin(), E = D.meta_end(); + I != E; ++I) { os << "<tr><td></td><td>" << html::EscapeText(*I) << "</td></tr>\n"; } @@ -388,11 +511,24 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, </table> <!-- REPORTSUMMARYEXTRA --> <h3>Annotated Source Code</h3> -<p><span class='macro'>[?] - <span class='expansion'>Use j/k keys for keyboard navigation</span> -</span></p> +<p>Press <a href="#" onclick="toggleHelp(); return false;">'?'</a> + to see keyboard shortcuts</p> +<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 << R"<<<( +</div> +<div id='tooltiphint' hidden="true"> + <p>Keyboard shortcuts: </p> + <ul> + <li>Use 'j/k' keys for keyboard navigation</li> + <li>Use 'Shift+S' to show/hide relevant lines</li> + <li>Use '?' to toggle this window</li> + </ul> + <a href="#" onclick="toggleHelp(); return false;">Close</a> +</div> )<<<"; - R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str()); } @@ -450,6 +586,34 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry->getName()); } +std::string HTMLDiagnostics::showHelpJavascript() { + return R"<<<( +<script type='text/javascript'> + +var toggleHelp = function() { + var hint = document.querySelector("#tooltiphint"); + var attributeName = "hidden"; + if (hint.hasAttribute(attributeName)) { + hint.removeAttribute(attributeName); + } else { + hint.setAttribute("hidden", "true"); + } +}; +window.addEventListener("keydown", function (event) { + if (event.defaultPrevented) { + return; + } + if (event.key == "?") { + toggleHelp(); + } else { + return; + } + event.preventDefault(); +}); +</script> +)<<<"; +} + void HTMLDiagnostics::RewriteFile(Rewriter &R, const SourceManager& SMgr, const PathPieces& path, FileID FID) { // Process the path. @@ -494,7 +658,6 @@ void HTMLDiagnostics::RewriteFile(Rewriter &R, const SourceManager& SMgr, void HTMLDiagnostics::HandlePiece(Rewriter& R, FileID BugFileID, const PathDiagnosticPiece& P, unsigned num, unsigned max) { - // For now, just draw a box above the line in question, and emit the // warning. FullSourceLoc Pos = P.getLocation().asLocation(); @@ -634,9 +797,7 @@ void HTMLDiagnostics::HandlePiece(Rewriter& R, FileID BugFileID, os << "</td><td>"; } - if (const PathDiagnosticMacroPiece *MP = - dyn_cast<PathDiagnosticMacroPiece>(&P)) { - + if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(&P)) { os << "Within the expansion of the macro '"; // Get the name of the macro by relexing it. @@ -707,10 +868,8 @@ void HTMLDiagnostics::HandlePiece(Rewriter& R, FileID BugFileID, // Now highlight the ranges. ArrayRef<SourceRange> Ranges = P.getRanges(); - for (ArrayRef<SourceRange>::iterator I = Ranges.begin(), - E = Ranges.end(); I != E; ++I) { - HighlightRange(R, LPosInfo.first, *I); - } + for (const auto &Range : Ranges) + HighlightRange(R, LPosInfo.first, Range); } static void EmitAlphaCounter(raw_ostream &os, unsigned n) { @@ -726,18 +885,13 @@ static void EmitAlphaCounter(raw_ostream &os, unsigned n) { unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os, const PathDiagnosticMacroPiece& P, unsigned num) { - - for (PathPieces::const_iterator I = P.subPieces.begin(), E=P.subPieces.end(); - I!=E; ++I) { - - if (const PathDiagnosticMacroPiece *MP = - dyn_cast<PathDiagnosticMacroPiece>(I->get())) { + for (const auto &subPiece : P.subPieces) { + if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(subPiece.get())) { num = ProcessMacroPiece(os, *MP, num); continue; } - if (PathDiagnosticEventPiece *EP = - dyn_cast<PathDiagnosticEventPiece>(I->get())) { + if (const auto *EP = dyn_cast<PathDiagnosticEventPiece>(subPiece.get())) { os << "<div class=\"msg msgEvent\" style=\"width:94%; " "margin-left:5px\">" "<table class=\"msgT\"><tr>" @@ -862,7 +1016,7 @@ window.addEventListener("keydown", function (event) { navigateTo(/*up=*/true); } else { return; - } + } event.preventDefault(); }, true); </script> diff --git a/lib/StaticAnalyzer/Core/LoopUnrolling.cpp b/lib/StaticAnalyzer/Core/LoopUnrolling.cpp index a8c4b05cea13..da4574c61515 100644 --- a/lib/StaticAnalyzer/Core/LoopUnrolling.cpp +++ b/lib/StaticAnalyzer/Core/LoopUnrolling.cpp @@ -97,9 +97,7 @@ changeIntBoundNode(internal::Matcher<Decl> VarNodeMatcher) { unaryOperator(anyOf(hasOperatorName("--"), hasOperatorName("++")), hasUnaryOperand(ignoringParenImpCasts( declRefExpr(to(varDecl(VarNodeMatcher)))))), - binaryOperator(anyOf(hasOperatorName("="), hasOperatorName("+="), - hasOperatorName("/="), hasOperatorName("*="), - hasOperatorName("-=")), + binaryOperator(isAssignmentOperator(), hasLHS(ignoringParenImpCasts( declRefExpr(to(varDecl(VarNodeMatcher))))))); } @@ -143,13 +141,15 @@ static internal::Matcher<Stmt> forLoopMatcher() { return forStmt( hasCondition(simpleCondition("initVarName")), // Initialization should match the form: 'int i = 6' or 'i = 42'. - hasLoopInit(anyOf( - declStmt(hasSingleDecl(varDecl( - allOf(hasInitializer(integerLiteral().bind("initNum")), - equalsBoundNode("initVarName"))))), - binaryOperator(hasLHS(declRefExpr(to( - varDecl(equalsBoundNode("initVarName"))))), - hasRHS(integerLiteral().bind("initNum"))))), + hasLoopInit( + anyOf(declStmt(hasSingleDecl( + varDecl(allOf(hasInitializer(ignoringParenImpCasts( + integerLiteral().bind("initNum"))), + equalsBoundNode("initVarName"))))), + binaryOperator(hasLHS(declRefExpr(to(varDecl( + equalsBoundNode("initVarName"))))), + hasRHS(ignoringParenImpCasts( + integerLiteral().bind("initNum")))))), // Incrementation should be a simple increment or decrement // operator call. hasIncrement(unaryOperator( diff --git a/lib/StaticAnalyzer/Core/LoopWidening.cpp b/lib/StaticAnalyzer/Core/LoopWidening.cpp index 05865c294cb7..9192f49eac6d 100644 --- a/lib/StaticAnalyzer/Core/LoopWidening.cpp +++ b/lib/StaticAnalyzer/Core/LoopWidening.cpp @@ -14,10 +14,16 @@ /// //===----------------------------------------------------------------------===// +#include "clang/AST/AST.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" #include "clang/StaticAnalyzer/Core/PathSensitive/LoopWidening.h" using namespace clang; using namespace ento; +using namespace clang::ast_matchers; + +const auto MatchRef = "matchref"; /// Return the loops condition Stmt or NULL if LoopStmt is not a loop static const Expr *getLoopCondition(const Stmt *LoopStmt) { @@ -49,7 +55,8 @@ ProgramStateRef getWidenedLoopState(ProgramStateRef PrevState, // TODO Nested loops are currently widened as a result of the invalidation // being so inprecise. When the invalidation is improved, the handling // of nested loops will also need to be improved. - const StackFrameContext *STC = LCtx->getCurrentStackFrame(); + ASTContext &ASTCtx = LCtx->getAnalysisDeclContext()->getASTContext(); + const StackFrameContext *STC = LCtx->getStackFrame(); MemRegionManager &MRMgr = PrevState->getStateManager().getRegionManager(); const MemRegion *Regions[] = {MRMgr.getStackLocalsRegion(STC), MRMgr.getStackArgumentsRegion(STC), @@ -59,6 +66,30 @@ ProgramStateRef getWidenedLoopState(ProgramStateRef PrevState, ITraits.setTrait(Region, RegionAndSymbolInvalidationTraits::TK_EntireMemSpace); } + + // References should not be invalidated. + auto Matches = match(findAll(stmt(hasDescendant(varDecl(hasType(referenceType())).bind(MatchRef)))), + *LCtx->getDecl()->getBody(), ASTCtx); + for (BoundNodes Match : Matches) { + const VarDecl *VD = Match.getNodeAs<VarDecl>(MatchRef); + assert(VD); + const VarRegion *VarMem = MRMgr.getVarRegion(VD, LCtx); + ITraits.setTrait(VarMem, + RegionAndSymbolInvalidationTraits::TK_PreserveContents); + } + + + // 'this' pointer is not an lvalue, we should not invalidate it. If the loop + // is located in a method, constructor or destructor, the value of 'this' + // pointer shoule remain unchanged. + if (const CXXMethodDecl *CXXMD = dyn_cast<CXXMethodDecl>(STC->getDecl())) { + const CXXThisRegion *ThisR = MRMgr.getCXXThisRegion( + CXXMD->getThisType(STC->getAnalysisDeclContext()->getASTContext()), + STC); + ITraits.setTrait(ThisR, + RegionAndSymbolInvalidationTraits::TK_PreserveContents); + } + return PrevState->invalidateRegions(Regions, getLoopCondition(LoopStmt), BlockCount, LCtx, true, nullptr, nullptr, &ITraits); diff --git a/lib/StaticAnalyzer/Core/MemRegion.cpp b/lib/StaticAnalyzer/Core/MemRegion.cpp index cb8ba6de3626..cb2122c7749e 100644 --- a/lib/StaticAnalyzer/Core/MemRegion.cpp +++ b/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -1,4 +1,4 @@ -//== MemRegion.cpp - Abstract memory regions for static analysis --*- C++ -*--// +//===- MemRegion.cpp - Abstract memory regions for static analysis --------===// // // The LLVM Compiler Infrastructure // @@ -14,19 +14,51 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" +#include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" #include "clang/AST/CharUnits.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" +#include "clang/AST/Expr.h" +#include "clang/AST/PrettyPrinter.h" #include "clang/AST/RecordLayout.h" +#include "clang/AST/Type.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/Support/BumpVector.h" +#include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/LLVM.h" #include "clang/Basic/SourceManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/CheckedArithmetic.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" +#include <cassert> +#include <cstdint> +#include <functional> +#include <iterator> +#include <string> +#include <tuple> +#include <utility> using namespace clang; using namespace ento; +#define DEBUG_TYPE "MemRegion" + //===----------------------------------------------------------------------===// // MemRegion Construction. //===----------------------------------------------------------------------===// @@ -37,8 +69,7 @@ RegionTy* MemRegionManager::getSubRegion(const Arg1Ty arg1, llvm::FoldingSetNodeID ID; RegionTy::ProfileRegion(ID, arg1, superRegion); void *InsertPos; - RegionTy* R = cast_or_null<RegionTy>(Regions.FindNodeOrInsertPos(ID, - InsertPos)); + auto *R = cast_or_null<RegionTy>(Regions.FindNodeOrInsertPos(ID, InsertPos)); if (!R) { R = A.Allocate<RegionTy>(); @@ -55,8 +86,7 @@ RegionTy* MemRegionManager::getSubRegion(const Arg1Ty arg1, const Arg2Ty arg2, llvm::FoldingSetNodeID ID; RegionTy::ProfileRegion(ID, arg1, arg2, superRegion); void *InsertPos; - RegionTy* R = cast_or_null<RegionTy>(Regions.FindNodeOrInsertPos(ID, - InsertPos)); + auto *R = cast_or_null<RegionTy>(Regions.FindNodeOrInsertPos(ID, InsertPos)); if (!R) { R = A.Allocate<RegionTy>(); @@ -75,8 +105,7 @@ RegionTy* MemRegionManager::getSubRegion(const Arg1Ty arg1, const Arg2Ty arg2, llvm::FoldingSetNodeID ID; RegionTy::ProfileRegion(ID, arg1, arg2, arg3, superRegion); void *InsertPos; - RegionTy* R = cast_or_null<RegionTy>(Regions.FindNodeOrInsertPos(ID, - InsertPos)); + auto *R = cast_or_null<RegionTy>(Regions.FindNodeOrInsertPos(ID, InsertPos)); if (!R) { R = A.Allocate<RegionTy>(); @@ -91,27 +120,26 @@ RegionTy* MemRegionManager::getSubRegion(const Arg1Ty arg1, const Arg2Ty arg2, // Object destruction. //===----------------------------------------------------------------------===// -MemRegion::~MemRegion() {} +MemRegion::~MemRegion() = default; -MemRegionManager::~MemRegionManager() { - // All regions and their data are BumpPtrAllocated. No need to call - // their destructors. -} +// All regions and their data are BumpPtrAllocated. No need to call their +// destructors. +MemRegionManager::~MemRegionManager() = default; //===----------------------------------------------------------------------===// // Basic methods. //===----------------------------------------------------------------------===// bool SubRegion::isSubRegionOf(const MemRegion* R) const { - const MemRegion* r = getSuperRegion(); - while (r != nullptr) { + const MemRegion* r = this; + do { if (r == R) return true; - if (const SubRegion* sr = dyn_cast<SubRegion>(r)) + if (const auto *sr = dyn_cast<SubRegion>(r)) r = sr->getSuperRegion(); else break; - } + } while (r != nullptr); return false; } @@ -119,16 +147,16 @@ MemRegionManager* SubRegion::getMemRegionManager() const { const SubRegion* r = this; do { const MemRegion *superRegion = r->getSuperRegion(); - if (const SubRegion *sr = dyn_cast<SubRegion>(superRegion)) { + if (const auto *sr = dyn_cast<SubRegion>(superRegion)) { r = sr; continue; } return superRegion->getMemRegionManager(); - } while (1); + } while (true); } const StackFrameContext *VarRegion::getStackFrame() const { - const StackSpaceRegion *SSR = dyn_cast<StackSpaceRegion>(getMemorySpace()); + const auto *SSR = dyn_cast<StackSpaceRegion>(getMemorySpace()); return SSR ? SSR->getStackFrame() : nullptr; } @@ -215,17 +243,17 @@ void StaticGlobalSpaceRegion::Profile(llvm::FoldingSetNodeID &ID) const { ID.AddPointer(getCodeRegion()); } -void StringRegion::ProfileRegion(llvm::FoldingSetNodeID& ID, - const StringLiteral* Str, - const MemRegion* superRegion) { +void StringRegion::ProfileRegion(llvm::FoldingSetNodeID &ID, + const StringLiteral *Str, + const MemRegion *superRegion) { ID.AddInteger(static_cast<unsigned>(StringRegionKind)); ID.AddPointer(Str); ID.AddPointer(superRegion); } -void ObjCStringRegion::ProfileRegion(llvm::FoldingSetNodeID& ID, - const ObjCStringLiteral* Str, - const MemRegion* superRegion) { +void ObjCStringRegion::ProfileRegion(llvm::FoldingSetNodeID &ID, + const ObjCStringLiteral *Str, + const MemRegion *superRegion) { ID.AddInteger(static_cast<unsigned>(ObjCStringRegionKind)); ID.AddPointer(Str); ID.AddPointer(superRegion); @@ -380,13 +408,19 @@ void CXXBaseObjectRegion::Profile(llvm::FoldingSetNodeID &ID) const { // Region anchors. //===----------------------------------------------------------------------===// -void GlobalsSpaceRegion::anchor() { } -void NonStaticGlobalSpaceRegion::anchor() { } -void StackSpaceRegion::anchor() { } -void TypedRegion::anchor() { } -void TypedValueRegion::anchor() { } -void CodeTextRegion::anchor() { } -void SubRegion::anchor() { } +void GlobalsSpaceRegion::anchor() {} + +void NonStaticGlobalSpaceRegion::anchor() {} + +void StackSpaceRegion::anchor() {} + +void TypedRegion::anchor() {} + +void TypedValueRegion::anchor() {} + +void CodeTextRegion::anchor() {} + +void SubRegion::anchor() {} //===----------------------------------------------------------------------===// // Region pretty-printing. @@ -408,7 +442,7 @@ void MemRegion::dumpToStream(raw_ostream &os) const { } void AllocaRegion::dumpToStream(raw_ostream &os) const { - os << "alloca{" << static_cast<const void*>(Ex) << ',' << Cnt << '}'; + os << "alloca{" << static_cast<const void *>(Ex) << ',' << Cnt << '}'; } void FunctionCodeRegion::dumpToStream(raw_ostream &os) const { @@ -416,7 +450,7 @@ void FunctionCodeRegion::dumpToStream(raw_ostream &os) const { } void BlockCodeRegion::dumpToStream(raw_ostream &os) const { - os << "block_code{" << static_cast<const void*>(this) << '}'; + os << "block_code{" << static_cast<const void *>(this) << '}'; } void BlockDataRegion::dumpToStream(raw_ostream &os) const { @@ -425,19 +459,19 @@ void BlockDataRegion::dumpToStream(raw_ostream &os) const { for (BlockDataRegion::referenced_vars_iterator I = referenced_vars_begin(), E = referenced_vars_end(); I != E; ++I) - os << "(" << I.getCapturedRegion() << "," << + os << "(" << I.getCapturedRegion() << "<-" << I.getOriginalRegion() << ") "; os << '}'; } void CompoundLiteralRegion::dumpToStream(raw_ostream &os) const { // FIXME: More elaborate pretty-printing. - os << "{ " << static_cast<const void*>(CL) << " }"; + os << "{ " << static_cast<const void *>(CL) << " }"; } void CXXTempObjectRegion::dumpToStream(raw_ostream &os) const { os << "temp_object{" << getValueType().getAsString() << ',' - << static_cast<const void*>(Ex) << '}'; + << static_cast<const void *>(Ex) << '}'; } void CXXBaseObjectRegion::dumpToStream(raw_ostream &os) const { @@ -478,7 +512,11 @@ void SymbolicRegion::dumpToStream(raw_ostream &os) const { } void VarRegion::dumpToStream(raw_ostream &os) const { - os << *cast<VarDecl>(D); + const auto *VD = cast<VarDecl>(D); + if (const IdentifierInfo *ID = VD->getIdentifier()) + os << ID->getName(); + else + os << "VarRegion{" << static_cast<const void *>(this) << '}'; } LLVM_DUMP_METHOD void RegionRawOffset::dump() const { @@ -622,19 +660,18 @@ std::string MemRegion::getDescriptiveName(bool UseQuotes) const { // Get variable name. if (R && R->canPrintPrettyAsExpr()) { R->printPrettyAsExpr(os); - if (UseQuotes) { + if (UseQuotes) return (llvm::Twine("'") + os.str() + ArrayIndices + "'").str(); - } else { + else return (llvm::Twine(os.str()) + ArrayIndices).str(); - } } return VariableName; } SourceRange MemRegion::sourceRange() const { - const VarRegion *const VR = dyn_cast<VarRegion>(this->getBaseRegion()); - const FieldRegion *const FR = dyn_cast<FieldRegion>(this); + const auto *const VR = dyn_cast<VarRegion>(this->getBaseRegion()); + const auto *const FR = dyn_cast<FieldRegion>(this); // Check for more specific regions first. // FieldRegion @@ -646,9 +683,8 @@ SourceRange MemRegion::sourceRange() const { return VR->getDecl()->getSourceRange(); } // Return invalid source range (can be checked by client). - else { - return SourceRange{}; - } + else + return {}; } //===----------------------------------------------------------------------===// @@ -738,13 +774,14 @@ const CodeSpaceRegion *MemRegionManager::getCodeRegion() { //===----------------------------------------------------------------------===// // Constructing regions. //===----------------------------------------------------------------------===// -const StringRegion* MemRegionManager::getStringRegion(const StringLiteral* Str){ + +const StringRegion *MemRegionManager::getStringRegion(const StringLiteral *Str){ return getSubRegion<StringRegion>( Str, cast<GlobalInternalSpaceRegion>(getGlobalsRegion())); } const ObjCStringRegion * -MemRegionManager::getObjCStringRegion(const ObjCStringLiteral* Str){ +MemRegionManager::getObjCStringRegion(const ObjCStringLiteral *Str){ return getSubRegion<ObjCStringRegion>( Str, cast<GlobalInternalSpaceRegion>(getGlobalsRegion())); } @@ -757,21 +794,20 @@ getStackOrCaptureRegionForDeclContext(const LocationContext *LC, const DeclContext *DC, const VarDecl *VD) { while (LC) { - if (const StackFrameContext *SFC = dyn_cast<StackFrameContext>(LC)) { + if (const auto *SFC = dyn_cast<StackFrameContext>(LC)) { if (cast<DeclContext>(SFC->getDecl()) == DC) return SFC; } - if (const BlockInvocationContext *BC = - dyn_cast<BlockInvocationContext>(LC)) { - const BlockDataRegion *BR = - static_cast<const BlockDataRegion*>(BC->getContextData()); + if (const auto *BC = dyn_cast<BlockInvocationContext>(LC)) { + const auto *BR = + static_cast<const BlockDataRegion *>(BC->getContextData()); // FIXME: This can be made more efficient. for (BlockDataRegion::referenced_vars_iterator I = BR->referenced_vars_begin(), E = BR->referenced_vars_end(); I != E; ++I) { - if (const VarRegion *VR = dyn_cast<VarRegion>(I.getOriginalRegion())) - if (VR->getDecl() == VD) - return cast<VarRegion>(I.getCapturedRegion()); + const VarRegion *VR = I.getOriginalRegion(); + if (VR->getDecl() == VD) + return cast<VarRegion>(I.getCapturedRegion()); } } @@ -818,7 +854,7 @@ const VarRegion* MemRegionManager::getVarRegion(const VarDecl *D, if (V.is<const VarRegion*>()) return V.get<const VarRegion*>(); - const StackFrameContext *STC = V.get<const StackFrameContext*>(); + const auto *STC = V.get<const StackFrameContext *>(); if (!STC) { // FIXME: Assign a more sensible memory space to static locals @@ -836,7 +872,7 @@ const VarRegion* MemRegionManager::getVarRegion(const VarDecl *D, if (isa<FunctionDecl>(STCD) || isa<ObjCMethodDecl>(STCD)) sReg = getGlobalsRegion(MemRegion::StaticGlobalSpaceRegionKind, getFunctionCodeRegion(cast<NamedDecl>(STCD))); - else if (const BlockDecl *BD = dyn_cast<BlockDecl>(STCD)) { + else if (const auto *BD = dyn_cast<BlockDecl>(STCD)) { // FIXME: The fallback type here is totally bogus -- though it should // never be queried, it will prevent uniquing with the real // BlockCodeRegion. Ideally we'd fix the AST so that we always had a @@ -885,7 +921,7 @@ MemRegionManager::getBlockDataRegion(const BlockCodeRegion *BC, if (LC) { // FIXME: Once we implement scope handling, we want the parent region // to be the scope. - const StackFrameContext *STC = LC->getCurrentStackFrame(); + const StackFrameContext *STC = LC->getStackFrame(); assert(STC); sReg = getStackLocalsRegion(STC); } @@ -913,7 +949,7 @@ MemRegionManager::getCompoundLiteralRegion(const CompoundLiteralExpr *CL, if (CL->isFileScope()) sReg = getGlobalsRegion(); else { - const StackFrameContext *STC = LC->getCurrentStackFrame(); + const StackFrameContext *STC = LC->getStackFrame(); assert(STC); sReg = getStackLocalsRegion(STC); } @@ -932,7 +968,7 @@ MemRegionManager::getElementRegion(QualType elementType, NonLoc Idx, void *InsertPos; MemRegion* data = Regions.FindNodeOrInsertPos(ID, InsertPos); - ElementRegion* R = cast_or_null<ElementRegion>(data); + auto *R = cast_or_null<ElementRegion>(data); if (!R) { R = A.Allocate<ElementRegion>(); @@ -954,7 +990,6 @@ MemRegionManager::getBlockCodeRegion(const BlockDecl *BD, CanQualType locTy, return getSubRegion<BlockCodeRegion>(BD, locTy, AC, getCodeRegion()); } - /// getSymbolicRegion - Retrieve or create a "symbolic" memory region. const SymbolicRegion *MemRegionManager::getSymbolicRegion(SymbolRef sym) { return getSubRegion<SymbolicRegion>(sym, getUnknownRegion()); @@ -979,7 +1014,7 @@ MemRegionManager::getObjCIvarRegion(const ObjCIvarDecl *d, const CXXTempObjectRegion* MemRegionManager::getCXXTempObjectRegion(Expr const *E, LocationContext const *LC) { - const StackFrameContext *SFC = LC->getCurrentStackFrame(); + const StackFrameContext *SFC = LC->getStackFrame(); assert(SFC); return getSubRegion<CXXTempObjectRegion>(E, getStackLocalsRegion(SFC)); } @@ -1017,10 +1052,8 @@ MemRegionManager::getCXXBaseObjectRegion(const CXXRecordDecl *RD, if (IsVirtual) { // Virtual base regions should not be layered, since the layout rules // are different. - while (const CXXBaseObjectRegion *Base = - dyn_cast<CXXBaseObjectRegion>(Super)) { + while (const auto *Base = dyn_cast<CXXBaseObjectRegion>(Super)) Super = cast<SubRegion>(Base->getSuperRegion()); - } assert(Super && !isa<MemSpaceRegion>(Super)); } } @@ -1031,7 +1064,7 @@ MemRegionManager::getCXXBaseObjectRegion(const CXXRecordDecl *RD, const CXXThisRegion* MemRegionManager::getCXXThisRegion(QualType thisPointerTy, const LocationContext *LC) { - const PointerType *PT = thisPointerTy->getAs<PointerType>(); + const auto *PT = thisPointerTy->getAs<PointerType>(); assert(PT); // Inside the body of the operator() of a lambda a this expr might refer to an // object in one of the parent location contexts. @@ -1045,7 +1078,7 @@ MemRegionManager::getCXXThisRegion(QualType thisPointerTy, LC = LC->getParent(); D = dyn_cast<CXXMethodDecl>(LC->getDecl()); } - const StackFrameContext *STC = LC->getCurrentStackFrame(); + const StackFrameContext *STC = LC->getStackFrame(); assert(STC); return getSubRegion<CXXThisRegion>(PT, getStackArgumentsRegion(STC)); } @@ -1053,14 +1086,14 @@ MemRegionManager::getCXXThisRegion(QualType thisPointerTy, const AllocaRegion* MemRegionManager::getAllocaRegion(const Expr *E, unsigned cnt, const LocationContext *LC) { - const StackFrameContext *STC = LC->getCurrentStackFrame(); + const StackFrameContext *STC = LC->getStackFrame(); assert(STC); return getSubRegion<AllocaRegion>(E, cnt, getStackLocalsRegion(STC)); } const MemSpaceRegion *MemRegion::getMemorySpace() const { const MemRegion *R = this; - const SubRegion* SR = dyn_cast<SubRegion>(this); + const auto *SR = dyn_cast<SubRegion>(this); while (SR) { R = SR->getSuperRegion(); @@ -1121,7 +1154,7 @@ const MemRegion *MemRegion::StripCasts(bool StripBaseCasts) const { while (true) { switch (R->getKind()) { case ElementRegionKind: { - const ElementRegion *ER = cast<ElementRegion>(R); + const auto *ER = cast<ElementRegion>(R); if (!ER->getIndex().isZeroConstant()) return R; R = ER->getSuperRegion(); @@ -1139,10 +1172,10 @@ const MemRegion *MemRegion::StripCasts(bool StripBaseCasts) const { } const SymbolicRegion *MemRegion::getSymbolicBase() const { - const SubRegion *SubR = dyn_cast<SubRegion>(this); + const auto *SubR = dyn_cast<SubRegion>(this); while (SubR) { - if (const SymbolicRegion *SymR = dyn_cast<SymbolicRegion>(SubR)) + if (const auto *SymR = dyn_cast<SymbolicRegion>(SubR)) return SymR; SubR = dyn_cast<SubRegion>(SubR->getSuperRegion()); } @@ -1150,7 +1183,7 @@ const SymbolicRegion *MemRegion::getSymbolicBase() const { } RegionRawOffset ElementRegion::getAsArrayOffset() const { - CharUnits offset = CharUnits::Zero(); + int64_t offset = 0; const ElementRegion *ER = this; const MemRegion *superR = nullptr; ASTContext &C = getContext(); @@ -1162,7 +1195,7 @@ RegionRawOffset ElementRegion::getAsArrayOffset() const { // FIXME: generalize to symbolic offsets. SVal index = ER->getIndex(); - if (Optional<nonloc::ConcreteInt> CI = index.getAs<nonloc::ConcreteInt>()) { + if (auto CI = index.getAs<nonloc::ConcreteInt>()) { // Update the offset. int64_t i = CI->getValue().getSExtValue(); @@ -1175,8 +1208,15 @@ RegionRawOffset ElementRegion::getAsArrayOffset() const { break; } - CharUnits size = C.getTypeSizeInChars(elemType); - offset += (i * size); + int64_t size = C.getTypeSizeInChars(elemType).getQuantity(); + if (auto NewOffset = llvm::checkedMulAdd(i, size, offset)) { + offset = *NewOffset; + } else { + LLVM_DEBUG(llvm::dbgs() << "MemRegion::getAsArrayOffset: " + << "offset overflowing, returning unknown\n"); + + return nullptr; + } } // Go to the next ElementRegion (if any). @@ -1188,10 +1228,9 @@ RegionRawOffset ElementRegion::getAsArrayOffset() const { } assert(superR && "super region cannot be NULL"); - return RegionRawOffset(superR, offset); + return RegionRawOffset(superR, CharUnits::fromQuantity(offset)); } - /// Returns true if \p Base is an immediate base class of \p Child static bool isImmediateBase(const CXXRecordDecl *Child, const CXXRecordDecl *Base) { @@ -1207,47 +1246,46 @@ static bool isImmediateBase(const CXXRecordDecl *Child, return false; } -RegionOffset MemRegion::getAsOffset() const { - const MemRegion *R = this; +static RegionOffset calculateOffset(const MemRegion *R) { const MemRegion *SymbolicOffsetBase = nullptr; int64_t Offset = 0; - while (1) { + while (true) { switch (R->getKind()) { - case CodeSpaceRegionKind: - case StackLocalsSpaceRegionKind: - case StackArgumentsSpaceRegionKind: - case HeapSpaceRegionKind: - case UnknownSpaceRegionKind: - case StaticGlobalSpaceRegionKind: - case GlobalInternalSpaceRegionKind: - case GlobalSystemSpaceRegionKind: - case GlobalImmutableSpaceRegionKind: + case MemRegion::CodeSpaceRegionKind: + case MemRegion::StackLocalsSpaceRegionKind: + case MemRegion::StackArgumentsSpaceRegionKind: + case MemRegion::HeapSpaceRegionKind: + case MemRegion::UnknownSpaceRegionKind: + case MemRegion::StaticGlobalSpaceRegionKind: + case MemRegion::GlobalInternalSpaceRegionKind: + case MemRegion::GlobalSystemSpaceRegionKind: + case MemRegion::GlobalImmutableSpaceRegionKind: // Stores can bind directly to a region space to set a default value. assert(Offset == 0 && !SymbolicOffsetBase); goto Finish; - case FunctionCodeRegionKind: - case BlockCodeRegionKind: - case BlockDataRegionKind: + case MemRegion::FunctionCodeRegionKind: + case MemRegion::BlockCodeRegionKind: + case MemRegion::BlockDataRegionKind: // These will never have bindings, but may end up having values requested // if the user does some strange casting. if (Offset != 0) SymbolicOffsetBase = R; goto Finish; - case SymbolicRegionKind: - case AllocaRegionKind: - case CompoundLiteralRegionKind: - case CXXThisRegionKind: - case StringRegionKind: - case ObjCStringRegionKind: - case VarRegionKind: - case CXXTempObjectRegionKind: + case MemRegion::SymbolicRegionKind: + case MemRegion::AllocaRegionKind: + case MemRegion::CompoundLiteralRegionKind: + case MemRegion::CXXThisRegionKind: + case MemRegion::StringRegionKind: + case MemRegion::ObjCStringRegionKind: + case MemRegion::VarRegionKind: + case MemRegion::CXXTempObjectRegionKind: // Usual base regions. goto Finish; - case ObjCIvarRegionKind: + case MemRegion::ObjCIvarRegionKind: // This is a little strange, but it's a compromise between // ObjCIvarRegions having unknown compile-time offsets (when using the // non-fragile runtime) and yet still being distinct, non-overlapping @@ -1255,15 +1293,15 @@ RegionOffset MemRegion::getAsOffset() const { // of computing offsets. goto Finish; - case CXXBaseObjectRegionKind: { - const CXXBaseObjectRegion *BOR = cast<CXXBaseObjectRegion>(R); + case MemRegion::CXXBaseObjectRegionKind: { + const auto *BOR = cast<CXXBaseObjectRegion>(R); R = BOR->getSuperRegion(); QualType Ty; bool RootIsSymbolic = false; - if (const TypedValueRegion *TVR = dyn_cast<TypedValueRegion>(R)) { - Ty = TVR->getDesugaredValueType(getContext()); - } else if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(R)) { + if (const auto *TVR = dyn_cast<TypedValueRegion>(R)) { + Ty = TVR->getDesugaredValueType(R->getContext()); + } else if (const auto *SR = dyn_cast<SymbolicRegion>(R)) { // If our base region is symbolic, we don't know what type it really is. // Pretend the type of the symbol is the true dynamic type. // (This will at least be self-consistent for the life of the symbol.) @@ -1296,18 +1334,18 @@ RegionOffset MemRegion::getAsOffset() const { continue; CharUnits BaseOffset; - const ASTRecordLayout &Layout = getContext().getASTRecordLayout(Child); + const ASTRecordLayout &Layout = R->getContext().getASTRecordLayout(Child); if (BOR->isVirtual()) BaseOffset = Layout.getVBaseClassOffset(BOR->getDecl()); else BaseOffset = Layout.getBaseClassOffset(BOR->getDecl()); // The base offset is in chars, not in bits. - Offset += BaseOffset.getQuantity() * getContext().getCharWidth(); + Offset += BaseOffset.getQuantity() * R->getContext().getCharWidth(); break; } - case ElementRegionKind: { - const ElementRegion *ER = cast<ElementRegion>(R); + case MemRegion::ElementRegionKind: { + const auto *ER = cast<ElementRegion>(R); R = ER->getSuperRegion(); QualType EleTy = ER->getValueType(); @@ -1327,15 +1365,15 @@ RegionOffset MemRegion::getAsOffset() const { int64_t i = CI->getValue().getSExtValue(); // This type size is in bits. - Offset += i * getContext().getTypeSize(EleTy); + Offset += i * R->getContext().getTypeSize(EleTy); } else { // We cannot compute offset for non-concrete index. SymbolicOffsetBase = R; } break; } - case FieldRegionKind: { - const FieldRegion *FR = cast<FieldRegion>(R); + case MemRegion::FieldRegionKind: { + const auto *FR = cast<FieldRegion>(R); R = FR->getSuperRegion(); const RecordDecl *RD = FR->getDecl()->getParent(); @@ -1360,7 +1398,7 @@ RegionOffset MemRegion::getAsOffset() const { if (FR->getDecl() == *FI) break; } - const ASTRecordLayout &Layout = getContext().getASTRecordLayout(RD); + const ASTRecordLayout &Layout = R->getContext().getASTRecordLayout(RD); // This is offset in bits. Offset += Layout.getFieldOffset(idx); break; @@ -1374,6 +1412,12 @@ RegionOffset MemRegion::getAsOffset() const { return RegionOffset(R, Offset); } +RegionOffset MemRegion::getAsOffset() const { + if (!cachedOffset) + cachedOffset = calculateOffset(this); + return *cachedOffset; +} + //===----------------------------------------------------------------------===// // BlockDataRegion //===----------------------------------------------------------------------===// @@ -1419,13 +1463,14 @@ void BlockDataRegion::LazyInitializeReferencedVars() { llvm::BumpPtrAllocator &A = MemMgr.getAllocator(); BumpVectorContext BC(A); - typedef BumpVector<const MemRegion*> VarVec; - VarVec *BV = A.Allocate<VarVec>(); + using VarVec = BumpVector<const MemRegion *>; + + auto *BV = A.Allocate<VarVec>(); new (BV) VarVec(BC, NumBlockVars); - VarVec *BVOriginal = A.Allocate<VarVec>(); + auto *BVOriginal = A.Allocate<VarVec>(); new (BVOriginal) VarVec(BC, NumBlockVars); - for (const VarDecl *VD : ReferencedBlockVars) { + for (const auto *VD : ReferencedBlockVars) { const VarRegion *VR = nullptr; const VarRegion *OriginalVR = nullptr; std::tie(VR, OriginalVR) = getCaptureRegions(VD); @@ -1443,14 +1488,13 @@ BlockDataRegion::referenced_vars_iterator BlockDataRegion::referenced_vars_begin() const { const_cast<BlockDataRegion*>(this)->LazyInitializeReferencedVars(); - BumpVector<const MemRegion*> *Vec = - static_cast<BumpVector<const MemRegion*>*>(ReferencedVars); + auto *Vec = static_cast<BumpVector<const MemRegion *> *>(ReferencedVars); if (Vec == (void*) 0x1) return BlockDataRegion::referenced_vars_iterator(nullptr, nullptr); - BumpVector<const MemRegion*> *VecOriginal = - static_cast<BumpVector<const MemRegion*>*>(OriginalVars); + auto *VecOriginal = + static_cast<BumpVector<const MemRegion *> *>(OriginalVars); return BlockDataRegion::referenced_vars_iterator(Vec->begin(), VecOriginal->begin()); @@ -1460,14 +1504,13 @@ BlockDataRegion::referenced_vars_iterator BlockDataRegion::referenced_vars_end() const { const_cast<BlockDataRegion*>(this)->LazyInitializeReferencedVars(); - BumpVector<const MemRegion*> *Vec = - static_cast<BumpVector<const MemRegion*>*>(ReferencedVars); + auto *Vec = static_cast<BumpVector<const MemRegion *> *>(ReferencedVars); if (Vec == (void*) 0x1) return BlockDataRegion::referenced_vars_iterator(nullptr, nullptr); - BumpVector<const MemRegion*> *VecOriginal = - static_cast<BumpVector<const MemRegion*>*>(OriginalVars); + auto *VecOriginal = + static_cast<BumpVector<const MemRegion *> *>(OriginalVars); return BlockDataRegion::referenced_vars_iterator(Vec->end(), VecOriginal->end()); @@ -1495,7 +1538,7 @@ void RegionAndSymbolInvalidationTraits::setTrait(SymbolRef Sym, void RegionAndSymbolInvalidationTraits::setTrait(const MemRegion *MR, InvalidationKinds IK) { assert(MR); - if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(MR)) + if (const auto *SR = dyn_cast<SymbolicRegion>(MR)) setTrait(SR->getSymbol(), IK); else MRTraitsMap[MR] |= IK; @@ -1515,7 +1558,7 @@ bool RegionAndSymbolInvalidationTraits::hasTrait(const MemRegion *MR, if (!MR) return false; - if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(MR)) + if (const auto *SR = dyn_cast<SymbolicRegion>(MR)) return hasTrait(SR->getSymbol(), IK); const_region_iterator I = MRTraitsMap.find(MR); diff --git a/lib/StaticAnalyzer/Core/PathDiagnostic.cpp b/lib/StaticAnalyzer/Core/PathDiagnostic.cpp index 669748c0127a..1b698ec5c086 100644 --- a/lib/StaticAnalyzer/Core/PathDiagnostic.cpp +++ b/lib/StaticAnalyzer/Core/PathDiagnostic.cpp @@ -1,4 +1,4 @@ -//===--- PathDiagnostic.cpp - Path-Specific Diagnostic Handling -*- C++ -*-===// +//===- PathDiagnostic.cpp - Path-Specific Diagnostic Handling -------------===// // // The LLVM Compiler Infrastructure // @@ -13,26 +13,52 @@ #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" +#include "clang/AST/DeclTemplate.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" +#include "clang/AST/OperationKinds.h" #include "clang/AST/ParentMap.h" -#include "clang/AST/StmtCXX.h" +#include "clang/AST/Stmt.h" +#include "clang/AST/Type.h" +#include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/Analysis/CFG.h" +#include "clang/Analysis/ProgramPoint.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" +#include <cassert> +#include <cstring> +#include <memory> +#include <utility> +#include <vector> using namespace clang; using namespace ento; bool PathDiagnosticMacroPiece::containsEvent() const { - for (auto &P : subPieces) { + for (const auto &P : subPieces) { if (isa<PathDiagnosticEventPiece>(*P)) return true; - if (auto *MP = dyn_cast<PathDiagnosticMacroPiece>(P.get())) + if (const auto *MP = dyn_cast<PathDiagnosticMacroPiece>(P.get())) if (MP->containsEvent()) return true; } @@ -43,23 +69,27 @@ static StringRef StripTrailingDots(StringRef s) { for (StringRef::size_type i = s.size(); i != 0; --i) if (s[i - 1] != '.') return s.substr(0, i); - return ""; + return {}; } PathDiagnosticPiece::PathDiagnosticPiece(StringRef s, Kind k, DisplayHint hint) - : str(StripTrailingDots(s)), kind(k), Hint(hint), - LastInMainSourceFile(false) {} + : str(StripTrailingDots(s)), kind(k), Hint(hint) {} PathDiagnosticPiece::PathDiagnosticPiece(Kind k, DisplayHint hint) - : kind(k), Hint(hint), LastInMainSourceFile(false) {} + : kind(k), Hint(hint) {} -PathDiagnosticPiece::~PathDiagnosticPiece() {} -PathDiagnosticEventPiece::~PathDiagnosticEventPiece() {} -PathDiagnosticCallPiece::~PathDiagnosticCallPiece() {} -PathDiagnosticControlFlowPiece::~PathDiagnosticControlFlowPiece() {} -PathDiagnosticMacroPiece::~PathDiagnosticMacroPiece() {} -PathDiagnosticNotePiece::~PathDiagnosticNotePiece() {} +PathDiagnosticPiece::~PathDiagnosticPiece() = default; + +PathDiagnosticEventPiece::~PathDiagnosticEventPiece() = default; + +PathDiagnosticCallPiece::~PathDiagnosticCallPiece() = default; + +PathDiagnosticControlFlowPiece::~PathDiagnosticControlFlowPiece() = default; + +PathDiagnosticMacroPiece::~PathDiagnosticMacroPiece() = default; + +PathDiagnosticNotePiece::~PathDiagnosticNotePiece() = default; void PathPieces::flattenTo(PathPieces &Primary, PathPieces &Current, bool ShouldFlattenMacros) const { @@ -96,22 +126,20 @@ void PathPieces::flattenTo(PathPieces &Primary, PathPieces &Current, } } -PathDiagnostic::~PathDiagnostic() {} - -PathDiagnostic::PathDiagnostic(StringRef CheckName, const Decl *declWithIssue, - StringRef bugtype, StringRef verboseDesc, - StringRef shortDesc, StringRef category, - PathDiagnosticLocation LocationToUnique, - const Decl *DeclToUnique) - : CheckName(CheckName), - DeclWithIssue(declWithIssue), - BugType(StripTrailingDots(bugtype)), - VerboseDesc(StripTrailingDots(verboseDesc)), - ShortDesc(StripTrailingDots(shortDesc)), - Category(StripTrailingDots(category)), - UniqueingLoc(LocationToUnique), - UniqueingDecl(DeclToUnique), - path(pathImpl) {} +PathDiagnostic::~PathDiagnostic() = default; + +PathDiagnostic::PathDiagnostic( + StringRef CheckName, const Decl *declWithIssue, StringRef bugtype, + StringRef verboseDesc, StringRef shortDesc, StringRef category, + PathDiagnosticLocation LocationToUnique, const Decl *DeclToUnique, + std::unique_ptr<FilesToLineNumsMap> ExecutedLines) + : CheckName(CheckName), DeclWithIssue(declWithIssue), + BugType(StripTrailingDots(bugtype)), + VerboseDesc(StripTrailingDots(verboseDesc)), + ShortDesc(StripTrailingDots(shortDesc)), + Category(StripTrailingDots(category)), UniqueingLoc(LocationToUnique), + UniqueingDecl(DeclToUnique), ExecutedLines(std::move(ExecutedLines)), + path(pathImpl) {} static PathDiagnosticCallPiece * getFirstStackedCallToHeaderFile(PathDiagnosticCallPiece *CP, @@ -122,11 +150,11 @@ getFirstStackedCallToHeaderFile(PathDiagnosticCallPiece *CP, if (CallLoc.isMacroID()) return nullptr; - assert(SMgr.isInMainFile(CallLoc) && - "The call piece should be in the main file."); + assert(AnalysisManager::isInCodeFile(CallLoc, SMgr) && + "The call piece should not be in a header file."); // Check if CP represents a path through a function outside of the main file. - if (!SMgr.isInMainFile(CP->callEnterWithin.asLocation())) + if (!AnalysisManager::isInCodeFile(CP->callEnterWithin.asLocation(), SMgr)) return CP; const PathPieces &Path = CP->path; @@ -135,10 +163,8 @@ getFirstStackedCallToHeaderFile(PathDiagnosticCallPiece *CP, // Check if the last piece in the callee path is a call to a function outside // of the main file. - if (PathDiagnosticCallPiece *CPInner = - dyn_cast<PathDiagnosticCallPiece>(Path.back().get())) { + if (auto *CPInner = dyn_cast<PathDiagnosticCallPiece>(Path.back().get())) return getFirstStackedCallToHeaderFile(CPInner, SMgr); - } // Otherwise, the last piece is in the main file. return nullptr; @@ -154,14 +180,14 @@ void PathDiagnostic::resetDiagnosticLocationToMainFile() { // We only need to check if the report ends inside headers, if the last piece // is a call piece. - if (PathDiagnosticCallPiece *CP = dyn_cast<PathDiagnosticCallPiece>(LastP)) { + if (auto *CP = dyn_cast<PathDiagnosticCallPiece>(LastP)) { CP = getFirstStackedCallToHeaderFile(CP, SMgr); if (CP) { // Mark the piece. CP->setAsLastInMainSourceFile(); // Update the path diagnostic message. - const NamedDecl *ND = dyn_cast<NamedDecl>(CP->getCallee()); + const auto *ND = dyn_cast<NamedDecl>(CP->getCallee()); if (ND) { SmallString<200> buf; llvm::raw_svector_ostream os(buf); @@ -178,14 +204,12 @@ void PathDiagnostic::resetDiagnosticLocationToMainFile() { } } -void PathDiagnosticConsumer::anchor() { } +void PathDiagnosticConsumer::anchor() {} PathDiagnosticConsumer::~PathDiagnosticConsumer() { // Delete the contents of the FoldingSet if it isn't empty already. - for (llvm::FoldingSet<PathDiagnostic>::iterator it = - Diags.begin(), et = Diags.end() ; it != et ; ++it) { - delete &*it; - } + for (auto &Diag : Diags) + delete &Diag; } void PathDiagnosticConsumer::HandlePathDiagnostic( @@ -216,9 +240,8 @@ void PathDiagnosticConsumer::HandlePathDiagnostic( while (!WorkList.empty()) { const PathPieces &path = *WorkList.pop_back_val(); - for (PathPieces::const_iterator I = path.begin(), E = path.end(); I != E; - ++I) { - const PathDiagnosticPiece *piece = I->get(); + for (const auto &I : path) { + const PathDiagnosticPiece *piece = I.get(); FullSourceLoc L = piece->getLocation().asLocation().getExpansionLoc(); if (FID.isInvalid()) { @@ -230,28 +253,23 @@ void PathDiagnosticConsumer::HandlePathDiagnostic( // Check the source ranges. ArrayRef<SourceRange> Ranges = piece->getRanges(); - for (ArrayRef<SourceRange>::iterator I = Ranges.begin(), - E = Ranges.end(); I != E; ++I) { - SourceLocation L = SMgr.getExpansionLoc(I->getBegin()); + for (const auto &I : Ranges) { + SourceLocation L = SMgr.getExpansionLoc(I.getBegin()); if (!L.isFileID() || SMgr.getFileID(L) != FID) { llvm::errs() << warning.str(); return; } - L = SMgr.getExpansionLoc(I->getEnd()); + L = SMgr.getExpansionLoc(I.getEnd()); if (!L.isFileID() || SMgr.getFileID(L) != FID) { llvm::errs() << warning.str(); return; } } - if (const PathDiagnosticCallPiece *call = - dyn_cast<PathDiagnosticCallPiece>(piece)) { + if (const auto *call = dyn_cast<PathDiagnosticCallPiece>(piece)) WorkList.push_back(&call->path); - } - else if (const PathDiagnosticMacroPiece *macro = - dyn_cast<PathDiagnosticMacroPiece>(piece)) { + else if (const auto *macro = dyn_cast<PathDiagnosticMacroPiece>(piece)) WorkList.push_back(¯o->subPieces); - } } } @@ -381,11 +399,29 @@ static Optional<bool> comparePath(const PathPieces &X, const PathPieces &Y) { return None; } +static bool compareCrossTUSourceLocs(FullSourceLoc XL, FullSourceLoc YL) { + std::pair<FileID, unsigned> XOffs = XL.getDecomposedLoc(); + std::pair<FileID, unsigned> YOffs = YL.getDecomposedLoc(); + const SourceManager &SM = XL.getManager(); + std::pair<bool, bool> InSameTU = SM.isInTheSameTranslationUnit(XOffs, YOffs); + if (InSameTU.first) + return XL.isBeforeInTranslationUnitThan(YL); + const FileEntry *XFE = SM.getFileEntryForID(XL.getSpellingLoc().getFileID()); + const FileEntry *YFE = SM.getFileEntryForID(YL.getSpellingLoc().getFileID()); + if (!XFE || !YFE) + return XFE && !YFE; + int NameCmp = XFE->getName().compare(YFE->getName()); + if (NameCmp != 0) + return NameCmp == -1; + // Last resort: Compare raw file IDs that are possibly expansions. + return XL.getFileID() < YL.getFileID(); +} + static bool compare(const PathDiagnostic &X, const PathDiagnostic &Y) { FullSourceLoc XL = X.getLocation().asLocation(); FullSourceLoc YL = Y.getLocation().asLocation(); if (XL != YL) - return XL.isBeforeInTranslationUnitThan(YL); + return compareCrossTUSourceLocs(XL, YL); if (X.getBugType() != Y.getBugType()) return X.getBugType() < Y.getBugType(); if (X.getCategory() != Y.getCategory()) @@ -405,7 +441,8 @@ static bool compare(const PathDiagnostic &X, const PathDiagnostic &Y) { SourceLocation YDL = YD->getLocation(); if (XDL != YDL) { const SourceManager &SM = XL.getManager(); - return SM.isBeforeInTranslationUnit(XDL, YDL); + return compareCrossTUSourceLocs(FullSourceLoc(XDL, SM), + FullSourceLoc(YDL, SM)); } } PathDiagnostic::meta_iterator XI = X.meta_begin(), XE = X.meta_end(); @@ -429,11 +466,8 @@ void PathDiagnosticConsumer::FlushDiagnostics( flushed = true; std::vector<const PathDiagnostic *> BatchDiags; - for (llvm::FoldingSet<PathDiagnostic>::iterator it = Diags.begin(), - et = Diags.end(); it != et; ++it) { - const PathDiagnostic *D = &*it; - BatchDiags.push_back(D); - } + for (const auto &D : Diags) + BatchDiags.push_back(&D); // Sort the diagnostics so that they are always emitted in a deterministic // order. @@ -450,11 +484,8 @@ void PathDiagnosticConsumer::FlushDiagnostics( FlushDiagnosticsImpl(BatchDiags, Files); // Delete the flushed diagnostics. - for (std::vector<const PathDiagnostic *>::iterator it = BatchDiags.begin(), - et = BatchDiags.end(); it != et; ++it) { - const PathDiagnostic *D = *it; + for (const auto D : BatchDiags) delete D; - } // Clear out the FoldingSet. Diags.clear(); @@ -553,6 +584,8 @@ getLocationForCaller(const StackFrameContext *SFC, switch (Source.getKind()) { case CFGElement::Statement: + case CFGElement::Constructor: + case CFGElement::CXXRecordTypedCall: return PathDiagnosticLocation(Source.castAs<CFGStmt>().getStmt(), SM, CallerCtx); case CFGElement::Initializer: { @@ -576,8 +609,20 @@ getLocationForCaller(const StackFrameContext *SFC, return PathDiagnosticLocation::createEnd(CallerBody, SM, CallerCtx); return PathDiagnosticLocation::create(CallerInfo->getDecl(), SM); } - case CFGElement::TemporaryDtor: - case CFGElement::NewAllocator: + case CFGElement::NewAllocator: { + const CFGNewAllocator &Alloc = Source.castAs<CFGNewAllocator>(); + return PathDiagnosticLocation(Alloc.getAllocatorExpr(), SM, CallerCtx); + } + case CFGElement::TemporaryDtor: { + // Temporary destructors are for temporaries. They die immediately at around + // the location of CXXBindTemporaryExpr. If they are lifetime-extended, + // they'd be dealt with via an AutomaticObjectDtor instead. + const auto &Dtor = Source.castAs<CFGTemporaryDtor>(); + return PathDiagnosticLocation::createEnd(Dtor.getBindTemporaryExpr(), SM, + CallerCtx); + } + case CFGElement::ScopeBegin: + case CFGElement::ScopeEnd: llvm_unreachable("not yet implemented!"); case CFGElement::LifetimeEnds: case CFGElement::LoopExit: @@ -605,7 +650,7 @@ PathDiagnosticLocation PathDiagnosticLocation::createEnd(const Stmt *S, const SourceManager &SM, LocationOrAnalysisDeclContext LAC) { - if (const CompoundStmt *CS = dyn_cast<CompoundStmt>(S)) + if (const auto *CS = dyn_cast<CompoundStmt>(S)) return createEndBrace(CS, SM); return PathDiagnosticLocation(getValidSourceLocation(S, LAC, /*End=*/true), SM, SingleLocK); @@ -624,7 +669,6 @@ PathDiagnosticLocation::createConditionalColonLoc( return PathDiagnosticLocation(CO->getColonLoc(), SM, SingleLocK); } - PathDiagnosticLocation PathDiagnosticLocation::createMemberLoc(const MemberExpr *ME, const SourceManager &SM) { @@ -649,8 +693,7 @@ PathDiagnosticLocation PathDiagnosticLocation::createDeclBegin(const LocationContext *LC, const SourceManager &SM) { // FIXME: Should handle CXXTryStmt if analyser starts supporting C++. - if (const CompoundStmt *CS = - dyn_cast_or_null<CompoundStmt>(LC->getDecl()->getBody())) + if (const auto *CS = dyn_cast_or_null<CompoundStmt>(LC->getDecl()->getBody())) if (!CS->body_empty()) { SourceLocation Loc = (*CS->body_begin())->getLocStart(); return PathDiagnosticLocation(Loc, SM, SingleLocK); @@ -741,6 +784,8 @@ const Stmt *PathDiagnosticLocation::getStmt(const ExplodedNode *N) { return CEE->getCalleeContext()->getCallSite(); if (Optional<PostInitializer> PIPP = P.getAs<PostInitializer>()) return PIPP->getInitializer()->getInit(); + if (Optional<CallExitBegin> CEB = P.getAs<CallExitBegin>()) + return CEB->getReturnStmt(); return nullptr; } @@ -790,11 +835,11 @@ PathDiagnosticLocation const LocationContext *LC = N->getLocationContext(); // For member expressions, return the location of the '.' or '->'. - if (const MemberExpr *ME = dyn_cast<MemberExpr>(S)) + if (const auto *ME = dyn_cast<MemberExpr>(S)) return PathDiagnosticLocation::createMemberLoc(ME, SM); // For binary operators, return the location of the operator. - if (const BinaryOperator *B = dyn_cast<BinaryOperator>(S)) + if (const auto *B = dyn_cast<BinaryOperator>(S)) return PathDiagnosticLocation::createOperatorLoc(B, SM); if (P.getAs<PostStmtPurgeDeadSymbols>()) @@ -856,7 +901,7 @@ PathDiagnosticRange default: break; case Stmt::DeclStmtClass: { - const DeclStmt *DS = cast<DeclStmt>(S); + const auto *DS = cast<DeclStmt>(S); if (DS->isSingleDecl()) { // Should always be the case, but we'll be defensive. return SourceRange(DS->getLocStart(), @@ -886,9 +931,9 @@ PathDiagnosticRange break; } case DeclK: - if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) + if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) return MD->getSourceRange(); - if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) { + if (const auto *FD = dyn_cast<FunctionDecl>(D)) { if (Stmt *Body = FD->getBody()) return Body->getSourceRange(); } @@ -898,7 +943,7 @@ PathDiagnosticRange } } - return SourceRange(Loc,Loc); + return SourceRange(Loc, Loc); } void PathDiagnosticLocation::flatten() { @@ -954,17 +999,55 @@ void PathDiagnosticCallPiece::setCallee(const CallEnter &CE, // non-autosynthesized callbacks. // Unless set here, the IsCalleeAnAutosynthesizedPropertyAccessor flag // defaults to false. - if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(Callee)) + if (const auto *MD = dyn_cast<ObjCMethodDecl>(Callee)) IsCalleeAnAutosynthesizedPropertyAccessor = ( MD->isPropertyAccessor() && CalleeCtx->getAnalysisDeclContext()->isBodyAutosynthesized()); } -static inline void describeClass(raw_ostream &Out, const CXXRecordDecl *D, - StringRef Prefix = StringRef()) { +static void describeTemplateParameters(raw_ostream &Out, + const ArrayRef<TemplateArgument> TAList, + const LangOptions &LO, + StringRef Prefix = StringRef(), + StringRef Postfix = StringRef()); + +static void describeTemplateParameter(raw_ostream &Out, + const TemplateArgument &TArg, + const LangOptions &LO) { + + if (TArg.getKind() == TemplateArgument::ArgKind::Pack) { + describeTemplateParameters(Out, TArg.getPackAsArray(), LO); + } else { + TArg.print(PrintingPolicy(LO), Out); + } +} + +static void describeTemplateParameters(raw_ostream &Out, + const ArrayRef<TemplateArgument> TAList, + const LangOptions &LO, + StringRef Prefix, StringRef Postfix) { + if (TAList.empty()) + return; + + Out << Prefix; + for (int I = 0, Last = TAList.size() - 1; I != Last; ++I) { + describeTemplateParameter(Out, TAList[I], LO); + Out << ", "; + } + describeTemplateParameter(Out, TAList[TAList.size() - 1], LO); + Out << Postfix; +} + +static void describeClass(raw_ostream &Out, const CXXRecordDecl *D, + StringRef Prefix = StringRef()) { if (!D->getIdentifier()) return; - Out << Prefix << '\'' << *D << '\''; + Out << Prefix << '\'' << *D; + if (const auto T = dyn_cast<ClassTemplateSpecializationDecl>(D)) + describeTemplateParameters(Out, T->getTemplateArgs().asArray(), + D->getASTContext().getLangOpts(), "<", ">"); + + Out << '\''; } static bool describeCodeDecl(raw_ostream &Out, const Decl *D, @@ -979,7 +1062,7 @@ static bool describeCodeDecl(raw_ostream &Out, const Decl *D, return ExtendedDescription; } - if (const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(D)) { + if (const auto *MD = dyn_cast<CXXMethodDecl>(D)) { Out << Prefix; if (ExtendedDescription && !MD->isUserProvided()) { if (MD->isExplicitlyDefaulted()) @@ -988,7 +1071,7 @@ static bool describeCodeDecl(raw_ostream &Out, const Decl *D, Out << "implicit "; } - if (const CXXConstructorDecl *CD = dyn_cast<CXXConstructorDecl>(MD)) { + if (const auto *CD = dyn_cast<CXXConstructorDecl>(MD)) { if (CD->isDefaultConstructor()) Out << "default "; else if (CD->isCopyConstructor()) @@ -998,7 +1081,6 @@ static bool describeCodeDecl(raw_ostream &Out, const Decl *D, Out << "constructor"; describeClass(Out, MD->getParent(), " for "); - } else if (isa<CXXDestructorDecl>(MD)) { if (!MD->isUserProvided()) { Out << "destructor"; @@ -1007,15 +1089,12 @@ static bool describeCodeDecl(raw_ostream &Out, const Decl *D, // Use ~Foo for explicitly-written destructors. Out << "'" << *MD << "'"; } - } else if (MD->isCopyAssignmentOperator()) { Out << "copy assignment operator"; describeClass(Out, MD->getParent(), " for "); - } else if (MD->isMoveAssignmentOperator()) { Out << "move assignment operator"; describeClass(Out, MD->getParent(), " for "); - } else { if (MD->getParent()->getIdentifier()) Out << "'" << *MD->getParent() << "::" << *MD << "'"; @@ -1026,7 +1105,16 @@ static bool describeCodeDecl(raw_ostream &Out, const Decl *D, return true; } - Out << Prefix << '\'' << cast<NamedDecl>(*D) << '\''; + Out << Prefix << '\'' << cast<NamedDecl>(*D); + + // Adding template parameters. + if (const auto FD = dyn_cast<FunctionDecl>(D)) + if (const TemplateArgumentList *TAList = + FD->getTemplateSpecializationArgs()) + describeTemplateParameters(Out, TAList->asArray(), + FD->getASTContext().getLangOpts(), "<", ">"); + + Out << '\''; return true; } @@ -1055,7 +1143,7 @@ PathDiagnosticCallPiece::getCallEnterWithinCallerEvent() const { return nullptr; if (Callee->isImplicit() || !Callee->hasBody()) return nullptr; - if (const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(Callee)) + if (const auto *MD = dyn_cast<CXXMethodDecl>(Callee)) if (MD->isDefaulted()) return nullptr; @@ -1095,13 +1183,10 @@ PathDiagnosticCallPiece::getCallExitEvent() const { } static void compute_path_size(const PathPieces &pieces, unsigned &size) { - for (PathPieces::const_iterator it = pieces.begin(), - et = pieces.end(); it != et; ++it) { - const PathDiagnosticPiece *piece = it->get(); - if (const PathDiagnosticCallPiece *cp = - dyn_cast<PathDiagnosticCallPiece>(piece)) { + for (const auto &I : pieces) { + const PathDiagnosticPiece *piece = I.get(); + if (const auto *cp = dyn_cast<PathDiagnosticCallPiece>(piece)) compute_path_size(cp->path, size); - } else ++size; } @@ -1129,19 +1214,16 @@ void PathDiagnosticPiece::Profile(llvm::FoldingSetNodeID &ID) const { // FIXME: Add profiling support for code hints. ID.AddInteger((unsigned) getDisplayHint()); ArrayRef<SourceRange> Ranges = getRanges(); - for (ArrayRef<SourceRange>::iterator I = Ranges.begin(), E = Ranges.end(); - I != E; ++I) { - ID.AddInteger(I->getBegin().getRawEncoding()); - ID.AddInteger(I->getEnd().getRawEncoding()); + for (const auto &I : Ranges) { + ID.AddInteger(I.getBegin().getRawEncoding()); + ID.AddInteger(I.getEnd().getRawEncoding()); } } void PathDiagnosticCallPiece::Profile(llvm::FoldingSetNodeID &ID) const { PathDiagnosticPiece::Profile(ID); - for (PathPieces::const_iterator it = path.begin(), - et = path.end(); it != et; ++it) { - ID.Add(**it); - } + for (const auto &I : path) + ID.Add(*I); } void PathDiagnosticSpotPiece::Profile(llvm::FoldingSetNodeID &ID) const { @@ -1151,15 +1233,14 @@ void PathDiagnosticSpotPiece::Profile(llvm::FoldingSetNodeID &ID) const { void PathDiagnosticControlFlowPiece::Profile(llvm::FoldingSetNodeID &ID) const { PathDiagnosticPiece::Profile(ID); - for (const_iterator I = begin(), E = end(); I != E; ++I) - ID.Add(*I); + for (const auto &I : *this) + ID.Add(I); } void PathDiagnosticMacroPiece::Profile(llvm::FoldingSetNodeID &ID) const { PathDiagnosticSpotPiece::Profile(ID); - for (PathPieces::const_iterator I = subPieces.begin(), E = subPieces.end(); - I != E; ++I) - ID.Add(**I); + for (const auto &I : subPieces) + ID.Add(*I); } void PathDiagnosticNotePiece::Profile(llvm::FoldingSetNodeID &ID) const { @@ -1175,34 +1256,32 @@ void PathDiagnostic::Profile(llvm::FoldingSetNodeID &ID) const { void PathDiagnostic::FullProfile(llvm::FoldingSetNodeID &ID) const { Profile(ID); - for (PathPieces::const_iterator I = path.begin(), E = path.end(); I != E; ++I) - ID.Add(**I); + for (const auto &I : path) + ID.Add(*I); for (meta_iterator I = meta_begin(), E = meta_end(); I != E; ++I) ID.AddString(*I); } -StackHintGenerator::~StackHintGenerator() {} +StackHintGenerator::~StackHintGenerator() = default; std::string StackHintGeneratorForSymbol::getMessage(const ExplodedNode *N){ + if (!N) + return getMessageForSymbolNotFound(); + ProgramPoint P = N->getLocation(); CallExitEnd CExit = P.castAs<CallExitEnd>(); // FIXME: Use CallEvent to abstract this over all calls. const Stmt *CallSite = CExit.getCalleeContext()->getCallSite(); - const CallExpr *CE = dyn_cast_or_null<CallExpr>(CallSite); + const auto *CE = dyn_cast_or_null<CallExpr>(CallSite); if (!CE) - return ""; - - if (!N) - return getMessageForSymbolNotFound(); + return {}; // Check if one of the parameters are set to the interesting symbol. - ProgramStateRef State = N->getState(); - const LocationContext *LCtx = N->getLocationContext(); unsigned ArgIndex = 0; for (CallExpr::const_arg_iterator I = CE->arg_begin(), E = CE->arg_end(); I != E; ++I, ++ArgIndex){ - SVal SV = State->getSVal(*I, LCtx); + SVal SV = N->getSVal(*I); // Check if the variable corresponding to the symbol is passed by value. SymbolRef AS = SV.getAsLocSymbol(); @@ -1212,7 +1291,10 @@ std::string StackHintGeneratorForSymbol::getMessage(const ExplodedNode *N){ // Check if the parameter is a pointer to the symbol. if (Optional<loc::MemRegionVal> Reg = SV.getAs<loc::MemRegionVal>()) { - SVal PSV = State->getSVal(Reg->getRegion()); + // Do not attempt to dereference void*. + if ((*I)->getType()->isVoidPointerType()) + continue; + SVal PSV = N->getState()->getSVal(Reg->getRegion()); SymbolRef AS = PSV.getAsLocSymbol(); if (AS == Sym) { return getMessageForArg(*I, ArgIndex); @@ -1221,7 +1303,7 @@ std::string StackHintGeneratorForSymbol::getMessage(const ExplodedNode *N){ } // Check if we are returning the interesting symbol. - SVal SV = State->getSVal(CE, LCtx); + SVal SV = N->getSVal(CE); SymbolRef RetSym = SV.getAsLocSymbol(); if (RetSym == Sym) { return getMessageForReturn(CE); @@ -1243,3 +1325,84 @@ std::string StackHintGeneratorForSymbol::getMessageForArg(const Expr *ArgE, return os.str(); } + +LLVM_DUMP_METHOD void PathPieces::dump() const { + unsigned index = 0; + for (PathPieces::const_iterator I = begin(), E = end(); I != E; ++I) { + llvm::errs() << "[" << index++ << "] "; + (*I)->dump(); + llvm::errs() << "\n"; + } +} + +LLVM_DUMP_METHOD void PathDiagnosticCallPiece::dump() const { + llvm::errs() << "CALL\n--------------\n"; + + if (const Stmt *SLoc = getLocation().getStmtOrNull()) + SLoc->dump(); + else if (const auto *ND = dyn_cast_or_null<NamedDecl>(getCallee())) + llvm::errs() << *ND << "\n"; + else + getLocation().dump(); +} + +LLVM_DUMP_METHOD void PathDiagnosticEventPiece::dump() const { + llvm::errs() << "EVENT\n--------------\n"; + llvm::errs() << getString() << "\n"; + llvm::errs() << " ---- at ----\n"; + getLocation().dump(); +} + +LLVM_DUMP_METHOD void PathDiagnosticControlFlowPiece::dump() const { + llvm::errs() << "CONTROL\n--------------\n"; + getStartLocation().dump(); + llvm::errs() << " ---- to ----\n"; + getEndLocation().dump(); +} + +LLVM_DUMP_METHOD void PathDiagnosticMacroPiece::dump() const { + llvm::errs() << "MACRO\n--------------\n"; + // FIXME: Print which macro is being invoked. +} + +LLVM_DUMP_METHOD void PathDiagnosticNotePiece::dump() const { + llvm::errs() << "NOTE\n--------------\n"; + llvm::errs() << getString() << "\n"; + llvm::errs() << " ---- at ----\n"; + getLocation().dump(); +} + +LLVM_DUMP_METHOD void PathDiagnosticLocation::dump() const { + if (!isValid()) { + llvm::errs() << "<INVALID>\n"; + return; + } + + switch (K) { + case RangeK: + // FIXME: actually print the range. + llvm::errs() << "<range>\n"; + break; + case SingleLocK: + asLocation().dump(); + llvm::errs() << "\n"; + break; + case StmtK: + if (S) + S->dump(); + else + llvm::errs() << "<NULL STMT>\n"; + break; + case DeclK: + if (const auto *ND = dyn_cast_or_null<NamedDecl>(D)) + llvm::errs() << *ND << "\n"; + else if (isa<BlockDecl>(D)) + // FIXME: Make this nicer. + llvm::errs() << "<block>\n"; + else if (D) + llvm::errs() << "<unknown decl>\n"; + else + llvm::errs() << "<NULL DECL>\n"; + break; + } +} diff --git a/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp index 66812ed8ff5b..cfe780db9ec9 100644 --- a/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ b/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -16,9 +16,12 @@ #include "clang/Basic/SourceManager.h" #include "clang/Basic/Version.h" #include "clang/Lex/Preprocessor.h" +#include "clang/Rewrite/Core/HTMLRewrite.h" +#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/IssueHash.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" +#include "llvm/ADT/Statistic.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Casting.h" using namespace clang; @@ -30,6 +33,7 @@ namespace { const std::string OutputFile; const LangOptions &LangOpts; const bool SupportsCrossFileDiagnostics; + const bool SerializeStatistics; public: PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, const std::string& prefix, @@ -61,7 +65,8 @@ PlistDiagnostics::PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, bool supportsMultipleFiles) : OutputFile(output), LangOpts(LO), - SupportsCrossFileDiagnostics(supportsMultipleFiles) {} + SupportsCrossFileDiagnostics(supportsMultipleFiles), + SerializeStatistics(AnalyzerOpts.shouldSerializeStats()) {} void ento::createPlistDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, @@ -79,6 +84,41 @@ void ento::createPlistMultiFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, PP.getLangOpts(), true)); } +static void EmitRanges(raw_ostream &o, + const ArrayRef<SourceRange> Ranges, + const FIDMap& FM, + const SourceManager &SM, + const LangOptions &LangOpts, + unsigned indent) { + + if (Ranges.empty()) + return; + + Indent(o, indent) << "<key>ranges</key>\n"; + Indent(o, indent) << "<array>\n"; + ++indent; + for (auto &R : Ranges) + EmitRange(o, SM, + Lexer::getAsCharRange(SM.getExpansionRange(R), SM, LangOpts), + FM, indent + 1); + --indent; + Indent(o, indent) << "</array>\n"; +} + +static void EmitMessage(raw_ostream &o, StringRef Message, unsigned indent) { + // Output the text. + assert(!Message.empty()); + Indent(o, indent) << "<key>extended_message</key>\n"; + Indent(o, indent); + EmitString(o, Message) << '\n'; + + // Output the short text. + // FIXME: Really use a short string. + Indent(o, indent) << "<key>message</key>\n"; + Indent(o, indent); + EmitString(o, Message) << '\n'; +} + static void ReportControlFlow(raw_ostream &o, const PathDiagnosticControlFlowPiece& P, const FIDMap& FM, @@ -133,7 +173,7 @@ static void ReportControlFlow(raw_ostream &o, Indent(o, indent) << "</dict>\n"; } -static void ReportEvent(raw_ostream &o, const PathDiagnosticPiece& P, +static void ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P, const FIDMap& FM, const SourceManager &SM, const LangOptions &LangOpts, @@ -158,34 +198,14 @@ static void ReportEvent(raw_ostream &o, const PathDiagnosticPiece& P, // Output the ranges (if any). ArrayRef<SourceRange> Ranges = P.getRanges(); - - if (!Ranges.empty()) { - Indent(o, indent) << "<key>ranges</key>\n"; - Indent(o, indent) << "<array>\n"; - ++indent; - for (auto &R : Ranges) - EmitRange(o, SM, - Lexer::getAsCharRange(SM.getExpansionRange(R), SM, LangOpts), - FM, indent + 1); - --indent; - Indent(o, indent) << "</array>\n"; - } + EmitRanges(o, Ranges, FM, SM, LangOpts, indent); // Output the call depth. Indent(o, indent) << "<key>depth</key>"; EmitInteger(o, depth) << '\n'; // Output the text. - assert(!P.getString().empty()); - Indent(o, indent) << "<key>extended_message</key>\n"; - Indent(o, indent); - EmitString(o, P.getString()) << '\n'; - - // Output the short text. - // FIXME: Really use a short string. - Indent(o, indent) << "<key>message</key>\n"; - Indent(o, indent); - EmitString(o, P.getString()) << '\n'; + EmitMessage(o, P.getString(), indent); // Finish up. --indent; @@ -241,6 +261,34 @@ static void ReportMacro(raw_ostream &o, } } +static void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P, + const FIDMap& FM, + const SourceManager &SM, + const LangOptions &LangOpts, + unsigned indent, + unsigned depth) { + + Indent(o, indent) << "<dict>\n"; + ++indent; + + // Output the location. + FullSourceLoc L = P.getLocation().asLocation(); + + Indent(o, indent) << "<key>location</key>\n"; + EmitLocation(o, SM, L, FM, indent); + + // Output the ranges (if any). + ArrayRef<SourceRange> Ranges = P.getRanges(); + EmitRanges(o, Ranges, FM, SM, LangOpts, indent); + + // Output the text. + EmitMessage(o, P.getString(), indent); + + // Finish up. + --indent; + Indent(o, indent); o << "</dict>\n"; +} + static void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P, const FIDMap& FM, const SourceManager &SM, const LangOptions &LangOpts) { @@ -266,7 +314,7 @@ static void ReportPiece(raw_ostream &o, indent, depth); break; case PathDiagnosticPiece::Event: - ReportEvent(o, cast<PathDiagnosticSpotPiece>(P), FM, SM, LangOpts, + ReportEvent(o, cast<PathDiagnosticEventPiece>(P), FM, SM, LangOpts, indent, depth, isKeyEvent); break; case PathDiagnosticPiece::Macro: @@ -274,7 +322,8 @@ static void ReportPiece(raw_ostream &o, indent, depth); break; case PathDiagnosticPiece::Note: - // FIXME: Extend the plist format to support those. + ReportNote(o, cast<PathDiagnosticNotePiece>(P), FM, SM, LangOpts, + indent, depth); break; } } @@ -359,15 +408,39 @@ void PlistDiagnostics::FlushDiagnosticsImpl( for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(), DE = Diags.end(); DI!=DE; ++DI) { - o << " <dict>\n" - " <key>path</key>\n"; + o << " <dict>\n"; const PathDiagnostic *D = *DI; + const PathPieces &PP = D->path; + + assert(std::is_partitioned( + PP.begin(), PP.end(), + [](const std::shared_ptr<PathDiagnosticPiece> &E) + { return E->getKind() == PathDiagnosticPiece::Note; }) && + "PathDiagnostic is not partitioned so that notes precede the rest"); + + PathPieces::const_iterator FirstNonNote = std::partition_point( + PP.begin(), PP.end(), + [](const std::shared_ptr<PathDiagnosticPiece> &E) + { return E->getKind() == PathDiagnosticPiece::Note; }); + + PathPieces::const_iterator I = PP.begin(); + + if (FirstNonNote != PP.begin()) { + o << " <key>notes</key>\n" + " <array>\n"; + + for (; I != FirstNonNote; ++I) + ReportDiag(o, **I, FM, *SM, LangOpts); + + o << " </array>\n"; + } + + o << " <key>path</key>\n"; o << " <array>\n"; - for (PathPieces::const_iterator I = D->path.begin(), E = D->path.end(); - I != E; ++I) + for (PathPieces::const_iterator E = PP.end(); I != E; ++I) ReportDiag(o, **I, FM, *SM, LangOpts); o << " </array>\n"; @@ -484,6 +557,15 @@ void PlistDiagnostics::FlushDiagnosticsImpl( o << " </array>\n"; + if (llvm::AreStatisticsEnabled() && SerializeStatistics) { + o << " <key>statistics</key>\n"; + std::string stats; + llvm::raw_string_ostream os(stats); + llvm::PrintStatisticsJSON(os); + os.flush(); + EmitString(o, html::EscapeText(stats)) << '\n'; + } + // Finish. o << "</dict>\n</plist>"; } diff --git a/lib/StaticAnalyzer/Core/ProgramState.cpp b/lib/StaticAnalyzer/Core/ProgramState.cpp index 5b6b7339697f..2b401607293b 100644 --- a/lib/StaticAnalyzer/Core/ProgramState.cpp +++ b/lib/StaticAnalyzer/Core/ProgramState.cpp @@ -17,6 +17,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/TaintManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" #include "llvm/Support/raw_ostream.h" using namespace clang; @@ -125,16 +126,27 @@ ProgramStateRef ProgramState::bindLoc(Loc LV, return newState; } -ProgramStateRef ProgramState::bindDefault(SVal loc, - SVal V, - const LocationContext *LCtx) const { +ProgramStateRef +ProgramState::bindDefaultInitial(SVal loc, SVal V, + const LocationContext *LCtx) const { + ProgramStateManager &Mgr = getStateManager(); + const MemRegion *R = loc.castAs<loc::MemRegionVal>().getRegion(); + const StoreRef &newStore = Mgr.StoreMgr->BindDefaultInitial(getStore(), R, V); + ProgramStateRef new_state = makeWithStore(newStore); + return Mgr.getOwningEngine() + ? Mgr.getOwningEngine()->processRegionChange(new_state, R, LCtx) + : new_state; +} + +ProgramStateRef +ProgramState::bindDefaultZero(SVal loc, const LocationContext *LCtx) const { ProgramStateManager &Mgr = getStateManager(); const MemRegion *R = loc.castAs<loc::MemRegionVal>().getRegion(); - const StoreRef &newStore = Mgr.StoreMgr->BindDefault(getStore(), R, V); + const StoreRef &newStore = Mgr.StoreMgr->BindDefaultZero(getStore(), R); ProgramStateRef new_state = makeWithStore(newStore); - return Mgr.getOwningEngine() ? - Mgr.getOwningEngine()->processRegionChange(new_state, R, LCtx) : - new_state; + return Mgr.getOwningEngine() + ? Mgr.getOwningEngine()->processRegionChange(new_state, R, LCtx) + : new_state; } typedef ArrayRef<const MemRegion *> RegionList; @@ -254,7 +266,7 @@ SVal ProgramState::getSValAsScalarOrLoc(const MemRegion *R) const { } SVal ProgramState::getSVal(Loc location, QualType T) const { - SVal V = getRawSVal(cast<Loc>(location), T); + SVal V = getRawSVal(location, T); // If 'V' is a symbolic value that is *perfectly* constrained to // be a constant value, use that value instead to lessen the burden @@ -324,9 +336,8 @@ ProgramStateRef ProgramState::assumeInBound(DefinedOrUnknownSVal Idx, // Get the offset: the minimum value of the array index type. BasicValueFactory &BVF = svalBuilder.getBasicValueFactory(); - // FIXME: This should be using ValueManager::ArrayindexTy...somehow. if (indexTy.isNull()) - indexTy = Ctx.IntTy; + indexTy = svalBuilder.getArrayIndexType(); nonloc::ConcreteInt Min(BVF.getMinValue(indexTy)); // Adjust the index. @@ -354,6 +365,17 @@ ProgramStateRef ProgramState::assumeInBound(DefinedOrUnknownSVal Idx, return CM.assume(this, inBound.castAs<DefinedSVal>(), Assumption); } +ConditionTruthVal ProgramState::isNonNull(SVal V) const { + ConditionTruthVal IsNull = isNull(V); + if (IsNull.isUnderconstrained()) + return IsNull; + return ConditionTruthVal(!IsNull.getValue()); +} + +ConditionTruthVal ProgramState::areEqual(SVal Lhs, SVal Rhs) const { + return stateMgr->getSValBuilder().areEqual(this, Lhs, Rhs); +} + ConditionTruthVal ProgramState::isNull(SVal V) const { if (V.isZeroConstant()) return true; @@ -426,24 +448,30 @@ void ProgramState::setStore(const StoreRef &newStore) { // State pretty-printing. //===----------------------------------------------------------------------===// -void ProgramState::print(raw_ostream &Out, - const char *NL, const char *Sep) const { +void ProgramState::print(raw_ostream &Out, const char *NL, const char *Sep, + const LocationContext *LC) const { // Print the store. ProgramStateManager &Mgr = getStateManager(); Mgr.getStoreManager().print(getStore(), Out, NL, Sep); // Print out the environment. - Env.print(Out, NL, Sep); + Env.print(Out, NL, Sep, LC); // Print out the constraints. Mgr.getConstraintManager().print(this, Out, NL, Sep); + // Print out the tracked dynamic types. + printDynamicTypeInfo(this, Out, NL, Sep); + + // Print out tainted symbols. + printTaint(Out, NL, Sep); + // Print checker-specific data. - Mgr.getOwningEngine()->printState(Out, this, NL, Sep); + Mgr.getOwningEngine()->printState(Out, this, NL, Sep, LC); } -void ProgramState::printDOT(raw_ostream &Out) const { - print(Out, "\\l", "\\|"); +void ProgramState::printDOT(raw_ostream &Out, const LocationContext *LC) const { + print(Out, "\\l", "\\|", LC); } LLVM_DUMP_METHOD void ProgramState::dump() const { @@ -455,7 +483,7 @@ void ProgramState::printTaint(raw_ostream &Out, TaintMapImpl TM = get<TaintMap>(); if (!TM.isEmpty()) - Out <<"Tainted Symbols:" << NL; + Out <<"Tainted symbols:" << NL; for (TaintMapImpl::iterator I = TM.begin(), E = TM.end(); I != E; ++I) { Out << I->first << " : " << I->second << NL; @@ -781,8 +809,7 @@ bool ProgramState::isTainted(SymbolRef Sym, TaintTagType Kind) const { // complete. For example, this would not currently identify // overlapping fields in a union as tainted. To identify this we can // check for overlapping/nested byte offsets. - if (Kind == I.second && - (R == I.first || R->isSubRegionOf(I.first))) + if (Kind == I.second && R->isSubRegionOf(I.first)) return true; } } diff --git a/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp b/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp index 5a4031c0b4a5..e8c7bdbde385 100644 --- a/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp +++ b/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp @@ -12,10 +12,10 @@ // //===----------------------------------------------------------------------===// -#include "RangedConstraintManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/RangedConstraintManager.h" #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/ImmutableSet.h" #include "llvm/Support/raw_ostream.h" @@ -23,263 +23,203 @@ using namespace clang; using namespace ento; -/// A Range represents the closed range [from, to]. The caller must -/// guarantee that from <= to. Note that Range is immutable, so as not -/// to subvert RangeSet's immutability. -namespace { -class Range : public std::pair<const llvm::APSInt *, const llvm::APSInt *> { -public: - Range(const llvm::APSInt &from, const llvm::APSInt &to) - : std::pair<const llvm::APSInt *, const llvm::APSInt *>(&from, &to) { - assert(from <= to); - } - bool Includes(const llvm::APSInt &v) const { - return *first <= v && v <= *second; - } - const llvm::APSInt &From() const { return *first; } - const llvm::APSInt &To() const { return *second; } - const llvm::APSInt *getConcreteValue() const { - return &From() == &To() ? &From() : nullptr; - } - - void Profile(llvm::FoldingSetNodeID &ID) const { - ID.AddPointer(&From()); - ID.AddPointer(&To()); - } -}; - -class RangeTrait : public llvm::ImutContainerInfo<Range> { -public: - // When comparing if one Range is less than another, we should compare - // the actual APSInt values instead of their pointers. This keeps the order - // consistent (instead of comparing by pointer values) and can potentially - // be used to speed up some of the operations in RangeSet. - static inline bool isLess(key_type_ref lhs, key_type_ref rhs) { - return *lhs.first < *rhs.first || - (!(*rhs.first < *lhs.first) && *lhs.second < *rhs.second); - } -}; - -/// RangeSet contains a set of ranges. If the set is empty, then -/// there the value of a symbol is overly constrained and there are no -/// possible values for that symbol. -class RangeSet { - typedef llvm::ImmutableSet<Range, RangeTrait> PrimRangeSet; - PrimRangeSet ranges; // no need to make const, since it is an - // ImmutableSet - this allows default operator= - // to work. -public: - typedef PrimRangeSet::Factory Factory; - typedef PrimRangeSet::iterator iterator; - - RangeSet(PrimRangeSet RS) : ranges(RS) {} - - /// Create a new set with all ranges of this set and RS. - /// Possible intersections are not checked here. - RangeSet addRange(Factory &F, const RangeSet &RS) { - PrimRangeSet Ranges(RS.ranges); - for (const auto &range : ranges) - Ranges = F.add(Ranges, range); - return RangeSet(Ranges); - } - - iterator begin() const { return ranges.begin(); } - iterator end() const { return ranges.end(); } - - bool isEmpty() const { return ranges.isEmpty(); } - - /// Construct a new RangeSet representing '{ [from, to] }'. - RangeSet(Factory &F, const llvm::APSInt &from, const llvm::APSInt &to) - : ranges(F.add(F.getEmptySet(), Range(from, to))) {} - - /// Profile - Generates a hash profile of this RangeSet for use - /// by FoldingSet. - void Profile(llvm::FoldingSetNodeID &ID) const { ranges.Profile(ID); } - - /// getConcreteValue - If a symbol is contrained to equal a specific integer - /// constant then this method returns that value. Otherwise, it returns - /// NULL. - const llvm::APSInt *getConcreteValue() const { - return ranges.isSingleton() ? ranges.begin()->getConcreteValue() : nullptr; - } +void RangeSet::IntersectInRange(BasicValueFactory &BV, Factory &F, + const llvm::APSInt &Lower, const llvm::APSInt &Upper, + PrimRangeSet &newRanges, PrimRangeSet::iterator &i, + PrimRangeSet::iterator &e) const { + // There are six cases for each range R in the set: + // 1. R is entirely before the intersection range. + // 2. R is entirely after the intersection range. + // 3. R contains the entire intersection range. + // 4. R starts before the intersection range and ends in the middle. + // 5. R starts in the middle of the intersection range and ends after it. + // 6. R is entirely contained in the intersection range. + // These correspond to each of the conditions below. + for (/* i = begin(), e = end() */; i != e; ++i) { + if (i->To() < Lower) { + continue; + } + if (i->From() > Upper) { + break; + } -private: - void IntersectInRange(BasicValueFactory &BV, Factory &F, - const llvm::APSInt &Lower, const llvm::APSInt &Upper, - PrimRangeSet &newRanges, PrimRangeSet::iterator &i, - PrimRangeSet::iterator &e) const { - // There are six cases for each range R in the set: - // 1. R is entirely before the intersection range. - // 2. R is entirely after the intersection range. - // 3. R contains the entire intersection range. - // 4. R starts before the intersection range and ends in the middle. - // 5. R starts in the middle of the intersection range and ends after it. - // 6. R is entirely contained in the intersection range. - // These correspond to each of the conditions below. - for (/* i = begin(), e = end() */; i != e; ++i) { - if (i->To() < Lower) { - continue; - } - if (i->From() > Upper) { + if (i->Includes(Lower)) { + if (i->Includes(Upper)) { + newRanges = + F.add(newRanges, Range(BV.getValue(Lower), BV.getValue(Upper))); break; - } - - if (i->Includes(Lower)) { - if (i->Includes(Upper)) { - newRanges = - F.add(newRanges, Range(BV.getValue(Lower), BV.getValue(Upper))); - break; - } else - newRanges = F.add(newRanges, Range(BV.getValue(Lower), i->To())); - } else { - if (i->Includes(Upper)) { - newRanges = F.add(newRanges, Range(i->From(), BV.getValue(Upper))); - break; - } else - newRanges = F.add(newRanges, *i); - } + } else + newRanges = F.add(newRanges, Range(BV.getValue(Lower), i->To())); + } else { + if (i->Includes(Upper)) { + newRanges = F.add(newRanges, Range(i->From(), BV.getValue(Upper))); + break; + } else + newRanges = F.add(newRanges, *i); } } +} - const llvm::APSInt &getMinValue() const { - assert(!isEmpty()); - return ranges.begin()->From(); - } +const llvm::APSInt &RangeSet::getMinValue() const { + assert(!isEmpty()); + return ranges.begin()->From(); +} - bool pin(llvm::APSInt &Lower, llvm::APSInt &Upper) const { - // This function has nine cases, the cartesian product of range-testing - // both the upper and lower bounds against the symbol's type. - // Each case requires a different pinning operation. - // The function returns false if the described range is entirely outside - // the range of values for the associated symbol. - APSIntType Type(getMinValue()); - APSIntType::RangeTestResultKind LowerTest = Type.testInRange(Lower, true); - APSIntType::RangeTestResultKind UpperTest = Type.testInRange(Upper, true); - - switch (LowerTest) { +bool RangeSet::pin(llvm::APSInt &Lower, llvm::APSInt &Upper) const { + // This function has nine cases, the cartesian product of range-testing + // both the upper and lower bounds against the symbol's type. + // Each case requires a different pinning operation. + // The function returns false if the described range is entirely outside + // the range of values for the associated symbol. + APSIntType Type(getMinValue()); + APSIntType::RangeTestResultKind LowerTest = Type.testInRange(Lower, true); + APSIntType::RangeTestResultKind UpperTest = Type.testInRange(Upper, true); + + switch (LowerTest) { + case APSIntType::RTR_Below: + switch (UpperTest) { case APSIntType::RTR_Below: - switch (UpperTest) { - case APSIntType::RTR_Below: - // The entire range is outside the symbol's set of possible values. - // If this is a conventionally-ordered range, the state is infeasible. - if (Lower <= Upper) - return false; - - // However, if the range wraps around, it spans all possible values. - Lower = Type.getMinValue(); - Upper = Type.getMaxValue(); - break; - case APSIntType::RTR_Within: - // The range starts below what's possible but ends within it. Pin. - Lower = Type.getMinValue(); - Type.apply(Upper); - break; - case APSIntType::RTR_Above: - // The range spans all possible values for the symbol. Pin. - Lower = Type.getMinValue(); - Upper = Type.getMaxValue(); - break; - } + // The entire range is outside the symbol's set of possible values. + // If this is a conventionally-ordered range, the state is infeasible. + if (Lower <= Upper) + return false; + + // However, if the range wraps around, it spans all possible values. + Lower = Type.getMinValue(); + Upper = Type.getMaxValue(); break; case APSIntType::RTR_Within: - switch (UpperTest) { - case APSIntType::RTR_Below: - // The range wraps around, but all lower values are not possible. - Type.apply(Lower); - Upper = Type.getMaxValue(); - break; - case APSIntType::RTR_Within: - // The range may or may not wrap around, but both limits are valid. - Type.apply(Lower); - Type.apply(Upper); - break; - case APSIntType::RTR_Above: - // The range starts within what's possible but ends above it. Pin. - Type.apply(Lower); - Upper = Type.getMaxValue(); - break; - } + // The range starts below what's possible but ends within it. Pin. + Lower = Type.getMinValue(); + Type.apply(Upper); break; case APSIntType::RTR_Above: - switch (UpperTest) { - case APSIntType::RTR_Below: - // The range wraps but is outside the symbol's set of possible values. - return false; - case APSIntType::RTR_Within: - // The range starts above what's possible but ends within it (wrap). - Lower = Type.getMinValue(); - Type.apply(Upper); - break; - case APSIntType::RTR_Above: - // The entire range is outside the symbol's set of possible values. - // If this is a conventionally-ordered range, the state is infeasible. - if (Lower <= Upper) - return false; - - // However, if the range wraps around, it spans all possible values. - Lower = Type.getMinValue(); - Upper = Type.getMaxValue(); - break; - } + // The range spans all possible values for the symbol. Pin. + Lower = Type.getMinValue(); + Upper = Type.getMaxValue(); + break; + } + break; + case APSIntType::RTR_Within: + switch (UpperTest) { + case APSIntType::RTR_Below: + // The range wraps around, but all lower values are not possible. + Type.apply(Lower); + Upper = Type.getMaxValue(); + break; + case APSIntType::RTR_Within: + // The range may or may not wrap around, but both limits are valid. + Type.apply(Lower); + Type.apply(Upper); + break; + case APSIntType::RTR_Above: + // The range starts within what's possible but ends above it. Pin. + Type.apply(Lower); + Upper = Type.getMaxValue(); break; } + break; + case APSIntType::RTR_Above: + switch (UpperTest) { + case APSIntType::RTR_Below: + // The range wraps but is outside the symbol's set of possible values. + return false; + case APSIntType::RTR_Within: + // The range starts above what's possible but ends within it (wrap). + Lower = Type.getMinValue(); + Type.apply(Upper); + break; + case APSIntType::RTR_Above: + // The entire range is outside the symbol's set of possible values. + // If this is a conventionally-ordered range, the state is infeasible. + if (Lower <= Upper) + return false; - return true; + // However, if the range wraps around, it spans all possible values. + Lower = Type.getMinValue(); + Upper = Type.getMaxValue(); + break; + } + break; } -public: - // Returns a set containing the values in the receiving set, intersected with - // the closed range [Lower, Upper]. Unlike the Range type, this range uses - // modular arithmetic, corresponding to the common treatment of C integer - // overflow. Thus, if the Lower bound is greater than the Upper bound, the - // range is taken to wrap around. This is equivalent to taking the - // intersection with the two ranges [Min, Upper] and [Lower, Max], - // or, alternatively, /removing/ all integers between Upper and Lower. - RangeSet Intersect(BasicValueFactory &BV, Factory &F, llvm::APSInt Lower, - llvm::APSInt Upper) const { - if (!pin(Lower, Upper)) - return F.getEmptySet(); - - PrimRangeSet newRanges = F.getEmptySet(); - - PrimRangeSet::iterator i = begin(), e = end(); - if (Lower <= Upper) - IntersectInRange(BV, F, Lower, Upper, newRanges, i, e); - else { - // The order of the next two statements is important! - // IntersectInRange() does not reset the iteration state for i and e. - // Therefore, the lower range most be handled first. - IntersectInRange(BV, F, BV.getMinValue(Upper), Upper, newRanges, i, e); - IntersectInRange(BV, F, Lower, BV.getMaxValue(Lower), newRanges, i, e); - } + return true; +} + +// Returns a set containing the values in the receiving set, intersected with +// the closed range [Lower, Upper]. Unlike the Range type, this range uses +// modular arithmetic, corresponding to the common treatment of C integer +// overflow. Thus, if the Lower bound is greater than the Upper bound, the +// range is taken to wrap around. This is equivalent to taking the +// intersection with the two ranges [Min, Upper] and [Lower, Max], +// or, alternatively, /removing/ all integers between Upper and Lower. +RangeSet RangeSet::Intersect(BasicValueFactory &BV, Factory &F, + llvm::APSInt Lower, llvm::APSInt Upper) const { + if (!pin(Lower, Upper)) + return F.getEmptySet(); - return newRanges; + PrimRangeSet newRanges = F.getEmptySet(); + + PrimRangeSet::iterator i = begin(), e = end(); + if (Lower <= Upper) + IntersectInRange(BV, F, Lower, Upper, newRanges, i, e); + else { + // The order of the next two statements is important! + // IntersectInRange() does not reset the iteration state for i and e. + // Therefore, the lower range most be handled first. + IntersectInRange(BV, F, BV.getMinValue(Upper), Upper, newRanges, i, e); + IntersectInRange(BV, F, Lower, BV.getMaxValue(Lower), newRanges, i, e); } - void print(raw_ostream &os) const { - bool isFirst = true; - os << "{ "; - for (iterator i = begin(), e = end(); i != e; ++i) { - if (isFirst) - isFirst = false; - else - os << ", "; - - os << '[' << i->From().toString(10) << ", " << i->To().toString(10) - << ']'; + return newRanges; +} + +// Turn all [A, B] ranges to [-B, -A]. Ranges [MIN, B] are turned to range set +// [MIN, MIN] U [-B, MAX], when MIN and MAX are the minimal and the maximal +// signed values of the type. +RangeSet RangeSet::Negate(BasicValueFactory &BV, Factory &F) const { + PrimRangeSet newRanges = F.getEmptySet(); + + for (iterator i = begin(), e = end(); i != e; ++i) { + const llvm::APSInt &from = i->From(), &to = i->To(); + const llvm::APSInt &newTo = (from.isMinSignedValue() ? + BV.getMaxValue(from) : + BV.getValue(- from)); + if (to.isMaxSignedValue() && !newRanges.isEmpty() && + newRanges.begin()->From().isMinSignedValue()) { + assert(newRanges.begin()->To().isMinSignedValue() && + "Ranges should not overlap"); + assert(!from.isMinSignedValue() && "Ranges should not overlap"); + const llvm::APSInt &newFrom = newRanges.begin()->From(); + newRanges = + F.add(F.remove(newRanges, *newRanges.begin()), Range(newFrom, newTo)); + } else if (!to.isMinSignedValue()) { + const llvm::APSInt &newFrom = BV.getValue(- to); + newRanges = F.add(newRanges, Range(newFrom, newTo)); + } + if (from.isMinSignedValue()) { + newRanges = F.add(newRanges, Range(BV.getMinValue(from), + BV.getMinValue(from))); } - os << " }"; } - bool operator==(const RangeSet &other) const { - return ranges == other.ranges; - } -}; -} // end anonymous namespace + return newRanges; +} -REGISTER_TRAIT_WITH_PROGRAMSTATE(ConstraintRange, - CLANG_ENTO_PROGRAMSTATE_MAP(SymbolRef, - RangeSet)) +void RangeSet::print(raw_ostream &os) const { + bool isFirst = true; + os << "{ "; + for (iterator i = begin(), e = end(); i != e; ++i) { + if (isFirst) + isFirst = false; + else + os << ", "; + + os << '[' << i->From().toString(10) << ", " << i->To().toString(10) + << ']'; + } + os << " }"; +} namespace { class RangeConstraintManager : public RangedConstraintManager { @@ -344,6 +284,8 @@ private: RangeSet::Factory F; RangeSet getRange(ProgramStateRef State, SymbolRef Sym); + const RangeSet* getRangeForMinusSymbol(ProgramStateRef State, + SymbolRef Sym); RangeSet getSymLTRange(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, @@ -360,6 +302,7 @@ private: RangeSet getSymGERange(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, const llvm::APSInt &Adjustment); + }; } // end anonymous namespace @@ -400,9 +343,11 @@ bool RangeConstraintManager::canReasonAbout(SVal X) const { if (BinaryOperator::isEqualityOp(SSE->getOpcode()) || BinaryOperator::isRelationalOp(SSE->getOpcode())) { // We handle Loc <> Loc comparisons, but not (yet) NonLoc <> NonLoc. + // We've recently started producing Loc <> NonLoc comparisons (that + // result from casts of one of the operands between eg. intptr_t and + // void *), but we can't reason about them yet. if (Loc::isLocType(SSE->getLHS()->getType())) { - assert(Loc::isLocType(SSE->getRHS()->getType())); - return true; + return Loc::isLocType(SSE->getRHS()->getType()); } } } @@ -474,7 +419,7 @@ static RangeSet assumeNonZero( --IntType.getZeroValue()); } -/// \brief Apply implicit constraints for bitwise OR- and AND-. +/// Apply implicit constraints for bitwise OR- and AND-. /// For unsigned types, bitwise OR with a constant always returns /// a value greater-or-equal than the constant, and bitwise AND /// returns a value less-or-equal then the constant. @@ -515,9 +460,15 @@ RangeSet RangeConstraintManager::getRange(ProgramStateRef State, if (ConstraintRangeTy::data_type *V = State->get<ConstraintRange>(Sym)) return *V; + BasicValueFactory &BV = getBasicVals(); + + // If Sym is a difference of symbols A - B, then maybe we have range set + // stored for B - A. + if (const RangeSet *R = getRangeForMinusSymbol(State, Sym)) + return R->Negate(BV, F); + // Lazily generate a new RangeSet representing all possible values for the // given symbol type. - BasicValueFactory &BV = getBasicVals(); QualType T = Sym->getType(); RangeSet Result(F, BV.getMinValue(T), BV.getMaxValue(T)); @@ -533,6 +484,32 @@ RangeSet RangeConstraintManager::getRange(ProgramStateRef State, return Result; } +// FIXME: Once SValBuilder supports unary minus, we should use SValBuilder to +// obtain the negated symbolic expression instead of constructing the +// symbol manually. This will allow us to support finding ranges of not +// only negated SymSymExpr-type expressions, but also of other, simpler +// expressions which we currently do not know how to negate. +const RangeSet* +RangeConstraintManager::getRangeForMinusSymbol(ProgramStateRef State, + SymbolRef Sym) { + if (const SymSymExpr *SSE = dyn_cast<SymSymExpr>(Sym)) { + if (SSE->getOpcode() == BO_Sub) { + QualType T = Sym->getType(); + SymbolManager &SymMgr = State->getSymbolManager(); + SymbolRef negSym = SymMgr.getSymSymExpr(SSE->getRHS(), BO_Sub, + SSE->getLHS(), T); + if (const RangeSet *negV = State->get<ConstraintRange>(negSym)) { + // Unsigned range set cannot be negated, unless it is [0, 0]. + if ((negV->getConcreteValue() && + (*negV->getConcreteValue() == 0)) || + T->isSignedIntegerOrEnumerationType()) + return negV; + } + } + } + return nullptr; +} + //===------------------------------------------------------------------------=== // assumeSymX methods: protected interface for RangeConstraintManager. //===------------------------------------------------------------------------===/ diff --git a/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp b/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp index 55ff15806efe..f99853f07073 100644 --- a/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp +++ b/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp @@ -12,8 +12,8 @@ // //===----------------------------------------------------------------------===// -#include "RangedConstraintManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/RangedConstraintManager.h" namespace clang { @@ -52,17 +52,18 @@ ProgramStateRef RangedConstraintManager::assumeSym(ProgramStateRef State, assert(BinaryOperator::isComparisonOp(Op)); // For now, we only support comparing pointers. - assert(Loc::isLocType(SSE->getLHS()->getType())); - assert(Loc::isLocType(SSE->getRHS()->getType())); - QualType DiffTy = SymMgr.getContext().getPointerDiffType(); - SymbolRef Subtraction = - SymMgr.getSymSymExpr(SSE->getRHS(), BO_Sub, SSE->getLHS(), DiffTy); - - const llvm::APSInt &Zero = getBasicVals().getValue(0, DiffTy); - Op = BinaryOperator::reverseComparisonOp(Op); - if (!Assumption) - Op = BinaryOperator::negateComparisonOp(Op); - return assumeSymRel(State, Subtraction, Op, Zero); + if (Loc::isLocType(SSE->getLHS()->getType()) && + Loc::isLocType(SSE->getRHS()->getType())) { + QualType DiffTy = SymMgr.getContext().getPointerDiffType(); + SymbolRef Subtraction = + SymMgr.getSymSymExpr(SSE->getRHS(), BO_Sub, SSE->getLHS(), DiffTy); + + const llvm::APSInt &Zero = getBasicVals().getValue(0, DiffTy); + Op = BinaryOperator::reverseComparisonOp(Op); + if (!Assumption) + Op = BinaryOperator::negateComparisonOp(Op); + return assumeSymRel(State, Subtraction, Op, Zero); + } } // If we get here, there's nothing else we can do but treat the symbol as diff --git a/lib/StaticAnalyzer/Core/RangedConstraintManager.h b/lib/StaticAnalyzer/Core/RangedConstraintManager.h deleted file mode 100644 index a4e6062a4f57..000000000000 --- a/lib/StaticAnalyzer/Core/RangedConstraintManager.h +++ /dev/null @@ -1,102 +0,0 @@ -//== RangedConstraintManager.h ----------------------------------*- C++ -*--==// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// -// Ranged constraint manager, built on SimpleConstraintManager. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_LIB_STATICANALYZER_CORE_RANGEDCONSTRAINTMANAGER_H -#define LLVM_CLANG_LIB_STATICANALYZER_CORE_RANGEDCONSTRAINTMANAGER_H - -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SimpleConstraintManager.h" - -namespace clang { - -namespace ento { - -class RangedConstraintManager : public SimpleConstraintManager { -public: - RangedConstraintManager(SubEngine *SE, SValBuilder &SB) - : SimpleConstraintManager(SE, SB) {} - - ~RangedConstraintManager() override; - - //===------------------------------------------------------------------===// - // Implementation for interface from SimpleConstraintManager. - //===------------------------------------------------------------------===// - - ProgramStateRef assumeSym(ProgramStateRef State, SymbolRef Sym, - bool Assumption) override; - - ProgramStateRef assumeSymInclusiveRange(ProgramStateRef State, SymbolRef Sym, - const llvm::APSInt &From, - const llvm::APSInt &To, - bool InRange) override; - - ProgramStateRef assumeSymUnsupported(ProgramStateRef State, SymbolRef Sym, - bool Assumption) override; - -protected: - /// Assume a constraint between a symbolic expression and a concrete integer. - virtual ProgramStateRef assumeSymRel(ProgramStateRef State, SymbolRef Sym, - BinaryOperator::Opcode op, - const llvm::APSInt &Int); - - //===------------------------------------------------------------------===// - // Interface that subclasses must implement. - //===------------------------------------------------------------------===// - - // Each of these is of the form "$Sym+Adj <> V", where "<>" is the comparison - // operation for the method being invoked. - - virtual ProgramStateRef assumeSymNE(ProgramStateRef State, SymbolRef Sym, - const llvm::APSInt &V, - const llvm::APSInt &Adjustment) = 0; - - virtual ProgramStateRef assumeSymEQ(ProgramStateRef State, SymbolRef Sym, - const llvm::APSInt &V, - const llvm::APSInt &Adjustment) = 0; - - virtual ProgramStateRef assumeSymLT(ProgramStateRef State, SymbolRef Sym, - const llvm::APSInt &V, - const llvm::APSInt &Adjustment) = 0; - - virtual ProgramStateRef assumeSymGT(ProgramStateRef State, SymbolRef Sym, - const llvm::APSInt &V, - const llvm::APSInt &Adjustment) = 0; - - virtual ProgramStateRef assumeSymLE(ProgramStateRef State, SymbolRef Sym, - const llvm::APSInt &V, - const llvm::APSInt &Adjustment) = 0; - - virtual ProgramStateRef assumeSymGE(ProgramStateRef State, SymbolRef Sym, - const llvm::APSInt &V, - const llvm::APSInt &Adjustment) = 0; - - virtual ProgramStateRef assumeSymWithinInclusiveRange( - ProgramStateRef State, SymbolRef Sym, const llvm::APSInt &From, - const llvm::APSInt &To, const llvm::APSInt &Adjustment) = 0; - - virtual ProgramStateRef assumeSymOutsideInclusiveRange( - ProgramStateRef State, SymbolRef Sym, const llvm::APSInt &From, - const llvm::APSInt &To, const llvm::APSInt &Adjustment) = 0; - - //===------------------------------------------------------------------===// - // Internal implementation. - //===------------------------------------------------------------------===// -private: - static void computeAdjustment(SymbolRef &Sym, llvm::APSInt &Adjustment); -}; - -} // end GR namespace - -} // end clang namespace - -#endif diff --git a/lib/StaticAnalyzer/Core/RegionStore.cpp b/lib/StaticAnalyzer/Core/RegionStore.cpp index e2e69bb28ec2..db6449e6d5f3 100644 --- a/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -230,11 +230,6 @@ Optional<SVal> RegionBindingsRef::getDirectBinding(const MemRegion *R) const { } Optional<SVal> RegionBindingsRef::getDefaultBinding(const MemRegion *R) const { - if (R->isBoundable()) - if (const TypedValueRegion *TR = dyn_cast<TypedValueRegion>(R)) - if (TR->getValueType()->isUnionType()) - return UnknownVal(); - return Optional<SVal>::create(lookup(R, BindingKey::Default)); } @@ -338,7 +333,7 @@ private: /// To disable all small-struct-dependent behavior, set the option to "0". unsigned SmallStructLimit; - /// \brief A helper used to populate the work list with the given set of + /// A helper used to populate the work list with the given set of /// regions. void populateWorkList(invalidateRegionsWorker &W, ArrayRef<SVal> Values, @@ -409,8 +404,22 @@ public: // Part of public interface to class. RegionBindingsRef bind(RegionBindingsConstRef B, Loc LV, SVal V); - // BindDefault is only used to initialize a region with a default value. - StoreRef BindDefault(Store store, const MemRegion *R, SVal V) override { + // BindDefaultInitial is only used to initialize a region with + // a default value. + StoreRef BindDefaultInitial(Store store, const MemRegion *R, + SVal V) override { + RegionBindingsRef B = getRegionBindings(store); + // Use other APIs when you have to wipe the region that was initialized + // earlier. + assert(!(B.getDefaultBinding(R) || B.getDirectBinding(R)) && + "Double initialization!"); + B = B.addBinding(BindingKey::Make(R, BindingKey::Default), V); + return StoreRef(B.asImmutableMap().getRootWithoutRetain(), *this); + } + + // BindDefaultZero is used for zeroing constructors that may accidentally + // overwrite existing bindings. + StoreRef BindDefaultZero(Store store, const MemRegion *R) override { // FIXME: The offsets of empty bases can be tricky because of // of the so called "empty base class optimization". // If a base class has been optimized out @@ -420,24 +429,14 @@ public: // Part of public interface to class. // and trying to infer them from offsets/alignments // seems to be error-prone and non-trivial because of the trailing padding. // As a temporary mitigation we don't create bindings for empty bases. - if (R->getKind() == MemRegion::CXXBaseObjectRegionKind && - cast<CXXBaseObjectRegion>(R)->getDecl()->isEmpty()) - return StoreRef(store, *this); + if (const auto *BR = dyn_cast<CXXBaseObjectRegion>(R)) + if (BR->getDecl()->isEmpty()) + return StoreRef(store, *this); RegionBindingsRef B = getRegionBindings(store); - assert(!B.lookup(R, BindingKey::Direct)); - - BindingKey Key = BindingKey::Make(R, BindingKey::Default); - if (B.lookup(Key)) { - const SubRegion *SR = cast<SubRegion>(R); - assert(SR->getAsOffset().getOffset() == - SR->getSuperRegion()->getAsOffset().getOffset() && - "A default value must come from a super-region"); - B = removeSubRegionBindings(B, SR); - } else { - B = B.addBinding(Key, V); - } - + SVal V = svalBuilder.makeZeroVal(Ctx.CharTy); + B = removeSubRegionBindings(B, cast<SubRegion>(R)); + B = B.addBinding(BindingKey::Make(R, BindingKey::Default), V); return StoreRef(B.asImmutableMap().getRootWithoutRetain(), *this); } @@ -474,7 +473,7 @@ public: // Part of public interface to class. const TypedRegion *R, SVal DefaultVal); - /// \brief Create a new store with the specified binding removed. + /// Create a new store with the specified binding removed. /// \param ST the original store, that is the basis for the new store. /// \param L the location whose binding should be removed. StoreRef killBinding(Store ST, Loc L) override; @@ -492,7 +491,7 @@ public: // Part of public interface to class. bool includedInBindings(Store store, const MemRegion *region) const override; - /// \brief Return the value bound to specified location in a given state. + /// Return the value bound to specified location in a given state. /// /// The high level logic for this method is this: /// getBinding (L) @@ -825,7 +824,7 @@ collectSubRegionBindings(SmallVectorImpl<BindingPair> &Bindings, FieldVector FieldsInSymbolicSubregions; if (TopKey.hasSymbolicOffset()) { getSymbolicOffsetFields(TopKey, FieldsInSymbolicSubregions); - Top = cast<SubRegion>(TopKey.getConcreteOffsetRegion()); + Top = TopKey.getConcreteOffsetRegion(); TopKey = BindingKey::Make(Top, BindingKey::Default); } @@ -871,7 +870,7 @@ collectSubRegionBindings(SmallVectorImpl<BindingPair> &Bindings, } else if (NextKey.hasSymbolicOffset()) { const MemRegion *Base = NextKey.getConcreteOffsetRegion(); - if (Top->isSubRegionOf(Base)) { + if (Top->isSubRegionOf(Base) && Top != Base) { // Case 3: The next key is symbolic and we just changed something within // its concrete region. We don't know if the binding is still valid, so // we'll be conservative and include it. @@ -881,7 +880,7 @@ collectSubRegionBindings(SmallVectorImpl<BindingPair> &Bindings, } else if (const SubRegion *BaseSR = dyn_cast<SubRegion>(Base)) { // Case 4: The next key is symbolic, but we changed a known // super-region. In this case the binding is certainly included. - if (Top == Base || BaseSR->isSubRegionOf(Top)) + if (BaseSR->isSubRegionOf(Top)) if (isCompatibleWithFields(NextKey, FieldsInSymbolicSubregions)) Bindings.push_back(*I); } @@ -1095,7 +1094,7 @@ void invalidateRegionsWorker::VisitCluster(const MemRegion *baseR, return; } - if (T->isStructureOrClassType()) { + if (T->isRecordType()) { // Invalidate the region by setting its default value to // conjured symbol. The type of the symbol is irrelevant. DefinedOrUnknownSVal V = svalBuilder.conjureSymbolVal(baseR, Ex, LCtx, @@ -1342,7 +1341,8 @@ RegionStoreManager::getSizeInElements(ProgramStateRef state, // If a variable is reinterpreted as a type that doesn't fit into a larger // type evenly, round it down. // This is a signed value, since it's used in arithmetic with signed indices. - return svalBuilder.makeIntVal(RegionSize / EleSize, false); + return svalBuilder.makeIntVal(RegionSize / EleSize, + svalBuilder.getArrayIndexType()); } //===----------------------------------------------------------------------===// @@ -1401,12 +1401,12 @@ SVal RegionStoreManager::getBinding(RegionBindingsConstRef B, Loc L, QualType T) T = TR->getLocationType()->getPointeeType(); else if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(MR)) T = SR->getSymbol()->getType()->getPointeeType(); - else if (isa<AllocaRegion>(MR)) - T = Ctx.VoidTy; } assert(!T.isNull() && "Unable to auto-detect binding type!"); assert(!T->isVoidType() && "Attempting to dereference a void pointer!"); MR = GetElementZeroRegion(cast<SubRegion>(MR), T); + } else { + T = cast<TypedValueRegion>(MR)->getValueType(); } // FIXME: Perhaps this method should just take a 'const MemRegion*' argument @@ -1446,7 +1446,7 @@ SVal RegionStoreManager::getBinding(RegionBindingsConstRef B, Loc L, QualType T) return UnknownVal(); if (const FieldRegion* FR = dyn_cast<FieldRegion>(R)) - return CastRetrievedVal(getBindingForField(B, FR), FR, T, false); + return CastRetrievedVal(getBindingForField(B, FR), FR, T); if (const ElementRegion* ER = dyn_cast<ElementRegion>(R)) { // FIXME: Here we actually perform an implicit conversion from the loaded @@ -1454,7 +1454,7 @@ SVal RegionStoreManager::getBinding(RegionBindingsConstRef B, Loc L, QualType T) // more intelligently. For example, an 'element' can encompass multiple // bound regions (e.g., several bound bytes), or could be a subset of // a larger value. - return CastRetrievedVal(getBindingForElement(B, ER), ER, T, false); + return CastRetrievedVal(getBindingForElement(B, ER), ER, T); } if (const ObjCIvarRegion *IVR = dyn_cast<ObjCIvarRegion>(R)) { @@ -1464,7 +1464,7 @@ SVal RegionStoreManager::getBinding(RegionBindingsConstRef B, Loc L, QualType T) // reinterpretted, it is possible we stored a different value that could // fit within the ivar. Either we need to cast these when storing them // or reinterpret them lazily (as we do here). - return CastRetrievedVal(getBindingForObjCIvar(B, IVR), IVR, T, false); + return CastRetrievedVal(getBindingForObjCIvar(B, IVR), IVR, T); } if (const VarRegion *VR = dyn_cast<VarRegion>(R)) { @@ -1474,7 +1474,7 @@ SVal RegionStoreManager::getBinding(RegionBindingsConstRef B, Loc L, QualType T) // variable is reinterpretted, it is possible we stored a different value // that could fit within the variable. Either we need to cast these when // storing them or reinterpret them lazily (as we do here). - return CastRetrievedVal(getBindingForVar(B, VR), VR, T, false); + return CastRetrievedVal(getBindingForVar(B, VR), VR, T); } const SVal *V = B.lookup(R, BindingKey::Direct); @@ -1606,7 +1606,7 @@ SVal RegionStoreManager::getBindingForElement(RegionBindingsConstRef B, const MemRegion* superR = R->getSuperRegion(); // Check if the region is an element region of a string literal. - if (const StringRegion *StrR=dyn_cast<StringRegion>(superR)) { + if (const StringRegion *StrR = dyn_cast<StringRegion>(superR)) { // FIXME: Handle loads from strings where the literal is treated as // an integer, e.g., *((unsigned int*)"hello") QualType T = Ctx.getAsArrayType(StrR->getValueType())->getElementType(); @@ -1629,6 +1629,36 @@ SVal RegionStoreManager::getBindingForElement(RegionBindingsConstRef B, char c = (i >= length) ? '\0' : Str->getCodeUnit(i); return svalBuilder.makeIntVal(c, T); } + } else if (const VarRegion *VR = dyn_cast<VarRegion>(superR)) { + // Check if the containing array is const and has an initialized value. + const VarDecl *VD = VR->getDecl(); + // Either the array or the array element has to be const. + if (VD->getType().isConstQualified() || R->getElementType().isConstQualified()) { + if (const Expr *Init = VD->getInit()) { + if (const auto *InitList = dyn_cast<InitListExpr>(Init)) { + // The array index has to be known. + if (auto CI = R->getIndex().getAs<nonloc::ConcreteInt>()) { + int64_t i = CI->getValue().getSExtValue(); + // If it is known that the index is out of bounds, we can return + // an undefined value. + if (i < 0) + return UndefinedVal(); + + if (auto CAT = Ctx.getAsConstantArrayType(VD->getType())) + if (CAT->getSize().sle(i)) + return UndefinedVal(); + + // If there is a list, but no init, it must be zero. + if (i >= InitList->getNumInits()) + return svalBuilder.makeZeroVal(R->getElementType()); + + if (const Expr *ElemInit = InitList->getInit(i)) + if (Optional<SVal> V = svalBuilder.getConstantVal(ElemInit)) + return *V; + } + } + } + } } // Check for loads from a code text region. For such loads, just give up. @@ -1678,7 +1708,34 @@ SVal RegionStoreManager::getBindingForField(RegionBindingsConstRef B, if (const Optional<SVal> &V = B.getDirectBinding(R)) return *V; - QualType Ty = R->getValueType(); + // Is the field declared constant and has an in-class initializer? + const FieldDecl *FD = R->getDecl(); + QualType Ty = FD->getType(); + if (Ty.isConstQualified()) + if (const Expr *Init = FD->getInClassInitializer()) + if (Optional<SVal> V = svalBuilder.getConstantVal(Init)) + return *V; + + // If the containing record was initialized, try to get its constant value. + const MemRegion* superR = R->getSuperRegion(); + if (const auto *VR = dyn_cast<VarRegion>(superR)) { + const VarDecl *VD = VR->getDecl(); + QualType RecordVarTy = VD->getType(); + unsigned Index = FD->getFieldIndex(); + // Either the record variable or the field has to be const qualified. + if (RecordVarTy.isConstQualified() || Ty.isConstQualified()) + if (const Expr *Init = VD->getInit()) + if (const auto *InitList = dyn_cast<InitListExpr>(Init)) { + if (Index < InitList->getNumInits()) { + if (const Expr *FieldInit = InitList->getInit(Index)) + if (Optional<SVal> V = svalBuilder.getConstantVal(FieldInit)) + return *V; + } else { + return svalBuilder.makeZeroVal(Ty); + } + } + } + return getBindingForFieldOrElementCommon(B, R, Ty); } @@ -1776,7 +1833,7 @@ RegionStoreManager::getBindingForFieldOrElementCommon(RegionBindingsConstRef B, // quickly result in a warning. bool hasPartialLazyBinding = false; - const SubRegion *SR = dyn_cast<SubRegion>(R); + const SubRegion *SR = R; while (SR) { const MemRegion *Base = SR->getSuperRegion(); if (Optional<SVal> D = getBindingForDerivedDefaultValue(B, Base, R, Ty)) { @@ -2050,6 +2107,9 @@ RegionStoreManager::bind(RegionBindingsConstRef B, Loc L, SVal V) { R = GetElementZeroRegion(SR, T); } + assert((!isa<CXXThisRegion>(R) || !B.lookup(R)) && + "'this' pointer is not an l-value and is not assignable"); + // Clear out bindings that may overlap with this binding. RegionBindingsRef NewB = removeSubRegionBindings(B, cast<SubRegion>(R)); return NewB.addBinding(BindingKey::Make(R, BindingKey::Direct), V); diff --git a/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp b/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp new file mode 100644 index 000000000000..d379562bf325 --- /dev/null +++ b/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp @@ -0,0 +1,181 @@ +//== SMTConstraintManager.cpp -----------------------------------*- C++ -*--==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConstraintManager.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" + +using namespace clang; +using namespace ento; + +ProgramStateRef SMTConstraintManager::assumeSym(ProgramStateRef State, + SymbolRef Sym, + bool Assumption) { + ASTContext &Ctx = getBasicVals().getContext(); + + QualType RetTy; + bool hasComparison; + + SMTExprRef Exp = Solver->getExpr(Ctx, Sym, &RetTy, &hasComparison); + + // Create zero comparison for implicit boolean cast, with reversed assumption + if (!hasComparison && !RetTy->isBooleanType()) + return assumeExpr(State, Sym, + Solver->getZeroExpr(Ctx, Exp, RetTy, !Assumption)); + + return assumeExpr(State, Sym, Assumption ? Exp : Solver->mkNot(Exp)); +} + +ProgramStateRef SMTConstraintManager::assumeSymInclusiveRange( + ProgramStateRef State, SymbolRef Sym, const llvm::APSInt &From, + const llvm::APSInt &To, bool InRange) { + ASTContext &Ctx = getBasicVals().getContext(); + return assumeExpr(State, Sym, + Solver->getRangeExpr(Ctx, Sym, From, To, InRange)); +} + +ProgramStateRef +SMTConstraintManager::assumeSymUnsupported(ProgramStateRef State, SymbolRef Sym, + bool Assumption) { + // Skip anything that is unsupported + return State; +} + +ConditionTruthVal SMTConstraintManager::checkNull(ProgramStateRef State, + SymbolRef Sym) { + ASTContext &Ctx = getBasicVals().getContext(); + + QualType RetTy; + // The expression may be casted, so we cannot call getZ3DataExpr() directly + SMTExprRef VarExp = Solver->getExpr(Ctx, Sym, &RetTy); + SMTExprRef Exp = Solver->getZeroExpr(Ctx, VarExp, RetTy, /*Assumption=*/true); + + // Negate the constraint + SMTExprRef NotExp = + Solver->getZeroExpr(Ctx, VarExp, RetTy, /*Assumption=*/false); + + Solver->reset(); + addStateConstraints(State); + + Solver->push(); + Solver->addConstraint(Exp); + ConditionTruthVal isSat = Solver->check(); + + Solver->pop(); + Solver->addConstraint(NotExp); + ConditionTruthVal isNotSat = Solver->check(); + + // Zero is the only possible solution + if (isSat.isConstrainedTrue() && isNotSat.isConstrainedFalse()) + return true; + + // Zero is not a solution + if (isSat.isConstrainedFalse() && isNotSat.isConstrainedTrue()) + return false; + + // Zero may be a solution + return ConditionTruthVal(); +} + +const llvm::APSInt *SMTConstraintManager::getSymVal(ProgramStateRef State, + SymbolRef Sym) const { + BasicValueFactory &BVF = getBasicVals(); + ASTContext &Ctx = BVF.getContext(); + + if (const SymbolData *SD = dyn_cast<SymbolData>(Sym)) { + QualType Ty = Sym->getType(); + assert(!Ty->isRealFloatingType()); + llvm::APSInt Value(Ctx.getTypeSize(Ty), + !Ty->isSignedIntegerOrEnumerationType()); + + SMTExprRef Exp = + Solver->fromData(SD->getSymbolID(), Ty, Ctx.getTypeSize(Ty)); + + Solver->reset(); + addStateConstraints(State); + + // Constraints are unsatisfiable + ConditionTruthVal isSat = Solver->check(); + if (!isSat.isConstrainedTrue()) + return nullptr; + + // Model does not assign interpretation + if (!Solver->getInterpretation(Exp, Value)) + return nullptr; + + // A value has been obtained, check if it is the only value + SMTExprRef NotExp = Solver->fromBinOp( + Exp, BO_NE, + Ty->isBooleanType() ? Solver->fromBoolean(Value.getBoolValue()) + : Solver->fromAPSInt(Value), + false); + + Solver->addConstraint(NotExp); + + ConditionTruthVal isNotSat = Solver->check(); + if (isNotSat.isConstrainedTrue()) + return nullptr; + + // This is the only solution, store it + return &BVF.getValue(Value); + } + + if (const SymbolCast *SC = dyn_cast<SymbolCast>(Sym)) { + SymbolRef CastSym = SC->getOperand(); + QualType CastTy = SC->getType(); + // Skip the void type + if (CastTy->isVoidType()) + return nullptr; + + const llvm::APSInt *Value; + if (!(Value = getSymVal(State, CastSym))) + return nullptr; + return &BVF.Convert(SC->getType(), *Value); + } + + if (const BinarySymExpr *BSE = dyn_cast<BinarySymExpr>(Sym)) { + const llvm::APSInt *LHS, *RHS; + if (const SymIntExpr *SIE = dyn_cast<SymIntExpr>(BSE)) { + LHS = getSymVal(State, SIE->getLHS()); + RHS = &SIE->getRHS(); + } else if (const IntSymExpr *ISE = dyn_cast<IntSymExpr>(BSE)) { + LHS = &ISE->getLHS(); + RHS = getSymVal(State, ISE->getRHS()); + } else if (const SymSymExpr *SSM = dyn_cast<SymSymExpr>(BSE)) { + // Early termination to avoid expensive call + LHS = getSymVal(State, SSM->getLHS()); + RHS = LHS ? getSymVal(State, SSM->getRHS()) : nullptr; + } else { + llvm_unreachable("Unsupported binary expression to get symbol value!"); + } + + if (!LHS || !RHS) + return nullptr; + + llvm::APSInt ConvertedLHS, ConvertedRHS; + QualType LTy, RTy; + std::tie(ConvertedLHS, LTy) = Solver->fixAPSInt(Ctx, *LHS); + std::tie(ConvertedRHS, RTy) = Solver->fixAPSInt(Ctx, *RHS); + Solver->doIntTypeConversion<llvm::APSInt, &SMTSolver::castAPSInt>( + Ctx, ConvertedLHS, LTy, ConvertedRHS, RTy); + return BVF.evalAPSInt(BSE->getOpcode(), ConvertedLHS, ConvertedRHS); + } + + llvm_unreachable("Unsupported expression to get symbol value!"); +} + +ConditionTruthVal +SMTConstraintManager::checkModel(ProgramStateRef State, + const SMTExprRef &Exp) const { + Solver->reset(); + Solver->addConstraint(Exp); + addStateConstraints(State); + return Solver->check(); +} diff --git a/lib/StaticAnalyzer/Core/SValBuilder.cpp b/lib/StaticAnalyzer/Core/SValBuilder.cpp index 04452e3e7cc2..f292dca8e99f 100644 --- a/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -1,4 +1,4 @@ -// SValBuilder.cpp - Basic class for all SValBuilder implementations -*- C++ -*- +//===- SValBuilder.cpp - Basic class for all SValBuilder implementations --===// // // The LLVM Compiler Infrastructure // @@ -13,12 +13,33 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/ExprCXX.h" +#include "clang/AST/ExprObjC.h" +#include "clang/AST/Stmt.h" +#include "clang/AST/Type.h" +#include "clang/Basic/LLVM.h" +#include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/Store.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Compiler.h" +#include <cassert> +#include <tuple> using namespace clang; using namespace ento; @@ -27,7 +48,7 @@ using namespace ento; // Basic SVal creation. //===----------------------------------------------------------------------===// -void SValBuilder::anchor() { } +void SValBuilder::anchor() {} DefinedOrUnknownSVal SValBuilder::makeZeroVal(QualType type) { if (Loc::isLocType(type)) @@ -95,12 +116,12 @@ nonloc::ConcreteInt SValBuilder::makeBoolVal(const CXXBoolLiteralExpr *boolean){ } DefinedOrUnknownSVal -SValBuilder::getRegionValueSymbolVal(const TypedValueRegion* region) { +SValBuilder::getRegionValueSymbolVal(const TypedValueRegion *region) { QualType T = region->getValueType(); if (T->isNullPtrType()) return makeZeroVal(T); - + if (!SymbolManager::canSymbolicate(T)) return UnknownVal(); @@ -149,7 +170,6 @@ DefinedOrUnknownSVal SValBuilder::conjureSymbolVal(const void *symbolTag, return nonloc::SymbolVal(sym); } - DefinedOrUnknownSVal SValBuilder::conjureSymbolVal(const Stmt *stmt, const LocationContext *LCtx, QualType type, @@ -217,10 +237,10 @@ SValBuilder::getDerivedRegionValueSymbolVal(SymbolRef parentSymbol, return nonloc::SymbolVal(sym); } -DefinedSVal SValBuilder::getMemberPointer(const DeclaratorDecl* DD) { +DefinedSVal SValBuilder::getMemberPointer(const DeclaratorDecl *DD) { assert(!DD || isa<CXXMethodDecl>(DD) || isa<FieldDecl>(DD)); - if (auto *MD = dyn_cast_or_null<CXXMethodDecl>(DD)) { + if (const auto *MD = dyn_cast_or_null<CXXMethodDecl>(DD)) { // 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 @@ -277,19 +297,19 @@ Optional<SVal> SValBuilder::getConstantVal(const Expr *E) { return makeZeroVal(E->getType()); case Stmt::ObjCStringLiteralClass: { - const ObjCStringLiteral *SL = cast<ObjCStringLiteral>(E); + const auto *SL = cast<ObjCStringLiteral>(E); return makeLoc(getRegionManager().getObjCStringRegion(SL)); } case Stmt::StringLiteralClass: { - const StringLiteral *SL = cast<StringLiteral>(E); + const auto *SL = cast<StringLiteral>(E); return makeLoc(getRegionManager().getStringRegion(SL)); } // Fast-path some expressions to avoid the overhead of going through the AST's // constant evaluator case Stmt::CharacterLiteralClass: { - const CharacterLiteral *C = cast<CharacterLiteral>(E); + const auto *C = cast<CharacterLiteral>(E); return makeIntVal(C->getValue(), C->getType()); } @@ -297,7 +317,7 @@ Optional<SVal> SValBuilder::getConstantVal(const Expr *E) { return makeBoolVal(cast<CXXBoolLiteralExpr>(E)); case Stmt::TypeTraitExprClass: { - const TypeTraitExpr *TE = cast<TypeTraitExpr>(E); + const auto *TE = cast<TypeTraitExpr>(E); return makeTruthVal(TE->getValue(), TE->getType()); } @@ -310,12 +330,19 @@ Optional<SVal> SValBuilder::getConstantVal(const Expr *E) { case Stmt::CXXNullPtrLiteralExprClass: return makeNull(); + case Stmt::CStyleCastExprClass: + case Stmt::CXXFunctionalCastExprClass: + case Stmt::CXXConstCastExprClass: + case Stmt::CXXReinterpretCastExprClass: + case Stmt::CXXStaticCastExprClass: case Stmt::ImplicitCastExprClass: { - const CastExpr *CE = cast<CastExpr>(E); + const auto *CE = cast<CastExpr>(E); switch (CE->getCastKind()) { default: break; case CK_ArrayToPointerDecay: + case CK_IntegralToPointer: + case CK_NoOp: case CK_BitCast: { const Expr *SE = CE->getSubExpr(); Optional<SVal> Val = getConstantVal(SE); @@ -348,20 +375,18 @@ Optional<SVal> SValBuilder::getConstantVal(const Expr *E) { } } -//===----------------------------------------------------------------------===// - SVal SValBuilder::makeSymExprValNN(ProgramStateRef State, BinaryOperator::Opcode Op, NonLoc LHS, NonLoc RHS, QualType ResultTy) { - if (!State->isTainted(RHS) && !State->isTainted(LHS)) - return UnknownVal(); - const SymExpr *symLHS = LHS.getAsSymExpr(); const SymExpr *symRHS = RHS.getAsSymExpr(); + // 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. - const unsigned MaxComp = 10000; // 100000 28X + const unsigned MaxComp = StateMgr.getOwningEngine() + ->getAnalysisManager() + .options.getMaxSymbolComplexity(); if (symLHS && symRHS && (symLHS->computeComplexity() + symRHS->computeComplexity()) < MaxComp) @@ -378,10 +403,8 @@ SVal SValBuilder::makeSymExprValNN(ProgramStateRef State, return UnknownVal(); } - SVal SValBuilder::evalBinOp(ProgramStateRef state, BinaryOperator::Opcode op, SVal lhs, SVal rhs, QualType type) { - if (lhs.isUndef() || rhs.isUndef()) return UndefinedVal(); @@ -413,10 +436,19 @@ SVal SValBuilder::evalBinOp(ProgramStateRef state, BinaryOperator::Opcode op, type); } +ConditionTruthVal SValBuilder::areEqual(ProgramStateRef state, SVal lhs, + SVal rhs) { + return state->isNonNull(evalEQ(state, lhs, rhs)); +} + +SVal SValBuilder::evalEQ(ProgramStateRef state, SVal lhs, SVal rhs) { + return evalBinOp(state, BO_EQ, lhs, rhs, getConditionType()); +} + DefinedOrUnknownSVal SValBuilder::evalEQ(ProgramStateRef state, DefinedOrUnknownSVal lhs, DefinedOrUnknownSVal rhs) { - return evalBinOp(state, BO_EQ, lhs, rhs, getConditionType()) + return evalEQ(state, static_cast<SVal>(lhs), static_cast<SVal>(rhs)) .castAs<DefinedOrUnknownSVal>(); } @@ -425,7 +457,7 @@ DefinedOrUnknownSVal SValBuilder::evalEQ(ProgramStateRef state, /// Assumes the input types are canonical. static bool shouldBeModeledWithNoOp(ASTContext &Context, QualType ToTy, QualType FromTy) { - while (Context.UnwrapSimilarPointerTypes(ToTy, FromTy)) { + while (Context.UnwrapSimilarTypes(ToTy, FromTy)) { Qualifiers Quals1, Quals2; ToTy = Context.getUnqualifiedArrayType(ToTy, Quals1); FromTy = Context.getUnqualifiedArrayType(FromTy, Quals2); @@ -440,6 +472,10 @@ static bool shouldBeModeledWithNoOp(ASTContext &Context, QualType ToTy, // If we are casting to void, the 'From' value can be used to represent the // 'To' value. + // + // FIXME: Doing this after unwrapping the types doesn't make any sense. A + // cast from 'int**' to 'void**' is not special in the way that a cast from + // 'int*' to 'void*' is. if (ToTy->isVoidType()) return true; @@ -454,7 +490,6 @@ static bool shouldBeModeledWithNoOp(ASTContext &Context, QualType ToTy, // of the original value is known to be greater than the max of the target type. SVal SValBuilder::evalIntegralCast(ProgramStateRef state, SVal val, QualType castTy, QualType originalTy) { - // No truncations if target type is big enough. if (getContext().getTypeSize(castTy) >= getContext().getTypeSize(originalTy)) return evalCast(val, castTy, originalTy); @@ -548,8 +583,8 @@ SVal SValBuilder::evalCast(SVal val, QualType castTy, QualType originalTy) { } // Check for casts from array type to another type. - if (const ArrayType *arrayT = - dyn_cast<ArrayType>(originalTy.getCanonicalType())) { + if (const auto *arrayT = + dyn_cast<ArrayType>(originalTy.getCanonicalType())) { // We will always decay to a pointer. QualType elemTy = arrayT->getElementType(); val = StateMgr.ArrayToPointer(val.castAs<Loc>(), elemTy); diff --git a/lib/StaticAnalyzer/Core/SVals.cpp b/lib/StaticAnalyzer/Core/SVals.cpp index a83421426a13..559ca2c9840d 100644 --- a/lib/StaticAnalyzer/Core/SVals.cpp +++ b/lib/StaticAnalyzer/Core/SVals.cpp @@ -1,4 +1,4 @@ -//= RValues.cpp - Abstract RValues for Path-Sens. Value Tracking -*- C++ -*-==// +//===- RValues.cpp - Abstract RValues for Path-Sens. Value Tracking -------===// // // The LLVM Compiler Infrastructure // @@ -12,20 +12,31 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" -#include "clang/AST/ExprObjC.h" -#include "clang/Basic/IdentifierTable.h" -#include "llvm/Support/raw_ostream.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" +#include "clang/AST/Expr.h" +#include "clang/AST/Type.h" +#include "clang/Basic/LLVM.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include "llvm/ADT/Optional.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/raw_ostream.h" +#include <cassert> + using namespace clang; using namespace ento; -using llvm::APSInt; //===----------------------------------------------------------------------===// // Symbol iteration within an SVal. //===----------------------------------------------------------------------===// - //===----------------------------------------------------------------------===// // Utility methods. //===----------------------------------------------------------------------===// @@ -39,7 +50,7 @@ bool SVal::hasConjuredSymbol() const { if (Optional<loc::MemRegionVal> RV = getAs<loc::MemRegionVal>()) { const MemRegion *R = RV->getRegion(); - if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(R)) { + if (const auto *SR = dyn_cast<SymbolicRegion>(R)) { SymbolRef sym = SR->getSymbol(); if (isa<SymbolConjured>(sym)) return true; @@ -53,18 +64,18 @@ const FunctionDecl *SVal::getAsFunctionDecl() const { if (Optional<loc::MemRegionVal> X = getAs<loc::MemRegionVal>()) { const MemRegion* R = X->getRegion(); if (const FunctionCodeRegion *CTR = R->getAs<FunctionCodeRegion>()) - if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(CTR->getDecl())) + if (const auto *FD = dyn_cast<FunctionDecl>(CTR->getDecl())) return FD; } if (auto X = getAs<nonloc::PointerToMember>()) { - if (const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(X->getDecl())) + if (const auto *MD = dyn_cast_or_null<CXXMethodDecl>(X->getDecl())) return MD; } return nullptr; } -/// \brief If this SVal is a location (subclasses Loc) and wraps a symbol, +/// If this SVal is a location (subclasses Loc) and wraps a symbol, /// return that SymbolRef. Otherwise return 0. /// /// Implicit casts (ex: void* -> char*) can turn Symbolic region into Element @@ -95,8 +106,8 @@ SymbolRef SVal::getLocSymbolInBase() const { const MemRegion *R = X->getRegion(); - while (const SubRegion *SR = dyn_cast<SubRegion>(R)) { - if (const SymbolicRegion *SymR = dyn_cast<SymbolicRegion>(SR)) + while (const auto *SR = dyn_cast<SubRegion>(R)) { + if (const auto *SymR = dyn_cast<SymbolicRegion>(SR)) return SymR->getSymbol(); else R = SR->getSuperRegion(); @@ -107,7 +118,7 @@ SymbolRef SVal::getLocSymbolInBase() const { // TODO: The next 3 functions have to be simplified. -/// \brief If this SVal wraps a symbol return that SymbolRef. +/// If this SVal wraps a symbol return that SymbolRef. /// Otherwise, return 0. /// /// Casts are ignored during lookup. @@ -189,14 +200,14 @@ nonloc::CompoundVal::iterator nonloc::CompoundVal::end() const { nonloc::PointerToMember::iterator nonloc::PointerToMember::begin() const { const PTMDataType PTMD = getPTMData(); if (PTMD.is<const DeclaratorDecl *>()) - return nonloc::PointerToMember::iterator(); + return {}; return PTMD.get<const PointerToMemberData *>()->begin(); } nonloc::PointerToMember::iterator nonloc::PointerToMember::end() const { const PTMDataType PTMD = getPTMData(); if (PTMD.is<const DeclaratorDecl *>()) - return nonloc::PointerToMember::iterator(); + return {}; return PTMD.get<const PointerToMemberData *>()->end(); } @@ -220,7 +231,6 @@ bool SVal::isZeroConstant() const { return isConstant(0); } - //===----------------------------------------------------------------------===// // Transfer function dispatch for Non-Locs. //===----------------------------------------------------------------------===// @@ -254,7 +264,6 @@ nonloc::ConcreteInt::evalMinus(SValBuilder &svalBuilder) const { SVal loc::ConcreteInt::evalBinOp(BasicValueFactory& BasicVals, BinaryOperator::Opcode Op, const loc::ConcreteInt& R) const { - assert(BinaryOperator::isComparisonOp(Op) || Op == BO_Sub); const llvm::APSInt *X = BasicVals.evalAPSInt(Op, getValue(), R.getValue()); @@ -291,19 +300,15 @@ void SVal::dumpToStream(raw_ostream &os) const { void NonLoc::dumpToStream(raw_ostream &os) const { switch (getSubKind()) { case nonloc::ConcreteIntKind: { - const nonloc::ConcreteInt& C = castAs<nonloc::ConcreteInt>(); - if (C.getValue().isUnsigned()) - os << C.getValue().getZExtValue(); - else - os << C.getValue().getSExtValue(); - os << ' ' << (C.getValue().isUnsigned() ? 'U' : 'S') - << C.getValue().getBitWidth() << 'b'; + const auto &Value = castAs<nonloc::ConcreteInt>().getValue(); + os << Value << ' ' << (Value.isSigned() ? 'S' : 'U') + << Value.getBitWidth() << 'b'; break; } - case nonloc::SymbolValKind: { + case nonloc::SymbolValKind: os << castAs<nonloc::SymbolVal>().getSymbol(); break; - } + case nonloc::LocAsIntegerKind: { const nonloc::LocAsInteger& C = castAs<nonloc::LocAsInteger>(); os << C.getLoc() << " [as " << C.getNumBits() << " bit integer]"; @@ -313,14 +318,14 @@ void NonLoc::dumpToStream(raw_ostream &os) const { const nonloc::CompoundVal& C = castAs<nonloc::CompoundVal>(); os << "compoundVal{"; bool first = true; - for (nonloc::CompoundVal::iterator I=C.begin(), E=C.end(); I!=E; ++I) { + for (const auto &I : C) { if (first) { os << ' '; first = false; } else os << ", "; - (*I).dumpToStream(os); + I.dumpToStream(os); } os << "}"; break; @@ -353,7 +358,7 @@ void NonLoc::dumpToStream(raw_ostream &os) const { break; } default: - assert (false && "Pretty-printed not implemented for this NonLoc."); + assert(false && "Pretty-printed not implemented for this NonLoc."); break; } } diff --git a/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp index 94d29d5a6ba3..beae0dfae289 100644 --- a/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp +++ b/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp @@ -12,8 +12,10 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SValVisitor.h" using namespace clang; @@ -157,7 +159,8 @@ SVal SimpleSValBuilder::evalCastFromLoc(Loc val, QualType castTy) { return nonloc::SymbolVal(SymMgr.getExtentSymbol(FTR)); if (const SymbolicRegion *SymR = R->getSymbolicBase()) - return nonloc::SymbolVal(SymR->getSymbol()); + return makeNonLoc(SymR->getSymbol(), BO_NE, + BasicVals.getZeroWithPtrWidth(), castTy); // FALL-THROUGH LLVM_FALLTHROUGH; @@ -307,6 +310,197 @@ SVal SimpleSValBuilder::MakeSymIntVal(const SymExpr *LHS, return makeNonLoc(LHS, op, *ConvertedRHS, resultTy); } +// See if Sym is known to be a relation Rel with Bound. +static bool isInRelation(BinaryOperator::Opcode Rel, SymbolRef Sym, + llvm::APSInt Bound, ProgramStateRef State) { + SValBuilder &SVB = State->getStateManager().getSValBuilder(); + SVal Result = + SVB.evalBinOpNN(State, Rel, nonloc::SymbolVal(Sym), + nonloc::ConcreteInt(Bound), SVB.getConditionType()); + if (auto DV = Result.getAs<DefinedSVal>()) { + return !State->assume(*DV, false); + } + return false; +} + +// See if Sym is known to be within [min/4, max/4], where min and max +// are the bounds of the symbol's integral type. With such symbols, +// some manipulations can be performed without the risk of overflow. +// assume() doesn't cause infinite recursion because we should be dealing +// with simpler symbols on every recursive call. +static bool isWithinConstantOverflowBounds(SymbolRef Sym, + ProgramStateRef State) { + SValBuilder &SVB = State->getStateManager().getSValBuilder(); + BasicValueFactory &BV = SVB.getBasicValueFactory(); + + QualType T = Sym->getType(); + assert(T->isSignedIntegerOrEnumerationType() && + "This only works with signed integers!"); + APSIntType AT = BV.getAPSIntType(T); + + llvm::APSInt Max = AT.getMaxValue() / AT.getValue(4), Min = -Max; + return isInRelation(BO_LE, Sym, Max, State) && + isInRelation(BO_GE, Sym, Min, State); +} + +// Same for the concrete integers: see if I is within [min/4, max/4]. +static bool isWithinConstantOverflowBounds(llvm::APSInt I) { + APSIntType AT(I); + assert(!AT.isUnsigned() && + "This only works with signed integers!"); + + llvm::APSInt Max = AT.getMaxValue() / AT.getValue(4), Min = -Max; + return (I <= Max) && (I >= -Max); +} + +static std::pair<SymbolRef, llvm::APSInt> +decomposeSymbol(SymbolRef Sym, BasicValueFactory &BV) { + if (const auto *SymInt = dyn_cast<SymIntExpr>(Sym)) + if (BinaryOperator::isAdditiveOp(SymInt->getOpcode())) + return std::make_pair(SymInt->getLHS(), + (SymInt->getOpcode() == BO_Add) ? + (SymInt->getRHS()) : + (-SymInt->getRHS())); + + // Fail to decompose: "reduce" the problem to the "$x + 0" case. + return std::make_pair(Sym, BV.getValue(0, Sym->getType())); +} + +// Simplify "(LSym + LInt) Op (RSym + RInt)" assuming all values are of the +// same signed integral type and no overflows occur (which should be checked +// by the caller). +static NonLoc doRearrangeUnchecked(ProgramStateRef State, + BinaryOperator::Opcode Op, + SymbolRef LSym, llvm::APSInt LInt, + SymbolRef RSym, llvm::APSInt RInt) { + SValBuilder &SVB = State->getStateManager().getSValBuilder(); + BasicValueFactory &BV = SVB.getBasicValueFactory(); + SymbolManager &SymMgr = SVB.getSymbolManager(); + + QualType SymTy = LSym->getType(); + assert(SymTy == RSym->getType() && + "Symbols are not of the same type!"); + assert(APSIntType(LInt) == BV.getAPSIntType(SymTy) && + "Integers are not of the same type as symbols!"); + assert(APSIntType(RInt) == BV.getAPSIntType(SymTy) && + "Integers are not of the same type as symbols!"); + + QualType ResultTy; + if (BinaryOperator::isComparisonOp(Op)) + ResultTy = SVB.getConditionType(); + else if (BinaryOperator::isAdditiveOp(Op)) + ResultTy = SymTy; + else + llvm_unreachable("Operation not suitable for unchecked rearrangement!"); + + // FIXME: Can we use assume() without getting into an infinite recursion? + if (LSym == RSym) + return SVB.evalBinOpNN(State, Op, nonloc::ConcreteInt(LInt), + nonloc::ConcreteInt(RInt), ResultTy) + .castAs<NonLoc>(); + + SymbolRef ResultSym = nullptr; + BinaryOperator::Opcode ResultOp; + llvm::APSInt ResultInt; + if (BinaryOperator::isComparisonOp(Op)) { + // Prefer comparing to a non-negative number. + // FIXME: Maybe it'd be better to have consistency in + // "$x - $y" vs. "$y - $x" because those are solver's keys. + if (LInt > RInt) { + ResultSym = SymMgr.getSymSymExpr(RSym, BO_Sub, LSym, SymTy); + ResultOp = BinaryOperator::reverseComparisonOp(Op); + ResultInt = LInt - RInt; // Opposite order! + } else { + ResultSym = SymMgr.getSymSymExpr(LSym, BO_Sub, RSym, SymTy); + ResultOp = Op; + ResultInt = RInt - LInt; // Opposite order! + } + } else { + ResultSym = SymMgr.getSymSymExpr(LSym, Op, RSym, SymTy); + ResultInt = (Op == BO_Add) ? (LInt + RInt) : (LInt - RInt); + ResultOp = BO_Add; + // Bring back the cosmetic difference. + if (ResultInt < 0) { + ResultInt = -ResultInt; + ResultOp = BO_Sub; + } else if (ResultInt == 0) { + // Shortcut: Simplify "$x + 0" to "$x". + return nonloc::SymbolVal(ResultSym); + } + } + const llvm::APSInt &PersistentResultInt = BV.getValue(ResultInt); + return nonloc::SymbolVal( + SymMgr.getSymIntExpr(ResultSym, ResultOp, PersistentResultInt, ResultTy)); +} + +// Rearrange if symbol type matches the result type and if the operator is a +// comparison operator, both symbol and constant must be within constant +// overflow bounds. +static bool shouldRearrange(ProgramStateRef State, BinaryOperator::Opcode Op, + SymbolRef Sym, llvm::APSInt Int, QualType Ty) { + return Sym->getType() == Ty && + (!BinaryOperator::isComparisonOp(Op) || + (isWithinConstantOverflowBounds(Sym, State) && + isWithinConstantOverflowBounds(Int))); +} + +static Optional<NonLoc> tryRearrange(ProgramStateRef State, + BinaryOperator::Opcode Op, NonLoc Lhs, + NonLoc Rhs, QualType ResultTy) { + ProgramStateManager &StateMgr = State->getStateManager(); + SValBuilder &SVB = StateMgr.getSValBuilder(); + + // We expect everything to be of the same type - this type. + QualType SingleTy; + + auto &Opts = + StateMgr.getOwningEngine()->getAnalysisManager().getAnalyzerOptions(); + + // FIXME: After putting complexity threshold to the symbols we can always + // rearrange additive operations but rearrange comparisons only if + // option is set. + if(!Opts.shouldAggressivelySimplifyBinaryOperation()) + return None; + + SymbolRef LSym = Lhs.getAsSymbol(); + if (!LSym) + return None; + + if (BinaryOperator::isComparisonOp(Op)) { + SingleTy = LSym->getType(); + if (ResultTy != SVB.getConditionType()) + return None; + // Initialize SingleTy later with a symbol's type. + } else if (BinaryOperator::isAdditiveOp(Op)) { + SingleTy = ResultTy; + if (LSym->getType() != SingleTy) + return None; + // Substracting unsigned integers is a nightmare. + if (!SingleTy->isSignedIntegerOrEnumerationType()) + return None; + } else { + // Don't rearrange other operations. + return None; + } + + assert(!SingleTy.isNull() && "We should have figured out the type by now!"); + + SymbolRef RSym = Rhs.getAsSymbol(); + if (!RSym || RSym->getType() != SingleTy) + return None; + + BasicValueFactory &BV = State->getBasicVals(); + llvm::APSInt LInt, RInt; + std::tie(LSym, LInt) = decomposeSymbol(LSym, BV); + std::tie(RSym, RInt) = decomposeSymbol(RSym, BV); + if (!shouldRearrange(State, Op, LSym, LInt, SingleTy) || + !shouldRearrange(State, Op, RSym, RInt, SingleTy)) + return None; + + // We know that no overflows can occur anymore. + return doRearrangeUnchecked(State, Op, LSym, LInt, RSym, RInt); +} + SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, BinaryOperator::Opcode op, NonLoc lhs, NonLoc rhs, @@ -559,6 +753,9 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, if (const llvm::APSInt *RHSValue = getKnownValue(state, rhs)) return MakeSymIntVal(Sym, op, *RHSValue, resultTy); + if (Optional<NonLoc> V = tryRearrange(state, op, lhs, rhs, resultTy)) + return *V; + // Give up -- this is not a symbolic expression we can handle. return makeSymExprValNN(state, op, InputLHS, InputRHS, resultTy); } @@ -988,6 +1185,12 @@ SVal SimpleSValBuilder::evalBinOpLN(ProgramStateRef state, elementType = resultTy->getPointeeType(); } + // Represent arithmetic on void pointers as arithmetic on char pointers. + // It is fine when a TypedValueRegion of char value type represents + // a void pointer. Note that arithmetic on void pointers is a GCC extension. + if (elementType->isVoidType()) + elementType = getContext().CharTy; + if (Optional<NonLoc> indexV = index.getAs<NonLoc>()) { return loc::MemRegionVal(MemMgr.getElementRegion(elementType, *indexV, superR, getContext())); @@ -1023,24 +1226,42 @@ SVal SimpleSValBuilder::simplifySVal(ProgramStateRef State, SVal V) { ProgramStateRef State; SValBuilder &SVB; + // Cache results for the lifetime of the Simplifier. Results change every + // time new constraints are added to the program state, which is the whole + // point of simplifying, and for that very reason it's pointless to maintain + // the same cache for the duration of the whole analysis. + llvm::DenseMap<SymbolRef, SVal> Cached; + + static bool isUnchanged(SymbolRef Sym, SVal Val) { + return Sym == Val.getAsSymbol(); + } + public: Simplifier(ProgramStateRef State) : State(State), SVB(State->getStateManager().getSValBuilder()) {} SVal VisitSymbolData(const SymbolData *S) { if (const llvm::APSInt *I = - SVB.getKnownValue(State, nonloc::SymbolVal(S))) + SVB.getKnownValue(State, SVB.makeSymbolVal(S))) return Loc::isLocType(S->getType()) ? (SVal)SVB.makeIntLocVal(*I) : (SVal)SVB.makeIntVal(*I); - return Loc::isLocType(S->getType()) ? (SVal)SVB.makeLoc(S) - : nonloc::SymbolVal(S); + return SVB.makeSymbolVal(S); } // TODO: Support SymbolCast. Support IntSymExpr when/if we actually // start producing them. SVal VisitSymIntExpr(const SymIntExpr *S) { + auto I = Cached.find(S); + if (I != Cached.end()) + return I->second; + SVal LHS = Visit(S->getLHS()); + if (isUnchanged(S->getLHS(), LHS)) { + SVal V = SVB.makeSymbolVal(S); + Cached[S] = V; + return V; + } SVal RHS; // By looking at the APSInt in the right-hand side of S, we cannot // figure out if it should be treated as a Loc or as a NonLoc. @@ -1059,13 +1280,27 @@ SVal SimpleSValBuilder::simplifySVal(ProgramStateRef State, SVal V) { } else { RHS = SVB.makeIntVal(S->getRHS()); } - return SVB.evalBinOp(State, S->getOpcode(), LHS, RHS, S->getType()); + + SVal V = SVB.evalBinOp(State, S->getOpcode(), LHS, RHS, S->getType()); + Cached[S] = V; + return V; } SVal VisitSymSymExpr(const SymSymExpr *S) { + auto I = Cached.find(S); + if (I != Cached.end()) + return I->second; + SVal LHS = Visit(S->getLHS()); SVal RHS = Visit(S->getRHS()); - return SVB.evalBinOp(State, S->getOpcode(), LHS, RHS, S->getType()); + if (isUnchanged(S->getLHS(), LHS) && isUnchanged(S->getRHS(), RHS)) { + SVal V = SVB.makeSymbolVal(S); + Cached[S] = V; + return V; + } + SVal V = SVB.evalBinOp(State, S->getOpcode(), LHS, RHS, S->getType()); + Cached[S] = V; + return V; } SVal VisitSymExpr(SymbolRef S) { return nonloc::SymbolVal(S); } @@ -1075,13 +1310,20 @@ SVal SimpleSValBuilder::simplifySVal(ProgramStateRef State, SVal V) { SVal VisitNonLocSymbolVal(nonloc::SymbolVal V) { // Simplification is much more costly than computing complexity. // For high complexity, it may be not worth it. - if (V.getSymbol()->computeComplexity() > 100) - return V; return Visit(V.getSymbol()); } SVal VisitSVal(SVal V) { return V; } }; - return Simplifier(State).Visit(V); + // A crude way of preventing this function from calling itself from evalBinOp. + static bool isReentering = false; + if (isReentering) + return V; + + isReentering = true; + SVal SimplifiedV = Simplifier(State).Visit(V); + isReentering = false; + + return SimplifiedV; } diff --git a/lib/StaticAnalyzer/Core/Store.cpp b/lib/StaticAnalyzer/Core/Store.cpp index 173fdd8d0056..5ab5c082269b 100644 --- a/lib/StaticAnalyzer/Core/Store.cpp +++ b/lib/StaticAnalyzer/Core/Store.cpp @@ -1,4 +1,4 @@ -//== Store.cpp - Interface for maps from Locations to Values ----*- C++ -*--==// +//===- Store.cpp - Interface for maps from Locations to Values ------------===// // // The LLVM Compiler Infrastructure // @@ -12,18 +12,37 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/PathSensitive/Store.h" +#include "clang/AST/ASTContext.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/CharUnits.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" +#include "clang/AST/Expr.h" +#include "clang/AST/Type.h" +#include "clang/Basic/LLVM.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/StoreRef.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" +#include <cassert> +#include <cstdint> using namespace clang; using namespace ento; StoreManager::StoreManager(ProgramStateManager &stateMgr) - : svalBuilder(stateMgr.getSValBuilder()), StateMgr(stateMgr), - MRMgr(svalBuilder.getRegionManager()), Ctx(stateMgr.getContext()) {} + : svalBuilder(stateMgr.getSValBuilder()), StateMgr(stateMgr), + MRMgr(svalBuilder.getRegionManager()), Ctx(stateMgr.getContext()) {} StoreRef StoreManager::enterStackFrame(Store OldStore, const CallEvent &Call, @@ -33,11 +52,8 @@ StoreRef StoreManager::enterStackFrame(Store OldStore, SmallVector<CallEvent::FrameBindingTy, 16> InitialBindings; Call.getInitialStackFrameContents(LCtx, InitialBindings); - for (CallEvent::BindingsTy::iterator I = InitialBindings.begin(), - E = InitialBindings.end(); - I != E; ++I) { - Store = Bind(Store.getStore(), I->first, I->second); - } + for (const auto &I : InitialBindings) + Store = Bind(Store.getStore(), I.first, I.second); return Store; } @@ -49,10 +65,6 @@ const ElementRegion *StoreManager::MakeElementRegion(const SubRegion *Base, return MRMgr.getElementRegion(EleTy, idx, Base, svalBuilder.getContext()); } -StoreRef StoreManager::BindDefault(Store store, const MemRegion *R, SVal V) { - return StoreRef(store, *this); -} - const ElementRegion *StoreManager::GetElementZeroRegion(const SubRegion *R, QualType T) { NonLoc idx = svalBuilder.makeZeroArrayIndex(); @@ -61,7 +73,6 @@ const ElementRegion *StoreManager::GetElementZeroRegion(const SubRegion *R, } const MemRegion *StoreManager::castRegion(const MemRegion *R, QualType CastToTy) { - ASTContext &Ctx = StateMgr.getContext(); // Handle casts to Objective-C objects. @@ -92,7 +103,7 @@ const MemRegion *StoreManager::castRegion(const MemRegion *R, QualType CastToTy) // Handle casts from compatible types. if (R->isBoundable()) - if (const TypedValueRegion *TR = dyn_cast<TypedValueRegion>(R)) { + if (const auto *TR = dyn_cast<TypedValueRegion>(R)) { QualType ObjTy = Ctx.getCanonicalType(TR->getValueType()); if (CanonPointeeTy == ObjTy) return R; @@ -164,7 +175,7 @@ const MemRegion *StoreManager::castRegion(const MemRegion *R, QualType CastToTy) // Edge case: we are at 0 bytes off the beginning of baseR. We // check to see if type we are casting to is the same as the base // region. If so, just return the base region. - if (const TypedValueRegion *TR = dyn_cast<TypedValueRegion>(baseR)) { + if (const auto *TR = dyn_cast<TypedValueRegion>(baseR)) { QualType ObjTy = Ctx.getCanonicalType(TR->getValueType()); QualType CanonPointeeTy = Ctx.getCanonicalType(PointeeTy); if (CanonPointeeTy == ObjTy) @@ -219,7 +230,7 @@ static bool regionMatchesCXXRecordType(SVal V, QualType Ty) { if (!MR) return true; - const TypedValueRegion *TVR = dyn_cast<TypedValueRegion>(MR); + const auto *TVR = dyn_cast<TypedValueRegion>(MR); if (!TVR) return true; @@ -253,11 +264,9 @@ SVal StoreManager::evalDerivedToBase(SVal Derived, const CastExpr *Cast) { SVal StoreManager::evalDerivedToBase(SVal Derived, const CXXBasePath &Path) { // Walk through the path to create nested CXXBaseRegions. SVal Result = Derived; - for (CXXBasePath::const_iterator I = Path.begin(), E = Path.end(); - I != E; ++I) { - Result = evalDerivedToBase(Result, I->Base->getType(), - I->Base->isVirtual()); - } + for (const auto &I : Path) + Result = evalDerivedToBase(Result, I.Base->getType(), + I.Base->isVirtual()); return Result; } @@ -286,9 +295,9 @@ SVal StoreManager::evalDerivedToBase(SVal Derived, QualType BaseType, /// symbolic regions, where the dynamic type is merely bounded (and even then, /// only ostensibly!), but does not take advantage of any dynamic type info. static const CXXRecordDecl *getCXXRecordType(const MemRegion *MR) { - if (const TypedValueRegion *TVR = dyn_cast<TypedValueRegion>(MR)) + if (const auto *TVR = dyn_cast<TypedValueRegion>(MR)) return TVR->getValueType()->getAsCXXRecordDecl(); - if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(MR)) + if (const auto *SR = dyn_cast<SymbolicRegion>(MR)) return SR->getSymbol()->getType()->getPointeeCXXRecordDecl(); return nullptr; } @@ -327,7 +336,7 @@ SVal StoreManager::attemptDownCast(SVal Base, QualType TargetType, return evalDerivedToBase(loc::MemRegionVal(MR), Paths.front()); } - if (const CXXBaseObjectRegion *BaseR = dyn_cast<CXXBaseObjectRegion>(MR)) { + if (const auto *BaseR = dyn_cast<CXXBaseObjectRegion>(MR)) { // Drill down the chain to get the derived classes. MR = BaseR->getSuperRegion(); continue; @@ -348,7 +357,7 @@ SVal StoreManager::attemptDownCast(SVal Base, QualType TargetType, const MemRegion *Uncasted = MR->StripCasts(/*IncludeBaseCasts=*/false); if (Uncasted == MR) { // We reached the bottom of the hierarchy and did not find the derived - // class. We we must be casting the base to derived, so the cast should + // class. We must be casting the base to derived, so the cast should // fail. break; } @@ -361,27 +370,27 @@ SVal StoreManager::attemptDownCast(SVal Base, QualType TargetType, return UnknownVal(); } - /// CastRetrievedVal - Used by subclasses of StoreManager to implement /// implicit casts that arise from loads from regions that are reinterpreted /// as another region. SVal StoreManager::CastRetrievedVal(SVal V, const TypedValueRegion *R, - QualType castTy, bool performTestOnly) { - + QualType castTy) { if (castTy.isNull() || V.isUnknownOrUndef()) return V; - ASTContext &Ctx = svalBuilder.getContext(); - - if (performTestOnly) { - // Automatically translate references to pointers. - QualType T = R->getValueType(); - if (const ReferenceType *RT = T->getAs<ReferenceType>()) - T = Ctx.getPointerType(RT->getPointeeType()); - - assert(svalBuilder.getContext().hasSameUnqualifiedType(castTy, T)); - return V; - } + // When retrieving symbolic pointer and expecting a non-void pointer, + // wrap them into element regions of the expected type if necessary. + // SValBuilder::dispatchCast() doesn't do that, but it is necessary to + // make sure that the retrieved value makes sense, because there's no other + // cast in the AST that would tell us to cast it to the correct pointer type. + // We might need to do that for non-void pointers as well. + // FIXME: We really need a single good function to perform casts for us + // correctly every time we need it. + if (castTy->isPointerType() && !castTy->isVoidPointerType()) + if (const auto *SR = dyn_cast_or_null<SymbolicRegion>(V.getAsRegion())) + if (SR->getSymbol()->getType().getCanonicalType() != + castTy.getCanonicalType()) + return loc::MemRegionVal(castRegion(SR, castTy)); return svalBuilder.dispatchCast(V, castTy); } @@ -421,7 +430,7 @@ SVal StoreManager::getLValueFieldOrIvar(const Decl *D, SVal Base) { // NOTE: We must have this check first because ObjCIvarDecl is a subclass // of FieldDecl. - if (const ObjCIvarDecl *ID = dyn_cast<ObjCIvarDecl>(D)) + if (const auto *ID = dyn_cast<ObjCIvarDecl>(D)) return loc::MemRegionVal(MRMgr.getObjCIvarRegion(ID, BaseR)); return loc::MemRegionVal(MRMgr.getFieldRegion(cast<FieldDecl>(D), BaseR)); @@ -433,7 +442,6 @@ SVal StoreManager::getLValueIvar(const ObjCIvarDecl *decl, SVal base) { SVal StoreManager::getLValueElement(QualType elementType, NonLoc Offset, SVal Base) { - // If the base is an unknown or undefined value, just return it back. // FIXME: For absolute pointer addresses, we just return that value back as // well, although in reality we should return the offset added to that @@ -448,13 +456,12 @@ SVal StoreManager::getLValueElement(QualType elementType, NonLoc Offset, Base.castAs<loc::MemRegionVal>().getRegionAs<SubRegion>(); // Pointer of any type can be cast and used as array base. - const ElementRegion *ElemR = dyn_cast<ElementRegion>(BaseRegion); + const auto *ElemR = dyn_cast<ElementRegion>(BaseRegion); // Convert the offset to the appropriate size and signedness. Offset = svalBuilder.convertToArrayIndex(Offset).castAs<NonLoc>(); if (!ElemR) { - // // If the base region is not an ElementRegion, create one. // This can happen in the following example: // @@ -462,7 +469,6 @@ SVal StoreManager::getLValueElement(QualType elementType, NonLoc Offset, // p[1] = 8; // // Observe that 'p' binds to an AllocaRegion. - // return loc::MemRegionVal(MRMgr.getElementRegion(elementType, Offset, BaseRegion, Ctx)); } @@ -499,7 +505,7 @@ SVal StoreManager::getLValueElement(QualType elementType, NonLoc Offset, Ctx)); } -StoreManager::BindingsHandler::~BindingsHandler() {} +StoreManager::BindingsHandler::~BindingsHandler() = default; bool StoreManager::FindUniqueBinding::HandleBinding(StoreManager& SMgr, Store store, diff --git a/lib/StaticAnalyzer/Core/SymbolManager.cpp b/lib/StaticAnalyzer/Core/SymbolManager.cpp index f2d5ee83f3cc..ed197010ebb7 100644 --- a/lib/StaticAnalyzer/Core/SymbolManager.cpp +++ b/lib/StaticAnalyzer/Core/SymbolManager.cpp @@ -1,4 +1,4 @@ -//== SymbolManager.h - Management of Symbolic Values ------------*- C++ -*--==// +//===- SymbolManager.h - Management of Symbolic Values --------------------===// // // The LLVM Compiler Infrastructure // @@ -13,15 +13,27 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" #include "clang/Analysis/Analyses/LiveVariables.h" +#include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/Basic/LLVM.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/Store.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" +#include <cassert> using namespace clang; using namespace ento; -void SymExpr::anchor() { } +void SymExpr::anchor() {} LLVM_DUMP_METHOD void SymExpr::dump() const { dumpToStream(llvm::errs()); @@ -88,7 +100,7 @@ void SymbolMetadata::dumpToStream(raw_ostream &os) const { << getRegion() << ',' << T.getAsString() << '}'; } -void SymbolData::anchor() { } +void SymbolData::anchor() {} void SymbolRegionValue::dumpToStream(raw_ostream &os) const { os << "reg_$" << getSymbolID() @@ -138,7 +150,7 @@ void SymExpr::symbol_iterator::expand() { itr.push_back(cast<IntSymExpr>(SE)->getRHS()); return; case SymExpr::SymSymExprKind: { - const SymSymExpr *x = cast<SymSymExpr>(SE); + const auto *x = cast<SymSymExpr>(SE); itr.push_back(x->getLHS()); itr.push_back(x->getRHS()); return; @@ -147,13 +159,6 @@ void SymExpr::symbol_iterator::expand() { llvm_unreachable("unhandled expansion case"); } -unsigned SymExpr::computeComplexity() const { - unsigned R = 0; - for (symbol_iterator I = symbol_begin(), E = symbol_end(); I != E; ++I) - R++; - return R; -} - const SymbolRegionValue* SymbolManager::getRegionValueSymbol(const TypedValueRegion* R) { llvm::FoldingSetNodeID profile; @@ -192,7 +197,6 @@ const SymbolConjured* SymbolManager::conjureSymbol(const Stmt *E, const SymbolDerived* SymbolManager::getDerivedSymbol(SymbolRef parentSymbol, const TypedValueRegion *R) { - llvm::FoldingSetNodeID profile; SymbolDerived::Profile(profile, parentSymbol, R); void *InsertPos; @@ -227,7 +231,6 @@ const SymbolMetadata * SymbolManager::getMetadataSymbol(const MemRegion* R, const Stmt *S, QualType T, const LocationContext *LCtx, unsigned Count, const void *SymbolTag) { - llvm::FoldingSetNodeID profile; SymbolMetadata::Profile(profile, R, S, T, LCtx, Count, SymbolTag); void *InsertPos; @@ -382,11 +385,10 @@ void SymbolReaper::markDependentsLive(SymbolRef sym) { LI->second = HaveMarkedDependents; if (const SymbolRefSmallVectorTy *Deps = SymMgr.getDependentSymbols(sym)) { - for (SymbolRefSmallVectorTy::const_iterator I = Deps->begin(), - E = Deps->end(); I != E; ++I) { - if (TheLiving.find(*I) != TheLiving.end()) + for (const auto I : *Deps) { + if (TheLiving.find(I) != TheLiving.end()) continue; - markLive(*I); + markLive(I); } } } @@ -405,7 +407,7 @@ void SymbolReaper::markLive(const MemRegion *region) { void SymbolReaper::markElementIndicesLive(const MemRegion *region) { for (auto SR = dyn_cast<SubRegion>(region); SR; SR = dyn_cast<SubRegion>(SR->getSuperRegion())) { - if (auto ER = dyn_cast<ElementRegion>(SR)) { + if (const auto ER = dyn_cast<ElementRegion>(SR)) { SVal Idx = ER->getIndex(); for (auto SI = Idx.symbol_begin(), SE = Idx.symbol_end(); SI != SE; ++SI) markLive(*SI); @@ -432,10 +434,10 @@ bool SymbolReaper::isLiveRegion(const MemRegion *MR) { MR = MR->getBaseRegion(); - if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(MR)) + if (const auto *SR = dyn_cast<SymbolicRegion>(MR)) return isLive(SR->getSymbol()); - if (const VarRegion *VR = dyn_cast<VarRegion>(MR)) + if (const auto *VR = dyn_cast<VarRegion>(MR)) return isLive(VR, true); // FIXME: This is a gross over-approximation. What we really need is a way to @@ -533,7 +535,7 @@ bool SymbolReaper::isLive(const VarRegion *VR, bool includeStoreBindings) const{ if (!LCtx) return false; - const StackFrameContext *CurrentContext = LCtx->getCurrentStackFrame(); + const StackFrameContext *CurrentContext = LCtx->getStackFrame(); if (VarContext == CurrentContext) { // If no statement is provided, everything is live. @@ -547,7 +549,7 @@ bool SymbolReaper::isLive(const VarRegion *VR, bool includeStoreBindings) const{ return false; unsigned &cachedQuery = - const_cast<SymbolReaper*>(this)->includedRegionCache[VR]; + const_cast<SymbolReaper *>(this)->includedRegionCache[VR]; if (cachedQuery) { return cachedQuery == 1; diff --git a/lib/StaticAnalyzer/Core/WorkList.cpp b/lib/StaticAnalyzer/Core/WorkList.cpp new file mode 100644 index 000000000000..4b227375da9b --- /dev/null +++ b/lib/StaticAnalyzer/Core/WorkList.cpp @@ -0,0 +1,254 @@ +//===- WorkList.cpp - Analyzer work-list implementation--------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Defines different worklist implementations for the static analyzer. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Core/PathSensitive/WorkList.h" +#include "llvm/ADT/PriorityQueue.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/Statistic.h" +#include <deque> +#include <vector> + +using namespace clang; +using namespace ento; + +#define DEBUG_TYPE "WorkList" + +STATISTIC(MaxQueueSize, "Maximum size of the worklist"); +STATISTIC(MaxReachableSize, "Maximum size of auxiliary worklist set"); + +//===----------------------------------------------------------------------===// +// Worklist classes for exploration of reachable states. +//===----------------------------------------------------------------------===// + +namespace { + +class DFS : public WorkList { + SmallVector<WorkListUnit, 20> Stack; + +public: + bool hasWork() const override { + return !Stack.empty(); + } + + void enqueue(const WorkListUnit& U) override { + Stack.push_back(U); + } + + WorkListUnit dequeue() override { + assert(!Stack.empty()); + const WorkListUnit& U = Stack.back(); + Stack.pop_back(); // This technically "invalidates" U, but we are fine. + return U; + } +}; + +class BFS : public WorkList { + std::deque<WorkListUnit> Queue; + +public: + bool hasWork() const override { + return !Queue.empty(); + } + + void enqueue(const WorkListUnit& U) override { + Queue.push_back(U); + } + + WorkListUnit dequeue() override { + WorkListUnit U = Queue.front(); + Queue.pop_front(); + return U; + } +}; + +} // namespace + +// Place the dstor for WorkList here because it contains virtual member +// functions, and we the code for the dstor generated in one compilation unit. +WorkList::~WorkList() = default; + +std::unique_ptr<WorkList> WorkList::makeDFS() { + return llvm::make_unique<DFS>(); +} + +std::unique_ptr<WorkList> WorkList::makeBFS() { + return llvm::make_unique<BFS>(); +} + +namespace { + + class BFSBlockDFSContents : public WorkList { + std::deque<WorkListUnit> Queue; + SmallVector<WorkListUnit, 20> Stack; + + public: + bool hasWork() const override { + return !Queue.empty() || !Stack.empty(); + } + + void enqueue(const WorkListUnit& U) override { + if (U.getNode()->getLocation().getAs<BlockEntrance>()) + Queue.push_front(U); + else + Stack.push_back(U); + } + + WorkListUnit dequeue() override { + // Process all basic blocks to completion. + if (!Stack.empty()) { + const WorkListUnit& U = Stack.back(); + Stack.pop_back(); // This technically "invalidates" U, but we are fine. + return U; + } + + assert(!Queue.empty()); + // Don't use const reference. The subsequent pop_back() might make it + // unsafe. + WorkListUnit U = Queue.front(); + Queue.pop_front(); + return U; + } + }; + +} // namespace + +std::unique_ptr<WorkList> WorkList::makeBFSBlockDFSContents() { + return llvm::make_unique<BFSBlockDFSContents>(); +} + +namespace { + +class UnexploredFirstStack : public WorkList { + /// Stack of nodes known to have statements we have not traversed yet. + SmallVector<WorkListUnit, 20> StackUnexplored; + + /// Stack of all other nodes. + SmallVector<WorkListUnit, 20> StackOthers; + + using BlockID = unsigned; + using LocIdentifier = std::pair<BlockID, const StackFrameContext *>; + + llvm::DenseSet<LocIdentifier> Reachable; + +public: + bool hasWork() const override { + return !(StackUnexplored.empty() && StackOthers.empty()); + } + + void enqueue(const WorkListUnit &U) override { + const ExplodedNode *N = U.getNode(); + auto BE = N->getLocation().getAs<BlockEntrance>(); + + if (!BE) { + // Assume the choice of the order of the preceeding block entrance was + // correct. + StackUnexplored.push_back(U); + } else { + LocIdentifier LocId = std::make_pair( + BE->getBlock()->getBlockID(), + N->getLocationContext()->getStackFrame()); + auto InsertInfo = Reachable.insert(LocId); + + if (InsertInfo.second) { + StackUnexplored.push_back(U); + } else { + StackOthers.push_back(U); + } + } + MaxReachableSize.updateMax(Reachable.size()); + MaxQueueSize.updateMax(StackUnexplored.size() + StackOthers.size()); + } + + WorkListUnit dequeue() override { + if (!StackUnexplored.empty()) { + WorkListUnit &U = StackUnexplored.back(); + StackUnexplored.pop_back(); + return U; + } else { + WorkListUnit &U = StackOthers.back(); + StackOthers.pop_back(); + return U; + } + } +}; + +} // namespace + +std::unique_ptr<WorkList> WorkList::makeUnexploredFirst() { + return llvm::make_unique<UnexploredFirstStack>(); +} + +namespace { +class UnexploredFirstPriorityQueue : public WorkList { + using BlockID = unsigned; + using LocIdentifier = std::pair<BlockID, const StackFrameContext *>; + + // How many times each location was visited. + // Is signed because we negate it later in order to have a reversed + // comparison. + using VisitedTimesMap = llvm::DenseMap<LocIdentifier, int>; + + // Compare by number of times the location was visited first (negated + // to prefer less often visited locations), then by insertion time (prefer + // expanding nodes inserted sooner first). + using QueuePriority = std::pair<int, unsigned long>; + using QueueItem = std::pair<WorkListUnit, QueuePriority>; + + struct ExplorationComparator { + bool operator() (const QueueItem &LHS, const QueueItem &RHS) { + return LHS.second < RHS.second; + } + }; + + // Number of inserted nodes, used to emulate DFS ordering in the priority + // queue when insertions are equal. + unsigned long Counter = 0; + + // Number of times a current location was reached. + VisitedTimesMap NumReached; + + // The top item is the largest one. + llvm::PriorityQueue<QueueItem, std::vector<QueueItem>, ExplorationComparator> + queue; + +public: + bool hasWork() const override { + return !queue.empty(); + } + + void enqueue(const WorkListUnit &U) override { + const ExplodedNode *N = U.getNode(); + unsigned NumVisited = 0; + if (auto BE = N->getLocation().getAs<BlockEntrance>()) { + LocIdentifier LocId = std::make_pair( + BE->getBlock()->getBlockID(), + N->getLocationContext()->getStackFrame()); + NumVisited = NumReached[LocId]++; + } + + queue.push(std::make_pair(U, std::make_pair(-NumVisited, ++Counter))); + } + + WorkListUnit dequeue() override { + QueueItem U = queue.top(); + queue.pop(); + return U.first; + } +}; +} // namespace + +std::unique_ptr<WorkList> WorkList::makeUnexploredFirstPriorityQueue() { + return llvm::make_unique<UnexploredFirstPriorityQueue>(); +} diff --git a/lib/StaticAnalyzer/Core/Z3ConstraintManager.cpp b/lib/StaticAnalyzer/Core/Z3ConstraintManager.cpp index f9f9057a89cd..7379ded49c80 100644 --- a/lib/StaticAnalyzer/Core/Z3ConstraintManager.cpp +++ b/lib/StaticAnalyzer/Core/Z3ConstraintManager.cpp @@ -10,7 +10,11 @@ #include "clang/Basic/TargetInfo.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SimpleConstraintManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConstraintManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SMTContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SMTExpr.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SMTSolver.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SMTSort.h" #include "clang/Config/config.h" @@ -21,30 +25,9 @@ using namespace ento; #include <z3.h> -// Forward declarations -namespace { -class Z3Expr; -class ConstraintZ3 {}; -} // end anonymous namespace - -typedef llvm::ImmutableSet<std::pair<SymbolRef, Z3Expr>> ConstraintZ3Ty; - -// Expansion of REGISTER_TRAIT_WITH_PROGRAMSTATE(ConstraintZ3, Z3SetPair) -namespace clang { -namespace ento { -template <> -struct ProgramStateTrait<ConstraintZ3> - : public ProgramStatePartialTrait<ConstraintZ3Ty> { - static void *GDMIndex() { - static int Index; - return &Index; - } -}; -} // end namespace ento -} // end namespace clang - namespace { +/// Configuration class for Z3 class Z3Config { friend class Z3Context; @@ -63,45 +46,60 @@ public: ~Z3Config() { Z3_del_config(Config); } }; // end class Z3Config -class Z3Context { - Z3_context ZC_P; +// Function used to report errors +void Z3ErrorHandler(Z3_context Context, Z3_error_code Error) { + llvm::report_fatal_error("Z3 error: " + + llvm::Twine(Z3_get_error_msg_ex(Context, Error))); +} +/// Wrapper for Z3 context +class Z3Context : public SMTContext { public: - static Z3_context ZC; + Z3_context Context; - Z3Context() : ZC_P(Z3_mk_context_rc(Z3Config().Config)) { ZC = ZC_P; } + Z3Context() : SMTContext() { + Context = Z3_mk_context_rc(Z3Config().Config); + // The error function is set here because the context is the first object + // created by the backend + Z3_set_error_handler(Context, Z3ErrorHandler); + } - ~Z3Context() { - Z3_del_context(ZC); - Z3_finalize_memory(); - ZC_P = nullptr; + virtual ~Z3Context() { + Z3_del_context(Context); + Context = nullptr; } }; // end class Z3Context -class Z3Sort { - friend class Z3Expr; +/// Wrapper for Z3 Sort +class Z3Sort : public SMTSort { + friend class Z3Solver; + + Z3Context &Context; Z3_sort Sort; - Z3Sort() : Sort(nullptr) {} - Z3Sort(Z3_sort ZS) : Sort(ZS) { - Z3_inc_ref(Z3Context::ZC, reinterpret_cast<Z3_ast>(Sort)); +public: + /// Default constructor, mainly used by make_shared + Z3Sort(Z3Context &C, Z3_sort ZS) : SMTSort(), Context(C), Sort(ZS) { + Z3_inc_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort)); } -public: /// Override implicit copy constructor for correct reference counting. - Z3Sort(const Z3Sort &Copy) : Sort(Copy.Sort) { - Z3_inc_ref(Z3Context::ZC, reinterpret_cast<Z3_ast>(Sort)); + Z3Sort(const Z3Sort &Copy) + : SMTSort(), Context(Copy.Context), Sort(Copy.Sort) { + Z3_inc_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort)); } /// Provide move constructor - Z3Sort(Z3Sort &&Move) : Sort(nullptr) { *this = std::move(Move); } + Z3Sort(Z3Sort &&Move) : SMTSort(), Context(Move.Context), Sort(nullptr) { + *this = std::move(Move); + } /// Provide move assignment constructor Z3Sort &operator=(Z3Sort &&Move) { if (this != &Move) { if (Sort) - Z3_dec_ref(Z3Context::ZC, reinterpret_cast<Z3_ast>(Sort)); + Z3_dec_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort)); Sort = Move.Sort; Move.Sort = nullptr; } @@ -110,119 +108,78 @@ public: ~Z3Sort() { if (Sort) - Z3_dec_ref(Z3Context::ZC, reinterpret_cast<Z3_ast>(Sort)); - } - - // Return a boolean sort. - static Z3Sort getBoolSort() { return Z3Sort(Z3_mk_bool_sort(Z3Context::ZC)); } - - // Return an appropriate bitvector sort for the given bitwidth. - static Z3Sort getBitvectorSort(unsigned BitWidth) { - return Z3Sort(Z3_mk_bv_sort(Z3Context::ZC, BitWidth)); - } - - // Return an appropriate floating-point sort for the given bitwidth. - static Z3Sort getFloatSort(unsigned BitWidth) { - Z3_sort Sort; - - switch (BitWidth) { - default: - llvm_unreachable("Unsupported floating-point bitwidth!"); - break; - case 16: - Sort = Z3_mk_fpa_sort_16(Z3Context::ZC); - break; - case 32: - Sort = Z3_mk_fpa_sort_32(Z3Context::ZC); - break; - case 64: - Sort = Z3_mk_fpa_sort_64(Z3Context::ZC); - break; - case 128: - Sort = Z3_mk_fpa_sort_128(Z3Context::ZC); - break; - } - return Z3Sort(Sort); + Z3_dec_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort)); } - // Return an appropriate sort for the given AST. - static Z3Sort getSort(Z3_ast AST) { - return Z3Sort(Z3_get_sort(Z3Context::ZC, AST)); + bool isBitvectorSortImpl() const override { + return (Z3_get_sort_kind(Context.Context, Sort) == Z3_BV_SORT); } - Z3_sort_kind getSortKind() const { - return Z3_get_sort_kind(Z3Context::ZC, Sort); + bool isFloatSortImpl() const override { + return (Z3_get_sort_kind(Context.Context, Sort) == Z3_FLOATING_POINT_SORT); } - unsigned getBitvectorSortSize() const { - assert(getSortKind() == Z3_BV_SORT && "Not a bitvector sort!"); - return Z3_get_bv_sort_size(Z3Context::ZC, Sort); + bool isBooleanSortImpl() const override { + return (Z3_get_sort_kind(Context.Context, Sort) == Z3_BOOL_SORT); } - unsigned getFloatSortSize() const { - assert(getSortKind() == Z3_FLOATING_POINT_SORT && - "Not a floating-point sort!"); - return Z3_fpa_get_ebits(Z3Context::ZC, Sort) + - Z3_fpa_get_sbits(Z3Context::ZC, Sort); + unsigned getBitvectorSortSizeImpl() const override { + return Z3_get_bv_sort_size(Context.Context, Sort); } - bool operator==(const Z3Sort &Other) const { - return Z3_is_eq_sort(Z3Context::ZC, Sort, Other.Sort); + unsigned getFloatSortSizeImpl() const override { + return Z3_fpa_get_ebits(Context.Context, Sort) + + Z3_fpa_get_sbits(Context.Context, Sort); + } + + bool equal_to(SMTSort const &Other) const override { + return Z3_is_eq_sort(Context.Context, Sort, + static_cast<const Z3Sort &>(Other).Sort); } Z3Sort &operator=(const Z3Sort &Move) { - Z3_inc_ref(Z3Context::ZC, reinterpret_cast<Z3_ast>(Move.Sort)); - Z3_dec_ref(Z3Context::ZC, reinterpret_cast<Z3_ast>(Sort)); + Z3_inc_ref(Context.Context, reinterpret_cast<Z3_ast>(Move.Sort)); + Z3_dec_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort)); Sort = Move.Sort; return *this; } - void print(raw_ostream &OS) const { - OS << Z3_sort_to_string(Z3Context::ZC, Sort); + void print(raw_ostream &OS) const override { + OS << Z3_sort_to_string(Context.Context, Sort); } - - LLVM_DUMP_METHOD void dump() const { print(llvm::errs()); } }; // end class Z3Sort -class Z3Expr { - friend class Z3Model; - friend class Z3Solver; +static const Z3Sort &toZ3Sort(const SMTSort &S) { + return static_cast<const Z3Sort &>(S); +} - Z3_ast AST; +class Z3Expr : public SMTExpr { + friend class Z3Solver; - Z3Expr(Z3_ast ZA) : AST(ZA) { Z3_inc_ref(Z3Context::ZC, AST); } + Z3Context &Context; - // Return an appropriate floating-point rounding mode. - static Z3Expr getFloatRoundingMode() { - // TODO: Don't assume nearest ties to even rounding mode - return Z3Expr(Z3_mk_fpa_rne(Z3Context::ZC)); - } + Z3_ast AST; - // Determine whether two float semantics are equivalent - static bool areEquivalent(const llvm::fltSemantics &LHS, - const llvm::fltSemantics &RHS) { - return (llvm::APFloat::semanticsPrecision(LHS) == - llvm::APFloat::semanticsPrecision(RHS)) && - (llvm::APFloat::semanticsMinExponent(LHS) == - llvm::APFloat::semanticsMinExponent(RHS)) && - (llvm::APFloat::semanticsMaxExponent(LHS) == - llvm::APFloat::semanticsMaxExponent(RHS)) && - (llvm::APFloat::semanticsSizeInBits(LHS) == - llvm::APFloat::semanticsSizeInBits(RHS)); +public: + Z3Expr(Z3Context &C, Z3_ast ZA) : SMTExpr(), Context(C), AST(ZA) { + Z3_inc_ref(Context.Context, AST); } -public: /// Override implicit copy constructor for correct reference counting. - Z3Expr(const Z3Expr &Copy) : AST(Copy.AST) { Z3_inc_ref(Z3Context::ZC, AST); } + Z3Expr(const Z3Expr &Copy) : SMTExpr(), Context(Copy.Context), AST(Copy.AST) { + Z3_inc_ref(Context.Context, AST); + } /// Provide move constructor - Z3Expr(Z3Expr &&Move) : AST(nullptr) { *this = std::move(Move); } + Z3Expr(Z3Expr &&Move) : SMTExpr(), Context(Move.Context), AST(nullptr) { + *this = std::move(Move); + } /// Provide move assignment constructor Z3Expr &operator=(Z3Expr &&Move) { if (this != &Move) { if (AST) - Z3_dec_ref(Z3Context::ZC, AST); + Z3_dec_ref(Context.Context, AST); AST = Move.AST; Move.AST = nullptr; } @@ -231,1388 +188,854 @@ public: ~Z3Expr() { if (AST) - Z3_dec_ref(Z3Context::ZC, AST); - } - - /// Get the corresponding IEEE floating-point type for a given bitwidth. - static const llvm::fltSemantics &getFloatSemantics(unsigned BitWidth) { - switch (BitWidth) { - default: - llvm_unreachable("Unsupported floating-point semantics!"); - break; - case 16: - return llvm::APFloat::IEEEhalf(); - case 32: - return llvm::APFloat::IEEEsingle(); - case 64: - return llvm::APFloat::IEEEdouble(); - case 128: - return llvm::APFloat::IEEEquad(); - } + Z3_dec_ref(Context.Context, AST); } - /// Construct a Z3Expr from a unary operator, given a Z3_context. - static Z3Expr fromUnOp(const UnaryOperator::Opcode Op, const Z3Expr &Exp) { - Z3_ast AST; + void Profile(llvm::FoldingSetNodeID &ID) const override { + ID.AddInteger(Z3_get_ast_hash(Context.Context, AST)); + } - switch (Op) { - default: - llvm_unreachable("Unimplemented opcode"); - break; + /// Comparison of AST equality, not model equivalence. + bool equal_to(SMTExpr const &Other) const override { + assert(Z3_is_eq_sort(Context.Context, Z3_get_sort(Context.Context, AST), + Z3_get_sort(Context.Context, + static_cast<const Z3Expr &>(Other).AST)) && + "AST's must have the same sort"); + return Z3_is_eq_ast(Context.Context, AST, + static_cast<const Z3Expr &>(Other).AST); + } - case UO_Minus: - AST = Z3_mk_bvneg(Z3Context::ZC, Exp.AST); - break; + /// Override implicit move constructor for correct reference counting. + Z3Expr &operator=(const Z3Expr &Move) { + Z3_inc_ref(Context.Context, Move.AST); + Z3_dec_ref(Context.Context, AST); + AST = Move.AST; + return *this; + } - case UO_Not: - AST = Z3_mk_bvnot(Z3Context::ZC, Exp.AST); - break; + void print(raw_ostream &OS) const override { + OS << Z3_ast_to_string(Context.Context, AST); + } +}; // end class Z3Expr - case UO_LNot: - AST = Z3_mk_not(Z3Context::ZC, Exp.AST); - break; - } +static const Z3Expr &toZ3Expr(const SMTExpr &E) { + return static_cast<const Z3Expr &>(E); +} - return Z3Expr(AST); - } +class Z3Model { + friend class Z3Solver; + + Z3Context &Context; + + Z3_model Model; - /// Construct a Z3Expr from a floating-point unary operator, given a - /// Z3_context. - static Z3Expr fromFloatUnOp(const UnaryOperator::Opcode Op, - const Z3Expr &Exp) { - Z3_ast AST; +public: + Z3Model(Z3Context &C, Z3_model ZM) : Context(C), Model(ZM) { + assert(C.Context != nullptr); + Z3_model_inc_ref(Context.Context, Model); + } - switch (Op) { - default: - llvm_unreachable("Unimplemented opcode"); - break; + /// Override implicit copy constructor for correct reference counting. + Z3Model(const Z3Model &Copy) : Context(Copy.Context), Model(Copy.Model) { + Z3_model_inc_ref(Context.Context, Model); + } - case UO_Minus: - AST = Z3_mk_fpa_neg(Z3Context::ZC, Exp.AST); - break; + /// Provide move constructor + Z3Model(Z3Model &&Move) : Context(Move.Context), Model(nullptr) { + *this = std::move(Move); + } - case UO_LNot: - return Z3Expr::fromUnOp(Op, Exp); + /// Provide move assignment constructor + Z3Model &operator=(Z3Model &&Move) { + if (this != &Move) { + if (Model) + Z3_model_dec_ref(Context.Context, Model); + Model = Move.Model; + Move.Model = nullptr; } + return *this; + } - return Z3Expr(AST); + ~Z3Model() { + if (Model) + Z3_model_dec_ref(Context.Context, Model); } - /// Construct a Z3Expr from a n-ary binary operator. - static Z3Expr fromNBinOp(const BinaryOperator::Opcode Op, - const std::vector<Z3_ast> &ASTs) { - Z3_ast AST; + void print(raw_ostream &OS) const { + OS << Z3_model_to_string(Context.Context, Model); + } - switch (Op) { - default: - llvm_unreachable("Unimplemented opcode"); - break; + LLVM_DUMP_METHOD void dump() const { print(llvm::errs()); } +}; // end class Z3Model - case BO_LAnd: - AST = Z3_mk_and(Z3Context::ZC, ASTs.size(), ASTs.data()); - break; +/// Get the corresponding IEEE floating-point type for a given bitwidth. +static const llvm::fltSemantics &getFloatSemantics(unsigned BitWidth) { + switch (BitWidth) { + default: + llvm_unreachable("Unsupported floating-point semantics!"); + break; + case 16: + return llvm::APFloat::IEEEhalf(); + case 32: + return llvm::APFloat::IEEEsingle(); + case 64: + return llvm::APFloat::IEEEdouble(); + case 128: + return llvm::APFloat::IEEEquad(); + } +} - case BO_LOr: - AST = Z3_mk_or(Z3Context::ZC, ASTs.size(), ASTs.data()); - break; - } +// Determine whether two float semantics are equivalent +static bool areEquivalent(const llvm::fltSemantics &LHS, + const llvm::fltSemantics &RHS) { + return (llvm::APFloat::semanticsPrecision(LHS) == + llvm::APFloat::semanticsPrecision(RHS)) && + (llvm::APFloat::semanticsMinExponent(LHS) == + llvm::APFloat::semanticsMinExponent(RHS)) && + (llvm::APFloat::semanticsMaxExponent(LHS) == + llvm::APFloat::semanticsMaxExponent(RHS)) && + (llvm::APFloat::semanticsSizeInBits(LHS) == + llvm::APFloat::semanticsSizeInBits(RHS)); +} - return Z3Expr(AST); - } - - /// Construct a Z3Expr from a binary operator, given a Z3_context. - static Z3Expr fromBinOp(const Z3Expr &LHS, const BinaryOperator::Opcode Op, - const Z3Expr &RHS, bool isSigned) { - Z3_ast AST; - - assert(Z3Sort::getSort(LHS.AST) == Z3Sort::getSort(RHS.AST) && - "AST's must have the same sort!"); - - switch (Op) { - default: - llvm_unreachable("Unimplemented opcode"); - break; - - // Multiplicative operators - case BO_Mul: - AST = Z3_mk_bvmul(Z3Context::ZC, LHS.AST, RHS.AST); - break; - case BO_Div: - AST = isSigned ? Z3_mk_bvsdiv(Z3Context::ZC, LHS.AST, RHS.AST) - : Z3_mk_bvudiv(Z3Context::ZC, LHS.AST, RHS.AST); - break; - case BO_Rem: - AST = isSigned ? Z3_mk_bvsrem(Z3Context::ZC, LHS.AST, RHS.AST) - : Z3_mk_bvurem(Z3Context::ZC, LHS.AST, RHS.AST); - break; - - // Additive operators - case BO_Add: - AST = Z3_mk_bvadd(Z3Context::ZC, LHS.AST, RHS.AST); - break; - case BO_Sub: - AST = Z3_mk_bvsub(Z3Context::ZC, LHS.AST, RHS.AST); - break; - - // Bitwise shift operators - case BO_Shl: - AST = Z3_mk_bvshl(Z3Context::ZC, LHS.AST, RHS.AST); - break; - case BO_Shr: - AST = isSigned ? Z3_mk_bvashr(Z3Context::ZC, LHS.AST, RHS.AST) - : Z3_mk_bvlshr(Z3Context::ZC, LHS.AST, RHS.AST); - break; - - // Relational operators - case BO_LT: - AST = isSigned ? Z3_mk_bvslt(Z3Context::ZC, LHS.AST, RHS.AST) - : Z3_mk_bvult(Z3Context::ZC, LHS.AST, RHS.AST); - break; - case BO_GT: - AST = isSigned ? Z3_mk_bvsgt(Z3Context::ZC, LHS.AST, RHS.AST) - : Z3_mk_bvugt(Z3Context::ZC, LHS.AST, RHS.AST); - break; - case BO_LE: - AST = isSigned ? Z3_mk_bvsle(Z3Context::ZC, LHS.AST, RHS.AST) - : Z3_mk_bvule(Z3Context::ZC, LHS.AST, RHS.AST); - break; - case BO_GE: - AST = isSigned ? Z3_mk_bvsge(Z3Context::ZC, LHS.AST, RHS.AST) - : Z3_mk_bvuge(Z3Context::ZC, LHS.AST, RHS.AST); - break; - - // Equality operators - case BO_EQ: - AST = Z3_mk_eq(Z3Context::ZC, LHS.AST, RHS.AST); - break; - case BO_NE: - return Z3Expr::fromUnOp(UO_LNot, - Z3Expr::fromBinOp(LHS, BO_EQ, RHS, isSigned)); - break; - - // Bitwise operators - case BO_And: - AST = Z3_mk_bvand(Z3Context::ZC, LHS.AST, RHS.AST); - break; - case BO_Xor: - AST = Z3_mk_bvxor(Z3Context::ZC, LHS.AST, RHS.AST); - break; - case BO_Or: - AST = Z3_mk_bvor(Z3Context::ZC, LHS.AST, RHS.AST); - break; - - // Logical operators - case BO_LAnd: - case BO_LOr: { - std::vector<Z3_ast> Args = {LHS.AST, RHS.AST}; - return Z3Expr::fromNBinOp(Op, Args); - } - } +} // end anonymous namespace - return Z3Expr(AST); - } - - /// Construct a Z3Expr from a special floating-point binary operator, given - /// a Z3_context. - static Z3Expr fromFloatSpecialBinOp(const Z3Expr &LHS, - const BinaryOperator::Opcode Op, - const llvm::APFloat::fltCategory &RHS) { - Z3_ast AST; - - switch (Op) { - default: - llvm_unreachable("Unimplemented opcode"); - break; - - // Equality operators - case BO_EQ: - switch (RHS) { - case llvm::APFloat::fcInfinity: - AST = Z3_mk_fpa_is_infinite(Z3Context::ZC, LHS.AST); - break; - case llvm::APFloat::fcNaN: - AST = Z3_mk_fpa_is_nan(Z3Context::ZC, LHS.AST); - break; - case llvm::APFloat::fcNormal: - AST = Z3_mk_fpa_is_normal(Z3Context::ZC, LHS.AST); - break; - case llvm::APFloat::fcZero: - AST = Z3_mk_fpa_is_zero(Z3Context::ZC, LHS.AST); - break; - } - break; - case BO_NE: - return Z3Expr::fromFloatUnOp( - UO_LNot, Z3Expr::fromFloatSpecialBinOp(LHS, BO_EQ, RHS)); - break; - } +typedef llvm::ImmutableSet<std::pair<SymbolRef, Z3Expr>> ConstraintZ3Ty; +REGISTER_TRAIT_WITH_PROGRAMSTATE(ConstraintZ3, ConstraintZ3Ty) - return Z3Expr(AST); - } +namespace { - /// Construct a Z3Expr from a floating-point binary operator, given a - /// Z3_context. - static Z3Expr fromFloatBinOp(const Z3Expr &LHS, - const BinaryOperator::Opcode Op, - const Z3Expr &RHS) { - Z3_ast AST; +class Z3Solver : public SMTSolver { + friend class Z3ConstraintManager; - assert(Z3Sort::getSort(LHS.AST) == Z3Sort::getSort(RHS.AST) && - "AST's must have the same sort!"); + Z3Context Context; - switch (Op) { - default: - llvm_unreachable("Unimplemented opcode"); - break; + Z3_solver Solver; - // Multiplicative operators - case BO_Mul: { - Z3Expr RoundingMode = Z3Expr::getFloatRoundingMode(); - AST = Z3_mk_fpa_mul(Z3Context::ZC, RoundingMode.AST, LHS.AST, RHS.AST); - break; - } - case BO_Div: { - Z3Expr RoundingMode = Z3Expr::getFloatRoundingMode(); - AST = Z3_mk_fpa_div(Z3Context::ZC, RoundingMode.AST, LHS.AST, RHS.AST); - break; - } - case BO_Rem: - AST = Z3_mk_fpa_rem(Z3Context::ZC, LHS.AST, RHS.AST); - break; - - // Additive operators - case BO_Add: { - Z3Expr RoundingMode = Z3Expr::getFloatRoundingMode(); - AST = Z3_mk_fpa_add(Z3Context::ZC, RoundingMode.AST, LHS.AST, RHS.AST); - break; - } - case BO_Sub: { - Z3Expr RoundingMode = Z3Expr::getFloatRoundingMode(); - AST = Z3_mk_fpa_sub(Z3Context::ZC, RoundingMode.AST, LHS.AST, RHS.AST); - break; - } +public: + Z3Solver() : SMTSolver(), Solver(Z3_mk_simple_solver(Context.Context)) { + Z3_solver_inc_ref(Context.Context, Solver); + } + + /// Override implicit copy constructor for correct reference counting. + Z3Solver(const Z3Solver &Copy) + : SMTSolver(), Context(Copy.Context), Solver(Copy.Solver) { + Z3_solver_inc_ref(Context.Context, Solver); + } + + /// Provide move constructor + Z3Solver(Z3Solver &&Move) + : SMTSolver(), Context(Move.Context), Solver(nullptr) { + *this = std::move(Move); + } - // Relational operators - case BO_LT: - AST = Z3_mk_fpa_lt(Z3Context::ZC, LHS.AST, RHS.AST); - break; - case BO_GT: - AST = Z3_mk_fpa_gt(Z3Context::ZC, LHS.AST, RHS.AST); - break; - case BO_LE: - AST = Z3_mk_fpa_leq(Z3Context::ZC, LHS.AST, RHS.AST); - break; - case BO_GE: - AST = Z3_mk_fpa_geq(Z3Context::ZC, LHS.AST, RHS.AST); - break; - - // Equality operators - case BO_EQ: - AST = Z3_mk_fpa_eq(Z3Context::ZC, LHS.AST, RHS.AST); - break; - case BO_NE: - return Z3Expr::fromFloatUnOp(UO_LNot, - Z3Expr::fromFloatBinOp(LHS, BO_EQ, RHS)); - break; - - // Logical operators - case BO_LAnd: - case BO_LOr: - return Z3Expr::fromBinOp(LHS, Op, RHS, false); + /// Provide move assignment constructor + Z3Solver &operator=(Z3Solver &&Move) { + if (this != &Move) { + if (Solver) + Z3_solver_dec_ref(Context.Context, Solver); + Solver = Move.Solver; + Move.Solver = nullptr; } + return *this; + } - return Z3Expr(AST); + ~Z3Solver() { + if (Solver) + Z3_solver_dec_ref(Context.Context, Solver); } - /// Construct a Z3Expr from a SymbolData, given a Z3_context. - static Z3Expr fromData(const SymbolID ID, bool isBool, bool isFloat, - uint64_t BitWidth) { - llvm::Twine Name = "$" + llvm::Twine(ID); + void addConstraint(const SMTExprRef &Exp) const override { + Z3_solver_assert(Context.Context, Solver, toZ3Expr(*Exp).AST); + } - Z3Sort Sort; - if (isBool) - Sort = Z3Sort::getBoolSort(); - else if (isFloat) - Sort = Z3Sort::getFloatSort(BitWidth); - else - Sort = Z3Sort::getBitvectorSort(BitWidth); - - Z3_symbol Symbol = Z3_mk_string_symbol(Z3Context::ZC, Name.str().c_str()); - Z3_ast AST = Z3_mk_const(Z3Context::ZC, Symbol, Sort.Sort); - return Z3Expr(AST); - } - - /// Construct a Z3Expr from a SymbolCast, given a Z3_context. - static Z3Expr fromCast(const Z3Expr &Exp, QualType ToTy, uint64_t ToBitWidth, - QualType FromTy, uint64_t FromBitWidth) { - Z3_ast AST; - - if ((FromTy->isIntegralOrEnumerationType() && - ToTy->isIntegralOrEnumerationType()) || - (FromTy->isAnyPointerType() ^ ToTy->isAnyPointerType()) || - (FromTy->isBlockPointerType() ^ ToTy->isBlockPointerType()) || - (FromTy->isReferenceType() ^ ToTy->isReferenceType())) { - // Special case: Z3 boolean type is distinct from bitvector type, so - // must use if-then-else expression instead of direct cast - if (FromTy->isBooleanType()) { - assert(ToBitWidth > 0 && "BitWidth must be positive!"); - Z3Expr Zero = Z3Expr::fromInt("0", ToBitWidth); - Z3Expr One = Z3Expr::fromInt("1", ToBitWidth); - AST = Z3_mk_ite(Z3Context::ZC, Exp.AST, One.AST, Zero.AST); - } else if (ToBitWidth > FromBitWidth) { - AST = FromTy->isSignedIntegerOrEnumerationType() - ? Z3_mk_sign_ext(Z3Context::ZC, ToBitWidth - FromBitWidth, - Exp.AST) - : Z3_mk_zero_ext(Z3Context::ZC, ToBitWidth - FromBitWidth, - Exp.AST); - } else if (ToBitWidth < FromBitWidth) { - AST = Z3_mk_extract(Z3Context::ZC, ToBitWidth - 1, 0, Exp.AST); - } else { - // Both are bitvectors with the same width, ignore the type cast - return Exp; - } - } else if (FromTy->isRealFloatingType() && ToTy->isRealFloatingType()) { - if (ToBitWidth != FromBitWidth) { - Z3Expr RoundingMode = Z3Expr::getFloatRoundingMode(); - Z3Sort Sort = Z3Sort::getFloatSort(ToBitWidth); - AST = Z3_mk_fpa_to_fp_float(Z3Context::ZC, RoundingMode.AST, Exp.AST, - Sort.Sort); - } else { - return Exp; - } - } else if (FromTy->isIntegralOrEnumerationType() && - ToTy->isRealFloatingType()) { - Z3Expr RoundingMode = Z3Expr::getFloatRoundingMode(); - Z3Sort Sort = Z3Sort::getFloatSort(ToBitWidth); - AST = FromTy->isSignedIntegerOrEnumerationType() - ? Z3_mk_fpa_to_fp_signed(Z3Context::ZC, RoundingMode.AST, - Exp.AST, Sort.Sort) - : Z3_mk_fpa_to_fp_unsigned(Z3Context::ZC, RoundingMode.AST, - Exp.AST, Sort.Sort); - } else if (FromTy->isRealFloatingType() && - ToTy->isIntegralOrEnumerationType()) { - Z3Expr RoundingMode = Z3Expr::getFloatRoundingMode(); - AST = ToTy->isSignedIntegerOrEnumerationType() - ? Z3_mk_fpa_to_sbv(Z3Context::ZC, RoundingMode.AST, Exp.AST, - ToBitWidth) - : Z3_mk_fpa_to_ubv(Z3Context::ZC, RoundingMode.AST, Exp.AST, - ToBitWidth); - } else { - llvm_unreachable("Unsupported explicit type cast!"); - } + SMTSortRef getBoolSort() override { + return std::make_shared<Z3Sort>(Context, Z3_mk_bool_sort(Context.Context)); + } - return Z3Expr(AST); + SMTSortRef getBitvectorSort(unsigned BitWidth) override { + return std::make_shared<Z3Sort>(Context, + Z3_mk_bv_sort(Context.Context, BitWidth)); } - /// Construct a Z3Expr from a boolean, given a Z3_context. - static Z3Expr fromBoolean(const bool Bool) { - Z3_ast AST = Bool ? Z3_mk_true(Z3Context::ZC) : Z3_mk_false(Z3Context::ZC); - return Z3Expr(AST); + SMTSortRef getSort(const SMTExprRef &Exp) override { + return std::make_shared<Z3Sort>( + Context, Z3_get_sort(Context.Context, toZ3Expr(*Exp).AST)); } - /// Construct a Z3Expr from a finite APFloat, given a Z3_context. - static Z3Expr fromAPFloat(const llvm::APFloat &Float) { - Z3_ast AST; - Z3Sort Sort = Z3Sort::getFloatSort( - llvm::APFloat::semanticsSizeInBits(Float.getSemantics())); + SMTSortRef getFloat16Sort() override { + return std::make_shared<Z3Sort>(Context, + Z3_mk_fpa_sort_16(Context.Context)); + } - llvm::APSInt Int = llvm::APSInt(Float.bitcastToAPInt(), true); - Z3Expr Z3Int = Z3Expr::fromAPSInt(Int); - AST = Z3_mk_fpa_to_fp_bv(Z3Context::ZC, Z3Int.AST, Sort.Sort); + SMTSortRef getFloat32Sort() override { + return std::make_shared<Z3Sort>(Context, + Z3_mk_fpa_sort_32(Context.Context)); + } - return Z3Expr(AST); + SMTSortRef getFloat64Sort() override { + return std::make_shared<Z3Sort>(Context, + Z3_mk_fpa_sort_64(Context.Context)); } - /// Construct a Z3Expr from an APSInt, given a Z3_context. - static Z3Expr fromAPSInt(const llvm::APSInt &Int) { - Z3Sort Sort = Z3Sort::getBitvectorSort(Int.getBitWidth()); - Z3_ast AST = - Z3_mk_numeral(Z3Context::ZC, Int.toString(10).c_str(), Sort.Sort); - return Z3Expr(AST); + SMTSortRef getFloat128Sort() override { + return std::make_shared<Z3Sort>(Context, + Z3_mk_fpa_sort_128(Context.Context)); } - /// Construct a Z3Expr from an integer, given a Z3_context. - static Z3Expr fromInt(const char *Int, uint64_t BitWidth) { - Z3Sort Sort = Z3Sort::getBitvectorSort(BitWidth); - Z3_ast AST = Z3_mk_numeral(Z3Context::ZC, Int, Sort.Sort); - return Z3Expr(AST); + SMTExprRef newExprRef(const SMTExpr &E) const override { + return std::make_shared<Z3Expr>(toZ3Expr(E)); } - /// Construct an APFloat from a Z3Expr, given the AST representation - static bool toAPFloat(const Z3Sort &Sort, const Z3_ast &AST, - llvm::APFloat &Float, bool useSemantics = true) { - assert(Sort.getSortKind() == Z3_FLOATING_POINT_SORT && - "Unsupported sort to floating-point!"); + SMTExprRef mkBVNeg(const SMTExprRef &Exp) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvneg(Context.Context, toZ3Expr(*Exp).AST))); + } - llvm::APSInt Int(Sort.getFloatSortSize(), true); - const llvm::fltSemantics &Semantics = - Z3Expr::getFloatSemantics(Sort.getFloatSortSize()); - Z3Sort BVSort = Z3Sort::getBitvectorSort(Sort.getFloatSortSize()); - if (!Z3Expr::toAPSInt(BVSort, AST, Int, true)) { - return false; - } + SMTExprRef mkBVNot(const SMTExprRef &Exp) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvnot(Context.Context, toZ3Expr(*Exp).AST))); + } - if (useSemantics && - !Z3Expr::areEquivalent(Float.getSemantics(), Semantics)) { - assert(false && "Floating-point types don't match!"); - return false; - } + SMTExprRef mkNot(const SMTExprRef &Exp) override { + return newExprRef( + Z3Expr(Context, Z3_mk_not(Context.Context, toZ3Expr(*Exp).AST))); + } - Float = llvm::APFloat(Semantics, Int); - return true; + SMTExprRef mkBVAdd(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvadd(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } - /// Construct an APSInt from a Z3Expr, given the AST representation - static bool toAPSInt(const Z3Sort &Sort, const Z3_ast &AST, llvm::APSInt &Int, - bool useSemantics = true) { - switch (Sort.getSortKind()) { - default: - llvm_unreachable("Unsupported sort to integer!"); - case Z3_BV_SORT: { - if (useSemantics && Int.getBitWidth() != Sort.getBitvectorSortSize()) { - assert(false && "Bitvector types don't match!"); - return false; - } + SMTExprRef mkBVSub(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvsub(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); + } - uint64_t Value[2]; - // Force cast because Z3 defines __uint64 to be a unsigned long long - // type, which isn't compatible with a unsigned long type, even if they - // are the same size. - Z3_get_numeral_uint64(Z3Context::ZC, AST, - reinterpret_cast<__uint64 *>(&Value[0])); - if (Sort.getBitvectorSortSize() <= 64) { - Int = llvm::APSInt(llvm::APInt(Int.getBitWidth(), Value[0]), true); - } else if (Sort.getBitvectorSortSize() == 128) { - Z3Expr ASTHigh = Z3Expr(Z3_mk_extract(Z3Context::ZC, 127, 64, AST)); - Z3_get_numeral_uint64(Z3Context::ZC, AST, - reinterpret_cast<__uint64 *>(&Value[1])); - Int = llvm::APSInt(llvm::APInt(Int.getBitWidth(), Value), true); - } else { - assert(false && "Bitwidth not supported!"); - return false; - } - return true; - } - case Z3_BOOL_SORT: - if (useSemantics && Int.getBitWidth() < 1) { - assert(false && "Boolean type doesn't match!"); - return false; - } - Int = llvm::APSInt( - llvm::APInt(Int.getBitWidth(), - Z3_get_bool_value(Z3Context::ZC, AST) == Z3_L_TRUE ? 1 - : 0), - true); - return true; - } + SMTExprRef mkBVMul(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvmul(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } - void Profile(llvm::FoldingSetNodeID &ID) const { - ID.AddInteger(Z3_get_ast_hash(Z3Context::ZC, AST)); + SMTExprRef mkBVSRem(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvsrem(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } - bool operator<(const Z3Expr &Other) const { - llvm::FoldingSetNodeID ID1, ID2; - Profile(ID1); - Other.Profile(ID2); - return ID1 < ID2; + SMTExprRef mkBVURem(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvurem(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } - /// Comparison of AST equality, not model equivalence. - bool operator==(const Z3Expr &Other) const { - assert(Z3_is_eq_sort(Z3Context::ZC, Z3_get_sort(Z3Context::ZC, AST), - Z3_get_sort(Z3Context::ZC, Other.AST)) && - "AST's must have the same sort"); - return Z3_is_eq_ast(Z3Context::ZC, AST, Other.AST); + SMTExprRef mkBVSDiv(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvsdiv(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } - /// Override implicit move constructor for correct reference counting. - Z3Expr &operator=(const Z3Expr &Move) { - Z3_inc_ref(Z3Context::ZC, Move.AST); - Z3_dec_ref(Z3Context::ZC, AST); - AST = Move.AST; - return *this; + SMTExprRef mkBVUDiv(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvudiv(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } - void print(raw_ostream &OS) const { - OS << Z3_ast_to_string(Z3Context::ZC, AST); + SMTExprRef mkBVShl(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvshl(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } - LLVM_DUMP_METHOD void dump() const { print(llvm::errs()); } -}; // end class Z3Expr + SMTExprRef mkBVAshr(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvashr(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); + } -class Z3Model { - Z3_model Model; + SMTExprRef mkBVLshr(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvlshr(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); + } -public: - Z3Model(Z3_model ZM) : Model(ZM) { Z3_model_inc_ref(Z3Context::ZC, Model); } + SMTExprRef mkBVXor(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvxor(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); + } - /// Override implicit copy constructor for correct reference counting. - Z3Model(const Z3Model &Copy) : Model(Copy.Model) { - Z3_model_inc_ref(Z3Context::ZC, Model); + SMTExprRef mkBVOr(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvor(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } - /// Provide move constructor - Z3Model(Z3Model &&Move) : Model(nullptr) { *this = std::move(Move); } + SMTExprRef mkBVAnd(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvand(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); + } - /// Provide move assignment constructor - Z3Model &operator=(Z3Model &&Move) { - if (this != &Move) { - if (Model) - Z3_model_dec_ref(Z3Context::ZC, Model); - Model = Move.Model; - Move.Model = nullptr; - } - return *this; + SMTExprRef mkBVUlt(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvult(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } - ~Z3Model() { - if (Model) - Z3_model_dec_ref(Z3Context::ZC, Model); + SMTExprRef mkBVSlt(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvslt(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } - /// Given an expression, extract the value of this operand in the model. - bool getInterpretation(const Z3Expr &Exp, llvm::APSInt &Int) const { - Z3_func_decl Func = - Z3_get_app_decl(Z3Context::ZC, Z3_to_app(Z3Context::ZC, Exp.AST)); - if (Z3_model_has_interp(Z3Context::ZC, Model, Func) != Z3_L_TRUE) - return false; + SMTExprRef mkBVUgt(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvugt(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); + } - Z3_ast Assign = Z3_model_get_const_interp(Z3Context::ZC, Model, Func); - Z3Sort Sort = Z3Sort::getSort(Assign); - return Z3Expr::toAPSInt(Sort, Assign, Int, true); + SMTExprRef mkBVSgt(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvsgt(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } - /// Given an expression, extract the value of this operand in the model. - bool getInterpretation(const Z3Expr &Exp, llvm::APFloat &Float) const { - Z3_func_decl Func = - Z3_get_app_decl(Z3Context::ZC, Z3_to_app(Z3Context::ZC, Exp.AST)); - if (Z3_model_has_interp(Z3Context::ZC, Model, Func) != Z3_L_TRUE) - return false; + SMTExprRef mkBVUle(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvule(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); + } - Z3_ast Assign = Z3_model_get_const_interp(Z3Context::ZC, Model, Func); - Z3Sort Sort = Z3Sort::getSort(Assign); - return Z3Expr::toAPFloat(Sort, Assign, Float, true); + SMTExprRef mkBVSle(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvsle(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } - void print(raw_ostream &OS) const { - OS << Z3_model_to_string(Z3Context::ZC, Model); + SMTExprRef mkBVUge(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvuge(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } - LLVM_DUMP_METHOD void dump() const { print(llvm::errs()); } -}; // end class Z3Model + SMTExprRef mkBVSge(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_bvsge(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); + } -class Z3Solver { - friend class Z3ConstraintManager; + SMTExprRef mkAnd(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + Z3_ast Args[2] = {toZ3Expr(*LHS).AST, toZ3Expr(*RHS).AST}; + return newExprRef(Z3Expr(Context, Z3_mk_and(Context.Context, 2, Args))); + } - Z3_solver Solver; + SMTExprRef mkOr(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + Z3_ast Args[2] = {toZ3Expr(*LHS).AST, toZ3Expr(*RHS).AST}; + return newExprRef(Z3Expr(Context, Z3_mk_or(Context.Context, 2, Args))); + } - Z3Solver(Z3_solver ZS) : Solver(ZS) { - Z3_solver_inc_ref(Z3Context::ZC, Solver); + SMTExprRef mkEqual(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_eq(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } -public: - /// Override implicit copy constructor for correct reference counting. - Z3Solver(const Z3Solver &Copy) : Solver(Copy.Solver) { - Z3_solver_inc_ref(Z3Context::ZC, Solver); + SMTExprRef mkFPNeg(const SMTExprRef &Exp) override { + return newExprRef( + Z3Expr(Context, Z3_mk_fpa_neg(Context.Context, toZ3Expr(*Exp).AST))); } - /// Provide move constructor - Z3Solver(Z3Solver &&Move) : Solver(nullptr) { *this = std::move(Move); } + SMTExprRef mkFPIsInfinite(const SMTExprRef &Exp) override { + return newExprRef(Z3Expr( + Context, Z3_mk_fpa_is_infinite(Context.Context, toZ3Expr(*Exp).AST))); + } - /// Provide move assignment constructor - Z3Solver &operator=(Z3Solver &&Move) { - if (this != &Move) { - if (Solver) - Z3_solver_dec_ref(Z3Context::ZC, Solver); - Solver = Move.Solver; - Move.Solver = nullptr; - } - return *this; + SMTExprRef mkFPIsNaN(const SMTExprRef &Exp) override { + return newExprRef( + Z3Expr(Context, Z3_mk_fpa_is_nan(Context.Context, toZ3Expr(*Exp).AST))); } - ~Z3Solver() { - if (Solver) - Z3_solver_dec_ref(Z3Context::ZC, Solver); + SMTExprRef mkFPIsNormal(const SMTExprRef &Exp) override { + return newExprRef(Z3Expr( + Context, Z3_mk_fpa_is_normal(Context.Context, toZ3Expr(*Exp).AST))); } - /// Given a constraint, add it to the solver - void addConstraint(const Z3Expr &Exp) { - Z3_solver_assert(Z3Context::ZC, Solver, Exp.AST); + SMTExprRef mkFPIsZero(const SMTExprRef &Exp) override { + return newExprRef(Z3Expr( + Context, Z3_mk_fpa_is_zero(Context.Context, toZ3Expr(*Exp).AST))); } - /// Given a program state, construct the logical conjunction and add it to - /// the solver - void addStateConstraints(ProgramStateRef State) { - // TODO: Don't add all the constraints, only the relevant ones - ConstraintZ3Ty CZ = State->get<ConstraintZ3>(); - ConstraintZ3Ty::iterator I = CZ.begin(), IE = CZ.end(); + SMTExprRef mkFPMul(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + SMTExprRef RoundingMode = getFloatRoundingMode(); + return newExprRef( + Z3Expr(Context, + Z3_mk_fpa_mul(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST, toZ3Expr(*RoundingMode).AST))); + } - // Construct the logical AND of all the constraints - if (I != IE) { - std::vector<Z3_ast> ASTs; + SMTExprRef mkFPDiv(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + SMTExprRef RoundingMode = getFloatRoundingMode(); + return newExprRef( + Z3Expr(Context, + Z3_mk_fpa_div(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST, toZ3Expr(*RoundingMode).AST))); + } - while (I != IE) - ASTs.push_back(I++->second.AST); + SMTExprRef mkFPRem(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_fpa_rem(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); + } - Z3Expr Conj = Z3Expr::fromNBinOp(BO_LAnd, ASTs); - addConstraint(Conj); - } + SMTExprRef mkFPAdd(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + SMTExprRef RoundingMode = getFloatRoundingMode(); + return newExprRef( + Z3Expr(Context, + Z3_mk_fpa_add(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST, toZ3Expr(*RoundingMode).AST))); } - /// Check if the constraints are satisfiable - Z3_lbool check() { return Z3_solver_check(Z3Context::ZC, Solver); } + SMTExprRef mkFPSub(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + SMTExprRef RoundingMode = getFloatRoundingMode(); + return newExprRef( + Z3Expr(Context, + Z3_mk_fpa_sub(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST, toZ3Expr(*RoundingMode).AST))); + } - /// Push the current solver state - void push() { return Z3_solver_push(Z3Context::ZC, Solver); } + SMTExprRef mkFPLt(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_fpa_lt(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); + } - /// Pop the previous solver state - void pop(unsigned NumStates = 1) { - assert(Z3_solver_get_num_scopes(Z3Context::ZC, Solver) >= NumStates); - return Z3_solver_pop(Z3Context::ZC, Solver, NumStates); + SMTExprRef mkFPGt(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_fpa_gt(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } - /// Get a model from the solver. Caller should check the model is - /// satisfiable. - Z3Model getModel() { - return Z3Model(Z3_solver_get_model(Z3Context::ZC, Solver)); + SMTExprRef mkFPLe(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_fpa_leq(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); } - /// Reset the solver and remove all constraints. - void reset() { Z3_solver_reset(Z3Context::ZC, Solver); } -}; // end class Z3Solver + SMTExprRef mkFPGe(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_fpa_geq(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); + } -void Z3ErrorHandler(Z3_context Context, Z3_error_code Error) { - llvm::report_fatal_error("Z3 error: " + - llvm::Twine(Z3_get_error_msg_ex(Context, Error))); -} + SMTExprRef mkFPEqual(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_fpa_eq(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); + } -class Z3ConstraintManager : public SimpleConstraintManager { - Z3Context Context; - mutable Z3Solver Solver; + SMTExprRef mkIte(const SMTExprRef &Cond, const SMTExprRef &T, + const SMTExprRef &F) override { + return newExprRef( + Z3Expr(Context, Z3_mk_ite(Context.Context, toZ3Expr(*Cond).AST, + toZ3Expr(*T).AST, toZ3Expr(*F).AST))); + } -public: - Z3ConstraintManager(SubEngine *SE, SValBuilder &SB) - : SimpleConstraintManager(SE, SB), - Solver(Z3_mk_simple_solver(Z3Context::ZC)) { - Z3_set_error_handler(Z3Context::ZC, Z3ErrorHandler); + SMTExprRef mkBVSignExt(unsigned i, const SMTExprRef &Exp) override { + return newExprRef(Z3Expr( + Context, Z3_mk_sign_ext(Context.Context, i, toZ3Expr(*Exp).AST))); } - //===------------------------------------------------------------------===// - // Implementation for interface from ConstraintManager. - //===------------------------------------------------------------------===// + SMTExprRef mkBVZeroExt(unsigned i, const SMTExprRef &Exp) override { + return newExprRef(Z3Expr( + Context, Z3_mk_zero_ext(Context.Context, i, toZ3Expr(*Exp).AST))); + } - bool canReasonAbout(SVal X) const override; + SMTExprRef mkBVExtract(unsigned High, unsigned Low, + const SMTExprRef &Exp) override { + return newExprRef(Z3Expr(Context, Z3_mk_extract(Context.Context, High, Low, + toZ3Expr(*Exp).AST))); + } - ConditionTruthVal checkNull(ProgramStateRef State, SymbolRef Sym) override; - - const llvm::APSInt *getSymVal(ProgramStateRef State, - SymbolRef Sym) const override; - - ProgramStateRef removeDeadBindings(ProgramStateRef St, - SymbolReaper &SymReaper) override; - - void print(ProgramStateRef St, raw_ostream &Out, const char *nl, - const char *sep) override; - - //===------------------------------------------------------------------===// - // Implementation for interface from SimpleConstraintManager. - //===------------------------------------------------------------------===// - - ProgramStateRef assumeSym(ProgramStateRef state, SymbolRef Sym, - bool Assumption) override; - - ProgramStateRef assumeSymInclusiveRange(ProgramStateRef State, SymbolRef Sym, - const llvm::APSInt &From, - const llvm::APSInt &To, - bool InRange) override; - - ProgramStateRef assumeSymUnsupported(ProgramStateRef State, SymbolRef Sym, - bool Assumption) override; - -private: - //===------------------------------------------------------------------===// - // Internal implementation. - //===------------------------------------------------------------------===// - - // Check whether a new model is satisfiable, and update the program state. - ProgramStateRef assumeZ3Expr(ProgramStateRef State, SymbolRef Sym, - const Z3Expr &Exp); - - // Generate and check a Z3 model, using the given constraint. - Z3_lbool checkZ3Model(ProgramStateRef State, const Z3Expr &Exp) const; - - // Generate a Z3Expr that represents the given symbolic expression. - // Sets the hasComparison parameter if the expression has a comparison - // operator. - // Sets the RetTy parameter to the final return type after promotions and - // casts. - Z3Expr getZ3Expr(SymbolRef Sym, QualType *RetTy = nullptr, - bool *hasComparison = nullptr) const; - - // Generate a Z3Expr that takes the logical not of an expression. - Z3Expr getZ3NotExpr(const Z3Expr &Exp) const; - - // Generate a Z3Expr that compares the expression to zero. - Z3Expr getZ3ZeroExpr(const Z3Expr &Exp, QualType RetTy, - bool Assumption) const; - - // Recursive implementation to unpack and generate symbolic expression. - // Sets the hasComparison and RetTy parameters. See getZ3Expr(). - Z3Expr getZ3SymExpr(SymbolRef Sym, QualType *RetTy, - bool *hasComparison) const; - - // Wrapper to generate Z3Expr from SymbolData. - Z3Expr getZ3DataExpr(const SymbolID ID, QualType Ty) const; - - // Wrapper to generate Z3Expr from SymbolCast. - Z3Expr getZ3CastExpr(const Z3Expr &Exp, QualType FromTy, QualType Ty) const; - - // Wrapper to generate Z3Expr from BinarySymExpr. - // Sets the hasComparison and RetTy parameters. See getZ3Expr(). - Z3Expr getZ3SymBinExpr(const BinarySymExpr *BSE, bool *hasComparison, - QualType *RetTy) const; - - // Wrapper to generate Z3Expr from unpacked binary symbolic expression. - // Sets the RetTy parameter. See getZ3Expr(). - Z3Expr getZ3BinExpr(const Z3Expr &LHS, QualType LTy, - BinaryOperator::Opcode Op, const Z3Expr &RHS, - QualType RTy, QualType *RetTy) const; - - //===------------------------------------------------------------------===// - // Helper functions. - //===------------------------------------------------------------------===// - - // Recover the QualType of an APSInt. - // TODO: Refactor to put elsewhere - QualType getAPSIntType(const llvm::APSInt &Int) const; - - // Perform implicit type conversion on binary symbolic expressions. - // May modify all input parameters. - // TODO: Refactor to use built-in conversion functions - void doTypeConversion(Z3Expr &LHS, Z3Expr &RHS, QualType <y, - QualType &RTy) const; - - // Perform implicit integer type conversion. - // May modify all input parameters. - // TODO: Refactor to use Sema::handleIntegerConversion() - template <typename T, - T(doCast)(const T &, QualType, uint64_t, QualType, uint64_t)> - void doIntTypeConversion(T &LHS, QualType <y, T &RHS, QualType &RTy) const; - - // Perform implicit floating-point type conversion. - // May modify all input parameters. - // TODO: Refactor to use Sema::handleFloatConversion() - template <typename T, - T(doCast)(const T &, QualType, uint64_t, QualType, uint64_t)> - void doFloatTypeConversion(T &LHS, QualType <y, T &RHS, - QualType &RTy) const; - - // Callback function for doCast parameter on APSInt type. - static llvm::APSInt castAPSInt(const llvm::APSInt &V, QualType ToTy, - uint64_t ToWidth, QualType FromTy, - uint64_t FromWidth); -}; // end class Z3ConstraintManager + SMTExprRef mkBVConcat(const SMTExprRef &LHS, const SMTExprRef &RHS) override { + return newExprRef( + Z3Expr(Context, Z3_mk_concat(Context.Context, toZ3Expr(*LHS).AST, + toZ3Expr(*RHS).AST))); + } -Z3_context Z3Context::ZC; + SMTExprRef mkFPtoFP(const SMTExprRef &From, const SMTSortRef &To) override { + SMTExprRef RoundingMode = getFloatRoundingMode(); + return newExprRef(Z3Expr( + Context, + Z3_mk_fpa_to_fp_float(Context.Context, toZ3Expr(*RoundingMode).AST, + toZ3Expr(*From).AST, toZ3Sort(*To).Sort))); + } -} // end anonymous namespace + SMTExprRef mkFPtoSBV(const SMTExprRef &From, const SMTSortRef &To) override { + SMTExprRef RoundingMode = getFloatRoundingMode(); + return newExprRef(Z3Expr( + Context, + Z3_mk_fpa_to_fp_signed(Context.Context, toZ3Expr(*RoundingMode).AST, + toZ3Expr(*From).AST, toZ3Sort(*To).Sort))); + } -ProgramStateRef Z3ConstraintManager::assumeSym(ProgramStateRef State, - SymbolRef Sym, bool Assumption) { - QualType RetTy; - bool hasComparison; + SMTExprRef mkFPtoUBV(const SMTExprRef &From, const SMTSortRef &To) override { + SMTExprRef RoundingMode = getFloatRoundingMode(); + return newExprRef(Z3Expr( + Context, + Z3_mk_fpa_to_fp_unsigned(Context.Context, toZ3Expr(*RoundingMode).AST, + toZ3Expr(*From).AST, toZ3Sort(*To).Sort))); + } - Z3Expr Exp = getZ3Expr(Sym, &RetTy, &hasComparison); - // Create zero comparison for implicit boolean cast, with reversed assumption - if (!hasComparison && !RetTy->isBooleanType()) - return assumeZ3Expr(State, Sym, getZ3ZeroExpr(Exp, RetTy, !Assumption)); + SMTExprRef mkSBVtoFP(const SMTExprRef &From, unsigned ToWidth) override { + SMTExprRef RoundingMode = getFloatRoundingMode(); + return newExprRef(Z3Expr( + Context, Z3_mk_fpa_to_sbv(Context.Context, toZ3Expr(*RoundingMode).AST, + toZ3Expr(*From).AST, ToWidth))); + } - return assumeZ3Expr(State, Sym, Assumption ? Exp : getZ3NotExpr(Exp)); -} + SMTExprRef mkUBVtoFP(const SMTExprRef &From, unsigned ToWidth) override { + SMTExprRef RoundingMode = getFloatRoundingMode(); + return newExprRef(Z3Expr( + Context, Z3_mk_fpa_to_ubv(Context.Context, toZ3Expr(*RoundingMode).AST, + toZ3Expr(*From).AST, ToWidth))); + } -ProgramStateRef Z3ConstraintManager::assumeSymInclusiveRange( - ProgramStateRef State, SymbolRef Sym, const llvm::APSInt &From, - const llvm::APSInt &To, bool InRange) { - QualType RetTy; - // The expression may be casted, so we cannot call getZ3DataExpr() directly - Z3Expr Exp = getZ3Expr(Sym, &RetTy); - - assert((getAPSIntType(From) == getAPSIntType(To)) && - "Range values have different types!"); - QualType RTy = getAPSIntType(From); - bool isSignedTy = RetTy->isSignedIntegerOrEnumerationType(); - Z3Expr FromExp = Z3Expr::fromAPSInt(From); - Z3Expr ToExp = Z3Expr::fromAPSInt(To); - - // Construct single (in)equality - if (From == To) - return assumeZ3Expr(State, Sym, - getZ3BinExpr(Exp, RetTy, InRange ? BO_EQ : BO_NE, - FromExp, RTy, nullptr)); - - // Construct two (in)equalities, and a logical and/or - Z3Expr LHS = - getZ3BinExpr(Exp, RetTy, InRange ? BO_GE : BO_LT, FromExp, RTy, nullptr); - Z3Expr RHS = - getZ3BinExpr(Exp, RetTy, InRange ? BO_LE : BO_GT, ToExp, RTy, nullptr); - return assumeZ3Expr( - State, Sym, - Z3Expr::fromBinOp(LHS, InRange ? BO_LAnd : BO_LOr, RHS, isSignedTy)); -} + SMTExprRef mkBoolean(const bool b) override { + return newExprRef(Z3Expr(Context, b ? Z3_mk_true(Context.Context) + : Z3_mk_false(Context.Context))); + } -ProgramStateRef Z3ConstraintManager::assumeSymUnsupported(ProgramStateRef State, - SymbolRef Sym, - bool Assumption) { - // Skip anything that is unsupported - return State; -} + SMTExprRef mkBitvector(const llvm::APSInt Int, unsigned BitWidth) override { + const SMTSortRef Sort = getBitvectorSort(BitWidth); + return newExprRef( + Z3Expr(Context, Z3_mk_numeral(Context.Context, Int.toString(10).c_str(), + toZ3Sort(*Sort).Sort))); + } -bool Z3ConstraintManager::canReasonAbout(SVal X) const { - const TargetInfo &TI = getBasicVals().getContext().getTargetInfo(); + SMTExprRef mkFloat(const llvm::APFloat Float) override { + SMTSortRef Sort = + getFloatSort(llvm::APFloat::semanticsSizeInBits(Float.getSemantics())); - Optional<nonloc::SymbolVal> SymVal = X.getAs<nonloc::SymbolVal>(); - if (!SymVal) - return true; + llvm::APSInt Int = llvm::APSInt(Float.bitcastToAPInt(), false); + SMTExprRef Z3Int = mkBitvector(Int, Int.getBitWidth()); + return newExprRef(Z3Expr( + Context, Z3_mk_fpa_to_fp_bv(Context.Context, toZ3Expr(*Z3Int).AST, + toZ3Sort(*Sort).Sort))); + } - const SymExpr *Sym = SymVal->getSymbol(); - do { - QualType Ty = Sym->getType(); + SMTExprRef mkSymbol(const char *Name, SMTSortRef Sort) override { + return newExprRef( + Z3Expr(Context, Z3_mk_const(Context.Context, + Z3_mk_string_symbol(Context.Context, Name), + toZ3Sort(*Sort).Sort))); + } - // Complex types are not modeled - if (Ty->isComplexType() || Ty->isComplexIntegerType()) - return false; + llvm::APSInt getBitvector(const SMTExprRef &Exp, unsigned BitWidth, + bool isUnsigned) override { + return llvm::APSInt(llvm::APInt( + BitWidth, Z3_get_numeral_string(Context.Context, toZ3Expr(*Exp).AST), + 10)); + } - // Non-IEEE 754 floating-point types are not modeled - if ((Ty->isSpecificBuiltinType(BuiltinType::LongDouble) && - (&TI.getLongDoubleFormat() == &llvm::APFloat::x87DoubleExtended() || - &TI.getLongDoubleFormat() == &llvm::APFloat::PPCDoubleDouble()))) - return false; + bool getBoolean(const SMTExprRef &Exp) override { + return Z3_get_bool_value(Context.Context, toZ3Expr(*Exp).AST) == Z3_L_TRUE; + } - if (isa<SymbolData>(Sym)) { - break; - } else if (const SymbolCast *SC = dyn_cast<SymbolCast>(Sym)) { - Sym = SC->getOperand(); - } else if (const BinarySymExpr *BSE = dyn_cast<BinarySymExpr>(Sym)) { - if (const SymIntExpr *SIE = dyn_cast<SymIntExpr>(BSE)) { - Sym = SIE->getLHS(); - } else if (const IntSymExpr *ISE = dyn_cast<IntSymExpr>(BSE)) { - Sym = ISE->getRHS(); - } else if (const SymSymExpr *SSM = dyn_cast<SymSymExpr>(BSE)) { - return canReasonAbout(nonloc::SymbolVal(SSM->getLHS())) && - canReasonAbout(nonloc::SymbolVal(SSM->getRHS())); - } else { - llvm_unreachable("Unsupported binary expression to reason about!"); - } - } else { - llvm_unreachable("Unsupported expression to reason about!"); - } - } while (Sym); + SMTExprRef getFloatRoundingMode() override { + // TODO: Don't assume nearest ties to even rounding mode + return newExprRef(Z3Expr(Context, Z3_mk_fpa_rne(Context.Context))); + } - return true; -} + SMTExprRef fromData(const SymbolID ID, const QualType &Ty, + uint64_t BitWidth) override { + llvm::Twine Name = "$" + llvm::Twine(ID); + return mkSymbol(Name.str().c_str(), mkSort(Ty, BitWidth)); + } + + SMTExprRef fromBoolean(const bool Bool) override { + Z3_ast AST = + Bool ? Z3_mk_true(Context.Context) : Z3_mk_false(Context.Context); + return newExprRef(Z3Expr(Context, AST)); + } + + SMTExprRef fromAPFloat(const llvm::APFloat &Float) override { + SMTSortRef Sort = + getFloatSort(llvm::APFloat::semanticsSizeInBits(Float.getSemantics())); -ConditionTruthVal Z3ConstraintManager::checkNull(ProgramStateRef State, - SymbolRef Sym) { - QualType RetTy; - // The expression may be casted, so we cannot call getZ3DataExpr() directly - Z3Expr VarExp = getZ3Expr(Sym, &RetTy); - Z3Expr Exp = getZ3ZeroExpr(VarExp, RetTy, true); - // Negate the constraint - Z3Expr NotExp = getZ3ZeroExpr(VarExp, RetTy, false); + llvm::APSInt Int = llvm::APSInt(Float.bitcastToAPInt(), false); + SMTExprRef Z3Int = fromAPSInt(Int); + return newExprRef(Z3Expr( + Context, Z3_mk_fpa_to_fp_bv(Context.Context, toZ3Expr(*Z3Int).AST, + toZ3Sort(*Sort).Sort))); + } - Solver.reset(); - Solver.addStateConstraints(State); + SMTExprRef fromAPSInt(const llvm::APSInt &Int) override { + SMTSortRef Sort = getBitvectorSort(Int.getBitWidth()); + Z3_ast AST = Z3_mk_numeral(Context.Context, Int.toString(10).c_str(), + toZ3Sort(*Sort).Sort); + return newExprRef(Z3Expr(Context, AST)); + } - Solver.push(); - Solver.addConstraint(Exp); - Z3_lbool isSat = Solver.check(); + SMTExprRef fromInt(const char *Int, uint64_t BitWidth) override { + SMTSortRef Sort = getBitvectorSort(BitWidth); + Z3_ast AST = Z3_mk_numeral(Context.Context, Int, toZ3Sort(*Sort).Sort); + return newExprRef(Z3Expr(Context, AST)); + } - Solver.pop(); - Solver.addConstraint(NotExp); - Z3_lbool isNotSat = Solver.check(); + bool toAPFloat(const SMTSortRef &Sort, const SMTExprRef &AST, + llvm::APFloat &Float, bool useSemantics) { + assert(Sort->isFloatSort() && "Unsupported sort to floating-point!"); - // Zero is the only possible solution - if (isSat == Z3_L_TRUE && isNotSat == Z3_L_FALSE) + llvm::APSInt Int(Sort->getFloatSortSize(), true); + const llvm::fltSemantics &Semantics = + getFloatSemantics(Sort->getFloatSortSize()); + SMTSortRef BVSort = getBitvectorSort(Sort->getFloatSortSize()); + if (!toAPSInt(BVSort, AST, Int, true)) { + return false; + } + + if (useSemantics && !areEquivalent(Float.getSemantics(), Semantics)) { + assert(false && "Floating-point types don't match!"); + return false; + } + + Float = llvm::APFloat(Semantics, Int); return true; - // Zero is not a solution - else if (isSat == Z3_L_FALSE && isNotSat == Z3_L_TRUE) - return false; + } - // Zero may be a solution - return ConditionTruthVal(); -} + bool toAPSInt(const SMTSortRef &Sort, const SMTExprRef &AST, + llvm::APSInt &Int, bool useSemantics) { + if (Sort->isBitvectorSort()) { + if (useSemantics && Int.getBitWidth() != Sort->getBitvectorSortSize()) { + assert(false && "Bitvector types don't match!"); + return false; + } -const llvm::APSInt *Z3ConstraintManager::getSymVal(ProgramStateRef State, - SymbolRef Sym) const { - BasicValueFactory &BV = getBasicVals(); - ASTContext &Ctx = BV.getContext(); + // FIXME: This function is also used to retrieve floating-point values, + // which can be 16, 32, 64 or 128 bits long. Bitvectors can be anything + // between 1 and 64 bits long, which is the reason we have this weird + // guard. In the future, we need proper calls in the backend to retrieve + // floating-points and its special values (NaN, +/-infinity, +/-zero), + // then we can drop this weird condition. + if (Sort->getBitvectorSortSize() <= 64 || + Sort->getBitvectorSortSize() == 128) { + Int = getBitvector(AST, Int.getBitWidth(), Int.isUnsigned()); + return true; + } - if (const SymbolData *SD = dyn_cast<SymbolData>(Sym)) { - QualType Ty = Sym->getType(); - assert(!Ty->isRealFloatingType()); - llvm::APSInt Value(Ctx.getTypeSize(Ty), - !Ty->isSignedIntegerOrEnumerationType()); - - Z3Expr Exp = getZ3DataExpr(SD->getSymbolID(), Ty); - - Solver.reset(); - Solver.addStateConstraints(State); - - // Constraints are unsatisfiable - if (Solver.check() != Z3_L_TRUE) - return nullptr; - - Z3Model Model = Solver.getModel(); - // Model does not assign interpretation - if (!Model.getInterpretation(Exp, Value)) - return nullptr; - - // A value has been obtained, check if it is the only value - Z3Expr NotExp = Z3Expr::fromBinOp( - Exp, BO_NE, - Ty->isBooleanType() ? Z3Expr::fromBoolean(Value.getBoolValue()) - : Z3Expr::fromAPSInt(Value), - false); - - Solver.addConstraint(NotExp); - if (Solver.check() == Z3_L_TRUE) - return nullptr; - - // This is the only solution, store it - return &BV.getValue(Value); - } else if (const SymbolCast *SC = dyn_cast<SymbolCast>(Sym)) { - SymbolRef CastSym = SC->getOperand(); - QualType CastTy = SC->getType(); - // Skip the void type - if (CastTy->isVoidType()) - return nullptr; - - const llvm::APSInt *Value; - if (!(Value = getSymVal(State, CastSym))) - return nullptr; - return &BV.Convert(SC->getType(), *Value); - } else if (const BinarySymExpr *BSE = dyn_cast<BinarySymExpr>(Sym)) { - const llvm::APSInt *LHS, *RHS; - if (const SymIntExpr *SIE = dyn_cast<SymIntExpr>(BSE)) { - LHS = getSymVal(State, SIE->getLHS()); - RHS = &SIE->getRHS(); - } else if (const IntSymExpr *ISE = dyn_cast<IntSymExpr>(BSE)) { - LHS = &ISE->getLHS(); - RHS = getSymVal(State, ISE->getRHS()); - } else if (const SymSymExpr *SSM = dyn_cast<SymSymExpr>(BSE)) { - // Early termination to avoid expensive call - LHS = getSymVal(State, SSM->getLHS()); - RHS = LHS ? getSymVal(State, SSM->getRHS()) : nullptr; - } else { - llvm_unreachable("Unsupported binary expression to get symbol value!"); + assert(false && "Bitwidth not supported!"); + return false; } - if (!LHS || !RHS) - return nullptr; + if (Sort->isBooleanSort()) { + if (useSemantics && Int.getBitWidth() < 1) { + assert(false && "Boolean type doesn't match!"); + return false; + } - llvm::APSInt ConvertedLHS = *LHS, ConvertedRHS = *RHS; - QualType LTy = getAPSIntType(*LHS), RTy = getAPSIntType(*RHS); - doIntTypeConversion<llvm::APSInt, Z3ConstraintManager::castAPSInt>( - ConvertedLHS, LTy, ConvertedRHS, RTy); - return BV.evalAPSInt(BSE->getOpcode(), ConvertedLHS, ConvertedRHS); - } + Int = llvm::APSInt(llvm::APInt(Int.getBitWidth(), getBoolean(AST)), + Int.isUnsigned()); + return true; + } - llvm_unreachable("Unsupported expression to get symbol value!"); -} + llvm_unreachable("Unsupported sort to integer!"); + } -ProgramStateRef -Z3ConstraintManager::removeDeadBindings(ProgramStateRef State, - SymbolReaper &SymReaper) { - ConstraintZ3Ty CZ = State->get<ConstraintZ3>(); - ConstraintZ3Ty::Factory &CZFactory = State->get_context<ConstraintZ3>(); + bool getInterpretation(const SMTExprRef &Exp, llvm::APSInt &Int) override { + Z3Model Model = getModel(); + Z3_func_decl Func = Z3_get_app_decl( + Context.Context, Z3_to_app(Context.Context, toZ3Expr(*Exp).AST)); + if (Z3_model_has_interp(Context.Context, Model.Model, Func) != Z3_L_TRUE) + return false; - for (ConstraintZ3Ty::iterator I = CZ.begin(), E = CZ.end(); I != E; ++I) { - if (SymReaper.maybeDead(I->first)) - CZ = CZFactory.remove(CZ, *I); + SMTExprRef Assign = newExprRef( + Z3Expr(Context, + Z3_model_get_const_interp(Context.Context, Model.Model, Func))); + SMTSortRef Sort = getSort(Assign); + return toAPSInt(Sort, Assign, Int, true); } - return State->set<ConstraintZ3>(CZ); -} + bool getInterpretation(const SMTExprRef &Exp, llvm::APFloat &Float) override { + Z3Model Model = getModel(); + Z3_func_decl Func = Z3_get_app_decl( + Context.Context, Z3_to_app(Context.Context, toZ3Expr(*Exp).AST)); + if (Z3_model_has_interp(Context.Context, Model.Model, Func) != Z3_L_TRUE) + return false; -//===------------------------------------------------------------------===// -// Internal implementation. -//===------------------------------------------------------------------===// + SMTExprRef Assign = newExprRef( + Z3Expr(Context, + Z3_model_get_const_interp(Context.Context, Model.Model, Func))); + SMTSortRef Sort = getSort(Assign); + return toAPFloat(Sort, Assign, Float, true); + } -ProgramStateRef Z3ConstraintManager::assumeZ3Expr(ProgramStateRef State, - SymbolRef Sym, - const Z3Expr &Exp) { - // Check the model, avoid simplifying AST to save time - if (checkZ3Model(State, Exp) == Z3_L_TRUE) - return State->add<ConstraintZ3>(std::make_pair(Sym, Exp)); + ConditionTruthVal check() const override { + Z3_lbool res = Z3_solver_check(Context.Context, Solver); + if (res == Z3_L_TRUE) + return true; - return nullptr; -} + if (res == Z3_L_FALSE) + return false; -Z3_lbool Z3ConstraintManager::checkZ3Model(ProgramStateRef State, - const Z3Expr &Exp) const { - Solver.reset(); - Solver.addConstraint(Exp); - Solver.addStateConstraints(State); - return Solver.check(); -} + return ConditionTruthVal(); + } -Z3Expr Z3ConstraintManager::getZ3Expr(SymbolRef Sym, QualType *RetTy, - bool *hasComparison) const { - if (hasComparison) { - *hasComparison = false; + void push() override { return Z3_solver_push(Context.Context, Solver); } + + void pop(unsigned NumStates = 1) override { + assert(Z3_solver_get_num_scopes(Context.Context, Solver) >= NumStates); + return Z3_solver_pop(Context.Context, Solver, NumStates); } - return getZ3SymExpr(Sym, RetTy, hasComparison); -} + /// Get a model from the solver. Caller should check the model is + /// satisfiable. + Z3Model getModel() { + return Z3Model(Context, Z3_solver_get_model(Context.Context, Solver)); + } -Z3Expr Z3ConstraintManager::getZ3NotExpr(const Z3Expr &Exp) const { - return Z3Expr::fromUnOp(UO_LNot, Exp); -} + /// Reset the solver and remove all constraints. + void reset() const override { Z3_solver_reset(Context.Context, Solver); } -Z3Expr Z3ConstraintManager::getZ3ZeroExpr(const Z3Expr &Exp, QualType Ty, - bool Assumption) const { - ASTContext &Ctx = getBasicVals().getContext(); - if (Ty->isRealFloatingType()) { - llvm::APFloat Zero = llvm::APFloat::getZero(Ctx.getFloatTypeSemantics(Ty)); - return Z3Expr::fromFloatBinOp(Exp, Assumption ? BO_EQ : BO_NE, - Z3Expr::fromAPFloat(Zero)); - } else if (Ty->isIntegralOrEnumerationType() || Ty->isAnyPointerType() || - Ty->isBlockPointerType() || Ty->isReferenceType()) { - bool isSigned = Ty->isSignedIntegerOrEnumerationType(); - // Skip explicit comparison for boolean types - if (Ty->isBooleanType()) - return Assumption ? getZ3NotExpr(Exp) : Exp; - return Z3Expr::fromBinOp(Exp, Assumption ? BO_EQ : BO_NE, - Z3Expr::fromInt("0", Ctx.getTypeSize(Ty)), - isSigned); - } - - llvm_unreachable("Unsupported type for zero value!"); -} + void print(raw_ostream &OS) const override { + OS << Z3_solver_to_string(Context.Context, Solver); + } +}; // end class Z3Solver -Z3Expr Z3ConstraintManager::getZ3SymExpr(SymbolRef Sym, QualType *RetTy, - bool *hasComparison) const { - if (const SymbolData *SD = dyn_cast<SymbolData>(Sym)) { - if (RetTy) - *RetTy = Sym->getType(); - - return getZ3DataExpr(SD->getSymbolID(), Sym->getType()); - } else if (const SymbolCast *SC = dyn_cast<SymbolCast>(Sym)) { - if (RetTy) - *RetTy = Sym->getType(); - - QualType FromTy; - Z3Expr Exp = getZ3SymExpr(SC->getOperand(), &FromTy, hasComparison); - // Casting an expression with a comparison invalidates it. Note that this - // must occur after the recursive call above. - // e.g. (signed char) (x > 0) - if (hasComparison) - *hasComparison = false; - return getZ3CastExpr(Exp, FromTy, Sym->getType()); - } else if (const BinarySymExpr *BSE = dyn_cast<BinarySymExpr>(Sym)) { - Z3Expr Exp = getZ3SymBinExpr(BSE, hasComparison, RetTy); - // Set the hasComparison parameter, in post-order traversal order. - if (hasComparison) - *hasComparison = BinaryOperator::isComparisonOp(BSE->getOpcode()); - return Exp; - } - - llvm_unreachable("Unsupported SymbolRef type!"); -} +class Z3ConstraintManager : public SMTConstraintManager { + SMTSolverRef Solver = CreateZ3Solver(); -Z3Expr Z3ConstraintManager::getZ3DataExpr(const SymbolID ID, - QualType Ty) const { - ASTContext &Ctx = getBasicVals().getContext(); - return Z3Expr::fromData(ID, Ty->isBooleanType(), Ty->isRealFloatingType(), - Ctx.getTypeSize(Ty)); -} +public: + Z3ConstraintManager(SubEngine *SE, SValBuilder &SB) + : SMTConstraintManager(SE, SB, Solver) {} -Z3Expr Z3ConstraintManager::getZ3CastExpr(const Z3Expr &Exp, QualType FromTy, - QualType ToTy) const { - ASTContext &Ctx = getBasicVals().getContext(); - return Z3Expr::fromCast(Exp, ToTy, Ctx.getTypeSize(ToTy), FromTy, - Ctx.getTypeSize(FromTy)); -} + void addStateConstraints(ProgramStateRef State) const override { + // TODO: Don't add all the constraints, only the relevant ones + ConstraintZ3Ty CZ = State->get<ConstraintZ3>(); + ConstraintZ3Ty::iterator I = CZ.begin(), IE = CZ.end(); -Z3Expr Z3ConstraintManager::getZ3SymBinExpr(const BinarySymExpr *BSE, - bool *hasComparison, - QualType *RetTy) const { - QualType LTy, RTy; - BinaryOperator::Opcode Op = BSE->getOpcode(); - - if (const SymIntExpr *SIE = dyn_cast<SymIntExpr>(BSE)) { - RTy = getAPSIntType(SIE->getRHS()); - Z3Expr LHS = getZ3SymExpr(SIE->getLHS(), <y, hasComparison); - Z3Expr RHS = Z3Expr::fromAPSInt(SIE->getRHS()); - return getZ3BinExpr(LHS, LTy, Op, RHS, RTy, RetTy); - } else if (const IntSymExpr *ISE = dyn_cast<IntSymExpr>(BSE)) { - LTy = getAPSIntType(ISE->getLHS()); - Z3Expr LHS = Z3Expr::fromAPSInt(ISE->getLHS()); - Z3Expr RHS = getZ3SymExpr(ISE->getRHS(), &RTy, hasComparison); - return getZ3BinExpr(LHS, LTy, Op, RHS, RTy, RetTy); - } else if (const SymSymExpr *SSM = dyn_cast<SymSymExpr>(BSE)) { - Z3Expr LHS = getZ3SymExpr(SSM->getLHS(), <y, hasComparison); - Z3Expr RHS = getZ3SymExpr(SSM->getRHS(), &RTy, hasComparison); - return getZ3BinExpr(LHS, LTy, Op, RHS, RTy, RetTy); - } else { - llvm_unreachable("Unsupported BinarySymExpr type!"); - } -} + // Construct the logical AND of all the constraints + if (I != IE) { + std::vector<SMTExprRef> ASTs; -Z3Expr Z3ConstraintManager::getZ3BinExpr(const Z3Expr &LHS, QualType LTy, - BinaryOperator::Opcode Op, - const Z3Expr &RHS, QualType RTy, - QualType *RetTy) const { - Z3Expr NewLHS = LHS; - Z3Expr NewRHS = RHS; - doTypeConversion(NewLHS, NewRHS, LTy, RTy); - // Update the return type parameter if the output type has changed. - if (RetTy) { - // A boolean result can be represented as an integer type in C/C++, but at - // this point we only care about the Z3 type. Set it as a boolean type to - // avoid subsequent Z3 errors. - if (BinaryOperator::isComparisonOp(Op) || BinaryOperator::isLogicalOp(Op)) { - ASTContext &Ctx = getBasicVals().getContext(); - *RetTy = Ctx.BoolTy; - } else { - *RetTy = LTy; - } + SMTExprRef Constraint = Solver->newExprRef(I++->second); + while (I != IE) { + Constraint = Solver->mkAnd(Constraint, Solver->newExprRef(I++->second)); + } - // If the two operands are pointers and the operation is a subtraction, the - // result is of type ptrdiff_t, which is signed - if (LTy->isAnyPointerType() && LTy == RTy && Op == BO_Sub) { - ASTContext &Ctx = getBasicVals().getContext(); - *RetTy = Ctx.getIntTypeForBitwidth(Ctx.getTypeSize(LTy), true); + Solver->addConstraint(Constraint); } } - return LTy->isRealFloatingType() - ? Z3Expr::fromFloatBinOp(NewLHS, Op, NewRHS) - : Z3Expr::fromBinOp(NewLHS, Op, NewRHS, - LTy->isSignedIntegerOrEnumerationType()); -} + bool canReasonAbout(SVal X) const override { + const TargetInfo &TI = getBasicVals().getContext().getTargetInfo(); -//===------------------------------------------------------------------===// -// Helper functions. -//===------------------------------------------------------------------===// + Optional<nonloc::SymbolVal> SymVal = X.getAs<nonloc::SymbolVal>(); + if (!SymVal) + return true; -QualType Z3ConstraintManager::getAPSIntType(const llvm::APSInt &Int) const { - ASTContext &Ctx = getBasicVals().getContext(); - return Ctx.getIntTypeForBitwidth(Int.getBitWidth(), Int.isSigned()); -} + const SymExpr *Sym = SymVal->getSymbol(); + QualType Ty = Sym->getType(); -void Z3ConstraintManager::doTypeConversion(Z3Expr &LHS, Z3Expr &RHS, - QualType <y, QualType &RTy) const { - ASTContext &Ctx = getBasicVals().getContext(); - - // Perform type conversion - if (LTy->isIntegralOrEnumerationType() && - RTy->isIntegralOrEnumerationType()) { - if (LTy->isArithmeticType() && RTy->isArithmeticType()) - return doIntTypeConversion<Z3Expr, Z3Expr::fromCast>(LHS, LTy, RHS, RTy); - } else if (LTy->isRealFloatingType() || RTy->isRealFloatingType()) { - return doFloatTypeConversion<Z3Expr, Z3Expr::fromCast>(LHS, LTy, RHS, RTy); - } else if ((LTy->isAnyPointerType() || RTy->isAnyPointerType()) || - (LTy->isBlockPointerType() || RTy->isBlockPointerType()) || - (LTy->isReferenceType() || RTy->isReferenceType())) { - // TODO: Refactor to Sema::FindCompositePointerType(), and - // Sema::CheckCompareOperands(). - - uint64_t LBitWidth = Ctx.getTypeSize(LTy); - uint64_t RBitWidth = Ctx.getTypeSize(RTy); - - // Cast the non-pointer type to the pointer type. - // TODO: Be more strict about this. - if ((LTy->isAnyPointerType() ^ RTy->isAnyPointerType()) || - (LTy->isBlockPointerType() ^ RTy->isBlockPointerType()) || - (LTy->isReferenceType() ^ RTy->isReferenceType())) { - if (LTy->isNullPtrType() || LTy->isBlockPointerType() || - LTy->isReferenceType()) { - LHS = Z3Expr::fromCast(LHS, RTy, RBitWidth, LTy, LBitWidth); - LTy = RTy; - } else { - RHS = Z3Expr::fromCast(RHS, LTy, LBitWidth, RTy, RBitWidth); - RTy = LTy; - } - } + // Complex types are not modeled + if (Ty->isComplexType() || Ty->isComplexIntegerType()) + return false; - // Cast the void pointer type to the non-void pointer type. - // For void types, this assumes that the casted value is equal to the value - // of the original pointer, and does not account for alignment requirements. - if (LTy->isVoidPointerType() ^ RTy->isVoidPointerType()) { - assert((Ctx.getTypeSize(LTy) == Ctx.getTypeSize(RTy)) && - "Pointer types have different bitwidths!"); - if (RTy->isVoidPointerType()) - RTy = LTy; - else - LTy = RTy; - } + // Non-IEEE 754 floating-point types are not modeled + if ((Ty->isSpecificBuiltinType(BuiltinType::LongDouble) && + (&TI.getLongDoubleFormat() == &llvm::APFloat::x87DoubleExtended() || + &TI.getLongDoubleFormat() == &llvm::APFloat::PPCDoubleDouble()))) + return false; - if (LTy == RTy) - return; - } + if (isa<SymbolData>(Sym)) + return true; - // Fallback: for the solver, assume that these types don't really matter - if ((LTy.getCanonicalType() == RTy.getCanonicalType()) || - (LTy->isObjCObjectPointerType() && RTy->isObjCObjectPointerType())) { - LTy = RTy; - return; - } + SValBuilder &SVB = getSValBuilder(); - // TODO: Refine behavior for invalid type casts -} + if (const SymbolCast *SC = dyn_cast<SymbolCast>(Sym)) + return canReasonAbout(SVB.makeSymbolVal(SC->getOperand())); -template <typename T, - T(doCast)(const T &, QualType, uint64_t, QualType, uint64_t)> -void Z3ConstraintManager::doIntTypeConversion(T &LHS, QualType <y, T &RHS, - QualType &RTy) const { - ASTContext &Ctx = getBasicVals().getContext(); - - uint64_t LBitWidth = Ctx.getTypeSize(LTy); - uint64_t RBitWidth = Ctx.getTypeSize(RTy); - - // Always perform integer promotion before checking type equality. - // Otherwise, e.g. (bool) a + (bool) b could trigger a backend assertion - if (LTy->isPromotableIntegerType()) { - QualType NewTy = Ctx.getPromotedIntegerType(LTy); - uint64_t NewBitWidth = Ctx.getTypeSize(NewTy); - LHS = (*doCast)(LHS, NewTy, NewBitWidth, LTy, LBitWidth); - LTy = NewTy; - LBitWidth = NewBitWidth; - } - if (RTy->isPromotableIntegerType()) { - QualType NewTy = Ctx.getPromotedIntegerType(RTy); - uint64_t NewBitWidth = Ctx.getTypeSize(NewTy); - RHS = (*doCast)(RHS, NewTy, NewBitWidth, RTy, RBitWidth); - RTy = NewTy; - RBitWidth = NewBitWidth; - } - - if (LTy == RTy) - return; - - // Perform integer type conversion - // Note: Safe to skip updating bitwidth because this must terminate - bool isLSignedTy = LTy->isSignedIntegerOrEnumerationType(); - bool isRSignedTy = RTy->isSignedIntegerOrEnumerationType(); - - int order = Ctx.getIntegerTypeOrder(LTy, RTy); - if (isLSignedTy == isRSignedTy) { - // Same signedness; use the higher-ranked type - if (order == 1) { - RHS = (*doCast)(RHS, LTy, LBitWidth, RTy, RBitWidth); - RTy = LTy; - } else { - LHS = (*doCast)(LHS, RTy, RBitWidth, LTy, LBitWidth); - LTy = RTy; - } - } else if (order != (isLSignedTy ? 1 : -1)) { - // The unsigned type has greater than or equal rank to the - // signed type, so use the unsigned type - if (isRSignedTy) { - RHS = (*doCast)(RHS, LTy, LBitWidth, RTy, RBitWidth); - RTy = LTy; - } else { - LHS = (*doCast)(LHS, RTy, RBitWidth, LTy, LBitWidth); - LTy = RTy; - } - } else if (LBitWidth != RBitWidth) { - // The two types are different widths; if we are here, that - // means the signed type is larger than the unsigned type, so - // use the signed type. - if (isLSignedTy) { - RHS = (*doCast)(RHS, LTy, LBitWidth, RTy, RBitWidth); - RTy = LTy; - } else { - LHS = (*doCast)(LHS, RTy, RBitWidth, LTy, LBitWidth); - LTy = RTy; + if (const BinarySymExpr *BSE = dyn_cast<BinarySymExpr>(Sym)) { + if (const SymIntExpr *SIE = dyn_cast<SymIntExpr>(BSE)) + return canReasonAbout(SVB.makeSymbolVal(SIE->getLHS())); + + if (const IntSymExpr *ISE = dyn_cast<IntSymExpr>(BSE)) + return canReasonAbout(SVB.makeSymbolVal(ISE->getRHS())); + + if (const SymSymExpr *SSE = dyn_cast<SymSymExpr>(BSE)) + return canReasonAbout(SVB.makeSymbolVal(SSE->getLHS())) && + canReasonAbout(SVB.makeSymbolVal(SSE->getRHS())); } - } else { - // The signed type is higher-ranked than the unsigned type, - // but isn't actually any bigger (like unsigned int and long - // on most 32-bit systems). Use the unsigned type corresponding - // to the signed type. - QualType NewTy = Ctx.getCorrespondingUnsignedType(isLSignedTy ? LTy : RTy); - RHS = (*doCast)(RHS, LTy, LBitWidth, RTy, RBitWidth); - RTy = NewTy; - LHS = (*doCast)(LHS, RTy, RBitWidth, LTy, LBitWidth); - LTy = NewTy; + + llvm_unreachable("Unsupported expression to reason about!"); } -} -template <typename T, - T(doCast)(const T &, QualType, uint64_t, QualType, uint64_t)> -void Z3ConstraintManager::doFloatTypeConversion(T &LHS, QualType <y, T &RHS, - QualType &RTy) const { - ASTContext &Ctx = getBasicVals().getContext(); - - uint64_t LBitWidth = Ctx.getTypeSize(LTy); - uint64_t RBitWidth = Ctx.getTypeSize(RTy); - - // Perform float-point type promotion - if (!LTy->isRealFloatingType()) { - LHS = (*doCast)(LHS, RTy, RBitWidth, LTy, LBitWidth); - LTy = RTy; - LBitWidth = RBitWidth; - } - if (!RTy->isRealFloatingType()) { - RHS = (*doCast)(RHS, LTy, LBitWidth, RTy, RBitWidth); - RTy = LTy; - RBitWidth = LBitWidth; - } - - if (LTy == RTy) - return; - - // If we have two real floating types, convert the smaller operand to the - // bigger result - // Note: Safe to skip updating bitwidth because this must terminate - int order = Ctx.getFloatingTypeOrder(LTy, RTy); - if (order > 0) { - RHS = Z3Expr::fromCast(RHS, LTy, LBitWidth, RTy, RBitWidth); - RTy = LTy; - } else if (order == 0) { - LHS = Z3Expr::fromCast(LHS, RTy, RBitWidth, LTy, LBitWidth); - LTy = RTy; - } else { - llvm_unreachable("Unsupported floating-point type cast!"); + ProgramStateRef removeDeadBindings(ProgramStateRef State, + SymbolReaper &SymReaper) override { + ConstraintZ3Ty CZ = State->get<ConstraintZ3>(); + ConstraintZ3Ty::Factory &CZFactory = State->get_context<ConstraintZ3>(); + + for (ConstraintZ3Ty::iterator I = CZ.begin(), E = CZ.end(); I != E; ++I) { + if (SymReaper.maybeDead(I->first)) + CZ = CZFactory.remove(CZ, *I); + } + + return State->set<ConstraintZ3>(CZ); } -} -llvm::APSInt Z3ConstraintManager::castAPSInt(const llvm::APSInt &V, - QualType ToTy, uint64_t ToWidth, - QualType FromTy, - uint64_t FromWidth) { - APSIntType TargetType(ToWidth, !ToTy->isSignedIntegerOrEnumerationType()); - return TargetType.convert(V); -} + ProgramStateRef assumeExpr(ProgramStateRef State, SymbolRef Sym, + const SMTExprRef &Exp) override { + // Check the model, avoid simplifying AST to save time + if (checkModel(State, Exp).isConstrainedTrue()) + return State->add<ConstraintZ3>(std::make_pair(Sym, toZ3Expr(*Exp))); + + return nullptr; + } -//==------------------------------------------------------------------------==/ -// Pretty-printing. -//==------------------------------------------------------------------------==/ + //==------------------------------------------------------------------------==/ + // Pretty-printing. + //==------------------------------------------------------------------------==/ -void Z3ConstraintManager::print(ProgramStateRef St, raw_ostream &OS, - const char *nl, const char *sep) { + void print(ProgramStateRef St, raw_ostream &OS, const char *nl, + const char *sep) override { - ConstraintZ3Ty CZ = St->get<ConstraintZ3>(); + ConstraintZ3Ty CZ = St->get<ConstraintZ3>(); - OS << nl << sep << "Constraints:"; - for (ConstraintZ3Ty::iterator I = CZ.begin(), E = CZ.end(); I != E; ++I) { - OS << nl << ' ' << I->first << " : "; - I->second.print(OS); + OS << nl << sep << "Constraints:"; + for (ConstraintZ3Ty::iterator I = CZ.begin(), E = CZ.end(); I != E; ++I) { + OS << nl << ' ' << I->first << " : "; + I->second.print(OS); + } + OS << nl; } - OS << nl; -} +}; // end class Z3ConstraintManager + +} // end anonymous namespace #endif +std::unique_ptr<SMTSolver> clang::ento::CreateZ3Solver() { +#if CLANG_ANALYZER_WITH_Z3 + return llvm::make_unique<Z3Solver>(); +#else + llvm::report_fatal_error("Clang was not compiled with Z3 support, rebuild " + "with -DCLANG_ANALYZER_BUILD_Z3=ON", + false); + return nullptr; +#endif +} + std::unique_ptr<ConstraintManager> ento::CreateZ3ConstraintManager(ProgramStateManager &StMgr, SubEngine *Eng) { #if CLANG_ANALYZER_WITH_Z3 return llvm::make_unique<Z3ConstraintManager>(Eng, StMgr.getSValBuilder()); #else - llvm::report_fatal_error("Clang was not compiled with Z3 support!", false); + llvm::report_fatal_error("Clang was not compiled with Z3 support, rebuild " + "with -DCLANG_ANALYZER_BUILD_Z3=ON", + false); return nullptr; #endif } diff --git a/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index fccea9ee53bf..44abde5da6d1 100644 --- a/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -22,6 +22,7 @@ #include "clang/Analysis/CallGraph.h" #include "clang/Analysis/CodeInjector.h" #include "clang/Basic/SourceManager.h" +#include "clang/CrossTU/CrossTranslationUnit.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Lex/Preprocessor.h" #include "clang/StaticAnalyzer/Checkers/LocalCheckers.h" @@ -57,6 +58,8 @@ STATISTIC(NumFunctionsAnalyzed, "with inlining turned on)."); STATISTIC(NumBlocksInAnalyzedFunctions, "The # of basic blocks in the analyzed functions."); +STATISTIC(NumVisitedBlocksInAnalyzedFunctions, + "The # of visited basic blocks in the analyzed functions."); STATISTIC(PercentReachableBlocks, "The % of reachable basic blocks."); STATISTIC(MaxCFGSize, "The maximum number of basic blocks in a function."); @@ -70,7 +73,7 @@ void ento::createPlistHTMLDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, const Preprocessor &PP) { createHTMLDiagnosticConsumer(AnalyzerOpts, C, llvm::sys::path::parent_path(prefix), PP); - createPlistDiagnosticConsumer(AnalyzerOpts, C, prefix, PP); + createPlistMultiFileDiagnosticConsumer(AnalyzerOpts, C, prefix, PP); } void ento::createTextPathDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, @@ -161,6 +164,8 @@ class AnalysisConsumer : public AnalysisASTConsumer, /// Bug Reporter to use while recursively visiting Decls. BugReporter *RecVisitorBR; + std::vector<std::function<void(CheckerRegistry &)>> CheckerRegistrationFns; + public: ASTContext *Ctx; const Preprocessor &PP; @@ -168,8 +173,9 @@ public: AnalyzerOptionsRef Opts; ArrayRef<std::string> Plugins; CodeInjector *Injector; + cross_tu::CrossTranslationUnitContext CTU; - /// \brief Stores the declarations from the local translation unit. + /// Stores the declarations from the local translation unit. /// Note, we pre-compute the local declarations at parse time as an /// optimization to make sure we do not deserialize everything from disk. /// The local declaration to all declarations ratio might be very small when @@ -186,28 +192,31 @@ public: std::unique_ptr<AnalysisManager> Mgr; /// Time the analyzes time of each translation unit. - static llvm::Timer* TUTotalTimer; + std::unique_ptr<llvm::TimerGroup> AnalyzerTimers; + std::unique_ptr<llvm::Timer> TUTotalTimer; /// The information about analyzed functions shared throughout the /// translation unit. FunctionSummariesTy FunctionSummaries; - AnalysisConsumer(const Preprocessor &pp, const std::string &outdir, + AnalysisConsumer(CompilerInstance &CI, const std::string &outdir, AnalyzerOptionsRef opts, ArrayRef<std::string> plugins, CodeInjector *injector) - : RecVisitorMode(0), RecVisitorBR(nullptr), Ctx(nullptr), PP(pp), - OutDir(outdir), Opts(std::move(opts)), Plugins(plugins), - Injector(injector) { + : RecVisitorMode(0), RecVisitorBR(nullptr), Ctx(nullptr), + PP(CI.getPreprocessor()), OutDir(outdir), Opts(std::move(opts)), + Plugins(plugins), Injector(injector), CTU(CI) { DigestAnalyzerOptions(); - if (Opts->PrintStats) { - llvm::EnableStatistics(false); - TUTotalTimer = new llvm::Timer("time", "Analyzer Total Time"); + if (Opts->PrintStats || Opts->shouldSerializeStats()) { + AnalyzerTimers = llvm::make_unique<llvm::TimerGroup>( + "analyzer", "Analyzer timers"); + TUTotalTimer = llvm::make_unique<llvm::Timer>( + "time", "Analyzer total time", *AnalyzerTimers); + llvm::EnableStatistics(/* PrintOnExit= */ false); } } ~AnalysisConsumer() override { if (Opts->PrintStats) { - delete TUTotalTimer; llvm::PrintStatistics(); } } @@ -286,32 +295,33 @@ public: void Initialize(ASTContext &Context) override { Ctx = &Context; - checkerMgr = createCheckerManager(*Opts, PP.getLangOpts(), Plugins, - PP.getDiagnostics()); + checkerMgr = + createCheckerManager(*Opts, PP.getLangOpts(), Plugins, + CheckerRegistrationFns, PP.getDiagnostics()); Mgr = llvm::make_unique<AnalysisManager>( *Ctx, PP.getDiagnostics(), PP.getLangOpts(), PathConsumers, CreateStoreMgr, CreateConstraintMgr, checkerMgr.get(), *Opts, Injector); } - /// \brief Store the top level decls in the set to be processed later on. + /// Store the top level decls in the set to be processed later on. /// (Doing this pre-processing avoids deserialization of data from PCH.) bool HandleTopLevelDecl(DeclGroupRef D) override; void HandleTopLevelDeclInObjCContainer(DeclGroupRef D) override; void HandleTranslationUnit(ASTContext &C) override; - /// \brief Determine which inlining mode should be used when this function is + /// Determine which inlining mode should be used when this function is /// analyzed. This allows to redefine the default inlining policies when /// analyzing a given function. ExprEngine::InliningModes getInliningModeForFunction(const Decl *D, const SetOfConstDecls &Visited); - /// \brief Build the call graph for all the top level decls of this TU and + /// Build the call graph for all the top level decls of this TU and /// use it to define the order in which the functions should be visited. void HandleDeclsCallGraph(const unsigned LocalTUDeclsSize); - /// \brief Run analyzes(syntax or path sensitive) on the given function. + /// Run analyzes(syntax or path sensitive) on the given function. /// \param Mode - determines if we are requesting syntax only or path /// sensitive only analysis. /// \param VisitedCallees - The output parameter, which is populated with the @@ -378,13 +388,20 @@ public: PathConsumers.push_back(Consumer); } + void AddCheckerRegistrationFn(std::function<void(CheckerRegistry&)> Fn) override { + CheckerRegistrationFns.push_back(std::move(Fn)); + } + private: void storeTopLevelDecls(DeclGroupRef DG); std::string getFunctionName(const Decl *D); - /// \brief Check if we should skip (not analyze) the given function. + /// Check if we should skip (not analyze) the given function. AnalysisMode getModeForDecl(Decl *D, AnalysisMode Mode); + void runAnalysisOnTranslationUnit(ASTContext &C); + /// Print \p S to stderr if \c Opts->AnalyzerDisplayProgress is set. + void reportAnalyzerProgress(StringRef S); }; } // end anonymous namespace @@ -392,8 +409,6 @@ private: //===----------------------------------------------------------------------===// // AnalysisConsumer implementation. //===----------------------------------------------------------------------===// -llvm::Timer* AnalysisConsumer::TUTotalTimer = nullptr; - bool AnalysisConsumer::HandleTopLevelDecl(DeclGroupRef DG) { storeTopLevelDecls(DG); return true; @@ -508,68 +523,90 @@ void AnalysisConsumer::HandleDeclsCallGraph(const unsigned LocalTUDeclsSize) { } } +static bool isBisonFile(ASTContext &C) { + const SourceManager &SM = C.getSourceManager(); + FileID FID = SM.getMainFileID(); + StringRef Buffer = SM.getBuffer(FID)->getBuffer(); + if (Buffer.startswith("/* A Bison parser, made by")) + return true; + return false; +} + +void AnalysisConsumer::runAnalysisOnTranslationUnit(ASTContext &C) { + BugReporter BR(*Mgr); + TranslationUnitDecl *TU = C.getTranslationUnitDecl(); + checkerMgr->runCheckersOnASTDecl(TU, *Mgr, BR); + + // Run the AST-only checks using the order in which functions are defined. + // If inlining is not turned on, use the simplest function order for path + // sensitive analyzes as well. + RecVisitorMode = AM_Syntax; + if (!Mgr->shouldInlineCall()) + RecVisitorMode |= AM_Path; + RecVisitorBR = &BR; + + // Process all the top level declarations. + // + // Note: TraverseDecl may modify LocalTUDecls, but only by appending more + // entries. Thus we don't use an iterator, but rely on LocalTUDecls + // random access. By doing so, we automatically compensate for iterators + // possibly being invalidated, although this is a bit slower. + const unsigned LocalTUDeclsSize = LocalTUDecls.size(); + for (unsigned i = 0 ; i < LocalTUDeclsSize ; ++i) { + TraverseDecl(LocalTUDecls[i]); + } + + if (Mgr->shouldInlineCall()) + HandleDeclsCallGraph(LocalTUDeclsSize); + + // After all decls handled, run checkers on the entire TranslationUnit. + checkerMgr->runCheckersOnEndOfTranslationUnit(TU, *Mgr, BR); + + RecVisitorBR = nullptr; +} + +void AnalysisConsumer::reportAnalyzerProgress(StringRef S) { + if (Opts->AnalyzerDisplayProgress) + llvm::errs() << S; +} + void AnalysisConsumer::HandleTranslationUnit(ASTContext &C) { + // Don't run the actions if an error has occurred with parsing the file. DiagnosticsEngine &Diags = PP.getDiagnostics(); if (Diags.hasErrorOccurred() || Diags.hasFatalErrorOccurred()) return; - // Don't analyze if the user explicitly asked for no checks to be performed - // on this file. - if (Opts->DisableAllChecks) - return; - - { - if (TUTotalTimer) TUTotalTimer->startTimer(); - - // Introduce a scope to destroy BR before Mgr. - BugReporter BR(*Mgr); - TranslationUnitDecl *TU = C.getTranslationUnitDecl(); - checkerMgr->runCheckersOnASTDecl(TU, *Mgr, BR); - - // Run the AST-only checks using the order in which functions are defined. - // If inlining is not turned on, use the simplest function order for path - // sensitive analyzes as well. - RecVisitorMode = AM_Syntax; - if (!Mgr->shouldInlineCall()) - RecVisitorMode |= AM_Path; - RecVisitorBR = &BR; - - // Process all the top level declarations. - // - // Note: TraverseDecl may modify LocalTUDecls, but only by appending more - // entries. Thus we don't use an iterator, but rely on LocalTUDecls - // random access. By doing so, we automatically compensate for iterators - // possibly being invalidated, although this is a bit slower. - const unsigned LocalTUDeclsSize = LocalTUDecls.size(); - for (unsigned i = 0 ; i < LocalTUDeclsSize ; ++i) { - TraverseDecl(LocalTUDecls[i]); - } + if (TUTotalTimer) TUTotalTimer->startTimer(); - if (Mgr->shouldInlineCall()) - HandleDeclsCallGraph(LocalTUDeclsSize); + if (isBisonFile(C)) { + reportAnalyzerProgress("Skipping bison-generated file\n"); + } else if (Opts->DisableAllChecks) { - // After all decls handled, run checkers on the entire TranslationUnit. - checkerMgr->runCheckersOnEndOfTranslationUnit(TU, *Mgr, BR); - - RecVisitorBR = nullptr; + // Don't analyze if the user explicitly asked for no checks to be performed + // on this file. + reportAnalyzerProgress("All checks are disabled using a supplied option\n"); + } else { + // Otherwise, just run the analysis. + runAnalysisOnTranslationUnit(C); } - // Explicitly destroy the PathDiagnosticConsumer. This will flush its output. - // FIXME: This should be replaced with something that doesn't rely on - // side-effects in PathDiagnosticConsumer's destructor. This is required when - // used with option -disable-free. - Mgr.reset(); - if (TUTotalTimer) TUTotalTimer->stopTimer(); // Count how many basic blocks we have not covered. NumBlocksInAnalyzedFunctions = FunctionSummaries.getTotalNumBasicBlocks(); + NumVisitedBlocksInAnalyzedFunctions = + FunctionSummaries.getTotalNumVisitedBasicBlocks(); if (NumBlocksInAnalyzedFunctions > 0) PercentReachableBlocks = (FunctionSummaries.getTotalNumVisitedBasicBlocks() * 100) / NumBlocksInAnalyzedFunctions; + // Explicitly destroy the PathDiagnosticConsumer. This will flush its output. + // FIXME: This should be replaced with something that doesn't rely on + // side-effects in PathDiagnosticConsumer's destructor. This is required when + // used with option -disable-free. + Mgr.reset(); } std::string AnalysisConsumer::getFunctionName(const Decl *D) { @@ -648,7 +685,7 @@ AnalysisConsumer::getModeForDecl(Decl *D, AnalysisMode Mode) { SourceLocation SL = Body ? Body->getLocStart() : D->getLocation(); SL = SM.getExpansionLoc(SL); - if (!Opts->AnalyzeAll && !SM.isWrittenInMainFile(SL)) { + if (!Opts->AnalyzeAll && !Mgr->isInCodeFile(SL)) { if (SL.isInvalid() || SM.isInSystemHeader(SL)) return AM_None; return Mode & ~AM_Path; @@ -704,7 +741,8 @@ void AnalysisConsumer::ActionExprEngine(Decl *D, bool ObjCGCEnabled, if (!Mgr->getAnalysisDeclContext(D)->getAnalysis<RelaxedLiveVariables>()) return; - ExprEngine Eng(*Mgr, ObjCGCEnabled, VisitedCallees, &FunctionSummaries,IMode); + ExprEngine Eng(CTU, *Mgr, ObjCGCEnabled, VisitedCallees, &FunctionSummaries, + IMode); // Set the graph auditor. std::unique_ptr<ExplodedNode::Auditor> Auditor; @@ -762,7 +800,7 @@ ento::CreateAnalysisConsumer(CompilerInstance &CI) { bool hasModelPath = analyzerOpts->Config.count("model-path") > 0; return llvm::make_unique<AnalysisConsumer>( - CI.getPreprocessor(), CI.getFrontendOpts().OutputFile, analyzerOpts, + CI, CI.getFrontendOpts().OutputFile, analyzerOpts, CI.getFrontendOpts().Plugins, hasModelPath ? new ModelInjector(CI) : nullptr); } @@ -852,9 +890,9 @@ UbigraphViz::~UbigraphViz() { std::string Ubiviz; if (auto Path = llvm::sys::findProgramByName("ubiviz")) Ubiviz = *Path; - const char *args[] = {Ubiviz.c_str(), Filename.c_str(), nullptr}; + std::array<StringRef, 2> Args{{Ubiviz, Filename}}; - if (llvm::sys::ExecuteAndWait(Ubiviz, &args[0], nullptr, {}, 0, 0, &ErrMsg)) { + if (llvm::sys::ExecuteAndWait(Ubiviz, Args, llvm::None, {}, 0, 0, &ErrMsg)) { llvm::errs() << "Error viewing graph: " << ErrMsg << "\n"; } diff --git a/lib/StaticAnalyzer/Frontend/CMakeLists.txt b/lib/StaticAnalyzer/Frontend/CMakeLists.txt index e3ca91aec9cd..ff0a6e19fc97 100644 --- a/lib/StaticAnalyzer/Frontend/CMakeLists.txt +++ b/lib/StaticAnalyzer/Frontend/CMakeLists.txt @@ -7,14 +7,15 @@ set(LLVM_LINK_COMPONENTS add_clang_library(clangStaticAnalyzerFrontend AnalysisConsumer.cpp CheckerRegistration.cpp - ModelConsumer.cpp FrontendActions.cpp + ModelConsumer.cpp ModelInjector.cpp LINK_LIBS clangAST clangAnalysis clangBasic + clangCrossTU clangFrontend clangLex clangStaticAnalyzerCheckers diff --git a/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp b/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp index 6792f89876cd..a260c2d85b11 100644 --- a/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp +++ b/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp @@ -111,16 +111,21 @@ getCheckerOptList(const AnalyzerOptions &opts) { return checkerOpts; } -std::unique_ptr<CheckerManager> -ento::createCheckerManager(AnalyzerOptions &opts, const LangOptions &langOpts, - ArrayRef<std::string> plugins, - DiagnosticsEngine &diags) { +std::unique_ptr<CheckerManager> ento::createCheckerManager( + AnalyzerOptions &opts, const LangOptions &langOpts, + ArrayRef<std::string> plugins, + ArrayRef<std::function<void(CheckerRegistry &)>> checkerRegistrationFns, + DiagnosticsEngine &diags) { std::unique_ptr<CheckerManager> checkerMgr( new CheckerManager(langOpts, opts)); SmallVector<CheckerOptInfo, 8> checkerOpts = getCheckerOptList(opts); ClangCheckerRegistry allCheckers(plugins, &diags); + + for (const auto &Fn : checkerRegistrationFns) + Fn(allCheckers); + allCheckers.initializeManager(*checkerMgr, checkerOpts); allCheckers.validateCheckerOptions(opts, diags); checkerMgr->finishedCheckerRegistration(); diff --git a/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp b/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp index a65a5ee0a451..60825ef7411d 100644 --- a/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp +++ b/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp @@ -8,7 +8,7 @@ //===----------------------------------------------------------------------===// /// /// \file -/// \brief This file implements an ASTConsumer for consuming model files. +/// This file implements an ASTConsumer for consuming model files. /// /// This ASTConsumer handles the AST of a parsed model file. All top level /// function definitions will be collected from that model file for later diff --git a/lib/StaticAnalyzer/Frontend/ModelInjector.cpp b/lib/StaticAnalyzer/Frontend/ModelInjector.cpp index cdb1ed9b3815..c43d30440c8f 100644 --- a/lib/StaticAnalyzer/Frontend/ModelInjector.cpp +++ b/lib/StaticAnalyzer/Frontend/ModelInjector.cpp @@ -10,6 +10,7 @@ #include "ModelInjector.h" #include "clang/AST/Decl.h" #include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/Stack.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" @@ -95,11 +96,10 @@ void ModelInjector::onBodySynthesis(const NamedDecl *D) { ParseModelFileAction parseModelFile(Bodies); - const unsigned ThreadStackSize = 8 << 20; llvm::CrashRecoveryContext CRC; CRC.RunSafelyOnThread([&]() { Instance.ExecuteAction(parseModelFile); }, - ThreadStackSize); + DesiredStackSize); Instance.getPreprocessor().FinalizeForModelFile(); @@ -109,7 +109,7 @@ void ModelInjector::onBodySynthesis(const NamedDecl *D) { // The preprocessor enters to the main file id when parsing is started, so // the main file id is changed to the model file during parsing and it needs - // to be reseted to the former main file id after parsing of the model file + // to be reset to the former main file id after parsing of the model file // is done. SM.setMainFileID(mainFileID); } diff --git a/lib/StaticAnalyzer/Frontend/ModelInjector.h b/lib/StaticAnalyzer/Frontend/ModelInjector.h index 98a5f69d68e8..b1b6de9ef9d9 100644 --- a/lib/StaticAnalyzer/Frontend/ModelInjector.h +++ b/lib/StaticAnalyzer/Frontend/ModelInjector.h @@ -8,7 +8,7 @@ //===----------------------------------------------------------------------===// /// /// \file -/// \brief This file defines the clang::ento::ModelInjector class which implements the +/// This file defines the clang::ento::ModelInjector class which implements the /// clang::CodeInjector interface. This class is responsible for injecting /// function definitions that were synthesized from model files. /// @@ -43,7 +43,7 @@ public: Stmt *getBody(const ObjCMethodDecl *D) override; private: - /// \brief Synthesize a body for a declaration + /// Synthesize a body for a declaration /// /// This method first looks up the appropriate model file based on the /// model-path configuration option and the name of the declaration that is |