diff options
Diffstat (limited to 'contrib/llvm-project/clang/lib/StaticAnalyzer')
166 files changed, 11661 insertions, 5675 deletions
diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp index 2ef50a727ece..0e8cbc60689a 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp @@ -13,13 +13,14 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ExprCXX.h" #include "clang/Analysis/CFGStmtMap.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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 "llvm/Support/ErrorHandling.h" using namespace clang; using namespace ento; @@ -27,24 +28,20 @@ using namespace ento; namespace { class AnalysisOrderChecker - : public Checker<check::PreStmt<CastExpr>, - 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::EndFunction, - check::NewAllocator, - check::Bind, - check::PointerEscape, - check::RegionChanges, - check::LiveSymbols> { - - bool isCallbackEnabled(AnalyzerOptions &Opts, StringRef CallbackName) const { + : public Checker< + check::PreStmt<CastExpr>, check::PostStmt<CastExpr>, + check::PreStmt<ArraySubscriptExpr>, + check::PostStmt<ArraySubscriptExpr>, check::PreStmt<CXXNewExpr>, + check::PostStmt<CXXNewExpr>, check::PreStmt<CXXDeleteExpr>, + check::PostStmt<CXXDeleteExpr>, check::PreStmt<CXXConstructExpr>, + check::PostStmt<CXXConstructExpr>, check::PreStmt<OffsetOfExpr>, + check::PostStmt<OffsetOfExpr>, check::PreCall, check::PostCall, + check::EndFunction, check::EndAnalysis, check::NewAllocator, + check::Bind, check::PointerEscape, check::RegionChanges, + check::LiveSymbols, eval::Call> { + + bool isCallbackEnabled(const AnalyzerOptions &Opts, + StringRef CallbackName) const { return Opts.getCheckerBooleanOption(this, "*") || Opts.getCheckerBooleanOption(this, CallbackName); } @@ -95,6 +92,26 @@ public: llvm::errs() << "PostStmt<CXXNewExpr>\n"; } + void checkPreStmt(const CXXDeleteExpr *NE, CheckerContext &C) const { + if (isCallbackEnabled(C, "PreStmtCXXDeleteExpr")) + llvm::errs() << "PreStmt<CXXDeleteExpr>\n"; + } + + void checkPostStmt(const CXXDeleteExpr *NE, CheckerContext &C) const { + if (isCallbackEnabled(C, "PostStmtCXXDeleteExpr")) + llvm::errs() << "PostStmt<CXXDeleteExpr>\n"; + } + + void checkPreStmt(const CXXConstructExpr *NE, CheckerContext &C) const { + if (isCallbackEnabled(C, "PreStmtCXXConstructExpr")) + llvm::errs() << "PreStmt<CXXConstructExpr>\n"; + } + + void checkPostStmt(const CXXConstructExpr *NE, CheckerContext &C) const { + if (isCallbackEnabled(C, "PostStmtCXXConstructExpr")) + llvm::errs() << "PostStmt<CXXConstructExpr>\n"; + } + void checkPreStmt(const OffsetOfExpr *OOE, CheckerContext &C) const { if (isCallbackEnabled(C, "PreStmtOffsetOfExpr")) llvm::errs() << "PreStmt<OffsetOfExpr>\n"; @@ -105,11 +122,25 @@ public: llvm::errs() << "PostStmt<OffsetOfExpr>\n"; } + bool evalCall(const CallEvent &Call, CheckerContext &C) const { + if (isCallbackEnabled(C, "EvalCall")) { + llvm::errs() << "EvalCall"; + if (const NamedDecl *ND = dyn_cast_or_null<NamedDecl>(Call.getDecl())) + llvm::errs() << " (" << ND->getQualifiedNameAsString() << ')'; + llvm::errs() << " {argno: " << Call.getNumArgs() << '}'; + llvm::errs() << " [" << Call.getKindAsString() << ']'; + llvm::errs() << '\n'; + return true; + } + return false; + } + 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() << " [" << Call.getKindAsString() << ']'; llvm::errs() << '\n'; } } @@ -119,6 +150,7 @@ public: llvm::errs() << "PostCall"; if (const NamedDecl *ND = dyn_cast_or_null<NamedDecl>(Call.getDecl())) llvm::errs() << " (" << ND->getQualifiedNameAsString() << ')'; + llvm::errs() << " [" << Call.getKindAsString() << ']'; llvm::errs() << '\n'; } } @@ -140,7 +172,13 @@ public: } } - void checkNewAllocator(const CXXNewExpr *CNE, SVal Target, + void checkEndAnalysis(ExplodedGraph &G, BugReporter &BR, + ExprEngine &Eng) const { + if (isCallbackEnabled(BR.getAnalyzerOptions(), "EndAnalysis")) + llvm::errs() << "EndAnalysis\n"; + } + + void checkNewAllocator(const CXXAllocatorCall &Call, CheckerContext &C) const { if (isCallbackEnabled(C, "NewAllocator")) llvm::errs() << "NewAllocator\n"; @@ -186,6 +224,6 @@ void ento::registerAnalysisOrderChecker(CheckerManager &mgr) { mgr.registerChecker<AnalysisOrderChecker>(); } -bool ento::shouldRegisterAnalysisOrderChecker(const LangOptions &LO) { +bool ento::shouldRegisterAnalysisOrderChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp index 20f3008b4a4b..c06604b6cffe 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp @@ -103,7 +103,7 @@ void AnalyzerStatsChecker::checkEndAnalysis(ExplodedGraph &G, NumBlocksUnreachable += unreachable; NumBlocks += total; - std::string NameOfRootFunction = output.str(); + std::string NameOfRootFunction = std::string(output.str()); output << " -> Total CFGBlocks: " << total << " | Unreachable CFGBlocks: " << unreachable << " | Exhausted Block: " @@ -140,6 +140,6 @@ void ento::registerAnalyzerStatsChecker(CheckerManager &mgr) { mgr.registerChecker<AnalyzerStatsChecker>(); } -bool ento::shouldRegisterAnalyzerStatsChecker(const LangOptions &LO) { +bool ento::shouldRegisterAnalyzerStatsChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp index 8d4793e0802f..59163c1f31fa 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp @@ -16,6 +16,7 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" using namespace clang; @@ -54,12 +55,11 @@ void ArrayBoundChecker::checkLocation(SVal l, bool isLoad, const Stmt* LoadS, ProgramStateRef state = C.getState(); // Get the size of the array. - DefinedOrUnknownSVal NumElements - = C.getStoreManager().getSizeInElements(state, ER->getSuperRegion(), - ER->getValueType()); + DefinedOrUnknownSVal ElementCount = getDynamicElementCount( + state, ER->getSuperRegion(), C.getSValBuilder(), ER->getValueType()); - ProgramStateRef StInBound = state->assumeInBound(Idx, NumElements, true); - ProgramStateRef StOutBound = state->assumeInBound(Idx, NumElements, false); + ProgramStateRef StInBound = state->assumeInBound(Idx, ElementCount, true); + ProgramStateRef StOutBound = state->assumeInBound(Idx, ElementCount, false); if (StOutBound && !StInBound) { ExplodedNode *N = C.generateErrorNode(StOutBound); if (!N) @@ -92,6 +92,6 @@ void ento::registerArrayBoundChecker(CheckerManager &mgr) { mgr.registerChecker<ArrayBoundChecker>(); } -bool ento::shouldRegisterArrayBoundChecker(const LangOptions &LO) { +bool ento::shouldRegisterArrayBoundChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp index 8f3bf138cae4..7c264bba4b6a 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp @@ -12,13 +12,14 @@ //===----------------------------------------------------------------------===// #include "Taint.h" -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/CharUnits.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/APSIntType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/raw_ostream.h" @@ -175,24 +176,23 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, } do { - // CHECK UPPER BOUND: Is byteOffset >= extent(baseRegion)? If so, + // CHECK UPPER BOUND: Is byteOffset >= size(baseRegion)? If so, // we are doing a load/store after the last valid offset. - DefinedOrUnknownSVal extentVal = - rawOffset.getRegion()->getExtent(svalBuilder); - if (!extentVal.getAs<NonLoc>()) + const MemRegion *MR = rawOffset.getRegion(); + DefinedOrUnknownSVal Size = getDynamicSize(state, MR, svalBuilder); + if (!Size.getAs<NonLoc>()) break; - if (extentVal.getAs<nonloc::ConcreteInt>()) { + if (Size.getAs<nonloc::ConcreteInt>()) { std::pair<NonLoc, nonloc::ConcreteInt> simplifiedOffsets = getSimplifiedOffsets(rawOffset.getByteOffset(), - extentVal.castAs<nonloc::ConcreteInt>(), - svalBuilder); + Size.castAs<nonloc::ConcreteInt>(), svalBuilder); rawOffsetVal = simplifiedOffsets.first; - extentVal = simplifiedOffsets.second; + Size = simplifiedOffsets.second; } SVal upperbound = svalBuilder.evalBinOpNN(state, BO_GE, rawOffsetVal, - extentVal.castAs<NonLoc>(), + Size.castAs<NonLoc>(), svalBuilder.getConditionType()); Optional<NonLoc> upperboundToCheck = upperbound.getAs<NonLoc>(); @@ -356,6 +356,6 @@ void ento::registerArrayBoundCheckerV2(CheckerManager &mgr) { mgr.registerChecker<ArrayBoundCheckerV2>(); } -bool ento::shouldRegisterArrayBoundCheckerV2(const LangOptions &LO) { +bool ento::shouldRegisterArrayBoundCheckerV2(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp index 325952fe4ed4..918c6e361381 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp @@ -1243,7 +1243,7 @@ void ento::registerNilArgChecker(CheckerManager &mgr) { mgr.registerChecker<NilArgChecker>(); } -bool ento::shouldRegisterNilArgChecker(const LangOptions &LO) { +bool ento::shouldRegisterNilArgChecker(const CheckerManager &mgr) { return true; } @@ -1251,7 +1251,7 @@ void ento::registerCFNumberChecker(CheckerManager &mgr) { mgr.registerChecker<CFNumberChecker>(); } -bool ento::shouldRegisterCFNumberChecker(const LangOptions &LO) { +bool ento::shouldRegisterCFNumberChecker(const CheckerManager &mgr) { return true; } @@ -1259,7 +1259,7 @@ void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) { mgr.registerChecker<CFRetainReleaseChecker>(); } -bool ento::shouldRegisterCFRetainReleaseChecker(const LangOptions &LO) { +bool ento::shouldRegisterCFRetainReleaseChecker(const CheckerManager &mgr) { return true; } @@ -1267,7 +1267,7 @@ void ento::registerClassReleaseChecker(CheckerManager &mgr) { mgr.registerChecker<ClassReleaseChecker>(); } -bool ento::shouldRegisterClassReleaseChecker(const LangOptions &LO) { +bool ento::shouldRegisterClassReleaseChecker(const CheckerManager &mgr) { return true; } @@ -1275,7 +1275,7 @@ void ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) { mgr.registerChecker<VariadicMethodTypeChecker>(); } -bool ento::shouldRegisterVariadicMethodTypeChecker(const LangOptions &LO) { +bool ento::shouldRegisterVariadicMethodTypeChecker(const CheckerManager &mgr) { return true; } @@ -1283,7 +1283,7 @@ void ento::registerObjCLoopChecker(CheckerManager &mgr) { mgr.registerChecker<ObjCLoopChecker>(); } -bool ento::shouldRegisterObjCLoopChecker(const LangOptions &LO) { +bool ento::shouldRegisterObjCLoopChecker(const CheckerManager &mgr) { return true; } @@ -1291,6 +1291,6 @@ void ento::registerObjCNonNilReturnValueChecker(CheckerManager &mgr) { mgr.registerChecker<ObjCNonNilReturnValueChecker>(); } -bool ento::shouldRegisterObjCNonNilReturnValueChecker(const LangOptions &LO) { +bool ento::shouldRegisterObjCNonNilReturnValueChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp index 0eb3c3d1d0e6..2752b37f9b3f 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp @@ -184,6 +184,6 @@ void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) { mgr.registerChecker<BlockInCriticalSectionChecker>(); } -bool ento::shouldRegisterBlockInCriticalSectionChecker(const LangOptions &LO) { +bool ento::shouldRegisterBlockInCriticalSectionChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp index 1423b9c39b26..6c0caf3c4e78 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp @@ -70,8 +70,8 @@ void BoolAssignmentChecker::checkBind(SVal loc, SVal val, const Stmt *S, // Get the value of the right-hand side. We only care about values // that are defined (UnknownVals and UndefinedVals are handled by other // checkers). - Optional<DefinedSVal> DV = val.getAs<DefinedSVal>(); - if (!DV) + Optional<NonLoc> NV = val.getAs<NonLoc>(); + if (!NV) return; // Check if the assigned value meets our criteria for correctness. It must @@ -79,84 +79,23 @@ void BoolAssignmentChecker::checkBind(SVal loc, SVal val, const Stmt *S, // the value is possibly < 0 (for a negative value) or greater than 1. ProgramStateRef state = C.getState(); SValBuilder &svalBuilder = C.getSValBuilder(); + BasicValueFactory &BVF = svalBuilder.getBasicValueFactory(); ConstraintManager &CM = C.getConstraintManager(); - // First, ensure that the value is >= 0. - DefinedSVal zeroVal = svalBuilder.makeIntVal(0, valTy); - SVal greaterThanOrEqualToZeroVal = - svalBuilder.evalBinOp(state, BO_GE, *DV, zeroVal, - svalBuilder.getConditionType()); + llvm::APSInt Zero = BVF.getValue(0, valTy); + llvm::APSInt One = BVF.getValue(1, valTy); - Optional<DefinedSVal> greaterThanEqualToZero = - greaterThanOrEqualToZeroVal.getAs<DefinedSVal>(); + ProgramStateRef StIn, StOut; + std::tie(StIn, StOut) = CM.assumeInclusiveRangeDual(state, *NV, Zero, One); - if (!greaterThanEqualToZero) { - // The SValBuilder cannot construct a valid SVal for this condition. - // This means we cannot properly reason about it. - return; - } - - ProgramStateRef stateLT, stateGE; - std::tie(stateGE, stateLT) = CM.assumeDual(state, *greaterThanEqualToZero); - - // Is it possible for the value to be less than zero? - if (stateLT) { - // It is possible for the value to be less than zero. We only - // want to emit a warning, however, if that value is fully constrained. - // If it it possible for the value to be >= 0, then essentially the - // value is underconstrained and there is nothing left to be done. - if (!stateGE) - emitReport(stateLT, C); - - // In either case, we are done. - return; - } - - // If we reach here, it must be the case that the value is constrained - // to only be >= 0. - assert(stateGE == state); - - // At this point we know that the value is >= 0. - // Now check to ensure that the value is <= 1. - DefinedSVal OneVal = svalBuilder.makeIntVal(1, valTy); - SVal lessThanEqToOneVal = - svalBuilder.evalBinOp(state, BO_LE, *DV, OneVal, - svalBuilder.getConditionType()); - - Optional<DefinedSVal> lessThanEqToOne = - lessThanEqToOneVal.getAs<DefinedSVal>(); - - if (!lessThanEqToOne) { - // The SValBuilder cannot construct a valid SVal for this condition. - // This means we cannot properly reason about it. - return; - } - - ProgramStateRef stateGT, stateLE; - std::tie(stateLE, stateGT) = CM.assumeDual(state, *lessThanEqToOne); - - // Is it possible for the value to be greater than one? - if (stateGT) { - // It is possible for the value to be greater than one. We only - // want to emit a warning, however, if that value is fully constrained. - // If it is possible for the value to be <= 1, then essentially the - // value is underconstrained and there is nothing left to be done. - if (!stateLE) - emitReport(stateGT, C); - - // In either case, we are done. - return; - } - - // If we reach here, it must be the case that the value is constrained - // to only be <= 1. - assert(stateLE == state); + if (!StIn) + emitReport(StOut, C); } void ento::registerBoolAssignmentChecker(CheckerManager &mgr) { mgr.registerChecker<BoolAssignmentChecker>(); } -bool ento::shouldRegisterBoolAssignmentChecker(const LangOptions &LO) { +bool ento::shouldRegisterBoolAssignmentChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp index 10594e331cbe..233ce57c3ac9 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp @@ -10,12 +10,13 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/Basic/Builtins.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/DynamicSize.h" using namespace clang; using namespace ento; @@ -63,10 +64,12 @@ bool BuiltinFunctionChecker::evalCall(const CallEvent &Call, case Builtin::BI__builtin_unpredictable: case Builtin::BI__builtin_expect: + case Builtin::BI__builtin_expect_with_probability: case Builtin::BI__builtin_assume_aligned: case Builtin::BI__builtin_addressof: { - // For __builtin_unpredictable, __builtin_expect, and - // __builtin_assume_aligned, just return the value of the subexpression. + // For __builtin_unpredictable, __builtin_expect, + // __builtin_expect_with_probability and __builtin_assume_aligned, + // just return the value of the subexpression. // __builtin_addressof is going from a reference to a pointer, but those // are represented the same way in the analyzer. assert (Call.getNumArgs() > 0); @@ -90,10 +93,10 @@ bool BuiltinFunctionChecker::evalCall(const CallEvent &Call, return true; // Return true to model purity. SValBuilder& svalBuilder = C.getSValBuilder(); - DefinedOrUnknownSVal Extent = R->getExtent(svalBuilder); - DefinedOrUnknownSVal extentMatchesSizeArg = - svalBuilder.evalEQ(state, Extent, Size.castAs<DefinedOrUnknownSVal>()); - state = state->assume(extentMatchesSizeArg, true); + DefinedOrUnknownSVal DynSize = getDynamicSize(state, R, svalBuilder); + DefinedOrUnknownSVal DynSizeMatchesSizeArg = + svalBuilder.evalEQ(state, DynSize, Size.castAs<DefinedOrUnknownSVal>()); + state = state->assume(DynSizeMatchesSizeArg, true); assert(state && "The region should not have any previous constraints"); C.addTransition(state->BindExpr(CE, LCtx, loc::MemRegionVal(R))); @@ -134,6 +137,6 @@ void ento::registerBuiltinFunctionChecker(CheckerManager &mgr) { mgr.registerChecker<BuiltinFunctionChecker>(); } -bool ento::shouldRegisterBuiltinFunctionChecker(const LangOptions &LO) { +bool ento::shouldRegisterBuiltinFunctionChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index 21c4bbc60264..30fd62f887c4 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -11,23 +11,66 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "InterCheckerAPI.h" #include "clang/Basic/CharInfo.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/DynamicSize.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/Support/raw_ostream.h" using namespace clang; using namespace ento; namespace { +struct AnyArgExpr { + // FIXME: Remove constructor in C++17 to turn it into an aggregate. + AnyArgExpr(const Expr *Expression, unsigned ArgumentIndex) + : Expression{Expression}, ArgumentIndex{ArgumentIndex} {} + const Expr *Expression; + unsigned ArgumentIndex; +}; + +struct SourceArgExpr : AnyArgExpr { + using AnyArgExpr::AnyArgExpr; // FIXME: Remove using in C++17. +}; + +struct DestinationArgExpr : AnyArgExpr { + using AnyArgExpr::AnyArgExpr; // FIXME: Same. +}; + +struct SizeArgExpr : AnyArgExpr { + using AnyArgExpr::AnyArgExpr; // FIXME: Same. +}; + +using ErrorMessage = SmallString<128>; +enum class AccessKind { write, read }; + +static ErrorMessage createOutOfBoundErrorMsg(StringRef FunctionDescription, + AccessKind Access) { + ErrorMessage Message; + llvm::raw_svector_ostream Os(Message); + + // Function classification like: Memory copy function + Os << toUppercase(FunctionDescription.front()) + << &FunctionDescription.data()[1]; + + if (Access == AccessKind::write) { + Os << " overflows the destination buffer"; + } else { // read access + Os << " accesses out-of-bound array element"; + } + + return Message; +} + enum class ConcatFnKind { none = 0, strcat = 1, strlcat = 2 }; class CStringChecker : public Checker< eval::Call, check::PreStmt<DeclStmt>, @@ -111,12 +154,9 @@ public: void evalMemmove(CheckerContext &C, const CallExpr *CE) const; void evalBcopy(CheckerContext &C, const CallExpr *CE) const; void evalCopyCommon(CheckerContext &C, const CallExpr *CE, - ProgramStateRef state, - const Expr *Size, - const Expr *Source, - const Expr *Dest, - bool Restricted = false, - bool IsMempcpy = false) const; + ProgramStateRef state, SizeArgExpr Size, + DestinationArgExpr Dest, SourceArgExpr Source, + bool Restricted, bool IsMempcpy) const; void evalMemcmp(CheckerContext &C, const CallExpr *CE) const; @@ -193,40 +233,17 @@ public: ProgramStateRef &State); // Re-usable checks - ProgramStateRef checkNonNull(CheckerContext &C, - ProgramStateRef state, - const Expr *S, - SVal l, - unsigned IdxOfArg) const; - ProgramStateRef CheckLocation(CheckerContext &C, - ProgramStateRef state, - const Expr *S, - SVal l, - const char *message = nullptr) const; - ProgramStateRef CheckBufferAccess(CheckerContext &C, - ProgramStateRef state, - const Expr *Size, - const Expr *FirstBuf, - const Expr *SecondBuf, - const char *firstMessage = nullptr, - const char *secondMessage = nullptr, - bool WarnAboutSize = false) const; - - ProgramStateRef CheckBufferAccess(CheckerContext &C, - ProgramStateRef state, - const Expr *Size, - const Expr *Buf, - const char *message = nullptr, - bool WarnAboutSize = false) const { - // This is a convenience overload. - return CheckBufferAccess(C, state, Size, Buf, nullptr, message, nullptr, - WarnAboutSize); - } - ProgramStateRef CheckOverlap(CheckerContext &C, - ProgramStateRef state, - const Expr *Size, - const Expr *First, - const Expr *Second) const; + ProgramStateRef checkNonNull(CheckerContext &C, ProgramStateRef State, + AnyArgExpr Arg, SVal l) const; + ProgramStateRef CheckLocation(CheckerContext &C, ProgramStateRef state, + AnyArgExpr Buffer, SVal Element, + AccessKind Access) const; + ProgramStateRef CheckBufferAccess(CheckerContext &C, ProgramStateRef State, + AnyArgExpr Buffer, SizeArgExpr Size, + AccessKind Access) const; + ProgramStateRef CheckOverlap(CheckerContext &C, ProgramStateRef state, + SizeArgExpr Size, AnyArgExpr First, + AnyArgExpr Second) const; void emitOverlapBug(CheckerContext &C, ProgramStateRef state, const Stmt *First, @@ -275,26 +292,26 @@ CStringChecker::assumeZero(CheckerContext &C, ProgramStateRef state, SVal V, } ProgramStateRef CStringChecker::checkNonNull(CheckerContext &C, - ProgramStateRef state, - const Expr *S, SVal l, - unsigned IdxOfArg) const { + ProgramStateRef State, + AnyArgExpr Arg, SVal l) const { // If a previous check has failed, propagate the failure. - if (!state) + if (!State) return nullptr; ProgramStateRef stateNull, stateNonNull; - std::tie(stateNull, stateNonNull) = assumeZero(C, state, l, S->getType()); + std::tie(stateNull, stateNonNull) = + assumeZero(C, State, l, Arg.Expression->getType()); if (stateNull && !stateNonNull) { if (Filter.CheckCStringNullArg) { SmallString<80> buf; llvm::raw_svector_ostream OS(buf); assert(CurrentFunctionDescription); - OS << "Null pointer passed as " << IdxOfArg - << llvm::getOrdinalSuffix(IdxOfArg) << " argument to " + OS << "Null pointer passed as " << (Arg.ArgumentIndex + 1) + << llvm::getOrdinalSuffix(Arg.ArgumentIndex + 1) << " argument to " << CurrentFunctionDescription; - emitNullArgBug(C, stateNull, S, OS.str()); + emitNullArgBug(C, stateNull, Arg.Expression, OS.str()); } return nullptr; } @@ -306,19 +323,20 @@ ProgramStateRef CStringChecker::checkNonNull(CheckerContext &C, // FIXME: This was originally copied from ArrayBoundChecker.cpp. Refactor? ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C, - ProgramStateRef state, - const Expr *S, SVal l, - const char *warningMsg) const { + ProgramStateRef state, + AnyArgExpr Buffer, SVal Element, + AccessKind Access) const { + // If a previous check has failed, propagate the failure. if (!state) return nullptr; // Check for out of bound array element access. - const MemRegion *R = l.getAsRegion(); + const MemRegion *R = Element.getAsRegion(); if (!R) return state; - const ElementRegion *ER = dyn_cast<ElementRegion>(R); + const auto *ER = dyn_cast<ElementRegion>(R); if (!ER) return state; @@ -326,11 +344,9 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C, return state; // Get the size of the array. - const SubRegion *superReg = cast<SubRegion>(ER->getSuperRegion()); - SValBuilder &svalBuilder = C.getSValBuilder(); - SVal Extent = - svalBuilder.convertToArrayIndex(superReg->getExtent(svalBuilder)); - DefinedOrUnknownSVal Size = Extent.castAs<DefinedOrUnknownSVal>(); + const auto *superReg = cast<SubRegion>(ER->getSuperRegion()); + DefinedOrUnknownSVal Size = + getDynamicSize(state, superReg, C.getSValBuilder()); // Get the index of the accessed element. DefinedOrUnknownSVal Idx = ER->getIndex().castAs<DefinedOrUnknownSVal>(); @@ -343,20 +359,11 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C, // In the latter case we only do modeling but do not emit warning. if (!Filter.CheckCStringOutOfBounds) return nullptr; - // Emit a bug report. - if (warningMsg) { - emitOutOfBoundsBug(C, StOutBound, S, warningMsg); - } else { - assert(CurrentFunctionDescription); - assert(CurrentFunctionDescription[0] != '\0'); - SmallString<80> buf; - llvm::raw_svector_ostream os(buf); - os << toUppercase(CurrentFunctionDescription[0]) - << &CurrentFunctionDescription[1] - << " accesses out-of-bound array element"; - emitOutOfBoundsBug(C, StOutBound, S, os.str()); - } + // Emit a bug report. + ErrorMessage Message = + createOutOfBoundErrorMsg(CurrentFunctionDescription, Access); + emitOutOfBoundsBug(C, StOutBound, Buffer.Expression, Message); return nullptr; } @@ -366,89 +373,68 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C, } ProgramStateRef CStringChecker::CheckBufferAccess(CheckerContext &C, - ProgramStateRef state, - const Expr *Size, - const Expr *FirstBuf, - const Expr *SecondBuf, - const char *firstMessage, - const char *secondMessage, - bool WarnAboutSize) const { + ProgramStateRef State, + AnyArgExpr Buffer, + SizeArgExpr Size, + AccessKind Access) const { // If a previous check has failed, propagate the failure. - if (!state) + if (!State) return nullptr; SValBuilder &svalBuilder = C.getSValBuilder(); ASTContext &Ctx = svalBuilder.getContext(); - const LocationContext *LCtx = C.getLocationContext(); - QualType sizeTy = Size->getType(); + QualType SizeTy = Size.Expression->getType(); QualType PtrTy = Ctx.getPointerType(Ctx.CharTy); // Check that the first buffer is non-null. - SVal BufVal = C.getSVal(FirstBuf); - state = checkNonNull(C, state, FirstBuf, BufVal, 1); - if (!state) + SVal BufVal = C.getSVal(Buffer.Expression); + State = checkNonNull(C, State, Buffer, BufVal); + if (!State) return nullptr; // If out-of-bounds checking is turned off, skip the rest. if (!Filter.CheckCStringOutOfBounds) - return state; + return State; // 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 = C.getSVal(Size); + SVal LengthVal = C.getSVal(Size.Expression); Optional<NonLoc> Length = LengthVal.getAs<NonLoc>(); if (!Length) - return state; + return State; // Compute the offset of the last element to be accessed: size-1. - NonLoc One = svalBuilder.makeIntVal(1, sizeTy).castAs<NonLoc>(); - SVal Offset = svalBuilder.evalBinOpNN(state, BO_Sub, *Length, One, sizeTy); + NonLoc One = svalBuilder.makeIntVal(1, 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()); + SVal BufStart = + svalBuilder.evalCast(BufVal, PtrTy, Buffer.Expression->getType()); if (Optional<Loc> BufLoc = BufStart.getAs<Loc>()) { - const Expr *warningExpr = (WarnAboutSize ? Size : FirstBuf); - SVal BufEnd = svalBuilder.evalBinOpLN(state, BO_Add, *BufLoc, - LastOffset, PtrTy); - state = CheckLocation(C, state, warningExpr, BufEnd, firstMessage); + SVal BufEnd = + svalBuilder.evalBinOpLN(State, BO_Add, *BufLoc, LastOffset, PtrTy); - // If the buffer isn't large enough, abort. - if (!state) - return nullptr; - } + State = CheckLocation(C, State, Buffer, BufEnd, Access); - // If there's a second buffer, check it as well. - if (SecondBuf) { - BufVal = state->getSVal(SecondBuf, LCtx); - state = checkNonNull(C, state, SecondBuf, BufVal, 2); - if (!state) + // If the buffer isn't large enough, abort. + if (!State) return nullptr; - - BufStart = svalBuilder.evalCast(BufVal, PtrTy, SecondBuf->getType()); - if (Optional<Loc> BufLoc = BufStart.getAs<Loc>()) { - const Expr *warningExpr = (WarnAboutSize ? Size : SecondBuf); - - SVal BufEnd = svalBuilder.evalBinOpLN(state, BO_Add, *BufLoc, - LastOffset, PtrTy); - state = CheckLocation(C, state, warningExpr, BufEnd, secondMessage); - } } // Large enough or not, return this state! - return state; + return State; } ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, - ProgramStateRef state, - const Expr *Size, - const Expr *First, - const Expr *Second) const { + ProgramStateRef state, + SizeArgExpr Size, AnyArgExpr First, + AnyArgExpr Second) const { if (!Filter.CheckCStringBufferOverlap) return state; @@ -464,8 +450,8 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, // Get the buffer values and make sure they're known locations. const LocationContext *LCtx = C.getLocationContext(); - SVal firstVal = state->getSVal(First, LCtx); - SVal secondVal = state->getSVal(Second, LCtx); + SVal firstVal = state->getSVal(First.Expression, LCtx); + SVal secondVal = state->getSVal(Second.Expression, LCtx); Optional<Loc> firstLoc = firstVal.getAs<Loc>(); if (!firstLoc) @@ -478,11 +464,11 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, // Are the two values the same? SValBuilder &svalBuilder = C.getSValBuilder(); std::tie(stateTrue, stateFalse) = - state->assume(svalBuilder.evalEQ(state, *firstLoc, *secondLoc)); + state->assume(svalBuilder.evalEQ(state, *firstLoc, *secondLoc)); if (stateTrue && !stateFalse) { // If the values are known to be equal, that's automatically an overlap. - emitOverlapBug(C, stateTrue, First, Second); + emitOverlapBug(C, stateTrue, First.Expression, Second.Expression); return nullptr; } @@ -492,8 +478,8 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, // Which value comes first? QualType cmpTy = svalBuilder.getConditionType(); - SVal reverse = svalBuilder.evalBinOpLL(state, BO_GT, - *firstLoc, *secondLoc, cmpTy); + SVal reverse = + svalBuilder.evalBinOpLL(state, BO_GT, *firstLoc, *secondLoc, cmpTy); Optional<DefinedOrUnknownSVal> reverseTest = reverse.getAs<DefinedOrUnknownSVal>(); if (!reverseTest) @@ -514,7 +500,7 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, } // Get the length, and make sure it too is known. - SVal LengthVal = state->getSVal(Size, LCtx); + SVal LengthVal = state->getSVal(Size.Expression, LCtx); Optional<NonLoc> Length = LengthVal.getAs<NonLoc>(); if (!Length) return state; @@ -523,22 +509,22 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, // Bail out if the cast fails. ASTContext &Ctx = svalBuilder.getContext(); QualType CharPtrTy = Ctx.getPointerType(Ctx.CharTy); - SVal FirstStart = svalBuilder.evalCast(*firstLoc, CharPtrTy, - First->getType()); + SVal FirstStart = + svalBuilder.evalCast(*firstLoc, CharPtrTy, First.Expression->getType()); Optional<Loc> FirstStartLoc = FirstStart.getAs<Loc>(); if (!FirstStartLoc) return state; // Compute the end of the first buffer. Bail out if THAT fails. - SVal FirstEnd = svalBuilder.evalBinOpLN(state, BO_Add, - *FirstStartLoc, *Length, CharPtrTy); + SVal FirstEnd = svalBuilder.evalBinOpLN(state, BO_Add, *FirstStartLoc, + *Length, CharPtrTy); Optional<Loc> FirstEndLoc = FirstEnd.getAs<Loc>(); if (!FirstEndLoc) return state; // Is the end of the first buffer past the start of the second buffer? - SVal Overlap = svalBuilder.evalBinOpLL(state, BO_GT, - *FirstEndLoc, *secondLoc, cmpTy); + SVal Overlap = + svalBuilder.evalBinOpLL(state, BO_GT, *FirstEndLoc, *secondLoc, cmpTy); Optional<DefinedOrUnknownSVal> OverlapTest = Overlap.getAs<DefinedOrUnknownSVal>(); if (!OverlapTest) @@ -548,7 +534,7 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, if (stateTrue && !stateFalse) { // Overlap! - emitOverlapBug(C, stateTrue, First, Second); + emitOverlapBug(C, stateTrue, First.Expression, Second.Expression); return nullptr; } @@ -723,7 +709,8 @@ ProgramStateRef CStringChecker::setCStringLength(ProgramStateRef state, case MemRegion::SymbolicRegionKind: case MemRegion::AllocaRegionKind: - case MemRegion::VarRegionKind: + case MemRegion::NonParamVarRegionKind: + case MemRegion::ParamVarRegionKind: case MemRegion::FieldRegionKind: case MemRegion::ObjCIvarRegionKind: // These are the types we can currently track string lengths for. @@ -828,7 +815,8 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, } case MemRegion::SymbolicRegionKind: case MemRegion::AllocaRegionKind: - case MemRegion::VarRegionKind: + case MemRegion::NonParamVarRegionKind: + case MemRegion::ParamVarRegionKind: case MemRegion::FieldRegionKind: case MemRegion::ObjCIvarRegionKind: return getCStringLengthForRegion(C, state, Ex, MR, hypothetical); @@ -935,14 +923,12 @@ bool CStringChecker::IsFirstBufInBound(CheckerContext &C, // Get the size of the array. const SubRegion *superReg = cast<SubRegion>(ER->getSuperRegion()); - SVal Extent = - svalBuilder.convertToArrayIndex(superReg->getExtent(svalBuilder)); - DefinedOrUnknownSVal ExtentSize = Extent.castAs<DefinedOrUnknownSVal>(); + DefinedOrUnknownSVal SizeDV = getDynamicSize(state, superReg, svalBuilder); // Get the index of the accessed element. DefinedOrUnknownSVal Idx = ER->getIndex().castAs<DefinedOrUnknownSVal>(); - ProgramStateRef StInBound = state->assumeInBound(Idx, ExtentSize, true); + ProgramStateRef StInBound = state->assumeInBound(Idx, SizeDV, true); return static_cast<bool>(StInBound); } @@ -1025,10 +1011,14 @@ bool CStringChecker::SummarizeRegion(raw_ostream &os, ASTContext &Ctx, os << "a C++ temp object of type " << cast<TypedValueRegion>(MR)->getValueType().getAsString(); return true; - case MemRegion::VarRegionKind: + case MemRegion::NonParamVarRegionKind: os << "a variable of type" << cast<TypedValueRegion>(MR)->getValueType().getAsString(); return true; + case MemRegion::ParamVarRegionKind: + os << "a parameter of type" + << cast<TypedValueRegion>(MR)->getValueType().getAsString(); + return true; case MemRegion::FieldRegionKind: os << "a field of type " << cast<TypedValueRegion>(MR)->getValueType().getAsString(); @@ -1069,13 +1059,12 @@ bool CStringChecker::memsetAux(const Expr *DstBuffer, SVal CharVal, // 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); + // Get the base region's size. + DefinedOrUnknownSVal SizeDV = getDynamicSize(State, BR, svalBuilder); ProgramStateRef StateWholeReg, StateNotWholeReg; std::tie(StateWholeReg, StateNotWholeReg) = - State->assume(svalBuilder.evalEQ(State, Extent, *SizeNL)); + State->assume(svalBuilder.evalEQ(State, SizeDV, *SizeNL)); // With the semantic of 'memset()', we should convert the CharVal to // unsigned char. @@ -1134,25 +1123,24 @@ bool CStringChecker::memsetAux(const Expr *DstBuffer, SVal CharVal, // evaluation of individual function calls. //===----------------------------------------------------------------------===// -void CStringChecker::evalCopyCommon(CheckerContext &C, - const CallExpr *CE, - ProgramStateRef state, - const Expr *Size, const Expr *Dest, - const Expr *Source, bool Restricted, +void CStringChecker::evalCopyCommon(CheckerContext &C, const CallExpr *CE, + ProgramStateRef state, SizeArgExpr Size, + DestinationArgExpr Dest, + SourceArgExpr Source, bool Restricted, bool IsMempcpy) const { CurrentFunctionDescription = "memory copy function"; // See if the size argument is zero. const LocationContext *LCtx = C.getLocationContext(); - SVal sizeVal = state->getSVal(Size, LCtx); - QualType sizeTy = Size->getType(); + SVal sizeVal = state->getSVal(Size.Expression, LCtx); + QualType sizeTy = Size.Expression->getType(); ProgramStateRef stateZeroSize, stateNonZeroSize; std::tie(stateZeroSize, stateNonZeroSize) = - assumeZero(C, state, sizeVal, sizeTy); + assumeZero(C, state, sizeVal, sizeTy); // Get the value of the Dest. - SVal destVal = state->getSVal(Dest, LCtx); + SVal destVal = state->getSVal(Dest.Expression, LCtx); // If the size is zero, there won't be any actual memory access, so // just bind the return value to the destination buffer and return. @@ -1168,24 +1156,23 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, // Ensure the destination is not null. If it is NULL there will be a // NULL pointer dereference. - state = checkNonNull(C, state, Dest, destVal, 1); + state = checkNonNull(C, state, Dest, destVal); if (!state) return; // Get the value of the Src. - SVal srcVal = state->getSVal(Source, LCtx); + SVal srcVal = state->getSVal(Source.Expression, LCtx); // Ensure the source is not null. If it is NULL there will be a // NULL pointer dereference. - state = checkNonNull(C, state, Source, srcVal, 2); + state = checkNonNull(C, state, Source, srcVal); if (!state) return; // Ensure the accesses are valid and that the buffers do not overlap. - const char * const writeWarning = - "Memory copy function overflows destination buffer"; - state = CheckBufferAccess(C, state, Size, Dest, Source, - writeWarning, /* sourceWarning = */ nullptr); + state = CheckBufferAccess(C, state, Dest, Size, AccessKind::write); + state = CheckBufferAccess(C, state, Source, Size, AccessKind::read); + if (Restricted) state = CheckOverlap(C, state, Size, Dest, Source); @@ -1200,9 +1187,9 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, ASTContext &Ctx = SvalBuilder.getContext(); QualType CharPtrTy = Ctx.getPointerType(Ctx.CharTy); SVal DestRegCharVal = - SvalBuilder.evalCast(destVal, CharPtrTy, Dest->getType()); + SvalBuilder.evalCast(destVal, CharPtrTy, Dest.Expression->getType()); SVal lastElement = C.getSValBuilder().evalBinOp( - state, BO_Add, DestRegCharVal, sizeVal, Dest->getType()); + state, BO_Add, DestRegCharVal, sizeVal, Dest.Expression->getType()); // If we don't know how much we copied, we can at least // conjure a return value for later. if (lastElement.isUnknown()) @@ -1223,120 +1210,136 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, // can use LazyCompoundVals to copy the source values into the destination. // This would probably remove any existing bindings past the end of the // copied region, but that's still an improvement over blank invalidation. - state = InvalidateBuffer(C, state, Dest, C.getSVal(Dest), - /*IsSourceBuffer*/false, Size); + state = + InvalidateBuffer(C, state, Dest.Expression, C.getSVal(Dest.Expression), + /*IsSourceBuffer*/ false, Size.Expression); // Invalidate the source (const-invalidation without const-pointer-escaping // the address of the top-level region). - state = InvalidateBuffer(C, state, Source, C.getSVal(Source), - /*IsSourceBuffer*/true, nullptr); + state = InvalidateBuffer(C, state, Source.Expression, + C.getSVal(Source.Expression), + /*IsSourceBuffer*/ true, nullptr); C.addTransition(state); } } - void CStringChecker::evalMemcpy(CheckerContext &C, const CallExpr *CE) const { // void *memcpy(void *restrict dst, const void *restrict src, size_t n); // The return value is the address of the destination buffer. - const Expr *Dest = CE->getArg(0); - ProgramStateRef state = C.getState(); + DestinationArgExpr Dest = {CE->getArg(0), 0}; + SourceArgExpr Src = {CE->getArg(1), 1}; + SizeArgExpr Size = {CE->getArg(2), 2}; + + ProgramStateRef State = C.getState(); - evalCopyCommon(C, CE, state, CE->getArg(2), Dest, CE->getArg(1), true); + constexpr bool IsRestricted = true; + constexpr bool IsMempcpy = false; + evalCopyCommon(C, CE, State, Size, Dest, Src, IsRestricted, IsMempcpy); } void CStringChecker::evalMempcpy(CheckerContext &C, const CallExpr *CE) const { // void *mempcpy(void *restrict dst, const void *restrict src, size_t n); // The return value is a pointer to the byte following the last written byte. - const Expr *Dest = CE->getArg(0); - ProgramStateRef state = C.getState(); + DestinationArgExpr Dest = {CE->getArg(0), 0}; + SourceArgExpr Src = {CE->getArg(1), 1}; + SizeArgExpr Size = {CE->getArg(2), 2}; - evalCopyCommon(C, CE, state, CE->getArg(2), Dest, CE->getArg(1), true, true); + constexpr bool IsRestricted = true; + constexpr bool IsMempcpy = true; + evalCopyCommon(C, CE, C.getState(), Size, Dest, Src, IsRestricted, IsMempcpy); } void CStringChecker::evalMemmove(CheckerContext &C, const CallExpr *CE) const { // void *memmove(void *dst, const void *src, size_t n); // The return value is the address of the destination buffer. - const Expr *Dest = CE->getArg(0); - ProgramStateRef state = C.getState(); + DestinationArgExpr Dest = {CE->getArg(0), 0}; + SourceArgExpr Src = {CE->getArg(1), 1}; + SizeArgExpr Size = {CE->getArg(2), 2}; - evalCopyCommon(C, CE, state, CE->getArg(2), Dest, CE->getArg(1)); + constexpr bool IsRestricted = false; + constexpr bool IsMempcpy = false; + evalCopyCommon(C, CE, C.getState(), Size, Dest, Src, IsRestricted, IsMempcpy); } void CStringChecker::evalBcopy(CheckerContext &C, const CallExpr *CE) const { // void bcopy(const void *src, void *dst, size_t n); - evalCopyCommon(C, CE, C.getState(), - CE->getArg(2), CE->getArg(1), CE->getArg(0)); + SourceArgExpr Src(CE->getArg(0), 0); + DestinationArgExpr Dest = {CE->getArg(1), 1}; + SizeArgExpr Size = {CE->getArg(2), 2}; + + constexpr bool IsRestricted = false; + constexpr bool IsMempcpy = false; + evalCopyCommon(C, CE, C.getState(), Size, Dest, Src, IsRestricted, IsMempcpy); } void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { // int memcmp(const void *s1, const void *s2, size_t n); CurrentFunctionDescription = "memory comparison function"; - const Expr *Left = CE->getArg(0); - const Expr *Right = CE->getArg(1); - const Expr *Size = CE->getArg(2); + AnyArgExpr Left = {CE->getArg(0), 0}; + AnyArgExpr Right = {CE->getArg(1), 1}; + SizeArgExpr Size = {CE->getArg(2), 2}; - ProgramStateRef state = C.getState(); - SValBuilder &svalBuilder = C.getSValBuilder(); + ProgramStateRef State = C.getState(); + SValBuilder &Builder = C.getSValBuilder(); + const LocationContext *LCtx = C.getLocationContext(); // See if the size argument is zero. - const LocationContext *LCtx = C.getLocationContext(); - SVal sizeVal = state->getSVal(Size, LCtx); - QualType sizeTy = Size->getType(); + SVal sizeVal = State->getSVal(Size.Expression, LCtx); + QualType sizeTy = Size.Expression->getType(); ProgramStateRef stateZeroSize, stateNonZeroSize; std::tie(stateZeroSize, stateNonZeroSize) = - assumeZero(C, state, sizeVal, sizeTy); + assumeZero(C, State, sizeVal, sizeTy); // If the size can be zero, the result will be 0 in that case, and we don't // have to check either of the buffers. if (stateZeroSize) { - state = stateZeroSize; - state = state->BindExpr(CE, LCtx, - svalBuilder.makeZeroVal(CE->getType())); - C.addTransition(state); + State = stateZeroSize; + State = State->BindExpr(CE, LCtx, Builder.makeZeroVal(CE->getType())); + C.addTransition(State); } // If the size can be nonzero, we have to check the other arguments. if (stateNonZeroSize) { - state = stateNonZeroSize; + State = stateNonZeroSize; // If we know the two buffers are the same, we know the result is 0. // First, get the two buffers' addresses. Another checker will have already // made sure they're not undefined. DefinedOrUnknownSVal LV = - state->getSVal(Left, LCtx).castAs<DefinedOrUnknownSVal>(); + State->getSVal(Left.Expression, LCtx).castAs<DefinedOrUnknownSVal>(); DefinedOrUnknownSVal RV = - state->getSVal(Right, LCtx).castAs<DefinedOrUnknownSVal>(); + State->getSVal(Right.Expression, LCtx).castAs<DefinedOrUnknownSVal>(); // See if they are the same. - DefinedOrUnknownSVal SameBuf = svalBuilder.evalEQ(state, LV, RV); - ProgramStateRef StSameBuf, StNotSameBuf; - std::tie(StSameBuf, StNotSameBuf) = state->assume(SameBuf); + ProgramStateRef SameBuffer, NotSameBuffer; + std::tie(SameBuffer, NotSameBuffer) = + State->assume(Builder.evalEQ(State, LV, RV)); // If the two arguments are the same buffer, we know the result is 0, // and we only need to check one size. - if (StSameBuf && !StNotSameBuf) { - state = StSameBuf; - state = CheckBufferAccess(C, state, Size, Left); - if (state) { - state = StSameBuf->BindExpr(CE, LCtx, - svalBuilder.makeZeroVal(CE->getType())); - C.addTransition(state); + if (SameBuffer && !NotSameBuffer) { + State = SameBuffer; + State = CheckBufferAccess(C, State, Left, Size, AccessKind::read); + if (State) { + State = + SameBuffer->BindExpr(CE, LCtx, Builder.makeZeroVal(CE->getType())); + C.addTransition(State); } return; } // If the two arguments might be different buffers, we have to check // the size of both of them. - assert(StNotSameBuf); - state = CheckBufferAccess(C, state, Size, Left, Right); - if (state) { + assert(NotSameBuffer); + State = CheckBufferAccess(C, State, Right, Size, AccessKind::read); + State = CheckBufferAccess(C, State, Left, Size, AccessKind::read); + if (State) { // The return value is the comparison result, which we don't know. - SVal CmpV = - svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()); - state = state->BindExpr(CE, LCtx, CmpV); - C.addTransition(state); + SVal CmpV = Builder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()); + State = State->BindExpr(CE, LCtx, CmpV); + C.addTransition(State); } } } @@ -1384,15 +1387,14 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, } // Check that the string argument is non-null. - const Expr *Arg = CE->getArg(0); - SVal ArgVal = state->getSVal(Arg, LCtx); - - state = checkNonNull(C, state, Arg, ArgVal, 1); + AnyArgExpr Arg = {CE->getArg(0), 0}; + SVal ArgVal = state->getSVal(Arg.Expression, LCtx); + state = checkNonNull(C, state, Arg, ArgVal); if (!state) return; - SVal strLength = getCStringLength(C, state, Arg, ArgVal); + SVal strLength = getCStringLength(C, state, Arg.Expression, ArgVal); // If the argument isn't a valid C string, there's no valid state to // transition to. @@ -1540,30 +1542,30 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, CurrentFunctionDescription = "string copy function"; else CurrentFunctionDescription = "string concatenation function"; + ProgramStateRef state = C.getState(); const LocationContext *LCtx = C.getLocationContext(); // Check that the destination is non-null. - const Expr *Dst = CE->getArg(0); - SVal DstVal = state->getSVal(Dst, LCtx); - - state = checkNonNull(C, state, Dst, DstVal, 1); + DestinationArgExpr Dst = {CE->getArg(0), 0}; + SVal DstVal = state->getSVal(Dst.Expression, LCtx); + state = checkNonNull(C, state, Dst, DstVal); if (!state) return; // Check that the source is non-null. - const Expr *srcExpr = CE->getArg(1); - SVal srcVal = state->getSVal(srcExpr, LCtx); - state = checkNonNull(C, state, srcExpr, srcVal, 2); + SourceArgExpr srcExpr = {CE->getArg(1), 1}; + SVal srcVal = state->getSVal(srcExpr.Expression, LCtx); + state = checkNonNull(C, state, srcExpr, srcVal); if (!state) return; // Get the string length of the source. - SVal strLength = getCStringLength(C, state, srcExpr, srcVal); + SVal strLength = getCStringLength(C, state, srcExpr.Expression, srcVal); Optional<NonLoc> strLengthNL = strLength.getAs<NonLoc>(); // Get the string length of the destination buffer. - SVal dstStrLength = getCStringLength(C, state, Dst, DstVal); + SVal dstStrLength = getCStringLength(C, state, Dst.Expression, DstVal); Optional<NonLoc> dstStrLengthNL = dstStrLength.getAs<NonLoc>(); // If the source isn't a valid C string, give up. @@ -1581,8 +1583,13 @@ 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); + // FIXME: Why do we choose the srcExpr if the access has no size? + // Note that the 3rd argument of the call would be the size parameter. + SizeArgExpr SrcExprAsSizeDummy = {srcExpr.Expression, srcExpr.ArgumentIndex}; + state = CheckOverlap( + C, state, + (IsBounded ? SizeArgExpr{CE->getArg(2), 2} : SrcExprAsSizeDummy), Dst, + srcExpr); if (!state) return; @@ -1590,11 +1597,12 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // If the function is strncpy, strncat, etc... it is bounded. if (IsBounded) { // Get the max number of characters to copy. - const Expr *lenExpr = CE->getArg(2); - SVal lenVal = state->getSVal(lenExpr, LCtx); + SizeArgExpr lenExpr = {CE->getArg(2), 2}; + SVal lenVal = state->getSVal(lenExpr.Expression, LCtx); // Protect against misdeclared strncpy(). - lenVal = svalBuilder.evalCast(lenVal, sizeTy, lenExpr->getType()); + lenVal = + svalBuilder.evalCast(lenVal, sizeTy, lenExpr.Expression->getType()); Optional<NonLoc> lenValNL = lenVal.getAs<NonLoc>(); @@ -1837,19 +1845,17 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // record the new string length. if (Optional<loc::MemRegionVal> dstRegVal = DstVal.getAs<loc::MemRegionVal>()) { - QualType ptrTy = Dst->getType(); + QualType ptrTy = Dst.Expression->getType(); // If we have an exact value on a bounded copy, use that to check for // overflows, rather than our estimate about how much is actually copied. - if (boundWarning) { - if (Optional<NonLoc> maxLastNL = maxLastElementIndex.getAs<NonLoc>()) { - SVal maxLastElement = svalBuilder.evalBinOpLN(state, BO_Add, *dstRegVal, - *maxLastNL, ptrTy); - state = CheckLocation(C, state, CE->getArg(2), maxLastElement, - boundWarning); - if (!state) - return; - } + if (Optional<NonLoc> maxLastNL = maxLastElementIndex.getAs<NonLoc>()) { + SVal maxLastElement = + svalBuilder.evalBinOpLN(state, BO_Add, *dstRegVal, *maxLastNL, ptrTy); + + state = CheckLocation(C, state, Dst, maxLastElement, AccessKind::write); + if (!state) + return; } // Then, if the final length is known... @@ -1859,9 +1865,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // ...and we haven't checked the bound, we'll check the actual copy. if (!boundWarning) { - const char * const warningMsg = - "String copy function overflows destination buffer"; - state = CheckLocation(C, state, Dst, lastElement, warningMsg); + state = CheckLocation(C, state, Dst, lastElement, AccessKind::write); if (!state) return; } @@ -1878,13 +1882,13 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // can use LazyCompoundVals to copy the source values into the destination. // 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); + state = InvalidateBuffer(C, state, Dst.Expression, *dstRegVal, + /*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); + state = InvalidateBuffer(C, state, srcExpr.Expression, srcVal, + /*IsSourceBuffer*/ true, nullptr); // Set the C string length of the destination, if we know it. if (IsBounded && (appendK == ConcatFnKind::none)) { @@ -1941,34 +1945,34 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, const LocationContext *LCtx = C.getLocationContext(); // Check that the first string is non-null - const Expr *s1 = CE->getArg(0); - SVal s1Val = state->getSVal(s1, LCtx); - state = checkNonNull(C, state, s1, s1Val, 1); + AnyArgExpr Left = {CE->getArg(0), 0}; + SVal LeftVal = state->getSVal(Left.Expression, LCtx); + state = checkNonNull(C, state, Left, LeftVal); if (!state) return; // Check that the second string is non-null. - const Expr *s2 = CE->getArg(1); - SVal s2Val = state->getSVal(s2, LCtx); - state = checkNonNull(C, state, s2, s2Val, 2); + AnyArgExpr Right = {CE->getArg(1), 1}; + SVal RightVal = state->getSVal(Right.Expression, LCtx); + state = checkNonNull(C, state, Right, RightVal); if (!state) return; // Get the string length of the first string or give up. - SVal s1Length = getCStringLength(C, state, s1, s1Val); - if (s1Length.isUndef()) + SVal LeftLength = getCStringLength(C, state, Left.Expression, LeftVal); + if (LeftLength.isUndef()) return; // Get the string length of the second string or give up. - SVal s2Length = getCStringLength(C, state, s2, s2Val); - if (s2Length.isUndef()) + SVal RightLength = getCStringLength(C, state, Right.Expression, RightVal); + if (RightLength.isUndef()) return; // If we know the two buffers are the same, we know the result is 0. // First, get the two buffers' addresses. Another checker will have already // made sure they're not undefined. - DefinedOrUnknownSVal LV = s1Val.castAs<DefinedOrUnknownSVal>(); - DefinedOrUnknownSVal RV = s2Val.castAs<DefinedOrUnknownSVal>(); + DefinedOrUnknownSVal LV = LeftVal.castAs<DefinedOrUnknownSVal>(); + DefinedOrUnknownSVal RV = RightVal.castAs<DefinedOrUnknownSVal>(); // See if they are the same. SValBuilder &svalBuilder = C.getSValBuilder(); @@ -1995,15 +1999,17 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, // For now, we only do this if they're both known string literals. // Attempt to extract string literals from both expressions. - const StringLiteral *s1StrLiteral = getCStringLiteral(C, state, s1, s1Val); - const StringLiteral *s2StrLiteral = getCStringLiteral(C, state, s2, s2Val); + const StringLiteral *LeftStrLiteral = + getCStringLiteral(C, state, Left.Expression, LeftVal); + const StringLiteral *RightStrLiteral = + getCStringLiteral(C, state, Right.Expression, RightVal); bool canComputeResult = false; SVal resultVal = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()); - if (s1StrLiteral && s2StrLiteral) { - StringRef s1StrRef = s1StrLiteral->getString(); - StringRef s2StrRef = s2StrLiteral->getString(); + if (LeftStrLiteral && RightStrLiteral) { + StringRef LeftStrRef = LeftStrLiteral->getString(); + StringRef RightStrRef = RightStrLiteral->getString(); if (IsBounded) { // Get the max number of characters to compare. @@ -2013,8 +2019,8 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, // If the length is known, we can get the right substrings. if (const llvm::APSInt *len = svalBuilder.getKnownValue(state, lenVal)) { // Create substrings of each to compare the prefix. - s1StrRef = s1StrRef.substr(0, (size_t)len->getZExtValue()); - s2StrRef = s2StrRef.substr(0, (size_t)len->getZExtValue()); + LeftStrRef = LeftStrRef.substr(0, (size_t)len->getZExtValue()); + RightStrRef = RightStrRef.substr(0, (size_t)len->getZExtValue()); canComputeResult = true; } } else { @@ -2024,17 +2030,17 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, if (canComputeResult) { // Real strcmp stops at null characters. - size_t s1Term = s1StrRef.find('\0'); + size_t s1Term = LeftStrRef.find('\0'); if (s1Term != StringRef::npos) - s1StrRef = s1StrRef.substr(0, s1Term); + LeftStrRef = LeftStrRef.substr(0, s1Term); - size_t s2Term = s2StrRef.find('\0'); + size_t s2Term = RightStrRef.find('\0'); if (s2Term != StringRef::npos) - s2StrRef = s2StrRef.substr(0, s2Term); + RightStrRef = RightStrRef.substr(0, s2Term); // Use StringRef's comparison methods to compute the actual result. - int compareRes = IgnoreCase ? s1StrRef.compare_lower(s2StrRef) - : s1StrRef.compare(s2StrRef); + int compareRes = IgnoreCase ? LeftStrRef.compare_lower(RightStrRef) + : LeftStrRef.compare(RightStrRef); // The strcmp function returns an integer greater than, equal to, or less // than zero, [c11, p7.24.4.2]. @@ -2064,8 +2070,9 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, void CStringChecker::evalStrsep(CheckerContext &C, const CallExpr *CE) const { //char *strsep(char **stringp, const char *delim); // Sanity: does the search string parameter match the return type? - const Expr *SearchStrPtr = CE->getArg(0); - QualType CharPtrTy = SearchStrPtr->getType()->getPointeeType(); + SourceArgExpr SearchStrPtr = {CE->getArg(0), 0}; + + QualType CharPtrTy = SearchStrPtr.Expression->getType()->getPointeeType(); if (CharPtrTy.isNull() || CE->getType().getUnqualifiedType() != CharPtrTy.getUnqualifiedType()) return; @@ -2076,15 +2083,15 @@ void CStringChecker::evalStrsep(CheckerContext &C, const CallExpr *CE) const { // Check that the search string pointer is non-null (though it may point to // a null string). - SVal SearchStrVal = State->getSVal(SearchStrPtr, LCtx); - State = checkNonNull(C, State, SearchStrPtr, SearchStrVal, 1); + SVal SearchStrVal = State->getSVal(SearchStrPtr.Expression, LCtx); + State = checkNonNull(C, State, SearchStrPtr, SearchStrVal); if (!State) return; // Check that the delimiter string is non-null. - const Expr *DelimStr = CE->getArg(1); - SVal DelimStrVal = State->getSVal(DelimStr, LCtx); - State = checkNonNull(C, State, DelimStr, DelimStrVal, 2); + AnyArgExpr DelimStr = {CE->getArg(1), 1}; + SVal DelimStrVal = State->getSVal(DelimStr.Expression, LCtx); + State = checkNonNull(C, State, DelimStr, DelimStrVal); if (!State) return; @@ -2096,8 +2103,8 @@ 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); + State = InvalidateBuffer(C, State, SearchStrPtr.Expression, Result, + /*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. @@ -2158,65 +2165,67 @@ void CStringChecker::evalStdCopyCommon(CheckerContext &C, } void CStringChecker::evalMemset(CheckerContext &C, const CallExpr *CE) const { + // void *memset(void *s, int c, size_t n); CurrentFunctionDescription = "memory set function"; - const Expr *Mem = CE->getArg(0); - const Expr *CharE = CE->getArg(1); - const Expr *Size = CE->getArg(2); + DestinationArgExpr Buffer = {CE->getArg(0), 0}; + AnyArgExpr CharE = {CE->getArg(1), 1}; + SizeArgExpr Size = {CE->getArg(2), 2}; + ProgramStateRef State = C.getState(); // See if the size argument is zero. const LocationContext *LCtx = C.getLocationContext(); - SVal SizeVal = State->getSVal(Size, LCtx); - QualType SizeTy = Size->getType(); + SVal SizeVal = C.getSVal(Size.Expression); + QualType SizeTy = Size.Expression->getType(); - ProgramStateRef StateZeroSize, StateNonZeroSize; - std::tie(StateZeroSize, StateNonZeroSize) = - assumeZero(C, State, SizeVal, SizeTy); + ProgramStateRef ZeroSize, NonZeroSize; + std::tie(ZeroSize, NonZeroSize) = assumeZero(C, State, SizeVal, SizeTy); // Get the value of the memory area. - SVal MemVal = State->getSVal(Mem, LCtx); + SVal BufferPtrVal = C.getSVal(Buffer.Expression); // If the size is zero, there won't be any actual memory access, so - // just bind the return value to the Mem buffer and return. - if (StateZeroSize && !StateNonZeroSize) { - StateZeroSize = StateZeroSize->BindExpr(CE, LCtx, MemVal); - C.addTransition(StateZeroSize); + // just bind the return value to the buffer and return. + if (ZeroSize && !NonZeroSize) { + ZeroSize = ZeroSize->BindExpr(CE, LCtx, BufferPtrVal); + C.addTransition(ZeroSize); return; } // Ensure the memory area is not null. // If it is NULL there will be a NULL pointer dereference. - State = checkNonNull(C, StateNonZeroSize, Mem, MemVal, 1); + State = checkNonNull(C, NonZeroSize, Buffer, BufferPtrVal); if (!State) return; - State = CheckBufferAccess(C, State, Size, Mem); + State = CheckBufferAccess(C, State, Buffer, Size, AccessKind::write); if (!State) return; // 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, C.getSVal(CharE), Size, C, State)) + if (!memsetAux(Buffer.Expression, C.getSVal(CharE.Expression), + Size.Expression, C, State)) return; - State = State->BindExpr(CE, LCtx, MemVal); + State = State->BindExpr(CE, LCtx, BufferPtrVal); C.addTransition(State); } void CStringChecker::evalBzero(CheckerContext &C, const CallExpr *CE) const { CurrentFunctionDescription = "memory clearance function"; - const Expr *Mem = CE->getArg(0); - const Expr *Size = CE->getArg(1); + DestinationArgExpr Buffer = {CE->getArg(0), 0}; + SizeArgExpr Size = {CE->getArg(1), 1}; SVal Zero = C.getSValBuilder().makeZeroVal(C.getASTContext().IntTy); ProgramStateRef State = C.getState(); // See if the size argument is zero. - SVal SizeVal = C.getSVal(Size); - QualType SizeTy = Size->getType(); + SVal SizeVal = C.getSVal(Size.Expression); + QualType SizeTy = Size.Expression->getType(); ProgramStateRef StateZeroSize, StateNonZeroSize; std::tie(StateZeroSize, StateNonZeroSize) = @@ -2230,19 +2239,19 @@ void CStringChecker::evalBzero(CheckerContext &C, const CallExpr *CE) const { } // Get the value of the memory area. - SVal MemVal = C.getSVal(Mem); + SVal MemVal = C.getSVal(Buffer.Expression); // Ensure the memory area is not null. // If it is NULL there will be a NULL pointer dereference. - State = checkNonNull(C, StateNonZeroSize, Mem, MemVal, 1); + State = checkNonNull(C, StateNonZeroSize, Buffer, MemVal); if (!State) return; - State = CheckBufferAccess(C, State, Size, Mem); + State = CheckBufferAccess(C, State, Buffer, Size, AccessKind::write); if (!State) return; - if (!memsetAux(Mem, Zero, Size, C, State)) + if (!memsetAux(Buffer.Expression, Zero, Size.Expression, C, State)) return; C.addTransition(State); @@ -2434,7 +2443,7 @@ void ento::registerCStringModeling(CheckerManager &Mgr) { Mgr.registerChecker<CStringChecker>(); } -bool ento::shouldRegisterCStringModeling(const LangOptions &LO) { +bool ento::shouldRegisterCStringModeling(const CheckerManager &mgr) { return true; } @@ -2445,7 +2454,7 @@ bool ento::shouldRegisterCStringModeling(const LangOptions &LO) { checker->Filter.CheckName##name = mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name(const LangOptions &LO) { return true; } + bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; } REGISTER_CHECKER(CStringNullArg) REGISTER_CHECKER(CStringOutOfBounds) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp index d84fcc69a492..888724f7ea3b 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp @@ -291,6 +291,6 @@ void ento::registerCStringSyntaxChecker(CheckerManager &mgr) { mgr.registerChecker<CStringSyntaxChecker>(); } -bool ento::shouldRegisterCStringSyntaxChecker(const LangOptions &LO) { +bool ento::shouldRegisterCStringSyntaxChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp index 01f5b9c889e3..24776338ce10 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp @@ -53,21 +53,21 @@ void CXXSelfAssignmentChecker::checkBeginFunction(CheckerContext &C) const { ProgramStateRef SelfAssignState = State->bindLoc(Param, ThisVal, LCtx); const NoteTag *SelfAssignTag = - C.getNoteTag([MD](BugReport &BR) -> std::string { + C.getNoteTag([MD](PathSensitiveBugReport &BR) -> std::string { SmallString<256> Msg; llvm::raw_svector_ostream Out(Msg); Out << "Assuming " << MD->getParamDecl(0)->getName() << " == *this"; - return Out.str(); + return std::string(Out.str()); }); C.addTransition(SelfAssignState, SelfAssignTag); ProgramStateRef NonSelfAssignState = State->bindLoc(Param, ParamVal, LCtx); const NoteTag *NonSelfAssignTag = - C.getNoteTag([MD](BugReport &BR) -> std::string { + C.getNoteTag([MD](PathSensitiveBugReport &BR) -> std::string { SmallString<256> Msg; llvm::raw_svector_ostream Out(Msg); Out << "Assuming " << MD->getParamDecl(0)->getName() << " != *this"; - return Out.str(); + return std::string(Out.str()); }); C.addTransition(NonSelfAssignState, NonSelfAssignTag); } @@ -76,6 +76,6 @@ void ento::registerCXXSelfAssignmentChecker(CheckerManager &Mgr) { Mgr.registerChecker<CXXSelfAssignmentChecker>(); } -bool ento::shouldRegisterCXXSelfAssignmentChecker(const LangOptions &LO) { +bool ento::shouldRegisterCXXSelfAssignmentChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp index 2fcb765cd4ee..3e46e2372516 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp @@ -11,9 +11,10 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/ParentMap.h" #include "clang/Basic/TargetInfo.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -21,6 +22,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Casting.h" #include "llvm/Support/raw_ostream.h" using namespace clang; @@ -29,11 +31,8 @@ using namespace ento; namespace { class CallAndMessageChecker - : public Checker< check::PreStmt<CallExpr>, - check::PreStmt<CXXDeleteExpr>, - check::PreObjCMessage, - check::ObjCMessageNil, - check::PreCall > { + : public Checker<check::PreObjCMessage, check::ObjCMessageNil, + check::PreCall> { mutable std::unique_ptr<BugType> BT_call_null; mutable std::unique_ptr<BugType> BT_call_undef; mutable std::unique_ptr<BugType> BT_cxx_call_null; @@ -48,11 +47,37 @@ class CallAndMessageChecker mutable std::unique_ptr<BugType> BT_call_few_args; public: - DefaultBool Check_CallAndMessageUnInitRefArg; - CheckerNameRef CheckName_CallAndMessageUnInitRefArg; + // These correspond with the checker options. Looking at other checkers such + // as MallocChecker and CStringChecker, this is similar as to how they pull + // off having a modeling class, but emitting diagnostics under a smaller + // checker's name that can be safely disabled without disturbing the + // underlaying modeling engine. + // The reason behind having *checker options* rather then actual *checkers* + // here is that CallAndMessage is among the oldest checkers out there, and can + // be responsible for the majority of the reports on any given project. This + // is obviously not ideal, but changing checker name has the consequence of + // changing the issue hashes associated with the reports, and databases + // relying on this (CodeChecker, for instance) would suffer greatly. + // If we ever end up making changes to the issue hash generation algorithm, or + // the warning messages here, we should totally jump on the opportunity to + // convert these to actual checkers. + enum CheckKind { + CK_FunctionPointer, + CK_ParameterCount, + CK_CXXThisMethodCall, + CK_CXXDeallocationArg, + CK_ArgInitializedness, + CK_ArgPointeeInitializedness, + CK_NilReceiver, + CK_UndefReceiver, + CK_NumCheckKinds + }; + + DefaultBool ChecksEnabled[CK_NumCheckKinds]; + // The original core.CallAndMessage checker name. This should rather be an + // array, as seen in MallocChecker and CStringChecker. + CheckerNameRef OriginalName; - void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; - void checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const; void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const; /// Fill in the return value that results from messaging nil based on the @@ -62,6 +87,25 @@ public: void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + ProgramStateRef checkFunctionPointerCall(const CallExpr *CE, + CheckerContext &C, + ProgramStateRef State) const; + + ProgramStateRef checkCXXMethodCall(const CXXInstanceCall *CC, + CheckerContext &C, + ProgramStateRef State) const; + + ProgramStateRef checkParameterCount(const CallEvent &Call, CheckerContext &C, + ProgramStateRef State) const; + + ProgramStateRef checkCXXDeallocation(const CXXDeallocatorCall *DC, + CheckerContext &C, + ProgramStateRef State) const; + + ProgramStateRef checkArgInitializedness(const CallEvent &Call, + CheckerContext &C, + ProgramStateRef State) const; + private: bool PreVisitProcessArg(CheckerContext &C, SVal V, SourceRange ArgRange, const Expr *ArgEx, int ArgumentNumber, @@ -79,7 +123,7 @@ private: void LazyInit_BT(const char *desc, std::unique_ptr<BugType> &BT) const { if (!BT) - BT.reset(new BuiltinBug(this, desc)); + BT.reset(new BuiltinBug(OriginalName, desc)); } bool uninitRefOrPointer(CheckerContext &C, const SVal &V, SourceRange ArgRange, const Expr *ArgEx, @@ -144,7 +188,10 @@ bool CallAndMessageChecker::uninitRefOrPointer( CheckerContext &C, const SVal &V, SourceRange ArgRange, const Expr *ArgEx, std::unique_ptr<BugType> &BT, const ParmVarDecl *ParamDecl, const char *BD, int ArgumentNumber) const { - if (!Check_CallAndMessageUnInitRefArg) + + // The pointee being uninitialized is a sign of code smell, not a bug, no need + // to sink here. + if (!ChecksEnabled[CK_ArgPointeeInitializedness]) return false; // No parameter declaration available, i.e. variadic function argument. @@ -246,6 +293,10 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, return true; if (V.isUndef()) { + if (!ChecksEnabled[CK_ArgInitializedness]) { + C.addSink(); + return true; + } if (ExplodedNode *N = C.generateErrorNode()) { LazyInit_BT(BD, BT); // Generate a report for this bug. @@ -272,6 +323,10 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, D->getStore()); if (F.Find(D->getRegion())) { + if (!ChecksEnabled[CK_ArgInitializedness]) { + C.addSink(); + return true; + } if (ExplodedNode *N = C.generateErrorNode()) { LazyInit_BT(BD, BT); SmallString<512> Str; @@ -311,126 +366,158 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, return false; } -void CallAndMessageChecker::checkPreStmt(const CallExpr *CE, - CheckerContext &C) const{ +ProgramStateRef CallAndMessageChecker::checkFunctionPointerCall( + const CallExpr *CE, CheckerContext &C, ProgramStateRef State) const { const Expr *Callee = CE->getCallee()->IgnoreParens(); - ProgramStateRef State = C.getState(); const LocationContext *LCtx = C.getLocationContext(); SVal L = State->getSVal(Callee, LCtx); if (L.isUndef()) { + if (!ChecksEnabled[CK_FunctionPointer]) { + C.addSink(State); + return nullptr; + } if (!BT_call_undef) BT_call_undef.reset(new BuiltinBug( - this, "Called function pointer is an uninitialized pointer value")); + OriginalName, + "Called function pointer is an uninitialized pointer value")); emitBadCall(BT_call_undef.get(), C, Callee); - return; + return nullptr; } ProgramStateRef StNonNull, StNull; std::tie(StNonNull, StNull) = State->assume(L.castAs<DefinedOrUnknownSVal>()); if (StNull && !StNonNull) { + if (!ChecksEnabled[CK_FunctionPointer]) { + C.addSink(StNull); + return nullptr; + } if (!BT_call_null) BT_call_null.reset(new BuiltinBug( - this, "Called function pointer is null (null dereference)")); + OriginalName, "Called function pointer is null (null dereference)")); emitBadCall(BT_call_null.get(), C, Callee); - return; + return nullptr; } - C.addTransition(StNonNull); + return StNonNull; } -void CallAndMessageChecker::checkPreStmt(const CXXDeleteExpr *DE, - CheckerContext &C) const { +ProgramStateRef CallAndMessageChecker::checkParameterCount( + const CallEvent &Call, CheckerContext &C, ProgramStateRef State) const { - SVal Arg = C.getSVal(DE->getArgument()); - if (Arg.isUndef()) { - StringRef Desc; - ExplodedNode *N = C.generateErrorNode(); - if (!N) - return; - if (!BT_cxx_delete_undef) - BT_cxx_delete_undef.reset( - new BuiltinBug(this, "Uninitialized argument value")); - if (DE->isArrayFormAsWritten()) - Desc = "Argument to 'delete[]' is uninitialized"; - else - Desc = "Argument to 'delete' is uninitialized"; - BugType *BT = BT_cxx_delete_undef.get(); - auto R = std::make_unique<PathSensitiveBugReport>(*BT, Desc, N); - bugreporter::trackExpressionValue(N, DE, *R); - C.emitReport(std::move(R)); - return; + // If we have a function or block declaration, we can make sure we pass + // enough parameters. + unsigned Params = Call.parameters().size(); + if (Call.getNumArgs() >= Params) + return State; + + if (!ChecksEnabled[CK_ParameterCount]) { + C.addSink(State); + return nullptr; + } + + ExplodedNode *N = C.generateErrorNode(); + if (!N) + return nullptr; + + LazyInit_BT("Function call with too few arguments", BT_call_few_args); + + SmallString<512> Str; + llvm::raw_svector_ostream os(Str); + if (isa<AnyFunctionCall>(Call)) { + os << "Function "; + } else { + assert(isa<BlockCall>(Call)); + os << "Block "; } + os << "taking " << Params << " argument" << (Params == 1 ? "" : "s") + << " is called with fewer (" << Call.getNumArgs() << ")"; + + C.emitReport( + std::make_unique<PathSensitiveBugReport>(*BT_call_few_args, os.str(), N)); + return nullptr; } -void CallAndMessageChecker::checkPreCall(const CallEvent &Call, - CheckerContext &C) const { - ProgramStateRef State = C.getState(); +ProgramStateRef CallAndMessageChecker::checkCXXMethodCall( + const CXXInstanceCall *CC, CheckerContext &C, ProgramStateRef State) const { - // If this is a call to a C++ method, check if the callee is null or - // undefined. - if (const CXXInstanceCall *CC = dyn_cast<CXXInstanceCall>(&Call)) { - SVal V = CC->getCXXThisVal(); - if (V.isUndef()) { - if (!BT_cxx_call_undef) - BT_cxx_call_undef.reset( - new BuiltinBug(this, "Called C++ object pointer is uninitialized")); - emitBadCall(BT_cxx_call_undef.get(), C, CC->getCXXThisExpr()); - return; + SVal V = CC->getCXXThisVal(); + if (V.isUndef()) { + if (!ChecksEnabled[CK_CXXThisMethodCall]) { + C.addSink(State); + return nullptr; } + if (!BT_cxx_call_undef) + BT_cxx_call_undef.reset(new BuiltinBug( + OriginalName, "Called C++ object pointer is uninitialized")); + emitBadCall(BT_cxx_call_undef.get(), C, CC->getCXXThisExpr()); + return nullptr; + } - ProgramStateRef StNonNull, StNull; - std::tie(StNonNull, StNull) = - State->assume(V.castAs<DefinedOrUnknownSVal>()); + ProgramStateRef StNonNull, StNull; + std::tie(StNonNull, StNull) = State->assume(V.castAs<DefinedOrUnknownSVal>()); - if (StNull && !StNonNull) { - if (!BT_cxx_call_null) - BT_cxx_call_null.reset( - new BuiltinBug(this, "Called C++ object pointer is null")); - emitBadCall(BT_cxx_call_null.get(), C, CC->getCXXThisExpr()); - return; + if (StNull && !StNonNull) { + if (!ChecksEnabled[CK_CXXThisMethodCall]) { + C.addSink(StNull); + return nullptr; } - - State = StNonNull; + if (!BT_cxx_call_null) + BT_cxx_call_null.reset( + new BuiltinBug(OriginalName, "Called C++ object pointer is null")); + emitBadCall(BT_cxx_call_null.get(), C, CC->getCXXThisExpr()); + return nullptr; } - const Decl *D = Call.getDecl(); - if (D && (isa<FunctionDecl>(D) || isa<BlockDecl>(D))) { - // If we have a function or block declaration, we can make sure we pass - // enough parameters. - unsigned Params = Call.parameters().size(); - if (Call.getNumArgs() < Params) { - ExplodedNode *N = C.generateErrorNode(); - if (!N) - return; - - LazyInit_BT("Function call with too few arguments", BT_call_few_args); - - SmallString<512> Str; - llvm::raw_svector_ostream os(Str); - if (isa<FunctionDecl>(D)) { - os << "Function "; - } else { - assert(isa<BlockDecl>(D)); - os << "Block "; - } - os << "taking " << Params << " argument" - << (Params == 1 ? "" : "s") << " is called with fewer (" - << Call.getNumArgs() << ")"; + return StNonNull; +} - C.emitReport(std::make_unique<PathSensitiveBugReport>(*BT_call_few_args, - os.str(), N)); - } +ProgramStateRef +CallAndMessageChecker::checkCXXDeallocation(const CXXDeallocatorCall *DC, + CheckerContext &C, + ProgramStateRef State) const { + const CXXDeleteExpr *DE = DC->getOriginExpr(); + assert(DE); + SVal Arg = C.getSVal(DE->getArgument()); + if (!Arg.isUndef()) + return State; + + if (!ChecksEnabled[CK_CXXDeallocationArg]) { + C.addSink(State); + return nullptr; } + StringRef Desc; + ExplodedNode *N = C.generateErrorNode(); + if (!N) + return nullptr; + if (!BT_cxx_delete_undef) + BT_cxx_delete_undef.reset( + new BuiltinBug(OriginalName, "Uninitialized argument value")); + if (DE->isArrayFormAsWritten()) + Desc = "Argument to 'delete[]' is uninitialized"; + else + Desc = "Argument to 'delete' is uninitialized"; + BugType *BT = BT_cxx_delete_undef.get(); + auto R = std::make_unique<PathSensitiveBugReport>(*BT, Desc, N); + bugreporter::trackExpressionValue(N, DE, *R); + C.emitReport(std::move(R)); + return nullptr; +} + +ProgramStateRef CallAndMessageChecker::checkArgInitializedness( + const CallEvent &Call, CheckerContext &C, ProgramStateRef State) const { + + const Decl *D = Call.getDecl(); + // Don't check for uninitialized field values in arguments if the // caller has a body that is available and we have the chance to inline it. // This is a hack, but is a reasonable compromise betweens sometimes warning // and sometimes not depending on if we decide to inline a function. const bool checkUninitFields = - !(C.getAnalysisManager().shouldInlineCall() && (D && D->getBody())); + !(C.getAnalysisManager().shouldInlineCall() && (D && D->getBody())); std::unique_ptr<BugType> *BT; if (isa<ObjCMethodCall>(Call)) @@ -441,13 +528,45 @@ void CallAndMessageChecker::checkPreCall(const CallEvent &Call, const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(D); for (unsigned i = 0, e = Call.getNumArgs(); i != e; ++i) { const ParmVarDecl *ParamDecl = nullptr; - if(FD && i < FD->getNumParams()) + if (FD && i < FD->getNumParams()) ParamDecl = FD->getParamDecl(i); if (PreVisitProcessArg(C, Call.getArgSVal(i), Call.getArgSourceRange(i), - Call.getArgExpr(i), i, - checkUninitFields, Call, *BT, ParamDecl)) - return; + Call.getArgExpr(i), i, checkUninitFields, Call, *BT, + ParamDecl)) + return nullptr; } + return State; +} + +void CallAndMessageChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + if (const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr())) + State = checkFunctionPointerCall(CE, C, State); + + if (!State) + return; + + if (Call.getDecl()) + State = checkParameterCount(Call, C, State); + + if (!State) + return; + + if (const auto *CC = dyn_cast<CXXInstanceCall>(&Call)) + State = checkCXXMethodCall(CC, C, State); + + if (!State) + return; + + if (const auto *DC = dyn_cast<CXXDeallocatorCall>(&Call)) + State = checkCXXDeallocation(DC, C, State); + + if (!State) + return; + + State = checkArgInitializedness(Call, C, State); // If we make it here, record our assumptions about the callee. C.addTransition(State); @@ -457,12 +576,16 @@ void CallAndMessageChecker::checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const { SVal recVal = msg.getReceiverSVal(); if (recVal.isUndef()) { + if (!ChecksEnabled[CK_UndefReceiver]) { + C.addSink(); + return; + } if (ExplodedNode *N = C.generateErrorNode()) { BugType *BT = nullptr; switch (msg.getMessageKind()) { case OCM_Message: if (!BT_msg_undef) - BT_msg_undef.reset(new BuiltinBug(this, + BT_msg_undef.reset(new BuiltinBug(OriginalName, "Receiver in message expression " "is an uninitialized value")); BT = BT_msg_undef.get(); @@ -470,13 +593,15 @@ void CallAndMessageChecker::checkPreObjCMessage(const ObjCMethodCall &msg, case OCM_PropertyAccess: if (!BT_objc_prop_undef) BT_objc_prop_undef.reset(new BuiltinBug( - this, "Property access on an uninitialized object pointer")); + OriginalName, + "Property access on an uninitialized object pointer")); BT = BT_objc_prop_undef.get(); break; case OCM_Subscript: if (!BT_objc_subscript_undef) BT_objc_subscript_undef.reset(new BuiltinBug( - this, "Subscript access on an uninitialized object pointer")); + OriginalName, + "Subscript access on an uninitialized object pointer")); BT = BT_objc_subscript_undef.get(); break; } @@ -503,10 +628,14 @@ void CallAndMessageChecker::checkObjCMessageNil(const ObjCMethodCall &msg, void CallAndMessageChecker::emitNilReceiverBug(CheckerContext &C, const ObjCMethodCall &msg, ExplodedNode *N) const { + if (!ChecksEnabled[CK_NilReceiver]) { + C.addSink(); + return; + } if (!BT_msg_ret) - BT_msg_ret.reset( - new BuiltinBug(this, "Receiver in message expression is 'nil'")); + BT_msg_ret.reset(new BuiltinBug(OriginalName, + "Receiver in message expression is 'nil'")); const ObjCMessageExpr *ME = msg.getOriginExpr(); @@ -601,20 +730,34 @@ void CallAndMessageChecker::HandleNilReceiver(CheckerContext &C, C.addTransition(state); } -void ento::registerCallAndMessageChecker(CheckerManager &mgr) { +void ento::registerCallAndMessageModeling(CheckerManager &mgr) { mgr.registerChecker<CallAndMessageChecker>(); } -bool ento::shouldRegisterCallAndMessageChecker(const LangOptions &LO) { +bool ento::shouldRegisterCallAndMessageModeling(const CheckerManager &mgr) { return true; } -void ento::registerCallAndMessageUnInitRefArg(CheckerManager &mgr) { - CallAndMessageChecker *Checker = mgr.getChecker<CallAndMessageChecker>(); - Checker->Check_CallAndMessageUnInitRefArg = true; - Checker->CheckName_CallAndMessageUnInitRefArg = mgr.getCurrentCheckerName(); +void ento::registerCallAndMessageChecker(CheckerManager &mgr) { + CallAndMessageChecker *checker = mgr.getChecker<CallAndMessageChecker>(); + + checker->OriginalName = mgr.getCurrentCheckerName(); + +#define QUERY_CHECKER_OPTION(OPTION) \ + checker->ChecksEnabled[CallAndMessageChecker::CK_##OPTION] = \ + mgr.getAnalyzerOptions().getCheckerBooleanOption( \ + mgr.getCurrentCheckerName(), #OPTION); + + QUERY_CHECKER_OPTION(FunctionPointer) + QUERY_CHECKER_OPTION(ParameterCount) + QUERY_CHECKER_OPTION(CXXThisMethodCall) + QUERY_CHECKER_OPTION(CXXDeallocationArg) + QUERY_CHECKER_OPTION(ArgInitializedness) + QUERY_CHECKER_OPTION(ArgPointeeInitializedness) + QUERY_CHECKER_OPTION(NilReceiver) + QUERY_CHECKER_OPTION(UndefReceiver) } -bool ento::shouldRegisterCallAndMessageUnInitRefArg(const LangOptions &LO) { +bool ento::shouldRegisterCallAndMessageChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp index 51c1d4409929..a498f252e693 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp @@ -10,12 +10,14 @@ // whether the size of the symbolic region is a multiple of the size of T. // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" + #include "clang/AST/CharUnits.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" using namespace clang; using namespace ento; @@ -109,12 +111,13 @@ void CastSizeChecker::checkPreStmt(const CastExpr *CE,CheckerContext &C) const { return; SValBuilder &svalBuilder = C.getSValBuilder(); - SVal extent = SR->getExtent(svalBuilder); - const llvm::APSInt *extentInt = svalBuilder.getKnownValue(state, extent); - if (!extentInt) + + DefinedOrUnknownSVal Size = getDynamicSize(state, SR, svalBuilder); + const llvm::APSInt *SizeInt = svalBuilder.getKnownValue(state, Size); + if (!SizeInt) return; - CharUnits regionSize = CharUnits::fromQuantity(extentInt->getSExtValue()); + CharUnits regionSize = CharUnits::fromQuantity(SizeInt->getZExtValue()); CharUnits typeSize = C.getASTContext().getTypeSizeInChars(ToPointeeTy); // Ignore void, and a few other un-sizeable types. @@ -143,10 +146,11 @@ void ento::registerCastSizeChecker(CheckerManager &mgr) { mgr.registerChecker<CastSizeChecker>(); } -bool ento::shouldRegisterCastSizeChecker(const LangOptions &LO) { +bool ento::shouldRegisterCastSizeChecker(const CheckerManager &mgr) { // PR31226: C++ is more complicated than what this checker currently supports. // There are derived-to-base casts, there are different rules for 0-size // structures, no flexible arrays, etc. // FIXME: Disabled on C++ for now. + const LangOptions &LO = mgr.getLangOpts(); return !LO.CPlusPlus; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp index 93665596be29..e674ec43bcd9 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp @@ -120,6 +120,6 @@ void ento::registerCastToStructChecker(CheckerManager &mgr) { mgr.registerChecker<CastToStructChecker>(); } -bool ento::shouldRegisterCastToStructChecker(const LangOptions &LO) { +bool ento::shouldRegisterCastToStructChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp index cc1c9a66b90e..1ef70b650414 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -30,7 +30,7 @@ using namespace clang; using namespace ento; namespace { -class CastValueChecker : public Checker<eval::Call> { +class CastValueChecker : public Checker<check::DeadSymbols, eval::Call> { enum class CallKind { Function, Method, InstanceOf }; using CastCheck = @@ -51,6 +51,7 @@ public: // 1) isa: The parameter is non-null, returns boolean. // 2) isa_and_nonnull: The parameter is null or non-null, returns boolean. bool evalCall(const CallEvent &Call, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; private: // These are known in the LLVM project. The pairs are in the following form: @@ -129,7 +130,7 @@ static const NoteTag *getNoteTag(CheckerContext &C, Out << ' ' << (CastSucceeds ? "is a" : "is not a") << " '" << CastToName << '\''; - return Out.str(); + return std::string(Out.str()); }, /*IsPrunable=*/true); } @@ -432,10 +433,15 @@ bool CastValueChecker::evalCall(const CallEvent &Call, return true; } +void CastValueChecker::checkDeadSymbols(SymbolReaper &SR, + CheckerContext &C) const { + C.addTransition(removeDeadCasts(C.getState(), SR)); +} + void ento::registerCastValueChecker(CheckerManager &Mgr) { Mgr.registerChecker<CastValueChecker>(); } -bool ento::shouldRegisterCastValueChecker(const LangOptions &LO) { +bool ento::shouldRegisterCastValueChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp index 50b872bd8682..13836f08a61e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp @@ -1088,7 +1088,8 @@ void ento::registerObjCDeallocChecker(CheckerManager &Mgr) { Mgr.registerChecker<ObjCDeallocChecker>(); } -bool ento::shouldRegisterObjCDeallocChecker(const LangOptions &LO) { +bool ento::shouldRegisterObjCDeallocChecker(const CheckerManager &mgr) { // These checker only makes sense under MRR. + const LangOptions &LO = mgr.getLangOpts(); return LO.getGC() != LangOptions::GCOnly && !LO.ObjCAutoRefCount; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp index 1694c237cda4..175dfcef0df4 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp @@ -138,6 +138,6 @@ void ento::registerObjCMethSigsChecker(CheckerManager &mgr) { mgr.registerChecker<ObjCMethSigsChecker>(); } -bool ento::shouldRegisterObjCMethSigsChecker(const LangOptions &LO) { +bool ento::shouldRegisterObjCMethSigsChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp index 48fee4a0ffb7..dc9cd717be9e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp @@ -1,6 +1,19 @@ +//==- CheckPlacementNew.cpp - Check for placement new operation --*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines a check for misuse of the default placement new operator. +// +//===----------------------------------------------------------------------===// + #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" #include "llvm/Support/FormatVariadic.h" using namespace clang; @@ -12,51 +25,59 @@ public: void checkPreStmt(const CXXNewExpr *NE, CheckerContext &C) const; private: + bool checkPlaceCapacityIsSufficient(const CXXNewExpr *NE, + CheckerContext &C) const; + + bool checkPlaceIsAlignedProperly(const CXXNewExpr *NE, + CheckerContext &C) const; + // Returns the size of the target in a placement new expression. // E.g. in "new (&s) long" it returns the size of `long`. - SVal getExtentSizeOfNewTarget(const CXXNewExpr *NE, ProgramStateRef State, - CheckerContext &C) const; + SVal getExtentSizeOfNewTarget(const CXXNewExpr *NE, CheckerContext &C, + bool &IsArray) const; // Returns the size of the place in a placement new expression. // E.g. in "new (&s) long" it returns the size of `s`. - SVal getExtentSizeOfPlace(const Expr *NE, ProgramStateRef State, - CheckerContext &C) const; - BugType BT{this, "Insufficient storage for placement new", - categories::MemoryError}; + SVal getExtentSizeOfPlace(const CXXNewExpr *NE, CheckerContext &C) const; + + void emitBadAlignReport(const Expr *P, CheckerContext &C, + unsigned AllocatedTAlign, + unsigned StorageTAlign) const; + unsigned getStorageAlign(CheckerContext &C, const ValueDecl *VD) const; + + void checkElementRegionAlign(const ElementRegion *R, CheckerContext &C, + const Expr *P, unsigned AllocatedTAlign) const; + + void checkFieldRegionAlign(const FieldRegion *R, CheckerContext &C, + const Expr *P, unsigned AllocatedTAlign) const; + + bool isVarRegionAlignedProperly(const VarRegion *R, CheckerContext &C, + const Expr *P, + unsigned AllocatedTAlign) const; + + BugType SBT{this, "Insufficient storage for placement new", + categories::MemoryError}; + BugType ABT{this, "Bad align storage for placement new", + categories::MemoryError}; }; } // namespace -SVal PlacementNewChecker::getExtentSizeOfPlace(const Expr *Place, - ProgramStateRef State, +SVal PlacementNewChecker::getExtentSizeOfPlace(const CXXNewExpr *NE, CheckerContext &C) const { - const MemRegion *MRegion = C.getSVal(Place).getAsRegion(); - if (!MRegion) - return UnknownVal(); - RegionOffset Offset = MRegion->getAsOffset(); - if (Offset.hasSymbolicOffset()) - return UnknownVal(); - const MemRegion *BaseRegion = MRegion->getBaseRegion(); - if (!BaseRegion) - return UnknownVal(); - - SValBuilder &SvalBuilder = C.getSValBuilder(); - NonLoc OffsetInBytes = SvalBuilder.makeArrayIndex( - Offset.getOffset() / C.getASTContext().getCharWidth()); - DefinedOrUnknownSVal ExtentInBytes = - BaseRegion->castAs<SubRegion>()->getExtent(SvalBuilder); - - return SvalBuilder.evalBinOp(State, BinaryOperator::Opcode::BO_Sub, - ExtentInBytes, OffsetInBytes, - SvalBuilder.getArrayIndexType()); + const Expr *Place = NE->getPlacementArg(0); + return getDynamicSizeWithOffset(C.getState(), C.getSVal(Place)); } SVal PlacementNewChecker::getExtentSizeOfNewTarget(const CXXNewExpr *NE, - ProgramStateRef State, - CheckerContext &C) const { + CheckerContext &C, + bool &IsArray) const { + ProgramStateRef State = C.getState(); SValBuilder &SvalBuilder = C.getSValBuilder(); QualType ElementType = NE->getAllocatedType(); ASTContext &AstContext = C.getASTContext(); CharUnits TypeSize = AstContext.getTypeSizeInChars(ElementType); + IsArray = false; if (NE->isArray()) { + IsArray = true; const Expr *SizeExpr = *NE->getArraySize(); SVal ElementCount = C.getSVal(SizeExpr); if (auto ElementCountNL = ElementCount.getAs<NonLoc>()) { @@ -78,44 +99,218 @@ SVal PlacementNewChecker::getExtentSizeOfNewTarget(const CXXNewExpr *NE, return UnknownVal(); } -void PlacementNewChecker::checkPreStmt(const CXXNewExpr *NE, - CheckerContext &C) const { - // Check only the default placement new. - if (!NE->getOperatorNew()->isReservedGlobalPlacementOperator()) - return; - if (NE->getNumPlacementArgs() == 0) - return; - - ProgramStateRef State = C.getState(); - SVal SizeOfTarget = getExtentSizeOfNewTarget(NE, State, C); - const Expr *Place = NE->getPlacementArg(0); - SVal SizeOfPlace = getExtentSizeOfPlace(Place, State, C); +bool PlacementNewChecker::checkPlaceCapacityIsSufficient( + const CXXNewExpr *NE, CheckerContext &C) const { + bool IsArrayTypeAllocated; + SVal SizeOfTarget = getExtentSizeOfNewTarget(NE, C, IsArrayTypeAllocated); + SVal SizeOfPlace = getExtentSizeOfPlace(NE, C); const auto SizeOfTargetCI = SizeOfTarget.getAs<nonloc::ConcreteInt>(); if (!SizeOfTargetCI) - return; + return true; const auto SizeOfPlaceCI = SizeOfPlace.getAs<nonloc::ConcreteInt>(); if (!SizeOfPlaceCI) - return; + return true; - if (SizeOfPlaceCI->getValue() < SizeOfTargetCI->getValue()) { - if (ExplodedNode *N = C.generateErrorNode(State)) { - std::string Msg = - llvm::formatv("Storage provided to placement new is only {0} bytes, " - "whereas the allocated type requires {1} bytes", - SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue()); + if ((SizeOfPlaceCI->getValue() < SizeOfTargetCI->getValue()) || + (IsArrayTypeAllocated && + SizeOfPlaceCI->getValue() >= SizeOfTargetCI->getValue())) { + if (ExplodedNode *N = C.generateErrorNode(C.getState())) { + std::string Msg; + // TODO: use clang constant + if (IsArrayTypeAllocated && + SizeOfPlaceCI->getValue() > SizeOfTargetCI->getValue()) + Msg = std::string(llvm::formatv( + "{0} bytes is possibly not enough for array allocation which " + "requires {1} bytes. Current overhead requires the size of {2} " + "bytes", + SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue(), + SizeOfPlaceCI->getValue() - SizeOfTargetCI->getValue())); + else if (IsArrayTypeAllocated && + SizeOfPlaceCI->getValue() == SizeOfTargetCI->getValue()) + Msg = std::string(llvm::formatv( + "Storage provided to placement new is only {0} bytes, " + "whereas the allocated array type requires more space for " + "internal needs", + SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue())); + else + Msg = std::string(llvm::formatv( + "Storage provided to placement new is only {0} bytes, " + "whereas the allocated type requires {1} bytes", + SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue())); - auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N); - bugreporter::trackExpressionValue(N, Place, *R); + auto R = std::make_unique<PathSensitiveBugReport>(SBT, Msg, N); + bugreporter::trackExpressionValue(N, NE->getPlacementArg(0), *R); C.emitReport(std::move(R)); + + return false; + } + } + + return true; +} + +void PlacementNewChecker::emitBadAlignReport(const Expr *P, CheckerContext &C, + unsigned AllocatedTAlign, + unsigned StorageTAlign) const { + ProgramStateRef State = C.getState(); + if (ExplodedNode *N = C.generateErrorNode(State)) { + std::string Msg(llvm::formatv("Storage type is aligned to {0} bytes but " + "allocated type is aligned to {1} bytes", + StorageTAlign, AllocatedTAlign)); + + auto R = std::make_unique<PathSensitiveBugReport>(ABT, Msg, N); + bugreporter::trackExpressionValue(N, P, *R); + C.emitReport(std::move(R)); + } +} + +unsigned PlacementNewChecker::getStorageAlign(CheckerContext &C, + const ValueDecl *VD) const { + unsigned StorageTAlign = C.getASTContext().getTypeAlign(VD->getType()); + if (unsigned SpecifiedAlignment = VD->getMaxAlignment()) + StorageTAlign = SpecifiedAlignment; + + return StorageTAlign / C.getASTContext().getCharWidth(); +} + +void PlacementNewChecker::checkElementRegionAlign( + const ElementRegion *R, CheckerContext &C, const Expr *P, + unsigned AllocatedTAlign) const { + auto IsBaseRegionAlignedProperly = [this, R, &C, P, + AllocatedTAlign]() -> bool { + // Unwind nested ElementRegion`s to get the type. + const MemRegion *SuperRegion = R; + while (true) { + if (SuperRegion->getKind() == MemRegion::ElementRegionKind) { + SuperRegion = cast<SubRegion>(SuperRegion)->getSuperRegion(); + continue; + } + + break; + } + + const DeclRegion *TheElementDeclRegion = SuperRegion->getAs<DeclRegion>(); + if (!TheElementDeclRegion) + return false; + + const DeclRegion *BaseDeclRegion = R->getBaseRegion()->getAs<DeclRegion>(); + if (!BaseDeclRegion) + return false; + + unsigned BaseRegionAlign = 0; + // We must use alignment TheElementDeclRegion if it has its own alignment + // specifier + if (TheElementDeclRegion->getDecl()->getMaxAlignment()) + BaseRegionAlign = getStorageAlign(C, TheElementDeclRegion->getDecl()); + else + BaseRegionAlign = getStorageAlign(C, BaseDeclRegion->getDecl()); + + if (AllocatedTAlign > BaseRegionAlign) { + emitBadAlignReport(P, C, AllocatedTAlign, BaseRegionAlign); + return false; + } + + return true; + }; + + auto CheckElementRegionOffset = [this, R, &C, P, AllocatedTAlign]() -> void { + RegionOffset TheOffsetRegion = R->getAsOffset(); + if (TheOffsetRegion.hasSymbolicOffset()) return; + + unsigned Offset = + TheOffsetRegion.getOffset() / C.getASTContext().getCharWidth(); + unsigned AddressAlign = Offset % AllocatedTAlign; + if (AddressAlign != 0) { + emitBadAlignReport(P, C, AllocatedTAlign, AddressAlign); + return; + } + }; + + if (IsBaseRegionAlignedProperly()) { + CheckElementRegionOffset(); + } +} + +void PlacementNewChecker::checkFieldRegionAlign( + const FieldRegion *R, CheckerContext &C, const Expr *P, + unsigned AllocatedTAlign) const { + const MemRegion *BaseRegion = R->getBaseRegion(); + if (!BaseRegion) + return; + + if (const VarRegion *TheVarRegion = BaseRegion->getAs<VarRegion>()) { + if (isVarRegionAlignedProperly(TheVarRegion, C, P, AllocatedTAlign)) { + // We've checked type align but, unless FieldRegion + // offset is zero, we also need to check its own + // align. + RegionOffset Offset = R->getAsOffset(); + if (Offset.hasSymbolicOffset()) + return; + + int64_t OffsetValue = + Offset.getOffset() / C.getASTContext().getCharWidth(); + unsigned AddressAlign = OffsetValue % AllocatedTAlign; + if (AddressAlign != 0) + emitBadAlignReport(P, C, AllocatedTAlign, AddressAlign); } } } +bool PlacementNewChecker::isVarRegionAlignedProperly( + const VarRegion *R, CheckerContext &C, const Expr *P, + unsigned AllocatedTAlign) const { + const VarDecl *TheVarDecl = R->getDecl(); + unsigned StorageTAlign = getStorageAlign(C, TheVarDecl); + if (AllocatedTAlign > StorageTAlign) { + emitBadAlignReport(P, C, AllocatedTAlign, StorageTAlign); + + return false; + } + + return true; +} + +bool PlacementNewChecker::checkPlaceIsAlignedProperly(const CXXNewExpr *NE, + CheckerContext &C) const { + const Expr *Place = NE->getPlacementArg(0); + + QualType AllocatedT = NE->getAllocatedType(); + unsigned AllocatedTAlign = C.getASTContext().getTypeAlign(AllocatedT) / + C.getASTContext().getCharWidth(); + + SVal PlaceVal = C.getSVal(Place); + if (const MemRegion *MRegion = PlaceVal.getAsRegion()) { + if (const ElementRegion *TheElementRegion = MRegion->getAs<ElementRegion>()) + checkElementRegionAlign(TheElementRegion, C, Place, AllocatedTAlign); + else if (const FieldRegion *TheFieldRegion = MRegion->getAs<FieldRegion>()) + checkFieldRegionAlign(TheFieldRegion, C, Place, AllocatedTAlign); + else if (const VarRegion *TheVarRegion = MRegion->getAs<VarRegion>()) + isVarRegionAlignedProperly(TheVarRegion, C, Place, AllocatedTAlign); + } + + return true; +} + +void PlacementNewChecker::checkPreStmt(const CXXNewExpr *NE, + CheckerContext &C) const { + // Check only the default placement new. + if (!NE->getOperatorNew()->isReservedGlobalPlacementOperator()) + return; + + if (NE->getNumPlacementArgs() == 0) + return; + + if (!checkPlaceCapacityIsSufficient(NE, C)) + return; + + checkPlaceIsAlignedProperly(NE, C); +} + void ento::registerPlacementNewChecker(CheckerManager &mgr) { mgr.registerChecker<PlacementNewChecker>(); } -bool ento::shouldRegisterPlacementNewChecker(const LangOptions &LO) { +bool ento::shouldRegisterPlacementNewChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp index d9ffa562c0aa..d06c87631bfb 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp @@ -1076,7 +1076,7 @@ void ento::registerSecuritySyntaxChecker(CheckerManager &mgr) { mgr.registerChecker<SecuritySyntaxChecker>(); } -bool ento::shouldRegisterSecuritySyntaxChecker(const LangOptions &LO) { +bool ento::shouldRegisterSecuritySyntaxChecker(const CheckerManager &mgr) { return true; } @@ -1087,7 +1087,7 @@ bool ento::shouldRegisterSecuritySyntaxChecker(const LangOptions &LO) { checker->filter.checkName_##name = mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name(const LangOptions &LO) { return true; } + bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; } REGISTER_CHECKER(bcmp) REGISTER_CHECKER(bcopy) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp index ec401cfa8985..0d2551f11583 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp @@ -91,6 +91,6 @@ void ento::registerSizeofPointerChecker(CheckerManager &mgr) { mgr.registerChecker<SizeofPointerChecker>(); } -bool ento::shouldRegisterSizeofPointerChecker(const LangOptions &LO) { +bool ento::shouldRegisterSizeofPointerChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp index 7a41a7b6b216..fd53c04f4bbf 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp @@ -136,6 +136,6 @@ void ento::registerChrootChecker(CheckerManager &mgr) { mgr.registerChecker<ChrootChecker>(); } -bool ento::shouldRegisterChrootChecker(const LangOptions &LO) { +bool ento::shouldRegisterChrootChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp index ce45b5be34c9..7968aed85e1b 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp @@ -208,6 +208,6 @@ void ento::registerCloneChecker(CheckerManager &Mgr) { .getCheckerStringOption(Checker, "IgnoredFilesPattern"); } -bool ento::shouldRegisterCloneChecker(const LangOptions &LO) { +bool ento::shouldRegisterCloneChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ContainerModeling.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ContainerModeling.cpp new file mode 100644 index 000000000000..73c6517fd0eb --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ContainerModeling.cpp @@ -0,0 +1,1083 @@ +//===-- ContainerModeling.cpp -------------------------------------*- C++ -*--// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines a modeling-checker for modeling STL container-like containers. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/Driver/DriverDiagnostic.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.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/DynamicType.h" + +#include "Iterator.h" + +#include <utility> + +using namespace clang; +using namespace ento; +using namespace iterator; + +namespace { + +class ContainerModeling + : public Checker<check::PostCall, check::LiveSymbols, check::DeadSymbols> { + + void handleBegin(CheckerContext &C, const Expr *CE, SVal RetVal, + SVal Cont) const; + void handleEnd(CheckerContext &C, const Expr *CE, SVal RetVal, + SVal Cont) const; + void handleAssignment(CheckerContext &C, SVal Cont, const Expr *CE = nullptr, + SVal OldCont = UndefinedVal()) const; + void handleAssign(CheckerContext &C, SVal Cont, const Expr *ContE) const; + void handleClear(CheckerContext &C, SVal Cont, const Expr *ContE) const; + void handlePushBack(CheckerContext &C, SVal Cont, const Expr *ContE) const; + void handlePopBack(CheckerContext &C, SVal Cont, const Expr *ContE) const; + void handlePushFront(CheckerContext &C, SVal Cont, const Expr *ContE) const; + void handlePopFront(CheckerContext &C, SVal Cont, const Expr *ContE) const; + void handleInsert(CheckerContext &C, SVal Cont, SVal Iter) const; + void handleErase(CheckerContext &C, SVal Cont, SVal Iter) const; + void handleErase(CheckerContext &C, SVal Cont, SVal Iter1, SVal Iter2) const; + void handleEraseAfter(CheckerContext &C, SVal Cont, SVal Iter) const; + void handleEraseAfter(CheckerContext &C, SVal Cont, SVal Iter1, + SVal Iter2) const; + const NoteTag *getChangeTag(CheckerContext &C, StringRef Text, + const MemRegion *ContReg, + const Expr *ContE) const; + void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, + const char *Sep) const override; + +public: + ContainerModeling() = default; + + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const; + void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; + + using NoItParamFn = void (ContainerModeling::*)(CheckerContext &, SVal, + const Expr *) const; + using OneItParamFn = void (ContainerModeling::*)(CheckerContext &, SVal, + SVal) const; + using TwoItParamFn = void (ContainerModeling::*)(CheckerContext &, SVal, SVal, + SVal) const; + + CallDescriptionMap<NoItParamFn> NoIterParamFunctions = { + {{0, "clear", 0}, + &ContainerModeling::handleClear}, + {{0, "assign", 2}, + &ContainerModeling::handleAssign}, + {{0, "push_back", 1}, + &ContainerModeling::handlePushBack}, + {{0, "emplace_back", 1}, + &ContainerModeling::handlePushBack}, + {{0, "pop_back", 0}, + &ContainerModeling::handlePopBack}, + {{0, "push_front", 1}, + &ContainerModeling::handlePushFront}, + {{0, "emplace_front", 1}, + &ContainerModeling::handlePushFront}, + {{0, "pop_front", 0}, + &ContainerModeling::handlePopFront}, + }; + + CallDescriptionMap<OneItParamFn> OneIterParamFunctions = { + {{0, "insert", 2}, + &ContainerModeling::handleInsert}, + {{0, "emplace", 2}, + &ContainerModeling::handleInsert}, + {{0, "erase", 1}, + &ContainerModeling::handleErase}, + {{0, "erase_after", 1}, + &ContainerModeling::handleEraseAfter}, + }; + + CallDescriptionMap<TwoItParamFn> TwoIterParamFunctions = { + {{0, "erase", 2}, + &ContainerModeling::handleErase}, + {{0, "erase_after", 2}, + &ContainerModeling::handleEraseAfter}, + }; + +}; + +bool isBeginCall(const FunctionDecl *Func); +bool isEndCall(const FunctionDecl *Func); +bool hasSubscriptOperator(ProgramStateRef State, const MemRegion *Reg); +bool frontModifiable(ProgramStateRef State, const MemRegion *Reg); +bool backModifiable(ProgramStateRef State, const MemRegion *Reg); +SymbolRef getContainerBegin(ProgramStateRef State, const MemRegion *Cont); +SymbolRef getContainerEnd(ProgramStateRef State, const MemRegion *Cont); +ProgramStateRef createContainerBegin(ProgramStateRef State, + const MemRegion *Cont, const Expr *E, + QualType T, const LocationContext *LCtx, + unsigned BlockCount); +ProgramStateRef createContainerEnd(ProgramStateRef State, const MemRegion *Cont, + const Expr *E, QualType T, + const LocationContext *LCtx, + unsigned BlockCount); +ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont, + const ContainerData &CData); +ProgramStateRef invalidateAllIteratorPositions(ProgramStateRef State, + const MemRegion *Cont); +ProgramStateRef +invalidateAllIteratorPositionsExcept(ProgramStateRef State, + const MemRegion *Cont, SymbolRef Offset, + BinaryOperator::Opcode Opc); +ProgramStateRef invalidateIteratorPositions(ProgramStateRef State, + SymbolRef Offset, + BinaryOperator::Opcode Opc); +ProgramStateRef invalidateIteratorPositions(ProgramStateRef State, + SymbolRef Offset1, + BinaryOperator::Opcode Opc1, + SymbolRef Offset2, + BinaryOperator::Opcode Opc2); +ProgramStateRef reassignAllIteratorPositions(ProgramStateRef State, + const MemRegion *Cont, + const MemRegion *NewCont); +ProgramStateRef reassignAllIteratorPositionsUnless(ProgramStateRef State, + const MemRegion *Cont, + const MemRegion *NewCont, + SymbolRef Offset, + BinaryOperator::Opcode Opc); +ProgramStateRef rebaseSymbolInIteratorPositionsIf( + ProgramStateRef State, SValBuilder &SVB, SymbolRef OldSym, + SymbolRef NewSym, SymbolRef CondSym, BinaryOperator::Opcode Opc); +SymbolRef rebaseSymbol(ProgramStateRef State, SValBuilder &SVB, SymbolRef Expr, + SymbolRef OldSym, SymbolRef NewSym); +bool hasLiveIterators(ProgramStateRef State, const MemRegion *Cont); + +} // namespace + +void ContainerModeling::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + if (!Func) + return; + + if (Func->isOverloadedOperator()) { + const auto Op = Func->getOverloadedOperator(); + if (Op == OO_Equal) { + // Overloaded 'operator=' must be a non-static member function. + const auto *InstCall = cast<CXXInstanceCall>(&Call); + if (cast<CXXMethodDecl>(Func)->isMoveAssignmentOperator()) { + handleAssignment(C, InstCall->getCXXThisVal(), Call.getOriginExpr(), + Call.getArgSVal(0)); + return; + } + + handleAssignment(C, InstCall->getCXXThisVal()); + return; + } + } else { + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + const NoItParamFn *Handler0 = NoIterParamFunctions.lookup(Call); + if (Handler0) { + (this->**Handler0)(C, InstCall->getCXXThisVal(), + InstCall->getCXXThisExpr()); + return; + } + + const OneItParamFn *Handler1 = OneIterParamFunctions.lookup(Call); + if (Handler1) { + (this->**Handler1)(C, InstCall->getCXXThisVal(), Call.getArgSVal(0)); + return; + } + + const TwoItParamFn *Handler2 = TwoIterParamFunctions.lookup(Call); + if (Handler2) { + (this->**Handler2)(C, InstCall->getCXXThisVal(), Call.getArgSVal(0), + Call.getArgSVal(1)); + return; + } + + const auto *OrigExpr = Call.getOriginExpr(); + if (!OrigExpr) + return; + + if (isBeginCall(Func)) { + handleBegin(C, OrigExpr, Call.getReturnValue(), + InstCall->getCXXThisVal()); + return; + } + + if (isEndCall(Func)) { + handleEnd(C, OrigExpr, Call.getReturnValue(), + InstCall->getCXXThisVal()); + return; + } + } + } +} + +void ContainerModeling::checkLiveSymbols(ProgramStateRef State, + SymbolReaper &SR) const { + // Keep symbolic expressions of container begins and ends alive + auto ContMap = State->get<ContainerMap>(); + for (const auto &Cont : ContMap) { + const auto CData = Cont.second; + if (CData.getBegin()) { + SR.markLive(CData.getBegin()); + if(const auto *SIE = dyn_cast<SymIntExpr>(CData.getBegin())) + SR.markLive(SIE->getLHS()); + } + if (CData.getEnd()) { + SR.markLive(CData.getEnd()); + if(const auto *SIE = dyn_cast<SymIntExpr>(CData.getEnd())) + SR.markLive(SIE->getLHS()); + } + } +} + +void ContainerModeling::checkDeadSymbols(SymbolReaper &SR, + CheckerContext &C) const { + // Cleanup + auto State = C.getState(); + + auto ContMap = State->get<ContainerMap>(); + for (const auto &Cont : ContMap) { + if (!SR.isLiveRegion(Cont.first)) { + // We must keep the container data while it has live iterators to be able + // to compare them to the begin and the end of the container. + if (!hasLiveIterators(State, Cont.first)) { + State = State->remove<ContainerMap>(Cont.first); + } + } + } + + C.addTransition(State); +} + +void ContainerModeling::handleBegin(CheckerContext &C, const Expr *CE, + SVal RetVal, SVal Cont) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + // 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) { + State = createContainerBegin(State, ContReg, CE, C.getASTContext().LongTy, + C.getLocationContext(), C.blockCount()); + BeginSym = getContainerBegin(State, ContReg); + } + State = setIteratorPosition(State, RetVal, + IteratorPosition::getPosition(ContReg, BeginSym)); + C.addTransition(State); +} + +void ContainerModeling::handleEnd(CheckerContext &C, const Expr *CE, + SVal RetVal, SVal Cont) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + // If the container already has an end symbol then use it. Otherwise first + // create a new one. + auto State = C.getState(); + auto EndSym = getContainerEnd(State, ContReg); + if (!EndSym) { + State = createContainerEnd(State, ContReg, CE, C.getASTContext().LongTy, + C.getLocationContext(), C.blockCount()); + EndSym = getContainerEnd(State, ContReg); + } + State = setIteratorPosition(State, RetVal, + IteratorPosition::getPosition(ContReg, EndSym)); + C.addTransition(State); +} + +void ContainerModeling::handleAssignment(CheckerContext &C, SVal Cont, + const Expr *CE, SVal OldCont) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + // Assignment of a new value to a container always invalidates all its + // iterators + auto State = C.getState(); + const auto CData = getContainerData(State, ContReg); + if (CData) { + State = invalidateAllIteratorPositions(State, ContReg); + } + + // In case of move, iterators of the old container (except the past-end + // iterators) remain valid but refer to the new container + if (!OldCont.isUndef()) { + const auto *OldContReg = OldCont.getAsRegion(); + if (OldContReg) { + OldContReg = OldContReg->getMostDerivedObjectRegion(); + const auto OldCData = getContainerData(State, OldContReg); + if (OldCData) { + if (const auto OldEndSym = OldCData->getEnd()) { + // If we already assigned an "end" symbol to the old container, then + // first reassign all iterator positions to the new container which + // are not past the container (thus not greater or equal to the + // current "end" symbol). + State = reassignAllIteratorPositionsUnless(State, OldContReg, ContReg, + OldEndSym, BO_GE); + auto &SymMgr = C.getSymbolManager(); + auto &SVB = C.getSValBuilder(); + // Then generate and assign a new "end" symbol for the new container. + auto NewEndSym = + SymMgr.conjureSymbol(CE, C.getLocationContext(), + C.getASTContext().LongTy, C.blockCount()); + State = assumeNoOverflow(State, NewEndSym, 4); + if (CData) { + State = setContainerData(State, ContReg, CData->newEnd(NewEndSym)); + } else { + State = setContainerData(State, ContReg, + ContainerData::fromEnd(NewEndSym)); + } + // Finally, replace the old "end" symbol in the already reassigned + // iterator positions with the new "end" symbol. + State = rebaseSymbolInIteratorPositionsIf( + State, SVB, OldEndSym, NewEndSym, OldEndSym, BO_LT); + } else { + // There was no "end" symbol assigned yet to the old container, + // so reassign all iterator positions to the new container. + State = reassignAllIteratorPositions(State, OldContReg, ContReg); + } + if (const auto OldBeginSym = OldCData->getBegin()) { + // If we already assigned a "begin" symbol to the old container, then + // assign it to the new container and remove it from the old one. + if (CData) { + State = + setContainerData(State, ContReg, CData->newBegin(OldBeginSym)); + } else { + State = setContainerData(State, ContReg, + ContainerData::fromBegin(OldBeginSym)); + } + State = + setContainerData(State, OldContReg, OldCData->newBegin(nullptr)); + } + } else { + // There was neither "begin" nor "end" symbol assigned yet to the old + // container, so reassign all iterator positions to the new container. + State = reassignAllIteratorPositions(State, OldContReg, ContReg); + } + } + } + C.addTransition(State); +} + +void ContainerModeling::handleAssign(CheckerContext &C, SVal Cont, + const Expr *ContE) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + // The assign() operation invalidates all the iterators + auto State = C.getState(); + State = invalidateAllIteratorPositions(State, ContReg); + C.addTransition(State); +} + +void ContainerModeling::handleClear(CheckerContext &C, SVal Cont, + const Expr *ContE) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + // The clear() operation invalidates all the iterators, except the past-end + // iterators of list-like containers + auto State = C.getState(); + if (!hasSubscriptOperator(State, ContReg) || + !backModifiable(State, ContReg)) { + const auto CData = getContainerData(State, ContReg); + if (CData) { + if (const auto EndSym = CData->getEnd()) { + State = + invalidateAllIteratorPositionsExcept(State, ContReg, EndSym, BO_GE); + C.addTransition(State); + return; + } + } + } + const NoteTag *ChangeTag = + getChangeTag(C, "became empty", ContReg, ContE); + State = invalidateAllIteratorPositions(State, ContReg); + C.addTransition(State, ChangeTag); +} + +void ContainerModeling::handlePushBack(CheckerContext &C, SVal Cont, + const Expr *ContE) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + // For deque-like containers invalidate all iterator positions + auto State = C.getState(); + if (hasSubscriptOperator(State, ContReg) && frontModifiable(State, ContReg)) { + State = invalidateAllIteratorPositions(State, ContReg); + C.addTransition(State); + return; + } + + const auto CData = getContainerData(State, ContReg); + if (!CData) + return; + + // For vector-like containers invalidate the past-end iterator positions + if (const auto EndSym = CData->getEnd()) { + if (hasSubscriptOperator(State, ContReg)) { + State = invalidateIteratorPositions(State, EndSym, BO_GE); + } + auto &SymMgr = C.getSymbolManager(); + auto &BVF = SymMgr.getBasicVals(); + auto &SVB = C.getSValBuilder(); + const auto newEndSym = + SVB.evalBinOp(State, BO_Add, + nonloc::SymbolVal(EndSym), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), + SymMgr.getType(EndSym)).getAsSymbol(); + const NoteTag *ChangeTag = + getChangeTag(C, "extended to the back by 1 position", ContReg, ContE); + State = setContainerData(State, ContReg, CData->newEnd(newEndSym)); + C.addTransition(State, ChangeTag); + } +} + +void ContainerModeling::handlePopBack(CheckerContext &C, SVal Cont, + const Expr *ContE) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + auto State = C.getState(); + const auto CData = getContainerData(State, ContReg); + if (!CData) + return; + + if (const auto EndSym = CData->getEnd()) { + auto &SymMgr = C.getSymbolManager(); + auto &BVF = SymMgr.getBasicVals(); + auto &SVB = C.getSValBuilder(); + const auto BackSym = + SVB.evalBinOp(State, BO_Sub, + nonloc::SymbolVal(EndSym), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), + SymMgr.getType(EndSym)).getAsSymbol(); + const NoteTag *ChangeTag = + getChangeTag(C, "shrank from the back by 1 position", ContReg, ContE); + // For vector-like and deque-like containers invalidate the last and the + // past-end iterator positions. For list-like containers only invalidate + // the last position + if (hasSubscriptOperator(State, ContReg) && + backModifiable(State, ContReg)) { + State = invalidateIteratorPositions(State, BackSym, BO_GE); + State = setContainerData(State, ContReg, CData->newEnd(nullptr)); + } else { + State = invalidateIteratorPositions(State, BackSym, BO_EQ); + } + auto newEndSym = BackSym; + State = setContainerData(State, ContReg, CData->newEnd(newEndSym)); + C.addTransition(State, ChangeTag); + } +} + +void ContainerModeling::handlePushFront(CheckerContext &C, SVal Cont, + const Expr *ContE) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + // For deque-like containers invalidate all iterator positions + auto State = C.getState(); + if (hasSubscriptOperator(State, ContReg)) { + State = invalidateAllIteratorPositions(State, ContReg); + C.addTransition(State); + } else { + const auto CData = getContainerData(State, ContReg); + if (!CData) + return; + + if (const auto BeginSym = CData->getBegin()) { + auto &SymMgr = C.getSymbolManager(); + auto &BVF = SymMgr.getBasicVals(); + auto &SVB = C.getSValBuilder(); + const auto newBeginSym = + SVB.evalBinOp(State, BO_Sub, + nonloc::SymbolVal(BeginSym), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), + SymMgr.getType(BeginSym)).getAsSymbol(); + const NoteTag *ChangeTag = + getChangeTag(C, "extended to the front by 1 position", ContReg, ContE); + State = setContainerData(State, ContReg, CData->newBegin(newBeginSym)); + C.addTransition(State, ChangeTag); + } + } +} + +void ContainerModeling::handlePopFront(CheckerContext &C, SVal Cont, + const Expr *ContE) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + auto State = C.getState(); + const auto CData = getContainerData(State, ContReg); + if (!CData) + return; + + // For deque-like containers invalidate all iterator positions. For list-like + // iterators only invalidate the first position + if (const auto BeginSym = CData->getBegin()) { + if (hasSubscriptOperator(State, ContReg)) { + State = invalidateIteratorPositions(State, BeginSym, BO_LE); + } else { + State = invalidateIteratorPositions(State, BeginSym, BO_EQ); + } + auto &SymMgr = C.getSymbolManager(); + auto &BVF = SymMgr.getBasicVals(); + auto &SVB = C.getSValBuilder(); + const auto newBeginSym = + SVB.evalBinOp(State, BO_Add, + nonloc::SymbolVal(BeginSym), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), + SymMgr.getType(BeginSym)).getAsSymbol(); + const NoteTag *ChangeTag = + getChangeTag(C, "shrank from the front by 1 position", ContReg, ContE); + State = setContainerData(State, ContReg, CData->newBegin(newBeginSym)); + C.addTransition(State, ChangeTag); + } +} + +void ContainerModeling::handleInsert(CheckerContext &C, SVal Cont, + SVal Iter) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Iter); + if (!Pos) + return; + + // For deque-like containers invalidate all iterator positions. For + // vector-like containers invalidate iterator positions after the insertion. + if (hasSubscriptOperator(State, ContReg) && backModifiable(State, ContReg)) { + if (frontModifiable(State, ContReg)) { + State = invalidateAllIteratorPositions(State, ContReg); + } else { + State = invalidateIteratorPositions(State, Pos->getOffset(), BO_GE); + } + if (const auto *CData = getContainerData(State, ContReg)) { + if (const auto EndSym = CData->getEnd()) { + State = invalidateIteratorPositions(State, EndSym, BO_GE); + State = setContainerData(State, ContReg, CData->newEnd(nullptr)); + } + } + C.addTransition(State); + } +} + +void ContainerModeling::handleErase(CheckerContext &C, SVal Cont, + SVal Iter) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Iter); + if (!Pos) + return; + + // For deque-like containers invalidate all iterator positions. For + // vector-like containers invalidate iterator positions at and after the + // deletion. For list-like containers only invalidate the deleted position. + if (hasSubscriptOperator(State, ContReg) && backModifiable(State, ContReg)) { + if (frontModifiable(State, ContReg)) { + State = invalidateAllIteratorPositions(State, ContReg); + } else { + State = invalidateIteratorPositions(State, Pos->getOffset(), BO_GE); + } + if (const auto *CData = getContainerData(State, ContReg)) { + if (const auto EndSym = CData->getEnd()) { + State = invalidateIteratorPositions(State, EndSym, BO_GE); + State = setContainerData(State, ContReg, CData->newEnd(nullptr)); + } + } + } else { + State = invalidateIteratorPositions(State, Pos->getOffset(), BO_EQ); + } + C.addTransition(State); +} + +void ContainerModeling::handleErase(CheckerContext &C, SVal Cont, SVal Iter1, + SVal Iter2) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + auto State = C.getState(); + const auto *Pos1 = getIteratorPosition(State, Iter1); + const auto *Pos2 = getIteratorPosition(State, Iter2); + if (!Pos1 || !Pos2) + return; + + // For deque-like containers invalidate all iterator positions. For + // vector-like containers invalidate iterator positions at and after the + // deletion range. For list-like containers only invalidate the deleted + // position range [first..last]. + if (hasSubscriptOperator(State, ContReg) && backModifiable(State, ContReg)) { + if (frontModifiable(State, ContReg)) { + State = invalidateAllIteratorPositions(State, ContReg); + } else { + State = invalidateIteratorPositions(State, Pos1->getOffset(), BO_GE); + } + if (const auto *CData = getContainerData(State, ContReg)) { + if (const auto EndSym = CData->getEnd()) { + State = invalidateIteratorPositions(State, EndSym, BO_GE); + State = setContainerData(State, ContReg, CData->newEnd(nullptr)); + } + } + } else { + State = invalidateIteratorPositions(State, Pos1->getOffset(), BO_GE, + Pos2->getOffset(), BO_LT); + } + C.addTransition(State); +} + +void ContainerModeling::handleEraseAfter(CheckerContext &C, SVal Cont, + SVal Iter) const { + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Iter); + if (!Pos) + return; + + // Invalidate the deleted iterator position, which is the position of the + // parameter plus one. + auto &SymMgr = C.getSymbolManager(); + auto &BVF = SymMgr.getBasicVals(); + auto &SVB = C.getSValBuilder(); + const auto NextSym = + SVB.evalBinOp(State, BO_Add, + nonloc::SymbolVal(Pos->getOffset()), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), + SymMgr.getType(Pos->getOffset())).getAsSymbol(); + State = invalidateIteratorPositions(State, NextSym, BO_EQ); + C.addTransition(State); +} + +void ContainerModeling::handleEraseAfter(CheckerContext &C, SVal Cont, + SVal Iter1, SVal Iter2) const { + auto State = C.getState(); + const auto *Pos1 = getIteratorPosition(State, Iter1); + const auto *Pos2 = getIteratorPosition(State, Iter2); + if (!Pos1 || !Pos2) + return; + + // Invalidate the deleted iterator position range (first..last) + State = invalidateIteratorPositions(State, Pos1->getOffset(), BO_GT, + Pos2->getOffset(), BO_LT); + C.addTransition(State); +} + +const NoteTag *ContainerModeling::getChangeTag(CheckerContext &C, + StringRef Text, + const MemRegion *ContReg, + const Expr *ContE) const { + StringRef Name; + // First try to get the name of the variable from the region + if (const auto *DR = dyn_cast<DeclRegion>(ContReg)) { + Name = DR->getDecl()->getName(); + // If the region is not a `DeclRegion` then use the expression instead + } else if (const auto *DRE = + dyn_cast<DeclRefExpr>(ContE->IgnoreParenCasts())) { + Name = DRE->getDecl()->getName(); + } + + return C.getNoteTag( + [Text, Name, ContReg](PathSensitiveBugReport &BR) -> std::string { + if (!BR.isInteresting(ContReg)) + return ""; + + SmallString<256> Msg; + llvm::raw_svector_ostream Out(Msg); + Out << "Container " << (!Name.empty() ? ("'" + Name.str() + "' ") : "" ) + << Text; + return std::string(Out.str()); + }); +} + +void ContainerModeling::printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const { + auto ContMap = State->get<ContainerMap>(); + + if (!ContMap.isEmpty()) { + Out << Sep << "Container Data :" << NL; + for (const auto &Cont : ContMap) { + Cont.first->dumpToStream(Out); + Out << " : [ "; + const auto CData = Cont.second; + if (CData.getBegin()) + CData.getBegin()->dumpToStream(Out); + else + Out << "<Unknown>"; + Out << " .. "; + if (CData.getEnd()) + CData.getEnd()->dumpToStream(Out); + else + Out << "<Unknown>"; + Out << " ]"; + } + } +} + +namespace { + +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) + return false; + return IdInfo->getName().endswith_lower("end"); +} + +const CXXRecordDecl *getCXXRecordDecl(ProgramStateRef State, + const MemRegion *Reg) { + auto TI = getDynamicTypeInfo(State, Reg); + if (!TI.isValid()) + return nullptr; + + auto Type = TI.getType(); + if (const auto *RefT = Type->getAs<ReferenceType>()) { + Type = RefT->getPointeeType(); + } + + return Type->getUnqualifiedDesugaredType()->getAsCXXRecordDecl(); +} + +bool hasSubscriptOperator(ProgramStateRef State, const MemRegion *Reg) { + const auto *CRD = getCXXRecordDecl(State, Reg); + if (!CRD) + return false; + + for (const auto *Method : CRD->methods()) { + if (!Method->isOverloadedOperator()) + continue; + const auto OPK = Method->getOverloadedOperator(); + if (OPK == OO_Subscript) { + return true; + } + } + return false; +} + +bool frontModifiable(ProgramStateRef State, const MemRegion *Reg) { + const auto *CRD = getCXXRecordDecl(State, Reg); + if (!CRD) + return false; + + for (const auto *Method : CRD->methods()) { + if (!Method->getDeclName().isIdentifier()) + continue; + if (Method->getName() == "push_front" || Method->getName() == "pop_front") { + return true; + } + } + return false; +} + +bool backModifiable(ProgramStateRef State, const MemRegion *Reg) { + const auto *CRD = getCXXRecordDecl(State, Reg); + if (!CRD) + return false; + + for (const auto *Method : CRD->methods()) { + if (!Method->getDeclName().isIdentifier()) + continue; + if (Method->getName() == "push_back" || Method->getName() == "pop_back") { + return true; + } + } + return false; +} + +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) + return nullptr; + + return CDataPtr->getEnd(); +} + +ProgramStateRef createContainerBegin(ProgramStateRef State, + const MemRegion *Cont, const Expr *E, + QualType T, const LocationContext *LCtx, + unsigned BlockCount) { + // Only create if it does not exist + const auto *CDataPtr = getContainerData(State, Cont); + if (CDataPtr && CDataPtr->getBegin()) + return State; + + auto &SymMgr = State->getSymbolManager(); + const SymbolConjured *Sym = SymMgr.conjureSymbol(E, LCtx, T, BlockCount, + "begin"); + State = assumeNoOverflow(State, Sym, 4); + + if (CDataPtr) { + 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 Expr *E, QualType T, + const LocationContext *LCtx, + unsigned BlockCount) { + // Only create if it does not exist + const auto *CDataPtr = getContainerData(State, Cont); + if (CDataPtr && CDataPtr->getEnd()) + return State; + + auto &SymMgr = State->getSymbolManager(); + const SymbolConjured *Sym = SymMgr.conjureSymbol(E, LCtx, T, BlockCount, + "end"); + State = assumeNoOverflow(State, Sym, 4); + + if (CDataPtr) { + const auto CData = CDataPtr->newEnd(Sym); + return setContainerData(State, Cont, CData); + } + + const auto CData = ContainerData::fromEnd(Sym); + return setContainerData(State, Cont, CData); +} + +ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont, + const ContainerData &CData) { + return State->set<ContainerMap>(Cont, CData); +} + +template <typename Condition, typename Process> +ProgramStateRef processIteratorPositions(ProgramStateRef State, Condition Cond, + Process Proc) { + auto &RegionMapFactory = State->get_context<IteratorRegionMap>(); + auto RegionMap = State->get<IteratorRegionMap>(); + bool Changed = false; + for (const auto &Reg : RegionMap) { + if (Cond(Reg.second)) { + RegionMap = RegionMapFactory.add(RegionMap, Reg.first, Proc(Reg.second)); + Changed = true; + } + } + + if (Changed) + State = State->set<IteratorRegionMap>(RegionMap); + + auto &SymbolMapFactory = State->get_context<IteratorSymbolMap>(); + auto SymbolMap = State->get<IteratorSymbolMap>(); + Changed = false; + for (const auto &Sym : SymbolMap) { + if (Cond(Sym.second)) { + SymbolMap = SymbolMapFactory.add(SymbolMap, Sym.first, Proc(Sym.second)); + Changed = true; + } + } + + if (Changed) + State = State->set<IteratorSymbolMap>(SymbolMap); + + return State; +} + +ProgramStateRef invalidateAllIteratorPositions(ProgramStateRef State, + const MemRegion *Cont) { + auto MatchCont = [&](const IteratorPosition &Pos) { + return Pos.getContainer() == Cont; + }; + auto Invalidate = [&](const IteratorPosition &Pos) { + return Pos.invalidate(); + }; + return processIteratorPositions(State, MatchCont, Invalidate); +} + +ProgramStateRef +invalidateAllIteratorPositionsExcept(ProgramStateRef State, + const MemRegion *Cont, SymbolRef Offset, + BinaryOperator::Opcode Opc) { + auto MatchContAndCompare = [&](const IteratorPosition &Pos) { + return Pos.getContainer() == Cont && + !compare(State, Pos.getOffset(), Offset, Opc); + }; + auto Invalidate = [&](const IteratorPosition &Pos) { + return Pos.invalidate(); + }; + return processIteratorPositions(State, MatchContAndCompare, Invalidate); +} + +ProgramStateRef invalidateIteratorPositions(ProgramStateRef State, + SymbolRef Offset, + BinaryOperator::Opcode Opc) { + auto Compare = [&](const IteratorPosition &Pos) { + return compare(State, Pos.getOffset(), Offset, Opc); + }; + auto Invalidate = [&](const IteratorPosition &Pos) { + return Pos.invalidate(); + }; + return processIteratorPositions(State, Compare, Invalidate); +} + +ProgramStateRef invalidateIteratorPositions(ProgramStateRef State, + SymbolRef Offset1, + BinaryOperator::Opcode Opc1, + SymbolRef Offset2, + BinaryOperator::Opcode Opc2) { + auto Compare = [&](const IteratorPosition &Pos) { + return compare(State, Pos.getOffset(), Offset1, Opc1) && + compare(State, Pos.getOffset(), Offset2, Opc2); + }; + auto Invalidate = [&](const IteratorPosition &Pos) { + return Pos.invalidate(); + }; + return processIteratorPositions(State, Compare, Invalidate); +} + +ProgramStateRef reassignAllIteratorPositions(ProgramStateRef State, + const MemRegion *Cont, + const MemRegion *NewCont) { + auto MatchCont = [&](const IteratorPosition &Pos) { + return Pos.getContainer() == Cont; + }; + auto ReAssign = [&](const IteratorPosition &Pos) { + return Pos.reAssign(NewCont); + }; + return processIteratorPositions(State, MatchCont, ReAssign); +} + +ProgramStateRef reassignAllIteratorPositionsUnless(ProgramStateRef State, + const MemRegion *Cont, + const MemRegion *NewCont, + SymbolRef Offset, + BinaryOperator::Opcode Opc) { + auto MatchContAndCompare = [&](const IteratorPosition &Pos) { + return Pos.getContainer() == Cont && + !compare(State, Pos.getOffset(), Offset, Opc); + }; + auto ReAssign = [&](const IteratorPosition &Pos) { + return Pos.reAssign(NewCont); + }; + return processIteratorPositions(State, MatchContAndCompare, ReAssign); +} + +// This function rebases symbolic expression `OldSym + Int` to `NewSym + Int`, +// `OldSym - Int` to `NewSym - Int` and `OldSym` to `NewSym` in any iterator +// position offsets where `CondSym` is true. +ProgramStateRef rebaseSymbolInIteratorPositionsIf( + ProgramStateRef State, SValBuilder &SVB, SymbolRef OldSym, + SymbolRef NewSym, SymbolRef CondSym, BinaryOperator::Opcode Opc) { + auto LessThanEnd = [&](const IteratorPosition &Pos) { + return compare(State, Pos.getOffset(), CondSym, Opc); + }; + auto RebaseSymbol = [&](const IteratorPosition &Pos) { + return Pos.setTo(rebaseSymbol(State, SVB, Pos.getOffset(), OldSym, + NewSym)); + }; + return processIteratorPositions(State, LessThanEnd, RebaseSymbol); +} + +// This function rebases symbolic expression `OldExpr + Int` to `NewExpr + Int`, +// `OldExpr - Int` to `NewExpr - Int` and `OldExpr` to `NewExpr` in expression +// `OrigExpr`. +SymbolRef rebaseSymbol(ProgramStateRef State, SValBuilder &SVB, + SymbolRef OrigExpr, SymbolRef OldExpr, + SymbolRef NewSym) { + auto &SymMgr = SVB.getSymbolManager(); + auto Diff = SVB.evalBinOpNN(State, BO_Sub, nonloc::SymbolVal(OrigExpr), + nonloc::SymbolVal(OldExpr), + SymMgr.getType(OrigExpr)); + + const auto DiffInt = Diff.getAs<nonloc::ConcreteInt>(); + if (!DiffInt) + return OrigExpr; + + return SVB.evalBinOpNN(State, BO_Add, *DiffInt, nonloc::SymbolVal(NewSym), + SymMgr.getType(OrigExpr)).getAsSymbol(); +} + +bool hasLiveIterators(ProgramStateRef State, const MemRegion *Cont) { + auto RegionMap = State->get<IteratorRegionMap>(); + for (const auto &Reg : RegionMap) { + if (Reg.second.getContainer() == Cont) + return true; + } + + auto SymbolMap = State->get<IteratorSymbolMap>(); + for (const auto &Sym : SymbolMap) { + if (Sym.second.getContainer() == Cont) + return true; + } + + return false; +} + +} // namespace + +void ento::registerContainerModeling(CheckerManager &mgr) { + mgr.registerChecker<ContainerModeling>(); +} + +bool ento::shouldRegisterContainerModeling(const CheckerManager &mgr) { + if (!mgr.getLangOpts().CPlusPlus) + return false; + + if (!mgr.getAnalyzerOptions().ShouldAggressivelySimplifyBinaryOperation) { + mgr.getASTContext().getDiagnostics().Report( + diag::err_analyzer_checker_incompatible_analyzer_option) + << "aggressive-binary-operation-simplification" << "false"; + return false; + } + + return true; +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp index 8dd3132f07e2..4216a6883119 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp @@ -196,6 +196,6 @@ void ento::registerConversionChecker(CheckerManager &mgr) { mgr.registerChecker<ConversionChecker>(); } -bool ento::shouldRegisterConversionChecker(const LangOptions &LO) { +bool ento::shouldRegisterConversionChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp index 61441889fc64..6bc186aa2755 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp @@ -540,6 +540,6 @@ void ento::registerDeadStoresChecker(CheckerManager &Mgr) { AnOpts.getCheckerBooleanOption(Chk, "ShowFixIts"); } -bool ento::shouldRegisterDeadStoresChecker(const LangOptions &LO) { +bool ento::shouldRegisterDeadStoresChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp index 0cb4be2c7fdc..03b7cbd1c833 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp @@ -47,7 +47,7 @@ void ento::registerDominatorsTreeDumper(CheckerManager &mgr) { mgr.registerChecker<DominatorsTreeDumper>(); } -bool ento::shouldRegisterDominatorsTreeDumper(const LangOptions &LO) { +bool ento::shouldRegisterDominatorsTreeDumper(const CheckerManager &mgr) { return true; } @@ -73,7 +73,7 @@ void ento::registerPostDominatorsTreeDumper(CheckerManager &mgr) { mgr.registerChecker<PostDominatorsTreeDumper>(); } -bool ento::shouldRegisterPostDominatorsTreeDumper(const LangOptions &LO) { +bool ento::shouldRegisterPostDominatorsTreeDumper(const CheckerManager &mgr) { return true; } @@ -98,7 +98,7 @@ void ento::registerControlDependencyTreeDumper(CheckerManager &mgr) { mgr.registerChecker<ControlDependencyTreeDumper>(); } -bool ento::shouldRegisterControlDependencyTreeDumper(const LangOptions &LO) { +bool ento::shouldRegisterControlDependencyTreeDumper(const CheckerManager &mgr) { return true; } @@ -122,7 +122,7 @@ void ento::registerLiveVariablesDumper(CheckerManager &mgr) { mgr.registerChecker<LiveVariablesDumper>(); } -bool ento::shouldRegisterLiveVariablesDumper(const LangOptions &LO) { +bool ento::shouldRegisterLiveVariablesDumper(const CheckerManager &mgr) { return true; } @@ -145,7 +145,7 @@ void ento::registerLiveStatementsDumper(CheckerManager &mgr) { mgr.registerChecker<LiveStatementsDumper>(); } -bool ento::shouldRegisterLiveStatementsDumper(const LangOptions &LO) { +bool ento::shouldRegisterLiveStatementsDumper(const CheckerManager &mgr) { return true; } @@ -169,7 +169,7 @@ void ento::registerCFGViewer(CheckerManager &mgr) { mgr.registerChecker<CFGViewer>(); } -bool ento::shouldRegisterCFGViewer(const LangOptions &LO) { +bool ento::shouldRegisterCFGViewer(const CheckerManager &mgr) { return true; } @@ -199,7 +199,7 @@ void ento::registerCFGDumper(CheckerManager &mgr) { mgr.registerChecker<CFGDumper>(); } -bool ento::shouldRegisterCFGDumper(const LangOptions &LO) { +bool ento::shouldRegisterCFGDumper(const CheckerManager &mgr) { return true; } @@ -223,7 +223,7 @@ void ento::registerCallGraphViewer(CheckerManager &mgr) { mgr.registerChecker<CallGraphViewer>(); } -bool ento::shouldRegisterCallGraphViewer(const LangOptions &LO) { +bool ento::shouldRegisterCallGraphViewer(const CheckerManager &mgr) { return true; } @@ -247,7 +247,7 @@ void ento::registerCallGraphDumper(CheckerManager &mgr) { mgr.registerChecker<CallGraphDumper>(); } -bool ento::shouldRegisterCallGraphDumper(const LangOptions &LO) { +bool ento::shouldRegisterCallGraphDumper(const CheckerManager &mgr) { return true; } @@ -281,8 +281,6 @@ public: llvm::errs() << Keys[I]->getKey() << " = " << (Keys[I]->second.empty() ? "\"\"" : Keys[I]->second) << '\n'; - - llvm::errs() << "[stats]\n" << "num-entries = " << Keys.size() << '\n'; } }; } @@ -291,7 +289,7 @@ void ento::registerConfigDumper(CheckerManager &mgr) { mgr.registerChecker<ConfigDumper>(); } -bool ento::shouldRegisterConfigDumper(const LangOptions &LO) { +bool ento::shouldRegisterConfigDumper(const CheckerManager &mgr) { return true; } @@ -314,7 +312,7 @@ void ento::registerExplodedGraphViewer(CheckerManager &mgr) { mgr.registerChecker<ExplodedGraphViewer>(); } -bool ento::shouldRegisterExplodedGraphViewer(const LangOptions &LO) { +bool ento::shouldRegisterExplodedGraphViewer(const CheckerManager &mgr) { return true; } @@ -346,6 +344,6 @@ void ento::registerReportStmts(CheckerManager &mgr) { mgr.registerChecker<ReportStmts>(); } -bool ento::shouldRegisterReportStmts(const LangOptions &LO) { +bool ento::shouldRegisterReportStmts(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugContainerModeling.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugContainerModeling.cpp new file mode 100644 index 000000000000..6fed999ffc80 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugContainerModeling.cpp @@ -0,0 +1,150 @@ +//==-- DebugContainerModeling.cpp ---------------------------------*- C++ -*--// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines a checker for debugging iterator modeling. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +#include "Iterator.h" + +using namespace clang; +using namespace ento; +using namespace iterator; + +namespace { + +class DebugContainerModeling + : public Checker<eval::Call> { + + std::unique_ptr<BugType> DebugMsgBugType; + + template <typename Getter> + void analyzerContainerDataField(const CallExpr *CE, CheckerContext &C, + Getter get) const; + void analyzerContainerBegin(const CallExpr *CE, CheckerContext &C) const; + void analyzerContainerEnd(const CallExpr *CE, CheckerContext &C) const; + ExplodedNode *reportDebugMsg(llvm::StringRef Msg, CheckerContext &C) const; + + typedef void (DebugContainerModeling::*FnCheck)(const CallExpr *, + CheckerContext &) const; + + CallDescriptionMap<FnCheck> Callbacks = { + {{0, "clang_analyzer_container_begin", 1}, + &DebugContainerModeling::analyzerContainerBegin}, + {{0, "clang_analyzer_container_end", 1}, + &DebugContainerModeling::analyzerContainerEnd}, + }; + +public: + DebugContainerModeling(); + + bool evalCall(const CallEvent &Call, CheckerContext &C) const; +}; + +} //namespace + +DebugContainerModeling::DebugContainerModeling() { + DebugMsgBugType.reset( + new BugType(this, "Checking analyzer assumptions", "debug", + /*SuppressOnSink=*/true)); +} + +bool DebugContainerModeling::evalCall(const CallEvent &Call, + CheckerContext &C) const { + const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return false; + + const FnCheck *Handler = Callbacks.lookup(Call); + if (!Handler) + return false; + + (this->**Handler)(CE, C); + return true; +} + +template <typename Getter> +void DebugContainerModeling::analyzerContainerDataField(const CallExpr *CE, + CheckerContext &C, + Getter get) const { + if (CE->getNumArgs() == 0) { + reportDebugMsg("Missing container argument", C); + return; + } + + auto State = C.getState(); + const MemRegion *Cont = C.getSVal(CE->getArg(0)).getAsRegion(); + if (Cont) { + const auto *Data = getContainerData(State, Cont); + if (Data) { + SymbolRef Field = get(Data); + if (Field) { + State = State->BindExpr(CE, C.getLocationContext(), + nonloc::SymbolVal(Field)); + + // Progpagate interestingness from the container's data (marked + // interesting by an `ExprInspection` debug call to the container + // itself. + const NoteTag *InterestingTag = + C.getNoteTag( + [Cont, Field](PathSensitiveBugReport &BR) -> std::string { + if (BR.isInteresting(Field)) { + BR.markInteresting(Cont); + } + return ""; + }); + C.addTransition(State, InterestingTag); + return; + } + } + } + + auto &BVF = C.getSValBuilder().getBasicValueFactory(); + State = State->BindExpr(CE, C.getLocationContext(), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(0)))); +} + +void DebugContainerModeling::analyzerContainerBegin(const CallExpr *CE, + CheckerContext &C) const { + analyzerContainerDataField(CE, C, [](const ContainerData *D) { + return D->getBegin(); + }); +} + +void DebugContainerModeling::analyzerContainerEnd(const CallExpr *CE, + CheckerContext &C) const { + analyzerContainerDataField(CE, C, [](const ContainerData *D) { + return D->getEnd(); + }); +} + +ExplodedNode *DebugContainerModeling::reportDebugMsg(llvm::StringRef Msg, + CheckerContext &C) const { + ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return nullptr; + + auto &BR = C.getBugReporter(); + BR.emitReport(std::make_unique<PathSensitiveBugReport>(*DebugMsgBugType, + Msg, N)); + return N; +} + +void ento::registerDebugContainerModeling(CheckerManager &mgr) { + mgr.registerChecker<DebugContainerModeling>(); +} + +bool ento::shouldRegisterDebugContainerModeling(const CheckerManager &mgr) { + return true; +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugIteratorModeling.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugIteratorModeling.cpp index 4717fef96341..5833eea56da8 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugIteratorModeling.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugIteratorModeling.cpp @@ -30,11 +30,6 @@ class DebugIteratorModeling std::unique_ptr<BugType> DebugMsgBugType; template <typename Getter> - void analyzerContainerDataField(const CallExpr *CE, CheckerContext &C, - Getter get) const; - void analyzerContainerBegin(const CallExpr *CE, CheckerContext &C) const; - void analyzerContainerEnd(const CallExpr *CE, CheckerContext &C) const; - template <typename Getter> void analyzerIteratorDataField(const CallExpr *CE, CheckerContext &C, Getter get, SVal Default) const; void analyzerIteratorPosition(const CallExpr *CE, CheckerContext &C) const; @@ -46,10 +41,6 @@ class DebugIteratorModeling CheckerContext &) const; CallDescriptionMap<FnCheck> Callbacks = { - {{0, "clang_analyzer_container_begin", 1}, - &DebugIteratorModeling::analyzerContainerBegin}, - {{0, "clang_analyzer_container_end", 1}, - &DebugIteratorModeling::analyzerContainerEnd}, {{0, "clang_analyzer_iterator_position", 1}, &DebugIteratorModeling::analyzerIteratorPosition}, {{0, "clang_analyzer_iterator_container", 1}, @@ -87,49 +78,6 @@ bool DebugIteratorModeling::evalCall(const CallEvent &Call, } template <typename Getter> -void DebugIteratorModeling::analyzerContainerDataField(const CallExpr *CE, - CheckerContext &C, - Getter get) const { - if (CE->getNumArgs() == 0) { - reportDebugMsg("Missing container argument", C); - return; - } - - auto State = C.getState(); - const MemRegion *Cont = C.getSVal(CE->getArg(0)).getAsRegion(); - if (Cont) { - const auto *Data = getContainerData(State, Cont); - if (Data) { - SymbolRef Field = get(Data); - if (Field) { - State = State->BindExpr(CE, C.getLocationContext(), - nonloc::SymbolVal(Field)); - C.addTransition(State); - return; - } - } - } - - auto &BVF = C.getSValBuilder().getBasicValueFactory(); - State = State->BindExpr(CE, C.getLocationContext(), - nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(0)))); -} - -void DebugIteratorModeling::analyzerContainerBegin(const CallExpr *CE, - CheckerContext &C) const { - analyzerContainerDataField(CE, C, [](const ContainerData *D) { - return D->getBegin(); - }); -} - -void DebugIteratorModeling::analyzerContainerEnd(const CallExpr *CE, - CheckerContext &C) const { - analyzerContainerDataField(CE, C, [](const ContainerData *D) { - return D->getEnd(); - }); -} - -template <typename Getter> void DebugIteratorModeling::analyzerIteratorDataField(const CallExpr *CE, CheckerContext &C, Getter get, @@ -191,6 +139,6 @@ void ento::registerDebugIteratorModeling(CheckerManager &mgr) { mgr.registerChecker<DebugIteratorModeling>(); } -bool ento::shouldRegisterDebugIteratorModeling(const LangOptions &LO) { +bool ento::shouldRegisterDebugIteratorModeling(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp index 45c1984c5e15..7c5833762008 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp @@ -92,6 +92,8 @@ void DeleteWithNonVirtualDtorChecker::checkPreStmt(const CXXDeleteExpr *DE, "Logic error")); ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return; auto R = std::make_unique<PathSensitiveBugReport>(*BT, BT->getDescription(), N); // Mark region of problematic base class for later use in the BugVisitor. @@ -148,6 +150,6 @@ void ento::registerDeleteWithNonVirtualDtorChecker(CheckerManager &mgr) { } bool ento::shouldRegisterDeleteWithNonVirtualDtorChecker( - const LangOptions &LO) { + const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp index 46100cd1dace..2411f0e2d058 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp @@ -304,6 +304,6 @@ void ento::registerDereferenceChecker(CheckerManager &mgr) { mgr.registerChecker<DereferenceChecker>(); } -bool ento::shouldRegisterDereferenceChecker(const LangOptions &LO) { +bool ento::shouldRegisterDereferenceChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp index 0c46447e1985..df88b71ff063 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp @@ -219,19 +219,12 @@ static bool AttrFilter(const ObjCMethodDecl *M) { // Register the checker that checks for direct accesses in all functions, // except for the initialization and copy routines. void ento::registerDirectIvarAssignment(CheckerManager &mgr) { - mgr.registerChecker<DirectIvarAssignment>(); + auto Chk = mgr.registerChecker<DirectIvarAssignment>(); + if (mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk, + "AnnotatedFunctions")) + Chk->ShouldSkipMethod = &AttrFilter; } -bool ento::shouldRegisterDirectIvarAssignment(const LangOptions &LO) { - return true; -} - -void ento::registerDirectIvarAssignmentForAnnotatedFunctions( - CheckerManager &mgr) { - mgr.getChecker<DirectIvarAssignment>()->ShouldSkipMethod = &AttrFilter; -} - -bool ento::shouldRegisterDirectIvarAssignmentForAnnotatedFunctions( - const LangOptions &LO) { +bool ento::shouldRegisterDirectIvarAssignment(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp index 8798bde88dcd..2b3164ba4a2c 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp @@ -101,6 +101,6 @@ void ento::registerDivZeroChecker(CheckerManager &mgr) { mgr.registerChecker<DivZeroChecker>(); } -bool ento::shouldRegisterDivZeroChecker(const LangOptions &LO) { +bool ento::shouldRegisterDivZeroChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp index 8cc38f9735f3..dbc930d7d37b 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp @@ -203,6 +203,6 @@ void ento::registerDynamicTypeChecker(CheckerManager &mgr) { mgr.registerChecker<DynamicTypeChecker>(); } -bool ento::shouldRegisterDynamicTypeChecker(const LangOptions &LO) { +bool ento::shouldRegisterDynamicTypeChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index cce3449b8873..14ba5d769969 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -55,6 +55,7 @@ class DynamicTypePropagation: check::PostStmt<CXXNewExpr>, check::PreObjCMessage, check::PostObjCMessage > { + const ObjCObjectType *getObjectTypeForAllocAndNew(const ObjCMessageExpr *MsgE, CheckerContext &C) const; @@ -69,8 +70,8 @@ class DynamicTypePropagation: mutable std::unique_ptr<BugType> ObjCGenericsBugType; void initBugType() const { if (!ObjCGenericsBugType) - ObjCGenericsBugType.reset( - new BugType(this, "Generics", categories::CoreFoundationObjectiveC)); + ObjCGenericsBugType.reset(new BugType( + GenericCheckName, "Generics", categories::CoreFoundationObjectiveC)); } class GenericsBugVisitor : public BugReporterVisitor { @@ -108,12 +109,129 @@ public: /// This value is set to true, when the Generics checker is turned on. DefaultBool CheckGenerics; + CheckerNameRef GenericCheckName; +}; + +bool isObjCClassType(QualType Type) { + if (const auto *PointerType = dyn_cast<ObjCObjectPointerType>(Type)) { + return PointerType->getObjectType()->isObjCClass(); + } + return false; +} + +struct RuntimeType { + const ObjCObjectType *Type = nullptr; + bool Precise = false; + + operator bool() const { return Type != nullptr; } }; + +RuntimeType inferReceiverType(const ObjCMethodCall &Message, + CheckerContext &C) { + const ObjCMessageExpr *MessageExpr = Message.getOriginExpr(); + + // Check if we can statically infer the actual type precisely. + // + // 1. Class is written directly in the message: + // \code + // [ActualClass classMethod]; + // \endcode + if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Class) { + return {MessageExpr->getClassReceiver()->getAs<ObjCObjectType>(), + /*Precise=*/true}; + } + + // 2. Receiver is 'super' from a class method (a.k.a 'super' is a + // class object). + // \code + // [super classMethod]; + // \endcode + if (MessageExpr->getReceiverKind() == ObjCMessageExpr::SuperClass) { + return {MessageExpr->getSuperType()->getAs<ObjCObjectType>(), + /*Precise=*/true}; + } + + // 3. Receiver is 'super' from an instance method (a.k.a 'super' is an + // instance of a super class). + // \code + // [super instanceMethod]; + // \encode + if (MessageExpr->getReceiverKind() == ObjCMessageExpr::SuperInstance) { + if (const auto *ObjTy = + MessageExpr->getSuperType()->getAs<ObjCObjectPointerType>()) + return {ObjTy->getObjectType(), /*Precise=*/true}; + } + + const Expr *RecE = MessageExpr->getInstanceReceiver(); + + if (!RecE) + return {}; + + // Otherwise, let's try to get type information from our estimations of + // runtime types. + QualType InferredType; + SVal ReceiverSVal = C.getSVal(RecE); + ProgramStateRef State = C.getState(); + + if (const MemRegion *ReceiverRegion = ReceiverSVal.getAsRegion()) { + if (DynamicTypeInfo DTI = getDynamicTypeInfo(State, ReceiverRegion)) { + InferredType = DTI.getType().getCanonicalType(); + } + } + + if (SymbolRef ReceiverSymbol = ReceiverSVal.getAsSymbol()) { + if (InferredType.isNull()) { + InferredType = ReceiverSymbol->getType(); + } + + // If receiver is a Class object, we want to figure out the type it + // represents. + if (isObjCClassType(InferredType)) { + // We actually might have some info on what type is contained in there. + if (DynamicTypeInfo DTI = + getClassObjectDynamicTypeInfo(State, ReceiverSymbol)) { + + // Types in Class objects can be ONLY Objective-C types + return {cast<ObjCObjectType>(DTI.getType()), !DTI.canBeASubClass()}; + } + + SVal SelfSVal = State->getSelfSVal(C.getLocationContext()); + + // Another way we can guess what is in Class object, is when it is a + // 'self' variable of the current class method. + if (ReceiverSVal == SelfSVal) { + // In this case, we should return the type of the enclosing class + // declaration. + if (const ObjCMethodDecl *MD = + dyn_cast<ObjCMethodDecl>(C.getStackFrame()->getDecl())) + if (const ObjCObjectType *ObjTy = dyn_cast<ObjCObjectType>( + MD->getClassInterface()->getTypeForDecl())) + return {ObjTy}; + } + } + } + + // Unfortunately, it seems like we have no idea what that type is. + if (InferredType.isNull()) { + return {}; + } + + // We can end up here if we got some dynamic type info and the + // receiver is not one of the known Class objects. + if (const auto *ReceiverInferredType = + dyn_cast<ObjCObjectPointerType>(InferredType)) { + return {ReceiverInferredType->getObjectType()}; + } + + // Any other type (like 'Class') is not really useful at this point. + return {}; +} } // end anonymous namespace void DynamicTypePropagation::checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const { ProgramStateRef State = removeDeadTypes(C.getState(), SR); + State = removeDeadClassObjectTypes(State, SR); MostSpecializedTypeArgsMapTy TyArgMap = State->get<MostSpecializedTypeArgsMap>(); @@ -209,12 +327,21 @@ void DynamicTypePropagation::checkPostCall(const CallEvent &Call, case OMF_alloc: case OMF_new: { // Get the type of object that will get created. - const ObjCMessageExpr *MsgE = Msg->getOriginExpr(); - const ObjCObjectType *ObjTy = getObjectTypeForAllocAndNew(MsgE, C); + RuntimeType ObjTy = inferReceiverType(*Msg, C); + if (!ObjTy) return; + QualType DynResTy = - C.getASTContext().getObjCObjectPointerType(QualType(ObjTy, 0)); + C.getASTContext().getObjCObjectPointerType(QualType(ObjTy.Type, 0)); + // We used to assume that whatever type we got from inferring the + // type is actually precise (and it is not exactly correct). + // A big portion of the existing behavior depends on that assumption + // (e.g. certain inlining won't take place). For this reason, we don't + // use ObjTy.Precise flag here. + // + // TODO: We should mitigate this problem some time in the future + // and replace hardcoded 'false' with '!ObjTy.Precise'. C.addTransition(setDynamicTypeInfo(State, RetReg, DynResTy, false)); break; } @@ -303,40 +430,6 @@ void DynamicTypePropagation::checkPostStmt(const CXXNewExpr *NewE, /*CanBeSubClassed=*/false)); } -const ObjCObjectType * -DynamicTypePropagation::getObjectTypeForAllocAndNew(const ObjCMessageExpr *MsgE, - CheckerContext &C) const { - if (MsgE->getReceiverKind() == ObjCMessageExpr::Class) { - if (const ObjCObjectType *ObjTy - = MsgE->getClassReceiver()->getAs<ObjCObjectType>()) - return ObjTy; - } - - if (MsgE->getReceiverKind() == ObjCMessageExpr::SuperClass) { - if (const ObjCObjectType *ObjTy - = MsgE->getSuperType()->getAs<ObjCObjectType>()) - return ObjTy; - } - - const Expr *RecE = MsgE->getInstanceReceiver(); - if (!RecE) - return nullptr; - - RecE= RecE->IgnoreParenImpCasts(); - if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(RecE)) { - const StackFrameContext *SFCtx = C.getStackFrame(); - // Are we calling [self alloc]? If this is self, get the type of the - // enclosing ObjC class. - if (DRE->getDecl() == SFCtx->getSelfDecl()) { - if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(SFCtx->getDecl())) - if (const ObjCObjectType *ObjTy = - dyn_cast<ObjCObjectType>(MD->getClassInterface()->getTypeForDecl())) - return ObjTy; - } - } - return nullptr; -} - // Return a better dynamic type if one can be derived from the cast. // Compare the current dynamic type of the region and the new type to which we // are casting. If the new type is lower in the inheritance hierarchy, pick it. @@ -821,25 +914,56 @@ void DynamicTypePropagation::checkPostObjCMessage(const ObjCMethodCall &M, Selector Sel = MessageExpr->getSelector(); ProgramStateRef State = C.getState(); - // Inference for class variables. - // We are only interested in cases where the class method is invoked on a - // class. This method is provided by the runtime and available on all classes. - if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Class && - Sel.getAsString() == "class") { - QualType ReceiverType = MessageExpr->getClassReceiver(); - const auto *ReceiverClassType = ReceiverType->castAs<ObjCObjectType>(); - if (!ReceiverClassType->isSpecialized()) - return; - QualType ReceiverClassPointerType = - C.getASTContext().getObjCObjectPointerType( - QualType(ReceiverClassType, 0)); - const auto *InferredType = - ReceiverClassPointerType->castAs<ObjCObjectPointerType>(); + // Here we try to propagate information on Class objects. + if (Sel.getAsString() == "class") { + // We try to figure out the type from the receiver of the 'class' message. + if (RuntimeType ReceiverRuntimeType = inferReceiverType(M, C)) { + + ReceiverRuntimeType.Type->getSuperClassType(); + QualType ReceiverClassType(ReceiverRuntimeType.Type, 0); + + // We want to consider only precise information on generics. + if (ReceiverRuntimeType.Type->isSpecialized() && + ReceiverRuntimeType.Precise) { + QualType ReceiverClassPointerType = + C.getASTContext().getObjCObjectPointerType(ReceiverClassType); + const auto *InferredType = + ReceiverClassPointerType->castAs<ObjCObjectPointerType>(); + State = State->set<MostSpecializedTypeArgsMap>(RetSym, InferredType); + } - State = State->set<MostSpecializedTypeArgsMap>(RetSym, InferredType); - C.addTransition(State); - return; + // Constrain the resulting class object to the inferred type. + State = setClassObjectDynamicTypeInfo(State, RetSym, ReceiverClassType, + !ReceiverRuntimeType.Precise); + + C.addTransition(State); + return; + } + } + + if (Sel.getAsString() == "superclass") { + // We try to figure out the type from the receiver of the 'superclass' + // message. + if (RuntimeType ReceiverRuntimeType = inferReceiverType(M, C)) { + + // Result type would be a super class of the receiver's type. + QualType ReceiversSuperClass = + ReceiverRuntimeType.Type->getSuperClassType(); + + // Check if it really had super class. + // + // TODO: we can probably pay closer attention to cases when the class + // object can be 'nil' as the result of such message. + if (!ReceiversSuperClass.isNull()) { + // Constrain the resulting class object to the inferred type. + State = setClassObjectDynamicTypeInfo( + State, RetSym, ReceiversSuperClass, !ReceiverRuntimeType.Precise); + + C.addTransition(State); + } + return; + } } // Tracking for return types. @@ -979,9 +1103,10 @@ PathDiagnosticPieceRef DynamicTypePropagation::GenericsBugVisitor::VisitNode( void ento::registerObjCGenericsChecker(CheckerManager &mgr) { DynamicTypePropagation *checker = mgr.getChecker<DynamicTypePropagation>(); checker->CheckGenerics = true; + checker->GenericCheckName = mgr.getCurrentCheckerName(); } -bool ento::shouldRegisterObjCGenericsChecker(const LangOptions &LO) { +bool ento::shouldRegisterObjCGenericsChecker(const CheckerManager &mgr) { return true; } @@ -989,6 +1114,6 @@ void ento::registerDynamicTypePropagation(CheckerManager &mgr) { mgr.registerChecker<DynamicTypePropagation>(); } -bool ento::shouldRegisterDynamicTypePropagation(const LangOptions &LO) { +bool ento::shouldRegisterDynamicTypePropagation(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp index 481a5685a71f..0e94b915a468 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp @@ -142,6 +142,6 @@ void ento::registerEnumCastOutOfRangeChecker(CheckerManager &mgr) { mgr.registerChecker<EnumCastOutOfRangeChecker>(); } -bool ento::shouldRegisterEnumCastOutOfRangeChecker(const LangOptions &LO) { +bool ento::shouldRegisterEnumCastOutOfRangeChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp index 17c813962a23..4225d890c47a 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +#include "Taint.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Checkers/SValExplainer.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" @@ -13,6 +14,7 @@ #include "clang/StaticAnalyzer/Core/IssueHash.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/ScopedPrinter.h" @@ -45,13 +47,17 @@ class ExprInspectionChecker : public Checker<eval::Call, check::DeadSymbols, void analyzerHashDump(const CallExpr *CE, CheckerContext &C) const; void analyzerDenote(const CallExpr *CE, CheckerContext &C) const; void analyzerExpress(const CallExpr *CE, CheckerContext &C) const; + void analyzerIsTainted(const CallExpr *CE, CheckerContext &C) const; typedef void (ExprInspectionChecker::*FnCheck)(const CallExpr *, CheckerContext &C) const; - ExplodedNode *reportBug(llvm::StringRef Msg, CheckerContext &C) const; + // Optional parameter `ExprVal` for expression value to be marked interesting. + ExplodedNode *reportBug(llvm::StringRef Msg, CheckerContext &C, + Optional<SVal> ExprVal = None) const; ExplodedNode *reportBug(llvm::StringRef Msg, BugReporter &BR, - ExplodedNode *N) const; + ExplodedNode *N, + Optional<SVal> ExprVal = None) const; public: bool evalCall(const CallEvent &Call, CheckerContext &C) const; @@ -72,26 +78,34 @@ bool ExprInspectionChecker::evalCall(const CallEvent &Call, // These checks should have no effect on the surrounding environment // (globals should not be invalidated, etc), hence the use of evalCall. - FnCheck Handler = llvm::StringSwitch<FnCheck>(C.getCalleeName(CE)) - .Case("clang_analyzer_eval", &ExprInspectionChecker::analyzerEval) - .Case("clang_analyzer_checkInlined", - &ExprInspectionChecker::analyzerCheckInlined) - .Case("clang_analyzer_crash", &ExprInspectionChecker::analyzerCrash) - .Case("clang_analyzer_warnIfReached", - &ExprInspectionChecker::analyzerWarnIfReached) - .Case("clang_analyzer_warnOnDeadSymbol", - &ExprInspectionChecker::analyzerWarnOnDeadSymbol) - .StartsWith("clang_analyzer_explain", &ExprInspectionChecker::analyzerExplain) - .StartsWith("clang_analyzer_dump", &ExprInspectionChecker::analyzerDump) - .Case("clang_analyzer_getExtent", &ExprInspectionChecker::analyzerGetExtent) - .Case("clang_analyzer_printState", - &ExprInspectionChecker::analyzerPrintState) - .Case("clang_analyzer_numTimesReached", - &ExprInspectionChecker::analyzerNumTimesReached) - .Case("clang_analyzer_hashDump", &ExprInspectionChecker::analyzerHashDump) - .Case("clang_analyzer_denote", &ExprInspectionChecker::analyzerDenote) - .Case("clang_analyzer_express", &ExprInspectionChecker::analyzerExpress) - .Default(nullptr); + FnCheck Handler = + llvm::StringSwitch<FnCheck>(C.getCalleeName(CE)) + .Case("clang_analyzer_eval", &ExprInspectionChecker::analyzerEval) + .Case("clang_analyzer_checkInlined", + &ExprInspectionChecker::analyzerCheckInlined) + .Case("clang_analyzer_crash", &ExprInspectionChecker::analyzerCrash) + .Case("clang_analyzer_warnIfReached", + &ExprInspectionChecker::analyzerWarnIfReached) + .Case("clang_analyzer_warnOnDeadSymbol", + &ExprInspectionChecker::analyzerWarnOnDeadSymbol) + .StartsWith("clang_analyzer_explain", + &ExprInspectionChecker::analyzerExplain) + .StartsWith("clang_analyzer_dump", + &ExprInspectionChecker::analyzerDump) + .Case("clang_analyzer_getExtent", + &ExprInspectionChecker::analyzerGetExtent) + .Case("clang_analyzer_printState", + &ExprInspectionChecker::analyzerPrintState) + .Case("clang_analyzer_numTimesReached", + &ExprInspectionChecker::analyzerNumTimesReached) + .Case("clang_analyzer_hashDump", + &ExprInspectionChecker::analyzerHashDump) + .Case("clang_analyzer_denote", &ExprInspectionChecker::analyzerDenote) + .Case("clang_analyzer_express", + &ExprInspectionChecker::analyzerExpress) + .StartsWith("clang_analyzer_isTainted", + &ExprInspectionChecker::analyzerIsTainted) + .Default(nullptr); if (!Handler) return false; @@ -133,22 +147,28 @@ static const char *getArgumentValueString(const CallExpr *CE, } ExplodedNode *ExprInspectionChecker::reportBug(llvm::StringRef Msg, - CheckerContext &C) const { + CheckerContext &C, + Optional<SVal> ExprVal) const { ExplodedNode *N = C.generateNonFatalErrorNode(); - reportBug(Msg, C.getBugReporter(), N); + reportBug(Msg, C.getBugReporter(), N, ExprVal); return N; } ExplodedNode *ExprInspectionChecker::reportBug(llvm::StringRef Msg, BugReporter &BR, - ExplodedNode *N) const { + ExplodedNode *N, + Optional<SVal> ExprVal) const { if (!N) return nullptr; if (!BT) BT.reset(new BugType(this, "Checking analyzer assumptions", "debug")); - BR.emitReport(std::make_unique<PathSensitiveBugReport>(*BT, Msg, N)); + auto R = std::make_unique<PathSensitiveBugReport>(*BT, Msg, N); + if (ExprVal) { + R->markInteresting(*ExprVal); + } + BR.emitReport(std::move(R)); return N; } @@ -234,8 +254,9 @@ void ExprInspectionChecker::analyzerGetExtent(const CallExpr *CE, } ProgramStateRef State = C.getState(); - State = State->BindExpr(CE, C.getLocationContext(), - MR->getExtent(C.getSValBuilder())); + DefinedOrUnknownSVal Size = getDynamicSize(State, MR, C.getSValBuilder()); + + State = State->BindExpr(CE, C.getLocationContext(), Size); C.addTransition(State); } @@ -394,7 +415,8 @@ void ExprInspectionChecker::analyzerExpress(const CallExpr *CE, return; } - SymbolRef Sym = C.getSVal(CE->getArg(0)).getAsSymbol(); + SVal ArgVal = C.getSVal(CE->getArg(0)); + SymbolRef Sym = ArgVal.getAsSymbol(); if (!Sym) { reportBug("Not a symbol", C); return; @@ -407,13 +429,24 @@ void ExprInspectionChecker::analyzerExpress(const CallExpr *CE, return; } - reportBug(*Str, C); + reportBug(*Str, C, ArgVal); +} + +void ExprInspectionChecker::analyzerIsTainted(const CallExpr *CE, + CheckerContext &C) const { + if (CE->getNumArgs() != 1) { + reportBug("clang_analyzer_isTainted() requires exactly one argument", C); + return; + } + const bool IsTainted = + taint::isTainted(C.getState(), CE->getArg(0), C.getLocationContext()); + reportBug(IsTainted ? "YES" : "NO", C); } void ento::registerExprInspectionChecker(CheckerManager &Mgr) { Mgr.registerChecker<ExprInspectionChecker>(); } -bool ento::shouldRegisterExprInspectionChecker(const LangOptions &LO) { +bool ento::shouldRegisterExprInspectionChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp index b315a8452285..6275e49e51ae 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp @@ -66,6 +66,6 @@ void ento::registerFixedAddressChecker(CheckerManager &mgr) { mgr.registerChecker<FixedAddressChecker>(); } -bool ento::shouldRegisterFixedAddressChecker(const LangOptions &LO) { +bool ento::shouldRegisterFixedAddressChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp index 3c04983df443..fc35082705fa 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp @@ -90,6 +90,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" +#include "llvm/ADT/StringExtras.h" using namespace clang; using namespace ento; @@ -149,6 +150,10 @@ public: CASE(Kind::Released) CASE(Kind::Escaped) } + if (ErrorSym) { + OS << " ErrorSym: "; + ErrorSym->dumpToStream(OS); + } } LLVM_DUMP_METHOD void dump() const { dump(llvm::errs()); } @@ -314,6 +319,17 @@ void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call, // Function returns an open handle. if (hasFuchsiaAttr<AcquireHandleAttr>(FuncDecl)) { SymbolRef RetSym = Call.getReturnValue().getAsSymbol(); + Notes.push_back([RetSym, FuncDecl](BugReport &BR) -> std::string { + auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR); + if (auto IsInteresting = PathBR->getInterestingnessKind(RetSym)) { + std::string SBuf; + llvm::raw_string_ostream OS(SBuf); + OS << "Function '" << FuncDecl->getNameAsString() + << "' returns an open handle"; + return OS.str(); + } else + return ""; + }); State = State->set<HStateMap>(RetSym, HandleState::getMaybeAllocated(nullptr)); } @@ -322,6 +338,7 @@ void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call, if (Arg >= FuncDecl->getNumParams()) break; const ParmVarDecl *PVD = FuncDecl->getParamDecl(Arg); + unsigned ParamDiagIdx = PVD->getFunctionScopeIndex() + 1; SymbolRef Handle = getFuchsiaHandleSymbol(PVD->getType(), Call.getArgSVal(Arg), State); if (!Handle) @@ -335,20 +352,28 @@ void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call, reportDoubleRelease(Handle, Call.getArgSourceRange(Arg), C); return; } else { - Notes.push_back([Handle](BugReport &BR) { + Notes.push_back([Handle, ParamDiagIdx](BugReport &BR) -> std::string { auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR); if (auto IsInteresting = PathBR->getInterestingnessKind(Handle)) { - return "Handle released here."; + std::string SBuf; + llvm::raw_string_ostream OS(SBuf); + OS << "Handle released through " << ParamDiagIdx + << llvm::getOrdinalSuffix(ParamDiagIdx) << " parameter"; + return OS.str(); } else return ""; }); State = State->set<HStateMap>(Handle, HandleState::getReleased()); } } else if (hasFuchsiaAttr<AcquireHandleAttr>(PVD)) { - Notes.push_back([Handle](BugReport &BR) { + Notes.push_back([Handle, ParamDiagIdx](BugReport &BR) -> std::string { auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR); if (auto IsInteresting = PathBR->getInterestingnessKind(Handle)) { - return "Handle allocated here."; + std::string SBuf; + llvm::raw_string_ostream OS(SBuf); + OS << "Handle allocated through " << ParamDiagIdx + << llvm::getOrdinalSuffix(ParamDiagIdx) << " parameter"; + return OS.str(); } else return ""; }); @@ -358,8 +383,8 @@ void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call, } const NoteTag *T = nullptr; if (!Notes.empty()) { - T = C.getNoteTag( - [this, Notes{std::move(Notes)}](BugReport &BR) -> std::string { + T = C.getNoteTag([this, Notes{std::move(Notes)}]( + PathSensitiveBugReport &BR) -> std::string { if (&BR.getBugType() != &UseAfterReleaseBugType && &BR.getBugType() != &LeakBugType && &BR.getBugType() != &DoubleReleaseBugType) @@ -381,7 +406,13 @@ void FuchsiaHandleChecker::checkDeadSymbols(SymbolReaper &SymReaper, SmallVector<SymbolRef, 2> LeakedSyms; HStateMapTy TrackedHandles = State->get<HStateMap>(); for (auto &CurItem : TrackedHandles) { - if (!SymReaper.isDead(CurItem.first)) + SymbolRef ErrorSym = CurItem.second.getErrorSym(); + // Keeping zombie handle symbols. In case the error symbol is dying later + // than the handle symbol we might produce spurious leak warnings (in case + // we find out later from the status code that the handle allocation failed + // in the first place). + if (!SymReaper.isDead(CurItem.first) || + (ErrorSym && !SymReaper.isDead(ErrorSym))) continue; if (CurItem.second.isAllocated() || CurItem.second.maybeAllocated()) LeakedSyms.push_back(CurItem.first); @@ -535,7 +566,7 @@ void ento::registerFuchsiaHandleChecker(CheckerManager &mgr) { mgr.registerChecker<FuchsiaHandleChecker>(); } -bool ento::shouldRegisterFuchsiaHandleChecker(const LangOptions &LO) { +bool ento::shouldRegisterFuchsiaHandleChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp index d471c23b83bf..63fbe75fd498 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp @@ -52,18 +52,16 @@ public: BugReporter &BR) const; }; -auto callsName(const char *FunctionName) - -> decltype(callee(functionDecl())) { +decltype(auto) callsName(const char *FunctionName) { return callee(functionDecl(hasName(FunctionName))); } -auto equalsBoundArgDecl(int ArgIdx, const char *DeclName) - -> decltype(hasArgument(0, expr())) { +decltype(auto) equalsBoundArgDecl(int ArgIdx, const char *DeclName) { return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr( to(varDecl(equalsBoundNode(DeclName)))))); } -auto bindAssignmentToDecl(const char *DeclName) -> decltype(hasLHS(expr())) { +decltype(auto) bindAssignmentToDecl(const char *DeclName) { return hasLHS(ignoringParenImpCasts( declRefExpr(to(varDecl().bind(DeclName))))); } @@ -227,6 +225,6 @@ void ento::registerGCDAntipattern(CheckerManager &Mgr) { Mgr.registerChecker<GCDAntipatternChecker>(); } -bool ento::shouldRegisterGCDAntipattern(const LangOptions &LO) { +bool ento::shouldRegisterGCDAntipattern(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GTestChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GTestChecker.cpp index f4308f510f0b..8d9afbe88aa8 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GTestChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GTestChecker.cpp @@ -291,8 +291,9 @@ void ento::registerGTestChecker(CheckerManager &Mgr) { Mgr.registerChecker<GTestChecker>(); } -bool ento::shouldRegisterGTestChecker(const LangOptions &LO) { +bool ento::shouldRegisterGTestChecker(const CheckerManager &mgr) { // gtest is a C++ API so there is no sense running the checker // if not compiling for C++. + const LangOptions &LO = mgr.getLangOpts(); return LO.CPlusPlus; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp index 302d5bb1bea8..c06d2fcd8e7d 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -22,11 +22,14 @@ #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/ProgramStateTrait.h" #include "llvm/Support/YAMLTraits.h" + #include <algorithm> #include <limits> +#include <memory> #include <unordered_map> #include <utility> @@ -35,17 +38,15 @@ using namespace ento; using namespace taint; namespace { -class GenericTaintChecker - : public Checker<check::PostStmt<CallExpr>, check::PreStmt<CallExpr>> { +class GenericTaintChecker : public Checker<check::PreCall, check::PostCall> { public: static void *getTag() { static int Tag; return &Tag; } - void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; - - void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const override; @@ -81,7 +82,7 @@ public: /// Convert SignedArgVector to ArgVector. ArgVector convertToArgVector(CheckerManager &Mgr, const std::string &Option, - SignedArgVector Args); + const SignedArgVector &Args); /// Parse the config. void parseConfiguration(CheckerManager &Mgr, const std::string &Option, @@ -96,7 +97,8 @@ private: mutable std::unique_ptr<BugType> BT; void initBugType() const { if (!BT) - BT.reset(new BugType(this, "Use of Untrusted Data", "Untrusted Data")); + BT = std::make_unique<BugType>(this, "Use of Untrusted Data", + "Untrusted Data"); } struct FunctionData { @@ -106,9 +108,12 @@ private: FunctionData &operator=(const FunctionData &) = delete; FunctionData &operator=(FunctionData &&) = delete; - static Optional<FunctionData> create(const CallExpr *CE, + static Optional<FunctionData> create(const CallEvent &Call, const CheckerContext &C) { - const FunctionDecl *FDecl = C.getCalleeDecl(CE); + if (!Call.getDecl()) + return None; + + const FunctionDecl *FDecl = Call.getDecl()->getAsFunction(); if (!FDecl || (FDecl->getKind() != Decl::Function && FDecl->getKind() != Decl::CXXMethod)) return None; @@ -132,33 +137,33 @@ private: /// Catch taint related bugs. Check if tainted data is passed to a /// system call etc. Returns true on matching. - bool checkPre(const CallExpr *CE, const FunctionData &FData, + bool checkPre(const CallEvent &Call, const FunctionData &FData, CheckerContext &C) const; /// Add taint sources on a pre-visit. Returns true on matching. - bool addSourcesPre(const CallExpr *CE, const FunctionData &FData, + bool addSourcesPre(const CallEvent &Call, const FunctionData &FData, CheckerContext &C) const; /// Mark filter's arguments not tainted on a pre-visit. Returns true on /// matching. - bool addFiltersPre(const CallExpr *CE, const FunctionData &FData, + bool addFiltersPre(const CallEvent &Call, const FunctionData &FData, CheckerContext &C) const; /// Propagate taint generated at pre-visit. Returns true on matching. - bool propagateFromPre(const CallExpr *CE, CheckerContext &C) const; + static bool propagateFromPre(const CallEvent &Call, CheckerContext &C); /// Check if the region the expression evaluates to is the standard input, /// and thus, is tainted. static bool isStdin(const Expr *E, CheckerContext &C); /// Given a pointer argument, return the value it points to. - static Optional<SVal> getPointedToSVal(CheckerContext &C, const Expr *Arg); + static Optional<SVal> getPointeeOf(CheckerContext &C, const Expr *Arg); /// Check for CWE-134: Uncontrolled Format String. static constexpr llvm::StringLiteral MsgUncontrolledFormatString = "Untrusted data is used as a format string " "(CWE-134: Uncontrolled Format String)"; - bool checkUncontrolledFormatString(const CallExpr *CE, + bool checkUncontrolledFormatString(const CallEvent &Call, CheckerContext &C) const; /// Check for: @@ -167,7 +172,7 @@ private: static constexpr llvm::StringLiteral MsgSanitizeSystemArgs = "Untrusted data is passed to a system call " "(CERT/STR02-C. Sanitize data passed to complex subsystems)"; - bool checkSystemCall(const CallExpr *CE, StringRef Name, + bool checkSystemCall(const CallEvent &Call, StringRef Name, CheckerContext &C) const; /// Check if tainted data is used as a buffer size ins strn.. functions, @@ -176,13 +181,12 @@ private: "Untrusted data is used to specify the buffer size " "(CERT/STR31-C. Guarantee that storage for strings has sufficient space " "for character data and the null terminator)"; - bool checkTaintedBufferSize(const CallExpr *CE, const FunctionDecl *FDecl, - CheckerContext &C) const; + bool checkTaintedBufferSize(const CallEvent &Call, CheckerContext &C) const; /// Check if tainted data is used as a custom sink's parameter. static constexpr llvm::StringLiteral MsgCustomSink = "Untrusted data is passed to a user-defined sink"; - bool checkCustomSinks(const CallExpr *CE, const FunctionData &FData, + bool checkCustomSinks(const CallEvent &Call, const FunctionData &FData, CheckerContext &C) const; /// Generate a report if the expression is tainted or points to tainted data. @@ -212,7 +216,7 @@ private: /// ReturnValueIndex is added to the dst list, the return value will be /// tainted. struct TaintPropagationRule { - using PropagationFuncType = bool (*)(bool IsTainted, const CallExpr *, + using PropagationFuncType = bool (*)(bool IsTainted, const CallEvent &Call, CheckerContext &C); /// List of arguments which can be taint sources and should be checked. @@ -256,7 +260,8 @@ private: return (llvm::find(DstArgs, ArgNum) != DstArgs.end()); } - static bool isTaintedOrPointsToTainted(const Expr *E, ProgramStateRef State, + static bool isTaintedOrPointsToTainted(const Expr *E, + const ProgramStateRef &State, CheckerContext &C) { if (isTainted(State, E, C.getLocationContext()) || isStdin(E, C)) return true; @@ -264,16 +269,16 @@ private: if (!E->getType().getTypePtr()->isPointerType()) return false; - Optional<SVal> V = getPointedToSVal(C, E); + Optional<SVal> V = getPointeeOf(C, E); return (V && isTainted(State, *V)); } /// Pre-process a function which propagates taint according to the /// taint rule. - ProgramStateRef process(const CallExpr *CE, CheckerContext &C) const; + ProgramStateRef process(const CallEvent &Call, CheckerContext &C) const; // Functions for custom taintedness propagation. - static bool postSocket(bool IsTainted, const CallExpr *CE, + static bool postSocket(bool IsTainted, const CallEvent &Call, CheckerContext &C); }; @@ -351,8 +356,10 @@ template <> struct MappingTraits<TaintConfig::NameScopeArgs> { /// points to data, which should be tainted on return. REGISTER_SET_WITH_PROGRAMSTATE(TaintArgsOnPostVisit, unsigned) -GenericTaintChecker::ArgVector GenericTaintChecker::convertToArgVector( - CheckerManager &Mgr, const std::string &Option, SignedArgVector Args) { +GenericTaintChecker::ArgVector +GenericTaintChecker::convertToArgVector(CheckerManager &Mgr, + const std::string &Option, + const SignedArgVector &Args) { ArgVector Result; for (int Arg : Args) { if (Arg == -1) @@ -396,7 +403,7 @@ void GenericTaintChecker::parseConfiguration(CheckerManager &Mgr, template <typename T> auto GenericTaintChecker::findFunctionInConfig(const ConfigDataMap<T> &Map, const FunctionData &FData) { - auto Range = Map.equal_range(FData.Name); + auto Range = Map.equal_range(std::string(FData.Name)); auto It = std::find_if(Range.first, Range.second, [&FData](const auto &Entry) { const auto &Value = Entry.second; @@ -419,125 +426,125 @@ GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( llvm::StringSwitch<TaintPropagationRule>(FData.FullName) // Source functions // TODO: Add support for vfscanf & family. - .Case("fdopen", TaintPropagationRule({}, {ReturnValueIndex})) - .Case("fopen", TaintPropagationRule({}, {ReturnValueIndex})) - .Case("freopen", TaintPropagationRule({}, {ReturnValueIndex})) - .Case("getch", TaintPropagationRule({}, {ReturnValueIndex})) - .Case("getchar", TaintPropagationRule({}, {ReturnValueIndex})) - .Case("getchar_unlocked", - TaintPropagationRule({}, {ReturnValueIndex})) - .Case("getenv", TaintPropagationRule({}, {ReturnValueIndex})) - .Case("gets", TaintPropagationRule({}, {0, ReturnValueIndex})) - .Case("scanf", TaintPropagationRule({}, {}, VariadicType::Dst, 1)) - .Case("socket", - TaintPropagationRule({}, {ReturnValueIndex}, VariadicType::None, - InvalidArgIndex, - &TaintPropagationRule::postSocket)) - .Case("wgetch", TaintPropagationRule({}, {ReturnValueIndex})) + .Case("fdopen", {{}, {ReturnValueIndex}}) + .Case("fopen", {{}, {ReturnValueIndex}}) + .Case("freopen", {{}, {ReturnValueIndex}}) + .Case("getch", {{}, {ReturnValueIndex}}) + .Case("getchar", {{}, {ReturnValueIndex}}) + .Case("getchar_unlocked", {{}, {ReturnValueIndex}}) + .Case("getenv", {{}, {ReturnValueIndex}}) + .Case("gets", {{}, {0, ReturnValueIndex}}) + .Case("scanf", {{}, {}, VariadicType::Dst, 1}) + .Case("socket", {{}, + {ReturnValueIndex}, + VariadicType::None, + InvalidArgIndex, + &TaintPropagationRule::postSocket}) + .Case("wgetch", {{}, {ReturnValueIndex}}) // Propagating functions - .Case("atoi", TaintPropagationRule({0}, {ReturnValueIndex})) - .Case("atol", TaintPropagationRule({0}, {ReturnValueIndex})) - .Case("atoll", TaintPropagationRule({0}, {ReturnValueIndex})) - .Case("fgetc", TaintPropagationRule({0}, {ReturnValueIndex})) - .Case("fgetln", TaintPropagationRule({0}, {ReturnValueIndex})) - .Case("fgets", TaintPropagationRule({2}, {0, ReturnValueIndex})) - .Case("fscanf", TaintPropagationRule({0}, {}, VariadicType::Dst, 2)) - .Case("getc", TaintPropagationRule({0}, {ReturnValueIndex})) - .Case("getc_unlocked", TaintPropagationRule({0}, {ReturnValueIndex})) - .Case("getdelim", TaintPropagationRule({3}, {0})) - .Case("getline", TaintPropagationRule({2}, {0})) - .Case("getw", TaintPropagationRule({0}, {ReturnValueIndex})) - .Case("pread", - TaintPropagationRule({0, 1, 2, 3}, {1, ReturnValueIndex})) - .Case("read", TaintPropagationRule({0, 2}, {1, ReturnValueIndex})) - .Case("strchr", TaintPropagationRule({0}, {ReturnValueIndex})) - .Case("strrchr", TaintPropagationRule({0}, {ReturnValueIndex})) - .Case("tolower", TaintPropagationRule({0}, {ReturnValueIndex})) - .Case("toupper", TaintPropagationRule({0}, {ReturnValueIndex})) - .Default(TaintPropagationRule()); + .Case("atoi", {{0}, {ReturnValueIndex}}) + .Case("atol", {{0}, {ReturnValueIndex}}) + .Case("atoll", {{0}, {ReturnValueIndex}}) + .Case("fgetc", {{0}, {ReturnValueIndex}}) + .Case("fgetln", {{0}, {ReturnValueIndex}}) + .Case("fgets", {{2}, {0, ReturnValueIndex}}) + .Case("fscanf", {{0}, {}, VariadicType::Dst, 2}) + .Case("sscanf", {{0}, {}, VariadicType::Dst, 2}) + .Case("getc", {{0}, {ReturnValueIndex}}) + .Case("getc_unlocked", {{0}, {ReturnValueIndex}}) + .Case("getdelim", {{3}, {0}}) + .Case("getline", {{2}, {0}}) + .Case("getw", {{0}, {ReturnValueIndex}}) + .Case("pread", {{0, 1, 2, 3}, {1, ReturnValueIndex}}) + .Case("read", {{0, 2}, {1, ReturnValueIndex}}) + .Case("strchr", {{0}, {ReturnValueIndex}}) + .Case("strrchr", {{0}, {ReturnValueIndex}}) + .Case("tolower", {{0}, {ReturnValueIndex}}) + .Case("toupper", {{0}, {ReturnValueIndex}}) + .Default({}); if (!Rule.isNull()) return Rule; + assert(FData.FDecl); // Check if it's one of the memory setting/copying functions. // This check is specialized but faster then calling isCLibraryFunction. const FunctionDecl *FDecl = FData.FDecl; unsigned BId = 0; - if ((BId = FDecl->getMemoryFunctionKind())) + if ((BId = FDecl->getMemoryFunctionKind())) { switch (BId) { case Builtin::BImemcpy: case Builtin::BImemmove: case Builtin::BIstrncpy: case Builtin::BIstrncat: - return TaintPropagationRule({1, 2}, {0, ReturnValueIndex}); + return {{1, 2}, {0, ReturnValueIndex}}; case Builtin::BIstrlcpy: case Builtin::BIstrlcat: - return TaintPropagationRule({1, 2}, {0}); + return {{1, 2}, {0}}; case Builtin::BIstrndup: - return TaintPropagationRule({0, 1}, {ReturnValueIndex}); + return {{0, 1}, {ReturnValueIndex}}; default: break; - }; + } + } // Process all other functions which could be defined as builtins. if (Rule.isNull()) { - if (C.isCLibraryFunction(FDecl, "snprintf")) - return TaintPropagationRule({1}, {0, ReturnValueIndex}, VariadicType::Src, - 3); - else if (C.isCLibraryFunction(FDecl, "sprintf")) - return TaintPropagationRule({}, {0, ReturnValueIndex}, VariadicType::Src, - 2); - else if (C.isCLibraryFunction(FDecl, "strcpy") || - C.isCLibraryFunction(FDecl, "stpcpy") || - C.isCLibraryFunction(FDecl, "strcat")) - return TaintPropagationRule({1}, {0, ReturnValueIndex}); - else if (C.isCLibraryFunction(FDecl, "bcopy")) - return TaintPropagationRule({0, 2}, {1}); - else if (C.isCLibraryFunction(FDecl, "strdup") || - C.isCLibraryFunction(FDecl, "strdupa")) - return TaintPropagationRule({0}, {ReturnValueIndex}); - else if (C.isCLibraryFunction(FDecl, "wcsdup")) - return TaintPropagationRule({0}, {ReturnValueIndex}); + const auto OneOf = [FDecl](const auto &... Name) { + // FIXME: use fold expression in C++17 + using unused = int[]; + bool ret = false; + static_cast<void>(unused{ + 0, (ret |= CheckerContext::isCLibraryFunction(FDecl, Name), 0)...}); + return ret; + }; + if (OneOf("snprintf")) + return {{1}, {0, ReturnValueIndex}, VariadicType::Src, 3}; + if (OneOf("sprintf")) + return {{}, {0, ReturnValueIndex}, VariadicType::Src, 2}; + if (OneOf("strcpy", "stpcpy", "strcat")) + return {{1}, {0, ReturnValueIndex}}; + if (OneOf("bcopy")) + return {{0, 2}, {1}}; + if (OneOf("strdup", "strdupa", "wcsdup")) + return {{0}, {ReturnValueIndex}}; } - // Skipping the following functions, since they might be used for cleansing - // or smart memory copy: + // Skipping the following functions, since they might be used for cleansing or + // smart memory copy: // - memccpy - copying until hitting a special character. auto It = findFunctionInConfig(CustomPropagations, FData); - if (It != CustomPropagations.end()) { - const auto &Value = It->second; - return Value.second; - } - - return TaintPropagationRule(); + if (It != CustomPropagations.end()) + return It->second.second; + return {}; } -void GenericTaintChecker::checkPreStmt(const CallExpr *CE, +void GenericTaintChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { - Optional<FunctionData> FData = FunctionData::create(CE, C); + Optional<FunctionData> FData = FunctionData::create(Call, C); if (!FData) return; // Check for taintedness related errors first: system call, uncontrolled // format string, tainted buffer size. - if (checkPre(CE, *FData, C)) + if (checkPre(Call, *FData, C)) return; // Marks the function's arguments and/or return value tainted if it present in // the list. - if (addSourcesPre(CE, *FData, C)) + if (addSourcesPre(Call, *FData, C)) return; - addFiltersPre(CE, *FData, C); + addFiltersPre(Call, *FData, C); } -void GenericTaintChecker::checkPostStmt(const CallExpr *CE, +void GenericTaintChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { // Set the marked values as tainted. The return value only accessible from // checkPostStmt. - propagateFromPre(CE, C); + propagateFromPre(Call, C); } void GenericTaintChecker::printState(raw_ostream &Out, ProgramStateRef State, @@ -545,14 +552,14 @@ void GenericTaintChecker::printState(raw_ostream &Out, ProgramStateRef State, printTaint(State, Out, NL, Sep); } -bool GenericTaintChecker::addSourcesPre(const CallExpr *CE, +bool GenericTaintChecker::addSourcesPre(const CallEvent &Call, const FunctionData &FData, CheckerContext &C) const { // First, try generating a propagation rule for this function. TaintPropagationRule Rule = TaintPropagationRule::getTaintPropagationRule( this->CustomPropagations, FData, C); if (!Rule.isNull()) { - ProgramStateRef State = Rule.process(CE, C); + ProgramStateRef State = Rule.process(Call, C); if (State) { C.addTransition(State); return true; @@ -561,7 +568,7 @@ bool GenericTaintChecker::addSourcesPre(const CallExpr *CE, return false; } -bool GenericTaintChecker::addFiltersPre(const CallExpr *CE, +bool GenericTaintChecker::addFiltersPre(const CallEvent &Call, const FunctionData &FData, CheckerContext &C) const { auto It = findFunctionInConfig(CustomFilters, FData); @@ -572,11 +579,11 @@ bool GenericTaintChecker::addFiltersPre(const CallExpr *CE, const auto &Value = It->second; const ArgVector &Args = Value.second; for (unsigned ArgNum : Args) { - if (ArgNum >= CE->getNumArgs()) + if (ArgNum >= Call.getNumArgs()) continue; - const Expr *Arg = CE->getArg(ArgNum); - Optional<SVal> V = getPointedToSVal(C, Arg); + const Expr *Arg = Call.getArgExpr(ArgNum); + Optional<SVal> V = getPointeeOf(C, Arg); if (V) State = removeTaint(State, *V); } @@ -588,8 +595,8 @@ bool GenericTaintChecker::addFiltersPre(const CallExpr *CE, return false; } -bool GenericTaintChecker::propagateFromPre(const CallExpr *CE, - CheckerContext &C) const { +bool GenericTaintChecker::propagateFromPre(const CallEvent &Call, + CheckerContext &C) { ProgramStateRef State = C.getState(); // Depending on what was tainted at pre-visit, we determined a set of @@ -602,16 +609,16 @@ bool GenericTaintChecker::propagateFromPre(const CallExpr *CE, for (unsigned ArgNum : TaintArgs) { // Special handling for the tainted return value. if (ArgNum == ReturnValueIndex) { - State = addTaint(State, CE, C.getLocationContext()); + State = addTaint(State, Call.getReturnValue()); continue; } // The arguments are pointer arguments. The data they are pointing at is // tainted after the call. - if (CE->getNumArgs() < (ArgNum + 1)) + if (Call.getNumArgs() < (ArgNum + 1)) return false; - const Expr *Arg = CE->getArg(ArgNum); - Optional<SVal> V = getPointedToSVal(C, Arg); + const Expr *Arg = Call.getArgExpr(ArgNum); + Optional<SVal> V = getPointeeOf(C, Arg); if (V) State = addTaint(State, *V); } @@ -626,27 +633,23 @@ bool GenericTaintChecker::propagateFromPre(const CallExpr *CE, return false; } -bool GenericTaintChecker::checkPre(const CallExpr *CE, +bool GenericTaintChecker::checkPre(const CallEvent &Call, const FunctionData &FData, CheckerContext &C) const { - - if (checkUncontrolledFormatString(CE, C)) - return true; - - if (checkSystemCall(CE, FData.Name, C)) + if (checkUncontrolledFormatString(Call, C)) return true; - if (checkTaintedBufferSize(CE, FData.FDecl, C)) + if (checkSystemCall(Call, FData.Name, C)) return true; - if (checkCustomSinks(CE, FData, C)) + if (checkTaintedBufferSize(Call, C)) return true; - return false; + return checkCustomSinks(Call, FData, C); } -Optional<SVal> GenericTaintChecker::getPointedToSVal(CheckerContext &C, - const Expr *Arg) { +Optional<SVal> GenericTaintChecker::getPointeeOf(CheckerContext &C, + const Expr *Arg) { ProgramStateRef State = C.getState(); SVal AddrVal = C.getSVal(Arg->IgnoreParens()); if (AddrVal.isUnknownOrUndef()) @@ -671,31 +674,33 @@ Optional<SVal> GenericTaintChecker::getPointedToSVal(CheckerContext &C, } ProgramStateRef -GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, +GenericTaintChecker::TaintPropagationRule::process(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); // Check for taint in arguments. bool IsTainted = true; for (unsigned ArgNum : SrcArgs) { - if (ArgNum >= CE->getNumArgs()) + if (ArgNum >= Call.getNumArgs()) continue; - if ((IsTainted = isTaintedOrPointsToTainted(CE->getArg(ArgNum), State, C))) + if ((IsTainted = + isTaintedOrPointsToTainted(Call.getArgExpr(ArgNum), State, C))) break; } // Check for taint in variadic arguments. if (!IsTainted && VariadicType::Src == VarType) { // Check if any of the arguments is tainted - for (unsigned i = VariadicIndex; i < CE->getNumArgs(); ++i) { - if ((IsTainted = isTaintedOrPointsToTainted(CE->getArg(i), State, C))) + for (unsigned i = VariadicIndex; i < Call.getNumArgs(); ++i) { + if ((IsTainted = + isTaintedOrPointsToTainted(Call.getArgExpr(i), State, C))) break; } } if (PropagationFunc) - IsTainted = PropagationFunc(IsTainted, CE, C); + IsTainted = PropagationFunc(IsTainted, Call, C); if (!IsTainted) return State; @@ -708,7 +713,7 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, continue; } - if (ArgNum >= CE->getNumArgs()) + if (ArgNum >= Call.getNumArgs()) continue; // Mark the given argument. @@ -721,14 +726,15 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, // If they are not pointing to const data, mark data as tainted. // TODO: So far we are just going one level down; ideally we'd need to // recurse here. - for (unsigned i = VariadicIndex; i < CE->getNumArgs(); ++i) { - const Expr *Arg = CE->getArg(i); + for (unsigned i = VariadicIndex; i < Call.getNumArgs(); ++i) { + const Expr *Arg = Call.getArgExpr(i); // Process pointer argument. const Type *ArgTy = Arg->getType().getTypePtr(); QualType PType = ArgTy->getPointeeType(); if ((!PType.isNull() && !PType.isConstQualified()) || - (ArgTy->isReferenceType() && !Arg->getType().isConstQualified())) + (ArgTy->isReferenceType() && !Arg->getType().isConstQualified())) { State = State->add<TaintArgsOnPostVisit>(i); + } } } @@ -736,16 +742,14 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, } // If argument 0(protocol domain) is network, the return value should get taint. -bool GenericTaintChecker::TaintPropagationRule::postSocket(bool /*IsTainted*/, - const CallExpr *CE, - CheckerContext &C) { - SourceLocation DomLoc = CE->getArg(0)->getExprLoc(); +bool GenericTaintChecker::TaintPropagationRule::postSocket( + bool /*IsTainted*/, const CallEvent &Call, CheckerContext &C) { + SourceLocation DomLoc = Call.getArgExpr(0)->getExprLoc(); StringRef DomName = C.getMacroNameOrSpelling(DomLoc); // White list the internal communication protocols. if (DomName.equals("AF_SYSTEM") || DomName.equals("AF_LOCAL") || DomName.equals("AF_UNIX") || DomName.equals("AF_RESERVED_36")) return false; - return true; } @@ -757,16 +761,15 @@ bool GenericTaintChecker::isStdin(const Expr *E, CheckerContext &C) { const MemRegion *MemReg = Val.getAsRegion(); // The region should be symbolic, we do not know it's value. - const SymbolicRegion *SymReg = dyn_cast_or_null<SymbolicRegion>(MemReg); + const auto *SymReg = dyn_cast_or_null<SymbolicRegion>(MemReg); if (!SymReg) return false; // Get it's symbol and find the declaration region it's pointing to. - const SymbolRegionValue *Sm = - dyn_cast<SymbolRegionValue>(SymReg->getSymbol()); + const auto *Sm = dyn_cast<SymbolRegionValue>(SymReg->getSymbol()); if (!Sm) return false; - const DeclRegion *DeclReg = dyn_cast_or_null<DeclRegion>(Sm->getRegion()); + const auto *DeclReg = dyn_cast_or_null<DeclRegion>(Sm->getRegion()); if (!DeclReg) return false; @@ -784,23 +787,24 @@ bool GenericTaintChecker::isStdin(const Expr *E, CheckerContext &C) { return false; } -static bool getPrintfFormatArgumentNum(const CallExpr *CE, +static bool getPrintfFormatArgumentNum(const CallEvent &Call, const CheckerContext &C, unsigned &ArgNum) { // Find if the function contains a format string argument. // Handles: fprintf, printf, sprintf, snprintf, vfprintf, vprintf, vsprintf, // vsnprintf, syslog, custom annotated functions. - const FunctionDecl *FDecl = C.getCalleeDecl(CE); + const FunctionDecl *FDecl = Call.getDecl()->getAsFunction(); if (!FDecl) return false; for (const auto *Format : FDecl->specific_attrs<FormatAttr>()) { ArgNum = Format->getFormatIdx() - 1; - if ((Format->getType()->getName() == "printf") && CE->getNumArgs() > ArgNum) + if ((Format->getType()->getName() == "printf") && + Call.getNumArgs() > ArgNum) return true; } // Or if a function is named setproctitle (this is a heuristic). - if (C.getCalleeName(CE).find("setproctitle") != StringRef::npos) { + if (C.getCalleeName(FDecl).find("setproctitle") != StringRef::npos) { ArgNum = 0; return true; } @@ -814,7 +818,7 @@ bool GenericTaintChecker::generateReportIfTainted(const Expr *E, StringRef Msg, // Check for taint. ProgramStateRef State = C.getState(); - Optional<SVal> PointedToSVal = getPointedToSVal(C, E); + Optional<SVal> PointedToSVal = getPointeeOf(C, E); SVal TaintedSVal; if (PointedToSVal && isTainted(State, *PointedToSVal)) TaintedSVal = *PointedToSVal; @@ -836,19 +840,19 @@ bool GenericTaintChecker::generateReportIfTainted(const Expr *E, StringRef Msg, } bool GenericTaintChecker::checkUncontrolledFormatString( - const CallExpr *CE, CheckerContext &C) const { + const CallEvent &Call, CheckerContext &C) const { // Check if the function contains a format string argument. unsigned ArgNum = 0; - if (!getPrintfFormatArgumentNum(CE, C, ArgNum)) + if (!getPrintfFormatArgumentNum(Call, C, ArgNum)) return false; // If either the format string content or the pointer itself are tainted, // warn. - return generateReportIfTainted(CE->getArg(ArgNum), + return generateReportIfTainted(Call.getArgExpr(ArgNum), MsgUncontrolledFormatString, C); } -bool GenericTaintChecker::checkSystemCall(const CallExpr *CE, StringRef Name, +bool GenericTaintChecker::checkSystemCall(const CallEvent &Call, StringRef Name, CheckerContext &C) const { // TODO: It might make sense to run this check on demand. In some cases, // we should check if the environment has been cleansed here. We also might @@ -866,21 +870,22 @@ bool GenericTaintChecker::checkSystemCall(const CallExpr *CE, StringRef Name, .Case("dlopen", 0) .Default(InvalidArgIndex); - if (ArgNum == InvalidArgIndex || CE->getNumArgs() < (ArgNum + 1)) + if (ArgNum == InvalidArgIndex || Call.getNumArgs() < (ArgNum + 1)) return false; - return generateReportIfTainted(CE->getArg(ArgNum), MsgSanitizeSystemArgs, C); + return generateReportIfTainted(Call.getArgExpr(ArgNum), MsgSanitizeSystemArgs, + C); } // TODO: Should this check be a part of the CString checker? // If yes, should taint be a global setting? -bool GenericTaintChecker::checkTaintedBufferSize(const CallExpr *CE, - const FunctionDecl *FDecl, +bool GenericTaintChecker::checkTaintedBufferSize(const CallEvent &Call, CheckerContext &C) const { + const auto *FDecl = Call.getDecl()->getAsFunction(); // If the function has a buffer size argument, set ArgNum. unsigned ArgNum = InvalidArgIndex; unsigned BId = 0; - if ((BId = FDecl->getMemoryFunctionKind())) + if ((BId = FDecl->getMemoryFunctionKind())) { switch (BId) { case Builtin::BImemcpy: case Builtin::BImemmove: @@ -892,26 +897,29 @@ bool GenericTaintChecker::checkTaintedBufferSize(const CallExpr *CE, break; default: break; - }; + } + } if (ArgNum == InvalidArgIndex) { - if (C.isCLibraryFunction(FDecl, "malloc") || - C.isCLibraryFunction(FDecl, "calloc") || - C.isCLibraryFunction(FDecl, "alloca")) + using CCtx = CheckerContext; + if (CCtx::isCLibraryFunction(FDecl, "malloc") || + CCtx::isCLibraryFunction(FDecl, "calloc") || + CCtx::isCLibraryFunction(FDecl, "alloca")) ArgNum = 0; - else if (C.isCLibraryFunction(FDecl, "memccpy")) + else if (CCtx::isCLibraryFunction(FDecl, "memccpy")) ArgNum = 3; - else if (C.isCLibraryFunction(FDecl, "realloc")) + else if (CCtx::isCLibraryFunction(FDecl, "realloc")) ArgNum = 1; - else if (C.isCLibraryFunction(FDecl, "bcopy")) + else if (CCtx::isCLibraryFunction(FDecl, "bcopy")) ArgNum = 2; } - return ArgNum != InvalidArgIndex && CE->getNumArgs() > ArgNum && - generateReportIfTainted(CE->getArg(ArgNum), MsgTaintedBufferSize, C); + return ArgNum != InvalidArgIndex && Call.getNumArgs() > ArgNum && + generateReportIfTainted(Call.getArgExpr(ArgNum), MsgTaintedBufferSize, + C); } -bool GenericTaintChecker::checkCustomSinks(const CallExpr *CE, +bool GenericTaintChecker::checkCustomSinks(const CallEvent &Call, const FunctionData &FData, CheckerContext &C) const { auto It = findFunctionInConfig(CustomSinks, FData); @@ -921,10 +929,10 @@ bool GenericTaintChecker::checkCustomSinks(const CallExpr *CE, const auto &Value = It->second; const GenericTaintChecker::ArgVector &Args = Value.second; for (unsigned ArgNum : Args) { - if (ArgNum >= CE->getNumArgs()) + if (ArgNum >= Call.getNumArgs()) continue; - if (generateReportIfTainted(CE->getArg(ArgNum), MsgCustomSink, C)) + if (generateReportIfTainted(Call.getArgExpr(ArgNum), MsgCustomSink, C)) return true; } @@ -942,6 +950,6 @@ void ento::registerGenericTaintChecker(CheckerManager &Mgr) { Checker->parseConfiguration(Mgr, Option, std::move(Config.getValue())); } -bool ento::shouldRegisterGenericTaintChecker(const LangOptions &LO) { +bool ento::shouldRegisterGenericTaintChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp index cc2cfb774227..1cf81b54e77d 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp @@ -351,6 +351,8 @@ static bool isIdenticalStmt(const ASTContext &Ctx, const Stmt *Stmt1, case Stmt::CallExprClass: case Stmt::ArraySubscriptExprClass: case Stmt::OMPArraySectionExprClass: + case Stmt::OMPArrayShapingExprClass: + case Stmt::OMPIteratorExprClass: case Stmt::ImplicitCastExprClass: case Stmt::ParenExprClass: case Stmt::BreakStmtClass: @@ -513,6 +515,6 @@ void ento::registerIdenticalExprChecker(CheckerManager &Mgr) { Mgr.registerChecker<FindIdenticalExprChecker>(); } -bool ento::shouldRegisterIdenticalExprChecker(const LangOptions &LO) { +bool ento::shouldRegisterIdenticalExprChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp index dd89c53478e8..65e52e139ee4 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp @@ -307,6 +307,6 @@ void ento::registerInnerPointerChecker(CheckerManager &Mgr) { Mgr.registerChecker<InnerPointerChecker>(); } -bool ento::shouldRegisterInnerPointerChecker(const LangOptions &LO) { +bool ento::shouldRegisterInnerPointerChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InterCheckerAPI.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InterCheckerAPI.h index 9642588d6a41..99731d6044a0 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InterCheckerAPI.h +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InterCheckerAPI.h @@ -11,13 +11,19 @@ #ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_INTERCHECKERAPI_H #define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_INTERCHECKERAPI_H -namespace clang { -class CheckerManager; +// FIXME: This file goes against how a checker should be implemented either in +// a single file, or be exposed in a header file. Let's try to get rid of it! + +namespace clang { namespace ento { +class CheckerManager; + /// Register the part of MallocChecker connected to InnerPointerChecker. void registerInnerPointerCheckerAux(CheckerManager &Mgr); -}} +} // namespace ento +} // namespace clang + #endif /* INTERCHECKERAPI_H_ */ diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InvalidatedIteratorChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InvalidatedIteratorChecker.cpp index d1a9a7df071d..6955ba11a28f 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InvalidatedIteratorChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InvalidatedIteratorChecker.cpp @@ -26,7 +26,10 @@ using namespace iterator; namespace { class InvalidatedIteratorChecker - : public Checker<check::PreCall> { + : public Checker<check::PreCall, check::PreStmt<UnaryOperator>, + check::PreStmt<BinaryOperator>, + check::PreStmt<ArraySubscriptExpr>, + check::PreStmt<MemberExpr>> { std::unique_ptr<BugType> InvalidatedBugType; @@ -37,6 +40,10 @@ public: InvalidatedIteratorChecker(); void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkPreStmt(const UnaryOperator *UO, CheckerContext &C) const; + void checkPreStmt(const BinaryOperator *BO, CheckerContext &C) const; + void checkPreStmt(const ArraySubscriptExpr *ASE, CheckerContext &C) const; + void checkPreStmt(const MemberExpr *ME, CheckerContext &C) const; }; @@ -65,6 +72,48 @@ void InvalidatedIteratorChecker::checkPreCall(const CallEvent &Call, } } +void InvalidatedIteratorChecker::checkPreStmt(const UnaryOperator *UO, + CheckerContext &C) const { + if (isa<CXXThisExpr>(UO->getSubExpr())) + return; + + ProgramStateRef State = C.getState(); + UnaryOperatorKind OK = UO->getOpcode(); + SVal SubVal = State->getSVal(UO->getSubExpr(), C.getLocationContext()); + + if (isAccessOperator(OK)) { + verifyAccess(C, SubVal); + } +} + +void InvalidatedIteratorChecker::checkPreStmt(const BinaryOperator *BO, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + BinaryOperatorKind OK = BO->getOpcode(); + SVal LVal = State->getSVal(BO->getLHS(), C.getLocationContext()); + + if (isAccessOperator(OK)) { + verifyAccess(C, LVal); + } +} + +void InvalidatedIteratorChecker::checkPreStmt(const ArraySubscriptExpr *ASE, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SVal LVal = State->getSVal(ASE->getLHS(), C.getLocationContext()); + verifyAccess(C, LVal); +} + +void InvalidatedIteratorChecker::checkPreStmt(const MemberExpr *ME, + CheckerContext &C) const { + if (!ME->isArrow() || ME->isImplicitAccess()) + return; + + ProgramStateRef State = C.getState(); + SVal BaseVal = State->getSVal(ME->getBase(), C.getLocationContext()); + verifyAccess(C, BaseVal); +} + void InvalidatedIteratorChecker::verifyAccess(CheckerContext &C, const SVal &Val) const { auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Val); @@ -90,6 +139,6 @@ void ento::registerInvalidatedIteratorChecker(CheckerManager &mgr) { mgr.registerChecker<InvalidatedIteratorChecker>(); } -bool ento::shouldRegisterInvalidatedIteratorChecker(const LangOptions &LO) { +bool ento::shouldRegisterInvalidatedIteratorChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.cpp index 6bca5515724c..ac0f24603dd9 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.cpp @@ -128,24 +128,54 @@ bool isAccessOperator(OverloadedOperatorKind OK) { isDecrementOperator(OK) || isRandomIncrOrDecrOperator(OK); } +bool isAccessOperator(UnaryOperatorKind OK) { + return isDereferenceOperator(OK) || isIncrementOperator(OK) || + isDecrementOperator(OK); +} + +bool isAccessOperator(BinaryOperatorKind OK) { + return isDereferenceOperator(OK) || isRandomIncrOrDecrOperator(OK); +} + bool isDereferenceOperator(OverloadedOperatorKind OK) { return OK == OO_Star || OK == OO_Arrow || OK == OO_ArrowStar || OK == OO_Subscript; } +bool isDereferenceOperator(UnaryOperatorKind OK) { + return OK == UO_Deref; +} + +bool isDereferenceOperator(BinaryOperatorKind OK) { + return OK == BO_PtrMemI; +} + bool isIncrementOperator(OverloadedOperatorKind OK) { return OK == OO_PlusPlus; } +bool isIncrementOperator(UnaryOperatorKind OK) { + return OK == UO_PreInc || OK == UO_PostInc; +} + bool isDecrementOperator(OverloadedOperatorKind OK) { return OK == OO_MinusMinus; } +bool isDecrementOperator(UnaryOperatorKind OK) { + return OK == UO_PreDec || OK == UO_PostDec; +} + bool isRandomIncrOrDecrOperator(OverloadedOperatorKind OK) { return OK == OO_Plus || OK == OO_PlusEqual || OK == OO_Minus || OK == OO_MinusEqual; } +bool isRandomIncrOrDecrOperator(BinaryOperatorKind OK) { + return OK == BO_Add || OK == BO_AddAssign || + OK == BO_Sub || OK == BO_SubAssign; +} + const ContainerData *getContainerData(ProgramStateRef State, const MemRegion *Cont) { return State->get<ContainerMap>(Cont); @@ -177,6 +207,20 @@ ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, return nullptr; } +ProgramStateRef createIteratorPosition(ProgramStateRef State, const SVal &Val, + const MemRegion *Cont, const Stmt* S, + const LocationContext *LCtx, + unsigned blockCount) { + auto &StateMgr = State->getStateManager(); + auto &SymMgr = StateMgr.getSymbolManager(); + auto &ACtx = StateMgr.getContext(); + + auto Sym = SymMgr.conjureSymbol(S, LCtx, ACtx.LongTy, blockCount); + State = assumeNoOverflow(State, Sym, 4); + return setIteratorPosition(State, Val, + IteratorPosition::getPosition(Cont, Sym)); +} + ProgramStateRef advancePosition(ProgramStateRef State, const SVal &Iter, OverloadedOperatorKind Op, const SVal &Distance) { @@ -186,22 +230,70 @@ ProgramStateRef advancePosition(ProgramStateRef State, const SVal &Iter, auto &SymMgr = State->getStateManager().getSymbolManager(); auto &SVB = State->getStateManager().getSValBuilder(); + auto &BVF = State->getStateManager().getBasicVals(); assert ((Op == OO_Plus || Op == OO_PlusEqual || Op == OO_Minus || Op == OO_MinusEqual) && "Advance operator must be one of +, -, += and -=."); auto BinOp = (Op == OO_Plus || Op == OO_PlusEqual) ? BO_Add : BO_Sub; - if (const auto IntDist = Distance.getAs<nonloc::ConcreteInt>()) { - // For concrete integers we can calculate the new position - const auto NewPos = - Pos->setTo(SVB.evalBinOp(State, BinOp, - nonloc::SymbolVal(Pos->getOffset()), - *IntDist, SymMgr.getType(Pos->getOffset())) - .getAsSymbol()); - return setIteratorPosition(State, Iter, NewPos); + const auto IntDistOp = Distance.getAs<nonloc::ConcreteInt>(); + if (!IntDistOp) + return nullptr; + + // For concrete integers we can calculate the new position + nonloc::ConcreteInt IntDist = *IntDistOp; + + if (IntDist.getValue().isNegative()) { + IntDist = nonloc::ConcreteInt(BVF.getValue(-IntDist.getValue())); + BinOp = (BinOp == BO_Add) ? BO_Sub : BO_Add; } + const auto NewPos = + Pos->setTo(SVB.evalBinOp(State, BinOp, + nonloc::SymbolVal(Pos->getOffset()), + IntDist, SymMgr.getType(Pos->getOffset())) + .getAsSymbol()); + return setIteratorPosition(State, Iter, NewPos); +} - return nullptr; +// 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? +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; } bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.h index c10d86691693..37157492fe3e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.h +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.h @@ -115,9 +115,12 @@ class IteratorSymbolMap {}; class IteratorRegionMap {}; class ContainerMap {}; -using IteratorSymbolMapTy = CLANG_ENTO_PROGRAMSTATE_MAP(SymbolRef, IteratorPosition); -using IteratorRegionMapTy = CLANG_ENTO_PROGRAMSTATE_MAP(const MemRegion *, IteratorPosition); -using ContainerMapTy = CLANG_ENTO_PROGRAMSTATE_MAP(const MemRegion *, ContainerData); +using IteratorSymbolMapTy = + CLANG_ENTO_PROGRAMSTATE_MAP(SymbolRef, IteratorPosition); +using IteratorRegionMapTy = + CLANG_ENTO_PROGRAMSTATE_MAP(const MemRegion *, IteratorPosition); +using ContainerMapTy = + CLANG_ENTO_PROGRAMSTATE_MAP(const MemRegion *, ContainerData); } // namespace iterator @@ -149,20 +152,33 @@ bool isEraseCall(const FunctionDecl *Func); bool isEraseAfterCall(const FunctionDecl *Func); bool isEmplaceCall(const FunctionDecl *Func); bool isAccessOperator(OverloadedOperatorKind OK); +bool isAccessOperator(UnaryOperatorKind OK); +bool isAccessOperator(BinaryOperatorKind OK); bool isDereferenceOperator(OverloadedOperatorKind OK); +bool isDereferenceOperator(UnaryOperatorKind OK); +bool isDereferenceOperator(BinaryOperatorKind OK); bool isIncrementOperator(OverloadedOperatorKind OK); +bool isIncrementOperator(UnaryOperatorKind OK); bool isDecrementOperator(OverloadedOperatorKind OK); +bool isDecrementOperator(UnaryOperatorKind OK); bool isRandomIncrOrDecrOperator(OverloadedOperatorKind OK); +bool isRandomIncrOrDecrOperator(BinaryOperatorKind OK); const ContainerData *getContainerData(ProgramStateRef State, const MemRegion *Cont); const IteratorPosition *getIteratorPosition(ProgramStateRef State, const SVal &Val); ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, const IteratorPosition &Pos); +ProgramStateRef createIteratorPosition(ProgramStateRef State, const SVal &Val, + const MemRegion *Cont, const Stmt* S, + const LocationContext *LCtx, + unsigned blockCount); ProgramStateRef advancePosition(ProgramStateRef State, const SVal &Iter, OverloadedOperatorKind Op, const SVal &Distance); +ProgramStateRef assumeNoOverflow(ProgramStateRef State, SymbolRef Sym, + long Scale); bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, BinaryOperator::Opcode Opc); bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2, diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp index eb962a2ffd9e..fd8cbd694b24 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp @@ -6,8 +6,7 @@ // //===----------------------------------------------------------------------===// // -// Defines a checker for using iterators outside their range (past end). Usage -// means here dereferencing, incrementing etc. +// Defines a modeling-checker for modeling STL iterator-like iterators. // //===----------------------------------------------------------------------===// // @@ -84,9 +83,20 @@ using namespace iterator; namespace { class IteratorModeling - : public Checker<check::PostCall, check::PostStmt<MaterializeTemporaryExpr>, + : public Checker<check::PostCall, check::PostStmt<UnaryOperator>, + check::PostStmt<BinaryOperator>, + check::PostStmt<MaterializeTemporaryExpr>, check::Bind, check::LiveSymbols, check::DeadSymbols> { + using AdvanceFn = void (IteratorModeling::*)(CheckerContext &, const Expr *, + SVal, SVal, SVal) const; + + void handleOverloadedOperator(CheckerContext &C, const CallEvent &Call, + OverloadedOperatorKind Op) const; + void handleAdvanceLikeFunction(CheckerContext &C, const CallEvent &Call, + const Expr *OrigExpr, + const AdvanceFn *Handler) const; + void handleComparison(CheckerContext &C, const Expr *CE, SVal RetVal, const SVal &LVal, const SVal &RVal, OverloadedOperatorKind Op) const; @@ -100,35 +110,46 @@ class IteratorModeling void handleRandomIncrOrDecr(CheckerContext &C, const Expr *CE, 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 handlePtrIncrOrDecr(CheckerContext &C, const Expr *Iterator, + OverloadedOperatorKind OK, SVal Offset) const; + void handleAdvance(CheckerContext &C, const Expr *CE, SVal RetVal, SVal Iter, + SVal Amount) const; + void handlePrev(CheckerContext &C, const Expr *CE, SVal RetVal, SVal Iter, + SVal Amount) const; + void handleNext(CheckerContext &C, const Expr *CE, SVal RetVal, SVal Iter, + SVal Amount) const; void assignToContainer(CheckerContext &C, const Expr *CE, const SVal &RetVal, const MemRegion *Cont) const; - void handleAssign(CheckerContext &C, const SVal &Cont, - const Expr *CE = nullptr, - const SVal &OldCont = UndefinedVal()) const; - void handleClear(CheckerContext &C, const SVal &Cont) const; - void handlePushBack(CheckerContext &C, const SVal &Cont) const; - void handlePopBack(CheckerContext &C, const SVal &Cont) const; - void handlePushFront(CheckerContext &C, const SVal &Cont) const; - void handlePopFront(CheckerContext &C, const SVal &Cont) const; - void handleInsert(CheckerContext &C, const SVal &Iter) const; - void handleErase(CheckerContext &C, const SVal &Iter) const; - void handleErase(CheckerContext &C, const SVal &Iter1, - const SVal &Iter2) const; - void handleEraseAfter(CheckerContext &C, const SVal &Iter) const; - void handleEraseAfter(CheckerContext &C, const SVal &Iter1, - const SVal &Iter2) const; + bool noChangeInAdvance(CheckerContext &C, SVal Iter, const Expr *CE) const; void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const override; + // std::advance, std::prev & std::next + CallDescriptionMap<AdvanceFn> AdvanceLikeFunctions = { + // template<class InputIt, class Distance> + // void advance(InputIt& it, Distance n); + {{{"std", "advance"}, 2}, &IteratorModeling::handleAdvance}, + + // template<class BidirIt> + // BidirIt prev( + // BidirIt it, + // typename std::iterator_traits<BidirIt>::difference_type n = 1); + {{{"std", "prev"}, 2}, &IteratorModeling::handlePrev}, + + // template<class ForwardIt> + // ForwardIt next( + // ForwardIt it, + // typename std::iterator_traits<ForwardIt>::difference_type n = 1); + {{{"std", "next"}, 2}, &IteratorModeling::handleNext}, + }; + public: - IteratorModeling() {} + IteratorModeling() = default; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; void checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &C) const; + void checkPostStmt(const UnaryOperator *UO, CheckerContext &C) const; + void checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const; void checkPostStmt(const CXXConstructExpr *CCE, CheckerContext &C) const; void checkPostStmt(const DeclStmt *DS, CheckerContext &C) const; void checkPostStmt(const MaterializeTemporaryExpr *MTE, @@ -137,68 +158,14 @@ public: void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; }; -bool isBeginCall(const FunctionDecl *Func); -bool isEndCall(const FunctionDecl *Func); -bool isAssignCall(const FunctionDecl *Func); -bool isClearCall(const FunctionDecl *Func); -bool isPushBackCall(const FunctionDecl *Func); -bool isEmplaceBackCall(const FunctionDecl *Func); -bool isPopBackCall(const FunctionDecl *Func); -bool isPushFrontCall(const FunctionDecl *Func); -bool isEmplaceFrontCall(const FunctionDecl *Func); -bool isPopFrontCall(const FunctionDecl *Func); -bool isAssignmentOperator(OverloadedOperatorKind OK); bool isSimpleComparisonOperator(OverloadedOperatorKind OK); -bool hasSubscriptOperator(ProgramStateRef State, const MemRegion *Reg); -bool frontModifiable(ProgramStateRef State, const MemRegion *Reg); -bool backModifiable(ProgramStateRef State, const MemRegion *Reg); -SymbolRef getContainerBegin(ProgramStateRef State, const MemRegion *Cont); -SymbolRef getContainerEnd(ProgramStateRef State, const MemRegion *Cont); -ProgramStateRef createContainerBegin(ProgramStateRef State, - const MemRegion *Cont, const Expr *E, - QualType T, const LocationContext *LCtx, - unsigned BlockCount); -ProgramStateRef createContainerEnd(ProgramStateRef State, const MemRegion *Cont, - const Expr *E, QualType T, - const LocationContext *LCtx, - unsigned BlockCount); -ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont, - const ContainerData &CData); +bool isSimpleComparisonOperator(BinaryOperatorKind OK); ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val); -ProgramStateRef assumeNoOverflow(ProgramStateRef State, SymbolRef Sym, - long Scale); -ProgramStateRef invalidateAllIteratorPositions(ProgramStateRef State, - const MemRegion *Cont); -ProgramStateRef -invalidateAllIteratorPositionsExcept(ProgramStateRef State, - const MemRegion *Cont, SymbolRef Offset, - BinaryOperator::Opcode Opc); -ProgramStateRef invalidateIteratorPositions(ProgramStateRef State, - SymbolRef Offset, - BinaryOperator::Opcode Opc); -ProgramStateRef invalidateIteratorPositions(ProgramStateRef State, - SymbolRef Offset1, - BinaryOperator::Opcode Opc1, - SymbolRef Offset2, - BinaryOperator::Opcode Opc2); -ProgramStateRef reassignAllIteratorPositions(ProgramStateRef State, - const MemRegion *Cont, - const MemRegion *NewCont); -ProgramStateRef reassignAllIteratorPositionsUnless(ProgramStateRef State, - const MemRegion *Cont, - const MemRegion *NewCont, - SymbolRef Offset, - BinaryOperator::Opcode Opc); -ProgramStateRef rebaseSymbolInIteratorPositionsIf( - ProgramStateRef State, SValBuilder &SVB, SymbolRef OldSym, - SymbolRef NewSym, SymbolRef CondSym, BinaryOperator::Opcode Opc); ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, bool Equal); -SymbolRef rebaseSymbol(ProgramStateRef State, SValBuilder &SVB, SymbolRef Expr, - SymbolRef OldSym, SymbolRef NewSym); -bool hasLiveIterators(ProgramStateRef State, const MemRegion *Cont); bool isBoundThroughLazyCompoundVal(const Environment &Env, const MemRegion *Reg); +const ExplodedNode *findCallEnter(const ExplodedNode *Node, const Expr *Call); } // namespace @@ -211,189 +178,57 @@ void IteratorModeling::checkPostCall(const CallEvent &Call, if (Func->isOverloadedOperator()) { const auto Op = Func->getOverloadedOperator(); - if (isAssignmentOperator(Op)) { - // Overloaded 'operator=' must be a non-static member function. - const auto *InstCall = cast<CXXInstanceCall>(&Call); - if (cast<CXXMethodDecl>(Func)->isMoveAssignmentOperator()) { - handleAssign(C, InstCall->getCXXThisVal(), Call.getOriginExpr(), - Call.getArgSVal(0)); - return; - } - - handleAssign(C, InstCall->getCXXThisVal()); - return; - } else if (isSimpleComparisonOperator(Op)) { - const auto *OrigExpr = Call.getOriginExpr(); - if (!OrigExpr) - return; - - if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { - handleComparison(C, OrigExpr, Call.getReturnValue(), - InstCall->getCXXThisVal(), Call.getArgSVal(0), Op); - return; - } - - handleComparison(C, OrigExpr, Call.getReturnValue(), Call.getArgSVal(0), - Call.getArgSVal(1), Op); - return; - } else if (isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { - const auto *OrigExpr = Call.getOriginExpr(); - if (!OrigExpr) - return; - - if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { - if (Call.getNumArgs() >= 1 && - Call.getArgExpr(0)->getType()->isIntegralOrEnumerationType()) { - handleRandomIncrOrDecr(C, OrigExpr, Func->getOverloadedOperator(), - Call.getReturnValue(), - InstCall->getCXXThisVal(), Call.getArgSVal(0)); - return; - } - } else { - if (Call.getNumArgs() >= 2 && - Call.getArgExpr(1)->getType()->isIntegralOrEnumerationType()) { - handleRandomIncrOrDecr(C, OrigExpr, Func->getOverloadedOperator(), - Call.getReturnValue(), Call.getArgSVal(0), - Call.getArgSVal(1)); - return; - } - } - } else if (isIncrementOperator(Func->getOverloadedOperator())) { - if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { - handleIncrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(), - Call.getNumArgs()); - return; - } - - handleIncrement(C, Call.getReturnValue(), Call.getArgSVal(0), - Call.getNumArgs()); - return; - } else if (isDecrementOperator(Func->getOverloadedOperator())) { - if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { - handleDecrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(), - Call.getNumArgs()); - return; - } - - handleDecrement(C, Call.getReturnValue(), Call.getArgSVal(0), - Call.getNumArgs()); - return; - } - } else { - if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { - if (isAssignCall(Func)) { - handleAssign(C, InstCall->getCXXThisVal()); - return; - } - - if (isClearCall(Func)) { - handleClear(C, InstCall->getCXXThisVal()); - return; - } - - if (isPushBackCall(Func) || isEmplaceBackCall(Func)) { - handlePushBack(C, InstCall->getCXXThisVal()); - return; - } - - if (isPopBackCall(Func)) { - handlePopBack(C, InstCall->getCXXThisVal()); - return; - } - - if (isPushFrontCall(Func) || isEmplaceFrontCall(Func)) { - handlePushFront(C, InstCall->getCXXThisVal()); - return; - } + handleOverloadedOperator(C, Call, Op); + return; + } - if (isPopFrontCall(Func)) { - handlePopFront(C, InstCall->getCXXThisVal()); - return; - } + const auto *OrigExpr = Call.getOriginExpr(); + if (!OrigExpr) + return; - if (isInsertCall(Func) || isEmplaceCall(Func)) { - handleInsert(C, Call.getArgSVal(0)); - return; - } + const AdvanceFn *Handler = AdvanceLikeFunctions.lookup(Call); + if (Handler) { + handleAdvanceLikeFunction(C, Call, OrigExpr, Handler); + return; + } - if (isEraseCall(Func)) { - if (Call.getNumArgs() == 1) { - handleErase(C, Call.getArgSVal(0)); - return; - } + if (!isIteratorType(Call.getResultType())) + return; - if (Call.getNumArgs() == 2) { - handleErase(C, Call.getArgSVal(0), Call.getArgSVal(1)); - return; - } - } + auto State = C.getState(); - if (isEraseAfterCall(Func)) { - if (Call.getNumArgs() == 1) { - handleEraseAfter(C, Call.getArgSVal(0)); - return; - } + // Already bound to container? + if (getIteratorPosition(State, Call.getReturnValue())) + return; - if (Call.getNumArgs() == 2) { - handleEraseAfter(C, Call.getArgSVal(0), Call.getArgSVal(1)); - return; - } + // Copy-like and move constructors + if (isa<CXXConstructorCall>(&Call) && Call.getNumArgs() == 1) { + if (const auto *Pos = getIteratorPosition(State, Call.getArgSVal(0))) { + State = setIteratorPosition(State, Call.getReturnValue(), *Pos); + if (cast<CXXConstructorDecl>(Func)->isMoveConstructor()) { + State = removeIteratorPosition(State, Call.getArgSVal(0)); } - } - - const auto *OrigExpr = Call.getOriginExpr(); - if (!OrigExpr) - return; - - if (!isIteratorType(Call.getResultType())) + C.addTransition(State); return; - - auto State = C.getState(); - - 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()); - return; - } } + } - // Already bound to container? - if (getIteratorPosition(State, Call.getReturnValue())) - return; - - // Copy-like and move constructors - if (isa<CXXConstructorCall>(&Call) && Call.getNumArgs() == 1) { - if (const auto *Pos = getIteratorPosition(State, Call.getArgSVal(0))) { - State = setIteratorPosition(State, Call.getReturnValue(), *Pos); - if (cast<CXXConstructorDecl>(Func)->isMoveConstructor()) { - State = removeIteratorPosition(State, Call.getArgSVal(0)); - } - C.addTransition(State); + // Assumption: if return value is an iterator which is not yet bound to a + // container, then look for the first iterator argument of the + // same type as the return value and bind the return value to + // the same container. This approach works for STL algorithms. + // FIXME: Add a more conservative mode + for (unsigned i = 0; i < Call.getNumArgs(); ++i) { + if (isIteratorType(Call.getArgExpr(i)->getType()) && + Call.getArgExpr(i)->getType().getNonReferenceType().getDesugaredType( + C.getASTContext()).getTypePtr() == + Call.getResultType().getDesugaredType(C.getASTContext()).getTypePtr()) { + if (const auto *Pos = getIteratorPosition(State, Call.getArgSVal(i))) { + assignToContainer(C, OrigExpr, Call.getReturnValue(), + Pos->getContainer()); return; } } - - // Assumption: if return value is an iterator which is not yet bound to a - // container, then look for the first iterator argument, and - // bind the return value to the same container. This approach - // works for STL algorithms. - // FIXME: Add a more conservative mode - for (unsigned i = 0; i < Call.getNumArgs(); ++i) { - if (isIteratorType(Call.getArgExpr(i)->getType())) { - if (const auto *Pos = getIteratorPosition(State, Call.getArgSVal(i))) { - assignToContainer(C, OrigExpr, Call.getReturnValue(), - Pos->getContainer()); - return; - } - } - } } } @@ -413,6 +248,35 @@ void IteratorModeling::checkBind(SVal Loc, SVal Val, const Stmt *S, } } +void IteratorModeling::checkPostStmt(const UnaryOperator *UO, + CheckerContext &C) const { + UnaryOperatorKind OK = UO->getOpcode(); + if (!isIncrementOperator(OK) && !isDecrementOperator(OK)) + return; + + auto &SVB = C.getSValBuilder(); + handlePtrIncrOrDecr(C, UO->getSubExpr(), + isIncrementOperator(OK) ? OO_Plus : OO_Minus, + SVB.makeArrayIndex(1)); +} + +void IteratorModeling::checkPostStmt(const BinaryOperator *BO, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + BinaryOperatorKind OK = BO->getOpcode(); + SVal RVal = State->getSVal(BO->getRHS(), C.getLocationContext()); + + if (isSimpleComparisonOperator(BO->getOpcode())) { + SVal LVal = State->getSVal(BO->getLHS(), C.getLocationContext()); + SVal Result = State->getSVal(BO, C.getLocationContext()); + handleComparison(C, BO, Result, LVal, RVal, + BinaryOperator::getOverloadedOperator(OK)); + } else if (isRandomIncrOrDecrOperator(OK)) { + handlePtrIncrOrDecr(C, BO->getLHS(), + BinaryOperator::getOverloadedOperator(OK), RVal); + } +} + void IteratorModeling::checkPostStmt(const MaterializeTemporaryExpr *MTE, CheckerContext &C) const { /* Transfer iterator state to temporary objects */ @@ -426,8 +290,7 @@ void IteratorModeling::checkPostStmt(const MaterializeTemporaryExpr *MTE, void IteratorModeling::checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const { - // Keep symbolic expressions of iterator positions, container begins and ends - // alive + // Keep symbolic expressions of iterator positions alive auto RegionMap = State->get<IteratorRegionMap>(); for (const auto &Reg : RegionMap) { const auto Offset = Reg.second.getOffset(); @@ -444,20 +307,6 @@ void IteratorModeling::checkLiveSymbols(ProgramStateRef State, 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(const auto *SIE = dyn_cast<SymIntExpr>(CData.getBegin())) - SR.markLive(SIE->getLHS()); - } - if (CData.getEnd()) { - SR.markLive(CData.getEnd()); - if(const auto *SIE = dyn_cast<SymIntExpr>(CData.getEnd())) - SR.markLive(SIE->getLHS()); - } - } } void IteratorModeling::checkDeadSymbols(SymbolReaper &SR, @@ -484,18 +333,92 @@ void IteratorModeling::checkDeadSymbols(SymbolReaper &SR, } } - auto ContMap = State->get<ContainerMap>(); - for (const auto &Cont : ContMap) { - if (!SR.isLiveRegion(Cont.first)) { - // We must keep the container data while it has live iterators to be able - // to compare them to the begin and the end of the container. - if (!hasLiveIterators(State, Cont.first)) { - State = State->remove<ContainerMap>(Cont.first); + C.addTransition(State); +} + +void +IteratorModeling::handleOverloadedOperator(CheckerContext &C, + const CallEvent &Call, + OverloadedOperatorKind Op) const { + if (isSimpleComparisonOperator(Op)) { + const auto *OrigExpr = Call.getOriginExpr(); + if (!OrigExpr) + return; + + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + handleComparison(C, OrigExpr, Call.getReturnValue(), + InstCall->getCXXThisVal(), Call.getArgSVal(0), Op); + return; } + + handleComparison(C, OrigExpr, Call.getReturnValue(), Call.getArgSVal(0), + Call.getArgSVal(1), Op); + return; + } else if (isRandomIncrOrDecrOperator(Op)) { + const auto *OrigExpr = Call.getOriginExpr(); + if (!OrigExpr) + return; + + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + if (Call.getNumArgs() >= 1 && + Call.getArgExpr(0)->getType()->isIntegralOrEnumerationType()) { + handleRandomIncrOrDecr(C, OrigExpr, Op, Call.getReturnValue(), + InstCall->getCXXThisVal(), Call.getArgSVal(0)); + return; + } + } else { + if (Call.getNumArgs() >= 2 && + Call.getArgExpr(1)->getType()->isIntegralOrEnumerationType()) { + handleRandomIncrOrDecr(C, OrigExpr, Op, Call.getReturnValue(), + Call.getArgSVal(0), Call.getArgSVal(1)); + return; + } + } + } else if (isIncrementOperator(Op)) { + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + handleIncrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(), + Call.getNumArgs()); + return; + } + + handleIncrement(C, Call.getReturnValue(), Call.getArgSVal(0), + Call.getNumArgs()); + return; + } else if (isDecrementOperator(Op)) { + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + handleDecrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(), + Call.getNumArgs()); + return; + } + + handleDecrement(C, Call.getReturnValue(), Call.getArgSVal(0), + Call.getNumArgs()); + return; } +} + +void +IteratorModeling::handleAdvanceLikeFunction(CheckerContext &C, + const CallEvent &Call, + const Expr *OrigExpr, + const AdvanceFn *Handler) const { + if (!C.wasInlined) { + (this->**Handler)(C, OrigExpr, Call.getReturnValue(), + Call.getArgSVal(0), Call.getArgSVal(1)); + return; } - C.addTransition(State); + // If std::advance() was inlined, but a non-standard function it calls inside + // was not, then we have to model it explicitly + const auto *IdInfo = cast<FunctionDecl>(Call.getDecl())->getIdentifier(); + if (IdInfo) { + if (IdInfo->getName() == "advance") { + if (noChangeInAdvance(C, Call.getArgSVal(0), OrigExpr)) { + (this->**Handler)(C, OrigExpr, Call.getReturnValue(), + Call.getArgSVal(0), Call.getArgSVal(1)); + } + } + } } void IteratorModeling::handleComparison(CheckerContext &C, const Expr *CE, @@ -518,7 +441,7 @@ void IteratorModeling::handleComparison(CheckerContext &C, const Expr *CE, if (!Cont) return; - // At least one of the iterators have recorded positions. If one of them has + // At least one of the iterators has recorded positions. If one of them does // not then create a new symbol for the offset. SymbolRef Sym; if (!LPos || !RPos) { @@ -538,7 +461,7 @@ void IteratorModeling::handleComparison(CheckerContext &C, const Expr *CE, RPos = getIteratorPosition(State, RVal); } - // We cannot make assumpotions on `UnknownVal`. Let us conjure a symbol + // We cannot make assumptions on `UnknownVal`. Let us conjure a symbol // instead. if (RetVal.isUnknown()) { auto &SymMgr = C.getSymbolManager(); @@ -574,7 +497,7 @@ void IteratorModeling::processComparison(CheckerContext &C, StateTrue = StateTrue->assume(*ConditionVal, true); C.addTransition(StateTrue); } - + if (auto StateFalse = relateSymbols(State, Sym1, Sym2, Op != OO_EqualEqual)) { StateFalse = StateFalse->assume(*ConditionVal, false); C.addTransition(StateFalse); @@ -648,481 +571,139 @@ void IteratorModeling::handleRandomIncrOrDecr(CheckerContext &C, return; const auto *value = &RHS; + SVal val; if (auto loc = RHS.getAs<Loc>()) { - const auto val = State->getRawSVal(*loc); + val = State->getRawSVal(*loc); value = &val; } auto &TgtVal = (Op == OO_PlusEqual || Op == OO_MinusEqual) ? LHS : RetVal; - auto NewState = - advancePosition(State, LHS, Op, *value); - if (NewState) { - const auto *NewPos = getIteratorPosition(NewState, LHS); + // `AdvancedState` is a state where the position of `LHS` is advanced. We + // only need this state to retrieve the new position, but we do not want + // to change the position of `LHS` (in every case). + auto AdvancedState = advancePosition(State, LHS, Op, *value); + if (AdvancedState) { + const auto *NewPos = getIteratorPosition(AdvancedState, LHS); assert(NewPos && "Iterator should have position after successful advancement"); - State = setIteratorPosition(NewState, TgtVal, *NewPos); + State = setIteratorPosition(State, TgtVal, *NewPos); C.addTransition(State); } else { assignToContainer(C, CE, TgtVal, Pos->getContainer()); } } -void IteratorModeling::handleBegin(CheckerContext &C, const Expr *CE, - const SVal &RetVal, const SVal &Cont) const { - const auto *ContReg = Cont.getAsRegion(); - if (!ContReg) - return; - - ContReg = ContReg->getMostDerivedObjectRegion(); - - // 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) { - State = createContainerBegin(State, ContReg, CE, C.getASTContext().LongTy, - C.getLocationContext(), C.blockCount()); - BeginSym = getContainerBegin(State, ContReg); - } - State = setIteratorPosition(State, RetVal, - IteratorPosition::getPosition(ContReg, BeginSym)); - C.addTransition(State); -} - -void IteratorModeling::handleEnd(CheckerContext &C, const Expr *CE, - const SVal &RetVal, const SVal &Cont) const { - const auto *ContReg = Cont.getAsRegion(); - if (!ContReg) - return; - - ContReg = ContReg->getMostDerivedObjectRegion(); - - // If the container already has an end symbol then use it. Otherwise first - // create a new one. - auto State = C.getState(); - auto EndSym = getContainerEnd(State, ContReg); - if (!EndSym) { - State = createContainerEnd(State, ContReg, CE, C.getASTContext().LongTy, - C.getLocationContext(), C.blockCount()); - EndSym = getContainerEnd(State, ContReg); - } - State = setIteratorPosition(State, RetVal, - IteratorPosition::getPosition(ContReg, EndSym)); - C.addTransition(State); -} - -void IteratorModeling::assignToContainer(CheckerContext &C, const Expr *CE, - const SVal &RetVal, - const MemRegion *Cont) const { - Cont = Cont->getMostDerivedObjectRegion(); - - auto State = C.getState(); - 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); -} - -void IteratorModeling::handleAssign(CheckerContext &C, const SVal &Cont, - const Expr *CE, const SVal &OldCont) const { - const auto *ContReg = Cont.getAsRegion(); - if (!ContReg) - return; - - ContReg = ContReg->getMostDerivedObjectRegion(); - - // Assignment of a new value to a container always invalidates all its - // iterators - auto State = C.getState(); - const auto CData = getContainerData(State, ContReg); - if (CData) { - State = invalidateAllIteratorPositions(State, ContReg); - } - - // In case of move, iterators of the old container (except the past-end - // iterators) remain valid but refer to the new container - if (!OldCont.isUndef()) { - const auto *OldContReg = OldCont.getAsRegion(); - if (OldContReg) { - OldContReg = OldContReg->getMostDerivedObjectRegion(); - const auto OldCData = getContainerData(State, OldContReg); - if (OldCData) { - if (const auto OldEndSym = OldCData->getEnd()) { - // If we already assigned an "end" symbol to the old container, then - // first reassign all iterator positions to the new container which - // are not past the container (thus not greater or equal to the - // current "end" symbol). - State = reassignAllIteratorPositionsUnless(State, OldContReg, ContReg, - OldEndSym, BO_GE); - auto &SymMgr = C.getSymbolManager(); - auto &SVB = C.getSValBuilder(); - // Then generate and assign a new "end" symbol for the new container. - auto NewEndSym = - SymMgr.conjureSymbol(CE, C.getLocationContext(), - C.getASTContext().LongTy, C.blockCount()); - State = assumeNoOverflow(State, NewEndSym, 4); - if (CData) { - State = setContainerData(State, ContReg, CData->newEnd(NewEndSym)); - } else { - State = setContainerData(State, ContReg, - ContainerData::fromEnd(NewEndSym)); - } - // Finally, replace the old "end" symbol in the already reassigned - // iterator positions with the new "end" symbol. - State = rebaseSymbolInIteratorPositionsIf( - State, SVB, OldEndSym, NewEndSym, OldEndSym, BO_LT); - } else { - // There was no "end" symbol assigned yet to the old container, - // so reassign all iterator positions to the new container. - State = reassignAllIteratorPositions(State, OldContReg, ContReg); - } - if (const auto OldBeginSym = OldCData->getBegin()) { - // If we already assigned a "begin" symbol to the old container, then - // assign it to the new container and remove it from the old one. - if (CData) { - State = - setContainerData(State, ContReg, CData->newBegin(OldBeginSym)); - } else { - State = setContainerData(State, ContReg, - ContainerData::fromBegin(OldBeginSym)); - } - State = - setContainerData(State, OldContReg, OldCData->newEnd(nullptr)); - } - } else { - // There was neither "begin" nor "end" symbol assigned yet to the old - // container, so reassign all iterator positions to the new container. - State = reassignAllIteratorPositions(State, OldContReg, ContReg); - } - } - } - C.addTransition(State); -} - -void IteratorModeling::handleClear(CheckerContext &C, const SVal &Cont) const { - const auto *ContReg = Cont.getAsRegion(); - if (!ContReg) - return; - - ContReg = ContReg->getMostDerivedObjectRegion(); - - // The clear() operation invalidates all the iterators, except the past-end - // iterators of list-like containers - auto State = C.getState(); - if (!hasSubscriptOperator(State, ContReg) || - !backModifiable(State, ContReg)) { - const auto CData = getContainerData(State, ContReg); - if (CData) { - if (const auto EndSym = CData->getEnd()) { - State = - invalidateAllIteratorPositionsExcept(State, ContReg, EndSym, BO_GE); - C.addTransition(State); - return; - } - } - } - State = invalidateAllIteratorPositions(State, ContReg); - C.addTransition(State); -} - -void IteratorModeling::handlePushBack(CheckerContext &C, - const SVal &Cont) const { - const auto *ContReg = Cont.getAsRegion(); - if (!ContReg) +void IteratorModeling::handlePtrIncrOrDecr(CheckerContext &C, + const Expr *Iterator, + OverloadedOperatorKind OK, + SVal Offset) const { + QualType PtrType = Iterator->getType(); + if (!PtrType->isPointerType()) return; + QualType ElementType = PtrType->getPointeeType(); - ContReg = ContReg->getMostDerivedObjectRegion(); + ProgramStateRef State = C.getState(); + SVal OldVal = State->getSVal(Iterator, C.getLocationContext()); - // For deque-like containers invalidate all iterator positions - auto State = C.getState(); - if (hasSubscriptOperator(State, ContReg) && frontModifiable(State, ContReg)) { - State = invalidateAllIteratorPositions(State, ContReg); - C.addTransition(State); + const IteratorPosition *OldPos = getIteratorPosition(State, OldVal); + if (!OldPos) return; - } - const auto CData = getContainerData(State, ContReg); - if (!CData) - return; + SVal NewVal; + if (OK == OO_Plus || OK == OO_PlusEqual) + NewVal = State->getLValue(ElementType, Offset, OldVal); + else { + const llvm::APSInt &OffsetInt = + Offset.castAs<nonloc::ConcreteInt>().getValue(); + auto &BVF = C.getSymbolManager().getBasicVals(); + SVal NegatedOffset = nonloc::ConcreteInt(BVF.getValue(-OffsetInt)); + NewVal = State->getLValue(ElementType, NegatedOffset, OldVal); + } + + // `AdvancedState` is a state where the position of `Old` is advanced. We + // only need this state to retrieve the new position, but we do not want + // ever to change the position of `OldVal`. + auto AdvancedState = advancePosition(State, OldVal, OK, Offset); + if (AdvancedState) { + const IteratorPosition *NewPos = getIteratorPosition(AdvancedState, OldVal); + assert(NewPos && + "Iterator should have position after successful advancement"); - // For vector-like containers invalidate the past-end iterator positions - if (const auto EndSym = CData->getEnd()) { - if (hasSubscriptOperator(State, ContReg)) { - State = invalidateIteratorPositions(State, EndSym, BO_GE); - } - auto &SymMgr = C.getSymbolManager(); - auto &BVF = SymMgr.getBasicVals(); - auto &SVB = C.getSValBuilder(); - const auto newEndSym = - SVB.evalBinOp(State, BO_Add, - nonloc::SymbolVal(EndSym), - nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), - SymMgr.getType(EndSym)).getAsSymbol(); - State = setContainerData(State, ContReg, CData->newEnd(newEndSym)); + ProgramStateRef NewState = setIteratorPosition(State, NewVal, *NewPos); + C.addTransition(NewState); + } else { + assignToContainer(C, Iterator, NewVal, OldPos->getContainer()); } - C.addTransition(State); } -void IteratorModeling::handlePopBack(CheckerContext &C, - const SVal &Cont) const { - const auto *ContReg = Cont.getAsRegion(); - if (!ContReg) - return; - - ContReg = ContReg->getMostDerivedObjectRegion(); - - auto State = C.getState(); - const auto CData = getContainerData(State, ContReg); - if (!CData) - return; - - if (const auto EndSym = CData->getEnd()) { - auto &SymMgr = C.getSymbolManager(); - auto &BVF = SymMgr.getBasicVals(); - auto &SVB = C.getSValBuilder(); - const auto BackSym = - SVB.evalBinOp(State, BO_Sub, - nonloc::SymbolVal(EndSym), - nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), - SymMgr.getType(EndSym)).getAsSymbol(); - // For vector-like and deque-like containers invalidate the last and the - // past-end iterator positions. For list-like containers only invalidate - // the last position - if (hasSubscriptOperator(State, ContReg) && - backModifiable(State, ContReg)) { - State = invalidateIteratorPositions(State, BackSym, BO_GE); - State = setContainerData(State, ContReg, CData->newEnd(nullptr)); - } else { - State = invalidateIteratorPositions(State, BackSym, BO_EQ); - } - auto newEndSym = BackSym; - State = setContainerData(State, ContReg, CData->newEnd(newEndSym)); - C.addTransition(State); - } +void IteratorModeling::handleAdvance(CheckerContext &C, const Expr *CE, + SVal RetVal, SVal Iter, + SVal Amount) const { + handleRandomIncrOrDecr(C, CE, OO_PlusEqual, RetVal, Iter, Amount); } -void IteratorModeling::handlePushFront(CheckerContext &C, - const SVal &Cont) const { - const auto *ContReg = Cont.getAsRegion(); - if (!ContReg) - return; - - ContReg = ContReg->getMostDerivedObjectRegion(); - - // For deque-like containers invalidate all iterator positions - auto State = C.getState(); - if (hasSubscriptOperator(State, ContReg)) { - State = invalidateAllIteratorPositions(State, ContReg); - C.addTransition(State); - } else { - const auto CData = getContainerData(State, ContReg); - if (!CData) - return; - - if (const auto BeginSym = CData->getBegin()) { - auto &SymMgr = C.getSymbolManager(); - auto &BVF = SymMgr.getBasicVals(); - auto &SVB = C.getSValBuilder(); - const auto newBeginSym = - SVB.evalBinOp(State, BO_Sub, - nonloc::SymbolVal(BeginSym), - nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), - SymMgr.getType(BeginSym)).getAsSymbol(); - State = setContainerData(State, ContReg, CData->newBegin(newBeginSym)); - C.addTransition(State); - } - } +void IteratorModeling::handlePrev(CheckerContext &C, const Expr *CE, + SVal RetVal, SVal Iter, SVal Amount) const { + handleRandomIncrOrDecr(C, CE, OO_Minus, RetVal, Iter, Amount); } -void IteratorModeling::handlePopFront(CheckerContext &C, - const SVal &Cont) const { - const auto *ContReg = Cont.getAsRegion(); - if (!ContReg) - return; - - ContReg = ContReg->getMostDerivedObjectRegion(); - - auto State = C.getState(); - const auto CData = getContainerData(State, ContReg); - if (!CData) - return; - - // For deque-like containers invalidate all iterator positions. For list-like - // iterators only invalidate the first position - if (const auto BeginSym = CData->getBegin()) { - if (hasSubscriptOperator(State, ContReg)) { - State = invalidateIteratorPositions(State, BeginSym, BO_LE); - } else { - State = invalidateIteratorPositions(State, BeginSym, BO_EQ); - } - auto &SymMgr = C.getSymbolManager(); - auto &BVF = SymMgr.getBasicVals(); - auto &SVB = C.getSValBuilder(); - const auto newBeginSym = - SVB.evalBinOp(State, BO_Add, - nonloc::SymbolVal(BeginSym), - nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), - SymMgr.getType(BeginSym)).getAsSymbol(); - State = setContainerData(State, ContReg, CData->newBegin(newBeginSym)); - C.addTransition(State); - } +void IteratorModeling::handleNext(CheckerContext &C, const Expr *CE, + SVal RetVal, SVal Iter, SVal Amount) const { + handleRandomIncrOrDecr(C, CE, OO_Plus, RetVal, Iter, Amount); } -void IteratorModeling::handleInsert(CheckerContext &C, const SVal &Iter) const { - auto State = C.getState(); - const auto *Pos = getIteratorPosition(State, Iter); - if (!Pos) - return; - - // For deque-like containers invalidate all iterator positions. For - // vector-like containers invalidate iterator positions after the insertion. - const auto *Cont = Pos->getContainer(); - if (hasSubscriptOperator(State, Cont) && backModifiable(State, Cont)) { - if (frontModifiable(State, Cont)) { - State = invalidateAllIteratorPositions(State, Cont); - } else { - State = invalidateIteratorPositions(State, Pos->getOffset(), BO_GE); - } - if (const auto *CData = getContainerData(State, Cont)) { - if (const auto EndSym = CData->getEnd()) { - State = invalidateIteratorPositions(State, EndSym, BO_GE); - State = setContainerData(State, Cont, CData->newEnd(nullptr)); - } - } - C.addTransition(State); - } -} +void IteratorModeling::assignToContainer(CheckerContext &C, const Expr *CE, + const SVal &RetVal, + const MemRegion *Cont) const { + Cont = Cont->getMostDerivedObjectRegion(); -void IteratorModeling::handleErase(CheckerContext &C, const SVal &Iter) const { auto State = C.getState(); - const auto *Pos = getIteratorPosition(State, Iter); - if (!Pos) - return; + const auto *LCtx = C.getLocationContext(); + State = createIteratorPosition(State, RetVal, Cont, CE, LCtx, C.blockCount()); - // For deque-like containers invalidate all iterator positions. For - // vector-like containers invalidate iterator positions at and after the - // deletion. For list-like containers only invalidate the deleted position. - const auto *Cont = Pos->getContainer(); - if (hasSubscriptOperator(State, Cont) && backModifiable(State, Cont)) { - if (frontModifiable(State, Cont)) { - State = invalidateAllIteratorPositions(State, Cont); - } else { - State = invalidateIteratorPositions(State, Pos->getOffset(), BO_GE); - } - if (const auto *CData = getContainerData(State, Cont)) { - if (const auto EndSym = CData->getEnd()) { - State = invalidateIteratorPositions(State, EndSym, BO_GE); - State = setContainerData(State, Cont, CData->newEnd(nullptr)); - } - } - } else { - State = invalidateIteratorPositions(State, Pos->getOffset(), BO_EQ); - } C.addTransition(State); } -void IteratorModeling::handleErase(CheckerContext &C, const SVal &Iter1, - const SVal &Iter2) const { - auto State = C.getState(); - const auto *Pos1 = getIteratorPosition(State, Iter1); - const auto *Pos2 = getIteratorPosition(State, Iter2); - if (!Pos1 || !Pos2) - return; +bool IteratorModeling::noChangeInAdvance(CheckerContext &C, SVal Iter, + const Expr *CE) const { + // Compare the iterator position before and after the call. (To be called + // from `checkPostCall()`.) + const auto StateAfter = C.getState(); - // For deque-like containers invalidate all iterator positions. For - // vector-like containers invalidate iterator positions at and after the - // deletion range. For list-like containers only invalidate the deleted - // position range [first..last]. - const auto *Cont = Pos1->getContainer(); - if (hasSubscriptOperator(State, Cont) && backModifiable(State, Cont)) { - if (frontModifiable(State, Cont)) { - State = invalidateAllIteratorPositions(State, Cont); - } else { - State = invalidateIteratorPositions(State, Pos1->getOffset(), BO_GE); - } - if (const auto *CData = getContainerData(State, Cont)) { - if (const auto EndSym = CData->getEnd()) { - State = invalidateIteratorPositions(State, EndSym, BO_GE); - State = setContainerData(State, Cont, CData->newEnd(nullptr)); - } - } - } else { - State = invalidateIteratorPositions(State, Pos1->getOffset(), BO_GE, - Pos2->getOffset(), BO_LT); - } - C.addTransition(State); -} + const auto *PosAfter = getIteratorPosition(StateAfter, Iter); + // If we have no position after the call of `std::advance`, then we are not + // interested. (Modeling of an inlined `std::advance()` should not remove the + // position in any case.) + if (!PosAfter) + return false; -void IteratorModeling::handleEraseAfter(CheckerContext &C, - const SVal &Iter) const { - auto State = C.getState(); - const auto *Pos = getIteratorPosition(State, Iter); - if (!Pos) - return; + const ExplodedNode *N = findCallEnter(C.getPredecessor(), CE); + assert(N && "Any call should have a `CallEnter` node."); - // Invalidate the deleted iterator position, which is the position of the - // parameter plus one. - auto &SymMgr = C.getSymbolManager(); - auto &BVF = SymMgr.getBasicVals(); - auto &SVB = C.getSValBuilder(); - const auto NextSym = - SVB.evalBinOp(State, BO_Add, - nonloc::SymbolVal(Pos->getOffset()), - nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), - SymMgr.getType(Pos->getOffset())).getAsSymbol(); - State = invalidateIteratorPositions(State, NextSym, BO_EQ); - C.addTransition(State); -} + const auto StateBefore = N->getState(); + const auto *PosBefore = getIteratorPosition(StateBefore, Iter); -void IteratorModeling::handleEraseAfter(CheckerContext &C, const SVal &Iter1, - const SVal &Iter2) const { - auto State = C.getState(); - const auto *Pos1 = getIteratorPosition(State, Iter1); - const auto *Pos2 = getIteratorPosition(State, Iter2); - if (!Pos1 || !Pos2) - return; + assert(PosBefore && "`std::advance() should not create new iterator " + "position but change existing ones"); - // Invalidate the deleted iterator position range (first..last) - State = invalidateIteratorPositions(State, Pos1->getOffset(), BO_GT, - Pos2->getOffset(), BO_LT); - C.addTransition(State); + return PosBefore->getOffset() == PosAfter->getOffset(); } void IteratorModeling::printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const { - - auto ContMap = State->get<ContainerMap>(); - - if (!ContMap.isEmpty()) { - Out << Sep << "Container Data :" << NL; - for (const auto &Cont : ContMap) { - Cont.first->dumpToStream(Out); - Out << " : [ "; - const auto CData = Cont.second; - if (CData.getBegin()) - CData.getBegin()->dumpToStream(Out); - else - Out << "<Unknown>"; - Out << " .. "; - if (CData.getEnd()) - CData.getEnd()->dumpToStream(Out); - else - Out << "<Unknown>"; - Out << " ]" << NL; - } - } - auto SymbolMap = State->get<IteratorSymbolMap>(); auto RegionMap = State->get<IteratorRegionMap>(); + // Use a counter to add newlines before every line except the first one. + unsigned Count = 0; if (!SymbolMap.isEmpty() || !RegionMap.isEmpty()) { Out << Sep << "Iterator Positions :" << NL; for (const auto &Sym : SymbolMap) { + if (Count++) + Out << NL; + Sym.first->dumpToStream(Out); Out << " : "; const auto Pos = Sym.second; @@ -1133,6 +714,9 @@ void IteratorModeling::printState(raw_ostream &Out, ProgramStateRef State, } for (const auto &Reg : RegionMap) { + if (Count++) + Out << NL; + Reg.first->dumpToStream(Out); Out << " : "; const auto Pos = Reg.second; @@ -1144,229 +728,14 @@ void IteratorModeling::printState(raw_ostream &Out, ProgramStateRef State, } } - namespace { -const CXXRecordDecl *getCXXRecordDecl(ProgramStateRef State, - const MemRegion *Reg); - -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) - return false; - return IdInfo->getName().endswith_lower("end"); -} - -bool isAssignCall(const FunctionDecl *Func) { - const auto *IdInfo = Func->getIdentifier(); - if (!IdInfo) - return false; - if (Func->getNumParams() > 2) - return false; - return IdInfo->getName() == "assign"; -} - -bool isClearCall(const FunctionDecl *Func) { - const auto *IdInfo = Func->getIdentifier(); - if (!IdInfo) - return false; - if (Func->getNumParams() > 0) - return false; - return IdInfo->getName() == "clear"; -} - -bool isPushBackCall(const FunctionDecl *Func) { - const auto *IdInfo = Func->getIdentifier(); - if (!IdInfo) - return false; - if (Func->getNumParams() != 1) - return false; - return IdInfo->getName() == "push_back"; -} - -bool isEmplaceBackCall(const FunctionDecl *Func) { - const auto *IdInfo = Func->getIdentifier(); - if (!IdInfo) - return false; - if (Func->getNumParams() < 1) - return false; - return IdInfo->getName() == "emplace_back"; -} - -bool isPopBackCall(const FunctionDecl *Func) { - const auto *IdInfo = Func->getIdentifier(); - if (!IdInfo) - return false; - if (Func->getNumParams() > 0) - return false; - return IdInfo->getName() == "pop_back"; -} - -bool isPushFrontCall(const FunctionDecl *Func) { - const auto *IdInfo = Func->getIdentifier(); - if (!IdInfo) - return false; - if (Func->getNumParams() != 1) - return false; - return IdInfo->getName() == "push_front"; -} - -bool isEmplaceFrontCall(const FunctionDecl *Func) { - const auto *IdInfo = Func->getIdentifier(); - if (!IdInfo) - return false; - if (Func->getNumParams() < 1) - return false; - return IdInfo->getName() == "emplace_front"; -} - -bool isPopFrontCall(const FunctionDecl *Func) { - const auto *IdInfo = Func->getIdentifier(); - if (!IdInfo) - return false; - if (Func->getNumParams() > 0) - return false; - return IdInfo->getName() == "pop_front"; -} - -bool isAssignmentOperator(OverloadedOperatorKind OK) { return OK == OO_Equal; } - bool isSimpleComparisonOperator(OverloadedOperatorKind OK) { return OK == OO_EqualEqual || OK == OO_ExclaimEqual; } -bool hasSubscriptOperator(ProgramStateRef State, const MemRegion *Reg) { - const auto *CRD = getCXXRecordDecl(State, Reg); - if (!CRD) - return false; - - for (const auto *Method : CRD->methods()) { - if (!Method->isOverloadedOperator()) - continue; - const auto OPK = Method->getOverloadedOperator(); - if (OPK == OO_Subscript) { - return true; - } - } - return false; -} - -bool frontModifiable(ProgramStateRef State, const MemRegion *Reg) { - const auto *CRD = getCXXRecordDecl(State, Reg); - if (!CRD) - return false; - - for (const auto *Method : CRD->methods()) { - if (!Method->getDeclName().isIdentifier()) - continue; - if (Method->getName() == "push_front" || Method->getName() == "pop_front") { - return true; - } - } - return false; -} - -bool backModifiable(ProgramStateRef State, const MemRegion *Reg) { - const auto *CRD = getCXXRecordDecl(State, Reg); - if (!CRD) - return false; - - for (const auto *Method : CRD->methods()) { - if (!Method->getDeclName().isIdentifier()) - continue; - if (Method->getName() == "push_back" || Method->getName() == "pop_back") { - return true; - } - } - return false; -} - -const CXXRecordDecl *getCXXRecordDecl(ProgramStateRef State, - const MemRegion *Reg) { - auto TI = getDynamicTypeInfo(State, Reg); - if (!TI.isValid()) - return nullptr; - - auto Type = TI.getType(); - if (const auto *RefT = Type->getAs<ReferenceType>()) { - Type = RefT->getPointeeType(); - } - - return Type->getUnqualifiedDesugaredType()->getAsCXXRecordDecl(); -} - -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) - return nullptr; - - return CDataPtr->getEnd(); -} - -ProgramStateRef createContainerBegin(ProgramStateRef State, - const MemRegion *Cont, const Expr *E, - QualType T, const LocationContext *LCtx, - unsigned BlockCount) { - // Only create if it does not exist - const auto *CDataPtr = getContainerData(State, Cont); - if (CDataPtr && CDataPtr->getBegin()) - return State; - - auto &SymMgr = State->getSymbolManager(); - const SymbolConjured *Sym = SymMgr.conjureSymbol(E, LCtx, T, BlockCount, - "begin"); - State = assumeNoOverflow(State, Sym, 4); - - if (CDataPtr) { - 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 Expr *E, QualType T, - const LocationContext *LCtx, - unsigned BlockCount) { - // Only create if it does not exist - const auto *CDataPtr = getContainerData(State, Cont); - if (CDataPtr && CDataPtr->getEnd()) - return State; - - auto &SymMgr = State->getSymbolManager(); - const SymbolConjured *Sym = SymMgr.conjureSymbol(E, LCtx, T, BlockCount, - "end"); - State = assumeNoOverflow(State, Sym, 4); - - if (CDataPtr) { - const auto CData = CDataPtr->newEnd(Sym); - return setContainerData(State, Cont, CData); - } - - const auto CData = ContainerData::fromEnd(Sym); - return setContainerData(State, Cont, CData); -} - -ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont, - const ContainerData &CData) { - return State->set<ContainerMap>(Cont, CData); +bool isSimpleComparisonOperator(BinaryOperatorKind OK) { + return OK == BO_EQ || OK == BO_NE; } ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val) { @@ -1381,47 +750,6 @@ ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val) { return nullptr; } -// 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? -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; -} - ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, bool Equal) { auto &SVB = State->getStateManager().getSValBuilder(); @@ -1454,22 +782,6 @@ ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1, return NewState; } -bool hasLiveIterators(ProgramStateRef State, const MemRegion *Cont) { - auto RegionMap = State->get<IteratorRegionMap>(); - for (const auto &Reg : RegionMap) { - if (Reg.second.getContainer() == Cont) - return true; - } - - auto SymbolMap = State->get<IteratorSymbolMap>(); - for (const auto &Sym : SymbolMap) { - if (Sym.second.getContainer() == Cont) - return true; - } - - return false; -} - bool isBoundThroughLazyCompoundVal(const Environment &Env, const MemRegion *Reg) { for (const auto &Binding : Env) { @@ -1482,150 +794,18 @@ bool isBoundThroughLazyCompoundVal(const Environment &Env, return false; } -template <typename Condition, typename Process> -ProgramStateRef processIteratorPositions(ProgramStateRef State, Condition Cond, - Process Proc) { - auto &RegionMapFactory = State->get_context<IteratorRegionMap>(); - auto RegionMap = State->get<IteratorRegionMap>(); - bool Changed = false; - for (const auto &Reg : RegionMap) { - if (Cond(Reg.second)) { - RegionMap = RegionMapFactory.add(RegionMap, Reg.first, Proc(Reg.second)); - Changed = true; +const ExplodedNode *findCallEnter(const ExplodedNode *Node, const Expr *Call) { + while (Node) { + ProgramPoint PP = Node->getLocation(); + if (auto Enter = PP.getAs<CallEnter>()) { + if (Enter->getCallExpr() == Call) + break; } - } - - if (Changed) - State = State->set<IteratorRegionMap>(RegionMap); - auto &SymbolMapFactory = State->get_context<IteratorSymbolMap>(); - auto SymbolMap = State->get<IteratorSymbolMap>(); - Changed = false; - for (const auto &Sym : SymbolMap) { - if (Cond(Sym.second)) { - SymbolMap = SymbolMapFactory.add(SymbolMap, Sym.first, Proc(Sym.second)); - Changed = true; - } + Node = Node->getFirstPred(); } - if (Changed) - State = State->set<IteratorSymbolMap>(SymbolMap); - - return State; -} - -ProgramStateRef invalidateAllIteratorPositions(ProgramStateRef State, - const MemRegion *Cont) { - auto MatchCont = [&](const IteratorPosition &Pos) { - return Pos.getContainer() == Cont; - }; - auto Invalidate = [&](const IteratorPosition &Pos) { - return Pos.invalidate(); - }; - return processIteratorPositions(State, MatchCont, Invalidate); -} - -ProgramStateRef -invalidateAllIteratorPositionsExcept(ProgramStateRef State, - const MemRegion *Cont, SymbolRef Offset, - BinaryOperator::Opcode Opc) { - auto MatchContAndCompare = [&](const IteratorPosition &Pos) { - return Pos.getContainer() == Cont && - !compare(State, Pos.getOffset(), Offset, Opc); - }; - auto Invalidate = [&](const IteratorPosition &Pos) { - return Pos.invalidate(); - }; - return processIteratorPositions(State, MatchContAndCompare, Invalidate); -} - -ProgramStateRef invalidateIteratorPositions(ProgramStateRef State, - SymbolRef Offset, - BinaryOperator::Opcode Opc) { - auto Compare = [&](const IteratorPosition &Pos) { - return compare(State, Pos.getOffset(), Offset, Opc); - }; - auto Invalidate = [&](const IteratorPosition &Pos) { - return Pos.invalidate(); - }; - return processIteratorPositions(State, Compare, Invalidate); -} - -ProgramStateRef invalidateIteratorPositions(ProgramStateRef State, - SymbolRef Offset1, - BinaryOperator::Opcode Opc1, - SymbolRef Offset2, - BinaryOperator::Opcode Opc2) { - auto Compare = [&](const IteratorPosition &Pos) { - return compare(State, Pos.getOffset(), Offset1, Opc1) && - compare(State, Pos.getOffset(), Offset2, Opc2); - }; - auto Invalidate = [&](const IteratorPosition &Pos) { - return Pos.invalidate(); - }; - return processIteratorPositions(State, Compare, Invalidate); -} - -ProgramStateRef reassignAllIteratorPositions(ProgramStateRef State, - const MemRegion *Cont, - const MemRegion *NewCont) { - auto MatchCont = [&](const IteratorPosition &Pos) { - return Pos.getContainer() == Cont; - }; - auto ReAssign = [&](const IteratorPosition &Pos) { - return Pos.reAssign(NewCont); - }; - return processIteratorPositions(State, MatchCont, ReAssign); -} - -ProgramStateRef reassignAllIteratorPositionsUnless(ProgramStateRef State, - const MemRegion *Cont, - const MemRegion *NewCont, - SymbolRef Offset, - BinaryOperator::Opcode Opc) { - auto MatchContAndCompare = [&](const IteratorPosition &Pos) { - return Pos.getContainer() == Cont && - !compare(State, Pos.getOffset(), Offset, Opc); - }; - auto ReAssign = [&](const IteratorPosition &Pos) { - return Pos.reAssign(NewCont); - }; - return processIteratorPositions(State, MatchContAndCompare, ReAssign); -} - -// This function rebases symbolic expression `OldSym + Int` to `NewSym + Int`, -// `OldSym - Int` to `NewSym - Int` and `OldSym` to `NewSym` in any iterator -// position offsets where `CondSym` is true. -ProgramStateRef rebaseSymbolInIteratorPositionsIf( - ProgramStateRef State, SValBuilder &SVB, SymbolRef OldSym, - SymbolRef NewSym, SymbolRef CondSym, BinaryOperator::Opcode Opc) { - auto LessThanEnd = [&](const IteratorPosition &Pos) { - return compare(State, Pos.getOffset(), CondSym, Opc); - }; - auto RebaseSymbol = [&](const IteratorPosition &Pos) { - return Pos.setTo(rebaseSymbol(State, SVB, Pos.getOffset(), OldSym, - NewSym)); - }; - return processIteratorPositions(State, LessThanEnd, RebaseSymbol); -} - -// This function rebases symbolic expression `OldExpr + Int` to `NewExpr + Int`, -// `OldExpr - Int` to `NewExpr - Int` and `OldExpr` to `NewExpr` in expression -// `OrigExpr`. -SymbolRef rebaseSymbol(ProgramStateRef State, SValBuilder &SVB, - SymbolRef OrigExpr, SymbolRef OldExpr, - SymbolRef NewSym) { - auto &SymMgr = SVB.getSymbolManager(); - auto Diff = SVB.evalBinOpNN(State, BO_Sub, nonloc::SymbolVal(OrigExpr), - nonloc::SymbolVal(OldExpr), - SymMgr.getType(OrigExpr)); - - const auto DiffInt = Diff.getAs<nonloc::ConcreteInt>(); - if (!DiffInt) - return OrigExpr; - - return SVB.evalBinOpNN(State, BO_Add, *DiffInt, nonloc::SymbolVal(NewSym), - SymMgr.getType(OrigExpr)).getAsSymbol(); + return Node; } } // namespace @@ -1634,6 +814,6 @@ void ento::registerIteratorModeling(CheckerManager &mgr) { mgr.registerChecker<IteratorModeling>(); } -bool ento::shouldRegisterIteratorModeling(const LangOptions &LO) { +bool ento::shouldRegisterIteratorModeling(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp index bd8b84d464b6..df8e379d1f20 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp @@ -27,22 +27,41 @@ using namespace iterator; namespace { class IteratorRangeChecker - : public Checker<check::PreCall> { + : public Checker<check::PreCall, check::PreStmt<UnaryOperator>, + check::PreStmt<BinaryOperator>, + check::PreStmt<ArraySubscriptExpr>, + check::PreStmt<MemberExpr>> { std::unique_ptr<BugType> OutOfRangeBugType; - void verifyDereference(CheckerContext &C, const SVal &Val) const; - void verifyIncrement(CheckerContext &C, const SVal &Iter) const; - void verifyDecrement(CheckerContext &C, const SVal &Iter) const; + void verifyDereference(CheckerContext &C, SVal Val) const; + void verifyIncrement(CheckerContext &C, SVal Iter) const; + void verifyDecrement(CheckerContext &C, SVal Iter) const; void verifyRandomIncrOrDecr(CheckerContext &C, OverloadedOperatorKind Op, - const SVal &LHS, const SVal &RHS) const; - void reportBug(const StringRef &Message, const SVal &Val, - CheckerContext &C, ExplodedNode *ErrNode) const; + SVal LHS, SVal RHS) const; + void verifyAdvance(CheckerContext &C, SVal LHS, SVal RHS) const; + void verifyPrev(CheckerContext &C, SVal LHS, SVal RHS) const; + void verifyNext(CheckerContext &C, SVal LHS, SVal RHS) const; + void reportBug(const StringRef &Message, SVal Val, CheckerContext &C, + ExplodedNode *ErrNode) const; + public: IteratorRangeChecker(); void checkPreCall(const CallEvent &Call, CheckerContext &C) const; - + void checkPreStmt(const UnaryOperator *UO, CheckerContext &C) const; + void checkPreStmt(const BinaryOperator *BO, CheckerContext &C) const; + void checkPreStmt(const ArraySubscriptExpr *ASE, CheckerContext &C) const; + void checkPreStmt(const MemberExpr *ME, CheckerContext &C) const; + + using AdvanceFn = void (IteratorRangeChecker::*)(CheckerContext &, SVal, + SVal) const; + + CallDescriptionMap<AdvanceFn> AdvanceFunctions = { + {{{"std", "advance"}, 2}, &IteratorRangeChecker::verifyAdvance}, + {{{"std", "prev"}, 2}, &IteratorRangeChecker::verifyPrev}, + {{{"std", "next"}, 2}, &IteratorRangeChecker::verifyNext}, + }; }; bool isPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos); @@ -107,11 +126,73 @@ void IteratorRangeChecker::checkPreCall(const CallEvent &Call, verifyDereference(C, Call.getArgSVal(0)); } } + } else { + const AdvanceFn *Verifier = AdvanceFunctions.lookup(Call); + if (Verifier) { + if (Call.getNumArgs() > 1) { + (this->**Verifier)(C, Call.getArgSVal(0), Call.getArgSVal(1)); + } else { + auto &BVF = C.getSValBuilder().getBasicValueFactory(); + (this->**Verifier)( + C, Call.getArgSVal(0), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); + } + } + } +} + +void IteratorRangeChecker::checkPreStmt(const UnaryOperator *UO, + CheckerContext &C) const { + if (isa<CXXThisExpr>(UO->getSubExpr())) + return; + + ProgramStateRef State = C.getState(); + UnaryOperatorKind OK = UO->getOpcode(); + SVal SubVal = State->getSVal(UO->getSubExpr(), C.getLocationContext()); + + if (isDereferenceOperator(OK)) { + verifyDereference(C, SubVal); + } else if (isIncrementOperator(OK)) { + verifyIncrement(C, SubVal); + } else if (isDecrementOperator(OK)) { + verifyDecrement(C, SubVal); + } +} + +void IteratorRangeChecker::checkPreStmt(const BinaryOperator *BO, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + BinaryOperatorKind OK = BO->getOpcode(); + SVal LVal = State->getSVal(BO->getLHS(), C.getLocationContext()); + + if (isDereferenceOperator(OK)) { + verifyDereference(C, LVal); + } else if (isRandomIncrOrDecrOperator(OK)) { + SVal RVal = State->getSVal(BO->getRHS(), C.getLocationContext()); + verifyRandomIncrOrDecr(C, BinaryOperator::getOverloadedOperator(OK), LVal, + RVal); } } +void IteratorRangeChecker::checkPreStmt(const ArraySubscriptExpr *ASE, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SVal LVal = State->getSVal(ASE->getLHS(), C.getLocationContext()); + verifyDereference(C, LVal); +} + +void IteratorRangeChecker::checkPreStmt(const MemberExpr *ME, + CheckerContext &C) const { + if (!ME->isArrow() || ME->isImplicitAccess()) + return; + + ProgramStateRef State = C.getState(); + SVal BaseVal = State->getSVal(ME->getBase(), C.getLocationContext()); + verifyDereference(C, BaseVal); +} + void IteratorRangeChecker::verifyDereference(CheckerContext &C, - const SVal &Val) const { + SVal Val) const { auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Val); if (Pos && isPastTheEnd(State, *Pos)) { @@ -123,24 +204,21 @@ void IteratorRangeChecker::verifyDereference(CheckerContext &C, } } -void IteratorRangeChecker::verifyIncrement(CheckerContext &C, - const SVal &Iter) const { +void IteratorRangeChecker::verifyIncrement(CheckerContext &C, SVal Iter) const { auto &BVF = C.getSValBuilder().getBasicValueFactory(); verifyRandomIncrOrDecr(C, OO_Plus, Iter, nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); } -void IteratorRangeChecker::verifyDecrement(CheckerContext &C, - const SVal &Iter) const { +void IteratorRangeChecker::verifyDecrement(CheckerContext &C, SVal Iter) const { auto &BVF = C.getSValBuilder().getBasicValueFactory(); verifyRandomIncrOrDecr(C, OO_Minus, Iter, nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); } void IteratorRangeChecker::verifyRandomIncrOrDecr(CheckerContext &C, - OverloadedOperatorKind Op, - const SVal &LHS, - const SVal &RHS) const { + OverloadedOperatorKind Op, + SVal LHS, SVal RHS) const { auto State = C.getState(); auto Value = RHS; @@ -180,12 +258,32 @@ void IteratorRangeChecker::verifyRandomIncrOrDecr(CheckerContext &C, } } -void IteratorRangeChecker::reportBug(const StringRef &Message, - const SVal &Val, CheckerContext &C, - ExplodedNode *ErrNode) const { +void IteratorRangeChecker::verifyAdvance(CheckerContext &C, SVal LHS, + SVal RHS) const { + verifyRandomIncrOrDecr(C, OO_PlusEqual, LHS, RHS); +} + +void IteratorRangeChecker::verifyPrev(CheckerContext &C, SVal LHS, + SVal RHS) const { + verifyRandomIncrOrDecr(C, OO_Minus, LHS, RHS); +} + +void IteratorRangeChecker::verifyNext(CheckerContext &C, SVal LHS, + SVal RHS) const { + verifyRandomIncrOrDecr(C, OO_Plus, LHS, RHS); +} + +void IteratorRangeChecker::reportBug(const StringRef &Message, SVal Val, + CheckerContext &C, + ExplodedNode *ErrNode) const { auto R = std::make_unique<PathSensitiveBugReport>(*OutOfRangeBugType, Message, ErrNode); + + const auto *Pos = getIteratorPosition(C.getState(), Val); + assert(Pos && "Iterator without known position cannot be out-of-range."); + R->markInteresting(Val); + R->markInteresting(Pos->getContainer()); C.emitReport(std::move(R)); } @@ -268,6 +366,6 @@ void ento::registerIteratorRangeChecker(CheckerManager &mgr) { mgr.registerChecker<IteratorRangeChecker>(); } -bool ento::shouldRegisterIteratorRangeChecker(const LangOptions &LO) { +bool ento::shouldRegisterIteratorRangeChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp index 0d64fbd6f62e..3e6756efe0e6 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp @@ -739,7 +739,7 @@ void ento::registerIvarInvalidationModeling(CheckerManager &mgr) { mgr.registerChecker<IvarInvalidationChecker>(); } -bool ento::shouldRegisterIvarInvalidationModeling(const LangOptions &LO) { +bool ento::shouldRegisterIvarInvalidationModeling(const CheckerManager &mgr) { return true; } @@ -751,7 +751,7 @@ bool ento::shouldRegisterIvarInvalidationModeling(const LangOptions &LO) { checker->Filter.checkName_##name = mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name(const LangOptions &LO) { return true; } + bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; } REGISTER_CHECKER(InstanceVariableInvalidation) REGISTER_CHECKER(MissingInvalidationMethod) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp index 7522fdd0a99b..1f3d8844d330 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp @@ -314,6 +314,6 @@ void ento::registerLLVMConventionsChecker(CheckerManager &mgr) { mgr.registerChecker<LLVMConventionsChecker>(); } -bool ento::shouldRegisterLLVMConventionsChecker(const LangOptions &LO) { +bool ento::shouldRegisterLLVMConventionsChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp index 79de1844e745..252377f24bd7 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp @@ -1403,7 +1403,7 @@ void ento::registerNonLocalizedStringChecker(CheckerManager &mgr) { checker, "AggressiveReport"); } -bool ento::shouldRegisterNonLocalizedStringChecker(const LangOptions &LO) { +bool ento::shouldRegisterNonLocalizedStringChecker(const CheckerManager &mgr) { return true; } @@ -1412,7 +1412,7 @@ void ento::registerEmptyLocalizationContextChecker(CheckerManager &mgr) { } bool ento::shouldRegisterEmptyLocalizationContextChecker( - const LangOptions &LO) { + const CheckerManager &mgr) { return true; } @@ -1420,6 +1420,6 @@ void ento::registerPluralMisuseChecker(CheckerManager &mgr) { mgr.registerChecker<PluralMisuseChecker>(); } -bool ento::shouldRegisterPluralMisuseChecker(const LangOptions &LO) { +bool ento::shouldRegisterPluralMisuseChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp index d73e2eb92d42..837213875a60 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp @@ -210,15 +210,16 @@ void MIGChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { if (!PVD || State->contains<RefCountedParameters>(PVD)) return; - const NoteTag *T = C.getNoteTag([this, PVD](BugReport &BR) -> std::string { - if (&BR.getBugType() != &BT) - return ""; - SmallString<64> Str; - llvm::raw_svector_ostream OS(Str); - OS << "Value passed through parameter '" << PVD->getName() - << "\' is deallocated"; - return OS.str(); - }); + const NoteTag *T = + C.getNoteTag([this, PVD](PathSensitiveBugReport &BR) -> std::string { + if (&BR.getBugType() != &BT) + return ""; + SmallString<64> Str; + llvm::raw_svector_ostream OS(Str); + OS << "Value passed through parameter '" << PVD->getName() + << "\' is deallocated"; + return std::string(OS.str()); + }); C.addTransition(State->set<ReleasedParameter>(true), T); } @@ -292,6 +293,6 @@ void ento::registerMIGChecker(CheckerManager &Mgr) { Mgr.registerChecker<MIGChecker>(); } -bool ento::shouldRegisterMIGChecker(const LangOptions &LO) { +bool ento::shouldRegisterMIGChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIChecker.cpp index 7f9ba0de1dc2..7ac7a38dacf3 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIChecker.cpp @@ -16,6 +16,7 @@ #include "MPIChecker.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" namespace clang { namespace ento { @@ -146,7 +147,7 @@ void MPIChecker::allRegionsUsedByWait( llvm::SmallVector<const MemRegion *, 2> &ReqRegions, const MemRegion *const MR, const CallEvent &CE, CheckerContext &Ctx) const { - MemRegionManager *const RegionManager = MR->getMemRegionManager(); + MemRegionManager &RegionManager = MR->getMemRegionManager(); if (FuncClassifier->isMPI_Waitall(CE.getCalleeIdentifier())) { const SubRegion *SuperRegion{nullptr}; @@ -160,15 +161,16 @@ void MPIChecker::allRegionsUsedByWait( return; } - const auto &Size = Ctx.getStoreManager().getSizeInElements( - Ctx.getState(), SuperRegion, + DefinedOrUnknownSVal ElementCount = getDynamicElementCount( + Ctx.getState(), SuperRegion, Ctx.getSValBuilder(), CE.getArgExpr(1)->getType()->getPointeeType()); - const llvm::APSInt &ArrSize = Size.getAs<nonloc::ConcreteInt>()->getValue(); + const llvm::APSInt &ArrSize = + ElementCount.getAs<nonloc::ConcreteInt>()->getValue(); for (size_t i = 0; i < ArrSize; ++i) { const NonLoc Idx = Ctx.getSValBuilder().makeArrayIndex(i); - const ElementRegion *const ER = RegionManager->getElementRegion( + const ElementRegion *const ER = RegionManager.getElementRegion( CE.getArgExpr(1)->getType()->getPointeeType(), Idx, SuperRegion, Ctx.getASTContext()); @@ -188,6 +190,6 @@ void clang::ento::registerMPIChecker(CheckerManager &MGR) { MGR.registerChecker<clang::ento::mpi::MPIChecker>(); } -bool clang::ento::shouldRegisterMPIChecker(const LangOptions &LO) { +bool clang::ento::shouldRegisterMPIChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp index e064ca6bd88f..87477e96d2d1 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp @@ -66,7 +66,7 @@ public: ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond, bool Assumption) const; void printState(raw_ostream &Out, ProgramStateRef State, - const char *NL, const char *Sep) const; + const char *NL, const char *Sep) const override; private: typedef std::pair<SymbolRef, const AllocationState*> AllocationPair; @@ -667,6 +667,6 @@ void ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) { mgr.registerChecker<MacOSKeychainAPIChecker>(); } -bool ento::shouldRegisterMacOSKeychainAPIChecker(const LangOptions &LO) { +bool ento::shouldRegisterMacOSKeychainAPIChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp index 410721d8b6ff..04e7f8dec8d7 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp @@ -176,6 +176,6 @@ void ento::registerMacOSXAPIChecker(CheckerManager &mgr) { mgr.registerChecker<MacOSXAPIChecker>(); } -bool ento::shouldRegisterMacOSXAPIChecker(const LangOptions &LO) { +bool ento::shouldRegisterMacOSXAPIChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 09306383d53f..d5b0a5b2220f 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -44,34 +44,49 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "AllocationState.h" #include "InterCheckerAPI.h" #include "clang/AST/Attr.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/ParentMap.h" +#include "clang/Basic/LLVM.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TargetInfo.h" #include "clang/Lex/Lexer.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.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/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" -#include "AllocationState.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/ErrorHandling.h" #include <climits> +#include <functional> #include <utility> using namespace clang; using namespace ento; +using namespace std::placeholders; //===----------------------------------------------------------------------===// -// The types of allocation we're modeling. +// The types of allocation we're modeling. This is used to check whether a +// dynamically allocated object is deallocated with the correct function, like +// not using operator delete on an object created by malloc(), or alloca regions +// aren't ever deallocated manually. //===----------------------------------------------------------------------===// namespace { @@ -87,26 +102,16 @@ enum AllocationFamily { AF_InnerBuffer }; -struct MemFunctionInfoTy; - } // end of anonymous namespace -/// Determine family of a deallocation expression. -static AllocationFamily -getAllocationFamily(const MemFunctionInfoTy &MemFunctionInfo, CheckerContext &C, - const Stmt *S); - /// Print names of allocators and deallocators. /// /// \returns true on success. -static bool printAllocDeallocName(raw_ostream &os, CheckerContext &C, - const Expr *E); +static bool printMemFnName(raw_ostream &os, CheckerContext &C, const Expr *E); -/// Print expected name of an allocator based on the deallocator's -/// family derived from the DeallocExpr. -static void printExpectedAllocName(raw_ostream &os, - const MemFunctionInfoTy &MemFunctionInfo, - CheckerContext &C, const Expr *E); +/// Print expected name of an allocator based on the deallocator's family +/// derived from the DeallocExpr. +static void printExpectedAllocName(raw_ostream &os, AllocationFamily Family); /// Print expected name of a deallocator based on the allocator's /// family. @@ -207,7 +212,7 @@ static bool isReleased(SymbolRef Sym, CheckerContext &C); /// 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, Optional<SVal> RetVal = None); //===----------------------------------------------------------------------===// @@ -265,60 +270,14 @@ struct ReallocPair { REGISTER_MAP_WITH_PROGRAMSTATE(ReallocPairs, SymbolRef, ReallocPair) -//===----------------------------------------------------------------------===// -// Kinds of memory operations, information about resource managing functions. -//===----------------------------------------------------------------------===// - -namespace { - -enum class MemoryOperationKind { MOK_Allocate, MOK_Free, MOK_Any }; - -struct MemFunctionInfoTy { - /// The value of the MallocChecker:Optimistic is stored in this variable. - /// - /// In pessimistic mode, the checker assumes that it does not know which - /// functions might free the memory. - /// In optimistic mode, the checker assumes that all user-defined functions - /// which might free a pointer are annotated. - DefaultBool ShouldIncludeOwnershipAnnotatedFunctions; - - // TODO: Change these to CallDescription, and get rid of lazy initialization. - mutable IdentifierInfo *II_alloca = nullptr, *II_win_alloca = nullptr, - *II_malloc = nullptr, *II_free = nullptr, - *II_realloc = nullptr, *II_calloc = nullptr, - *II_valloc = nullptr, *II_reallocf = nullptr, - *II_strndup = nullptr, *II_strdup = nullptr, - *II_win_strdup = nullptr, *II_kmalloc = nullptr, - *II_if_nameindex = nullptr, - *II_if_freenameindex = nullptr, *II_wcsdup = nullptr, - *II_win_wcsdup = nullptr, *II_g_malloc = nullptr, - *II_g_malloc0 = nullptr, *II_g_realloc = nullptr, - *II_g_try_malloc = nullptr, - *II_g_try_malloc0 = nullptr, - *II_g_try_realloc = nullptr, *II_g_free = nullptr, - *II_g_memdup = nullptr, *II_g_malloc_n = nullptr, - *II_g_malloc0_n = nullptr, *II_g_realloc_n = nullptr, - *II_g_try_malloc_n = nullptr, - *II_g_try_malloc0_n = nullptr, *II_kfree = nullptr, - *II_g_try_realloc_n = nullptr; - - void initIdentifierInfo(ASTContext &C) const; - - ///@{ - /// Check if this is one of the functions which can allocate/reallocate - /// memory pointed to by one of its arguments. - bool isMemFunction(const FunctionDecl *FD, ASTContext &C) const; - bool isCMemFunction(const FunctionDecl *FD, ASTContext &C, - AllocationFamily Family, - MemoryOperationKind MemKind) const; - - /// Tells if the callee is one of the builtin new/delete operators, including - /// placement operators and other standard overloads. - bool isStandardNewDelete(const FunctionDecl *FD, ASTContext &C) const; - ///@} -}; - -} // end of anonymous namespace +/// Tells if the callee is one of the builtin new/delete operators, including +/// placement operators and other standard overloads. +static bool isStandardNewDelete(const FunctionDecl *FD); +static bool isStandardNewDelete(const CallEvent &Call) { + if (!Call.getDecl() || !isa<FunctionDecl>(Call.getDecl())) + return false; + return isStandardNewDelete(cast<FunctionDecl>(Call.getDecl())); +} //===----------------------------------------------------------------------===// // Definition of the MallocChecker class. @@ -329,13 +288,15 @@ namespace { class MallocChecker : public Checker<check::DeadSymbols, check::PointerEscape, check::ConstPointerEscape, check::PreStmt<ReturnStmt>, - check::EndFunction, check::PreCall, - check::PostStmt<CallExpr>, check::PostStmt<CXXNewExpr>, - check::NewAllocator, check::PreStmt<CXXDeleteExpr>, - check::PostStmt<BlockExpr>, check::PostObjCMessage, - check::Location, eval::Assume> { + check::EndFunction, check::PreCall, check::PostCall, + check::NewAllocator, check::PostStmt<BlockExpr>, + check::PostObjCMessage, check::Location, eval::Assume> { public: - MemFunctionInfoTy MemFunctionInfo; + /// In pessimistic mode, the checker assumes that it does not know which + /// functions might free the memory. + /// In optimistic mode, the checker assumes that all user-defined functions + /// which might free a pointer are annotated. + DefaultBool ShouldIncludeOwnershipAnnotatedFunctions; /// Many checkers are essentially built into this one, so enabling them will /// make MallocChecker perform additional modeling and reporting. @@ -357,11 +318,8 @@ public: CheckerNameRef CheckNames[CK_NumCheckKinds]; 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 checkPostCall(const CallEvent &Call, CheckerContext &C) const; + void checkNewAllocator(const CXXAllocatorCall &Call, CheckerContext &C) const; void checkPostObjCMessage(const ObjCMethodCall &Call, CheckerContext &C) const; void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; @@ -395,23 +353,107 @@ private: mutable std::unique_ptr<BugType> BT_OffsetFree[CK_NumCheckKinds]; mutable std::unique_ptr<BugType> BT_UseZerroAllocated[CK_NumCheckKinds]; +#define CHECK_FN(NAME) \ + void NAME(const CallEvent &Call, CheckerContext &C) const; + + CHECK_FN(checkFree) + CHECK_FN(checkIfNameIndex) + CHECK_FN(checkBasicAlloc) + CHECK_FN(checkKernelMalloc) + CHECK_FN(checkCalloc) + CHECK_FN(checkAlloca) + CHECK_FN(checkStrdup) + CHECK_FN(checkIfFreeNameIndex) + CHECK_FN(checkCXXNewOrCXXDelete) + CHECK_FN(checkGMalloc0) + CHECK_FN(checkGMemdup) + CHECK_FN(checkGMallocN) + CHECK_FN(checkGMallocN0) + CHECK_FN(checkReallocN) + CHECK_FN(checkOwnershipAttr) + + void checkRealloc(const CallEvent &Call, CheckerContext &C, + bool ShouldFreeOnFail) const; + + using CheckFn = std::function<void(const MallocChecker *, + const CallEvent &Call, CheckerContext &C)>; + + const CallDescriptionMap<CheckFn> FreeingMemFnMap{ + {{"free", 1}, &MallocChecker::checkFree}, + {{"if_freenameindex", 1}, &MallocChecker::checkIfFreeNameIndex}, + {{"kfree", 1}, &MallocChecker::checkFree}, + {{"g_free", 1}, &MallocChecker::checkFree}, + }; + + bool isFreeingCall(const CallEvent &Call) const; + + CallDescriptionMap<CheckFn> AllocatingMemFnMap{ + {{"alloca", 1}, &MallocChecker::checkAlloca}, + {{"_alloca", 1}, &MallocChecker::checkAlloca}, + {{"malloc", 1}, &MallocChecker::checkBasicAlloc}, + {{"malloc", 3}, &MallocChecker::checkKernelMalloc}, + {{"calloc", 2}, &MallocChecker::checkCalloc}, + {{"valloc", 1}, &MallocChecker::checkBasicAlloc}, + {{CDF_MaybeBuiltin, "strndup", 2}, &MallocChecker::checkStrdup}, + {{CDF_MaybeBuiltin, "strdup", 1}, &MallocChecker::checkStrdup}, + {{"_strdup", 1}, &MallocChecker::checkStrdup}, + {{"kmalloc", 2}, &MallocChecker::checkKernelMalloc}, + {{"if_nameindex", 1}, &MallocChecker::checkIfNameIndex}, + {{CDF_MaybeBuiltin, "wcsdup", 1}, &MallocChecker::checkStrdup}, + {{CDF_MaybeBuiltin, "_wcsdup", 1}, &MallocChecker::checkStrdup}, + {{"g_malloc", 1}, &MallocChecker::checkBasicAlloc}, + {{"g_malloc0", 1}, &MallocChecker::checkGMalloc0}, + {{"g_try_malloc", 1}, &MallocChecker::checkBasicAlloc}, + {{"g_try_malloc0", 1}, &MallocChecker::checkGMalloc0}, + {{"g_memdup", 2}, &MallocChecker::checkGMemdup}, + {{"g_malloc_n", 2}, &MallocChecker::checkGMallocN}, + {{"g_malloc0_n", 2}, &MallocChecker::checkGMallocN0}, + {{"g_try_malloc_n", 2}, &MallocChecker::checkGMallocN}, + {{"g_try_malloc0_n", 2}, &MallocChecker::checkGMallocN0}, + }; + + CallDescriptionMap<CheckFn> ReallocatingMemFnMap{ + {{"realloc", 2}, + std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)}, + {{"reallocf", 2}, + std::bind(&MallocChecker::checkRealloc, _1, _2, _3, true)}, + {{"g_realloc", 2}, + std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)}, + {{"g_try_realloc", 2}, + std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)}, + {{"g_realloc_n", 3}, &MallocChecker::checkReallocN}, + {{"g_try_realloc_n", 3}, &MallocChecker::checkReallocN}, + }; + + bool isMemCall(const CallEvent &Call) const; + // TODO: Remove mutable by moving the initializtaion to the registry function. mutable Optional<uint64_t> KernelZeroFlagVal; + using KernelZeroSizePtrValueTy = Optional<int>; + /// Store the value of macro called `ZERO_SIZE_PTR`. + /// The value is initialized at first use, before first use the outer + /// Optional is empty, afterwards it contains another Optional that indicates + /// if the macro value could be determined, and if yes the value itself. + mutable Optional<KernelZeroSizePtrValueTy> KernelZeroSizePtrValue; + /// 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; + LLVM_NODISCARD + ProgramStateRef processNewAllocation(const CXXAllocatorCall &Call, + CheckerContext &C, + AllocationFamily Family) const; /// Perform a zero-allocation check. /// - /// \param [in] E The expression that allocates memory. + /// \param [in] Call The expression that allocates memory. /// \param [in] IndexOfSizeArg Index of the argument that specifies the size /// of the memory that needs to be allocated. E.g. for malloc, this would be /// 0. /// \param [in] RetVal Specifies the newly allocated pointer value; /// if unspecified, the value of expression \p E is used. - static ProgramStateRef ProcessZeroAllocCheck(CheckerContext &C, const Expr *E, + LLVM_NODISCARD + static ProgramStateRef ProcessZeroAllocCheck(const CallEvent &Call, const unsigned IndexOfSizeArg, ProgramStateRef State, Optional<SVal> RetVal = None); @@ -428,50 +470,54 @@ private: /// - first: name of the resource (e.g. 'malloc') /// - (OPTIONAL) second: size of the allocated region /// - /// \param [in] CE The expression that allocates memory. + /// \param [in] Call The expression that allocates memory. /// \param [in] Att The ownership_returns attribute. /// \param [in] State The \c ProgramState right before allocation. /// \returns The ProgramState right after allocation. - ProgramStateRef MallocMemReturnsAttr(CheckerContext &C, - const CallExpr *CE, - const OwnershipAttr* Att, + LLVM_NODISCARD + ProgramStateRef MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call, + const OwnershipAttr *Att, ProgramStateRef State) const; /// Models memory allocation. /// - /// \param [in] CE The expression that allocates memory. + /// \param [in] Call The expression that allocates memory. /// \param [in] SizeEx Size of the memory that needs to be allocated. /// \param [in] Init The value the allocated memory needs to be initialized. /// with. For example, \c calloc initializes the allocated memory to 0, /// malloc leaves it undefined. /// \param [in] State The \c ProgramState right before allocation. /// \returns The ProgramState right after allocation. - static ProgramStateRef MallocMemAux(CheckerContext &C, const CallExpr *CE, + LLVM_NODISCARD + static ProgramStateRef MallocMemAux(CheckerContext &C, const CallEvent &Call, const Expr *SizeEx, SVal Init, ProgramStateRef State, - AllocationFamily Family = AF_Malloc); + AllocationFamily Family); /// Models memory allocation. /// - /// \param [in] CE The expression that allocates memory. + /// \param [in] Call The expression that allocates memory. /// \param [in] Size Size of the memory that needs to be allocated. /// \param [in] Init The value the allocated memory needs to be initialized. /// with. For example, \c calloc initializes the allocated memory to 0, /// malloc leaves it undefined. /// \param [in] State The \c ProgramState right before allocation. /// \returns The ProgramState right after allocation. - static ProgramStateRef MallocMemAux(CheckerContext &C, const CallExpr *CE, + LLVM_NODISCARD + static ProgramStateRef MallocMemAux(CheckerContext &C, const CallEvent &Call, SVal Size, SVal Init, ProgramStateRef State, - AllocationFamily Family = AF_Malloc); + AllocationFamily Family); + LLVM_NODISCARD static ProgramStateRef addExtentSize(CheckerContext &C, const CXXNewExpr *NE, 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). + LLVM_NODISCARD llvm::Optional<ProgramStateRef> - performKernelMalloc(const CallExpr *CE, CheckerContext &C, + performKernelMalloc(const CallEvent &Call, CheckerContext &C, const ProgramStateRef &State) const; /// Model functions with the ownership_takes and ownership_holds attributes. @@ -487,17 +533,18 @@ private: /// - first: name of the resource (e.g. 'malloc') /// - second: index of the parameter the attribute applies to /// - /// \param [in] CE The expression that frees memory. + /// \param [in] Call The expression that frees memory. /// \param [in] Att The ownership_takes or ownership_holds attribute. /// \param [in] State The \c ProgramState right before allocation. /// \returns The ProgramState right after deallocation. - ProgramStateRef FreeMemAttr(CheckerContext &C, const CallExpr *CE, - const OwnershipAttr* Att, + LLVM_NODISCARD + ProgramStateRef FreeMemAttr(CheckerContext &C, const CallEvent &Call, + const OwnershipAttr *Att, ProgramStateRef State) const; /// Models memory deallocation. /// - /// \param [in] CE The expression that frees memory. + /// \param [in] Call The expression that frees memory. /// \param [in] State The \c ProgramState right before allocation. /// \param [in] Num Index of the argument that needs to be freed. This is /// normally 0, but for custom free functions it may be different. @@ -514,15 +561,17 @@ private: /// \param [in] ReturnsNullOnFailure Whether the memory deallocation function /// we're modeling returns with Null on failure. /// \returns The ProgramState right after deallocation. - ProgramStateRef FreeMemAux(CheckerContext &C, const CallExpr *CE, + LLVM_NODISCARD + ProgramStateRef FreeMemAux(CheckerContext &C, const CallEvent &Call, ProgramStateRef State, unsigned Num, bool Hold, bool &IsKnownToBeAllocated, + AllocationFamily Family, bool ReturnsNullOnFailure = false) const; /// Models memory deallocation. /// /// \param [in] ArgExpr The variable who's pointee needs to be freed. - /// \param [in] ParentExpr The expression that frees the memory. + /// \param [in] Call The expression that frees the memory. /// \param [in] State The \c ProgramState right before allocation. /// normally 0, but for custom free functions it may be different. /// \param [in] Hold Whether the parameter at \p Index has the ownership_holds @@ -538,9 +587,11 @@ private: /// \param [in] ReturnsNullOnFailure Whether the memory deallocation function /// we're modeling returns with Null on failure. /// \returns The ProgramState right after deallocation. + LLVM_NODISCARD ProgramStateRef FreeMemAux(CheckerContext &C, const Expr *ArgExpr, - const Expr *ParentExpr, ProgramStateRef State, + const CallEvent &Call, ProgramStateRef State, bool Hold, bool &IsKnownToBeAllocated, + AllocationFamily Family, bool ReturnsNullOnFailure = false) const; // TODO: Needs some refactoring, as all other deallocation modeling @@ -549,15 +600,17 @@ private: // /// Models memory reallocation. /// - /// \param [in] CE The expression that reallocated memory + /// \param [in] Call The expression that reallocated memory /// \param [in] ShouldFreeOnFail Whether if reallocation fails, the supplied /// memory should be freed. /// \param [in] State The \c ProgramState right before reallocation. /// \param [in] SuffixWithN Whether the reallocation function we're modeling /// has an '_n' suffix, such as g_realloc_n. /// \returns The ProgramState right after reallocation. - ProgramStateRef ReallocMemAux(CheckerContext &C, const CallExpr *CE, + LLVM_NODISCARD + ProgramStateRef ReallocMemAux(CheckerContext &C, const CallEvent &Call, bool ShouldFreeOnFail, ProgramStateRef State, + AllocationFamily Family, bool SuffixWithN = false) const; /// Evaluates the buffer size that needs to be allocated. @@ -565,20 +618,22 @@ private: /// \param [in] Blocks The amount of blocks that needs to be allocated. /// \param [in] BlockBytes The size of a block. /// \returns The symbolic value of \p Blocks * \p BlockBytes. + LLVM_NODISCARD static SVal evalMulForBufferSize(CheckerContext &C, const Expr *Blocks, const Expr *BlockBytes); /// Models zero initialized array allocation. /// - /// \param [in] CE The expression that reallocated memory + /// \param [in] Call The expression that reallocated memory /// \param [in] State The \c ProgramState right before reallocation. /// \returns The ProgramState right after allocation. - static ProgramStateRef CallocMem(CheckerContext &C, const CallExpr *CE, + LLVM_NODISCARD + static ProgramStateRef CallocMem(CheckerContext &C, const CallEvent &Call, ProgramStateRef State); /// See if deallocation happens in a suspicious context. If so, escape the /// pointers that otherwise would have been deallocated and return true. - bool suppressDeallocationsInSuspiciousContexts(const CallExpr *CE, + bool suppressDeallocationsInSuspiciousContexts(const CallEvent &Call, CheckerContext &C) const; /// If in \p S \p Sym is used, check whether \p Sym was already freed. @@ -607,6 +662,7 @@ private: SymbolRef &EscapingSymbol) const; /// Implementation of the checkPointerEscape callbacks. + LLVM_NODISCARD ProgramStateRef checkPointerEscapeAux(ProgramStateRef State, const InvalidatedSymbols &Escaped, const CallEvent *Call, @@ -622,44 +678,53 @@ private: /// family/call/symbol. Optional<CheckKind> getCheckIfTracked(AllocationFamily Family, bool IsALeakCheck = false) const; - Optional<CheckKind> getCheckIfTracked(CheckerContext &C, - const Stmt *AllocDeallocStmt, - bool IsALeakCheck = false) const; + Optional<CheckKind> getCheckIfTracked(CheckerContext &C, SymbolRef Sym, bool IsALeakCheck = false) const; ///@} static bool SummarizeValue(raw_ostream &os, SVal V); static bool SummarizeRegion(raw_ostream &os, const MemRegion *MR); - void ReportBadFree(CheckerContext &C, SVal ArgVal, SourceRange Range, - const Expr *DeallocExpr) const; - void ReportFreeAlloca(CheckerContext &C, SVal ArgVal, + void HandleNonHeapDealloc(CheckerContext &C, SVal ArgVal, SourceRange Range, + const Expr *DeallocExpr, + AllocationFamily Family) const; + + void HandleFreeAlloca(CheckerContext &C, SVal ArgVal, SourceRange Range) const; - void ReportMismatchedDealloc(CheckerContext &C, SourceRange Range, + + void HandleMismatchedDealloc(CheckerContext &C, SourceRange Range, const Expr *DeallocExpr, const RefState *RS, SymbolRef Sym, bool OwnershipTransferred) const; - void ReportOffsetFree(CheckerContext &C, SVal ArgVal, SourceRange Range, - const Expr *DeallocExpr, + + void HandleOffsetFree(CheckerContext &C, SVal ArgVal, SourceRange Range, + const Expr *DeallocExpr, AllocationFamily Family, const Expr *AllocExpr = nullptr) const; - void ReportUseAfterFree(CheckerContext &C, SourceRange Range, + + void HandleUseAfterFree(CheckerContext &C, SourceRange Range, SymbolRef Sym) const; - void ReportDoubleFree(CheckerContext &C, SourceRange Range, bool Released, + + void HandleDoubleFree(CheckerContext &C, SourceRange Range, bool Released, SymbolRef Sym, SymbolRef PrevSym) const; - void ReportDoubleDelete(CheckerContext &C, SymbolRef Sym) const; + void HandleDoubleDelete(CheckerContext &C, SymbolRef Sym) const; - void ReportUseZeroAllocated(CheckerContext &C, SourceRange Range, - SymbolRef Sym) const; + void HandleUseZeroAlloc(CheckerContext &C, SourceRange Range, + SymbolRef Sym) const; - void ReportFunctionPointerFree(CheckerContext &C, SVal ArgVal, - SourceRange Range, const Expr *FreeExpr) const; + void HandleFunctionPtrFree(CheckerContext &C, SVal ArgVal, SourceRange Range, + const Expr *FreeExpr, + AllocationFamily Family) const; /// Find the location of the allocation for Sym on the path leading to the /// exploded node N. static LeakInfo getAllocationSite(const ExplodedNode *N, SymbolRef Sym, CheckerContext &C); - void reportLeak(SymbolRef Sym, ExplodedNode *N, CheckerContext &C) const; + void HandleLeak(SymbolRef Sym, ExplodedNode *N, CheckerContext &C) const; + + /// Test if value in ArgVal equals to value in macro `ZERO_SIZE_PTR`. + bool isArgZERO_SIZE_PTR(ProgramStateRef State, CheckerContext &C, + SVal ArgVal) const; }; //===----------------------------------------------------------------------===// @@ -782,7 +847,7 @@ private: os << "Reallocation of " << ArgIndex << llvm::getOrdinalSuffix(ArgIndex) << " parameter failed"; - return os.str(); + return std::string(os.str()); } std::string getMessageForReturn(const CallExpr *CallExpr) override { @@ -800,6 +865,7 @@ REGISTER_MAP_WITH_PROGRAMSTATE(FreeReturnValue, SymbolRef, SymbolRef) namespace { class StopTrackingCallback final : public SymbolVisitor { ProgramStateRef state; + public: StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {} ProgramStateRef getState() const { return state; } @@ -811,160 +877,57 @@ public: }; } // end anonymous namespace -//===----------------------------------------------------------------------===// -// Methods of MemFunctionInfoTy. -//===----------------------------------------------------------------------===// - -void MemFunctionInfoTy::initIdentifierInfo(ASTContext &Ctx) const { - if (II_malloc) - return; - II_alloca = &Ctx.Idents.get("alloca"); - II_malloc = &Ctx.Idents.get("malloc"); - II_free = &Ctx.Idents.get("free"); - II_realloc = &Ctx.Idents.get("realloc"); - II_reallocf = &Ctx.Idents.get("reallocf"); - II_calloc = &Ctx.Idents.get("calloc"); - II_valloc = &Ctx.Idents.get("valloc"); - II_strdup = &Ctx.Idents.get("strdup"); - II_strndup = &Ctx.Idents.get("strndup"); - II_wcsdup = &Ctx.Idents.get("wcsdup"); - II_kmalloc = &Ctx.Idents.get("kmalloc"); - II_kfree = &Ctx.Idents.get("kfree"); - II_if_nameindex = &Ctx.Idents.get("if_nameindex"); - II_if_freenameindex = &Ctx.Idents.get("if_freenameindex"); - - //MSVC uses `_`-prefixed instead, so we check for them too. - II_win_strdup = &Ctx.Idents.get("_strdup"); - II_win_wcsdup = &Ctx.Idents.get("_wcsdup"); - II_win_alloca = &Ctx.Idents.get("_alloca"); - - // Glib - II_g_malloc = &Ctx.Idents.get("g_malloc"); - II_g_malloc0 = &Ctx.Idents.get("g_malloc0"); - II_g_realloc = &Ctx.Idents.get("g_realloc"); - II_g_try_malloc = &Ctx.Idents.get("g_try_malloc"); - II_g_try_malloc0 = &Ctx.Idents.get("g_try_malloc0"); - II_g_try_realloc = &Ctx.Idents.get("g_try_realloc"); - II_g_free = &Ctx.Idents.get("g_free"); - II_g_memdup = &Ctx.Idents.get("g_memdup"); - II_g_malloc_n = &Ctx.Idents.get("g_malloc_n"); - II_g_malloc0_n = &Ctx.Idents.get("g_malloc0_n"); - II_g_realloc_n = &Ctx.Idents.get("g_realloc_n"); - II_g_try_malloc_n = &Ctx.Idents.get("g_try_malloc_n"); - II_g_try_malloc0_n = &Ctx.Idents.get("g_try_malloc0_n"); - II_g_try_realloc_n = &Ctx.Idents.get("g_try_realloc_n"); -} - -bool MemFunctionInfoTy::isMemFunction(const FunctionDecl *FD, - ASTContext &C) const { - if (isCMemFunction(FD, C, AF_Malloc, MemoryOperationKind::MOK_Any)) - return true; - - if (isCMemFunction(FD, C, AF_IfNameIndex, MemoryOperationKind::MOK_Any)) - return true; - - if (isCMemFunction(FD, C, AF_Alloca, MemoryOperationKind::MOK_Any)) - return true; - - if (isStandardNewDelete(FD, C)) - return true; - - return false; -} - -bool MemFunctionInfoTy::isCMemFunction(const FunctionDecl *FD, ASTContext &C, - AllocationFamily Family, - MemoryOperationKind MemKind) const { +static bool isStandardNewDelete(const FunctionDecl *FD) { if (!FD) return false; - bool CheckFree = (MemKind == MemoryOperationKind::MOK_Any || - MemKind == MemoryOperationKind::MOK_Free); - bool CheckAlloc = (MemKind == MemoryOperationKind::MOK_Any || - MemKind == MemoryOperationKind::MOK_Allocate); - - if (FD->getKind() == Decl::Function) { - const IdentifierInfo *FunI = FD->getIdentifier(); - initIdentifierInfo(C); - - if (Family == AF_Malloc && CheckFree) { - if (FunI == II_free || FunI == II_realloc || FunI == II_reallocf || - FunI == II_g_free || FunI == II_kfree) - return true; - } - - if (Family == AF_Malloc && CheckAlloc) { - if (FunI == II_malloc || FunI == II_realloc || FunI == II_reallocf || - FunI == II_calloc || FunI == II_valloc || FunI == II_strdup || - FunI == II_win_strdup || FunI == II_strndup || FunI == II_wcsdup || - FunI == II_win_wcsdup || FunI == II_kmalloc || - FunI == II_g_malloc || FunI == II_g_malloc0 || - FunI == II_g_realloc || FunI == II_g_try_malloc || - FunI == II_g_try_malloc0 || FunI == II_g_try_realloc || - FunI == II_g_memdup || FunI == II_g_malloc_n || - FunI == II_g_malloc0_n || FunI == II_g_realloc_n || - FunI == II_g_try_malloc_n || FunI == II_g_try_malloc0_n || - FunI == II_g_try_realloc_n) - return true; - } - - if (Family == AF_IfNameIndex && CheckFree) { - if (FunI == II_if_freenameindex) - return true; - } + OverloadedOperatorKind Kind = FD->getOverloadedOperator(); + if (Kind != OO_New && Kind != OO_Array_New && Kind != OO_Delete && + Kind != OO_Array_Delete) + return false; - if (Family == AF_IfNameIndex && CheckAlloc) { - if (FunI == II_if_nameindex) - return true; - } + // This is standard if and only if it's not defined in a user file. + SourceLocation L = FD->getLocation(); + // If the header for operator delete is not included, it's still defined + // in an invalid source location. Check to make sure we don't crash. + return !L.isValid() || + FD->getASTContext().getSourceManager().isInSystemHeader(L); +} - if (Family == AF_Alloca && CheckAlloc) { - if (FunI == II_alloca || FunI == II_win_alloca) - return true; - } - } +//===----------------------------------------------------------------------===// +// Methods of MallocChecker and MallocBugVisitor. +//===----------------------------------------------------------------------===// - if (Family != AF_Malloc) - return false; +bool MallocChecker::isFreeingCall(const CallEvent &Call) const { + if (FreeingMemFnMap.lookup(Call) || ReallocatingMemFnMap.lookup(Call)) + return true; - if (ShouldIncludeOwnershipAnnotatedFunctions && FD->hasAttrs()) { - for (const auto *I : FD->specific_attrs<OwnershipAttr>()) { + const auto *Func = dyn_cast<FunctionDecl>(Call.getDecl()); + if (Func && Func->hasAttrs()) { + for (const auto *I : Func->specific_attrs<OwnershipAttr>()) { OwnershipAttr::OwnershipKind OwnKind = I->getOwnKind(); - if(OwnKind == OwnershipAttr::Takes || OwnKind == OwnershipAttr::Holds) { - if (CheckFree) - return true; - } else if (OwnKind == OwnershipAttr::Returns) { - if (CheckAlloc) - return true; - } + if (OwnKind == OwnershipAttr::Takes || OwnKind == OwnershipAttr::Holds) + return true; } } - return false; } -bool MemFunctionInfoTy::isStandardNewDelete(const FunctionDecl *FD, - ASTContext &C) const { - if (!FD) - return false; - OverloadedOperatorKind Kind = FD->getOverloadedOperator(); - if (Kind != OO_New && Kind != OO_Array_New && - Kind != OO_Delete && Kind != OO_Array_Delete) +bool MallocChecker::isMemCall(const CallEvent &Call) const { + if (FreeingMemFnMap.lookup(Call) || AllocatingMemFnMap.lookup(Call) || + ReallocatingMemFnMap.lookup(Call)) + return true; + + if (!ShouldIncludeOwnershipAnnotatedFunctions) return false; - // This is standard if and only if it's not defined in a user file. - SourceLocation L = FD->getLocation(); - // If the header for operator delete is not included, it's still defined - // in an invalid source location. Check to make sure we don't crash. - return !L.isValid() || C.getSourceManager().isInSystemHeader(L); + const auto *Func = dyn_cast<FunctionDecl>(Call.getDecl()); + return Func && Func->hasAttr<OwnershipAttr>(); } -//===----------------------------------------------------------------------===// -// Methods of MallocChecker and MallocBugVisitor. -//===----------------------------------------------------------------------===// - -llvm::Optional<ProgramStateRef> MallocChecker::performKernelMalloc( - const CallExpr *CE, CheckerContext &C, const ProgramStateRef &State) const { +llvm::Optional<ProgramStateRef> +MallocChecker::performKernelMalloc(const CallEvent &Call, CheckerContext &C, + const ProgramStateRef &State) const { // 3-argument malloc(), as commonly used in {Free,Net,Open}BSD Kernels: // // void *malloc(unsigned long size, struct malloc_type *mtp, int flags); @@ -1006,10 +969,10 @@ llvm::Optional<ProgramStateRef> MallocChecker::performKernelMalloc( // We treat the last argument as the flags argument, and callers fall-back to // normal malloc on a None return. This works for the FreeBSD kernel malloc // as well as Linux kmalloc. - if (CE->getNumArgs() < 2) + if (Call.getNumArgs() < 2) return None; - const Expr *FlagsEx = CE->getArg(CE->getNumArgs() - 1); + const Expr *FlagsEx = Call.getArgExpr(Call.getNumArgs() - 1); 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, @@ -1035,7 +998,8 @@ llvm::Optional<ProgramStateRef> MallocChecker::performKernelMalloc( // If M_ZERO is set, treat this like calloc (initialized). if (TrueState && !FalseState) { SVal ZeroVal = C.getSValBuilder().makeZeroVal(Ctx.CharTy); - return MallocMemAux(C, CE, CE->getArg(0), ZeroVal, TrueState); + return MallocMemAux(C, Call, Call.getArgExpr(0), ZeroVal, TrueState, + AF_Malloc); } return None; @@ -1052,161 +1016,234 @@ SVal MallocChecker::evalMulForBufferSize(CheckerContext &C, const Expr *Blocks, return TotalSize; } -void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { - if (C.wasInlined) +void MallocChecker::checkBasicAlloc(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State, + AF_Malloc); + State = ProcessZeroAllocCheck(Call, 0, State); + C.addTransition(State); +} + +void MallocChecker::checkKernelMalloc(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + llvm::Optional<ProgramStateRef> MaybeState = + performKernelMalloc(Call, C, State); + if (MaybeState.hasValue()) + State = MaybeState.getValue(); + else + State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State, + AF_Malloc); + C.addTransition(State); +} + +static bool isStandardRealloc(const CallEvent &Call) { + const FunctionDecl *FD = dyn_cast<FunctionDecl>(Call.getDecl()); + assert(FD); + ASTContext &AC = FD->getASTContext(); + + if (isa<CXXMethodDecl>(FD)) + return false; + + return FD->getDeclaredReturnType().getDesugaredType(AC) == AC.VoidPtrTy && + FD->getParamDecl(0)->getType().getDesugaredType(AC) == AC.VoidPtrTy && + FD->getParamDecl(1)->getType().getDesugaredType(AC) == + AC.getSizeType(); +} + +static bool isGRealloc(const CallEvent &Call) { + const FunctionDecl *FD = dyn_cast<FunctionDecl>(Call.getDecl()); + assert(FD); + ASTContext &AC = FD->getASTContext(); + + if (isa<CXXMethodDecl>(FD)) + return false; + + return FD->getDeclaredReturnType().getDesugaredType(AC) == AC.VoidPtrTy && + FD->getParamDecl(0)->getType().getDesugaredType(AC) == AC.VoidPtrTy && + FD->getParamDecl(1)->getType().getDesugaredType(AC) == + AC.UnsignedLongTy; +} + +void MallocChecker::checkRealloc(const CallEvent &Call, CheckerContext &C, + bool ShouldFreeOnFail) const { + // HACK: CallDescription currently recognizes non-standard realloc functions + // as standard because it doesn't check the type, or wether its a non-method + // function. This should be solved by making CallDescription smarter. + // Mind that this came from a bug report, and all other functions suffer from + // this. + // https://bugs.llvm.org/show_bug.cgi?id=46253 + if (!isStandardRealloc(Call) && !isGRealloc(Call)) return; + ProgramStateRef State = C.getState(); + State = ReallocMemAux(C, Call, ShouldFreeOnFail, State, AF_Malloc); + State = ProcessZeroAllocCheck(Call, 1, State); + C.addTransition(State); +} - const FunctionDecl *FD = C.getCalleeDecl(CE); - if (!FD) +void MallocChecker::checkCalloc(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + State = CallocMem(C, Call, State); + State = ProcessZeroAllocCheck(Call, 0, State); + State = ProcessZeroAllocCheck(Call, 1, State); + C.addTransition(State); +} + +void MallocChecker::checkFree(const CallEvent &Call, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + bool IsKnownToBeAllocatedMemory = false; + if (suppressDeallocationsInSuspiciousContexts(Call, C)) return; + State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory, + AF_Malloc); + C.addTransition(State); +} +void MallocChecker::checkAlloca(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State, + AF_Alloca); + State = ProcessZeroAllocCheck(Call, 0, State); + C.addTransition(State); +} + +void MallocChecker::checkStrdup(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return; + State = MallocUpdateRefState(C, CE, State, AF_Malloc); + + C.addTransition(State); +} + +void MallocChecker::checkIfNameIndex(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + // Should we model this differently? We can allocate a fixed number of + // elements with zeros in the last one. + State = + MallocMemAux(C, Call, UnknownVal(), UnknownVal(), State, AF_IfNameIndex); + + C.addTransition(State); +} + +void MallocChecker::checkIfFreeNameIndex(const CallEvent &Call, + CheckerContext &C) const { ProgramStateRef State = C.getState(); bool IsKnownToBeAllocatedMemory = false; + State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory, + AF_IfNameIndex); + C.addTransition(State); +} - if (FD->getKind() == Decl::Function) { - MemFunctionInfo.initIdentifierInfo(C.getASTContext()); - IdentifierInfo *FunI = FD->getIdentifier(); +void MallocChecker::checkCXXNewOrCXXDelete(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + bool IsKnownToBeAllocatedMemory = false; + const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return; - if (FunI == MemFunctionInfo.II_malloc || - FunI == MemFunctionInfo.II_g_malloc || - FunI == MemFunctionInfo.II_g_try_malloc) { - switch (CE->getNumArgs()) { - default: - return; - case 1: - State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); - State = ProcessZeroAllocCheck(C, CE, 0, State); - break; - case 2: - State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); - break; - case 3: - llvm::Optional<ProgramStateRef> MaybeState = - performKernelMalloc(CE, C, State); - if (MaybeState.hasValue()) - State = MaybeState.getValue(); - else - State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); - break; - } - } else if (FunI == MemFunctionInfo.II_kmalloc) { - if (CE->getNumArgs() < 1) - return; - llvm::Optional<ProgramStateRef> MaybeState = - performKernelMalloc(CE, C, State); - if (MaybeState.hasValue()) - State = MaybeState.getValue(); - else - State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); - } else if (FunI == MemFunctionInfo.II_valloc) { - if (CE->getNumArgs() < 1) - return; - State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); - State = ProcessZeroAllocCheck(C, CE, 0, State); - } else if (FunI == MemFunctionInfo.II_realloc || - FunI == MemFunctionInfo.II_g_realloc || - FunI == MemFunctionInfo.II_g_try_realloc) { - State = ReallocMemAux(C, CE, /*ShouldFreeOnFail*/ false, State); - State = ProcessZeroAllocCheck(C, CE, 1, State); - } else if (FunI == MemFunctionInfo.II_reallocf) { - State = ReallocMemAux(C, CE, /*ShouldFreeOnFail*/ true, State); - State = ProcessZeroAllocCheck(C, CE, 1, State); - } else if (FunI == MemFunctionInfo.II_calloc) { - State = CallocMem(C, CE, State); - State = ProcessZeroAllocCheck(C, CE, 0, State); - State = ProcessZeroAllocCheck(C, CE, 1, State); - } else if (FunI == MemFunctionInfo.II_free || - FunI == MemFunctionInfo.II_g_free || - FunI == MemFunctionInfo.II_kfree) { - if (suppressDeallocationsInSuspiciousContexts(CE, C)) - return; + assert(isStandardNewDelete(Call)); - State = FreeMemAux(C, CE, State, 0, false, IsKnownToBeAllocatedMemory); - } else if (FunI == MemFunctionInfo.II_strdup || - FunI == MemFunctionInfo.II_win_strdup || - FunI == MemFunctionInfo.II_wcsdup || - FunI == MemFunctionInfo.II_win_wcsdup) { - State = MallocUpdateRefState(C, CE, State); - } else if (FunI == MemFunctionInfo.II_strndup) { - State = MallocUpdateRefState(C, CE, State); - } else if (FunI == MemFunctionInfo.II_alloca || - FunI == MemFunctionInfo.II_win_alloca) { - if (CE->getNumArgs() < 1) - return; - State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, - AF_Alloca); - State = ProcessZeroAllocCheck(C, CE, 0, State); - } else if (MemFunctionInfo.isStandardNewDelete(FD, C.getASTContext())) { - // Process direct calls to operator new/new[]/delete/delete[] functions - // as distinct from new/new[]/delete/delete[] expressions that are - // processed by the checkPostStmt callbacks for CXXNewExpr and - // CXXDeleteExpr. - switch (FD->getOverloadedOperator()) { - case OO_New: - State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, - AF_CXXNew); - State = ProcessZeroAllocCheck(C, CE, 0, State); - break; - case OO_Array_New: - State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, - AF_CXXNewArray); - State = ProcessZeroAllocCheck(C, CE, 0, State); - break; - case OO_Delete: - case OO_Array_Delete: - State = FreeMemAux(C, CE, State, 0, false, IsKnownToBeAllocatedMemory); - break; - default: - llvm_unreachable("not a new/delete operator"); - } - } else if (FunI == MemFunctionInfo.II_if_nameindex) { - // Should we model this differently? We can allocate a fixed number of - // elements with zeros in the last one. - State = MallocMemAux(C, CE, UnknownVal(), UnknownVal(), State, - AF_IfNameIndex); - } else if (FunI == MemFunctionInfo.II_if_freenameindex) { - State = FreeMemAux(C, CE, State, 0, false, IsKnownToBeAllocatedMemory); - } else if (FunI == MemFunctionInfo.II_g_malloc0 || - FunI == MemFunctionInfo.II_g_try_malloc0) { - if (CE->getNumArgs() < 1) - return; - SValBuilder &svalBuilder = C.getSValBuilder(); - SVal zeroVal = svalBuilder.makeZeroVal(svalBuilder.getContext().CharTy); - State = MallocMemAux(C, CE, CE->getArg(0), zeroVal, State); - State = ProcessZeroAllocCheck(C, CE, 0, State); - } else if (FunI == MemFunctionInfo.II_g_memdup) { - if (CE->getNumArgs() < 2) - return; - State = MallocMemAux(C, CE, CE->getArg(1), UndefinedVal(), State); - State = ProcessZeroAllocCheck(C, CE, 1, State); - } else if (FunI == MemFunctionInfo.II_g_malloc_n || - FunI == MemFunctionInfo.II_g_try_malloc_n || - FunI == MemFunctionInfo.II_g_malloc0_n || - FunI == MemFunctionInfo.II_g_try_malloc0_n) { - if (CE->getNumArgs() < 2) - return; - SVal Init = UndefinedVal(); - if (FunI == MemFunctionInfo.II_g_malloc0_n || - FunI == MemFunctionInfo.II_g_try_malloc0_n) { - SValBuilder &SB = C.getSValBuilder(); - Init = SB.makeZeroVal(SB.getContext().CharTy); - } - SVal TotalSize = evalMulForBufferSize(C, CE->getArg(0), CE->getArg(1)); - State = MallocMemAux(C, CE, TotalSize, Init, State); - State = ProcessZeroAllocCheck(C, CE, 0, State); - State = ProcessZeroAllocCheck(C, CE, 1, State); - } else if (FunI == MemFunctionInfo.II_g_realloc_n || - FunI == MemFunctionInfo.II_g_try_realloc_n) { - if (CE->getNumArgs() < 3) - return; - State = ReallocMemAux(C, CE, /*ShouldFreeOnFail*/ false, State, - /*SuffixWithN*/ true); - State = ProcessZeroAllocCheck(C, CE, 1, State); - State = ProcessZeroAllocCheck(C, CE, 2, State); - } + // Process direct calls to operator new/new[]/delete/delete[] functions + // as distinct from new/new[]/delete/delete[] expressions that are + // processed by the checkPostStmt callbacks for CXXNewExpr and + // CXXDeleteExpr. + const FunctionDecl *FD = C.getCalleeDecl(CE); + switch (FD->getOverloadedOperator()) { + case OO_New: + State = + MallocMemAux(C, Call, CE->getArg(0), UndefinedVal(), State, AF_CXXNew); + State = ProcessZeroAllocCheck(Call, 0, State); + break; + case OO_Array_New: + State = MallocMemAux(C, Call, CE->getArg(0), UndefinedVal(), State, + AF_CXXNewArray); + State = ProcessZeroAllocCheck(Call, 0, State); + break; + case OO_Delete: + State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory, + AF_CXXNew); + break; + case OO_Array_Delete: + State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory, + AF_CXXNewArray); + break; + default: + llvm_unreachable("not a new/delete operator"); } - if (MemFunctionInfo.ShouldIncludeOwnershipAnnotatedFunctions || + C.addTransition(State); +} + +void MallocChecker::checkGMalloc0(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SValBuilder &svalBuilder = C.getSValBuilder(); + SVal zeroVal = svalBuilder.makeZeroVal(svalBuilder.getContext().CharTy); + State = MallocMemAux(C, Call, Call.getArgExpr(0), zeroVal, State, AF_Malloc); + State = ProcessZeroAllocCheck(Call, 0, State); + C.addTransition(State); +} + +void MallocChecker::checkGMemdup(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + State = MallocMemAux(C, Call, Call.getArgExpr(1), UndefinedVal(), State, + AF_Malloc); + State = ProcessZeroAllocCheck(Call, 1, State); + C.addTransition(State); +} + +void MallocChecker::checkGMallocN(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SVal Init = UndefinedVal(); + SVal TotalSize = evalMulForBufferSize(C, Call.getArgExpr(0), Call.getArgExpr(1)); + State = MallocMemAux(C, Call, TotalSize, Init, State, AF_Malloc); + State = ProcessZeroAllocCheck(Call, 0, State); + State = ProcessZeroAllocCheck(Call, 1, State); + C.addTransition(State); +} + +void MallocChecker::checkGMallocN0(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SValBuilder &SB = C.getSValBuilder(); + SVal Init = SB.makeZeroVal(SB.getContext().CharTy); + SVal TotalSize = evalMulForBufferSize(C, Call.getArgExpr(0), Call.getArgExpr(1)); + State = MallocMemAux(C, Call, TotalSize, Init, State, AF_Malloc); + State = ProcessZeroAllocCheck(Call, 0, State); + State = ProcessZeroAllocCheck(Call, 1, State); + C.addTransition(State); +} + +void MallocChecker::checkReallocN(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + State = ReallocMemAux(C, Call, /*ShouldFreeOnFail=*/false, State, AF_Malloc, + /*SuffixWithN=*/true); + State = ProcessZeroAllocCheck(Call, 1, State); + State = ProcessZeroAllocCheck(Call, 2, State); + C.addTransition(State); +} + +void MallocChecker::checkOwnershipAttr(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return; + const FunctionDecl *FD = C.getCalleeDecl(CE); + if (!FD) + return; + if (ShouldIncludeOwnershipAnnotatedFunctions || ChecksEnabled[CK_MismatchedDeallocatorChecker]) { // Check all the attributes, if there are any. // There can be multiple of these attributes. @@ -1214,11 +1251,11 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { for (const auto *I : FD->specific_attrs<OwnershipAttr>()) { switch (I->getOwnKind()) { case OwnershipAttr::Returns: - State = MallocMemReturnsAttr(C, CE, I, State); + State = MallocMemReturnsAttr(C, Call, I, State); break; case OwnershipAttr::Takes: case OwnershipAttr::Holds: - State = FreeMemAttr(C, CE, I, State); + State = FreeMemAttr(C, Call, I, State); break; } } @@ -1226,40 +1263,73 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { C.addTransition(State); } +void MallocChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + if (C.wasInlined) + return; + if (!Call.getOriginExpr()) + return; + + ProgramStateRef State = C.getState(); + + if (const CheckFn *Callback = FreeingMemFnMap.lookup(Call)) { + (*Callback)(this, Call, C); + return; + } + + if (const CheckFn *Callback = AllocatingMemFnMap.lookup(Call)) { + (*Callback)(this, Call, C); + return; + } + + if (const CheckFn *Callback = ReallocatingMemFnMap.lookup(Call)) { + (*Callback)(this, Call, C); + return; + } + + if (isStandardNewDelete(Call)) { + checkCXXNewOrCXXDelete(Call, C); + return; + } + + checkOwnershipAttr(Call, C); +} + // Performs a 0-sized allocations check. ProgramStateRef MallocChecker::ProcessZeroAllocCheck( - CheckerContext &C, const Expr *E, const unsigned IndexOfSizeArg, - ProgramStateRef State, Optional<SVal> RetVal) { + const CallEvent &Call, const unsigned IndexOfSizeArg, ProgramStateRef State, + Optional<SVal> RetVal) { if (!State) return nullptr; if (!RetVal) - RetVal = C.getSVal(E); + RetVal = Call.getReturnValue(); const Expr *Arg = nullptr; - if (const CallExpr *CE = dyn_cast<CallExpr>(E)) { + if (const CallExpr *CE = dyn_cast<CallExpr>(Call.getOriginExpr())) { Arg = CE->getArg(IndexOfSizeArg); - } - else if (const CXXNewExpr *NE = dyn_cast<CXXNewExpr>(E)) { - if (NE->isArray()) + } else if (const CXXNewExpr *NE = + dyn_cast<CXXNewExpr>(Call.getOriginExpr())) { + if (NE->isArray()) { Arg = *NE->getArraySize(); - else + } else { return State; - } - else + } + } else llvm_unreachable("not a CallExpr or CXXNewExpr"); assert(Arg); - Optional<DefinedSVal> DefArgVal = C.getSVal(Arg).getAs<DefinedSVal>(); + auto DefArgVal = + State->getSVal(Arg, Call.getLocationContext()).getAs<DefinedSVal>(); if (!DefArgVal) return State; // Check if the allocation size is 0. ProgramStateRef TrueState, FalseState; - SValBuilder &SvalBuilder = C.getSValBuilder(); + SValBuilder &SvalBuilder = State->getStateManager().getSValBuilder(); DefinedSVal Zero = SvalBuilder.makeZeroVal(Arg->getType()).castAs<DefinedSVal>(); @@ -1330,44 +1400,43 @@ static bool hasNonTrivialConstructorCall(const CXXNewExpr *NE) { return false; } -void MallocChecker::processNewAllocation(const CXXNewExpr *NE, - CheckerContext &C, - SVal Target) const { - if (!MemFunctionInfo.isStandardNewDelete(NE->getOperatorNew(), - C.getASTContext())) - return; +ProgramStateRef +MallocChecker::processNewAllocation(const CXXAllocatorCall &Call, + CheckerContext &C, + AllocationFamily Family) const { + if (!isStandardNewDelete(Call)) + return nullptr; + const CXXNewExpr *NE = Call.getOriginExpr(); const ParentMap &PM = C.getLocationContext()->getParentMap(); + ProgramStateRef State = C.getState(); // Non-trivial constructors have a chance to escape 'this', but marking all // invocations of trivial constructors as escaped would cause too great of // reduction of true positives, so let's just do that for constructors that // have an argument of a pointer-to-record type. if (!PM.isConsumedExpr(NE) && hasNonTrivialConstructorCall(NE)) - return; + return State; - ProgramStateRef State = C.getState(); // The return value from operator new is bound to a specified initialization // value (if any) and we don't want to loose this value. So we call // MallocUpdateRefState() instead of MallocMemAux() which breaks the // existing binding. - State = MallocUpdateRefState(C, NE, State, NE->isArray() ? AF_CXXNewArray - : AF_CXXNew, Target); + SVal Target = Call.getObjectUnderConstruction(); + State = MallocUpdateRefState(C, NE, State, Family, Target); State = addExtentSize(C, NE, State, Target); - State = ProcessZeroAllocCheck(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)); + State = ProcessZeroAllocCheck(Call, 0, State, Target); + return State; } -void MallocChecker::checkNewAllocator(const CXXNewExpr *NE, SVal Target, +void MallocChecker::checkNewAllocator(const CXXAllocatorCall &Call, CheckerContext &C) const { - if (!C.wasInlined) - processNewAllocation(NE, C, Target); + if (!C.wasInlined) { + ProgramStateRef State = processNewAllocation( + Call, C, + (Call.getOriginExpr()->isArray() ? AF_CXXNewArray : AF_CXXNew)); + C.addTransition(State); + } } // Sets the extent value of the MemRegion allocated by @@ -1402,38 +1471,20 @@ ProgramStateRef MallocChecker::addExtentSize(CheckerContext &C, CharUnits TypeSize = AstContext.getTypeSizeInChars(ElementType); if (ElementCount.getAs<NonLoc>()) { - DefinedOrUnknownSVal Extent = Region->getExtent(svalBuilder); + DefinedOrUnknownSVal DynSize = getDynamicSize(State, Region, svalBuilder); + // size in Bytes = ElementCount*TypeSize SVal SizeInBytes = svalBuilder.evalBinOpNN( State, BO_Mul, ElementCount.castAs<NonLoc>(), svalBuilder.makeArrayIndex(TypeSize.getQuantity()), svalBuilder.getArrayIndexType()); - DefinedOrUnknownSVal extentMatchesSize = svalBuilder.evalEQ( - State, Extent, SizeInBytes.castAs<DefinedOrUnknownSVal>()); - State = State->assume(extentMatchesSize, true); + DefinedOrUnknownSVal DynSizeMatchesSize = svalBuilder.evalEQ( + State, DynSize, SizeInBytes.castAs<DefinedOrUnknownSVal>()); + State = State->assume(DynSizeMatchesSize, true); } return State; } -void MallocChecker::checkPreStmt(const CXXDeleteExpr *DE, - CheckerContext &C) const { - - if (!ChecksEnabled[CK_NewDeleteChecker]) - if (SymbolRef Sym = C.getSVal(DE->getArgument()).getAsSymbol()) - checkUseAfterFree(Sym, C, DE->getArgument()); - - if (!MemFunctionInfo.isStandardNewDelete(DE->getOperatorDelete(), - C.getASTContext())) - return; - - ProgramStateRef State = C.getState(); - bool IsKnownToBeAllocated; - State = FreeMemAux(C, DE->getArgument(), DE, State, - /*Hold*/ false, IsKnownToBeAllocated); - - C.addTransition(State); -} - static bool isKnownDeallocObjCMethodName(const ObjCMethodCall &Call) { // If the first selector piece is one of the names below, assume that the // object takes ownership of the memory, promising to eventually deallocate it @@ -1474,50 +1525,52 @@ void MallocChecker::checkPostObjCMessage(const ObjCMethodCall &Call, bool IsKnownToBeAllocatedMemory; ProgramStateRef State = - FreeMemAux(C, Call.getArgExpr(0), Call.getOriginExpr(), C.getState(), - /*Hold=*/true, IsKnownToBeAllocatedMemory, + FreeMemAux(C, Call.getArgExpr(0), Call, C.getState(), + /*Hold=*/true, IsKnownToBeAllocatedMemory, AF_Malloc, /*RetNullOnFailure=*/true); C.addTransition(State); } ProgramStateRef -MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallExpr *CE, +MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call, const OwnershipAttr *Att, ProgramStateRef State) const { if (!State) return nullptr; - if (Att->getModule() != MemFunctionInfo.II_malloc) + if (Att->getModule()->getName() != "malloc") return nullptr; OwnershipAttr::args_iterator I = Att->args_begin(), E = Att->args_end(); if (I != E) { - return MallocMemAux(C, CE, CE->getArg(I->getASTIndex()), UndefinedVal(), - State); + return MallocMemAux(C, Call, Call.getArgExpr(I->getASTIndex()), + UndefinedVal(), State, AF_Malloc); } - return MallocMemAux(C, CE, UnknownVal(), UndefinedVal(), State); + return MallocMemAux(C, Call, UnknownVal(), UndefinedVal(), State, AF_Malloc); } ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, - const CallExpr *CE, + const CallEvent &Call, const Expr *SizeEx, SVal Init, ProgramStateRef State, AllocationFamily Family) { if (!State) return nullptr; - return MallocMemAux(C, CE, C.getSVal(SizeEx), Init, State, Family); + assert(SizeEx); + return MallocMemAux(C, Call, C.getSVal(SizeEx), Init, State, Family); } ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, - const CallExpr *CE, - SVal Size, SVal Init, - ProgramStateRef State, - AllocationFamily Family) { + const CallEvent &Call, SVal Size, + SVal Init, ProgramStateRef State, + AllocationFamily Family) { if (!State) return nullptr; + const Expr *CE = Call.getOriginExpr(); + // We expect the malloc functions to return a pointer. if (!Loc::isLocType(CE->getType())) return nullptr; @@ -1542,12 +1595,12 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, return nullptr; if (Optional<DefinedOrUnknownSVal> DefinedSize = Size.getAs<DefinedOrUnknownSVal>()) { - SValBuilder &svalBuilder = C.getSValBuilder(); - DefinedOrUnknownSVal Extent = R->getExtent(svalBuilder); - DefinedOrUnknownSVal extentMatchesSize = - svalBuilder.evalEQ(State, Extent, *DefinedSize); + DefinedOrUnknownSVal DynSize = getDynamicSize(State, R, svalBuilder); + + DefinedOrUnknownSVal DynSizeMatchesSize = + svalBuilder.evalEQ(State, DynSize, *DefinedSize); - State = State->assume(extentMatchesSize, true); + State = State->assume(DynSizeMatchesSize, true); assert(State); } @@ -1579,39 +1632,42 @@ static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, } ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C, - const CallExpr *CE, + const CallEvent &Call, const OwnershipAttr *Att, ProgramStateRef State) const { if (!State) return nullptr; - if (Att->getModule() != MemFunctionInfo.II_malloc) + if (Att->getModule()->getName() != "malloc") return nullptr; bool IsKnownToBeAllocated = false; for (const auto &Arg : Att->args()) { - ProgramStateRef StateI = FreeMemAux( - C, CE, State, Arg.getASTIndex(), - Att->getOwnKind() == OwnershipAttr::Holds, IsKnownToBeAllocated); + ProgramStateRef StateI = + FreeMemAux(C, Call, State, Arg.getASTIndex(), + Att->getOwnKind() == OwnershipAttr::Holds, + IsKnownToBeAllocated, AF_Malloc); if (StateI) State = StateI; } return State; } -ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, const CallExpr *CE, +ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, + const CallEvent &Call, ProgramStateRef State, unsigned Num, bool Hold, bool &IsKnownToBeAllocated, + AllocationFamily Family, bool ReturnsNullOnFailure) const { if (!State) return nullptr; - if (CE->getNumArgs() < (Num + 1)) + if (Call.getNumArgs() < (Num + 1)) return nullptr; - return FreeMemAux(C, CE->getArg(Num), CE, State, Hold, IsKnownToBeAllocated, - ReturnsNullOnFailure); + return FreeMemAux(C, Call.getArgExpr(Num), Call, State, Hold, + IsKnownToBeAllocated, Family, ReturnsNullOnFailure); } /// Checks if the previous call to free on the given symbol failed - if free @@ -1629,58 +1685,7 @@ static bool didPreviousFreeFail(ProgramStateRef State, return false; } -static AllocationFamily -getAllocationFamily(const MemFunctionInfoTy &MemFunctionInfo, CheckerContext &C, - const Stmt *S) { - - if (!S) - return AF_None; - - if (const CallExpr *CE = dyn_cast<CallExpr>(S)) { - const FunctionDecl *FD = C.getCalleeDecl(CE); - - if (!FD) - FD = dyn_cast<FunctionDecl>(CE->getCalleeDecl()); - - ASTContext &Ctx = C.getASTContext(); - - if (MemFunctionInfo.isCMemFunction(FD, Ctx, AF_Malloc, - MemoryOperationKind::MOK_Any)) - return AF_Malloc; - - if (MemFunctionInfo.isStandardNewDelete(FD, Ctx)) { - OverloadedOperatorKind Kind = FD->getOverloadedOperator(); - if (Kind == OO_New || Kind == OO_Delete) - return AF_CXXNew; - else if (Kind == OO_Array_New || Kind == OO_Array_Delete) - return AF_CXXNewArray; - } - - if (MemFunctionInfo.isCMemFunction(FD, Ctx, AF_IfNameIndex, - MemoryOperationKind::MOK_Any)) - return AF_IfNameIndex; - - if (MemFunctionInfo.isCMemFunction(FD, Ctx, AF_Alloca, - MemoryOperationKind::MOK_Any)) - return AF_Alloca; - - return AF_None; - } - - if (const CXXNewExpr *NE = dyn_cast<CXXNewExpr>(S)) - return NE->isArray() ? AF_CXXNewArray : AF_CXXNew; - - if (const CXXDeleteExpr *DE = dyn_cast<CXXDeleteExpr>(S)) - return DE->isArrayForm() ? AF_CXXNewArray : AF_CXXNew; - - if (isa<ObjCMessageExpr>(S)) - return AF_Malloc; - - return AF_None; -} - -static bool printAllocDeallocName(raw_ostream &os, CheckerContext &C, - const Expr *E) { +static bool printMemFnName(raw_ostream &os, CheckerContext &C, const Expr *E) { if (const CallExpr *CE = dyn_cast<CallExpr>(E)) { // FIXME: This doesn't handle indirect calls. const FunctionDecl *FD = CE->getDirectCallee(); @@ -1719,10 +1724,7 @@ static bool printAllocDeallocName(raw_ostream &os, CheckerContext &C, return false; } -static void printExpectedAllocName(raw_ostream &os, - const MemFunctionInfoTy &MemFunctionInfo, - CheckerContext &C, const Expr *E) { - AllocationFamily Family = getAllocationFamily(MemFunctionInfo, C, E); +static void printExpectedAllocName(raw_ostream &os, AllocationFamily Family) { switch(Family) { case AF_Malloc: os << "malloc()"; return; @@ -1747,12 +1749,10 @@ static void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) { } } -ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, - const Expr *ArgExpr, - const Expr *ParentExpr, - ProgramStateRef State, bool Hold, - bool &IsKnownToBeAllocated, - bool ReturnsNullOnFailure) const { +ProgramStateRef MallocChecker::FreeMemAux( + CheckerContext &C, const Expr *ArgExpr, const CallEvent &Call, + ProgramStateRef State, bool Hold, bool &IsKnownToBeAllocated, + AllocationFamily Family, bool ReturnsNullOnFailure) const { if (!State) return nullptr; @@ -1778,11 +1778,28 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, return nullptr; const MemRegion *R = ArgVal.getAsRegion(); + const Expr *ParentExpr = Call.getOriginExpr(); + + // NOTE: We detected a bug, but the checker under whose name we would emit the + // error could be disabled. Generally speaking, the MallocChecker family is an + // integral part of the Static Analyzer, and disabling any part of it should + // only be done under exceptional circumstances, such as frequent false + // positives. If this is the case, we can reasonably believe that there are + // serious faults in our understanding of the source code, and even if we + // don't emit an warning, we should terminate further analysis with a sink + // node. // Nonlocs can't be freed, of course. // Non-region locations (labels and fixed addresses) also shouldn't be freed. if (!R) { - ReportBadFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr); + // Exception: + // If the macro ZERO_SIZE_PTR is defined, this could be a kernel source + // code. In that case, the ZERO_SIZE_PTR defines a special value used for a + // zero-sized memory block which is allowed to be freed, despite not being a + // null pointer. + if (Family != AF_Malloc || !isArgZERO_SIZE_PTR(State, C, ArgVal)) + HandleNonHeapDealloc(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, + Family); return nullptr; } @@ -1790,7 +1807,8 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, // Blocks might show up as heap data, but should not be free()d if (isa<BlockDataRegion>(R)) { - ReportBadFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr); + HandleNonHeapDealloc(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, + Family); return nullptr; } @@ -1808,9 +1826,10 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, // False negatives are better than false positives. if (isa<AllocaRegion>(R)) - ReportFreeAlloca(C, ArgVal, ArgExpr->getSourceRange()); + HandleFreeAlloca(C, ArgVal, ArgExpr->getSourceRange()); else - ReportBadFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr); + HandleNonHeapDealloc(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, + Family); return nullptr; } @@ -1832,14 +1851,14 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, // Memory returned by alloca() shouldn't be freed. if (RsBase->getAllocationFamily() == AF_Alloca) { - ReportFreeAlloca(C, ArgVal, ArgExpr->getSourceRange()); + HandleFreeAlloca(C, ArgVal, ArgExpr->getSourceRange()); return nullptr; } // Check for double free first. if ((RsBase->isReleased() || RsBase->isRelinquished()) && !didPreviousFreeFail(State, SymBase, PreviousRetStatusSymbol)) { - ReportDoubleFree(C, ParentExpr->getSourceRange(), RsBase->isReleased(), + HandleDoubleFree(C, ParentExpr->getSourceRange(), RsBase->isReleased(), SymBase, PreviousRetStatusSymbol); return nullptr; @@ -1849,12 +1868,10 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, RsBase->isEscaped()) { // Check if an expected deallocation function matches the real one. - bool DeallocMatchesAlloc = - RsBase->getAllocationFamily() == - getAllocationFamily(MemFunctionInfo, C, ParentExpr); + bool DeallocMatchesAlloc = RsBase->getAllocationFamily() == Family; if (!DeallocMatchesAlloc) { - ReportMismatchedDealloc(C, ArgExpr->getSourceRange(), - ParentExpr, RsBase, SymBase, Hold); + HandleMismatchedDealloc(C, ArgExpr->getSourceRange(), ParentExpr, + RsBase, SymBase, Hold); return nullptr; } @@ -1865,15 +1882,16 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, !Offset.hasSymbolicOffset() && Offset.getOffset() != 0) { const Expr *AllocExpr = cast<Expr>(RsBase->getStmt()); - ReportOffsetFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, - AllocExpr); + HandleOffsetFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, + Family, AllocExpr); return nullptr; } } } if (SymBase->getType()->isFunctionPointerType()) { - ReportFunctionPointerFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr); + HandleFunctionPtrFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, + Family); return nullptr; } @@ -1891,9 +1909,12 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, } } - AllocationFamily Family = - RsBase ? RsBase->getAllocationFamily() - : getAllocationFamily(MemFunctionInfo, C, ParentExpr); + // If we don't know anything about this symbol, a free on it may be totally + // valid. If this is the case, lets assume that the allocation family of the + // freeing function is the same as the symbols allocation family, and go with + // that. + assert(!RsBase || (RsBase && RsBase->getAllocationFamily() == Family)); + // Normal free. if (Hold) return State->set<RegionState>(SymBase, @@ -1940,14 +1961,6 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family, } Optional<MallocChecker::CheckKind> -MallocChecker::getCheckIfTracked(CheckerContext &C, - const Stmt *AllocDeallocStmt, - bool IsALeakCheck) const { - return getCheckIfTracked( - getAllocationFamily(MemFunctionInfo, C, AllocDeallocStmt), IsALeakCheck); -} - -Optional<MallocChecker::CheckKind> MallocChecker::getCheckIfTracked(CheckerContext &C, SymbolRef Sym, bool IsALeakCheck) const { if (C.getState()->contains<ReallocSizeZeroSymbols>(Sym)) @@ -2045,16 +2058,17 @@ bool MallocChecker::SummarizeRegion(raw_ostream &os, } } -void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal, - SourceRange Range, - const Expr *DeallocExpr) const { +void MallocChecker::HandleNonHeapDealloc(CheckerContext &C, SVal ArgVal, + SourceRange Range, + const Expr *DeallocExpr, + AllocationFamily Family) const { - if (!ChecksEnabled[CK_MallocChecker] && - !ChecksEnabled[CK_NewDeleteChecker]) + if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteChecker]) { + C.addSink(); return; + } - Optional<MallocChecker::CheckKind> CheckKind = - getCheckIfTracked(C, DeallocExpr); + Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family); if (!CheckKind.hasValue()) return; @@ -2071,7 +2085,7 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal, MR = ER->getSuperRegion(); os << "Argument to "; - if (!printAllocDeallocName(os, C, DeallocExpr)) + if (!printMemFnName(os, C, DeallocExpr)) os << "deallocator"; os << " is "; @@ -2082,7 +2096,7 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal, else os << "not memory allocated by "; - printExpectedAllocName(os, MemFunctionInfo, C, DeallocExpr); + printExpectedAllocName(os, Family); auto R = std::make_unique<PathSensitiveBugReport>(*BT_BadFree[*CheckKind], os.str(), N); @@ -2092,7 +2106,7 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal, } } -void MallocChecker::ReportFreeAlloca(CheckerContext &C, SVal ArgVal, +void MallocChecker::HandleFreeAlloca(CheckerContext &C, SVal ArgVal, SourceRange Range) const { Optional<MallocChecker::CheckKind> CheckKind; @@ -2101,8 +2115,10 @@ void MallocChecker::ReportFreeAlloca(CheckerContext &C, SVal ArgVal, CheckKind = CK_MallocChecker; else if (ChecksEnabled[CK_MismatchedDeallocatorChecker]) CheckKind = CK_MismatchedDeallocatorChecker; - else + else { + C.addSink(); return; + } if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_FreeAlloca[*CheckKind]) @@ -2118,15 +2134,16 @@ void MallocChecker::ReportFreeAlloca(CheckerContext &C, SVal ArgVal, } } -void MallocChecker::ReportMismatchedDealloc(CheckerContext &C, +void MallocChecker::HandleMismatchedDealloc(CheckerContext &C, SourceRange Range, const Expr *DeallocExpr, - const RefState *RS, - SymbolRef Sym, + const RefState *RS, SymbolRef Sym, bool OwnershipTransferred) const { - if (!ChecksEnabled[CK_MismatchedDeallocatorChecker]) + if (!ChecksEnabled[CK_MismatchedDeallocatorChecker]) { + C.addSink(); return; + } if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_MismatchedDealloc) @@ -2144,25 +2161,25 @@ void MallocChecker::ReportMismatchedDealloc(CheckerContext &C, llvm::raw_svector_ostream DeallocOs(DeallocBuf); if (OwnershipTransferred) { - if (printAllocDeallocName(DeallocOs, C, DeallocExpr)) + if (printMemFnName(DeallocOs, C, DeallocExpr)) os << DeallocOs.str() << " cannot"; else os << "Cannot"; os << " take ownership of memory"; - if (printAllocDeallocName(AllocOs, C, AllocExpr)) + if (printMemFnName(AllocOs, C, AllocExpr)) os << " allocated by " << AllocOs.str(); } else { os << "Memory"; - if (printAllocDeallocName(AllocOs, C, AllocExpr)) + if (printMemFnName(AllocOs, C, AllocExpr)) os << " allocated by " << AllocOs.str(); os << " should be deallocated by "; printExpectedDeallocName(os, RS->getAllocationFamily()); - if (printAllocDeallocName(DeallocOs, C, DeallocExpr)) - os << ", not " << DeallocOs.str(); + if (printMemFnName(DeallocOs, C, DeallocExpr)) + os << ", not " << DeallocOs.str(); } auto R = std::make_unique<PathSensitiveBugReport>(*BT_MismatchedDealloc, @@ -2174,17 +2191,17 @@ void MallocChecker::ReportMismatchedDealloc(CheckerContext &C, } } -void MallocChecker::ReportOffsetFree(CheckerContext &C, SVal ArgVal, +void MallocChecker::HandleOffsetFree(CheckerContext &C, SVal ArgVal, SourceRange Range, const Expr *DeallocExpr, + AllocationFamily Family, const Expr *AllocExpr) const { - - if (!ChecksEnabled[CK_MallocChecker] && - !ChecksEnabled[CK_NewDeleteChecker]) + if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteChecker]) { + C.addSink(); return; + } - Optional<MallocChecker::CheckKind> CheckKind = - getCheckIfTracked(C, AllocExpr); + Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family); if (!CheckKind.hasValue()) return; @@ -2213,14 +2230,14 @@ void MallocChecker::ReportOffsetFree(CheckerContext &C, SVal ArgVal, int offsetBytes = Offset.getOffset() / C.getASTContext().getCharWidth(); os << "Argument to "; - if (!printAllocDeallocName(os, C, DeallocExpr)) + if (!printMemFnName(os, C, DeallocExpr)) os << "deallocator"; os << " is offset by " << offsetBytes << " " << ((abs(offsetBytes) > 1) ? "bytes" : "byte") << " from the start of "; - if (AllocExpr && printAllocDeallocName(AllocNameOs, C, AllocExpr)) + if (AllocExpr && printMemFnName(AllocNameOs, C, AllocExpr)) os << "memory allocated by " << AllocNameOs.str(); else os << "allocated memory"; @@ -2232,13 +2249,14 @@ void MallocChecker::ReportOffsetFree(CheckerContext &C, SVal ArgVal, C.emitReport(std::move(R)); } -void MallocChecker::ReportUseAfterFree(CheckerContext &C, SourceRange Range, +void MallocChecker::HandleUseAfterFree(CheckerContext &C, SourceRange Range, SymbolRef Sym) const { - if (!ChecksEnabled[CK_MallocChecker] && - !ChecksEnabled[CK_NewDeleteChecker] && - !ChecksEnabled[CK_InnerPointerChecker]) + if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteChecker] && + !ChecksEnabled[CK_InnerPointerChecker]) { + C.addSink(); return; + } Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); if (!CheckKind.hasValue()) @@ -2270,13 +2288,14 @@ void MallocChecker::ReportUseAfterFree(CheckerContext &C, SourceRange Range, } } -void MallocChecker::ReportDoubleFree(CheckerContext &C, SourceRange Range, +void MallocChecker::HandleDoubleFree(CheckerContext &C, SourceRange Range, bool Released, SymbolRef Sym, SymbolRef PrevSym) const { - if (!ChecksEnabled[CK_MallocChecker] && - !ChecksEnabled[CK_NewDeleteChecker]) + if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteChecker]) { + C.addSink(); return; + } Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); if (!CheckKind.hasValue()) @@ -2301,10 +2320,12 @@ void MallocChecker::ReportDoubleFree(CheckerContext &C, SourceRange Range, } } -void MallocChecker::ReportDoubleDelete(CheckerContext &C, SymbolRef Sym) const { +void MallocChecker::HandleDoubleDelete(CheckerContext &C, SymbolRef Sym) const { - if (!ChecksEnabled[CK_NewDeleteChecker]) + if (!ChecksEnabled[CK_NewDeleteChecker]) { + C.addSink(); return; + } Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); if (!CheckKind.hasValue()) @@ -2325,13 +2346,13 @@ void MallocChecker::ReportDoubleDelete(CheckerContext &C, SymbolRef Sym) const { } } -void MallocChecker::ReportUseZeroAllocated(CheckerContext &C, - SourceRange Range, - SymbolRef Sym) const { +void MallocChecker::HandleUseZeroAlloc(CheckerContext &C, SourceRange Range, + SymbolRef Sym) const { - if (!ChecksEnabled[CK_MallocChecker] && - !ChecksEnabled[CK_NewDeleteChecker]) + if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteChecker]) { + C.addSink(); return; + } Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); @@ -2356,13 +2377,16 @@ void MallocChecker::ReportUseZeroAllocated(CheckerContext &C, } } -void MallocChecker::ReportFunctionPointerFree(CheckerContext &C, SVal ArgVal, - SourceRange Range, - const Expr *FreeExpr) const { - if (!ChecksEnabled[CK_MallocChecker]) +void MallocChecker::HandleFunctionPtrFree(CheckerContext &C, SVal ArgVal, + SourceRange Range, + const Expr *FreeExpr, + AllocationFamily Family) const { + if (!ChecksEnabled[CK_MallocChecker]) { + C.addSink(); return; + } - Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, FreeExpr); + Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family); if (!CheckKind.hasValue()) return; @@ -2379,7 +2403,7 @@ void MallocChecker::ReportFunctionPointerFree(CheckerContext &C, SVal ArgVal, MR = ER->getSuperRegion(); Os << "Argument to "; - if (!printAllocDeallocName(Os, C, FreeExpr)) + if (!printMemFnName(Os, C, FreeExpr)) Os << "deallocator"; Os << " is a function pointer"; @@ -2392,14 +2416,15 @@ void MallocChecker::ReportFunctionPointerFree(CheckerContext &C, SVal ArgVal, } } -ProgramStateRef MallocChecker::ReallocMemAux(CheckerContext &C, - const CallExpr *CE, - bool ShouldFreeOnFail, - ProgramStateRef State, - bool SuffixWithN) const { +ProgramStateRef +MallocChecker::ReallocMemAux(CheckerContext &C, const CallEvent &Call, + bool ShouldFreeOnFail, ProgramStateRef State, + AllocationFamily Family, bool SuffixWithN) const { if (!State) return nullptr; + const CallExpr *CE = cast<CallExpr>(Call.getOriginExpr()); + if (SuffixWithN && CE->getNumArgs() < 3) return nullptr; else if (CE->getNumArgs() < 2) @@ -2443,21 +2468,15 @@ ProgramStateRef MallocChecker::ReallocMemAux(CheckerContext &C, // If the ptr is NULL and the size is not 0, the call is equivalent to // malloc(size). if (PrtIsNull && !SizeIsZero) { - ProgramStateRef stateMalloc = MallocMemAux(C, CE, TotalSize, - UndefinedVal(), StatePtrIsNull); + ProgramStateRef stateMalloc = MallocMemAux( + C, Call, TotalSize, UndefinedVal(), StatePtrIsNull, Family); return stateMalloc; } if (PrtIsNull && SizeIsZero) return State; - // Get the from and to pointer symbols as in toPtr = realloc(fromPtr, size). assert(!PrtIsNull); - SymbolRef FromPtr = arg0Val.getAsSymbol(); - SVal RetVal = C.getSVal(CE); - SymbolRef ToPtr = RetVal.getAsSymbol(); - if (!FromPtr || !ToPtr) - return nullptr; bool IsKnownToBeAllocated = false; @@ -2467,16 +2486,16 @@ ProgramStateRef MallocChecker::ReallocMemAux(CheckerContext &C, // If size was equal to 0, either NULL or a pointer suitable to be passed // to free() is returned. We just free the input pointer and do not add // any constrains on the output pointer. - if (ProgramStateRef stateFree = - FreeMemAux(C, CE, StateSizeIsZero, 0, false, IsKnownToBeAllocated)) + if (ProgramStateRef stateFree = FreeMemAux( + C, Call, StateSizeIsZero, 0, false, IsKnownToBeAllocated, Family)) return stateFree; // Default behavior. if (ProgramStateRef stateFree = - FreeMemAux(C, CE, State, 0, false, IsKnownToBeAllocated)) { + FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocated, Family)) { - ProgramStateRef stateRealloc = MallocMemAux(C, CE, TotalSize, - UnknownVal(), stateFree); + ProgramStateRef stateRealloc = + MallocMemAux(C, Call, TotalSize, UnknownVal(), stateFree, Family); if (!stateRealloc) return nullptr; @@ -2486,6 +2505,14 @@ ProgramStateRef MallocChecker::ReallocMemAux(CheckerContext &C, else if (!IsKnownToBeAllocated) Kind = OAR_DoNotTrackAfterFailure; + // Get the from and to pointer symbols as in toPtr = realloc(fromPtr, size). + SymbolRef FromPtr = arg0Val.getLocSymbolInBase(); + SVal RetVal = C.getSVal(CE); + SymbolRef ToPtr = RetVal.getAsSymbol(); + assert(FromPtr && ToPtr && + "By this point, FreeMemAux and MallocMemAux should have checked " + "whether the argument or the return value is symbolic!"); + // Record the info about the reallocated symbol so that we could properly // process failed reallocation. stateRealloc = stateRealloc->set<ReallocPairs>(ToPtr, @@ -2497,19 +2524,21 @@ ProgramStateRef MallocChecker::ReallocMemAux(CheckerContext &C, return nullptr; } -ProgramStateRef MallocChecker::CallocMem(CheckerContext &C, const CallExpr *CE, +ProgramStateRef MallocChecker::CallocMem(CheckerContext &C, + const CallEvent &Call, ProgramStateRef State) { if (!State) return nullptr; - if (CE->getNumArgs() < 2) + if (Call.getNumArgs() < 2) return nullptr; SValBuilder &svalBuilder = C.getSValBuilder(); SVal zeroVal = svalBuilder.makeZeroVal(svalBuilder.getContext().CharTy); - SVal TotalSize = evalMulForBufferSize(C, CE->getArg(0), CE->getArg(1)); + SVal TotalSize = + evalMulForBufferSize(C, Call.getArgExpr(0), Call.getArgExpr(1)); - return MallocMemAux(C, CE, TotalSize, zeroVal, State); + return MallocMemAux(C, Call, TotalSize, zeroVal, State, AF_Malloc); } MallocChecker::LeakInfo MallocChecker::getAllocationSite(const ExplodedNode *N, @@ -2553,7 +2582,7 @@ MallocChecker::LeakInfo MallocChecker::getAllocationSite(const ExplodedNode *N, return LeakInfo(AllocNode, ReferenceRegion); } -void MallocChecker::reportLeak(SymbolRef Sym, ExplodedNode *N, +void MallocChecker::HandleLeak(SymbolRef Sym, ExplodedNode *N, CheckerContext &C) const { if (!ChecksEnabled[CK_MallocChecker] && @@ -2669,7 +2698,7 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, if (N) { for (SmallVectorImpl<SymbolRef>::iterator I = Errors.begin(), E = Errors.end(); I != E; ++I) { - reportLeak(*I, N, C); + HandleLeak(*I, N, C); } } } @@ -2680,7 +2709,27 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, void MallocChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { - if (const CXXDestructorCall *DC = dyn_cast<CXXDestructorCall>(&Call)) { + if (const auto *DC = dyn_cast<CXXDeallocatorCall>(&Call)) { + const CXXDeleteExpr *DE = DC->getOriginExpr(); + + if (!ChecksEnabled[CK_NewDeleteChecker]) + if (SymbolRef Sym = C.getSVal(DE->getArgument()).getAsSymbol()) + checkUseAfterFree(Sym, C, DE->getArgument()); + + if (!isStandardNewDelete(DC->getDecl())) + return; + + ProgramStateRef State = C.getState(); + bool IsKnownToBeAllocated; + State = FreeMemAux(C, DE->getArgument(), Call, State, + /*Hold*/ false, IsKnownToBeAllocated, + (DE->isArrayForm() ? AF_CXXNewArray : AF_CXXNew)); + + C.addTransition(State); + return; + } + + if (const auto *DC = dyn_cast<CXXDestructorCall>(&Call)) { SymbolRef Sym = DC->getCXXThisVal().getAsSymbol(); if (!Sym || checkDoubleDelete(Sym, C)) return; @@ -2692,12 +2741,7 @@ void MallocChecker::checkPreCall(const CallEvent &Call, if (!FD) return; - ASTContext &Ctx = C.getASTContext(); - if (ChecksEnabled[CK_MallocChecker] && - (MemFunctionInfo.isCMemFunction(FD, Ctx, AF_Malloc, - MemoryOperationKind::MOK_Free) || - MemFunctionInfo.isCMemFunction(FD, Ctx, AF_IfNameIndex, - MemoryOperationKind::MOK_Free))) + if (ChecksEnabled[CK_MallocChecker] && isFreeingCall(Call)) return; } @@ -2807,8 +2851,8 @@ static bool isReleased(SymbolRef Sym, CheckerContext &C) { } bool MallocChecker::suppressDeallocationsInSuspiciousContexts( - const CallExpr *CE, CheckerContext &C) const { - if (CE->getNumArgs() == 0) + const CallEvent &Call, CheckerContext &C) const { + if (Call.getNumArgs() == 0) return false; StringRef FunctionStr = ""; @@ -2826,7 +2870,7 @@ bool MallocChecker::suppressDeallocationsInSuspiciousContexts( ProgramStateRef State = C.getState(); - for (const Expr *Arg : CE->arguments()) + for (const Expr *Arg : cast<CallExpr>(Call.getOriginExpr())->arguments()) if (SymbolRef Sym = C.getSVal(Arg).getAsSymbol()) if (const RefState *RS = State->get<RegionState>(Sym)) State = State->set<RegionState>(Sym, RefState::getEscaped(RS)); @@ -2839,7 +2883,7 @@ bool MallocChecker::checkUseAfterFree(SymbolRef Sym, CheckerContext &C, const Stmt *S) const { if (isReleased(Sym, C)) { - ReportUseAfterFree(C, S->getSourceRange(), Sym); + HandleUseAfterFree(C, S->getSourceRange(), Sym); return true; } @@ -2852,17 +2896,17 @@ void MallocChecker::checkUseZeroAllocated(SymbolRef Sym, CheckerContext &C, if (const RefState *RS = C.getState()->get<RegionState>(Sym)) { if (RS->isAllocatedOfSizeZero()) - ReportUseZeroAllocated(C, RS->getStmt()->getSourceRange(), Sym); + HandleUseZeroAlloc(C, RS->getStmt()->getSourceRange(), Sym); } else if (C.getState()->contains<ReallocSizeZeroSymbols>(Sym)) { - ReportUseZeroAllocated(C, S->getSourceRange(), Sym); + HandleUseZeroAlloc(C, S->getSourceRange(), Sym); } } bool MallocChecker::checkDoubleDelete(SymbolRef Sym, CheckerContext &C) const { if (isReleased(Sym, C)) { - ReportDoubleDelete(C, Sym); + HandleDoubleDelete(C, Sym); return true; } return false; @@ -2994,11 +3038,9 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly( if (!FD) return true; - ASTContext &ASTC = State->getStateManager().getContext(); - // If it's one of the allocation functions we can reason about, we model // its behavior explicitly. - if (MemFunctionInfo.isMemFunction(FD, ASTC)) + if (isMemCall(*Call)) return false; // If it's not a system call, assume it frees memory. @@ -3142,6 +3184,18 @@ ProgramStateRef MallocChecker::checkPointerEscapeAux( return State; } +bool MallocChecker::isArgZERO_SIZE_PTR(ProgramStateRef State, CheckerContext &C, + SVal ArgVal) const { + if (!KernelZeroSizePtrValue) + KernelZeroSizePtrValue = + tryExpandAsInteger("ZERO_SIZE_PTR", C.getPreprocessor()); + + const llvm::APSInt *ArgValKnown = + C.getSValBuilder().getKnownValue(State, ArgVal); + return ArgValKnown && *KernelZeroSizePtrValue && + ArgValKnown->getSExtValue() == **KernelZeroSizePtrValue; +} + static SymbolRef findFailedReallocSymbol(ProgramStateRef currState, ProgramStateRef prevState) { ReallocPairsTy currMap = currState->get<ReallocPairs>(); @@ -3404,11 +3458,11 @@ void ento::registerInnerPointerCheckerAux(CheckerManager &mgr) { void ento::registerDynamicMemoryModeling(CheckerManager &mgr) { auto *checker = mgr.registerChecker<MallocChecker>(); - checker->MemFunctionInfo.ShouldIncludeOwnershipAnnotatedFunctions = + checker->ShouldIncludeOwnershipAnnotatedFunctions = mgr.getAnalyzerOptions().getCheckerBooleanOption(checker, "Optimistic"); } -bool ento::shouldRegisterDynamicMemoryModeling(const LangOptions &LO) { +bool ento::shouldRegisterDynamicMemoryModeling(const CheckerManager &mgr) { return true; } @@ -3420,7 +3474,7 @@ bool ento::shouldRegisterDynamicMemoryModeling(const LangOptions &LO) { mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name(const LangOptions &LO) { return true; } + bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; } REGISTER_CHECKER(MallocChecker) REGISTER_CHECKER(NewDeleteChecker) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp index 4fd06f24c5bc..e31630f63b5a 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp @@ -337,6 +337,6 @@ void ento::registerMallocOverflowSecurityChecker(CheckerManager &mgr) { mgr.registerChecker<MallocOverflowSecurityChecker>(); } -bool ento::shouldRegisterMallocOverflowSecurityChecker(const LangOptions &LO) { +bool ento::shouldRegisterMallocOverflowSecurityChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp index b5881a9e6533..71f593cb2b56 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp @@ -250,6 +250,6 @@ void ento::registerMallocSizeofChecker(CheckerManager &mgr) { mgr.registerChecker<MallocSizeofChecker>(); } -bool ento::shouldRegisterMallocSizeofChecker(const LangOptions &LO) { +bool ento::shouldRegisterMallocSizeofChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp index 143910588959..1960873599f7 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp @@ -28,7 +28,7 @@ using namespace iterator; namespace { class MismatchedIteratorChecker - : public Checker<check::PreCall> { + : public Checker<check::PreCall, check::PreStmt<BinaryOperator>> { std::unique_ptr<BugType> MismatchedBugType; @@ -47,6 +47,7 @@ public: MismatchedIteratorChecker(); void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkPreStmt(const BinaryOperator *BO, CheckerContext &C) const; }; @@ -141,7 +142,7 @@ void MismatchedIteratorChecker::checkPreCall(const CallEvent &Call, // Example: // template<typename I1, typename I2> // void f(I1 first1, I1 last1, I2 first2, I2 last2); - // + // // In this case the first two arguments to f() must be iterators must belong // to the same container and the last to also to the same container but // not necessarily to the same as the first two. @@ -188,6 +189,17 @@ void MismatchedIteratorChecker::checkPreCall(const CallEvent &Call, } } +void MismatchedIteratorChecker::checkPreStmt(const BinaryOperator *BO, + CheckerContext &C) const { + if (!BO->isComparisonOp()) + return; + + ProgramStateRef State = C.getState(); + SVal LVal = State->getSVal(BO->getLHS(), C.getLocationContext()); + SVal RVal = State->getSVal(BO->getRHS(), C.getLocationContext()); + verifyMatch(C, LVal, RVal); +} + void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, const SVal &Iter, const MemRegion *Cont) const { // Verify match between a container and the container of an iterator @@ -290,6 +302,6 @@ void ento::registerMismatchedIteratorChecker(CheckerManager &mgr) { mgr.registerChecker<MismatchedIteratorChecker>(); } -bool ento::shouldRegisterMismatchedIteratorChecker(const LangOptions &LO) { +bool ento::shouldRegisterMismatchedIteratorChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp index ceea62160545..5d63d6efd234 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp @@ -88,6 +88,6 @@ void ento::registerMmapWriteExecChecker(CheckerManager &mgr) { .getCheckerIntegerOption(Mwec, "MmapProtRead"); } -bool ento::shouldRegisterMmapWriteExecChecker(const LangOptions &LO) { +bool ento::shouldRegisterMmapWriteExecChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp index 40eb113e3f8e..7f0519c695b0 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp @@ -757,6 +757,6 @@ void ento::registerMoveChecker(CheckerManager &mgr) { mgr.getAnalyzerOptions().getCheckerStringOption(chk, "WarnOn"), mgr); } -bool ento::shouldRegisterMoveChecker(const LangOptions &LO) { +bool ento::shouldRegisterMoveChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp index 41b7fe5e43b6..be17e401fb53 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp @@ -80,6 +80,7 @@ void ento::registerNSAutoreleasePoolChecker(CheckerManager &mgr) { mgr.registerChecker<NSAutoreleasePoolChecker>(); } -bool ento::shouldRegisterNSAutoreleasePoolChecker(const LangOptions &LO) { +bool ento::shouldRegisterNSAutoreleasePoolChecker(const CheckerManager &mgr) { + const LangOptions &LO = mgr.getLangOpts(); return LO.getGC() != LangOptions::NonGC; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp index 85370bf133cd..90c5583d8969 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp @@ -95,6 +95,15 @@ public: }; } +static bool hasReservedReturnType(const FunctionDecl *D) { + if (isa<CXXConstructorDecl>(D)) + return true; + + // operators delete and delete[] are required to have 'void' return type + auto OperatorKind = D->getOverloadedOperator(); + return OperatorKind == OO_Delete || OperatorKind == OO_Array_Delete; +} + void CFErrorFunctionChecker::checkASTDecl(const FunctionDecl *D, AnalysisManager &mgr, BugReporter &BR) const { @@ -102,6 +111,8 @@ void CFErrorFunctionChecker::checkASTDecl(const FunctionDecl *D, return; if (!D->getReturnType()->isVoidType()) return; + if (hasReservedReturnType(D)) + return; if (!II) II = &D->getASTContext().Idents.get("CFErrorRef"); @@ -133,14 +144,14 @@ namespace { class NSErrorDerefBug : public BugType { public: - NSErrorDerefBug(const CheckerBase *Checker) + NSErrorDerefBug(const CheckerNameRef Checker) : BugType(Checker, "NSError** null dereference", "Coding conventions (Apple)") {} }; class CFErrorDerefBug : public BugType { public: - CFErrorDerefBug(const CheckerBase *Checker) + CFErrorDerefBug(const CheckerNameRef Checker) : BugType(Checker, "CFErrorRef* null dereference", "Coding conventions (Apple)") {} }; @@ -155,9 +166,9 @@ class NSOrCFErrorDerefChecker mutable std::unique_ptr<NSErrorDerefBug> NSBT; mutable std::unique_ptr<CFErrorDerefBug> CFBT; public: - bool ShouldCheckNSError, ShouldCheckCFError; - NSOrCFErrorDerefChecker() : NSErrorII(nullptr), CFErrorII(nullptr), - ShouldCheckNSError(0), ShouldCheckCFError(0) { } + DefaultBool ShouldCheckNSError, ShouldCheckCFError; + CheckerNameRef NSErrorName, CFErrorName; + NSOrCFErrorDerefChecker() : NSErrorII(nullptr), CFErrorII(nullptr) {} void checkLocation(SVal loc, bool isLoad, const Stmt *S, CheckerContext &C) const; @@ -265,12 +276,12 @@ void NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event) const { BugType *bug = nullptr; if (isNSError) { if (!NSBT) - NSBT.reset(new NSErrorDerefBug(this)); + NSBT.reset(new NSErrorDerefBug(NSErrorName)); bug = NSBT.get(); } else { if (!CFBT) - CFBT.reset(new CFErrorDerefBug(this)); + CFBT.reset(new CFErrorDerefBug(CFErrorName)); bug = CFBT.get(); } BR.emitReport( @@ -312,7 +323,7 @@ void ento::registerNSOrCFErrorDerefChecker(CheckerManager &mgr) { mgr.registerChecker<NSOrCFErrorDerefChecker>(); } -bool ento::shouldRegisterNSOrCFErrorDerefChecker(const LangOptions &LO) { +bool ento::shouldRegisterNSOrCFErrorDerefChecker(const CheckerManager &mgr) { return true; } @@ -320,9 +331,10 @@ void ento::registerNSErrorChecker(CheckerManager &mgr) { mgr.registerChecker<NSErrorMethodChecker>(); NSOrCFErrorDerefChecker *checker = mgr.getChecker<NSOrCFErrorDerefChecker>(); checker->ShouldCheckNSError = true; + checker->NSErrorName = mgr.getCurrentCheckerName(); } -bool ento::shouldRegisterNSErrorChecker(const LangOptions &LO) { +bool ento::shouldRegisterNSErrorChecker(const CheckerManager &mgr) { return true; } @@ -330,8 +342,9 @@ void ento::registerCFErrorChecker(CheckerManager &mgr) { mgr.registerChecker<CFErrorFunctionChecker>(); NSOrCFErrorDerefChecker *checker = mgr.getChecker<NSOrCFErrorDerefChecker>(); checker->ShouldCheckCFError = true; + checker->CFErrorName = mgr.getCurrentCheckerName(); } -bool ento::shouldRegisterCFErrorChecker(const LangOptions &LO) { +bool ento::shouldRegisterCFErrorChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp index fc34255bf6c9..af208e867318 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp @@ -143,6 +143,6 @@ void ento::registerNoReturnFunctionChecker(CheckerManager &mgr) { mgr.registerChecker<NoReturnFunctionChecker>(); } -bool ento::shouldRegisterNoReturnFunctionChecker(const LangOptions &LO) { +bool ento::shouldRegisterNoReturnFunctionChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp index 6ffc89745365..534b5d68434f 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp @@ -14,57 +14,97 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Attr.h" +#include "clang/Analysis/AnyCall.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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 "llvm/ADT/StringExtras.h" using namespace clang; using namespace ento; namespace { class NonNullParamChecker - : public Checker< check::PreCall, EventDispatcher<ImplicitNullDerefEvent> > { + : public Checker<check::PreCall, check::BeginFunction, + EventDispatcher<ImplicitNullDerefEvent>> { mutable std::unique_ptr<BugType> BTAttrNonNull; mutable std::unique_ptr<BugType> BTNullRefArg; public: - void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkBeginFunction(CheckerContext &C) const; std::unique_ptr<PathSensitiveBugReport> - genReportNullAttrNonNull(const ExplodedNode *ErrorN, - const Expr *ArgE, + genReportNullAttrNonNull(const ExplodedNode *ErrorN, const Expr *ArgE, unsigned IdxOfArg) const; std::unique_ptr<PathSensitiveBugReport> genReportReferenceToNullPointer(const ExplodedNode *ErrorN, const Expr *ArgE) const; }; -} // end anonymous namespace -/// \return Bitvector marking non-null attributes. -static llvm::SmallBitVector getNonNullAttrs(const CallEvent &Call) { +template <class CallType> +void setBitsAccordingToFunctionAttributes(const CallType &Call, + llvm::SmallBitVector &AttrNonNull) { const Decl *FD = Call.getDecl(); - unsigned NumArgs = Call.getNumArgs(); - llvm::SmallBitVector AttrNonNull(NumArgs); + for (const auto *NonNull : FD->specific_attrs<NonNullAttr>()) { if (!NonNull->args_size()) { - AttrNonNull.set(0, NumArgs); + // Lack of attribute parameters means that all of the parameters are + // implicitly marked as non-null. + AttrNonNull.set(); break; } + for (const ParamIdx &Idx : NonNull->args()) { + // 'nonnull' attribute's parameters are 1-based and should be adjusted to + // match actual AST parameter/argument indices. unsigned IdxAST = Idx.getASTIndex(); - if (IdxAST >= NumArgs) + if (IdxAST >= AttrNonNull.size()) continue; AttrNonNull.set(IdxAST); } } +} + +template <class CallType> +void setBitsAccordingToParameterAttributes(const CallType &Call, + llvm::SmallBitVector &AttrNonNull) { + for (const ParmVarDecl *Parameter : Call.parameters()) { + unsigned ParameterIndex = Parameter->getFunctionScopeIndex(); + if (ParameterIndex == AttrNonNull.size()) + break; + + if (Parameter->hasAttr<NonNullAttr>()) + AttrNonNull.set(ParameterIndex); + } +} + +template <class CallType> +llvm::SmallBitVector getNonNullAttrsImpl(const CallType &Call, + unsigned ExpectedSize) { + llvm::SmallBitVector AttrNonNull(ExpectedSize); + + setBitsAccordingToFunctionAttributes(Call, AttrNonNull); + setBitsAccordingToParameterAttributes(Call, AttrNonNull); + return AttrNonNull; } +/// \return Bitvector marking non-null attributes. +llvm::SmallBitVector getNonNullAttrs(const CallEvent &Call) { + return getNonNullAttrsImpl(Call, Call.getNumArgs()); +} + +/// \return Bitvector marking non-null attributes. +llvm::SmallBitVector getNonNullAttrs(const AnyCall &Call) { + return getNonNullAttrsImpl(Call, Call.param_size()); +} +} // end anonymous namespace + void NonNullParamChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { if (!Call.getDecl()) @@ -74,7 +114,7 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, unsigned NumArgs = Call.getNumArgs(); ProgramStateRef state = C.getState(); - ArrayRef<ParmVarDecl*> parms = Call.parameters(); + ArrayRef<ParmVarDecl *> parms = Call.parameters(); for (unsigned idx = 0; idx < NumArgs; ++idx) { // For vararg functions, a corresponding parameter decl may not exist. @@ -82,15 +122,11 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, // Check if the parameter is a reference. We want to report when reference // to a null pointer is passed as a parameter. - bool haveRefTypeParam = + bool HasRefTypeParam = HasParam ? parms[idx]->getType()->isReferenceType() : false; - bool haveAttrNonNull = AttrNonNull[idx]; + bool ExpectedToBeNonNull = AttrNonNull.test(idx); - // Check if the parameter is also marked 'nonnull'. - if (!haveAttrNonNull && HasParam) - haveAttrNonNull = parms[idx]->hasAttr<NonNullAttr>(); - - if (!haveAttrNonNull && !haveRefTypeParam) + if (!ExpectedToBeNonNull && !HasRefTypeParam) continue; // If the value is unknown or undefined, we can't perform this check. @@ -100,10 +136,10 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, if (!DV) continue; - assert(!haveRefTypeParam || DV->getAs<Loc>()); + assert(!HasRefTypeParam || DV->getAs<Loc>()); // Process the case when the argument is not a location. - if (haveAttrNonNull && !DV->getAs<Loc>()) { + if (ExpectedToBeNonNull && !DV->getAs<Loc>()) { // If the argument is a union type, we want to handle a potential // transparent_union GCC extension. if (!ArgE) @@ -144,9 +180,9 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, if (ExplodedNode *errorNode = C.generateErrorNode(stateNull)) { std::unique_ptr<BugReport> R; - if (haveAttrNonNull) + if (ExpectedToBeNonNull) R = genReportNullAttrNonNull(errorNode, ArgE, idx + 1); - else if (haveRefTypeParam) + else if (HasRefTypeParam) R = genReportReferenceToNullPointer(errorNode, ArgE); // Highlight the range of the argument that was null. @@ -163,8 +199,8 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, 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=*/HasRefTypeParam}; dispatchEvent(event); } } @@ -179,6 +215,65 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, C.addTransition(state); } +/// We want to trust developer annotations and consider all 'nonnull' parameters +/// as non-null indeed. Each marked parameter will get a corresponding +/// constraint. +/// +/// This approach will not only help us to get rid of some false positives, but +/// remove duplicates and shorten warning traces as well. +/// +/// \code +/// void foo(int *x) [[gnu::nonnull]] { +/// // . . . +/// *x = 42; // we don't want to consider this as an error... +/// // . . . +/// } +/// +/// foo(nullptr); // ...and report here instead +/// \endcode +void NonNullParamChecker::checkBeginFunction(CheckerContext &Context) const { + // Planned assumption makes sense only for top-level functions. + // Inlined functions will get similar constraints as part of 'checkPreCall'. + if (!Context.inTopFrame()) + return; + + const LocationContext *LocContext = Context.getLocationContext(); + + const Decl *FD = LocContext->getDecl(); + // AnyCall helps us here to avoid checking for FunctionDecl and ObjCMethodDecl + // separately and aggregates interfaces of these classes. + auto AbstractCall = AnyCall::forDecl(FD); + if (!AbstractCall) + return; + + ProgramStateRef State = Context.getState(); + llvm::SmallBitVector ParameterNonNullMarks = getNonNullAttrs(*AbstractCall); + + for (const ParmVarDecl *Parameter : AbstractCall->parameters()) { + // 1. Check parameter if it is annotated as non-null + if (!ParameterNonNullMarks.test(Parameter->getFunctionScopeIndex())) + continue; + + // 2. Check that parameter is a pointer. + // Nonnull attribute can be applied to non-pointers (by default + // __attribute__(nonnull) implies "all parameters"). + if (!Parameter->getType()->isPointerType()) + continue; + + Loc ParameterLoc = State->getLValue(Parameter, LocContext); + // We never consider top-level function parameters undefined. + auto StoredVal = + State->getSVal(ParameterLoc).castAs<DefinedOrUnknownSVal>(); + + // 3. Assume that it is indeed non-null + if (ProgramStateRef NewState = State->assume(StoredVal, true)) { + State = NewState; + } + } + + Context.addTransition(State); +} + std::unique_ptr<PathSensitiveBugReport> NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode, const Expr *ArgE, @@ -226,6 +321,6 @@ void ento::registerNonNullParamChecker(CheckerManager &mgr) { mgr.registerChecker<NonNullParamChecker>(); } -bool ento::shouldRegisterNonNullParamChecker(const LangOptions &LO) { +bool ento::shouldRegisterNonNullParamChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp index 6efba433eed2..80b705fb7392 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp @@ -147,6 +147,6 @@ void ento::registerNonnullGlobalConstantsChecker(CheckerManager &Mgr) { Mgr.registerChecker<NonnullGlobalConstantsChecker>(); } -bool ento::shouldRegisterNonnullGlobalConstantsChecker(const LangOptions &LO) { +bool ento::shouldRegisterNonnullGlobalConstantsChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp index 922048733c7c..bc7a8a3b12a1 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -81,8 +81,7 @@ class NullabilityChecker : public Checker<check::Bind, check::PreCall, check::PreStmt<ReturnStmt>, check::PostCall, check::PostStmt<ExplicitCastExpr>, check::PostObjCMessage, check::DeadSymbols, - check::Event<ImplicitNullDerefEvent>> { - mutable std::unique_ptr<BugType> BT; + check::Location, check::Event<ImplicitNullDerefEvent>> { public: // If true, the checker will not diagnose nullabilility issues for calls @@ -101,25 +100,32 @@ public: void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; void checkEvent(ImplicitNullDerefEvent Event) const; + void checkLocation(SVal Location, bool IsLoad, const Stmt *S, + CheckerContext &C) const; void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const override; - struct NullabilityChecksFilter { - DefaultBool CheckNullPassedToNonnull; - DefaultBool CheckNullReturnedFromNonnull; - DefaultBool CheckNullableDereferenced; - DefaultBool CheckNullablePassedToNonnull; - DefaultBool CheckNullableReturnedFromNonnull; - - CheckerNameRef CheckNameNullPassedToNonnull; - CheckerNameRef CheckNameNullReturnedFromNonnull; - CheckerNameRef CheckNameNullableDereferenced; - CheckerNameRef CheckNameNullablePassedToNonnull; - CheckerNameRef CheckNameNullableReturnedFromNonnull; + enum CheckKind { + CK_NullPassedToNonnull, + CK_NullReturnedFromNonnull, + CK_NullableDereferenced, + CK_NullablePassedToNonnull, + CK_NullableReturnedFromNonnull, + CK_NumCheckKinds }; - NullabilityChecksFilter Filter; + DefaultBool ChecksEnabled[CK_NumCheckKinds]; + CheckerNameRef CheckNames[CK_NumCheckKinds]; + mutable std::unique_ptr<BugType> BTs[CK_NumCheckKinds]; + + const std::unique_ptr<BugType> &getBugType(CheckKind Kind) const { + if (!BTs[Kind]) + BTs[Kind].reset(new BugType(CheckNames[Kind], "Nullability", + categories::MemoryError)); + return BTs[Kind]; + } + // When set to false no nullability information will be tracked in // NullabilityMap. It is possible to catch errors like passing a null pointer // to a callee that expects nonnull argument without the information that is @@ -151,18 +157,16 @@ private: /// /// When \p SuppressPath is set to true, no more bugs will be reported on this /// path by this checker. - void reportBugIfInvariantHolds(StringRef Msg, ErrorKind Error, + void reportBugIfInvariantHolds(StringRef Msg, ErrorKind Error, CheckKind CK, ExplodedNode *N, const MemRegion *Region, CheckerContext &C, const Stmt *ValueExpr = nullptr, - bool SuppressPath = false) const; + bool SuppressPath = false) const; - void reportBug(StringRef Msg, ErrorKind Error, ExplodedNode *N, + void reportBug(StringRef Msg, ErrorKind Error, CheckKind CK, ExplodedNode *N, const MemRegion *Region, BugReporter &BR, const Stmt *ValueExpr = nullptr) const { - if (!BT) - BT.reset(new BugType(this, "Nullability", categories::MemoryError)); - + const std::unique_ptr<BugType> &BT = getBugType(CK); auto R = std::make_unique<PathSensitiveBugReport>(*BT, Msg, N); if (Region) { R->markInteresting(Region); @@ -430,9 +434,10 @@ static bool checkInvariantViolation(ProgramStateRef State, ExplodedNode *N, return false; } -void NullabilityChecker::reportBugIfInvariantHolds(StringRef Msg, - ErrorKind Error, ExplodedNode *N, const MemRegion *Region, - CheckerContext &C, const Stmt *ValueExpr, bool SuppressPath) const { +void NullabilityChecker::reportBugIfInvariantHolds( + StringRef Msg, ErrorKind Error, CheckKind CK, ExplodedNode *N, + const MemRegion *Region, CheckerContext &C, const Stmt *ValueExpr, + bool SuppressPath) const { ProgramStateRef OriginalState = N->getState(); if (checkInvariantViolation(OriginalState, N, C)) @@ -442,7 +447,7 @@ void NullabilityChecker::reportBugIfInvariantHolds(StringRef Msg, N = C.addTransition(OriginalState, N); } - reportBug(Msg, Error, N, Region, C.getBugReporter(), ValueExpr); + reportBug(Msg, Error, CK, N, Region, C.getBugReporter(), ValueExpr); } /// Cleaning up the program state. @@ -487,34 +492,76 @@ void NullabilityChecker::checkEvent(ImplicitNullDerefEvent Event) const { if (!TrackedNullability) return; - if (Filter.CheckNullableDereferenced && + if (ChecksEnabled[CK_NullableDereferenced] && TrackedNullability->getValue() == Nullability::Nullable) { BugReporter &BR = *Event.BR; // Do not suppress errors on defensive code paths, because dereferencing // a nullable pointer is always an error. if (Event.IsDirectDereference) reportBug("Nullable pointer is dereferenced", - ErrorKind::NullableDereferenced, Event.SinkNode, Region, BR); + ErrorKind::NullableDereferenced, CK_NullableDereferenced, + Event.SinkNode, Region, BR); else { reportBug("Nullable pointer is passed to a callee that requires a " - "non-null", ErrorKind::NullablePassedToNonnull, + "non-null", + ErrorKind::NullablePassedToNonnull, CK_NullableDereferenced, Event.SinkNode, Region, BR); } } } +// Whenever we see a load from a typed memory region that's been annotated as +// 'nonnull', we want to trust the user on that and assume that it is is indeed +// non-null. +// +// We do so even if the value is known to have been assigned to null. +// The user should be warned on assigning the null value to a non-null pointer +// as opposed to warning on the later dereference of this pointer. +// +// \code +// int * _Nonnull var = 0; // we want to warn the user here... +// // . . . +// *var = 42; // ...and not here +// \endcode +void NullabilityChecker::checkLocation(SVal Location, bool IsLoad, + const Stmt *S, + CheckerContext &Context) const { + // We should care only about loads. + // The main idea is to add a constraint whenever we're loading a value from + // an annotated pointer type. + if (!IsLoad) + return; + + // Annotations that we want to consider make sense only for types. + const auto *Region = + dyn_cast_or_null<TypedValueRegion>(Location.getAsRegion()); + if (!Region) + return; + + ProgramStateRef State = Context.getState(); + + auto StoredVal = State->getSVal(Region).getAs<loc::MemRegionVal>(); + if (!StoredVal) + return; + + Nullability NullabilityOfTheLoadedValue = + getNullabilityAnnotation(Region->getValueType()); + + if (NullabilityOfTheLoadedValue == Nullability::Nonnull) { + // It doesn't matter what we think about this particular pointer, it should + // be considered non-null as annotated by the developer. + if (ProgramStateRef NewState = State->assume(*StoredVal, true)) { + Context.addTransition(NewState); + } + } +} + /// Find the outermost subexpression of E that is not an implicit cast. /// This looks through the implicit casts to _Nonnull that ARC adds to /// return expressions of ObjC types when the return type of the function or /// method is non-null but the express is not. static const Expr *lookThroughImplicitCasts(const Expr *E) { - assert(E); - - while (auto *ICE = dyn_cast<ImplicitCastExpr>(E)) { - E = ICE->getSubExpr(); - } - - return E; + return E->IgnoreImpCasts(); } /// This method check when nullable pointer or null value is returned from a @@ -572,11 +619,9 @@ void NullabilityChecker::checkPreStmt(const ReturnStmt *S, bool NullReturnedFromNonNull = (RequiredNullability == Nullability::Nonnull && Nullness == NullConstraint::IsNull); - if (Filter.CheckNullReturnedFromNonnull && - NullReturnedFromNonNull && + if (ChecksEnabled[CK_NullReturnedFromNonnull] && NullReturnedFromNonNull && RetExprTypeLevelNullability != Nullability::Nonnull && - !InSuppressedMethodFamily && - C.getLocationContext()->inTopFrame()) { + !InSuppressedMethodFamily && C.getLocationContext()->inTopFrame()) { static CheckerProgramPointTag Tag(this, "NullReturnedFromNonnull"); ExplodedNode *N = C.generateErrorNode(State, &Tag); if (!N) @@ -587,8 +632,8 @@ void NullabilityChecker::checkPreStmt(const ReturnStmt *S, OS << (RetExpr->getType()->isObjCObjectPointerType() ? "nil" : "Null"); OS << " returned from a " << C.getDeclDescription(D) << " that is expected to return a non-null value"; - reportBugIfInvariantHolds(OS.str(), - ErrorKind::NilReturnedToNonnull, N, nullptr, C, + reportBugIfInvariantHolds(OS.str(), ErrorKind::NilReturnedToNonnull, + CK_NullReturnedFromNonnull, N, nullptr, C, RetExpr); return; } @@ -609,7 +654,7 @@ void NullabilityChecker::checkPreStmt(const ReturnStmt *S, State->get<NullabilityMap>(Region); if (TrackedNullability) { Nullability TrackedNullabValue = TrackedNullability->getValue(); - if (Filter.CheckNullableReturnedFromNonnull && + if (ChecksEnabled[CK_NullableReturnedFromNonnull] && Nullness != NullConstraint::IsNotNull && TrackedNullabValue == Nullability::Nullable && RequiredNullability == Nullability::Nonnull) { @@ -621,9 +666,8 @@ void NullabilityChecker::checkPreStmt(const ReturnStmt *S, OS << "Nullable pointer is returned from a " << C.getDeclDescription(D) << " that is expected to return a non-null value"; - reportBugIfInvariantHolds(OS.str(), - ErrorKind::NullableReturnedToNonnull, N, - Region, C); + reportBugIfInvariantHolds(OS.str(), ErrorKind::NullableReturnedToNonnull, + CK_NullableReturnedFromNonnull, N, Region, C); } return; } @@ -674,7 +718,8 @@ void NullabilityChecker::checkPreCall(const CallEvent &Call, unsigned ParamIdx = Param->getFunctionScopeIndex() + 1; - if (Filter.CheckNullPassedToNonnull && Nullness == NullConstraint::IsNull && + if (ChecksEnabled[CK_NullPassedToNonnull] && + Nullness == NullConstraint::IsNull && ArgExprTypeLevelNullability != Nullability::Nonnull && RequiredNullability == Nullability::Nonnull && isDiagnosableCall(Call)) { @@ -687,9 +732,9 @@ void NullabilityChecker::checkPreCall(const CallEvent &Call, OS << (Param->getType()->isObjCObjectPointerType() ? "nil" : "Null"); OS << " passed to a callee that requires a non-null " << ParamIdx << llvm::getOrdinalSuffix(ParamIdx) << " parameter"; - reportBugIfInvariantHolds(OS.str(), ErrorKind::NilPassedToNonnull, N, - nullptr, C, - ArgExpr, /*SuppressPath=*/false); + reportBugIfInvariantHolds(OS.str(), ErrorKind::NilPassedToNonnull, + CK_NullPassedToNonnull, N, nullptr, C, ArgExpr, + /*SuppressPath=*/false); return; } @@ -705,7 +750,7 @@ void NullabilityChecker::checkPreCall(const CallEvent &Call, TrackedNullability->getValue() != Nullability::Nullable) continue; - if (Filter.CheckNullablePassedToNonnull && + if (ChecksEnabled[CK_NullablePassedToNonnull] && RequiredNullability == Nullability::Nonnull && isDiagnosableCall(Call)) { ExplodedNode *N = C.addTransition(State); @@ -713,17 +758,18 @@ void NullabilityChecker::checkPreCall(const CallEvent &Call, llvm::raw_svector_ostream OS(SBuf); OS << "Nullable pointer is passed to a callee that requires a non-null " << ParamIdx << llvm::getOrdinalSuffix(ParamIdx) << " parameter"; - reportBugIfInvariantHolds(OS.str(), - ErrorKind::NullablePassedToNonnull, N, - Region, C, ArgExpr, /*SuppressPath=*/true); + reportBugIfInvariantHolds(OS.str(), ErrorKind::NullablePassedToNonnull, + CK_NullablePassedToNonnull, N, Region, C, + ArgExpr, /*SuppressPath=*/true); return; } - if (Filter.CheckNullableDereferenced && + if (ChecksEnabled[CK_NullableDereferenced] && Param->getType()->isReferenceType()) { ExplodedNode *N = C.addTransition(State); reportBugIfInvariantHolds("Nullable pointer is dereferenced", - ErrorKind::NullableDereferenced, N, Region, - C, ArgExpr, /*SuppressPath=*/true); + ErrorKind::NullableDereferenced, + CK_NullableDereferenced, N, Region, C, + ArgExpr, /*SuppressPath=*/true); return; } continue; @@ -1083,8 +1129,7 @@ void NullabilityChecker::checkBind(SVal L, SVal V, const Stmt *S, bool NullAssignedToNonNull = (LocNullability == Nullability::Nonnull && RhsNullness == NullConstraint::IsNull); - if (Filter.CheckNullPassedToNonnull && - NullAssignedToNonNull && + if (ChecksEnabled[CK_NullPassedToNonnull] && NullAssignedToNonNull && ValNullability != Nullability::Nonnull && ValueExprTypeLevelNullability != Nullability::Nonnull && !isARCNilInitializedLocal(C, S)) { @@ -1102,9 +1147,8 @@ void NullabilityChecker::checkBind(SVal L, SVal V, const Stmt *S, llvm::raw_svector_ostream OS(SBuf); OS << (LocType->isObjCObjectPointerType() ? "nil" : "Null"); OS << " assigned to a pointer which is expected to have non-null value"; - reportBugIfInvariantHolds(OS.str(), - ErrorKind::NilAssignedToNonnull, N, nullptr, C, - ValueStmt); + reportBugIfInvariantHolds(OS.str(), ErrorKind::NilAssignedToNonnull, + CK_NullPassedToNonnull, N, nullptr, C, ValueStmt); return; } @@ -1130,14 +1174,14 @@ void NullabilityChecker::checkBind(SVal L, SVal V, const Stmt *S, if (RhsNullness == NullConstraint::IsNotNull || TrackedNullability->getValue() != Nullability::Nullable) return; - if (Filter.CheckNullablePassedToNonnull && + if (ChecksEnabled[CK_NullablePassedToNonnull] && LocNullability == Nullability::Nonnull) { static CheckerProgramPointTag Tag(this, "NullablePassedToNonnull"); ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag); reportBugIfInvariantHolds("Nullable pointer is assigned to a pointer " "which is expected to have non-null value", - ErrorKind::NullableAssignedToNonnull, N, - ValueRegion, C); + ErrorKind::NullableAssignedToNonnull, + CK_NullablePassedToNonnull, N, ValueRegion, C); } return; } @@ -1188,15 +1232,16 @@ void ento::registerNullabilityBase(CheckerManager &mgr) { mgr.registerChecker<NullabilityChecker>(); } -bool ento::shouldRegisterNullabilityBase(const LangOptions &LO) { +bool ento::shouldRegisterNullabilityBase(const CheckerManager &mgr) { return true; } #define REGISTER_CHECKER(name, trackingRequired) \ void ento::register##name##Checker(CheckerManager &mgr) { \ NullabilityChecker *checker = mgr.getChecker<NullabilityChecker>(); \ - checker->Filter.Check##name = true; \ - checker->Filter.CheckName##name = mgr.getCurrentCheckerName(); \ + checker->ChecksEnabled[NullabilityChecker::CK_##name] = true; \ + checker->CheckNames[NullabilityChecker::CK_##name] = \ + mgr.getCurrentCheckerName(); \ checker->NeedTracking = checker->NeedTracking || trackingRequired; \ checker->NoDiagnoseCallsToSystemHeaders = \ checker->NoDiagnoseCallsToSystemHeaders || \ @@ -1204,7 +1249,7 @@ bool ento::shouldRegisterNullabilityBase(const LangOptions &LO) { checker, "NoDiagnoseCallsToSystemHeaders", true); \ } \ \ - bool ento::shouldRegister##name##Checker(const LangOptions &LO) { \ + bool ento::shouldRegister##name##Checker(const CheckerManager &mgr) { \ return true; \ } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp index 1053424ae6fa..df01cc760e7e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp @@ -57,7 +57,7 @@ public: Callback(const NumberObjectConversionChecker *C, BugReporter &BR, AnalysisDeclContext *ADC) : C(C), BR(BR), ADC(ADC) {} - virtual void run(const MatchFinder::MatchResult &Result); + void run(const MatchFinder::MatchResult &Result) override; }; } // end of anonymous namespace @@ -338,7 +338,7 @@ void NumberObjectConversionChecker::checkASTCodeBody(const Decl *D, MatchFinder F; Callback CB(this, BR, AM.getAnalysisDeclContext(D)); - F.addMatcher(stmt(forEachDescendant(FinalM)), &CB); + F.addMatcher(traverse(TK_AsIs, stmt(forEachDescendant(FinalM))), &CB); F.match(*D->getBody(), AM.getASTContext()); } @@ -349,6 +349,6 @@ void ento::registerNumberObjectConversionChecker(CheckerManager &Mgr) { Mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk, "Pedantic"); } -bool ento::shouldRegisterNumberObjectConversionChecker(const LangOptions &LO) { +bool ento::shouldRegisterNumberObjectConversionChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/OSObjectCStyleCast.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/OSObjectCStyleCast.cpp index 5b9895c338d8..53ed0e187a4c 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/OSObjectCStyleCast.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/OSObjectCStyleCast.cpp @@ -55,8 +55,7 @@ static void emitDiagnostics(const BoundNodes &Nodes, CE->getSourceRange()); } -static auto hasTypePointingTo(DeclarationMatcher DeclM) - -> decltype(hasType(pointerType())) { +static decltype(auto) hasTypePointingTo(DeclarationMatcher DeclM) { return hasType(pointerType(pointee(hasDeclaration(DeclM)))); } @@ -85,6 +84,6 @@ void ento::registerOSObjectCStyleCast(CheckerManager &Mgr) { Mgr.registerChecker<OSObjectCStyleCastChecker>(); } -bool ento::shouldRegisterOSObjectCStyleCast(const LangOptions &LO) { +bool ento::shouldRegisterOSObjectCStyleCast(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp index 0e25817c8793..43af4bb14286 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp @@ -91,6 +91,7 @@ void ento::registerObjCAtSyncChecker(CheckerManager &mgr) { mgr.registerChecker<ObjCAtSyncChecker>(); } -bool ento::shouldRegisterObjCAtSyncChecker(const LangOptions &LO) { +bool ento::shouldRegisterObjCAtSyncChecker(const CheckerManager &mgr) { + const LangOptions &LO = mgr.getLangOpts(); return LO.ObjC; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp index d2371fe60d21..7fd6e2abef4c 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp @@ -30,6 +30,7 @@ #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.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/AnalysisManager.h" #include "llvm/ADT/Twine.h" @@ -44,6 +45,7 @@ const char *ProblematicWriteBind = "problematicwrite"; const char *CapturedBind = "capturedbind"; const char *ParamBind = "parambind"; const char *IsMethodBind = "ismethodbind"; +const char *IsARPBind = "isautoreleasepoolbind"; class ObjCAutoreleaseWriteChecker : public Checker<check::ASTCodeBody> { public: @@ -100,8 +102,7 @@ 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())) { +static decltype(auto) callsNames(std::vector<std::string> FunctionNames) { return callee(functionDecl(hasAnyName(toRefs(FunctionNames)))); } @@ -129,21 +130,39 @@ static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR, 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(), - /*BugCategory=*/"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); + const char *FunctionDescription = IsMethod ? "method" : "function"; + bool IsARP = Match.getNodeAs<ObjCAutoreleasePoolStmt>(IsARPBind) != nullptr; + + llvm::SmallString<128> BugNameBuf; + llvm::raw_svector_ostream BugName(BugNameBuf); + BugName << ActionMsg + << " autoreleasing out parameter inside autorelease pool"; + + llvm::SmallString<128> BugMessageBuf; + llvm::raw_svector_ostream BugMessage(BugMessageBuf); + BugMessage << ActionMsg << " autoreleasing out parameter "; + if (IsCapture) + BugMessage << "'" + PVD->getName() + "' "; + + BugMessage << "inside "; + if (IsARP) + BugMessage << "locally-scoped autorelease pool;"; + else + BugMessage << "autorelease pool that may exit before " + << FunctionDescription << " returns;"; + + BugMessage << " consider writing first to a strong local variable" + " declared outside "; + if (IsARP) + BugMessage << "of the autorelease pool"; + else + BugMessage << "of the block"; + + BR.EmitBasicReport(ADC->getDecl(), Checker, BugName.str(), + categories::MemoryRefCount, BugMessage.str(), Location, + Range); } void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D, @@ -189,9 +208,16 @@ void ObjCAutoreleaseWriteChecker::checkASTCodeBody(const Decl *D, WritesOrCapturesInBlockM)) )); - auto HasParamAndWritesInMarkedFuncM = allOf( - hasAnyParameter(DoublePointerParamM), - forEachDescendant(BlockPassedToMarkedFuncM)); + // WritesIntoM happens inside an explicit @autoreleasepool. + auto WritesOrCapturesInPoolM = + autoreleasePoolStmt( + forEachDescendant(stmt(anyOf(WritesIntoM, CapturedInParamM)))) + .bind(IsARPBind); + + auto HasParamAndWritesInMarkedFuncM = + allOf(hasAnyParameter(DoublePointerParamM), + anyOf(forEachDescendant(BlockPassedToMarkedFuncM), + forEachDescendant(WritesOrCapturesInPoolM))); auto MatcherM = decl(anyOf( objcMethodDecl(HasParamAndWritesInMarkedFuncM).bind(IsMethodBind), @@ -207,6 +233,6 @@ void ento::registerAutoreleaseWriteChecker(CheckerManager &Mgr) { Mgr.registerChecker<ObjCAutoreleaseWriteChecker>(); } -bool ento::shouldRegisterAutoreleaseWriteChecker(const LangOptions &LO) { +bool ento::shouldRegisterAutoreleaseWriteChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp index 4450c464f89d..8428b2294ba6 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp @@ -172,6 +172,6 @@ void ento::registerObjCContainersASTChecker(CheckerManager &mgr) { mgr.registerChecker<ObjCContainersASTChecker>(); } -bool ento::shouldRegisterObjCContainersASTChecker(const LangOptions &LO) { +bool ento::shouldRegisterObjCContainersASTChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp index 8abb926d4862..8c2008a7ceb4 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp @@ -58,7 +58,7 @@ public: PointerEscapeKind Kind) const; void printState(raw_ostream &OS, ProgramStateRef State, - const char *NL, const char *Sep) const; + const char *NL, const char *Sep) const override; }; } // end anonymous namespace @@ -188,6 +188,6 @@ void ento::registerObjCContainersChecker(CheckerManager &mgr) { mgr.registerChecker<ObjCContainersChecker>(); } -bool ento::shouldRegisterObjCContainersChecker(const LangOptions &LO) { +bool ento::shouldRegisterObjCContainersChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp index 1870c08432de..24e2a4dea922 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp @@ -221,7 +221,7 @@ void ento::registerObjCSuperCallChecker(CheckerManager &Mgr) { Mgr.registerChecker<ObjCSuperCallChecker>(); } -bool ento::shouldRegisterObjCSuperCallChecker(const LangOptions &LO) { +bool ento::shouldRegisterObjCSuperCallChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp index 9a49200545e3..4636fd160511 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp @@ -79,6 +79,6 @@ void ento::registerObjCPropertyChecker(CheckerManager &Mgr) { Mgr.registerChecker<ObjCPropertyChecker>(); } -bool ento::shouldRegisterObjCPropertyChecker(const LangOptions &LO) { +bool ento::shouldRegisterObjCPropertyChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp index 344285750f0e..17d3c042ac40 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp @@ -437,6 +437,6 @@ void ento::registerObjCSelfInitChecker(CheckerManager &mgr) { mgr.registerChecker<ObjCSelfInitChecker>(); } -bool ento::shouldRegisterObjCSelfInitChecker(const LangOptions &LO) { +bool ento::shouldRegisterObjCSelfInitChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp index 0575be845374..3547b7bb61a2 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp @@ -116,13 +116,14 @@ void ObjCSuperDeallocChecker::checkPostObjCMessage(const ObjCMethodCall &M, return; ProgramStateRef State = C.getState(); - SymbolRef ReceiverSymbol = M.getSelfSVal().getAsSymbol(); - assert(ReceiverSymbol && "No receiver symbol at call to [super dealloc]?"); + const LocationContext *LC = C.getLocationContext(); + SymbolRef SelfSymbol = State->getSelfSVal(LC).getAsSymbol(); + assert(SelfSymbol && "No receiver symbol at call to [super dealloc]?"); // We add this transition in checkPostObjCMessage to avoid warning when // we inline a call to [super dealloc] where the inlined call itself // calls [super dealloc]. - State = State->add<CalledSuperDealloc>(ReceiverSymbol); + State = State->add<CalledSuperDealloc>(SelfSymbol); C.addTransition(State); } @@ -284,6 +285,6 @@ void ento::registerObjCSuperDeallocChecker(CheckerManager &Mgr) { Mgr.registerChecker<ObjCSuperDeallocChecker>(); } -bool ento::shouldRegisterObjCSuperDeallocChecker(const LangOptions &LO) { +bool ento::shouldRegisterObjCSuperDeallocChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp index cb4770451572..c9828c36a06a 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp @@ -186,6 +186,6 @@ void ento::registerObjCUnusedIvarsChecker(CheckerManager &mgr) { mgr.registerChecker<ObjCUnusedIvarsChecker>(); } -bool ento::shouldRegisterObjCUnusedIvarsChecker(const LangOptions &LO) { +bool ento::shouldRegisterObjCUnusedIvarsChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp index 4a3c2b8cd40e..0b00664c7c10 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp @@ -353,6 +353,6 @@ void ento::registerPaddingChecker(CheckerManager &Mgr) { Checker, "AllowedPad", "a non-negative value"); } -bool ento::shouldRegisterPaddingChecker(const LangOptions &LO) { +bool ento::shouldRegisterPaddingChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp index 259f23abdc95..d3e2849a0ce6 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp @@ -343,6 +343,6 @@ void ento::registerPointerArithChecker(CheckerManager &mgr) { mgr.registerChecker<PointerArithChecker>(); } -bool ento::shouldRegisterPointerArithChecker(const LangOptions &LO) { +bool ento::shouldRegisterPointerArithChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerIterationChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerIterationChecker.cpp index 307e59b8eebc..8aca6d009cdb 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerIterationChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerIterationChecker.cpp @@ -95,6 +95,7 @@ void ento::registerPointerIterationChecker(CheckerManager &Mgr) { Mgr.registerChecker<PointerIterationChecker>(); } -bool ento::shouldRegisterPointerIterationChecker(const LangOptions &LO) { +bool ento::shouldRegisterPointerIterationChecker(const CheckerManager &mgr) { + const LangOptions &LO = mgr.getLangOpts(); return LO.CPlusPlus; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerSortingChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerSortingChecker.cpp index 586d9d3af2a6..25d87f4acfc9 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerSortingChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerSortingChecker.cpp @@ -54,7 +54,7 @@ static void emitDiagnostics(const BoundNodes &Match, const Decl *D, OS.str(), Location, Range); } -auto callsName(const char *FunctionName) -> decltype(callee(functionDecl())) { +decltype(auto) callsName(const char *FunctionName) { return callee(functionDecl(hasName(FunctionName))); } @@ -86,8 +86,9 @@ auto matchSortWithPointers() -> decltype(decl()) { ))) )))); - auto PointerSortM = stmt(callExpr(allOf(SortFuncM, IteratesPointerEltsM)) - ).bind(WarnAtNode); + auto PointerSortM = traverse( + TK_AsIs, + stmt(callExpr(allOf(SortFuncM, IteratesPointerEltsM))).bind(WarnAtNode)); return decl(forEachDescendant(PointerSortM)); } @@ -108,6 +109,7 @@ void ento::registerPointerSortingChecker(CheckerManager &Mgr) { Mgr.registerChecker<PointerSortingChecker>(); } -bool ento::shouldRegisterPointerSortingChecker(const LangOptions &LO) { +bool ento::shouldRegisterPointerSortingChecker(const CheckerManager &mgr) { + const LangOptions &LO = mgr.getLangOpts(); return LO.CPlusPlus; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp index 88d0eb2ae748..81c19d9a0940 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp @@ -74,6 +74,6 @@ void ento::registerPointerSubChecker(CheckerManager &mgr) { mgr.registerChecker<PointerSubChecker>(); } -bool ento::shouldRegisterPointerSubChecker(const LangOptions &LO) { +bool ento::shouldRegisterPointerSubChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp index 8649b8b96dd0..285d2da104f1 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp @@ -6,8 +6,14 @@ // //===----------------------------------------------------------------------===// // -// This defines PthreadLockChecker, a simple lock -> unlock checker. -// Also handles XNU locks, which behave similarly enough to share code. +// This file defines: +// * PthreadLockChecker, a simple lock -> unlock checker. +// Which also checks for XNU locks, which behave similarly enough to share +// code. +// * FuchsiaLocksChecker, which is also rather similar. +// * C11LockChecker which also closely follows Pthread semantics. +// +// TODO: Path notes. // //===----------------------------------------------------------------------===// @@ -15,8 +21,8 @@ #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/ProgramStateTrait.h" using namespace clang; using namespace ento; @@ -46,9 +52,7 @@ public: return LockState(UnlockedAndPossiblyDestroyed); } - bool operator==(const LockState &X) const { - return K == X.K; - } + bool operator==(const LockState &X) const { return K == X.K; } bool isLocked() const { return K == Locked; } bool isUnlocked() const { return K == Unlocked; } @@ -60,40 +64,182 @@ public: return K == UnlockedAndPossiblyDestroyed; } - void Profile(llvm::FoldingSetNodeID &ID) const { - ID.AddInteger(K); - } + void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); } }; -class PthreadLockChecker - : public Checker<check::PostStmt<CallExpr>, check::DeadSymbols> { - mutable std::unique_ptr<BugType> BT_doublelock; - mutable std::unique_ptr<BugType> BT_doubleunlock; - mutable std::unique_ptr<BugType> BT_destroylock; - mutable std::unique_ptr<BugType> BT_initlock; - mutable std::unique_ptr<BugType> BT_lor; - enum LockingSemantics { - NotApplicable = 0, - PthreadSemantics, - XNUSemantics - }; +class PthreadLockChecker : public Checker<check::PostCall, check::DeadSymbols, + check::RegionChanges> { public: - void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; - void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; - void printState(raw_ostream &Out, ProgramStateRef State, - const char *NL, const char *Sep) const override; + enum LockingSemantics { NotApplicable = 0, PthreadSemantics, XNUSemantics }; + enum CheckerKind { + CK_PthreadLockChecker, + CK_FuchsiaLockChecker, + CK_C11LockChecker, + CK_NumCheckKinds + }; + DefaultBool ChecksEnabled[CK_NumCheckKinds]; + CheckerNameRef CheckNames[CK_NumCheckKinds]; + +private: + typedef void (PthreadLockChecker::*FnCheck)(const CallEvent &Call, + CheckerContext &C, + CheckerKind checkkind) const; + CallDescriptionMap<FnCheck> PThreadCallbacks = { + // Init. + {{"pthread_mutex_init", 2}, &PthreadLockChecker::InitAnyLock}, + // TODO: pthread_rwlock_init(2 arguments). + // TODO: lck_mtx_init(3 arguments). + // TODO: lck_mtx_alloc_init(2 arguments) => returns the mutex. + // TODO: lck_rw_init(3 arguments). + // TODO: lck_rw_alloc_init(2 arguments) => returns the mutex. + + // Acquire. + {{"pthread_mutex_lock", 1}, &PthreadLockChecker::AcquirePthreadLock}, + {{"pthread_rwlock_rdlock", 1}, &PthreadLockChecker::AcquirePthreadLock}, + {{"pthread_rwlock_wrlock", 1}, &PthreadLockChecker::AcquirePthreadLock}, + {{"lck_mtx_lock", 1}, &PthreadLockChecker::AcquireXNULock}, + {{"lck_rw_lock_exclusive", 1}, &PthreadLockChecker::AcquireXNULock}, + {{"lck_rw_lock_shared", 1}, &PthreadLockChecker::AcquireXNULock}, + + // Try. + {{"pthread_mutex_trylock", 1}, &PthreadLockChecker::TryPthreadLock}, + {{"pthread_rwlock_tryrdlock", 1}, &PthreadLockChecker::TryPthreadLock}, + {{"pthread_rwlock_trywrlock", 1}, &PthreadLockChecker::TryPthreadLock}, + {{"lck_mtx_try_lock", 1}, &PthreadLockChecker::TryXNULock}, + {{"lck_rw_try_lock_exclusive", 1}, &PthreadLockChecker::TryXNULock}, + {{"lck_rw_try_lock_shared", 1}, &PthreadLockChecker::TryXNULock}, + + // Release. + {{"pthread_mutex_unlock", 1}, &PthreadLockChecker::ReleaseAnyLock}, + {{"pthread_rwlock_unlock", 1}, &PthreadLockChecker::ReleaseAnyLock}, + {{"lck_mtx_unlock", 1}, &PthreadLockChecker::ReleaseAnyLock}, + {{"lck_rw_unlock_exclusive", 1}, &PthreadLockChecker::ReleaseAnyLock}, + {{"lck_rw_unlock_shared", 1}, &PthreadLockChecker::ReleaseAnyLock}, + {{"lck_rw_done", 1}, &PthreadLockChecker::ReleaseAnyLock}, + + // Destroy. + {{"pthread_mutex_destroy", 1}, &PthreadLockChecker::DestroyPthreadLock}, + {{"lck_mtx_destroy", 2}, &PthreadLockChecker::DestroyXNULock}, + // TODO: pthread_rwlock_destroy(1 argument). + // TODO: lck_rw_destroy(2 arguments). + }; - void AcquireLock(CheckerContext &C, const CallExpr *CE, SVal lock, - bool isTryLock, enum LockingSemantics semantics) const; + CallDescriptionMap<FnCheck> FuchsiaCallbacks = { + // Init. + {{"spin_lock_init", 1}, &PthreadLockChecker::InitAnyLock}, + + // Acquire. + {{"spin_lock", 1}, &PthreadLockChecker::AcquirePthreadLock}, + {{"spin_lock_save", 3}, &PthreadLockChecker::AcquirePthreadLock}, + {{"sync_mutex_lock", 1}, &PthreadLockChecker::AcquirePthreadLock}, + {{"sync_mutex_lock_with_waiter", 1}, + &PthreadLockChecker::AcquirePthreadLock}, + + // Try. + {{"spin_trylock", 1}, &PthreadLockChecker::TryFuchsiaLock}, + {{"sync_mutex_trylock", 1}, &PthreadLockChecker::TryFuchsiaLock}, + {{"sync_mutex_timedlock", 2}, &PthreadLockChecker::TryFuchsiaLock}, + + // Release. + {{"spin_unlock", 1}, &PthreadLockChecker::ReleaseAnyLock}, + {{"spin_unlock_restore", 3}, &PthreadLockChecker::ReleaseAnyLock}, + {{"sync_mutex_unlock", 1}, &PthreadLockChecker::ReleaseAnyLock}, + }; + + CallDescriptionMap<FnCheck> C11Callbacks = { + // Init. + {{"mtx_init", 2}, &PthreadLockChecker::InitAnyLock}, + + // Acquire. + {{"mtx_lock", 1}, &PthreadLockChecker::AcquirePthreadLock}, + + // Try. + {{"mtx_trylock", 1}, &PthreadLockChecker::TryC11Lock}, + {{"mtx_timedlock", 2}, &PthreadLockChecker::TryC11Lock}, + + // Release. + {{"mtx_unlock", 1}, &PthreadLockChecker::ReleaseAnyLock}, + + // Destroy + {{"mtx_destroy", 1}, &PthreadLockChecker::DestroyPthreadLock}, + }; - void ReleaseLock(CheckerContext &C, const CallExpr *CE, SVal lock) const; - void DestroyLock(CheckerContext &C, const CallExpr *CE, SVal Lock, - enum LockingSemantics semantics) const; - void InitLock(CheckerContext &C, const CallExpr *CE, SVal Lock) const; - void reportUseDestroyedBug(CheckerContext &C, const CallExpr *CE) const; ProgramStateRef resolvePossiblyDestroyedMutex(ProgramStateRef state, const MemRegion *lockR, const SymbolRef *sym) const; + void reportUseDestroyedBug(const CallEvent &Call, CheckerContext &C, + unsigned ArgNo, CheckerKind checkKind) const; + + // Init. + void InitAnyLock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void InitLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, + SVal Lock, CheckerKind checkkind) const; + + // Lock, Try-lock. + void AcquirePthreadLock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void AcquireXNULock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void TryPthreadLock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void TryXNULock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void TryFuchsiaLock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void TryC11Lock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void AcquireLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, + SVal lock, bool isTryLock, LockingSemantics semantics, + CheckerKind checkkind) const; + + // Release. + void ReleaseAnyLock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void ReleaseLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, + SVal lock, CheckerKind checkkind) const; + + // Destroy. + void DestroyPthreadLock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void DestroyXNULock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkkind) const; + void DestroyLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, + SVal Lock, LockingSemantics semantics, + CheckerKind checkkind) const; + +public: + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + ProgramStateRef + checkRegionChanges(ProgramStateRef State, const InvalidatedSymbols *Symbols, + ArrayRef<const MemRegion *> ExplicitRegions, + ArrayRef<const MemRegion *> Regions, + const LocationContext *LCtx, const CallEvent *Call) const; + void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, + const char *Sep) const override; + +private: + mutable std::unique_ptr<BugType> BT_doublelock[CK_NumCheckKinds]; + mutable std::unique_ptr<BugType> BT_doubleunlock[CK_NumCheckKinds]; + mutable std::unique_ptr<BugType> BT_destroylock[CK_NumCheckKinds]; + mutable std::unique_ptr<BugType> BT_initlock[CK_NumCheckKinds]; + mutable std::unique_ptr<BugType> BT_lor[CK_NumCheckKinds]; + + void initBugType(CheckerKind checkKind) const { + if (BT_doublelock[checkKind]) + return; + BT_doublelock[checkKind].reset( + new BugType{CheckNames[checkKind], "Double locking", "Lock checker"}); + BT_doubleunlock[checkKind].reset( + new BugType{CheckNames[checkKind], "Double unlocking", "Lock checker"}); + BT_destroylock[checkKind].reset(new BugType{ + CheckNames[checkKind], "Use destroyed lock", "Lock checker"}); + BT_initlock[checkKind].reset(new BugType{ + CheckNames[checkKind], "Init invalid lock", "Lock checker"}); + BT_lor[checkKind].reset(new BugType{CheckNames[checkKind], + "Lock order reversal", "Lock checker"}); + } }; } // end anonymous namespace @@ -106,43 +252,23 @@ REGISTER_MAP_WITH_PROGRAMSTATE(LockMap, const MemRegion *, LockState) // Return values for unresolved calls to pthread_mutex_destroy(). REGISTER_MAP_WITH_PROGRAMSTATE(DestroyRetVal, const MemRegion *, SymbolRef) -void PthreadLockChecker::checkPostStmt(const CallExpr *CE, +void PthreadLockChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { - StringRef FName = C.getCalleeName(CE); - if (FName.empty()) + // An additional umbrella check that all functions modeled by this checker + // are global C functions. + // TODO: Maybe make this the default behavior of CallDescription + // with exactly one identifier? + // FIXME: Try to handle cases when the implementation was inlined rather + // than just giving up. + if (!Call.isGlobalCFunction() || C.wasInlined) return; - if (CE->getNumArgs() != 1 && CE->getNumArgs() != 2) - return; - - if (FName == "pthread_mutex_lock" || - FName == "pthread_rwlock_rdlock" || - FName == "pthread_rwlock_wrlock") - 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, C.getSVal(CE->getArg(0)), false, XNUSemantics); - else if (FName == "pthread_mutex_trylock" || - FName == "pthread_rwlock_tryrdlock" || - FName == "pthread_rwlock_trywrlock") - 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, 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, C.getSVal(CE->getArg(0))); - else if (FName == "pthread_mutex_destroy") - DestroyLock(C, CE, C.getSVal(CE->getArg(0)), PthreadSemantics); - else if (FName == "lck_mtx_destroy") - DestroyLock(C, CE, C.getSVal(CE->getArg(0)), XNUSemantics); - else if (FName == "pthread_mutex_init") - InitLock(C, CE, C.getSVal(CE->getArg(0))); + if (const FnCheck *Callback = PThreadCallbacks.lookup(Call)) + (this->**Callback)(Call, C, CK_PthreadLockChecker); + else if (const FnCheck *Callback = FuchsiaCallbacks.lookup(Call)) + (this->**Callback)(Call, C, CK_FuchsiaLockChecker); + else if (const FnCheck *Callback = C11Callbacks.lookup(Call)) + (this->**Callback)(Call, C, CK_C11LockChecker); } // When a lock is destroyed, in some semantics(like PthreadSemantics) we are not @@ -204,7 +330,7 @@ void PthreadLockChecker::printState(raw_ostream &Out, ProgramStateRef State, LockSetTy LS = State->get<LockSet>(); if (!LS.isEmpty()) { Out << Sep << "Mutex lock order:" << NL; - for (auto I: LS) { + for (auto I : LS) { I->dumpToStream(Out); Out << NL; } @@ -213,9 +339,53 @@ void PthreadLockChecker::printState(raw_ostream &Out, ProgramStateRef State, // TODO: Dump destroyed mutex symbols? } -void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, - SVal lock, bool isTryLock, - enum LockingSemantics semantics) const { +void PthreadLockChecker::AcquirePthreadLock(const CallEvent &Call, + CheckerContext &C, + CheckerKind checkKind) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), false, PthreadSemantics, + checkKind); +} + +void PthreadLockChecker::AcquireXNULock(const CallEvent &Call, + CheckerContext &C, + CheckerKind checkKind) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), false, XNUSemantics, + checkKind); +} + +void PthreadLockChecker::TryPthreadLock(const CallEvent &Call, + CheckerContext &C, + CheckerKind checkKind) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics, + checkKind); +} + +void PthreadLockChecker::TryXNULock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkKind) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics, + checkKind); +} + +void PthreadLockChecker::TryFuchsiaLock(const CallEvent &Call, + CheckerContext &C, + CheckerKind checkKind) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics, + checkKind); +} + +void PthreadLockChecker::TryC11Lock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkKind) const { + AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics, + checkKind); +} + +void PthreadLockChecker::AcquireLockAux(const CallEvent &Call, + CheckerContext &C, unsigned ArgNo, + SVal lock, bool isTryLock, + enum LockingSemantics semantics, + CheckerKind checkKind) const { + if (!ChecksEnabled[checkKind]) + return; const MemRegion *lockR = lock.getAsRegion(); if (!lockR) @@ -226,27 +396,19 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, if (sym) state = resolvePossiblyDestroyedMutex(state, lockR, sym); - SVal X = C.getSVal(CE); - if (X.isUnknownOrUndef()) - return; - - DefinedSVal retVal = X.castAs<DefinedSVal>(); - if (const LockState *LState = state->get<LockMap>(lockR)) { if (LState->isLocked()) { - if (!BT_doublelock) - BT_doublelock.reset(new BugType(this, "Double locking", - "Lock checker")); ExplodedNode *N = C.generateErrorNode(); if (!N) return; + initBugType(checkKind); auto report = std::make_unique<PathSensitiveBugReport>( - *BT_doublelock, "This lock has already been acquired", N); - report->addRange(CE->getArg(0)->getSourceRange()); + *BT_doublelock[checkKind], "This lock has already been acquired", N); + report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(report)); return; } else if (LState->isDestroyed()) { - reportUseDestroyedBug(C, CE); + reportUseDestroyedBug(Call, C, ArgNo, checkKind); return; } } @@ -254,25 +416,35 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, ProgramStateRef lockSucc = state; if (isTryLock) { // Bifurcate the state, and allow a mode where the lock acquisition fails. - ProgramStateRef lockFail; - switch (semantics) { - case PthreadSemantics: - std::tie(lockFail, lockSucc) = state->assume(retVal); - break; - case XNUSemantics: - std::tie(lockSucc, lockFail) = state->assume(retVal); - break; - default: - llvm_unreachable("Unknown tryLock locking semantics"); + SVal RetVal = Call.getReturnValue(); + if (auto DefinedRetVal = RetVal.getAs<DefinedSVal>()) { + ProgramStateRef lockFail; + switch (semantics) { + case PthreadSemantics: + std::tie(lockFail, lockSucc) = state->assume(*DefinedRetVal); + break; + case XNUSemantics: + std::tie(lockSucc, lockFail) = state->assume(*DefinedRetVal); + break; + default: + llvm_unreachable("Unknown tryLock locking semantics"); + } + assert(lockFail && lockSucc); + C.addTransition(lockFail); } - assert(lockFail && lockSucc); - C.addTransition(lockFail); - + // We might want to handle the case when the mutex lock function was inlined + // and returned an Unknown or Undefined value. } else if (semantics == PthreadSemantics) { // Assume that the return value was 0. - lockSucc = state->assume(retVal, false); - assert(lockSucc); - + SVal RetVal = Call.getReturnValue(); + if (auto DefinedRetVal = RetVal.getAs<DefinedSVal>()) { + // FIXME: If the lock function was inlined and returned true, + // we need to behave sanely - at least generate sink. + lockSucc = state->assume(*DefinedRetVal, false); + assert(lockSucc); + } + // We might want to handle the case when the mutex lock function was inlined + // and returned an Unknown or Undefined value. } else { // XNU locking semantics return void on non-try locks assert((semantics == XNUSemantics) && "Unknown locking semantics"); @@ -285,8 +457,18 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, C.addTransition(lockSucc); } -void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE, - SVal lock) const { +void PthreadLockChecker::ReleaseAnyLock(const CallEvent &Call, + CheckerContext &C, + CheckerKind checkKind) const { + ReleaseLockAux(Call, C, 0, Call.getArgSVal(0), checkKind); +} + +void PthreadLockChecker::ReleaseLockAux(const CallEvent &Call, + CheckerContext &C, unsigned ArgNo, + SVal lock, + CheckerKind checkKind) const { + if (!ChecksEnabled[checkKind]) + return; const MemRegion *lockR = lock.getAsRegion(); if (!lockR) @@ -299,39 +481,37 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE, if (const LockState *LState = state->get<LockMap>(lockR)) { if (LState->isUnlocked()) { - if (!BT_doubleunlock) - BT_doubleunlock.reset(new BugType(this, "Double unlocking", - "Lock checker")); ExplodedNode *N = C.generateErrorNode(); if (!N) return; + initBugType(checkKind); auto Report = std::make_unique<PathSensitiveBugReport>( - *BT_doubleunlock, "This lock has already been unlocked", N); - Report->addRange(CE->getArg(0)->getSourceRange()); + *BT_doubleunlock[checkKind], "This lock has already been unlocked", + N); + Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(Report)); return; } else if (LState->isDestroyed()) { - reportUseDestroyedBug(C, CE); + reportUseDestroyedBug(Call, C, ArgNo, checkKind); return; } } LockSetTy LS = state->get<LockSet>(); - // FIXME: Better analysis requires IPA for wrappers. - if (!LS.isEmpty()) { const MemRegion *firstLockR = LS.getHead(); if (firstLockR != lockR) { - if (!BT_lor) - BT_lor.reset(new BugType(this, "Lock order reversal", "Lock checker")); ExplodedNode *N = C.generateErrorNode(); if (!N) return; + initBugType(checkKind); auto report = std::make_unique<PathSensitiveBugReport>( - *BT_lor, "This was not the most recently acquired lock. Possible " - "lock order reversal", N); - report->addRange(CE->getArg(0)->getSourceRange()); + *BT_lor[checkKind], + "This was not the most recently acquired lock. Possible " + "lock order reversal", + N); + report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(report)); return; } @@ -343,9 +523,25 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE, C.addTransition(state); } -void PthreadLockChecker::DestroyLock(CheckerContext &C, const CallExpr *CE, - SVal Lock, - enum LockingSemantics semantics) const { +void PthreadLockChecker::DestroyPthreadLock(const CallEvent &Call, + CheckerContext &C, + CheckerKind checkKind) const { + DestroyLockAux(Call, C, 0, Call.getArgSVal(0), PthreadSemantics, checkKind); +} + +void PthreadLockChecker::DestroyXNULock(const CallEvent &Call, + CheckerContext &C, + CheckerKind checkKind) const { + DestroyLockAux(Call, C, 0, Call.getArgSVal(0), XNUSemantics, checkKind); +} + +void PthreadLockChecker::DestroyLockAux(const CallEvent &Call, + CheckerContext &C, unsigned ArgNo, + SVal Lock, + enum LockingSemantics semantics, + CheckerKind checkKind) const { + if (!ChecksEnabled[checkKind]) + return; const MemRegion *LockR = Lock.getAsRegion(); if (!LockR) @@ -362,7 +558,7 @@ void PthreadLockChecker::DestroyLock(CheckerContext &C, const CallExpr *CE, // PthreadSemantics if (semantics == PthreadSemantics) { if (!LState || LState->isUnlocked()) { - SymbolRef sym = C.getSVal(CE).getAsSymbol(); + SymbolRef sym = Call.getReturnValue().getAsSymbol(); if (!sym) { State = State->remove<LockMap>(LockR); C.addTransition(State); @@ -393,20 +589,26 @@ void PthreadLockChecker::DestroyLock(CheckerContext &C, const CallExpr *CE, Message = "This lock has already been destroyed"; } - if (!BT_destroylock) - BT_destroylock.reset(new BugType(this, "Destroy invalid lock", - "Lock checker")); ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto Report = - std::make_unique<PathSensitiveBugReport>(*BT_destroylock, Message, N); - Report->addRange(CE->getArg(0)->getSourceRange()); + initBugType(checkKind); + auto Report = std::make_unique<PathSensitiveBugReport>( + *BT_destroylock[checkKind], Message, N); + Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(Report)); } -void PthreadLockChecker::InitLock(CheckerContext &C, const CallExpr *CE, - SVal Lock) const { +void PthreadLockChecker::InitAnyLock(const CallEvent &Call, CheckerContext &C, + CheckerKind checkKind) const { + InitLockAux(Call, C, 0, Call.getArgSVal(0), checkKind); +} + +void PthreadLockChecker::InitLockAux(const CallEvent &Call, CheckerContext &C, + unsigned ArgNo, SVal Lock, + CheckerKind checkKind) const { + if (!ChecksEnabled[checkKind]) + return; const MemRegion *LockR = Lock.getAsRegion(); if (!LockR) @@ -433,29 +635,27 @@ void PthreadLockChecker::InitLock(CheckerContext &C, const CallExpr *CE, Message = "This lock has already been initialized"; } - if (!BT_initlock) - BT_initlock.reset(new BugType(this, "Init invalid lock", - "Lock checker")); ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto Report = - std::make_unique<PathSensitiveBugReport>(*BT_initlock, Message, N); - Report->addRange(CE->getArg(0)->getSourceRange()); + initBugType(checkKind); + auto Report = std::make_unique<PathSensitiveBugReport>( + *BT_initlock[checkKind], Message, N); + Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(Report)); } -void PthreadLockChecker::reportUseDestroyedBug(CheckerContext &C, - const CallExpr *CE) const { - if (!BT_destroylock) - BT_destroylock.reset(new BugType(this, "Use destroyed lock", - "Lock checker")); +void PthreadLockChecker::reportUseDestroyedBug(const CallEvent &Call, + CheckerContext &C, + unsigned ArgNo, + CheckerKind checkKind) const { ExplodedNode *N = C.generateErrorNode(); if (!N) return; + initBugType(checkKind); auto Report = std::make_unique<PathSensitiveBugReport>( - *BT_destroylock, "This lock has already been destroyed", N); - Report->addRange(CE->getArg(0)->getSourceRange()); + *BT_destroylock[checkKind], "This lock has already been destroyed", N); + Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); C.emitReport(std::move(Report)); } @@ -463,26 +663,80 @@ void PthreadLockChecker::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { ProgramStateRef State = C.getState(); - // TODO: Clean LockMap when a mutex region dies. - - DestroyRetValTy TrackedSymbols = State->get<DestroyRetVal>(); - for (DestroyRetValTy::iterator I = TrackedSymbols.begin(), - E = TrackedSymbols.end(); - I != E; ++I) { - const SymbolRef Sym = I->second; - const MemRegion *lockR = I->first; - bool IsSymDead = SymReaper.isDead(Sym); - // Remove the dead symbol from the return value symbols map. - if (IsSymDead) - State = resolvePossiblyDestroyedMutex(State, lockR, &Sym); + for (auto I : State->get<DestroyRetVal>()) { + // Once the return value symbol dies, no more checks can be performed + // against it. See if the return value was checked before this point. + // This would remove the symbol from the map as well. + if (SymReaper.isDead(I.second)) + State = resolvePossiblyDestroyedMutex(State, I.first, &I.second); + } + + for (auto I : State->get<LockMap>()) { + // Stop tracking dead mutex regions as well. + if (!SymReaper.isLiveRegion(I.first)) + State = State->remove<LockMap>(I.first); } + + // TODO: We probably need to clean up the lock stack as well. + // It is tricky though: even if the mutex cannot be unlocked anymore, + // it can still participate in lock order reversal resolution. + C.addTransition(State); } -void ento::registerPthreadLockChecker(CheckerManager &mgr) { - mgr.registerChecker<PthreadLockChecker>(); +ProgramStateRef PthreadLockChecker::checkRegionChanges( + ProgramStateRef State, const InvalidatedSymbols *Symbols, + ArrayRef<const MemRegion *> ExplicitRegions, + ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx, + const CallEvent *Call) const { + + bool IsLibraryFunction = false; + if (Call && Call->isGlobalCFunction()) { + // Avoid invalidating mutex state when a known supported function is called. + if (PThreadCallbacks.lookup(*Call) || FuchsiaCallbacks.lookup(*Call) || + C11Callbacks.lookup(*Call)) + return State; + + if (Call->isInSystemHeader()) + IsLibraryFunction = true; + } + + for (auto R : Regions) { + // We assume that system library function wouldn't touch the mutex unless + // it takes the mutex explicitly as an argument. + // FIXME: This is a bit quadratic. + if (IsLibraryFunction && + std::find(ExplicitRegions.begin(), ExplicitRegions.end(), R) == + ExplicitRegions.end()) + continue; + + State = State->remove<LockMap>(R); + State = State->remove<DestroyRetVal>(R); + + // TODO: We need to invalidate the lock stack as well. This is tricky + // to implement correctly and efficiently though, because the effects + // of mutex escapes on lock order may be fairly varied. + } + + return State; } -bool ento::shouldRegisterPthreadLockChecker(const LangOptions &LO) { - return true; +void ento::registerPthreadLockBase(CheckerManager &mgr) { + mgr.registerChecker<PthreadLockChecker>(); } + +bool ento::shouldRegisterPthreadLockBase(const CheckerManager &mgr) { return true; } + +#define REGISTER_CHECKER(name) \ + void ento::register##name(CheckerManager &mgr) { \ + PthreadLockChecker *checker = mgr.getChecker<PthreadLockChecker>(); \ + checker->ChecksEnabled[PthreadLockChecker::CK_##name] = true; \ + checker->CheckNames[PthreadLockChecker::CK_##name] = \ + mgr.getCurrentCheckerName(); \ + } \ + \ + bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; } + +REGISTER_CHECKER(PthreadLockChecker) +REGISTER_CHECKER(FuchsiaLockChecker) +REGISTER_CHECKER(C11LockChecker) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp index 6f8cb1432bb1..3f3267ff9391 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp @@ -12,12 +12,12 @@ //===----------------------------------------------------------------------===// #include "RetainCountChecker.h" +#include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" using namespace clang; using namespace ento; using namespace retaincountchecker; -using llvm::StrInStrNoCase; REGISTER_MAP_WITH_PROGRAMSTATE(RefBindings, SymbolRef, RefVal) @@ -701,7 +701,7 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ, for (ProgramStateRef St : Out) { if (DeallocSent) { - C.addTransition(St, C.getPredecessor(), &DeallocSentTag); + C.addTransition(St, C.getPredecessor(), &getDeallocSentTag()); } else { C.addTransition(St); } @@ -844,13 +844,13 @@ RetainCountChecker::errorKindToBugKind(RefVal::Kind ErrorKind, SymbolRef Sym) const { switch (ErrorKind) { case RefVal::ErrorUseAfterRelease: - return useAfterRelease; + return *UseAfterRelease; case RefVal::ErrorReleaseNotOwned: - return releaseNotOwned; + return *ReleaseNotOwned; case RefVal::ErrorDeallocNotOwned: if (Sym->getType()->getPointeeCXXRecordDecl()) - return freeNotOwned; - return deallocNotOwned; + return *FreeNotOwned; + return *DeallocNotOwned; default: llvm_unreachable("Unhandled error."); } @@ -946,7 +946,7 @@ bool RetainCountChecker::evalCall(const CallEvent &Call, // Assume that output is zero on the other branch. NullOutputState = NullOutputState->BindExpr( CE, LCtx, C.getSValBuilder().makeNull(), /*Invalidate=*/false); - C.addTransition(NullOutputState, &CastFailTag); + C.addTransition(NullOutputState, &getCastFailTag()); // And on the original branch assume that both input and // output are non-zero. @@ -1095,7 +1095,7 @@ ExplodedNode * RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S, if (N) { const LangOptions &LOpts = C.getASTContext().getLangOpts(); auto R = - std::make_unique<RefLeakReport>(leakAtReturn, LOpts, N, Sym, C); + std::make_unique<RefLeakReport>(*LeakAtReturn, LOpts, N, Sym, C); C.emitReport(std::move(R)); } return N; @@ -1120,7 +1120,7 @@ ExplodedNode * RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S, ExplodedNode *N = C.addTransition(state, Pred, &ReturnNotOwnedTag); if (N) { auto R = std::make_unique<RefCountReport>( - returnNotOwnedForOwned, C.getASTContext().getLangOpts(), N, Sym); + *ReturnNotOwnedForOwned, C.getASTContext().getLangOpts(), N, Sym); C.emitReport(std::move(R)); } return N; @@ -1273,8 +1273,8 @@ RetainCountChecker::handleAutoreleaseCounts(ProgramStateRef state, os << "has a +" << V.getCount() << " retain count"; const LangOptions &LOpts = Ctx.getASTContext().getLangOpts(); - auto R = std::make_unique<RefCountReport>(overAutorelease, LOpts, N, Sym, - os.str()); + auto R = std::make_unique<RefCountReport>(*OverAutorelease, LOpts, N, Sym, + os.str()); Ctx.emitReport(std::move(R)); } @@ -1320,7 +1320,7 @@ RetainCountChecker::processLeaks(ProgramStateRef state, if (N) { for (SymbolRef L : Leaked) { - const RefCountBug &BT = Pred ? leakWithinFunction : leakAtReturn; + const RefCountBug &BT = Pred ? *LeakWithinFunction : *LeakAtReturn; Ctx.emitReport(std::make_unique<RefLeakReport>(BT, LOpts, N, L, Ctx)); } } @@ -1473,48 +1473,73 @@ void RetainCountChecker::printState(raw_ostream &Out, ProgramStateRef State, // Checker registration. //===----------------------------------------------------------------------===// +std::unique_ptr<CheckerProgramPointTag> RetainCountChecker::DeallocSentTag; +std::unique_ptr<CheckerProgramPointTag> RetainCountChecker::CastFailTag; + void ento::registerRetainCountBase(CheckerManager &Mgr) { - Mgr.registerChecker<RetainCountChecker>(); + auto *Chk = Mgr.registerChecker<RetainCountChecker>(); + Chk->DeallocSentTag = + std::make_unique<CheckerProgramPointTag>(Chk, "DeallocSent"); + Chk->CastFailTag = + std::make_unique<CheckerProgramPointTag>(Chk, "DynamicCastFail"); } -bool ento::shouldRegisterRetainCountBase(const LangOptions &LO) { +bool ento::shouldRegisterRetainCountBase(const CheckerManager &mgr) { return true; } - -// FIXME: remove this, hack for backwards compatibility: -// it should be possible to enable the NS/CF retain count checker as -// osx.cocoa.RetainCount, and it should be possible to disable -// osx.OSObjectRetainCount using osx.cocoa.RetainCount:CheckOSObject=false. -static bool getOption(AnalyzerOptions &Options, - StringRef Postfix, - StringRef Value) { - auto I = Options.Config.find( - (StringRef("osx.cocoa.RetainCount:") + Postfix).str()); - if (I != Options.Config.end()) - return I->getValue() == Value; - return false; -} - void ento::registerRetainCountChecker(CheckerManager &Mgr) { auto *Chk = Mgr.getChecker<RetainCountChecker>(); Chk->TrackObjCAndCFObjects = true; - Chk->TrackNSCFStartParam = getOption(Mgr.getAnalyzerOptions(), - "TrackNSCFStartParam", - "true"); + Chk->TrackNSCFStartParam = Mgr.getAnalyzerOptions().getCheckerBooleanOption( + Mgr.getCurrentCheckerName(), "TrackNSCFStartParam"); + +#define INIT_BUGTYPE(KIND) \ + Chk->KIND = std::make_unique<RefCountBug>(Mgr.getCurrentCheckerName(), \ + RefCountBug::KIND); + // TODO: Ideally, we should have a checker for each of these bug types. + INIT_BUGTYPE(UseAfterRelease) + INIT_BUGTYPE(ReleaseNotOwned) + INIT_BUGTYPE(DeallocNotOwned) + INIT_BUGTYPE(FreeNotOwned) + INIT_BUGTYPE(OverAutorelease) + INIT_BUGTYPE(ReturnNotOwnedForOwned) + INIT_BUGTYPE(LeakWithinFunction) + INIT_BUGTYPE(LeakAtReturn) +#undef INIT_BUGTYPE } -bool ento::shouldRegisterRetainCountChecker(const LangOptions &LO) { +bool ento::shouldRegisterRetainCountChecker(const CheckerManager &mgr) { return true; } void ento::registerOSObjectRetainCountChecker(CheckerManager &Mgr) { auto *Chk = Mgr.getChecker<RetainCountChecker>(); - if (!getOption(Mgr.getAnalyzerOptions(), - "CheckOSObject", - "false")) - Chk->TrackOSObjects = true; + Chk->TrackOSObjects = true; + + // FIXME: We want bug reports to always have the same checker name associated + // with them, yet here, if RetainCountChecker is disabled but + // OSObjectRetainCountChecker is enabled, the checker names will be different. + // This hack will make it so that the checker name depends on which checker is + // enabled rather than on the registration order. + // For the most part, we want **non-hidden checkers** to be associated with + // diagnostics, and **hidden checker options** with the fine-tuning of + // modeling. Following this logic, OSObjectRetainCountChecker should be the + // latter, but we can't just remove it for backward compatibility reasons. +#define LAZY_INIT_BUGTYPE(KIND) \ + if (!Chk->KIND) \ + Chk->KIND = std::make_unique<RefCountBug>(Mgr.getCurrentCheckerName(), \ + RefCountBug::KIND); + LAZY_INIT_BUGTYPE(UseAfterRelease) + LAZY_INIT_BUGTYPE(ReleaseNotOwned) + LAZY_INIT_BUGTYPE(DeallocNotOwned) + LAZY_INIT_BUGTYPE(FreeNotOwned) + LAZY_INIT_BUGTYPE(OverAutorelease) + LAZY_INIT_BUGTYPE(ReturnNotOwnedForOwned) + LAZY_INIT_BUGTYPE(LeakWithinFunction) + LAZY_INIT_BUGTYPE(LeakAtReturn) +#undef LAZY_INIT_BUGTYPE } -bool ento::shouldRegisterOSObjectRetainCountChecker(const LangOptions &LO) { +bool ento::shouldRegisterOSObjectRetainCountChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h index dd79bbef321c..223e28c2c5b8 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h @@ -251,20 +251,20 @@ class RetainCountChecker eval::Assume, eval::Call > { - RefCountBug useAfterRelease{this, RefCountBug::UseAfterRelease}; - RefCountBug releaseNotOwned{this, RefCountBug::ReleaseNotOwned}; - RefCountBug deallocNotOwned{this, RefCountBug::DeallocNotOwned}; - RefCountBug freeNotOwned{this, RefCountBug::FreeNotOwned}; - RefCountBug overAutorelease{this, RefCountBug::OverAutorelease}; - RefCountBug returnNotOwnedForOwned{this, RefCountBug::ReturnNotOwnedForOwned}; - RefCountBug leakWithinFunction{this, RefCountBug::LeakWithinFunction}; - RefCountBug leakAtReturn{this, RefCountBug::LeakAtReturn}; - - CheckerProgramPointTag DeallocSentTag{this, "DeallocSent"}; - CheckerProgramPointTag CastFailTag{this, "DynamicCastFail"}; +public: + std::unique_ptr<RefCountBug> UseAfterRelease; + std::unique_ptr<RefCountBug> ReleaseNotOwned; + std::unique_ptr<RefCountBug> DeallocNotOwned; + std::unique_ptr<RefCountBug> FreeNotOwned; + std::unique_ptr<RefCountBug> OverAutorelease; + std::unique_ptr<RefCountBug> ReturnNotOwnedForOwned; + std::unique_ptr<RefCountBug> LeakWithinFunction; + std::unique_ptr<RefCountBug> LeakAtReturn; mutable std::unique_ptr<RetainSummaryManager> Summaries; -public: + + static std::unique_ptr<CheckerProgramPointTag> DeallocSentTag; + static std::unique_ptr<CheckerProgramPointTag> CastFailTag; /// Track Objective-C and CoreFoundation objects. bool TrackObjCAndCFObjects = false; @@ -360,13 +360,11 @@ public: CheckerContext &Ctx, ExplodedNode *Pred = nullptr) const; - const CheckerProgramPointTag &getDeallocSentTag() const { - return DeallocSentTag; + static const CheckerProgramPointTag &getDeallocSentTag() { + return *DeallocSentTag; } - const CheckerProgramPointTag &getCastFailTag() const { - return CastFailTag; - } + static const CheckerProgramPointTag &getCastFailTag() { return *CastFailTag; } private: /// Perform the necessary checks and state adjustments at the end of the diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp index 9853758f7f2c..1d8ed90f7590 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp @@ -18,7 +18,7 @@ using namespace clang; using namespace ento; using namespace retaincountchecker; -StringRef RefCountBug::bugTypeToName(RefCountBug::RefCountBugType BT) { +StringRef RefCountBug::bugTypeToName(RefCountBug::RefCountBugKind BT) { switch (BT) { case UseAfterRelease: return "Use-after-release"; @@ -37,7 +37,7 @@ StringRef RefCountBug::bugTypeToName(RefCountBug::RefCountBugType BT) { case LeakAtReturn: return "Leak of returned object"; } - llvm_unreachable("Unknown RefCountBugType"); + llvm_unreachable("Unknown RefCountBugKind"); } StringRef RefCountBug::getDescription() const { @@ -60,13 +60,14 @@ StringRef RefCountBug::getDescription() const { case LeakAtReturn: return ""; } - llvm_unreachable("Unknown RefCountBugType"); + llvm_unreachable("Unknown RefCountBugKind"); } -RefCountBug::RefCountBug(const CheckerBase *Checker, RefCountBugType BT) +RefCountBug::RefCountBug(CheckerNameRef Checker, RefCountBugKind BT) : BugType(Checker, bugTypeToName(BT), categories::MemoryRefCount, - /*SuppressOnSink=*/BT == LeakWithinFunction || BT == LeakAtReturn), - BT(BT), Checker(Checker) {} + /*SuppressOnSink=*/BT == LeakWithinFunction || + BT == LeakAtReturn), + BT(BT) {} static bool isNumericLiteralExpression(const Expr *E) { // FIXME: This set of cases was copied from SemaExprObjC. @@ -84,7 +85,7 @@ static std::string getPrettyTypeName(QualType QT) { QualType PT = QT->getPointeeType(); if (!PT.isNull() && !QT->getAs<TypedefType>()) if (const auto *RD = PT->getAsCXXRecordDecl()) - return RD->getName(); + return std::string(RD->getName()); return QT.getAsString(); } @@ -453,8 +454,6 @@ RefCountReportVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &BR) { const auto &BT = static_cast<const RefCountBug&>(BR.getBugType()); - const auto *Checker = - static_cast<const RetainCountChecker *>(BT.getChecker()); bool IsFreeUnowned = BT.getBugType() == RefCountBug::FreeNotOwned || BT.getBugType() == RefCountBug::DeallocNotOwned; @@ -545,11 +544,11 @@ RefCountReportVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, const ProgramPointTag *Tag = N->getLocation().getTag(); - if (Tag == &Checker->getCastFailTag()) { + if (Tag == &RetainCountChecker::getCastFailTag()) { os << "Assuming dynamic cast returns null due to type mismatch"; } - if (Tag == &Checker->getDeallocSentTag()) { + if (Tag == &RetainCountChecker::getDeallocSentTag()) { // We only have summaries attached to nodes after evaluating CallExpr and // ObjCMessageExprs. const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt(); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h index e9e277754054..286a8ae2ef7d 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h @@ -26,7 +26,7 @@ namespace retaincountchecker { class RefCountBug : public BugType { public: - enum RefCountBugType { + enum RefCountBugKind { UseAfterRelease, ReleaseNotOwned, DeallocNotOwned, @@ -36,21 +36,14 @@ public: LeakWithinFunction, LeakAtReturn, }; - RefCountBug(const CheckerBase *checker, RefCountBugType BT); + RefCountBug(CheckerNameRef Checker, RefCountBugKind BT); StringRef getDescription() const; - RefCountBugType getBugType() const { - return BT; - } - - const CheckerBase *getChecker() const { - return Checker; - } + RefCountBugKind getBugType() const { return BT; } private: - RefCountBugType BT; - const CheckerBase *Checker; - static StringRef bugTypeToName(RefCountBugType BT); + RefCountBugKind BT; + static StringRef bugTypeToName(RefCountBugKind BT); }; class RefCountReport : public PathSensitiveBugReport { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp index abd1a074b487..599d4f306aa1 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp @@ -16,6 +16,7 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" using namespace clang; @@ -51,15 +52,14 @@ void ReturnPointerRangeChecker::checkPreStmt(const ReturnStmt *RS, // pointer casts. if (Idx.isZeroConstant()) return; + // FIXME: All of this out-of-bounds checking should eventually be refactored // into a common place. + DefinedOrUnknownSVal ElementCount = getDynamicElementCount( + state, ER->getSuperRegion(), C.getSValBuilder(), ER->getValueType()); - DefinedOrUnknownSVal NumElements - = C.getStoreManager().getSizeInElements(state, ER->getSuperRegion(), - ER->getValueType()); - - ProgramStateRef StInBound = state->assumeInBound(Idx, NumElements, true); - ProgramStateRef StOutBound = state->assumeInBound(Idx, NumElements, false); + ProgramStateRef StInBound = state->assumeInBound(Idx, ElementCount, true); + ProgramStateRef StOutBound = state->assumeInBound(Idx, ElementCount, false); if (StOutBound && !StInBound) { ExplodedNode *N = C.generateErrorNode(StOutBound); @@ -91,6 +91,6 @@ void ento::registerReturnPointerRangeChecker(CheckerManager &mgr) { mgr.registerChecker<ReturnPointerRangeChecker>(); } -bool ento::shouldRegisterReturnPointerRangeChecker(const LangOptions &LO) { +bool ento::shouldRegisterReturnPointerRangeChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp index fbd15d864424..5266cbf86b44 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp @@ -122,6 +122,6 @@ void ento::registerReturnUndefChecker(CheckerManager &mgr) { mgr.registerChecker<ReturnUndefChecker>(); } -bool ento::shouldRegisterReturnUndefChecker(const LangOptions &LO) { +bool ento::shouldRegisterReturnUndefChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp index 103208d8b5a5..14ecede17083 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp @@ -99,13 +99,13 @@ void ReturnValueChecker::checkPostCall(const CallEvent &Call, std::string Name = getName(Call); const NoteTag *CallTag = C.getNoteTag( - [Name, ExpectedValue](BugReport &) -> std::string { + [Name, ExpectedValue](PathSensitiveBugReport &) -> std::string { SmallString<128> Msg; llvm::raw_svector_ostream Out(Msg); Out << '\'' << Name << "' returns " << (ExpectedValue ? "true" : "false"); - return Out.str(); + return std::string(Out.str()); }, /*IsPrunable=*/true); @@ -154,7 +154,7 @@ void ReturnValueChecker::checkEndFunction(const ReturnStmt *RS, Out << '\'' << Name << "' returns " << (ExpectedValue ? "false" : "true"); - return Out.str(); + return std::string(Out.str()); }, /*IsPrunable=*/false); @@ -165,6 +165,6 @@ void ento::registerReturnValueChecker(CheckerManager &Mgr) { Mgr.registerChecker<ReturnValueChecker>(); } -bool ento::shouldRegisterReturnValueChecker(const LangOptions &LO) { +bool ento::shouldRegisterReturnValueChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp index 5e305aa709b6..d9dc72ddaa21 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp @@ -203,6 +203,6 @@ void ento::registerRunLoopAutoreleaseLeakChecker(CheckerManager &mgr) { mgr.registerChecker<RunLoopAutoreleaseLeakChecker>(); } -bool ento::shouldRegisterRunLoopAutoreleaseLeakChecker(const LangOptions &LO) { +bool ento::shouldRegisterRunLoopAutoreleaseLeakChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/STLAlgorithmModeling.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/STLAlgorithmModeling.cpp new file mode 100644 index 000000000000..933e0146ff59 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/STLAlgorithmModeling.cpp @@ -0,0 +1,180 @@ +//===-- STLAlgorithmModeling.cpp -----------------------------------*- C++ -*--// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Models STL algorithms. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +#include "Iterator.h" + +using namespace clang; +using namespace ento; +using namespace iterator; + +namespace { + +class STLAlgorithmModeling : public Checker<eval::Call> { + bool evalFind(CheckerContext &C, const CallExpr *CE) const; + + void Find(CheckerContext &C, const CallExpr *CE, unsigned paramNum) const; + + using FnCheck = bool (STLAlgorithmModeling::*)(CheckerContext &, + const CallExpr *) const; + + const CallDescriptionMap<FnCheck> Callbacks = { + {{{"std", "find"}, 3}, &STLAlgorithmModeling::evalFind}, + {{{"std", "find"}, 4}, &STLAlgorithmModeling::evalFind}, + {{{"std", "find_if"}, 3}, &STLAlgorithmModeling::evalFind}, + {{{"std", "find_if"}, 4}, &STLAlgorithmModeling::evalFind}, + {{{"std", "find_if_not"}, 3}, &STLAlgorithmModeling::evalFind}, + {{{"std", "find_if_not"}, 4}, &STLAlgorithmModeling::evalFind}, + {{{"std", "find_first_of"}, 4}, &STLAlgorithmModeling::evalFind}, + {{{"std", "find_first_of"}, 5}, &STLAlgorithmModeling::evalFind}, + {{{"std", "find_first_of"}, 6}, &STLAlgorithmModeling::evalFind}, + {{{"std", "find_end"}, 4}, &STLAlgorithmModeling::evalFind}, + {{{"std", "find_end"}, 5}, &STLAlgorithmModeling::evalFind}, + {{{"std", "find_end"}, 6}, &STLAlgorithmModeling::evalFind}, + {{{"std", "lower_bound"}, 3}, &STLAlgorithmModeling::evalFind}, + {{{"std", "lower_bound"}, 4}, &STLAlgorithmModeling::evalFind}, + {{{"std", "upper_bound"}, 3}, &STLAlgorithmModeling::evalFind}, + {{{"std", "upper_bound"}, 4}, &STLAlgorithmModeling::evalFind}, + {{{"std", "search"}, 3}, &STLAlgorithmModeling::evalFind}, + {{{"std", "search"}, 4}, &STLAlgorithmModeling::evalFind}, + {{{"std", "search"}, 5}, &STLAlgorithmModeling::evalFind}, + {{{"std", "search"}, 6}, &STLAlgorithmModeling::evalFind}, + {{{"std", "search_n"}, 4}, &STLAlgorithmModeling::evalFind}, + {{{"std", "search_n"}, 5}, &STLAlgorithmModeling::evalFind}, + {{{"std", "search_n"}, 6}, &STLAlgorithmModeling::evalFind}, + }; + +public: + STLAlgorithmModeling() = default; + + bool AggressiveStdFindModeling; + + bool evalCall(const CallEvent &Call, CheckerContext &C) const; +}; // + +bool STLAlgorithmModeling::evalCall(const CallEvent &Call, + CheckerContext &C) const { + const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return false; + + const FnCheck *Handler = Callbacks.lookup(Call); + if (!Handler) + return false; + + return (this->**Handler)(C, CE); +} + +bool STLAlgorithmModeling::evalFind(CheckerContext &C, + const CallExpr *CE) const { + // std::find()-like functions either take their primary range in the first + // two parameters, or if the first parameter is "execution policy" then in + // the second and third. This means that the second parameter must always be + // an iterator. + if (!isIteratorType(CE->getArg(1)->getType())) + return false; + + // If no "execution policy" parameter is used then the first argument is the + // beginning of the range. + if (isIteratorType(CE->getArg(0)->getType())) { + Find(C, CE, 0); + return true; + } + + // If "execution policy" parameter is used then the second argument is the + // beginning of the range. + if (isIteratorType(CE->getArg(2)->getType())) { + Find(C, CE, 1); + return true; + } + + return false; +} + +void STLAlgorithmModeling::Find(CheckerContext &C, const CallExpr *CE, + unsigned paramNum) const { + auto State = C.getState(); + auto &SVB = C.getSValBuilder(); + const auto *LCtx = C.getLocationContext(); + + SVal RetVal = SVB.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()); + SVal Param = State->getSVal(CE->getArg(paramNum), LCtx); + + auto StateFound = State->BindExpr(CE, LCtx, RetVal); + + // If we have an iterator position for the range-begin argument then we can + // assume that in case of successful search the position of the found element + // is not ahead of it. + // FIXME: Reverse iterators + const auto *Pos = getIteratorPosition(State, Param); + if (Pos) { + StateFound = createIteratorPosition(StateFound, RetVal, Pos->getContainer(), + CE, LCtx, C.blockCount()); + const auto *NewPos = getIteratorPosition(StateFound, RetVal); + assert(NewPos && "Failed to create new iterator position."); + + SVal GreaterOrEqual = SVB.evalBinOp(StateFound, BO_GE, + nonloc::SymbolVal(NewPos->getOffset()), + nonloc::SymbolVal(Pos->getOffset()), + SVB.getConditionType()); + assert(GreaterOrEqual.getAs<DefinedSVal>() && + "Symbol comparison must be a `DefinedSVal`"); + StateFound = StateFound->assume(GreaterOrEqual.castAs<DefinedSVal>(), true); + } + + Param = State->getSVal(CE->getArg(paramNum + 1), LCtx); + + // If we have an iterator position for the range-end argument then we can + // assume that in case of successful search the position of the found element + // is ahead of it. + // FIXME: Reverse iterators + Pos = getIteratorPosition(State, Param); + if (Pos) { + StateFound = createIteratorPosition(StateFound, RetVal, Pos->getContainer(), + CE, LCtx, C.blockCount()); + const auto *NewPos = getIteratorPosition(StateFound, RetVal); + assert(NewPos && "Failed to create new iterator position."); + + SVal Less = SVB.evalBinOp(StateFound, BO_LT, + nonloc::SymbolVal(NewPos->getOffset()), + nonloc::SymbolVal(Pos->getOffset()), + SVB.getConditionType()); + assert(Less.getAs<DefinedSVal>() && + "Symbol comparison must be a `DefinedSVal`"); + StateFound = StateFound->assume(Less.castAs<DefinedSVal>(), true); + } + + C.addTransition(StateFound); + + if (AggressiveStdFindModeling) { + auto StateNotFound = State->BindExpr(CE, LCtx, Param); + C.addTransition(StateNotFound); + } +} + +} // namespace + +void ento::registerSTLAlgorithmModeling(CheckerManager &Mgr) { + auto *Checker = Mgr.registerChecker<STLAlgorithmModeling>(); + Checker->AggressiveStdFindModeling = + Mgr.getAnalyzerOptions().getCheckerBooleanOption(Checker, + "AggressiveStdFindModeling"); +} + +bool ento::shouldRegisterSTLAlgorithmModeling(const CheckerManager &mgr) { + return true; +} + diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp index 8193bcbef4cd..8d380ed1b93d 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp @@ -271,6 +271,6 @@ void ento::registerSimpleStreamChecker(CheckerManager &mgr) { } // This checker should be enabled regardless of how language options are set. -bool ento::shouldRegisterSimpleStreamChecker(const LangOptions &LO) { +bool ento::shouldRegisterSimpleStreamChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtr.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtr.h new file mode 100644 index 000000000000..ec43a23e30a9 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtr.h @@ -0,0 +1,33 @@ +//=== SmartPtr.h - Tracking smart pointer state. -------------------*- C++ -*-// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines inter-checker API for the smart pointer modeling. It allows +// dependent checkers to figure out if an smart pointer is null or not. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_SMARTPTR_H +#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_SMARTPTR_H + +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" + +namespace clang { +namespace ento { +namespace smartptr { + +/// Returns true if the event call is on smart pointer. +bool isStdSmartPtrCall(const CallEvent &Call); + +/// Returns whether the smart pointer is null or not. +bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion); + +} // namespace smartptr +} // namespace ento +} // namespace clang + +#endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_SMARTPTR_H diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtrChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtrChecker.cpp new file mode 100644 index 000000000000..7bb25f397d01 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtrChecker.cpp @@ -0,0 +1,80 @@ +// SmartPtrChecker.cpp - Check for smart pointer dereference - C++ --------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines a checker that check for null dereference of C++ smart +// pointer. +// +//===----------------------------------------------------------------------===// +#include "SmartPtr.h" + +#include "clang/AST/DeclCXX.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/Type.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" + +using namespace clang; +using namespace ento; + +namespace { +class SmartPtrChecker : public Checker<check::PreCall> { + BugType NullDereferenceBugType{this, "Null SmartPtr dereference", + "C++ Smart Pointer"}; + +public: + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + +private: + void reportBug(CheckerContext &C, const CallEvent &Call) const; +}; +} // end of anonymous namespace + +void SmartPtrChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + if (!smartptr::isStdSmartPtrCall(Call)) + return; + ProgramStateRef State = C.getState(); + const auto *OC = dyn_cast<CXXMemberOperatorCall>(&Call); + if (!OC) + return; + const MemRegion *ThisRegion = OC->getCXXThisVal().getAsRegion(); + if (!ThisRegion) + return; + + OverloadedOperatorKind OOK = OC->getOverloadedOperator(); + if (OOK == OO_Star || OOK == OO_Arrow) { + if (smartptr::isNullSmartPtr(State, ThisRegion)) + reportBug(C, Call); + } +} + +void SmartPtrChecker::reportBug(CheckerContext &C, + const CallEvent &Call) const { + ExplodedNode *ErrNode = C.generateErrorNode(); + if (!ErrNode) + return; + + auto R = std::make_unique<PathSensitiveBugReport>( + NullDereferenceBugType, "Dereference of null smart pointer", ErrNode); + C.emitReport(std::move(R)); +} + +void ento::registerSmartPtrChecker(CheckerManager &Mgr) { + Mgr.registerChecker<SmartPtrChecker>(); +} + +bool ento::shouldRegisterSmartPtrChecker(const CheckerManager &mgr) { + const LangOptions &LO = mgr.getLangOpts(); + return LO.CPlusPlus; +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp index fd372aafa50d..bcc7d4103c1c 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp @@ -12,27 +12,81 @@ //===----------------------------------------------------------------------===// #include "Move.h" +#include "SmartPtr.h" +#include "clang/AST/DeclCXX.h" #include "clang/AST/ExprCXX.h" +#include "clang/AST/Type.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" using namespace clang; using namespace ento; namespace { -class SmartPtrModeling : public Checker<eval::Call> { +class SmartPtrModeling : public Checker<eval::Call, check::DeadSymbols> { + bool isNullAfterMoveMethod(const CallEvent &Call) const; public: + // Whether the checker should model for null dereferences of smart pointers. + DefaultBool ModelSmartPtrDereference; bool evalCall(const CallEvent &Call, CheckerContext &C) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + +private: + ProgramStateRef updateTrackedRegion(const CallEvent &Call, CheckerContext &C, + const MemRegion *ThisValRegion) const; + void handleReset(const CallEvent &Call, CheckerContext &C) const; + void handleRelease(const CallEvent &Call, CheckerContext &C) const; + void handleSwap(const CallEvent &Call, CheckerContext &C) const; + + using SmartPtrMethodHandlerFn = + void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const; + CallDescriptionMap<SmartPtrMethodHandlerFn> SmartPtrMethodHandlers{ + {{"reset"}, &SmartPtrModeling::handleReset}, + {{"release"}, &SmartPtrModeling::handleRelease}, + {{"swap", 1}, &SmartPtrModeling::handleSwap}}; }; } // end of anonymous namespace +REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal) + +// Define the inter-checker API. +namespace clang { +namespace ento { +namespace smartptr { +bool isStdSmartPtrCall(const CallEvent &Call) { + const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl()); + if (!MethodDecl || !MethodDecl->getParent()) + return false; + + const auto *RecordDecl = MethodDecl->getParent(); + if (!RecordDecl || !RecordDecl->getDeclContext()->isStdNamespace()) + return false; + + if (RecordDecl->getDeclName().isIdentifier()) { + StringRef Name = RecordDecl->getName(); + return Name == "shared_ptr" || Name == "unique_ptr" || Name == "weak_ptr"; + } + return false; +} + +bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion) { + const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion); + return InnerPointVal && InnerPointVal->isZeroConstant(); +} +} // namespace smartptr +} // namespace ento +} // namespace clang + bool SmartPtrModeling::isNullAfterMoveMethod(const CallEvent &Call) const { // TODO: Update CallDescription to support anonymous calls? // TODO: Handle other methods, such as .get() or .release(). @@ -44,29 +98,136 @@ bool SmartPtrModeling::isNullAfterMoveMethod(const CallEvent &Call) const { bool SmartPtrModeling::evalCall(const CallEvent &Call, CheckerContext &C) const { - if (!isNullAfterMoveMethod(Call)) + + if (!smartptr::isStdSmartPtrCall(Call)) return false; - ProgramStateRef State = C.getState(); - const MemRegion *ThisR = - cast<CXXInstanceCall>(&Call)->getCXXThisVal().getAsRegion(); + if (isNullAfterMoveMethod(Call)) { + ProgramStateRef State = C.getState(); + const MemRegion *ThisR = + cast<CXXInstanceCall>(&Call)->getCXXThisVal().getAsRegion(); + + if (!move::isMovedFrom(State, ThisR)) { + // TODO: Model this case as well. At least, avoid invalidation of globals. + return false; + } + + // TODO: Add a note to bug reports describing this decision. + C.addTransition( + State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), + C.getSValBuilder().makeZeroVal(Call.getResultType()))); + return true; + } - if (!move::isMovedFrom(State, ThisR)) { - // TODO: Model this case as well. At least, avoid invalidation of globals. + if (!ModelSmartPtrDereference) return false; + + if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) { + if (CC->getDecl()->isCopyOrMoveConstructor()) + return false; + + const MemRegion *ThisValRegion = CC->getCXXThisVal().getAsRegion(); + if (!ThisValRegion) + return false; + + auto State = updateTrackedRegion(Call, C, ThisValRegion); + C.addTransition(State); + return true; + } + + const SmartPtrMethodHandlerFn *Handler = SmartPtrMethodHandlers.lookup(Call); + if (!Handler) + return false; + (this->**Handler)(Call, C); + + return C.isDifferent(); +} + +void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + // Clean up dead regions from the region map. + TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>(); + for (auto E : TrackedRegions) { + const MemRegion *Region = E.first; + bool IsRegDead = !SymReaper.isLiveRegion(Region); + + if (IsRegDead) + State = State->remove<TrackedRegionMap>(Region); + } + C.addTransition(State); +} + +void SmartPtrModeling::handleReset(const CallEvent &Call, + CheckerContext &C) const { + const auto *IC = dyn_cast<CXXInstanceCall>(&Call); + if (!IC) + return; + + const MemRegion *ThisValRegion = IC->getCXXThisVal().getAsRegion(); + if (!ThisValRegion) + return; + auto State = updateTrackedRegion(Call, C, ThisValRegion); + C.addTransition(State); + // TODO: Make sure to ivalidate the the region in the Store if we don't have + // time to model all methods. +} + +void SmartPtrModeling::handleRelease(const CallEvent &Call, + CheckerContext &C) const { + const auto *IC = dyn_cast<CXXInstanceCall>(&Call); + if (!IC) + return; + + const MemRegion *ThisValRegion = IC->getCXXThisVal().getAsRegion(); + if (!ThisValRegion) + return; + + auto State = updateTrackedRegion(Call, C, ThisValRegion); + + const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisValRegion); + if (InnerPointVal) { + State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), + *InnerPointVal); + } + C.addTransition(State); + // TODO: Add support to enable MallocChecker to start tracking the raw + // pointer. +} + +void SmartPtrModeling::handleSwap(const CallEvent &Call, + CheckerContext &C) const { + // TODO: Add support to handle swap method. +} + +ProgramStateRef +SmartPtrModeling::updateTrackedRegion(const CallEvent &Call, CheckerContext &C, + const MemRegion *ThisValRegion) const { + // TODO: Refactor and clean up handling too many things. + ProgramStateRef State = C.getState(); + auto NumArgs = Call.getNumArgs(); + + if (NumArgs == 0) { + auto NullSVal = C.getSValBuilder().makeNull(); + State = State->set<TrackedRegionMap>(ThisValRegion, NullSVal); + } else if (NumArgs == 1) { + auto ArgVal = Call.getArgSVal(0); + assert(Call.getArgExpr(0)->getType()->isPointerType() && + "Adding a non pointer value to TrackedRegionMap"); + State = State->set<TrackedRegionMap>(ThisValRegion, ArgVal); } - // TODO: Add a note to bug reports describing this decision. - C.addTransition( - State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), - C.getSValBuilder().makeZeroVal(Call.getResultType()))); - return true; + return State; } void ento::registerSmartPtrModeling(CheckerManager &Mgr) { - Mgr.registerChecker<SmartPtrModeling>(); + auto *Checker = Mgr.registerChecker<SmartPtrModeling>(); + Checker->ModelSmartPtrDereference = + Mgr.getAnalyzerOptions().getCheckerBooleanOption( + Checker, "ModelSmartPtrDereference"); } -bool ento::shouldRegisterSmartPtrModeling(const LangOptions &LO) { +bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) { + const LangOptions &LO = mgr.getLangOpts(); return LO.CPlusPlus; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp index 7285d27495a7..b5c9356322fc 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp @@ -43,6 +43,7 @@ public: }; DefaultBool ChecksEnabled[CK_NumCheckKinds]; + CheckerNameRef CheckNames[CK_NumCheckKinds]; void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const; @@ -156,7 +157,8 @@ void StackAddrEscapeChecker::EmitStackError(CheckerContext &C, return; if (!BT_returnstack) BT_returnstack = std::make_unique<BuiltinBug>( - this, "Return of address to stack-allocated memory"); + CheckNames[CK_StackAddrEscapeChecker], + "Return of address to stack-allocated memory"); // Generate a report for this bug. SmallString<128> buf; llvm::raw_svector_ostream os(buf); @@ -195,7 +197,8 @@ void StackAddrEscapeChecker::checkAsyncExecutedBlockCaptures( continue; if (!BT_capturedstackasync) BT_capturedstackasync = std::make_unique<BuiltinBug>( - this, "Address of stack-allocated memory is captured"); + CheckNames[CK_StackAddrAsyncEscapeChecker], + "Address of stack-allocated memory is captured"); SmallString<128> Buf; llvm::raw_svector_ostream Out(Buf); SourceRange Range = genName(Out, Region, C.getASTContext()); @@ -218,7 +221,8 @@ void StackAddrEscapeChecker::checkReturnedBlockCaptures( continue; if (!BT_capturedstackret) BT_capturedstackret = std::make_unique<BuiltinBug>( - this, "Address of stack-allocated memory is captured"); + CheckNames[CK_StackAddrEscapeChecker], + "Address of stack-allocated memory is captured"); SmallString<128> Buf; llvm::raw_svector_ostream Out(Buf); SourceRange Range = genName(Out, Region, C.getASTContext()); @@ -277,7 +281,7 @@ void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS, // The CK_CopyAndAutoreleaseBlockObject cast causes the block to be copied // so the stack address is not escaping here. - if (auto *ICE = dyn_cast<ImplicitCastExpr>(RetE)) { + if (const auto *ICE = dyn_cast<ImplicitCastExpr>(RetE)) { if (isa<BlockDataRegion>(R) && ICE->getCastKind() == CK_CopyAndAutoreleaseBlockObject) { return; @@ -333,7 +337,8 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, if (!BT_stackleak) BT_stackleak = std::make_unique<BuiltinBug>( - this, "Stack address stored into global variable", + CheckNames[CK_StackAddrEscapeChecker], + "Stack address stored into global variable", "Stack address was saved into a global variable. " "This is dangerous because the address will become " "invalid after returning from the function"); @@ -365,20 +370,19 @@ void ento::registerStackAddrEscapeBase(CheckerManager &mgr) { mgr.registerChecker<StackAddrEscapeChecker>(); } -bool ento::shouldRegisterStackAddrEscapeBase(const LangOptions &LO) { +bool ento::shouldRegisterStackAddrEscapeBase(const CheckerManager &mgr) { return true; } #define REGISTER_CHECKER(name) \ void ento::register##name(CheckerManager &Mgr) { \ - StackAddrEscapeChecker *Chk = \ - Mgr.getChecker<StackAddrEscapeChecker>(); \ + StackAddrEscapeChecker *Chk = Mgr.getChecker<StackAddrEscapeChecker>(); \ Chk->ChecksEnabled[StackAddrEscapeChecker::CK_##name] = true; \ + Chk->CheckNames[StackAddrEscapeChecker::CK_##name] = \ + Mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name(const LangOptions &LO) { \ - return true; \ - } + bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; } REGISTER_CHECKER(StackAddrEscapeChecker) REGISTER_CHECKER(StackAddrAsyncEscapeChecker) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp index 2cdee8da375e..8b575f4f4759 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp @@ -7,9 +7,8 @@ //===----------------------------------------------------------------------===// // // This checker improves modeling of a few simple library functions. -// It does not generate warnings. // -// This checker provides a specification format - `FunctionSummaryTy' - and +// This checker provides a specification format - `Summary' - and // contains descriptions of some library functions in this format. Each // specification contains a list of branches for splitting the program state // upon call, and range constraints on argument and return-value symbols that @@ -21,7 +20,7 @@ // consider standard C function `ispunct(int x)', which returns a non-zero value // iff `x' is a punctuation character, that is, when `x' is in range // ['!', '/'] [':', '@'] U ['[', '\`'] U ['{', '~']. -// `FunctionSummaryTy' provides only two branches for this function. However, +// `Summary' provides only two branches for this function. However, // any attempt to describe this range with if-statements in the body farm // would result in many more branches. Because each branch needs to be analyzed // independently, this significantly reduces performance. Additionally, @@ -30,13 +29,13 @@ // which may lead to false positives because considering this particular path // was not consciously intended, and therefore it might have been unreachable. // -// This checker uses eval::Call for modeling "pure" functions, for which -// their `FunctionSummaryTy' is a precise model. This avoids unnecessary -// invalidation passes. Conflicts with other checkers are unlikely because -// if the function has no other effects, other checkers would probably never -// want to improve upon the modeling done by this checker. +// This checker uses eval::Call for modeling pure functions (functions without +// side effets), for which their `Summary' is a precise model. This avoids +// unnecessary invalidation passes. Conflicts with other checkers are unlikely +// because if the function has no other effects, other checkers would probably +// never want to improve upon the modeling done by this checker. // -// Non-"pure" functions, for which only partial improvement over the default +// Non-pure functions, for which only partial improvement over the default // behavior is expected, are modeled via check::PostCall, non-intrusively. // // The following standard C functions are currently supported: @@ -51,203 +50,461 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/CheckerHelpers.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" using namespace clang; using namespace clang::ento; namespace { -class StdLibraryFunctionsChecker : public Checker<check::PostCall, eval::Call> { - /// Below is a series of typedefs necessary to define function specs. - /// We avoid nesting types here because each additional qualifier - /// would need to be repeated in every function spec. - struct FunctionSummaryTy; +class StdLibraryFunctionsChecker + : public Checker<check::PreCall, check::PostCall, eval::Call> { + + class Summary; /// Specify how much the analyzer engine should entrust modeling this function /// to us. If he doesn't, he performs additional invalidations. - enum InvalidationKindTy { NoEvalCall, EvalCallAsPure }; - - /// A pair of ValueRangeKindTy and IntRangeVectorTy would describe a range - /// imposed on a particular argument or return value symbol. - /// - /// Given a range, should the argument stay inside or outside this range? - /// The special `ComparesToArgument' value indicates that we should - /// impose a constraint that involves other argument or return value symbols. - enum ValueRangeKindTy { OutOfRange, WithinRange, ComparesToArgument }; + enum InvalidationKind { NoEvalCall, EvalCallAsPure }; // The universal integral type to use in value range descriptions. // Unsigned to make sure overflows are well-defined. - typedef uint64_t RangeIntTy; + typedef uint64_t RangeInt; /// Normally, describes a single range constraint, eg. {{0, 1}, {3, 4}} is /// a non-negative integer, which less than 5 and not equal to 2. For /// `ComparesToArgument', holds information about how exactly to compare to /// the argument. - typedef std::vector<std::pair<RangeIntTy, RangeIntTy>> IntRangeVectorTy; + typedef std::vector<std::pair<RangeInt, RangeInt>> IntRangeVector; /// A reference to an argument or return value by its number. /// ArgNo in CallExpr and CallEvent is defined as Unsigned, but /// obviously uint32_t should be enough for all practical purposes. - typedef uint32_t ArgNoTy; - static const ArgNoTy Ret = std::numeric_limits<ArgNoTy>::max(); - - /// Incapsulates a single range on a single symbol within a branch. - class ValueRange { - ArgNoTy ArgNo; // Argument to which we apply the range. - ValueRangeKindTy Kind; // Kind of range definition. - IntRangeVectorTy Args; // Polymorphic arguments. - + typedef uint32_t ArgNo; + static const ArgNo Ret; + + class ValueConstraint; + + // Pointer to the ValueConstraint. We need a copyable, polymorphic and + // default initialize able type (vector needs that). A raw pointer was good, + // however, we cannot default initialize that. unique_ptr makes the Summary + // class non-copyable, therefore not an option. Releasing the copyability + // requirement would render the initialization of the Summary map infeasible. + using ValueConstraintPtr = std::shared_ptr<ValueConstraint>; + + /// Polymorphic base class that represents a constraint on a given argument + /// (or return value) of a function. Derived classes implement different kind + /// of constraints, e.g range constraints or correlation between two + /// arguments. + class ValueConstraint { public: - ValueRange(ArgNoTy ArgNo, ValueRangeKindTy Kind, - const IntRangeVectorTy &Args) - : ArgNo(ArgNo), Kind(Kind), Args(Args) {} - - ArgNoTy getArgNo() const { return ArgNo; } - ValueRangeKindTy getKind() const { return Kind; } - - BinaryOperator::Opcode getOpcode() const { - assert(Kind == ComparesToArgument); - assert(Args.size() == 1); - BinaryOperator::Opcode Op = - static_cast<BinaryOperator::Opcode>(Args[0].first); - assert(BinaryOperator::isComparisonOp(Op) && - "Only comparison ops are supported for ComparesToArgument"); - return Op; + ValueConstraint(ArgNo ArgN) : ArgN(ArgN) {} + virtual ~ValueConstraint() {} + /// Apply the effects of the constraint on the given program state. If null + /// is returned then the constraint is not feasible. + virtual ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const = 0; + virtual ValueConstraintPtr negate() const { + llvm_unreachable("Not implemented"); + }; + + // Check whether the constraint is malformed or not. It is malformed if the + // specified argument has a mismatch with the given FunctionDecl (e.g. the + // arg number is out-of-range of the function's argument list). + bool checkValidity(const FunctionDecl *FD) const { + const bool ValidArg = ArgN == Ret || ArgN < FD->getNumParams(); + assert(ValidArg && "Arg out of range!"); + if (!ValidArg) + return false; + // Subclasses may further refine the validation. + return checkSpecificValidity(FD); } + ArgNo getArgNo() const { return ArgN; } - ArgNoTy getOtherArgNo() const { - assert(Kind == ComparesToArgument); - assert(Args.size() == 1); - return static_cast<ArgNoTy>(Args[0].second); + protected: + ArgNo ArgN; // Argument to which we apply the constraint. + + /// Do polymorphic sanity check on the constraint. + virtual bool checkSpecificValidity(const FunctionDecl *FD) const { + return true; } + }; + + /// Given a range, should the argument stay inside or outside this range? + enum RangeKind { OutOfRange, WithinRange }; + + /// Encapsulates a single range on a single symbol within a branch. + class RangeConstraint : public ValueConstraint { + RangeKind Kind; // Kind of range definition. + IntRangeVector Args; // Polymorphic arguments. - const IntRangeVectorTy &getRanges() const { - assert(Kind != ComparesToArgument); + public: + RangeConstraint(ArgNo ArgN, RangeKind Kind, const IntRangeVector &Args) + : ValueConstraint(ArgN), Kind(Kind), Args(Args) {} + + const IntRangeVector &getRanges() const { return Args; } - // We avoid creating a virtual apply() method because - // it makes initializer lists harder to write. private: - ProgramStateRef - applyAsOutOfRange(ProgramStateRef State, const CallEvent &Call, - const FunctionSummaryTy &Summary) const; - ProgramStateRef - applyAsWithinRange(ProgramStateRef State, const CallEvent &Call, - const FunctionSummaryTy &Summary) const; - ProgramStateRef - applyAsComparesToArgument(ProgramStateRef State, const CallEvent &Call, - const FunctionSummaryTy &Summary) const; - + ProgramStateRef applyAsOutOfRange(ProgramStateRef State, + const CallEvent &Call, + const Summary &Summary) const; + ProgramStateRef applyAsWithinRange(ProgramStateRef State, + const CallEvent &Call, + const Summary &Summary) const; public: ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, - const FunctionSummaryTy &Summary) const { + const Summary &Summary, + CheckerContext &C) const override { switch (Kind) { case OutOfRange: return applyAsOutOfRange(State, Call, Summary); case WithinRange: return applyAsWithinRange(State, Call, Summary); - case ComparesToArgument: - return applyAsComparesToArgument(State, Call, Summary); } - llvm_unreachable("Unknown ValueRange kind!"); + llvm_unreachable("Unknown range kind!"); + } + + ValueConstraintPtr negate() const override { + RangeConstraint Tmp(*this); + switch (Kind) { + case OutOfRange: + Tmp.Kind = WithinRange; + break; + case WithinRange: + Tmp.Kind = OutOfRange; + break; + } + return std::make_shared<RangeConstraint>(Tmp); + } + + bool checkSpecificValidity(const FunctionDecl *FD) const override { + const bool ValidArg = + getArgType(FD, ArgN)->isIntegralType(FD->getASTContext()); + assert(ValidArg && + "This constraint should be applied on an integral type"); + return ValidArg; } }; - /// The complete list of ranges that defines a single branch. - typedef std::vector<ValueRange> ValueRangeSet; + class ComparisonConstraint : public ValueConstraint { + BinaryOperator::Opcode Opcode; + ArgNo OtherArgN; + + public: + ComparisonConstraint(ArgNo ArgN, BinaryOperator::Opcode Opcode, + ArgNo OtherArgN) + : ValueConstraint(ArgN), Opcode(Opcode), OtherArgN(OtherArgN) {} + ArgNo getOtherArgNo() const { return OtherArgN; } + BinaryOperator::Opcode getOpcode() const { return Opcode; } + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override; + }; + + class NotNullConstraint : public ValueConstraint { + using ValueConstraint::ValueConstraint; + // This variable has a role when we negate the constraint. + bool CannotBeNull = true; - /// Includes information about function prototype (which is necessary to - /// ensure we're modeling the right function and casting values properly), - /// approach to invalidation, and a list of branches - essentially, a list - /// of list of ranges - essentially, a list of lists of lists of segments. - struct FunctionSummaryTy { - const std::vector<QualType> ArgTypes; - const QualType RetType; - const InvalidationKindTy InvalidationKind; - const std::vector<ValueRangeSet> Ranges; + public: + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override { + SVal V = getArgSVal(Call, getArgNo()); + if (V.isUndef()) + return State; + + DefinedOrUnknownSVal L = V.castAs<DefinedOrUnknownSVal>(); + if (!L.getAs<Loc>()) + return State; + + return State->assume(L, CannotBeNull); + } + + ValueConstraintPtr negate() const override { + NotNullConstraint Tmp(*this); + Tmp.CannotBeNull = !this->CannotBeNull; + return std::make_shared<NotNullConstraint>(Tmp); + } + + bool checkSpecificValidity(const FunctionDecl *FD) const override { + const bool ValidArg = getArgType(FD, ArgN)->isPointerType(); + assert(ValidArg && + "This constraint should be applied only on a pointer type"); + return ValidArg; + } + }; + + // Represents a buffer argument with an additional size argument. + // E.g. the first two arguments here: + // ctime_s(char *buffer, rsize_t bufsz, const time_t *time); + // Another example: + // size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); + // // Here, ptr is the buffer, and its minimum size is `size * nmemb`. + class BufferSizeConstraint : public ValueConstraint { + // The argument which holds the size of the buffer. + ArgNo SizeArgN; + // The argument which is a multiplier to size. This is set in case of + // `fread` like functions where the size is computed as a multiplication of + // two arguments. + llvm::Optional<ArgNo> SizeMultiplierArgN; + // The operator we use in apply. This is negated in negate(). + BinaryOperator::Opcode Op = BO_LE; + + public: + BufferSizeConstraint(ArgNo Buffer, ArgNo BufSize) + : ValueConstraint(Buffer), SizeArgN(BufSize) {} + + BufferSizeConstraint(ArgNo Buffer, ArgNo BufSize, ArgNo BufSizeMultiplier) + : ValueConstraint(Buffer), SizeArgN(BufSize), + SizeMultiplierArgN(BufSizeMultiplier) {} + + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override { + SValBuilder &SvalBuilder = C.getSValBuilder(); + // The buffer argument. + SVal BufV = getArgSVal(Call, getArgNo()); + // The size argument. + SVal SizeV = getArgSVal(Call, SizeArgN); + // Multiply with another argument if given. + if (SizeMultiplierArgN) { + SVal SizeMulV = getArgSVal(Call, *SizeMultiplierArgN); + SizeV = SvalBuilder.evalBinOp(State, BO_Mul, SizeV, SizeMulV, + Summary.getArgType(SizeArgN)); + } + // The dynamic size of the buffer argument, got from the analyzer engine. + SVal BufDynSize = getDynamicSizeWithOffset(State, BufV); + + SVal Feasible = SvalBuilder.evalBinOp(State, Op, SizeV, BufDynSize, + SvalBuilder.getContext().BoolTy); + if (auto F = Feasible.getAs<DefinedOrUnknownSVal>()) + return State->assume(*F, true); + + // We can get here only if the size argument or the dynamic size is + // undefined. But the dynamic size should never be undefined, only + // unknown. So, here, the size of the argument is undefined, i.e. we + // cannot apply the constraint. Actually, other checkers like + // CallAndMessage should catch this situation earlier, because we call a + // function with an uninitialized argument. + llvm_unreachable("Size argument or the dynamic size is Undefined"); + } + + ValueConstraintPtr negate() const override { + BufferSizeConstraint Tmp(*this); + Tmp.Op = BinaryOperator::negateComparisonOp(Op); + return std::make_shared<BufferSizeConstraint>(Tmp); + } + }; + + /// The complete list of constraints that defines a single branch. + typedef std::vector<ValueConstraintPtr> ConstraintSet; + + using ArgTypes = std::vector<QualType>; + + // A placeholder type, we use it whenever we do not care about the concrete + // type in a Signature. + const QualType Irrelevant{}; + bool static isIrrelevant(QualType T) { return T.isNull(); } + + // The signature of a function we want to describe with a summary. This is a + // concessive signature, meaning there may be irrelevant types in the + // signature which we do not check against a function with concrete types. + struct Signature { + const ArgTypes ArgTys; + const QualType RetTy; + Signature(ArgTypes ArgTys, QualType RetTy) : ArgTys(ArgTys), RetTy(RetTy) { + assertRetTypeSuitableForSignature(RetTy); + for (size_t I = 0, E = ArgTys.size(); I != E; ++I) { + QualType ArgTy = ArgTys[I]; + assertArgTypeSuitableForSignature(ArgTy); + } + } + bool matches(const FunctionDecl *FD) const; private: - static void assertTypeSuitableForSummary(QualType T) { - assert(!T->isVoidType() && - "We should have had no significant void types in the spec"); - assert(T.isCanonical() && + static void assertArgTypeSuitableForSignature(QualType T) { + assert((T.isNull() || !T->isVoidType()) && + "We should have no void types in the spec"); + assert((T.isNull() || T.isCanonical()) && + "We should only have canonical types in the spec"); + } + static void assertRetTypeSuitableForSignature(QualType T) { + assert((T.isNull() || T.isCanonical()) && "We should only have canonical types in the spec"); - // FIXME: lift this assert (but not the ones above!) - assert(T->isIntegralOrEnumerationType() && - "We only support integral ranges in the spec"); } + }; + + static QualType getArgType(const FunctionDecl *FD, ArgNo ArgN) { + assert(FD && "Function must be set"); + QualType T = (ArgN == Ret) + ? FD->getReturnType().getCanonicalType() + : FD->getParamDecl(ArgN)->getType().getCanonicalType(); + return T; + } + + using Cases = std::vector<ConstraintSet>; + + /// A summary includes information about + /// * function prototype (signature) + /// * approach to invalidation, + /// * a list of branches - a list of list of ranges - + /// A branch represents a path in the exploded graph of a function (which + /// is a tree). So, a branch is a series of assumptions. In other words, + /// branches represent split states and additional assumptions on top of + /// the splitting assumption. + /// For example, consider the branches in `isalpha(x)` + /// Branch 1) + /// x is in range ['A', 'Z'] or in ['a', 'z'] + /// then the return value is not 0. (I.e. out-of-range [0, 0]) + /// Branch 2) + /// x is out-of-range ['A', 'Z'] and out-of-range ['a', 'z'] + /// then the return value is 0. + /// * a list of argument constraints, that must be true on every branch. + /// If these constraints are not satisfied that means a fatal error + /// usually resulting in undefined behaviour. + /// + /// Application of a summary: + /// The signature and argument constraints together contain information + /// about which functions are handled by the summary. The signature can use + /// "wildcards", i.e. Irrelevant types. Irrelevant type of a parameter in + /// a signature means that type is not compared to the type of the parameter + /// in the found FunctionDecl. Argument constraints may specify additional + /// rules for the given parameter's type, those rules are checked once the + /// signature is matched. + class Summary { + const Signature Sign; + const InvalidationKind InvalidationKd; + Cases CaseConstraints; + ConstraintSet ArgConstraints; + + // The function to which the summary applies. This is set after lookup and + // match to the signature. + const FunctionDecl *FD = nullptr; public: - QualType getArgType(ArgNoTy ArgNo) const { - QualType T = (ArgNo == Ret) ? RetType : ArgTypes[ArgNo]; - assertTypeSuitableForSummary(T); - return T; + Summary(ArgTypes ArgTys, QualType RetTy, InvalidationKind InvalidationKd) + : Sign(ArgTys, RetTy), InvalidationKd(InvalidationKd) {} + + Summary &Case(ConstraintSet&& CS) { + CaseConstraints.push_back(std::move(CS)); + return *this; + } + Summary &ArgConstraint(ValueConstraintPtr VC) { + ArgConstraints.push_back(VC); + return *this; } - /// Try our best to figure out if the call expression is the call of - /// *the* library function to which this specification applies. - bool matchesCall(const CallExpr *CE) const; - }; + InvalidationKind getInvalidationKd() const { return InvalidationKd; } + const Cases &getCaseConstraints() const { return CaseConstraints; } + const ConstraintSet &getArgConstraints() const { return ArgConstraints; } - // The same function (as in, function identifier) may have different - // summaries assigned to it, with different argument and return value types. - // We call these "variants" of the function. This can be useful for handling - // C++ function overloads, and also it can be used when the same function - // may have different definitions on different platforms. - typedef std::vector<FunctionSummaryTy> FunctionVariantsTy; + QualType getArgType(ArgNo ArgN) const { + return StdLibraryFunctionsChecker::getArgType(FD, ArgN); + } + + // Returns true if the summary should be applied to the given function. + // And if yes then store the function declaration. + bool matchesAndSet(const FunctionDecl *FD) { + bool Result = Sign.matches(FD) && validateByConstraints(FD); + if (Result) { + assert(!this->FD && "FD must not be set more than once"); + this->FD = FD; + } + return Result; + } + + private: + // Once we know the exact type of the function then do sanity check on all + // the given constraints. + bool validateByConstraints(const FunctionDecl *FD) const { + for (const ConstraintSet &Case : CaseConstraints) + for (const ValueConstraintPtr &Constraint : Case) + if (!Constraint->checkValidity(FD)) + return false; + for (const ValueConstraintPtr &Constraint : ArgConstraints) + if (!Constraint->checkValidity(FD)) + return false; + return true; + } + }; // The map of all functions supported by the checker. It is initialized // lazily, and it doesn't change after initialization. - typedef llvm::StringMap<FunctionVariantsTy> FunctionSummaryMapTy; - mutable FunctionSummaryMapTy FunctionSummaryMap; + using FunctionSummaryMapType = llvm::DenseMap<const FunctionDecl *, Summary>; + mutable FunctionSummaryMapType FunctionSummaryMap; - // Auxiliary functions to support ArgNoTy within all structures - // in a unified manner. - static QualType getArgType(const FunctionSummaryTy &Summary, ArgNoTy ArgNo) { - return Summary.getArgType(ArgNo); - } - static QualType getArgType(const CallEvent &Call, ArgNoTy ArgNo) { - return ArgNo == Ret ? Call.getResultType().getCanonicalType() - : Call.getArgExpr(ArgNo)->getType().getCanonicalType(); - } - static QualType getArgType(const CallExpr *CE, ArgNoTy ArgNo) { - return ArgNo == Ret ? CE->getType().getCanonicalType() - : CE->getArg(ArgNo)->getType().getCanonicalType(); - } - static SVal getArgSVal(const CallEvent &Call, ArgNoTy ArgNo) { - return ArgNo == Ret ? Call.getReturnValue() : Call.getArgSVal(ArgNo); + mutable std::unique_ptr<BugType> BT_InvalidArg; + + static SVal getArgSVal(const CallEvent &Call, ArgNo ArgN) { + return ArgN == Ret ? Call.getReturnValue() : Call.getArgSVal(ArgN); } public: + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; bool evalCall(const CallEvent &Call, CheckerContext &C) const; -private: - Optional<FunctionSummaryTy> findFunctionSummary(const FunctionDecl *FD, - const CallExpr *CE, - CheckerContext &C) const; + enum CheckKind { + CK_StdCLibraryFunctionArgsChecker, + CK_StdCLibraryFunctionsTesterChecker, + CK_NumCheckKinds + }; + DefaultBool ChecksEnabled[CK_NumCheckKinds]; + CheckerNameRef CheckNames[CK_NumCheckKinds]; + + bool DisplayLoadedSummaries = false; + bool ModelPOSIX = false; - void initFunctionSummaries(BasicValueFactory &BVF) const; +private: + Optional<Summary> findFunctionSummary(const FunctionDecl *FD, + CheckerContext &C) const; + Optional<Summary> findFunctionSummary(const CallEvent &Call, + CheckerContext &C) const; + + void initFunctionSummaries(CheckerContext &C) const; + + void reportBug(const CallEvent &Call, ExplodedNode *N, + CheckerContext &C) const { + if (!ChecksEnabled[CK_StdCLibraryFunctionArgsChecker]) + return; + // TODO Add detailed diagnostic. + StringRef Msg = "Function argument constraint is not satisfied"; + if (!BT_InvalidArg) + BT_InvalidArg = std::make_unique<BugType>( + CheckNames[CK_StdCLibraryFunctionArgsChecker], + "Unsatisfied argument constraints", categories::LogicError); + auto R = std::make_unique<PathSensitiveBugReport>(*BT_InvalidArg, Msg, N); + bugreporter::trackExpressionValue(N, Call.getArgExpr(0), *R); + C.emitReport(std::move(R)); + } }; + +const StdLibraryFunctionsChecker::ArgNo StdLibraryFunctionsChecker::Ret = + std::numeric_limits<ArgNo>::max(); + } // end of anonymous namespace -ProgramStateRef StdLibraryFunctionsChecker::ValueRange::applyAsOutOfRange( +ProgramStateRef StdLibraryFunctionsChecker::RangeConstraint::applyAsOutOfRange( ProgramStateRef State, const CallEvent &Call, - const FunctionSummaryTy &Summary) const { + const Summary &Summary) const { ProgramStateManager &Mgr = State->getStateManager(); SValBuilder &SVB = Mgr.getSValBuilder(); BasicValueFactory &BVF = SVB.getBasicValueFactory(); ConstraintManager &CM = Mgr.getConstraintManager(); - QualType T = getArgType(Summary, getArgNo()); + QualType T = Summary.getArgType(getArgNo()); SVal V = getArgSVal(Call, getArgNo()); if (auto N = V.getAs<NonLoc>()) { - const IntRangeVectorTy &R = getRanges(); + const IntRangeVector &R = getRanges(); size_t E = R.size(); for (size_t I = 0; I != E; ++I) { const llvm::APSInt &Min = BVF.getValue(R[I].first, T); @@ -262,23 +519,28 @@ ProgramStateRef StdLibraryFunctionsChecker::ValueRange::applyAsOutOfRange( return State; } -ProgramStateRef -StdLibraryFunctionsChecker::ValueRange::applyAsWithinRange( +ProgramStateRef StdLibraryFunctionsChecker::RangeConstraint::applyAsWithinRange( ProgramStateRef State, const CallEvent &Call, - const FunctionSummaryTy &Summary) const { + const Summary &Summary) const { ProgramStateManager &Mgr = State->getStateManager(); SValBuilder &SVB = Mgr.getSValBuilder(); BasicValueFactory &BVF = SVB.getBasicValueFactory(); ConstraintManager &CM = Mgr.getConstraintManager(); - QualType T = getArgType(Summary, getArgNo()); + QualType T = Summary.getArgType(getArgNo()); SVal V = getArgSVal(Call, getArgNo()); // "WithinRange R" is treated as "outside [T_MIN, T_MAX] \ R". // We cut off [T_MIN, min(R) - 1] and [max(R) + 1, T_MAX] if necessary, // and then cut away all holes in R one by one. + // + // E.g. consider a range list R as [A, B] and [C, D] + // -------+--------+------------------+------------+-----------> + // A B C D + // Then we assume that the value is not in [-inf, A - 1], + // then not in [D + 1, +inf], then not in [B + 1, C - 1] if (auto N = V.getAs<NonLoc>()) { - const IntRangeVectorTy &R = getRanges(); + const IntRangeVector &R = getRanges(); size_t E = R.size(); const llvm::APSInt &MinusInf = BVF.getMinValue(T); @@ -303,31 +565,31 @@ StdLibraryFunctionsChecker::ValueRange::applyAsWithinRange( for (size_t I = 1; I != E; ++I) { const llvm::APSInt &Min = BVF.getValue(R[I - 1].second + 1ULL, T); const llvm::APSInt &Max = BVF.getValue(R[I].first - 1ULL, T); - assert(Min <= Max); - State = CM.assumeInclusiveRange(State, *N, Min, Max, false); - if (!State) - return nullptr; + if (Min <= Max) { + State = CM.assumeInclusiveRange(State, *N, Min, Max, false); + if (!State) + return nullptr; + } } } return State; } -ProgramStateRef -StdLibraryFunctionsChecker::ValueRange::applyAsComparesToArgument( - ProgramStateRef State, const CallEvent &Call, - const FunctionSummaryTy &Summary) const { +ProgramStateRef StdLibraryFunctionsChecker::ComparisonConstraint::apply( + ProgramStateRef State, const CallEvent &Call, const Summary &Summary, + CheckerContext &C) const { ProgramStateManager &Mgr = State->getStateManager(); SValBuilder &SVB = Mgr.getSValBuilder(); QualType CondT = SVB.getConditionType(); - QualType T = getArgType(Summary, getArgNo()); + QualType T = Summary.getArgType(getArgNo()); SVal V = getArgSVal(Call, getArgNo()); BinaryOperator::Opcode Op = getOpcode(); - ArgNoTy OtherArg = getOtherArgNo(); + ArgNo OtherArg = getOtherArgNo(); SVal OtherV = getArgSVal(Call, OtherArg); - QualType OtherT = getArgType(Call, OtherArg); + QualType OtherT = Summary.getArgType(OtherArg); // Note: we avoid integral promotion for comparison. OtherV = SVB.evalCast(OtherV, T, OtherT); if (auto CompV = SVB.evalBinOp(State, Op, V, OtherV, CondT) @@ -336,28 +598,53 @@ StdLibraryFunctionsChecker::ValueRange::applyAsComparesToArgument( return State; } -void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call, - CheckerContext &C) const { - const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); - if (!FD) +void StdLibraryFunctionsChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + Optional<Summary> FoundSummary = findFunctionSummary(Call, C); + if (!FoundSummary) return; - const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); - if (!CE) - return; + const Summary &Summary = *FoundSummary; + ProgramStateRef State = C.getState(); + + ProgramStateRef NewState = State; + for (const ValueConstraintPtr &Constraint : Summary.getArgConstraints()) { + ProgramStateRef SuccessSt = Constraint->apply(NewState, Call, Summary, C); + ProgramStateRef FailureSt = + Constraint->negate()->apply(NewState, Call, Summary, C); + // The argument constraint is not satisfied. + if (FailureSt && !SuccessSt) { + if (ExplodedNode *N = C.generateErrorNode(NewState)) + reportBug(Call, N, C); + break; + } else { + // We will apply the constraint even if we cannot reason about the + // argument. This means both SuccessSt and FailureSt can be true. If we + // weren't applying the constraint that would mean that symbolic + // execution continues on a code whose behaviour is undefined. + assert(SuccessSt); + NewState = SuccessSt; + } + } + if (NewState && NewState != State) + C.addTransition(NewState); +} - Optional<FunctionSummaryTy> FoundSummary = findFunctionSummary(FD, CE, C); +void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + Optional<Summary> FoundSummary = findFunctionSummary(Call, C); if (!FoundSummary) return; - // Now apply ranges. - const FunctionSummaryTy &Summary = *FoundSummary; + // Now apply the constraints. + const Summary &Summary = *FoundSummary; ProgramStateRef State = C.getState(); - for (const auto &VRS: Summary.Ranges) { + // Apply case/branch specifications. + for (const ConstraintSet &Case : Summary.getCaseConstraints()) { ProgramStateRef NewState = State; - for (const auto &VR: VRS) { - NewState = VR.apply(NewState, Call, Summary); + for (const ValueConstraintPtr &Constraint : Case) { + NewState = Constraint->apply(NewState, Call, Summary, C); if (!NewState) break; } @@ -369,23 +656,16 @@ void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call, bool StdLibraryFunctionsChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { - const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); - if (!FD) - return false; - - const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); - if (!CE) - return false; - - Optional<FunctionSummaryTy> FoundSummary = findFunctionSummary(FD, CE, C); + Optional<Summary> FoundSummary = findFunctionSummary(Call, C); if (!FoundSummary) return false; - const FunctionSummaryTy &Summary = *FoundSummary; - switch (Summary.InvalidationKind) { + const Summary &Summary = *FoundSummary; + switch (Summary.getInvalidationKd()) { case EvalCallAsPure: { ProgramStateRef State = C.getState(); const LocationContext *LC = C.getLocationContext(); + const auto *CE = cast_or_null<CallExpr>(Call.getOriginExpr()); SVal V = C.getSValBuilder().conjureSymbolVal( CE, LC, CE->getType().getCanonicalType(), C.blockCount()); State = State->BindExpr(CE, LC, V); @@ -400,79 +680,86 @@ bool StdLibraryFunctionsChecker::evalCall(const CallEvent &Call, llvm_unreachable("Unknown invalidation kind!"); } -bool StdLibraryFunctionsChecker::FunctionSummaryTy::matchesCall( - const CallExpr *CE) const { +bool StdLibraryFunctionsChecker::Signature::matches( + const FunctionDecl *FD) const { // Check number of arguments: - if (CE->getNumArgs() != ArgTypes.size()) + if (FD->param_size() != ArgTys.size()) return false; - // Check return type if relevant: - if (!RetType.isNull() && RetType != CE->getType().getCanonicalType()) - return false; + // Check return type. + if (!isIrrelevant(RetTy)) + if (RetTy != FD->getReturnType().getCanonicalType()) + return false; - // Check argument types when relevant: - for (size_t I = 0, E = ArgTypes.size(); I != E; ++I) { - QualType FormalT = ArgTypes[I]; - // Null type marks irrelevant arguments. - if (FormalT.isNull()) + // Check argument types. + for (size_t I = 0, E = ArgTys.size(); I != E; ++I) { + QualType ArgTy = ArgTys[I]; + if (isIrrelevant(ArgTy)) continue; - - assertTypeSuitableForSummary(FormalT); - - QualType ActualT = StdLibraryFunctionsChecker::getArgType(CE, I); - assert(ActualT.isCanonical()); - if (ActualT != FormalT) + if (ArgTy != FD->getParamDecl(I)->getType().getCanonicalType()) return false; } return true; } -Optional<StdLibraryFunctionsChecker::FunctionSummaryTy> +Optional<StdLibraryFunctionsChecker::Summary> StdLibraryFunctionsChecker::findFunctionSummary(const FunctionDecl *FD, - const CallExpr *CE, CheckerContext &C) const { - // Note: we cannot always obtain FD from CE - // (eg. virtual call, or call by pointer). - assert(CE); - if (!FD) return None; - SValBuilder &SVB = C.getSValBuilder(); - BasicValueFactory &BVF = SVB.getBasicValueFactory(); - initFunctionSummaries(BVF); + initFunctionSummaries(C); - IdentifierInfo *II = FD->getIdentifier(); - if (!II) - return None; - StringRef Name = II->getName(); - if (Name.empty() || !C.isCLibraryFunction(FD, Name)) + auto FSMI = FunctionSummaryMap.find(FD->getCanonicalDecl()); + if (FSMI == FunctionSummaryMap.end()) return None; + return FSMI->second; +} - auto FSMI = FunctionSummaryMap.find(Name); - if (FSMI == FunctionSummaryMap.end()) +Optional<StdLibraryFunctionsChecker::Summary> +StdLibraryFunctionsChecker::findFunctionSummary(const CallEvent &Call, + CheckerContext &C) const { + const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + if (!FD) return None; + return findFunctionSummary(FD, C); +} - // Verify that function signature matches the spec in advance. - // Otherwise we might be modeling the wrong function. - // Strict checking is important because we will be conducting - // very integral-type-sensitive operations on arguments and - // return values. - const FunctionVariantsTy &SpecVariants = FSMI->second; - for (const FunctionSummaryTy &Spec : SpecVariants) - if (Spec.matchesCall(CE)) - return Spec; +static llvm::Optional<QualType> lookupType(StringRef Name, + const ASTContext &ACtx) { + IdentifierInfo &II = ACtx.Idents.get(Name); + auto LookupRes = ACtx.getTranslationUnitDecl()->lookup(&II); + if (LookupRes.size() == 0) + return None; + // Prioritze typedef declarations. + // This is needed in case of C struct typedefs. E.g.: + // typedef struct FILE FILE; + // In this case, we have a RecordDecl 'struct FILE' with the name 'FILE' and + // we have a TypedefDecl with the name 'FILE'. + for (Decl *D : LookupRes) + if (auto *TD = dyn_cast<TypedefNameDecl>(D)) + return ACtx.getTypeDeclType(TD).getCanonicalType(); + + // Find the first TypeDecl. + // There maybe cases when a function has the same name as a struct. + // E.g. in POSIX: `struct stat` and the function `stat()`: + // int stat(const char *restrict path, struct stat *restrict buf); + for (Decl *D : LookupRes) + if (auto *TD = dyn_cast<TypeDecl>(D)) + return ACtx.getTypeDeclType(TD).getCanonicalType(); return None; } void StdLibraryFunctionsChecker::initFunctionSummaries( - BasicValueFactory &BVF) const { + CheckerContext &C) const { if (!FunctionSummaryMap.empty()) return; - ASTContext &ACtx = BVF.getContext(); + SValBuilder &SVB = C.getSValBuilder(); + BasicValueFactory &BVF = SVB.getBasicValueFactory(); + const ASTContext &ACtx = BVF.getContext(); // These types are useful for writing specifications quickly, // New specifications should probably introduce more types. @@ -481,15 +768,105 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( // of function summary for common cases (eg. ssize_t could be int or long // or long long, so three summary variants would be enough). // Of course, function variants are also useful for C++ overloads. - QualType Irrelevant; // A placeholder, whenever we do not care about the type. - QualType IntTy = ACtx.IntTy; - QualType LongTy = ACtx.LongTy; - QualType LongLongTy = ACtx.LongLongTy; - QualType SizeTy = ACtx.getSizeType(); + const QualType VoidTy = ACtx.VoidTy; + const QualType IntTy = ACtx.IntTy; + const QualType UnsignedIntTy = ACtx.UnsignedIntTy; + const QualType LongTy = ACtx.LongTy; + const QualType LongLongTy = ACtx.LongLongTy; + const QualType SizeTy = ACtx.getSizeType(); + + const QualType VoidPtrTy = ACtx.VoidPtrTy; // void * + const QualType IntPtrTy = ACtx.getPointerType(IntTy); // int * + const QualType UnsignedIntPtrTy = + ACtx.getPointerType(UnsignedIntTy); // unsigned int * + const QualType VoidPtrRestrictTy = + ACtx.getLangOpts().C99 ? ACtx.getRestrictType(VoidPtrTy) // void *restrict + : VoidPtrTy; + const QualType ConstVoidPtrTy = + ACtx.getPointerType(ACtx.VoidTy.withConst()); // const void * + const QualType CharPtrTy = ACtx.getPointerType(ACtx.CharTy); // char * + const QualType CharPtrRestrictTy = + ACtx.getLangOpts().C99 ? ACtx.getRestrictType(CharPtrTy) // char *restrict + : CharPtrTy; + const QualType ConstCharPtrTy = + ACtx.getPointerType(ACtx.CharTy.withConst()); // const char * + const QualType ConstCharPtrRestrictTy = + ACtx.getLangOpts().C99 + ? ACtx.getRestrictType(ConstCharPtrTy) // const char *restrict + : ConstCharPtrTy; + const QualType Wchar_tPtrTy = ACtx.getPointerType(ACtx.WCharTy); // wchar_t * + const QualType ConstWchar_tPtrTy = + ACtx.getPointerType(ACtx.WCharTy.withConst()); // const wchar_t * + const QualType ConstVoidPtrRestrictTy = + ACtx.getLangOpts().C99 + ? ACtx.getRestrictType(ConstVoidPtrTy) // const void *restrict + : ConstVoidPtrTy; + + const RangeInt IntMax = BVF.getMaxValue(IntTy).getLimitedValue(); + const RangeInt UnsignedIntMax = + BVF.getMaxValue(UnsignedIntTy).getLimitedValue(); + const RangeInt LongMax = BVF.getMaxValue(LongTy).getLimitedValue(); + const RangeInt LongLongMax = BVF.getMaxValue(LongLongTy).getLimitedValue(); + const RangeInt SizeMax = BVF.getMaxValue(SizeTy).getLimitedValue(); + + // Set UCharRangeMax to min of int or uchar maximum value. + // The C standard states that the arguments of functions like isalpha must + // be representable as an unsigned char. Their type is 'int', so the max + // value of the argument should be min(UCharMax, IntMax). This just happen + // to be true for commonly used and well tested instruction set + // architectures, but not for others. + const RangeInt UCharRangeMax = + std::min(BVF.getMaxValue(ACtx.UnsignedCharTy).getLimitedValue(), IntMax); + + // The platform dependent value of EOF. + // Try our best to parse this from the Preprocessor, otherwise fallback to -1. + const auto EOFv = [&C]() -> RangeInt { + if (const llvm::Optional<int> OptInt = + tryExpandAsInteger("EOF", C.getPreprocessor())) + return *OptInt; + return -1; + }(); + + // Auxiliary class to aid adding summaries to the summary map. + struct AddToFunctionSummaryMap { + const ASTContext &ACtx; + FunctionSummaryMapType ⤅ + bool DisplayLoadedSummaries; + AddToFunctionSummaryMap(const ASTContext &ACtx, FunctionSummaryMapType &FSM, + bool DisplayLoadedSummaries) + : ACtx(ACtx), Map(FSM), DisplayLoadedSummaries(DisplayLoadedSummaries) { + } - RangeIntTy IntMax = BVF.getMaxValue(IntTy).getLimitedValue(); - RangeIntTy LongMax = BVF.getMaxValue(LongTy).getLimitedValue(); - RangeIntTy LongLongMax = BVF.getMaxValue(LongLongTy).getLimitedValue(); + // Add a summary to a FunctionDecl found by lookup. The lookup is performed + // by the given Name, and in the global scope. The summary will be attached + // to the found FunctionDecl only if the signatures match. + void operator()(StringRef Name, Summary S) { + IdentifierInfo &II = ACtx.Idents.get(Name); + auto LookupRes = ACtx.getTranslationUnitDecl()->lookup(&II); + if (LookupRes.size() == 0) + return; + for (Decl *D : LookupRes) { + if (auto *FD = dyn_cast<FunctionDecl>(D)) { + if (S.matchesAndSet(FD)) { + auto Res = Map.insert({FD->getCanonicalDecl(), S}); + assert(Res.second && "Function already has a summary set!"); + (void)Res; + if (DisplayLoadedSummaries) { + llvm::errs() << "Loaded summary for: "; + FD->print(llvm::errs()); + llvm::errs() << "\n"; + } + return; + } + } + } + } + // Add several summaries for the given name. + void operator()(StringRef Name, const std::vector<Summary> &Summaries) { + for (const Summary &S : Summaries) + operator()(Name, S); + } + } addToFunctionSummaryMap(ACtx, FunctionSummaryMap, DisplayLoadedSummaries); // We are finally ready to define specifications for all supported functions. // @@ -516,550 +893,876 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( // return value, however the correct range is [-1, 10]. // // Please update the list of functions in the header after editing! - // - // The format is as follows: - // - //{ "function name", - // { spec: - // { argument types list, ... }, - // return type, purity, { range set list: - // { range list: - // { argument index, within or out of, {{from, to}, ...} }, - // { argument index, compares to argument, {{how, which}} }, - // ... - // } - // } - // } - //} - -#define SUMMARY_WITH_VARIANTS(identifier) {#identifier, { -#define END_SUMMARY_WITH_VARIANTS }}, -#define VARIANT(argument_types, return_type, invalidation_approach) \ - { argument_types, return_type, invalidation_approach, { -#define END_VARIANT } }, -#define SUMMARY(identifier, argument_types, return_type, \ - invalidation_approach) \ - { #identifier, { { argument_types, return_type, invalidation_approach, { -#define END_SUMMARY } } } }, -#define ARGUMENT_TYPES(...) { __VA_ARGS__ } -#define RETURN_TYPE(x) x -#define INVALIDATION_APPROACH(x) x -#define CASE { -#define END_CASE }, -#define ARGUMENT_CONDITION(argument_number, condition_kind) \ - { argument_number, condition_kind, { -#define END_ARGUMENT_CONDITION }}, -#define RETURN_VALUE_CONDITION(condition_kind) \ - { Ret, condition_kind, { -#define END_RETURN_VALUE_CONDITION }}, -#define ARG_NO(x) x##U -#define RANGE(x, y) { x, y }, -#define SINGLE_VALUE(x) RANGE(x, x) -#define IS_LESS_THAN(arg) { BO_LE, arg } - - FunctionSummaryMap = { - // The isascii() family of functions. - SUMMARY(isalnum, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // Boils down to isupper() or islower() or isdigit() - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE('0', '9') - RANGE('A', 'Z') - RANGE('a', 'z') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE // The locale-specific range. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(128, 255) - END_ARGUMENT_CONDITION - // No post-condition. We are completely unaware of - // locale-specific return values. - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE('0', '9') - RANGE('A', 'Z') - RANGE('a', 'z') - RANGE(128, 255) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isalpha, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // isupper() or islower(). Note that 'Z' is less than 'a'. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE('A', 'Z') - RANGE('a', 'z') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE // The locale-specific range. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(128, 255) - END_ARGUMENT_CONDITION - END_CASE - CASE // Other. - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE('A', 'Z') - RANGE('a', 'z') - RANGE(128, 255) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isascii, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // Is ASCII. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(0, 127) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE(0, 127) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isblank, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - SINGLE_VALUE('\t') - SINGLE_VALUE(' ') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - SINGLE_VALUE('\t') - SINGLE_VALUE(' ') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(iscntrl, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // 0..31 or 127 - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(0, 32) - SINGLE_VALUE(127) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE(0, 32) - SINGLE_VALUE(127) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isdigit, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // Is a digit. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE('0', '9') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE('0', '9') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isgraph, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(33, 126) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE(33, 126) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(islower, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // Is certainly lowercase. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE('a', 'z') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE // Is ascii but not lowercase. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(0, 127) - END_ARGUMENT_CONDITION - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE('a', 'z') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE // The locale-specific range. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(128, 255) - END_ARGUMENT_CONDITION - END_CASE - CASE // Is not an unsigned char. - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE(0, 255) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isprint, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(32, 126) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE(32, 126) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(ispunct, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE('!', '/') - RANGE(':', '@') - RANGE('[', '`') - RANGE('{', '~') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE('!', '/') - RANGE(':', '@') - RANGE('[', '`') - RANGE('{', '~') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isspace, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // Space, '\f', '\n', '\r', '\t', '\v'. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(9, 13) - SINGLE_VALUE(' ') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE // The locale-specific range. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(128, 255) - END_ARGUMENT_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE(9, 13) - SINGLE_VALUE(' ') - RANGE(128, 255) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isupper, ARGUMENT_TYPES(IntTy), RETURN_TYPE (IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // Is certainly uppercase. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE('A', 'Z') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE // The locale-specific range. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(128, 255) - END_ARGUMENT_CONDITION - END_CASE - CASE // Other. - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE('A', 'Z') RANGE(128, 255) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isxdigit, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE('0', '9') - RANGE('A', 'F') - RANGE('a', 'f') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE('0', '9') - RANGE('A', 'F') - RANGE('a', 'f') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - - // The getc() family of functions that returns either a char or an EOF. - SUMMARY(getc, ARGUMENT_TYPES(Irrelevant), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(NoEvalCall)) - CASE // FIXME: EOF is assumed to be defined as -1. - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, 255) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(fgetc, ARGUMENT_TYPES(Irrelevant), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(NoEvalCall)) - CASE // FIXME: EOF is assumed to be defined as -1. - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, 255) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(getchar, ARGUMENT_TYPES(), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(NoEvalCall)) - CASE // FIXME: EOF is assumed to be defined as -1. - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, 255) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - - // read()-like functions that never return more than buffer size. - // We are not sure how ssize_t is defined on every platform, so we provide - // three variants that should cover common cases. - SUMMARY_WITH_VARIANTS(read) - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy), - RETURN_TYPE(IntTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, IntMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy), - RETURN_TYPE(LongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, LongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy), - RETURN_TYPE(LongLongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, LongLongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - END_SUMMARY_WITH_VARIANTS - SUMMARY_WITH_VARIANTS(write) - // Again, due to elusive nature of ssize_t, we have duplicate - // our summaries to cover different variants. - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy), - RETURN_TYPE(IntTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, IntMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy), - RETURN_TYPE(LongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, LongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy), - RETURN_TYPE(LongLongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, LongLongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - END_SUMMARY_WITH_VARIANTS - SUMMARY(fread, - ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy, Irrelevant), - RETURN_TYPE(SizeTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(fwrite, - ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy, Irrelevant), - RETURN_TYPE(SizeTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - - // getline()-like functions either fail or read at least the delimiter. - SUMMARY_WITH_VARIANTS(getline) - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant), - RETURN_TYPE(IntTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(-1) - RANGE(1, IntMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant), - RETURN_TYPE(LongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(-1) - RANGE(1, LongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant), - RETURN_TYPE(LongLongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(-1) - RANGE(1, LongLongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - END_SUMMARY_WITH_VARIANTS - SUMMARY_WITH_VARIANTS(getdelim) - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant, Irrelevant), - RETURN_TYPE(IntTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(-1) - RANGE(1, IntMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant, Irrelevant), - RETURN_TYPE(LongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(-1) - RANGE(1, LongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant, Irrelevant), - RETURN_TYPE(LongLongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(-1) - RANGE(1, LongLongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - END_SUMMARY_WITH_VARIANTS + + // Below are helpers functions to create the summaries. + auto ArgumentCondition = [](ArgNo ArgN, RangeKind Kind, + IntRangeVector Ranges) { + return std::make_shared<RangeConstraint>(ArgN, Kind, Ranges); + }; + auto BufferSize = [](auto... Args) { + return std::make_shared<BufferSizeConstraint>(Args...); + }; + struct { + auto operator()(RangeKind Kind, IntRangeVector Ranges) { + return std::make_shared<RangeConstraint>(Ret, Kind, Ranges); + } + auto operator()(BinaryOperator::Opcode Op, ArgNo OtherArgN) { + return std::make_shared<ComparisonConstraint>(Ret, Op, OtherArgN); + } + } ReturnValueCondition; + auto Range = [](RangeInt b, RangeInt e) { + return IntRangeVector{std::pair<RangeInt, RangeInt>{b, e}}; + }; + auto SingleValue = [](RangeInt v) { + return IntRangeVector{std::pair<RangeInt, RangeInt>{v, v}}; }; + auto LessThanOrEq = BO_LE; + auto NotNull = [&](ArgNo ArgN) { + return std::make_shared<NotNullConstraint>(ArgN); + }; + + Optional<QualType> FileTy = lookupType("FILE", ACtx); + Optional<QualType> FilePtrTy, FilePtrRestrictTy; + if (FileTy) { + // FILE * + FilePtrTy = ACtx.getPointerType(*FileTy); + // FILE *restrict + FilePtrRestrictTy = + ACtx.getLangOpts().C99 ? ACtx.getRestrictType(*FilePtrTy) : *FilePtrTy; + } + + using RetType = QualType; + // Templates for summaries that are reused by many functions. + auto Getc = [&]() { + return Summary(ArgTypes{*FilePtrTy}, RetType{IntTy}, NoEvalCall) + .Case({ReturnValueCondition(WithinRange, + {{EOFv, EOFv}, {0, UCharRangeMax}})}); + }; + auto Read = [&](RetType R, RangeInt Max) { + return Summary(ArgTypes{Irrelevant, Irrelevant, SizeTy}, RetType{R}, + NoEvalCall) + .Case({ReturnValueCondition(LessThanOrEq, ArgNo(2)), + ReturnValueCondition(WithinRange, Range(-1, Max))}); + }; + auto Fread = [&]() { + return Summary( + ArgTypes{VoidPtrRestrictTy, SizeTy, SizeTy, *FilePtrRestrictTy}, + RetType{SizeTy}, NoEvalCall) + .Case({ + ReturnValueCondition(LessThanOrEq, ArgNo(2)), + }) + .ArgConstraint(NotNull(ArgNo(0))); + }; + auto Fwrite = [&]() { + return Summary(ArgTypes{ConstVoidPtrRestrictTy, SizeTy, SizeTy, + *FilePtrRestrictTy}, + RetType{SizeTy}, NoEvalCall) + .Case({ + ReturnValueCondition(LessThanOrEq, ArgNo(2)), + }) + .ArgConstraint(NotNull(ArgNo(0))); + }; + auto Getline = [&](RetType R, RangeInt Max) { + return Summary(ArgTypes{Irrelevant, Irrelevant, Irrelevant}, RetType{R}, + NoEvalCall) + .Case({ReturnValueCondition(WithinRange, {{-1, -1}, {1, Max}})}); + }; + + // The isascii() family of functions. + // The behavior is undefined if the value of the argument is not + // representable as unsigned char or is not equal to EOF. See e.g. C99 + // 7.4.1.2 The isalpha function (p: 181-182). + addToFunctionSummaryMap( + "isalnum", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + // Boils down to isupper() or islower() or isdigit(). + .Case({ArgumentCondition(0U, WithinRange, + {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}}), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + // The locale-specific range. + // No post-condition. We are completely unaware of + // locale-specific return values. + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}) + .Case( + {ArgumentCondition( + 0U, OutOfRange, + {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}, {128, UCharRangeMax}}), + ReturnValueCondition(WithinRange, SingleValue(0))}) + .ArgConstraint(ArgumentCondition( + 0U, WithinRange, {{EOFv, EOFv}, {0, UCharRangeMax}}))); + addToFunctionSummaryMap( + "isalpha", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, {{'A', 'Z'}, {'a', 'z'}}), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + // The locale-specific range. + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}) + .Case({ArgumentCondition( + 0U, OutOfRange, + {{'A', 'Z'}, {'a', 'z'}, {128, UCharRangeMax}}), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isascii", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, Range(0, 127)), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition(0U, OutOfRange, Range(0, 127)), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isblank", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, {{'\t', '\t'}, {' ', ' '}}), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition(0U, OutOfRange, {{'\t', '\t'}, {' ', ' '}}), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "iscntrl", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, {{0, 32}, {127, 127}}), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition(0U, OutOfRange, {{0, 32}, {127, 127}}), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isdigit", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, Range('0', '9')), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition(0U, OutOfRange, Range('0', '9')), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isgraph", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, Range(33, 126)), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition(0U, OutOfRange, Range(33, 126)), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "islower", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + // Is certainly lowercase. + .Case({ArgumentCondition(0U, WithinRange, Range('a', 'z')), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + // Is ascii but not lowercase. + .Case({ArgumentCondition(0U, WithinRange, Range(0, 127)), + ArgumentCondition(0U, OutOfRange, Range('a', 'z')), + ReturnValueCondition(WithinRange, SingleValue(0))}) + // The locale-specific range. + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}) + // Is not an unsigned char. + .Case({ArgumentCondition(0U, OutOfRange, Range(0, UCharRangeMax)), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isprint", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, Range(32, 126)), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition(0U, OutOfRange, Range(32, 126)), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "ispunct", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition( + 0U, WithinRange, + {{'!', '/'}, {':', '@'}, {'[', '`'}, {'{', '~'}}), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition( + 0U, OutOfRange, + {{'!', '/'}, {':', '@'}, {'[', '`'}, {'{', '~'}}), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isspace", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + // Space, '\f', '\n', '\r', '\t', '\v'. + .Case({ArgumentCondition(0U, WithinRange, {{9, 13}, {' ', ' '}}), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + // The locale-specific range. + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}) + .Case({ArgumentCondition(0U, OutOfRange, + {{9, 13}, {' ', ' '}, {128, UCharRangeMax}}), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isupper", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + // Is certainly uppercase. + .Case({ArgumentCondition(0U, WithinRange, Range('A', 'Z')), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + // The locale-specific range. + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}) + // Other. + .Case({ArgumentCondition(0U, OutOfRange, + {{'A', 'Z'}, {128, UCharRangeMax}}), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isxdigit", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, + {{'0', '9'}, {'A', 'F'}, {'a', 'f'}}), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition(0U, OutOfRange, + {{'0', '9'}, {'A', 'F'}, {'a', 'f'}}), + ReturnValueCondition(WithinRange, SingleValue(0))})); + + // The getc() family of functions that returns either a char or an EOF. + if (FilePtrTy) { + addToFunctionSummaryMap("getc", Getc()); + addToFunctionSummaryMap("fgetc", Getc()); + } + addToFunctionSummaryMap( + "getchar", Summary(ArgTypes{}, RetType{IntTy}, NoEvalCall) + .Case({ReturnValueCondition( + WithinRange, {{EOFv, EOFv}, {0, UCharRangeMax}})})); + + // read()-like functions that never return more than buffer size. + if (FilePtrRestrictTy) { + addToFunctionSummaryMap("fread", Fread()); + addToFunctionSummaryMap("fwrite", Fwrite()); + } + + // We are not sure how ssize_t is defined on every platform, so we + // provide three variants that should cover common cases. + // FIXME these are actually defined by POSIX and not by the C standard, we + // should handle them together with the rest of the POSIX functions. + addToFunctionSummaryMap("read", {Read(IntTy, IntMax), Read(LongTy, LongMax), + Read(LongLongTy, LongLongMax)}); + addToFunctionSummaryMap("write", {Read(IntTy, IntMax), Read(LongTy, LongMax), + Read(LongLongTy, LongLongMax)}); + + // getline()-like functions either fail or read at least the delimiter. + // FIXME these are actually defined by POSIX and not by the C standard, we + // should handle them together with the rest of the POSIX functions. + addToFunctionSummaryMap("getline", + {Getline(IntTy, IntMax), Getline(LongTy, LongMax), + Getline(LongLongTy, LongLongMax)}); + addToFunctionSummaryMap("getdelim", + {Getline(IntTy, IntMax), Getline(LongTy, LongMax), + Getline(LongLongTy, LongLongMax)}); + + if (ModelPOSIX) { + + // long a64l(const char *str64); + addToFunctionSummaryMap( + "a64l", Summary(ArgTypes{ConstCharPtrTy}, RetType{LongTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // char *l64a(long value); + addToFunctionSummaryMap( + "l64a", Summary(ArgTypes{LongTy}, RetType{CharPtrTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, LongMax)))); + + // int access(const char *pathname, int amode); + addToFunctionSummaryMap("access", Summary(ArgTypes{ConstCharPtrTy, IntTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int faccessat(int dirfd, const char *pathname, int mode, int flags); + addToFunctionSummaryMap( + "faccessat", Summary(ArgTypes{IntTy, ConstCharPtrTy, IntTy, IntTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int dup(int fildes); + addToFunctionSummaryMap( + "dup", Summary(ArgTypes{IntTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + + // int dup2(int fildes1, int filedes2); + addToFunctionSummaryMap( + "dup2", + Summary(ArgTypes{IntTy, IntTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .ArgConstraint( + ArgumentCondition(1, WithinRange, Range(0, IntMax)))); + + // int fdatasync(int fildes); + addToFunctionSummaryMap( + "fdatasync", Summary(ArgTypes{IntTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, + Range(0, IntMax)))); + + // int fnmatch(const char *pattern, const char *string, int flags); + addToFunctionSummaryMap( + "fnmatch", Summary(ArgTypes{ConstCharPtrTy, ConstCharPtrTy, IntTy}, + RetType{IntTy}, EvalCallAsPure) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int fsync(int fildes); + addToFunctionSummaryMap( + "fsync", Summary(ArgTypes{IntTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + + Optional<QualType> Off_tTy = lookupType("off_t", ACtx); + + if (Off_tTy) + // int truncate(const char *path, off_t length); + addToFunctionSummaryMap("truncate", + Summary(ArgTypes{ConstCharPtrTy, *Off_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int symlink(const char *oldpath, const char *newpath); + addToFunctionSummaryMap("symlink", + Summary(ArgTypes{ConstCharPtrTy, ConstCharPtrTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int symlinkat(const char *oldpath, int newdirfd, const char *newpath); + addToFunctionSummaryMap( + "symlinkat", + Summary(ArgTypes{ConstCharPtrTy, IntTy, ConstCharPtrTy}, RetType{IntTy}, + NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(ArgumentCondition(1, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(2)))); + + if (Off_tTy) + // int lockf(int fd, int cmd, off_t len); + addToFunctionSummaryMap( + "lockf", + Summary(ArgTypes{IntTy, IntTy, *Off_tTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + + Optional<QualType> Mode_tTy = lookupType("mode_t", ACtx); + + if (Mode_tTy) + // int creat(const char *pathname, mode_t mode); + addToFunctionSummaryMap("creat", + Summary(ArgTypes{ConstCharPtrTy, *Mode_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // unsigned int sleep(unsigned int seconds); + addToFunctionSummaryMap( + "sleep", + Summary(ArgTypes{UnsignedIntTy}, RetType{UnsignedIntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, UnsignedIntMax)))); + + Optional<QualType> DirTy = lookupType("DIR", ACtx); + Optional<QualType> DirPtrTy; + if (DirTy) + DirPtrTy = ACtx.getPointerType(*DirTy); + + if (DirPtrTy) + // int dirfd(DIR *dirp); + addToFunctionSummaryMap( + "dirfd", Summary(ArgTypes{*DirPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // unsigned int alarm(unsigned int seconds); + addToFunctionSummaryMap( + "alarm", + Summary(ArgTypes{UnsignedIntTy}, RetType{UnsignedIntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, UnsignedIntMax)))); + + if (DirPtrTy) + // int closedir(DIR *dir); + addToFunctionSummaryMap( + "closedir", Summary(ArgTypes{*DirPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // char *strdup(const char *s); + addToFunctionSummaryMap("strdup", Summary(ArgTypes{ConstCharPtrTy}, + RetType{CharPtrTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // char *strndup(const char *s, size_t n); + addToFunctionSummaryMap( + "strndup", Summary(ArgTypes{ConstCharPtrTy, SizeTy}, RetType{CharPtrTy}, + NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(ArgumentCondition(1, WithinRange, + Range(0, SizeMax)))); + + // wchar_t *wcsdup(const wchar_t *s); + addToFunctionSummaryMap("wcsdup", Summary(ArgTypes{ConstWchar_tPtrTy}, + RetType{Wchar_tPtrTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int mkstemp(char *template); + addToFunctionSummaryMap( + "mkstemp", Summary(ArgTypes{CharPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // char *mkdtemp(char *template); + addToFunctionSummaryMap( + "mkdtemp", Summary(ArgTypes{CharPtrTy}, RetType{CharPtrTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // char *getcwd(char *buf, size_t size); + addToFunctionSummaryMap( + "getcwd", + Summary(ArgTypes{CharPtrTy, SizeTy}, RetType{CharPtrTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(1, WithinRange, Range(0, SizeMax)))); + + if (Mode_tTy) { + // int mkdir(const char *pathname, mode_t mode); + addToFunctionSummaryMap("mkdir", + Summary(ArgTypes{ConstCharPtrTy, *Mode_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int mkdirat(int dirfd, const char *pathname, mode_t mode); + addToFunctionSummaryMap( + "mkdirat", Summary(ArgTypes{IntTy, ConstCharPtrTy, *Mode_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(1)))); + } + + Optional<QualType> Dev_tTy = lookupType("dev_t", ACtx); + + if (Mode_tTy && Dev_tTy) { + // int mknod(const char *pathname, mode_t mode, dev_t dev); + addToFunctionSummaryMap( + "mknod", Summary(ArgTypes{ConstCharPtrTy, *Mode_tTy, *Dev_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int mknodat(int dirfd, const char *pathname, mode_t mode, dev_t dev); + addToFunctionSummaryMap("mknodat", Summary(ArgTypes{IntTy, ConstCharPtrTy, + *Mode_tTy, *Dev_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(1)))); + } + + if (Mode_tTy) { + // int chmod(const char *path, mode_t mode); + addToFunctionSummaryMap("chmod", + Summary(ArgTypes{ConstCharPtrTy, *Mode_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int fchmodat(int dirfd, const char *pathname, mode_t mode, int flags); + addToFunctionSummaryMap( + "fchmodat", Summary(ArgTypes{IntTy, ConstCharPtrTy, *Mode_tTy, IntTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, + Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int fchmod(int fildes, mode_t mode); + addToFunctionSummaryMap( + "fchmod", + Summary(ArgTypes{IntTy, *Mode_tTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + } + + Optional<QualType> Uid_tTy = lookupType("uid_t", ACtx); + Optional<QualType> Gid_tTy = lookupType("gid_t", ACtx); + + if (Uid_tTy && Gid_tTy) { + // int fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, + // int flags); + addToFunctionSummaryMap( + "fchownat", + Summary(ArgTypes{IntTy, ConstCharPtrTy, *Uid_tTy, *Gid_tTy, IntTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int chown(const char *path, uid_t owner, gid_t group); + addToFunctionSummaryMap( + "chown", Summary(ArgTypes{ConstCharPtrTy, *Uid_tTy, *Gid_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int lchown(const char *path, uid_t owner, gid_t group); + addToFunctionSummaryMap( + "lchown", Summary(ArgTypes{ConstCharPtrTy, *Uid_tTy, *Gid_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int fchown(int fildes, uid_t owner, gid_t group); + addToFunctionSummaryMap( + "fchown", Summary(ArgTypes{IntTy, *Uid_tTy, *Gid_tTy}, RetType{IntTy}, + NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, + Range(0, IntMax)))); + } + + // int rmdir(const char *pathname); + addToFunctionSummaryMap( + "rmdir", Summary(ArgTypes{ConstCharPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int chdir(const char *path); + addToFunctionSummaryMap( + "chdir", Summary(ArgTypes{ConstCharPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int link(const char *oldpath, const char *newpath); + addToFunctionSummaryMap("link", + Summary(ArgTypes{ConstCharPtrTy, ConstCharPtrTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int linkat(int fd1, const char *path1, int fd2, const char *path2, + // int flag); + addToFunctionSummaryMap( + "linkat", + Summary(ArgTypes{IntTy, ConstCharPtrTy, IntTy, ConstCharPtrTy, IntTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(ArgumentCondition(2, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(3)))); + + // int unlink(const char *pathname); + addToFunctionSummaryMap( + "unlink", Summary(ArgTypes{ConstCharPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int unlinkat(int fd, const char *path, int flag); + addToFunctionSummaryMap( + "unlinkat", + Summary(ArgTypes{IntTy, ConstCharPtrTy, IntTy}, RetType{IntTy}, + NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1)))); + + Optional<QualType> StructStatTy = lookupType("stat", ACtx); + Optional<QualType> StructStatPtrTy, StructStatPtrRestrictTy; + if (StructStatTy) { + StructStatPtrTy = ACtx.getPointerType(*StructStatTy); + StructStatPtrRestrictTy = ACtx.getLangOpts().C99 + ? ACtx.getRestrictType(*StructStatPtrTy) + : *StructStatPtrTy; + } + + if (StructStatPtrTy) + // int fstat(int fd, struct stat *statbuf); + addToFunctionSummaryMap( + "fstat", + Summary(ArgTypes{IntTy, *StructStatPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1)))); + + if (StructStatPtrRestrictTy) { + // int stat(const char *restrict path, struct stat *restrict buf); + addToFunctionSummaryMap( + "stat", + Summary(ArgTypes{ConstCharPtrRestrictTy, *StructStatPtrRestrictTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int lstat(const char *restrict path, struct stat *restrict buf); + addToFunctionSummaryMap( + "lstat", + Summary(ArgTypes{ConstCharPtrRestrictTy, *StructStatPtrRestrictTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int fstatat(int fd, const char *restrict path, + // struct stat *restrict buf, int flag); + addToFunctionSummaryMap( + "fstatat", Summary(ArgTypes{IntTy, ConstCharPtrRestrictTy, + *StructStatPtrRestrictTy, IntTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, + Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(NotNull(ArgNo(2)))); + } + + if (DirPtrTy) { + // DIR *opendir(const char *name); + addToFunctionSummaryMap("opendir", Summary(ArgTypes{ConstCharPtrTy}, + RetType{*DirPtrTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // DIR *fdopendir(int fd); + addToFunctionSummaryMap( + "fdopendir", Summary(ArgTypes{IntTy}, RetType{*DirPtrTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, + Range(0, IntMax)))); + } + + // int isatty(int fildes); + addToFunctionSummaryMap( + "isatty", Summary(ArgTypes{IntTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + + if (FilePtrTy) { + // FILE *popen(const char *command, const char *type); + addToFunctionSummaryMap("popen", + Summary(ArgTypes{ConstCharPtrTy, ConstCharPtrTy}, + RetType{*FilePtrTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int pclose(FILE *stream); + addToFunctionSummaryMap( + "pclose", Summary(ArgTypes{*FilePtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + } + + // int close(int fildes); + addToFunctionSummaryMap( + "close", Summary(ArgTypes{IntTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + + // long fpathconf(int fildes, int name); + addToFunctionSummaryMap( + "fpathconf", + Summary(ArgTypes{IntTy, IntTy}, RetType{LongTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + + // long pathconf(const char *path, int name); + addToFunctionSummaryMap("pathconf", Summary(ArgTypes{ConstCharPtrTy, IntTy}, + RetType{LongTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + if (FilePtrTy) + // FILE *fdopen(int fd, const char *mode); + addToFunctionSummaryMap( + "fdopen", Summary(ArgTypes{IntTy, ConstCharPtrTy}, + RetType{*FilePtrTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1)))); + + if (DirPtrTy) { + // void rewinddir(DIR *dir); + addToFunctionSummaryMap( + "rewinddir", Summary(ArgTypes{*DirPtrTy}, RetType{VoidTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // void seekdir(DIR *dirp, long loc); + addToFunctionSummaryMap("seekdir", Summary(ArgTypes{*DirPtrTy, LongTy}, + RetType{VoidTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + } + + // int rand_r(unsigned int *seedp); + addToFunctionSummaryMap("rand_r", Summary(ArgTypes{UnsignedIntPtrTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int strcasecmp(const char *s1, const char *s2); + addToFunctionSummaryMap("strcasecmp", + Summary(ArgTypes{ConstCharPtrTy, ConstCharPtrTy}, + RetType{IntTy}, EvalCallAsPure) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int strncasecmp(const char *s1, const char *s2, size_t n); + addToFunctionSummaryMap( + "strncasecmp", Summary(ArgTypes{ConstCharPtrTy, ConstCharPtrTy, SizeTy}, + RetType{IntTy}, EvalCallAsPure) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(ArgumentCondition( + 2, WithinRange, Range(0, SizeMax)))); + + if (FilePtrTy && Off_tTy) { + + // int fileno(FILE *stream); + addToFunctionSummaryMap( + "fileno", Summary(ArgTypes{*FilePtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int fseeko(FILE *stream, off_t offset, int whence); + addToFunctionSummaryMap("fseeko", + Summary(ArgTypes{*FilePtrTy, *Off_tTy, IntTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // off_t ftello(FILE *stream); + addToFunctionSummaryMap( + "ftello", Summary(ArgTypes{*FilePtrTy}, RetType{*Off_tTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + } + + if (Off_tTy) { + Optional<RangeInt> Off_tMax = BVF.getMaxValue(*Off_tTy).getLimitedValue(); + + // void *mmap(void *addr, size_t length, int prot, int flags, int fd, + // off_t offset); + addToFunctionSummaryMap( + "mmap", + Summary(ArgTypes{VoidPtrTy, SizeTy, IntTy, IntTy, IntTy, *Off_tTy}, + RetType{VoidPtrTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(1, WithinRange, Range(1, SizeMax))) + .ArgConstraint( + ArgumentCondition(4, WithinRange, Range(0, *Off_tMax)))); + } + + Optional<QualType> Off64_tTy = lookupType("off64_t", ACtx); + Optional<RangeInt> Off64_tMax; + if (Off64_tTy) { + Off64_tMax = BVF.getMaxValue(*Off_tTy).getLimitedValue(); + // void *mmap64(void *addr, size_t length, int prot, int flags, int fd, + // off64_t offset); + addToFunctionSummaryMap( + "mmap64", + Summary(ArgTypes{VoidPtrTy, SizeTy, IntTy, IntTy, IntTy, *Off64_tTy}, + RetType{VoidPtrTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(1, WithinRange, Range(1, SizeMax))) + .ArgConstraint( + ArgumentCondition(4, WithinRange, Range(0, *Off64_tMax)))); + } + + // int pipe(int fildes[2]); + addToFunctionSummaryMap( + "pipe", Summary(ArgTypes{IntPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + if (Off_tTy) + // off_t lseek(int fildes, off_t offset, int whence); + addToFunctionSummaryMap( + "lseek", Summary(ArgTypes{IntTy, *Off_tTy, IntTy}, RetType{*Off_tTy}, + NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, + Range(0, IntMax)))); + + Optional<QualType> Ssize_tTy = lookupType("ssize_t", ACtx); + + if (Ssize_tTy) { + // ssize_t readlink(const char *restrict path, char *restrict buf, + // size_t bufsize); + addToFunctionSummaryMap( + "readlink", + Summary(ArgTypes{ConstCharPtrRestrictTy, CharPtrRestrictTy, SizeTy}, + RetType{*Ssize_tTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(1), + /*BufSize=*/ArgNo(2))) + .ArgConstraint( + ArgumentCondition(2, WithinRange, Range(0, SizeMax)))); + + // ssize_t readlinkat(int fd, const char *restrict path, + // char *restrict buf, size_t bufsize); + addToFunctionSummaryMap( + "readlinkat", Summary(ArgTypes{IntTy, ConstCharPtrRestrictTy, + CharPtrRestrictTy, SizeTy}, + RetType{*Ssize_tTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, + Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(NotNull(ArgNo(2))) + .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(2), + /*BufSize=*/ArgNo(3))) + .ArgConstraint(ArgumentCondition( + 3, WithinRange, Range(0, SizeMax)))); + } + + // int renameat(int olddirfd, const char *oldpath, int newdirfd, const char + // *newpath); + addToFunctionSummaryMap("renameat", Summary(ArgTypes{IntTy, ConstCharPtrTy, + IntTy, ConstCharPtrTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(NotNull(ArgNo(3)))); + + // char *realpath(const char *restrict file_name, + // char *restrict resolved_name); + addToFunctionSummaryMap( + "realpath", Summary(ArgTypes{ConstCharPtrRestrictTy, CharPtrRestrictTy}, + RetType{CharPtrTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + QualType CharPtrConstPtr = ACtx.getPointerType(CharPtrTy.withConst()); + + // int execv(const char *path, char *const argv[]); + addToFunctionSummaryMap("execv", + Summary(ArgTypes{ConstCharPtrTy, CharPtrConstPtr}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int execvp(const char *file, char *const argv[]); + addToFunctionSummaryMap("execvp", + Summary(ArgTypes{ConstCharPtrTy, CharPtrConstPtr}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int getopt(int argc, char * const argv[], const char *optstring); + addToFunctionSummaryMap( + "getopt", + Summary(ArgTypes{IntTy, CharPtrConstPtr, ConstCharPtrTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(NotNull(ArgNo(2)))); + } + + // Functions for testing. + if (ChecksEnabled[CK_StdCLibraryFunctionsTesterChecker]) { + addToFunctionSummaryMap( + "__two_constrained_args", + Summary(ArgTypes{IntTy, IntTy}, RetType{IntTy}, EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0U, WithinRange, SingleValue(1))) + .ArgConstraint(ArgumentCondition(1U, WithinRange, SingleValue(1)))); + addToFunctionSummaryMap( + "__arg_constrained_twice", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0U, OutOfRange, SingleValue(1))) + .ArgConstraint(ArgumentCondition(0U, OutOfRange, SingleValue(2)))); + addToFunctionSummaryMap( + "__defaultparam", + Summary(ArgTypes{Irrelevant, IntTy}, RetType{IntTy}, EvalCallAsPure) + .ArgConstraint(NotNull(ArgNo(0)))); + addToFunctionSummaryMap("__variadic", + Summary(ArgTypes{VoidPtrTy, ConstCharPtrTy}, + RetType{IntTy}, EvalCallAsPure) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + addToFunctionSummaryMap( + "__buf_size_arg_constraint", + Summary(ArgTypes{ConstVoidPtrTy, SizeTy}, RetType{IntTy}, + EvalCallAsPure) + .ArgConstraint( + BufferSize(/*Buffer=*/ArgNo(0), /*BufSize=*/ArgNo(1)))); + addToFunctionSummaryMap( + "__buf_size_arg_constraint_mul", + Summary(ArgTypes{ConstVoidPtrTy, SizeTy, SizeTy}, RetType{IntTy}, + EvalCallAsPure) + .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(0), /*BufSize=*/ArgNo(1), + /*BufSizeMultiplier=*/ArgNo(2)))); + } } void ento::registerStdCLibraryFunctionsChecker(CheckerManager &mgr) { - // If this checker grows large enough to support C++, Objective-C, or other - // standard libraries, we could use multiple register...Checker() functions, - // which would register various checkers with the help of the same Checker - // class, turning on different function summaries. - mgr.registerChecker<StdLibraryFunctionsChecker>(); + auto *Checker = mgr.registerChecker<StdLibraryFunctionsChecker>(); + Checker->DisplayLoadedSummaries = + mgr.getAnalyzerOptions().getCheckerBooleanOption( + Checker, "DisplayLoadedSummaries"); + Checker->ModelPOSIX = + mgr.getAnalyzerOptions().getCheckerBooleanOption(Checker, "ModelPOSIX"); } -bool ento::shouldRegisterStdCLibraryFunctionsChecker(const LangOptions &LO) { +bool ento::shouldRegisterStdCLibraryFunctionsChecker(const CheckerManager &mgr) { return true; } + +#define REGISTER_CHECKER(name) \ + void ento::register##name(CheckerManager &mgr) { \ + StdLibraryFunctionsChecker *checker = \ + mgr.getChecker<StdLibraryFunctionsChecker>(); \ + checker->ChecksEnabled[StdLibraryFunctionsChecker::CK_##name] = true; \ + checker->CheckNames[StdLibraryFunctionsChecker::CK_##name] = \ + mgr.getCurrentCheckerName(); \ + } \ + \ + bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; } + +REGISTER_CHECKER(StdCLibraryFunctionArgsChecker) +REGISTER_CHECKER(StdCLibraryFunctionsTesterChecker) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp index 47099f2afb6a..f6abbe4f8f03 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -27,142 +27,453 @@ using namespace std::placeholders; namespace { +struct FnDescription; + +/// State of the stream error flags. +/// Sometimes it is not known to the checker what error flags are set. +/// This is indicated by setting more than one flag to true. +/// This is an optimization to avoid state splits. +/// A stream can either be in FEOF or FERROR but not both at the same time. +/// Multiple flags are set to handle the corresponding states together. +struct StreamErrorState { + /// The stream can be in state where none of the error flags set. + bool NoError = true; + /// The stream can be in state where the EOF indicator is set. + bool FEof = false; + /// The stream can be in state where the error indicator is set. + bool FError = false; + + bool isNoError() const { return NoError && !FEof && !FError; } + bool isFEof() const { return !NoError && FEof && !FError; } + bool isFError() const { return !NoError && !FEof && FError; } + + bool operator==(const StreamErrorState &ES) const { + return NoError == ES.NoError && FEof == ES.FEof && FError == ES.FError; + } + + bool operator!=(const StreamErrorState &ES) const { return !(*this == ES); } + + StreamErrorState operator|(const StreamErrorState &E) const { + return {NoError || E.NoError, FEof || E.FEof, FError || E.FError}; + } + + StreamErrorState operator&(const StreamErrorState &E) const { + return {NoError && E.NoError, FEof && E.FEof, FError && E.FError}; + } + + StreamErrorState operator~() const { return {!NoError, !FEof, !FError}; } + + /// Returns if the StreamErrorState is a valid object. + operator bool() const { return NoError || FEof || FError; } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddBoolean(NoError); + ID.AddBoolean(FEof); + ID.AddBoolean(FError); + } +}; + +const StreamErrorState ErrorNone{true, false, false}; +const StreamErrorState ErrorFEof{false, true, false}; +const StreamErrorState ErrorFError{false, false, true}; + +/// Full state information about a stream pointer. struct StreamState { - enum Kind { Opened, Closed, OpenFailed, Escaped } K; + /// The last file operation called in the stream. + const FnDescription *LastOperation; - StreamState(Kind k) : K(k) {} + /// State of a stream symbol. + /// FIXME: We need maybe an "escaped" state later. + enum KindTy { + Opened, /// Stream is opened. + Closed, /// Closed stream (an invalid stream pointer after it was closed). + OpenFailed /// The last open operation has failed. + } State; - bool isOpened() const { return K == Opened; } - bool isClosed() const { return K == Closed; } - //bool isOpenFailed() const { return K == OpenFailed; } - //bool isEscaped() const { return K == Escaped; } + /// State of the error flags. + /// Ignored in non-opened stream state but must be NoError. + StreamErrorState const ErrorState; - bool operator==(const StreamState &X) const { return K == X.K; } + /// Indicate if the file has an "indeterminate file position indicator". + /// This can be set at a failing read or write or seek operation. + /// If it is set no more read or write is allowed. + /// This value is not dependent on the stream error flags: + /// The error flag may be cleared with `clearerr` but the file position + /// remains still indeterminate. + /// This value applies to all error states in ErrorState except FEOF. + /// An EOF+indeterminate state is the same as EOF state. + bool const FilePositionIndeterminate = false; - static StreamState getOpened() { return StreamState(Opened); } - static StreamState getClosed() { return StreamState(Closed); } - static StreamState getOpenFailed() { return StreamState(OpenFailed); } - static StreamState getEscaped() { return StreamState(Escaped); } + StreamState(const FnDescription *L, KindTy S, const StreamErrorState &ES, + bool IsFilePositionIndeterminate) + : LastOperation(L), State(S), ErrorState(ES), + FilePositionIndeterminate(IsFilePositionIndeterminate) { + assert((!ES.isFEof() || !IsFilePositionIndeterminate) && + "FilePositionIndeterminate should be false in FEof case."); + assert((State == Opened || ErrorState.isNoError()) && + "ErrorState should be None in non-opened stream state."); + } + + bool isOpened() const { return State == Opened; } + bool isClosed() const { return State == Closed; } + bool isOpenFailed() const { return State == OpenFailed; } + + bool operator==(const StreamState &X) const { + // In not opened state error state should always NoError, so comparison + // here is no problem. + return LastOperation == X.LastOperation && State == X.State && + ErrorState == X.ErrorState && + FilePositionIndeterminate == X.FilePositionIndeterminate; + } + + static StreamState getOpened(const FnDescription *L, + const StreamErrorState &ES = ErrorNone, + bool IsFilePositionIndeterminate = false) { + return StreamState{L, Opened, ES, IsFilePositionIndeterminate}; + } + static StreamState getClosed(const FnDescription *L) { + return StreamState{L, Closed, {}, false}; + } + static StreamState getOpenFailed(const FnDescription *L) { + return StreamState{L, OpenFailed, {}, false}; + } void Profile(llvm::FoldingSetNodeID &ID) const { - ID.AddInteger(K); + ID.AddPointer(LastOperation); + ID.AddInteger(State); + ID.AddInteger(ErrorState); + ID.AddBoolean(FilePositionIndeterminate); } }; -class StreamChecker : public Checker<eval::Call, - check::DeadSymbols > { - mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence, - BT_doubleclose, BT_ResourceLeak; +class StreamChecker; +using FnCheck = std::function<void(const StreamChecker *, const FnDescription *, + const CallEvent &, CheckerContext &)>; + +using ArgNoTy = unsigned int; +static const ArgNoTy ArgNone = std::numeric_limits<ArgNoTy>::max(); + +struct FnDescription { + FnCheck PreFn; + FnCheck EvalFn; + ArgNoTy StreamArgNo; +}; + +/// Get the value of the stream argument out of the passed call event. +/// The call should contain a function that is described by Desc. +SVal getStreamArg(const FnDescription *Desc, const CallEvent &Call) { + assert(Desc && Desc->StreamArgNo != ArgNone && + "Try to get a non-existing stream argument."); + return Call.getArgSVal(Desc->StreamArgNo); +} + +/// Create a conjured symbol return value for a call expression. +DefinedSVal makeRetVal(CheckerContext &C, const CallExpr *CE) { + assert(CE && "Expecting a call expression."); + + const LocationContext *LCtx = C.getLocationContext(); + return C.getSValBuilder() + .conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) + .castAs<DefinedSVal>(); +} + +ProgramStateRef bindAndAssumeTrue(ProgramStateRef State, CheckerContext &C, + const CallExpr *CE) { + DefinedSVal RetVal = makeRetVal(C, CE); + State = State->BindExpr(CE, C.getLocationContext(), RetVal); + State = State->assume(RetVal, true); + assert(State && "Assumption on new value should not fail."); + return State; +} + +ProgramStateRef bindInt(uint64_t Value, ProgramStateRef State, + CheckerContext &C, const CallExpr *CE) { + State = State->BindExpr(CE, C.getLocationContext(), + C.getSValBuilder().makeIntVal(Value, false)); + return State; +} + +class StreamChecker : public Checker<check::PreCall, eval::Call, + check::DeadSymbols, check::PointerEscape> { + BugType BT_FileNull{this, "NULL stream pointer", "Stream handling error"}; + BugType BT_UseAfterClose{this, "Closed stream", "Stream handling error"}; + BugType BT_UseAfterOpenFailed{this, "Invalid stream", + "Stream handling error"}; + BugType BT_IndeterminatePosition{this, "Invalid stream state", + "Stream handling error"}; + BugType BT_IllegalWhence{this, "Illegal whence argument", + "Stream handling error"}; + BugType BT_StreamEof{this, "Stream already in EOF", "Stream handling error"}; + BugType BT_ResourceLeak{this, "Resource leak", "Stream handling error"}; public: + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; bool evalCall(const CallEvent &Call, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + ProgramStateRef checkPointerEscape(ProgramStateRef State, + const InvalidatedSymbols &Escaped, + const CallEvent *Call, + PointerEscapeKind Kind) const; + + /// If true, evaluate special testing stream functions. + bool TestMode = false; private: - using FnCheck = std::function<void(const StreamChecker *, const CallEvent &, - CheckerContext &)>; - - CallDescriptionMap<FnCheck> Callbacks = { - {{"fopen"}, &StreamChecker::evalFopen}, - {{"freopen", 3}, &StreamChecker::evalFreopen}, - {{"tmpfile"}, &StreamChecker::evalFopen}, - {{"fclose", 1}, &StreamChecker::evalFclose}, + CallDescriptionMap<FnDescription> FnDescriptions = { + {{"fopen"}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, + {{"freopen", 3}, + {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}}, + {{"tmpfile"}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, + {{"fclose", 1}, + {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}}, {{"fread", 4}, - std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)}, + {&StreamChecker::preFread, + std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, true), 3}}, {{"fwrite", 4}, - std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)}, - {{"fseek", 3}, &StreamChecker::evalFseek}, - {{"ftell", 1}, - std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, - {{"rewind", 1}, - std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, - {{"fgetpos", 2}, - std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, - {{"fsetpos", 2}, - std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {&StreamChecker::preFwrite, + std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, false), 3}}, + {{"fseek", 3}, {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}}, + {{"ftell", 1}, {&StreamChecker::preDefault, nullptr, 0}}, + {{"rewind", 1}, {&StreamChecker::preDefault, nullptr, 0}}, + {{"fgetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}}, + {{"fsetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}}, {{"clearerr", 1}, - std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}}, {{"feof", 1}, - std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {&StreamChecker::preDefault, + std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFEof), + 0}}, {{"ferror", 1}, - std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, - {{"fileno", 1}, - std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {&StreamChecker::preDefault, + std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFError), + 0}}, + {{"fileno", 1}, {&StreamChecker::preDefault, nullptr, 0}}, + }; + + CallDescriptionMap<FnDescription> FnTestDescriptions = { + {{"StreamTesterChecker_make_feof_stream", 1}, + {nullptr, + std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, ErrorFEof), + 0}}, + {{"StreamTesterChecker_make_ferror_stream", 1}, + {nullptr, + std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, + ErrorFError), + 0}}, + }; + + void evalFopen(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void preFreopen(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + void evalFreopen(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void evalFclose(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void preFread(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void preFwrite(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C, bool IsFread) const; + + void preFseek(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + void evalFseek(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void preDefault(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void evalClearerr(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C, + const StreamErrorState &ErrorKind) const; + + void evalSetFeofFerror(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C, + const StreamErrorState &ErrorKind) const; + + /// Check that the stream (in StreamVal) is not NULL. + /// If it can only be NULL a fatal error is emitted and nullptr returned. + /// Otherwise the return value is a new state where the stream is constrained + /// to be non-null. + ProgramStateRef ensureStreamNonNull(SVal StreamVal, CheckerContext &C, + ProgramStateRef State) const; + + /// Check that the stream is the opened state. + /// If the stream is known to be not opened an error is generated + /// and nullptr returned, otherwise the original state is returned. + ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C, + ProgramStateRef State) const; + + /// Check that the stream has not an invalid ("indeterminate") file position, + /// generate warning for it. + /// (EOF is not an invalid position.) + /// The returned state can be nullptr if a fatal error was generated. + /// It can return non-null state if the stream has not an invalid position or + /// there is execution path with non-invalid position. + ProgramStateRef + ensureNoFilePositionIndeterminate(SVal StreamVal, CheckerContext &C, + ProgramStateRef State) const; + + /// Check the legality of the 'whence' argument of 'fseek'. + /// Generate error and return nullptr if it is found to be illegal. + /// Otherwise returns the state. + /// (State is not changed here because the "whence" value is already known.) + ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, + ProgramStateRef State) const; + + /// Generate warning about stream in EOF state. + /// There will be always a state transition into the passed State, + /// by the new non-fatal error node or (if failed) a normal transition, + /// to ensure uniform handling. + void reportFEofWarning(CheckerContext &C, ProgramStateRef State) const; + + /// Find the description data of the function called by a call event. + /// Returns nullptr if no function is recognized. + const FnDescription *lookupFn(const CallEvent &Call) const { + // Recognize "global C functions" with only integral or pointer arguments + // (and matching name) as stream functions. + if (!Call.isGlobalCFunction()) + return nullptr; + for (auto P : Call.parameters()) { + QualType T = P->getType(); + if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) + return nullptr; + } + + return FnDescriptions.lookup(Call); + } + + /// Generate a message for BugReporterVisitor if the stored symbol is + /// marked as interesting by the actual bug report. + struct NoteFn { + const CheckerNameRef CheckerName; + SymbolRef StreamSym; + std::string Message; + + std::string operator()(PathSensitiveBugReport &BR) const { + if (BR.isInteresting(StreamSym) && + CheckerName == BR.getBugType().getCheckerName()) + return Message; + + return ""; + } }; - void evalFopen(const CallEvent &Call, CheckerContext &C) const; - void evalFreopen(const CallEvent &Call, CheckerContext &C) const; - void evalFclose(const CallEvent &Call, CheckerContext &C) const; - void evalFseek(const CallEvent &Call, CheckerContext &C) const; - - void checkArgNullStream(const CallEvent &Call, CheckerContext &C, - unsigned ArgI) const; - bool checkNullStream(SVal SV, CheckerContext &C, - ProgramStateRef &State) const; - void checkFseekWhence(SVal SV, CheckerContext &C, - ProgramStateRef &State) const; - bool checkDoubleClose(const CallEvent &Call, CheckerContext &C, - ProgramStateRef &State) const; + const NoteTag *constructNoteTag(CheckerContext &C, SymbolRef StreamSym, + const std::string &Message) const { + return C.getNoteTag(NoteFn{getCheckerName(), StreamSym, Message}); + } + + /// Searches for the ExplodedNode where the file descriptor was acquired for + /// StreamSym. + static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N, + SymbolRef StreamSym, + CheckerContext &C); }; } // end anonymous namespace REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) +inline void assertStreamStateOpened(const StreamState *SS) { + assert(SS->isOpened() && + "Previous create of error node for non-opened stream failed?"); +} -bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { - const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); - if (!FD || FD->getKind() != Decl::Function) - return false; +const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N, + SymbolRef StreamSym, + CheckerContext &C) { + ProgramStateRef State = N->getState(); + // When bug type is resource leak, exploded node N may not have state info + // for leaked file descriptor, but predecessor should have it. + if (!State->get<StreamMap>(StreamSym)) + N = N->getFirstPred(); - // Recognize "global C functions" with only integral or pointer arguments - // (and matching name) as stream functions. - if (!Call.isGlobalCFunction()) - return false; - for (auto P : Call.parameters()) { - QualType T = P->getType(); - if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) - return false; + const ExplodedNode *Pred = N; + while (N) { + State = N->getState(); + if (!State->get<StreamMap>(StreamSym)) + return Pred; + Pred = N; + N = N->getFirstPred(); } - const FnCheck *Callback = Callbacks.lookup(Call); - if (!Callback) + return nullptr; +} + +void StreamChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + const FnDescription *Desc = lookupFn(Call); + if (!Desc || !Desc->PreFn) + return; + + Desc->PreFn(this, Desc, Call, C); +} + +bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { + const FnDescription *Desc = lookupFn(Call); + if (!Desc && TestMode) + Desc = FnTestDescriptions.lookup(Call); + if (!Desc || !Desc->EvalFn) return false; - (*Callback)(this, Call, C); + Desc->EvalFn(this, Desc, Call, C); return C.isDifferent(); } -void StreamChecker::evalFopen(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef state = C.getState(); - SValBuilder &svalBuilder = C.getSValBuilder(); - const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); - auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); +void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); if (!CE) return; - DefinedSVal RetVal = - svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) - .castAs<DefinedSVal>(); - state = state->BindExpr(CE, C.getLocationContext(), RetVal); + DefinedSVal RetVal = makeRetVal(C, CE); + SymbolRef RetSym = RetVal.getAsSymbol(); + assert(RetSym && "RetVal must be a symbol here."); + + State = State->BindExpr(CE, C.getLocationContext(), RetVal); - ConstraintManager &CM = C.getConstraintManager(); // Bifurcate the state into two: one with a valid FILE* pointer, the other // with a NULL. - ProgramStateRef stateNotNull, stateNull; - std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal); + ProgramStateRef StateNotNull, StateNull; + std::tie(StateNotNull, StateNull) = + C.getConstraintManager().assumeDual(State, RetVal); + + StateNotNull = + StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened(Desc)); + StateNull = + StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed(Desc)); + + C.addTransition(StateNotNull, + constructNoteTag(C, RetSym, "Stream opened here")); + C.addTransition(StateNull); +} - SymbolRef Sym = RetVal.getAsSymbol(); - assert(Sym && "RetVal must be a symbol here."); - stateNotNull = stateNotNull->set<StreamMap>(Sym, StreamState::getOpened()); - stateNull = stateNull->set<StreamMap>(Sym, StreamState::getOpenFailed()); +void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { + // Do not allow NULL as passed stream pointer but allow a closed stream. + ProgramStateRef State = C.getState(); + State = ensureStreamNonNull(getStreamArg(Desc, Call), C, State); + if (!State) + return; - C.addTransition(stateNotNull); - C.addTransition(stateNull); + C.addTransition(State); } -void StreamChecker::evalFreopen(const CallEvent &Call, +void StreamChecker::evalFreopen(const FnDescription *Desc, + const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); @@ -170,21 +481,21 @@ void StreamChecker::evalFreopen(const CallEvent &Call, if (!CE) return; - Optional<DefinedSVal> StreamVal = Call.getArgSVal(2).getAs<DefinedSVal>(); + Optional<DefinedSVal> StreamVal = + getStreamArg(Desc, Call).getAs<DefinedSVal>(); if (!StreamVal) return; - // Do not allow NULL as passed stream pointer. - // This is not specified in the man page but may crash on some system. - checkNullStream(*StreamVal, C, State); - // Check if error was generated. - if (C.isDifferent()) - return; SymbolRef StreamSym = StreamVal->getAsSymbol(); - // Do not care about special values for stream ("(FILE *)0x12345"?). + // Do not care about concrete values for stream ("(FILE *)0x12345"?). + // FIXME: Can be stdin, stdout, stderr such values? if (!StreamSym) return; + // Do not handle untracked stream. It is probably escaped. + if (!State->get<StreamMap>(StreamSym)) + return; + // Generate state for non-failed case. // Return value is the passed stream pointer. // According to the documentations, the stream is closed first @@ -197,129 +508,452 @@ void StreamChecker::evalFreopen(const CallEvent &Call, C.getSValBuilder().makeNull()); StateRetNotNull = - StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened()); + StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); StateRetNull = - StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed()); + StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed(Desc)); - C.addTransition(StateRetNotNull); + C.addTransition(StateRetNotNull, + constructNoteTag(C, StreamSym, "Stream reopened here")); C.addTransition(StateRetNull); } -void StreamChecker::evalFclose(const CallEvent &Call, CheckerContext &C) const { +void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { ProgramStateRef State = C.getState(); - if (checkDoubleClose(Call, C, State)) + SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol(); + if (!Sym) + return; + + const StreamState *SS = State->get<StreamMap>(Sym); + if (!SS) + return; + + assertStreamStateOpened(SS); + + // Close the File Descriptor. + // Regardless if the close fails or not, stream becomes "closed" + // and can not be used any more. + State = State->set<StreamMap>(Sym, StreamState::getClosed(Desc)); + + C.addTransition(State); +} + +void StreamChecker::preFread(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SVal StreamVal = getStreamArg(Desc, Call); + State = ensureStreamNonNull(StreamVal, C, State); + if (!State) + return; + State = ensureStreamOpened(StreamVal, C, State); + if (!State) + return; + State = ensureNoFilePositionIndeterminate(StreamVal, C, State); + if (!State) + return; + + SymbolRef Sym = StreamVal.getAsSymbol(); + if (Sym && State->get<StreamMap>(Sym)) { + const StreamState *SS = State->get<StreamMap>(Sym); + if (SS->ErrorState & ErrorFEof) + reportFEofWarning(C, State); + } else { C.addTransition(State); + } } -void StreamChecker::evalFseek(const CallEvent &Call, CheckerContext &C) const { - const Expr *AE2 = Call.getArgExpr(2); - if (!AE2) +void StreamChecker::preFwrite(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SVal StreamVal = getStreamArg(Desc, Call); + State = ensureStreamNonNull(StreamVal, C, State); + if (!State) return; + State = ensureStreamOpened(StreamVal, C, State); + if (!State) + return; + State = ensureNoFilePositionIndeterminate(StreamVal, C, State); + if (!State) + return; + + C.addTransition(State); +} +void StreamChecker::evalFreadFwrite(const FnDescription *Desc, + const CallEvent &Call, CheckerContext &C, + bool IsFread) const { ProgramStateRef State = C.getState(); + SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); + if (!StreamSym) + return; + + const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return; + + Optional<NonLoc> SizeVal = Call.getArgSVal(1).getAs<NonLoc>(); + if (!SizeVal) + return; + Optional<NonLoc> NMembVal = Call.getArgSVal(2).getAs<NonLoc>(); + if (!NMembVal) + return; - bool StateChanged = checkNullStream(Call.getArgSVal(0), C, State); - // Check if error was generated. - if (C.isDifferent()) + const StreamState *SS = State->get<StreamMap>(StreamSym); + if (!SS) return; - // Check the legality of the 'whence' argument of 'fseek'. - checkFseekWhence(State->getSVal(AE2, C.getLocationContext()), C, State); + assertStreamStateOpened(SS); + + // C'99 standard, §7.19.8.1.3, the return value of fread: + // The fread function returns the number of elements successfully read, which + // may be less than nmemb if a read error or end-of-file is encountered. If + // size or nmemb is zero, fread returns zero and the contents of the array and + // the state of the stream remain unchanged. - if (!C.isDifferent() && StateChanged) + if (State->isNull(*SizeVal).isConstrainedTrue() || + State->isNull(*NMembVal).isConstrainedTrue()) { + // This is the "size or nmemb is zero" case. + // Just return 0, do nothing more (not clear the error flags). + State = bindInt(0, State, C, CE); C.addTransition(State); + return; + } - return; + // Generate a transition for the success state. + // If we know the state to be FEOF at fread, do not add a success state. + if (!IsFread || (SS->ErrorState != ErrorFEof)) { + ProgramStateRef StateNotFailed = + State->BindExpr(CE, C.getLocationContext(), *NMembVal); + if (StateNotFailed) { + StateNotFailed = StateNotFailed->set<StreamMap>( + StreamSym, StreamState::getOpened(Desc)); + C.addTransition(StateNotFailed); + } + } + + // Add transition for the failed state. + Optional<NonLoc> RetVal = makeRetVal(C, CE).castAs<NonLoc>(); + assert(RetVal && "Value should be NonLoc."); + ProgramStateRef StateFailed = + State->BindExpr(CE, C.getLocationContext(), *RetVal); + if (!StateFailed) + return; + auto Cond = C.getSValBuilder() + .evalBinOpNN(State, BO_LT, *RetVal, *NMembVal, + C.getASTContext().IntTy) + .getAs<DefinedOrUnknownSVal>(); + if (!Cond) + return; + StateFailed = StateFailed->assume(*Cond, true); + if (!StateFailed) + return; + + StreamErrorState NewES; + if (IsFread) + NewES = (SS->ErrorState == ErrorFEof) ? ErrorFEof : ErrorFEof | ErrorFError; + else + NewES = ErrorFError; + // If a (non-EOF) error occurs, the resulting value of the file position + // indicator for the stream is indeterminate. + StreamState NewState = StreamState::getOpened(Desc, NewES, !NewES.isFEof()); + StateFailed = StateFailed->set<StreamMap>(StreamSym, NewState); + C.addTransition(StateFailed); } -void StreamChecker::checkArgNullStream(const CallEvent &Call, CheckerContext &C, - unsigned ArgI) const { +void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { ProgramStateRef State = C.getState(); - if (checkNullStream(Call.getArgSVal(ArgI), C, State)) - C.addTransition(State); + SVal StreamVal = getStreamArg(Desc, Call); + State = ensureStreamNonNull(StreamVal, C, State); + if (!State) + return; + State = ensureStreamOpened(StreamVal, C, State); + if (!State) + return; + State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State); + if (!State) + return; + + C.addTransition(State); } -bool StreamChecker::checkNullStream(SVal SV, CheckerContext &C, - ProgramStateRef &State) const { - Optional<DefinedSVal> DV = SV.getAs<DefinedSVal>(); - if (!DV) - return false; +void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); + if (!StreamSym) + return; + + const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return; + + // Ignore the call if the stream is not tracked. + if (!State->get<StreamMap>(StreamSym)) + return; + + DefinedSVal RetVal = makeRetVal(C, CE); + + // Make expression result. + State = State->BindExpr(CE, C.getLocationContext(), RetVal); + + // Bifurcate the state into failed and non-failed. + // Return zero on success, nonzero on error. + ProgramStateRef StateNotFailed, StateFailed; + std::tie(StateFailed, StateNotFailed) = + C.getConstraintManager().assumeDual(State, RetVal); + + // Reset the state to opened with no error. + StateNotFailed = + StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); + // We get error. + // It is possible that fseek fails but sets none of the error flags. + // If fseek failed, assume that the file position becomes indeterminate in any + // case. + StateFailed = StateFailed->set<StreamMap>( + StreamSym, + StreamState::getOpened(Desc, ErrorNone | ErrorFEof | ErrorFError, true)); + + C.addTransition(StateNotFailed); + C.addTransition(StateFailed); +} + +void StreamChecker::evalClearerr(const FnDescription *Desc, + const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); + if (!StreamSym) + return; + + const StreamState *SS = State->get<StreamMap>(StreamSym); + if (!SS) + return; + + assertStreamStateOpened(SS); + + // FilePositionIndeterminate is not cleared. + State = State->set<StreamMap>( + StreamSym, + StreamState::getOpened(Desc, ErrorNone, SS->FilePositionIndeterminate)); + C.addTransition(State); +} + +void StreamChecker::evalFeofFerror(const FnDescription *Desc, + const CallEvent &Call, CheckerContext &C, + const StreamErrorState &ErrorKind) const { + ProgramStateRef State = C.getState(); + SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); + if (!StreamSym) + return; + + const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return; + + const StreamState *SS = State->get<StreamMap>(StreamSym); + if (!SS) + return; + + assertStreamStateOpened(SS); + + if (SS->ErrorState & ErrorKind) { + // Execution path with error of ErrorKind. + // Function returns true. + // From now on it is the only one error state. + ProgramStateRef TrueState = bindAndAssumeTrue(State, C, CE); + C.addTransition(TrueState->set<StreamMap>( + StreamSym, StreamState::getOpened(Desc, ErrorKind, + SS->FilePositionIndeterminate && + !ErrorKind.isFEof()))); + } + if (StreamErrorState NewES = SS->ErrorState & (~ErrorKind)) { + // Execution path(s) with ErrorKind not set. + // Function returns false. + // New error state is everything before minus ErrorKind. + ProgramStateRef FalseState = bindInt(0, State, C, CE); + C.addTransition(FalseState->set<StreamMap>( + StreamSym, + StreamState::getOpened( + Desc, NewES, SS->FilePositionIndeterminate && !NewES.isFEof()))); + } +} + +void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SVal StreamVal = getStreamArg(Desc, Call); + State = ensureStreamNonNull(StreamVal, C, State); + if (!State) + return; + State = ensureStreamOpened(StreamVal, C, State); + if (!State) + return; + + C.addTransition(State); +} + +void StreamChecker::evalSetFeofFerror(const FnDescription *Desc, + const CallEvent &Call, CheckerContext &C, + const StreamErrorState &ErrorKind) const { + ProgramStateRef State = C.getState(); + SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); + assert(StreamSym && "Operation not permitted on non-symbolic stream value."); + const StreamState *SS = State->get<StreamMap>(StreamSym); + assert(SS && "Stream should be tracked by the checker."); + State = State->set<StreamMap>( + StreamSym, StreamState::getOpened(SS->LastOperation, ErrorKind)); + C.addTransition(State); +} + +ProgramStateRef +StreamChecker::ensureStreamNonNull(SVal StreamVal, CheckerContext &C, + ProgramStateRef State) const { + auto Stream = StreamVal.getAs<DefinedSVal>(); + if (!Stream) + return State; ConstraintManager &CM = C.getConstraintManager(); + ProgramStateRef StateNotNull, StateNull; - std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *DV); + std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *Stream); if (!StateNotNull && StateNull) { if (ExplodedNode *N = C.generateErrorNode(StateNull)) { - if (!BT_nullfp) - BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", - "Stream pointer might be NULL.")); C.emitReport(std::make_unique<PathSensitiveBugReport>( - *BT_nullfp, BT_nullfp->getDescription(), N)); + BT_FileNull, "Stream pointer might be NULL.", N)); } - return false; - } - - if (StateNotNull) { - State = StateNotNull; - return true; + return nullptr; } - return false; + return StateNotNull; } -void StreamChecker::checkFseekWhence(SVal SV, CheckerContext &C, - ProgramStateRef &State) const { - Optional<nonloc::ConcreteInt> CI = SV.getAs<nonloc::ConcreteInt>(); - if (!CI) - return; +ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal, + CheckerContext &C, + ProgramStateRef State) const { + SymbolRef Sym = StreamVal.getAsSymbol(); + if (!Sym) + return State; - int64_t X = CI->getValue().getSExtValue(); - if (X >= 0 && X <= 2) - return; + const StreamState *SS = State->get<StreamMap>(Sym); + if (!SS) + return State; - if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { - if (!BT_illegalwhence) - BT_illegalwhence.reset( - new BuiltinBug(this, "Illegal whence argument", - "The whence argument to fseek() should be " - "SEEK_SET, SEEK_END, or SEEK_CUR.")); - C.emitReport(std::make_unique<PathSensitiveBugReport>( - *BT_illegalwhence, BT_illegalwhence->getDescription(), N)); + if (SS->isClosed()) { + // Using a stream pointer after 'fclose' causes undefined behavior + // according to cppreference.com . + ExplodedNode *N = C.generateErrorNode(); + if (N) { + C.emitReport(std::make_unique<PathSensitiveBugReport>( + BT_UseAfterClose, + "Stream might be already closed. Causes undefined behaviour.", N)); + return nullptr; + } + + return State; } + + if (SS->isOpenFailed()) { + // Using a stream that has failed to open is likely to cause problems. + // This should usually not occur because stream pointer is NULL. + // But freopen can cause a state when stream pointer remains non-null but + // failed to open. + ExplodedNode *N = C.generateErrorNode(); + if (N) { + C.emitReport(std::make_unique<PathSensitiveBugReport>( + BT_UseAfterOpenFailed, + "Stream might be invalid after " + "(re-)opening it has failed. " + "Can cause undefined behaviour.", + N)); + return nullptr; + } + return State; + } + + return State; } -bool StreamChecker::checkDoubleClose(const CallEvent &Call, CheckerContext &C, - ProgramStateRef &State) const { - SymbolRef Sym = Call.getArgSVal(0).getAsSymbol(); +ProgramStateRef StreamChecker::ensureNoFilePositionIndeterminate( + SVal StreamVal, CheckerContext &C, ProgramStateRef State) const { + static const char *BugMessage = + "File position of the stream might be 'indeterminate' " + "after a failed operation. " + "Can cause undefined behavior."; + + SymbolRef Sym = StreamVal.getAsSymbol(); if (!Sym) - return false; + return State; const StreamState *SS = State->get<StreamMap>(Sym); - - // If the file stream is not tracked, return. if (!SS) - return false; + return State; + + assert(SS->isOpened() && "First ensure that stream is opened."); + + if (SS->FilePositionIndeterminate) { + if (SS->ErrorState & ErrorFEof) { + // The error is unknown but may be FEOF. + // Continue analysis with the FEOF error state. + // Report warning because the other possible error states. + ExplodedNode *N = C.generateNonFatalErrorNode(State); + if (!N) + return nullptr; - // Check: Double close a File Descriptor could cause undefined behaviour. - // Conforming to man-pages - if (SS->isClosed()) { - ExplodedNode *N = C.generateErrorNode(); - if (N) { - if (!BT_doubleclose) - BT_doubleclose.reset(new BuiltinBug( - this, "Double fclose", "Try to close a file Descriptor already" - " closed. Cause undefined behaviour.")); C.emitReport(std::make_unique<PathSensitiveBugReport>( - *BT_doubleclose, BT_doubleclose->getDescription(), N)); + BT_IndeterminatePosition, BugMessage, N)); + return State->set<StreamMap>( + Sym, StreamState::getOpened(SS->LastOperation, ErrorFEof, false)); } - return false; + + // Known or unknown error state without FEOF possible. + // Stop analysis, report error. + ExplodedNode *N = C.generateErrorNode(State); + if (N) + C.emitReport(std::make_unique<PathSensitiveBugReport>( + BT_IndeterminatePosition, BugMessage, N)); + + return nullptr; } - // Close the File Descriptor. - State = State->set<StreamMap>(Sym, StreamState::getClosed()); + return State; +} - return true; +ProgramStateRef +StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, + ProgramStateRef State) const { + Optional<nonloc::ConcreteInt> CI = WhenceVal.getAs<nonloc::ConcreteInt>(); + if (!CI) + return State; + + int64_t X = CI->getValue().getSExtValue(); + if (X >= 0 && X <= 2) + return State; + + if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { + C.emitReport(std::make_unique<PathSensitiveBugReport>( + BT_IllegalWhence, + "The whence argument to fseek() should be " + "SEEK_SET, SEEK_END, or SEEK_CUR.", + N)); + return nullptr; + } + + return State; +} + +void StreamChecker::reportFEofWarning(CheckerContext &C, + ProgramStateRef State) const { + if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { + C.emitReport(std::make_unique<PathSensitiveBugReport>( + BT_StreamEof, + "Read function called when stream is in EOF state. " + "Function has no effect.", + N)); + return; + } + C.addTransition(State); } void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, @@ -328,7 +962,7 @@ void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, // TODO: Clean up the state. const StreamMapTy &Map = State->get<StreamMap>(); - for (const auto &I: Map) { + for (const auto &I : Map) { SymbolRef Sym = I.first; const StreamState &SS = I.second; if (!SymReaper.isDead(Sym) || !SS.isOpened()) @@ -338,19 +972,77 @@ void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, if (!N) continue; - if (!BT_ResourceLeak) - BT_ResourceLeak.reset( - new BuiltinBug(this, "Resource Leak", - "Opened File never closed. Potential Resource leak.")); - C.emitReport(std::make_unique<PathSensitiveBugReport>( - *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N)); + // Do not warn for non-closed stream at program exit. + ExplodedNode *Pred = C.getPredecessor(); + if (Pred && Pred->getCFGBlock() && + Pred->getCFGBlock()->hasNoReturnElement()) + continue; + + // Resource leaks can result in multiple warning that describe the same kind + // of programming error: + // void f() { + // FILE *F = fopen("a.txt"); + // if (rand()) // state split + // return; // warning + // } // warning + // While this isn't necessarily true (leaking the same stream could result + // from a different kinds of errors), the reduction in redundant reports + // makes this a worthwhile heuristic. + // FIXME: Add a checker option to turn this uniqueing feature off. + + const ExplodedNode *StreamOpenNode = getAcquisitionSite(N, Sym, C); + assert(StreamOpenNode && "Could not find place of stream opening."); + PathDiagnosticLocation LocUsedForUniqueing = + PathDiagnosticLocation::createBegin( + StreamOpenNode->getStmtForDiagnostics(), C.getSourceManager(), + StreamOpenNode->getLocationContext()); + + std::unique_ptr<PathSensitiveBugReport> R = + std::make_unique<PathSensitiveBugReport>( + BT_ResourceLeak, + "Opened stream never closed. Potential resource leak.", N, + LocUsedForUniqueing, + StreamOpenNode->getLocationContext()->getDecl()); + R->markInteresting(Sym); + C.emitReport(std::move(R)); + } +} + +ProgramStateRef StreamChecker::checkPointerEscape( + ProgramStateRef State, const InvalidatedSymbols &Escaped, + const CallEvent *Call, PointerEscapeKind Kind) const { + // Check for file-handling system call that is not handled by the checker. + // FIXME: The checker should be updated to handle all system calls that take + // 'FILE*' argument. These are now ignored. + if (Kind == PSK_DirectEscapeOnCall && Call->isInSystemHeader()) + return State; + + for (SymbolRef Sym : Escaped) { + // The symbol escaped. + // From now the stream can be manipulated in unknown way to the checker, + // it is not possible to handle it any more. + // Optimistically, assume that the corresponding file handle will be closed + // somewhere else. + // Remove symbol from state so the following stream calls on this symbol are + // not handled by the checker. + State = State->remove<StreamMap>(Sym); } + return State; +} + +void ento::registerStreamChecker(CheckerManager &Mgr) { + Mgr.registerChecker<StreamChecker>(); +} + +bool ento::shouldRegisterStreamChecker(const CheckerManager &Mgr) { + return true; } -void ento::registerStreamChecker(CheckerManager &mgr) { - mgr.registerChecker<StreamChecker>(); +void ento::registerStreamTesterChecker(CheckerManager &Mgr) { + auto *Checker = Mgr.getChecker<StreamChecker>(); + Checker->TestMode = true; } -bool ento::shouldRegisterStreamChecker(const LangOptions &LO) { +bool ento::shouldRegisterStreamTesterChecker(const CheckerManager &Mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp index f81705304f3a..916977c10c0c 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp @@ -63,6 +63,6 @@ void ento::registerTaintTesterChecker(CheckerManager &mgr) { mgr.registerChecker<TaintTesterChecker>(); } -bool ento::shouldRegisterTaintTesterChecker(const LangOptions &LO) { +bool ento::shouldRegisterTaintTesterChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp index 3663b0963692..eeec807ccee4 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp @@ -261,6 +261,6 @@ void ento::registerTestAfterDivZeroChecker(CheckerManager &mgr) { mgr.registerChecker<TestAfterDivZeroChecker>(); } -bool ento::shouldRegisterTestAfterDivZeroChecker(const LangOptions &LO) { +bool ento::shouldRegisterTestAfterDivZeroChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp index 73183aa468f6..2f316bd3b20d 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp @@ -64,7 +64,7 @@ void ento::registerTraversalDumper(CheckerManager &mgr) { mgr.registerChecker<TraversalDumper>(); } -bool ento::shouldRegisterTraversalDumper(const LangOptions &LO) { +bool ento::shouldRegisterTraversalDumper(const CheckerManager &mgr) { return true; } @@ -116,6 +116,6 @@ void ento::registerCallDumper(CheckerManager &mgr) { mgr.registerChecker<CallDumper>(); } -bool ento::shouldRegisterCallDumper(const LangOptions &LO) { +bool ento::shouldRegisterCallDumper(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp index 62a4c2ab0209..5cc713172527 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp @@ -252,6 +252,6 @@ void ento::registerTrustNonnullChecker(CheckerManager &Mgr) { Mgr.registerChecker<TrustNonnullChecker>(Mgr.getASTContext()); } -bool ento::shouldRegisterTrustNonnullChecker(const LangOptions &LO) { +bool ento::shouldRegisterTrustNonnullChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp index 247cba7dc933..3e0caaf79ca0 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp @@ -110,6 +110,6 @@ void ento::registerUndefBranchChecker(CheckerManager &mgr) { mgr.registerChecker<UndefBranchChecker>(); } -bool ento::shouldRegisterUndefBranchChecker(const LangOptions &LO) { +bool ento::shouldRegisterUndefBranchChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp index 7b581bef3900..e457513d8de4 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp @@ -101,6 +101,6 @@ void ento::registerUndefCapturedBlockVarChecker(CheckerManager &mgr) { mgr.registerChecker<UndefCapturedBlockVarChecker>(); } -bool ento::shouldRegisterUndefCapturedBlockVarChecker(const LangOptions &LO) { +bool ento::shouldRegisterUndefCapturedBlockVarChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp index a2f3e0da13fb..392da4818098 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp @@ -16,6 +16,7 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/raw_ostream.h" @@ -50,10 +51,10 @@ static bool isArrayIndexOutOfBounds(CheckerContext &C, const Expr *Ex) { return false; DefinedOrUnknownSVal Idx = ER->getIndex().castAs<DefinedOrUnknownSVal>(); - DefinedOrUnknownSVal NumElements = C.getStoreManager().getSizeInElements( - state, ER->getSuperRegion(), ER->getValueType()); - ProgramStateRef StInBound = state->assumeInBound(Idx, NumElements, true); - ProgramStateRef StOutBound = state->assumeInBound(Idx, NumElements, false); + DefinedOrUnknownSVal ElementCount = getDynamicElementCount( + state, ER->getSuperRegion(), C.getSValBuilder(), ER->getValueType()); + ProgramStateRef StInBound = state->assumeInBound(Idx, ElementCount, true); + ProgramStateRef StOutBound = state->assumeInBound(Idx, ElementCount, false); return StOutBound && !StInBound; } @@ -186,6 +187,6 @@ void ento::registerUndefResultChecker(CheckerManager &mgr) { mgr.registerChecker<UndefResultChecker>(); } -bool ento::shouldRegisterUndefResultChecker(const LangOptions &LO) { +bool ento::shouldRegisterUndefResultChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp index 2f075eaeb03b..fdefe75e8201 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp @@ -62,6 +62,6 @@ void ento::registerUndefinedArraySubscriptChecker(CheckerManager &mgr) { mgr.registerChecker<UndefinedArraySubscriptChecker>(); } -bool ento::shouldRegisterUndefinedArraySubscriptChecker(const LangOptions &LO) { +bool ento::shouldRegisterUndefinedArraySubscriptChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp index 277a8a143328..05f8f6084c0b 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp @@ -120,6 +120,6 @@ void ento::registerUndefinedAssignmentChecker(CheckerManager &mgr) { mgr.registerChecker<UndefinedAssignmentChecker>(); } -bool ento::shouldRegisterUndefinedAssignmentChecker(const LangOptions &LO) { +bool ento::shouldRegisterUndefinedAssignmentChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp index 020df8a1bb8c..4182b51c02b0 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp @@ -541,14 +541,11 @@ static bool hasUnguardedAccess(const FieldDecl *FD, ProgramStateRef State) { auto FieldAccessM = memberExpr(hasDeclaration(equalsNode(FD))).bind("access"); auto AssertLikeM = callExpr(callee(functionDecl( - anyOf(hasName("exit"), hasName("panic"), hasName("error"), - hasName("Assert"), hasName("assert"), hasName("ziperr"), - hasName("assfail"), hasName("db_error"), hasName("__assert"), - hasName("__assert2"), hasName("_wassert"), hasName("__assert_rtn"), - hasName("__assert_fail"), hasName("dtrace_assfail"), - hasName("yy_fatal_error"), hasName("_XCAssertionFailureHandler"), - hasName("_DTAssertionFailureHandler"), - hasName("_TSAssertionFailureHandler"))))); + hasAnyName("exit", "panic", "error", "Assert", "assert", "ziperr", + "assfail", "db_error", "__assert", "__assert2", "_wassert", + "__assert_rtn", "__assert_fail", "dtrace_assfail", + "yy_fatal_error", "_XCAssertionFailureHandler", + "_DTAssertionFailureHandler", "_TSAssertionFailureHandler")))); auto NoReturnFuncM = callExpr(callee(functionDecl(isNoReturn()))); @@ -602,13 +599,13 @@ std::string clang::ento::getVariableName(const FieldDecl *Field) { llvm_unreachable("No other capture type is expected!"); } - return Field->getName(); + return std::string(Field->getName()); } void ento::registerUninitializedObjectChecker(CheckerManager &Mgr) { auto Chk = Mgr.registerChecker<UninitializedObjectChecker>(); - AnalyzerOptions &AnOpts = Mgr.getAnalyzerOptions(); + const AnalyzerOptions &AnOpts = Mgr.getAnalyzerOptions(); UninitObjCheckerOptions &ChOpts = Chk->Opts; ChOpts.IsPedantic = AnOpts.getCheckerBooleanOption(Chk, "Pedantic"); @@ -617,7 +614,7 @@ void ento::registerUninitializedObjectChecker(CheckerManager &Mgr) { ChOpts.CheckPointeeInitialization = AnOpts.getCheckerBooleanOption( Chk, "CheckPointeeInitialization"); ChOpts.IgnoredRecordsWithFieldPattern = - AnOpts.getCheckerStringOption(Chk, "IgnoreRecordsWithField"); + std::string(AnOpts.getCheckerStringOption(Chk, "IgnoreRecordsWithField")); ChOpts.IgnoreGuardedFields = AnOpts.getCheckerBooleanOption(Chk, "IgnoreGuardedFields"); @@ -628,6 +625,6 @@ void ento::registerUninitializedObjectChecker(CheckerManager &Mgr) { "\"" + ErrorMsg + "\""); } -bool ento::shouldRegisterUninitializedObjectChecker(const LangOptions &LO) { +bool ento::shouldRegisterUninitializedObjectChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp index f4e225d836f3..381334de068e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp @@ -20,6 +20,7 @@ #include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/Support/raw_ostream.h" using namespace clang; @@ -503,7 +504,7 @@ void UnixAPIPortabilityChecker::checkPreStmt(const CallExpr *CE, mgr.registerChecker<CHECKERNAME>(); \ } \ \ - bool ento::shouldRegister##CHECKERNAME(const LangOptions &LO) { \ + bool ento::shouldRegister##CHECKERNAME(const CheckerManager &mgr) { \ return true; \ } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp index 65dd82675df9..74eec81ffb3e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp @@ -257,6 +257,6 @@ void ento::registerUnreachableCodeChecker(CheckerManager &mgr) { mgr.registerChecker<UnreachableCodeChecker>(); } -bool ento::shouldRegisterUnreachableCodeChecker(const LangOptions &LO) { +bool ento::shouldRegisterUnreachableCodeChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp index b92757312dc6..d76b2a06aba5 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp @@ -14,12 +14,13 @@ //===----------------------------------------------------------------------===// #include "Taint.h" -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/CharUnits.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/raw_ostream.h" @@ -29,9 +30,28 @@ using namespace ento; using namespace taint; namespace { -class VLASizeChecker : public Checker< check::PreStmt<DeclStmt> > { +class VLASizeChecker + : public Checker<check::PreStmt<DeclStmt>, + check::PreStmt<UnaryExprOrTypeTraitExpr>> { mutable std::unique_ptr<BugType> BT; - enum VLASize_Kind { VLA_Garbage, VLA_Zero, VLA_Tainted, VLA_Negative }; + enum VLASize_Kind { + VLA_Garbage, + VLA_Zero, + VLA_Tainted, + VLA_Negative, + VLA_Overflow + }; + + /// Check a VLA for validity. + /// Every dimension of the array and the total size is checked for validity. + /// Returns null or a new state where the size is validated. + /// 'ArraySize' will contain SVal that refers to the total size (in char) + /// of the array. + ProgramStateRef checkVLA(CheckerContext &C, ProgramStateRef State, + const VariableArrayType *VLA, SVal &ArraySize) const; + /// Check a single VLA index size expression for validity. + ProgramStateRef checkVLAIndexSize(CheckerContext &C, ProgramStateRef State, + const Expr *SizeE) const; void reportBug(VLASize_Kind Kind, const Expr *SizeE, ProgramStateRef State, CheckerContext &C, @@ -39,9 +59,155 @@ class VLASizeChecker : public Checker< check::PreStmt<DeclStmt> > { public: void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const; + void checkPreStmt(const UnaryExprOrTypeTraitExpr *UETTE, + CheckerContext &C) const; }; } // end anonymous namespace +ProgramStateRef VLASizeChecker::checkVLA(CheckerContext &C, + ProgramStateRef State, + const VariableArrayType *VLA, + SVal &ArraySize) const { + assert(VLA && "Function should be called with non-null VLA argument."); + + const VariableArrayType *VLALast = nullptr; + llvm::SmallVector<const Expr *, 2> VLASizes; + + // Walk over the VLAs for every dimension until a non-VLA is found. + // There is a VariableArrayType for every dimension (fixed or variable) until + // the most inner array that is variably modified. + // Dimension sizes are collected into 'VLASizes'. 'VLALast' is set to the + // innermost VLA that was encountered. + // In "int vla[x][2][y][3]" this will be the array for index "y" (with type + // int[3]). 'VLASizes' contains 'x', '2', and 'y'. + while (VLA) { + const Expr *SizeE = VLA->getSizeExpr(); + State = checkVLAIndexSize(C, State, SizeE); + if (!State) + return nullptr; + VLASizes.push_back(SizeE); + VLALast = VLA; + VLA = C.getASTContext().getAsVariableArrayType(VLA->getElementType()); + }; + assert(VLALast && + "Array should have at least one variably-modified dimension."); + + ASTContext &Ctx = C.getASTContext(); + SValBuilder &SVB = C.getSValBuilder(); + CanQualType SizeTy = Ctx.getSizeType(); + uint64_t SizeMax = + SVB.getBasicValueFactory().getMaxValue(SizeTy).getZExtValue(); + + // Get the element size. + CharUnits EleSize = Ctx.getTypeSizeInChars(VLALast->getElementType()); + NonLoc ArrSize = + SVB.makeIntVal(EleSize.getQuantity(), SizeTy).castAs<NonLoc>(); + + // Try to calculate the known real size of the array in KnownSize. + uint64_t KnownSize = 0; + if (const llvm::APSInt *KV = SVB.getKnownValue(State, ArrSize)) + KnownSize = KV->getZExtValue(); + + for (const Expr *SizeE : VLASizes) { + auto SizeD = C.getSVal(SizeE).castAs<DefinedSVal>(); + // Convert the array length to size_t. + NonLoc IndexLength = + SVB.evalCast(SizeD, SizeTy, SizeE->getType()).castAs<NonLoc>(); + // Multiply the array length by the element size. + SVal Mul = SVB.evalBinOpNN(State, BO_Mul, ArrSize, IndexLength, SizeTy); + if (auto MulNonLoc = Mul.getAs<NonLoc>()) + ArrSize = *MulNonLoc; + else + // Extent could not be determined. + return State; + + if (const llvm::APSInt *IndexLVal = SVB.getKnownValue(State, IndexLength)) { + // Check if the array size will overflow. + // Size overflow check does not work with symbolic expressions because a + // overflow situation can not be detected easily. + uint64_t IndexL = IndexLVal->getZExtValue(); + // FIXME: See https://reviews.llvm.org/D80903 for discussion of + // some difference in assume and getKnownValue that leads to + // unexpected behavior. Just bail on IndexL == 0 at this point. + if (IndexL == 0) + return nullptr; + + if (KnownSize <= SizeMax / IndexL) { + KnownSize *= IndexL; + } else { + // Array size does not fit into size_t. + reportBug(VLA_Overflow, SizeE, State, C); + return nullptr; + } + } else { + KnownSize = 0; + } + } + + ArraySize = ArrSize; + + return State; +} + +ProgramStateRef VLASizeChecker::checkVLAIndexSize(CheckerContext &C, + ProgramStateRef State, + const Expr *SizeE) const { + SVal SizeV = C.getSVal(SizeE); + + if (SizeV.isUndef()) { + reportBug(VLA_Garbage, SizeE, State, C); + return nullptr; + } + + // See if the size value is known. It can't be undefined because we would have + // warned about that already. + if (SizeV.isUnknown()) + return nullptr; + + // Check if the size is tainted. + if (isTainted(State, SizeV)) { + reportBug(VLA_Tainted, SizeE, nullptr, C, + std::make_unique<TaintBugVisitor>(SizeV)); + return nullptr; + } + + // Check if the size is zero. + DefinedSVal SizeD = SizeV.castAs<DefinedSVal>(); + + ProgramStateRef StateNotZero, StateZero; + std::tie(StateNotZero, StateZero) = State->assume(SizeD); + + if (StateZero && !StateNotZero) { + reportBug(VLA_Zero, SizeE, StateZero, C); + return nullptr; + } + + // From this point on, assume that the size is not zero. + State = StateNotZero; + + // Check if the size is negative. + SValBuilder &SVB = C.getSValBuilder(); + + QualType SizeTy = SizeE->getType(); + DefinedOrUnknownSVal Zero = SVB.makeZeroVal(SizeTy); + + SVal LessThanZeroVal = SVB.evalBinOp(State, BO_LT, SizeD, Zero, SizeTy); + if (Optional<DefinedSVal> LessThanZeroDVal = + LessThanZeroVal.getAs<DefinedSVal>()) { + ConstraintManager &CM = C.getConstraintManager(); + ProgramStateRef StatePos, StateNeg; + + std::tie(StateNeg, StatePos) = CM.assumeDual(State, *LessThanZeroDVal); + if (StateNeg && !StatePos) { + reportBug(VLA_Negative, SizeE, State, C); + return nullptr; + } + State = StatePos; + } + + return State; +} + void VLASizeChecker::reportBug( VLASize_Kind Kind, const Expr *SizeE, ProgramStateRef State, CheckerContext &C, std::unique_ptr<BugReporterVisitor> Visitor) const { @@ -70,6 +236,9 @@ void VLASizeChecker::reportBug( case VLA_Negative: os << "has negative size"; break; + case VLA_Overflow: + os << "has too large size"; + break; } auto report = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N); @@ -83,108 +252,89 @@ void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { if (!DS->isSingleDecl()) return; - const VarDecl *VD = dyn_cast<VarDecl>(DS->getSingleDecl()); - if (!VD) - return; - ASTContext &Ctx = C.getASTContext(); - const VariableArrayType *VLA = Ctx.getAsVariableArrayType(VD->getType()); - if (!VLA) - return; + SValBuilder &SVB = C.getSValBuilder(); + ProgramStateRef State = C.getState(); + QualType TypeToCheck; - // FIXME: Handle multi-dimensional VLAs. - const Expr *SE = VLA->getSizeExpr(); - ProgramStateRef state = C.getState(); - SVal sizeV = C.getSVal(SE); + const VarDecl *VD = dyn_cast<VarDecl>(DS->getSingleDecl()); - if (sizeV.isUndef()) { - reportBug(VLA_Garbage, SE, state, C); + if (VD) + TypeToCheck = VD->getType().getCanonicalType(); + else if (const auto *TND = dyn_cast<TypedefNameDecl>(DS->getSingleDecl())) + TypeToCheck = TND->getUnderlyingType().getCanonicalType(); + else return; - } - // See if the size value is known. It can't be undefined because we would have - // warned about that already. - if (sizeV.isUnknown()) + const VariableArrayType *VLA = Ctx.getAsVariableArrayType(TypeToCheck); + if (!VLA) return; - // Check if the size is tainted. - if (isTainted(state, sizeV)) { - reportBug(VLA_Tainted, SE, nullptr, C, - std::make_unique<TaintBugVisitor>(sizeV)); - return; - } + // Check the VLA sizes for validity. - // Check if the size is zero. - DefinedSVal sizeD = sizeV.castAs<DefinedSVal>(); + SVal ArraySize; - ProgramStateRef stateNotZero, stateZero; - std::tie(stateNotZero, stateZero) = state->assume(sizeD); + State = checkVLA(C, State, VLA, ArraySize); + if (!State) + return; - if (stateZero && !stateNotZero) { - reportBug(VLA_Zero, SE, stateZero, C); + auto ArraySizeNL = ArraySize.getAs<NonLoc>(); + if (!ArraySizeNL) { + // Array size could not be determined but state may contain new assumptions. + C.addTransition(State); return; } - // From this point on, assume that the size is not zero. - state = stateNotZero; - // VLASizeChecker is responsible for defining the extent of the array being // declared. We do this by multiplying the array length by the element size, // then matching that with the array region's extent symbol. - // Check if the size is negative. - SValBuilder &svalBuilder = C.getSValBuilder(); + if (VD) { + // Assume that the array's size matches the region size. + const LocationContext *LC = C.getLocationContext(); + DefinedOrUnknownSVal DynSize = + getDynamicSize(State, State->getRegion(VD, LC), SVB); - QualType Ty = SE->getType(); - DefinedOrUnknownSVal Zero = svalBuilder.makeZeroVal(Ty); + DefinedOrUnknownSVal SizeIsKnown = SVB.evalEQ(State, DynSize, *ArraySizeNL); + State = State->assume(SizeIsKnown, true); - SVal LessThanZeroVal = svalBuilder.evalBinOp(state, BO_LT, sizeD, Zero, Ty); - if (Optional<DefinedSVal> LessThanZeroDVal = - LessThanZeroVal.getAs<DefinedSVal>()) { - ConstraintManager &CM = C.getConstraintManager(); - ProgramStateRef StatePos, StateNeg; - - std::tie(StateNeg, StatePos) = CM.assumeDual(state, *LessThanZeroDVal); - if (StateNeg && !StatePos) { - reportBug(VLA_Negative, SE, state, C); - return; - } - state = StatePos; + // Assume should not fail at this point. + assert(State); } - // Convert the array length to size_t. - QualType SizeTy = Ctx.getSizeType(); - NonLoc ArrayLength = - svalBuilder.evalCast(sizeD, SizeTy, SE->getType()).castAs<NonLoc>(); + // Remember our assumptions! + C.addTransition(State); +} - // Get the element size. - CharUnits EleSize = Ctx.getTypeSizeInChars(VLA->getElementType()); - SVal EleSizeVal = svalBuilder.makeIntVal(EleSize.getQuantity(), SizeTy); +void VLASizeChecker::checkPreStmt(const UnaryExprOrTypeTraitExpr *UETTE, + CheckerContext &C) const { + // Want to check for sizeof. + if (UETTE->getKind() != UETT_SizeOf) + return; - // Multiply the array length by the element size. - SVal ArraySizeVal = svalBuilder.evalBinOpNN( - state, BO_Mul, ArrayLength, EleSizeVal.castAs<NonLoc>(), SizeTy); + // Ensure a type argument. + if (!UETTE->isArgumentType()) + return; - // Finally, assume that the array's extent matches the given size. - const LocationContext *LC = C.getLocationContext(); - DefinedOrUnknownSVal Extent = - state->getRegion(VD, LC)->getExtent(svalBuilder); - DefinedOrUnknownSVal ArraySize = ArraySizeVal.castAs<DefinedOrUnknownSVal>(); - DefinedOrUnknownSVal sizeIsKnown = - svalBuilder.evalEQ(state, Extent, ArraySize); - state = state->assume(sizeIsKnown, true); + const VariableArrayType *VLA = C.getASTContext().getAsVariableArrayType( + UETTE->getTypeOfArgument().getCanonicalType()); + // Ensure that the type is a VLA. + if (!VLA) + return; - // Assume should not fail at this point. - assert(state); + ProgramStateRef State = C.getState(); + SVal ArraySize; + State = checkVLA(C, State, VLA, ArraySize); + if (!State) + return; - // Remember our assumptions! - C.addTransition(state); + C.addTransition(State); } void ento::registerVLASizeChecker(CheckerManager &mgr) { mgr.registerChecker<VLASizeChecker>(); } -bool ento::shouldRegisterVLASizeChecker(const LangOptions &LO) { +bool ento::shouldRegisterVLASizeChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp index a3610514a924..dde5912b6d6e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp @@ -404,7 +404,7 @@ void ento::registerValistBase(CheckerManager &mgr) { mgr.registerChecker<ValistChecker>(); } -bool ento::shouldRegisterValistBase(const LangOptions &LO) { +bool ento::shouldRegisterValistBase(const CheckerManager &mgr) { return true; } @@ -416,7 +416,7 @@ bool ento::shouldRegisterValistBase(const LangOptions &LO) { mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name##Checker(const LangOptions &LO) { \ + bool ento::shouldRegister##name##Checker(const CheckerManager &mgr) { \ return true; \ } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp index 6724eead5072..8f147026ae19 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp @@ -98,12 +98,13 @@ bool VforkChecker::isCallWhitelisted(const IdentifierInfo *II, if (VforkWhitelist.empty()) { // According to manpage. const char *ids[] = { - "_exit", "_Exit", + "_exit", "execl", - "execlp", "execle", + "execlp", "execv", + "execve", "execvp", "execvpe", nullptr @@ -216,6 +217,6 @@ void ento::registerVforkChecker(CheckerManager &mgr) { mgr.registerChecker<VforkChecker>(); } -bool ento::shouldRegisterVforkChecker(const LangOptions &LO) { +bool ento::shouldRegisterVforkChecker(const CheckerManager &mgr) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp index fd93fc33115f..f49ee5fa5ad3 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -224,14 +224,17 @@ void ento::registerVirtualCallChecker(CheckerManager &Mgr) { } } -bool ento::shouldRegisterVirtualCallModeling(const LangOptions &LO) { +bool ento::shouldRegisterVirtualCallModeling(const CheckerManager &mgr) { + const LangOptions &LO = mgr.getLangOpts(); return LO.CPlusPlus; } -bool ento::shouldRegisterPureVirtualCallChecker(const LangOptions &LO) { +bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &mgr) { + const LangOptions &LO = mgr.getLangOpts(); return LO.CPlusPlus; } -bool ento::shouldRegisterVirtualCallChecker(const LangOptions &LO) { +bool ento::shouldRegisterVirtualCallChecker(const CheckerManager &mgr) { + const LangOptions &LO = mgr.getLangOpts(); return LO.CPlusPlus; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp new file mode 100644 index 000000000000..34c072ac2241 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp @@ -0,0 +1,93 @@ +//=======- ASTUtils.cpp ------------------------------------------*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ASTUtils.h" +#include "PtrTypesSemantics.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/ExprCXX.h" + +using llvm::Optional; +namespace clang { + +std::pair<const Expr *, bool> +tryToFindPtrOrigin(const Expr *E, bool StopAtFirstRefCountedObj) { + while (E) { + if (auto *cast = dyn_cast<CastExpr>(E)) { + if (StopAtFirstRefCountedObj) { + if (auto *ConversionFunc = + dyn_cast_or_null<FunctionDecl>(cast->getConversionFunction())) { + if (isCtorOfRefCounted(ConversionFunc)) + return {E, true}; + } + } + // FIXME: This can give false "origin" that would lead to false negatives + // in checkers. See https://reviews.llvm.org/D37023 for reference. + E = cast->getSubExpr(); + continue; + } + if (auto *call = dyn_cast<CallExpr>(E)) { + if (auto *memberCall = dyn_cast<CXXMemberCallExpr>(call)) { + if (isGetterOfRefCounted(memberCall->getMethodDecl())) { + E = memberCall->getImplicitObjectArgument(); + if (StopAtFirstRefCountedObj) { + return {E, true}; + } + continue; + } + } + + if (auto *operatorCall = dyn_cast<CXXOperatorCallExpr>(E)) { + if (operatorCall->getNumArgs() == 1) { + E = operatorCall->getArg(0); + continue; + } + } + + if (auto *callee = call->getDirectCallee()) { + if (isCtorOfRefCounted(callee)) { + if (StopAtFirstRefCountedObj) + return {E, true}; + + E = call->getArg(0); + continue; + } + + if (isPtrConversion(callee)) { + E = call->getArg(0); + continue; + } + } + } + if (auto *unaryOp = dyn_cast<UnaryOperator>(E)) { + // FIXME: Currently accepts ANY unary operator. Is it OK? + E = unaryOp->getSubExpr(); + continue; + } + + break; + } + // Some other expression. + return {E, false}; +} + +bool isASafeCallArg(const Expr *E) { + assert(E); + if (auto *Ref = dyn_cast<DeclRefExpr>(E)) { + if (auto *D = dyn_cast_or_null<VarDecl>(Ref->getFoundDecl())) { + if (isa<ParmVarDecl>(D) || D->isLocalVarDecl()) + return true; + } + } + + // TODO: checker for method calls on non-refcounted objects + return isa<CXXThisExpr>(E); +} + +} // namespace clang diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h new file mode 100644 index 000000000000..ed4577755457 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h @@ -0,0 +1,84 @@ +//=======- ASTUtis.h ---------------------------------------------*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYZER_WEBKIT_ASTUTILS_H +#define LLVM_CLANG_ANALYZER_WEBKIT_ASTUTILS_H + +#include "clang/AST/Decl.h" +#include "llvm/ADT/APInt.h" +#include "llvm/Support/Casting.h" + +#include <string> +#include <utility> + +namespace clang { +class CXXRecordDecl; +class CXXBaseSpecifier; +class FunctionDecl; +class CXXMethodDecl; +class Expr; + +/// This function de-facto defines a set of transformations that we consider +/// safe (in heuristical sense). These transformation if passed a safe value as +/// an input should provide a safe value (or an object that provides safe +/// values). +/// +/// For more context see Static Analyzer checkers documentation - specifically +/// webkit.UncountedCallArgsChecker checker. Whitelist of transformations: +/// - constructors of ref-counted types (including factory methods) +/// - getters of ref-counted types +/// - member overloaded operators +/// - casts +/// - unary operators like ``&`` or ``*`` +/// +/// If passed expression is of type uncounted pointer/reference we try to find +/// the "origin" of the pointer value. +/// Origin can be for example a local variable, nullptr, constant or +/// this-pointer. +/// +/// Certain subexpression nodes represent transformations that don't affect +/// where the memory address originates from. We try to traverse such +/// subexpressions to get to the relevant child nodes. Whenever we encounter a +/// subexpression that either can't be ignored, we don't model its semantics or +/// that has multiple children we stop. +/// +/// \p E is an expression of uncounted pointer/reference type. +/// If \p StopAtFirstRefCountedObj is true and we encounter a subexpression that +/// represents ref-counted object during the traversal we return relevant +/// sub-expression and true. +/// +/// \returns subexpression that we traversed to and if \p +/// StopAtFirstRefCountedObj is true we also return whether we stopped early. +std::pair<const clang::Expr *, bool> +tryToFindPtrOrigin(const clang::Expr *E, bool StopAtFirstRefCountedObj); + +/// For \p E referring to a ref-countable/-counted pointer/reference we return +/// whether it's a safe call argument. Examples: function parameter or +/// this-pointer. The logic relies on the set of recursive rules we enforce for +/// WebKit codebase. +/// +/// \returns Whether \p E is a safe call arugment. +bool isASafeCallArg(const clang::Expr *E); + +/// \returns name of AST node or empty string. +template <typename T> std::string safeGetName(const T *ASTNode) { + const auto *const ND = llvm::dyn_cast_or_null<clang::NamedDecl>(ASTNode); + if (!ND) + return ""; + + // In case F is for example "operator|" the getName() method below would + // assert. + if (!ND->getDeclName().isIdentifier()) + return ""; + + return ND->getName().str(); +} + +} // namespace clang + +#endif diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/DiagOutputUtils.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/DiagOutputUtils.h new file mode 100644 index 000000000000..781a8d746001 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/DiagOutputUtils.h @@ -0,0 +1,36 @@ +//=======- DiagOutputUtils.h -------------------------------------*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYZER_WEBKIT_DIAGPRINTUTILS_H +#define LLVM_CLANG_ANALYZER_WEBKIT_DIAGPRINTUTILS_H + +#include "clang/AST/Decl.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { + +template <typename NamedDeclDerivedT> +void printQuotedQualifiedName(llvm::raw_ostream &Os, + const NamedDeclDerivedT &D) { + Os << "'"; + D->getNameForDiagnostic(Os, D->getASTContext().getPrintingPolicy(), + /*Qualified=*/true); + Os << "'"; +} + +template <typename NamedDeclDerivedT> +void printQuotedName(llvm::raw_ostream &Os, const NamedDeclDerivedT &D) { + Os << "'"; + D->getNameForDiagnostic(Os, D->getASTContext().getPrintingPolicy(), + /*Qualified=*/false); + Os << "'"; +} + +} // namespace clang + +#endif diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/NoUncountedMembersChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/NoUncountedMembersChecker.cpp new file mode 100644 index 000000000000..3956db933b35 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/NoUncountedMembersChecker.cpp @@ -0,0 +1,155 @@ +//=======- NoUncountedMembersChecker.cpp -------------------------*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ASTUtils.h" +#include "DiagOutputUtils.h" +#include "PtrTypesSemantics.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/Support/Casting.h" + +using namespace clang; +using namespace ento; + +namespace { + +class NoUncountedMemberChecker + : public Checker<check::ASTDecl<TranslationUnitDecl>> { +private: + BugType Bug; + mutable BugReporter *BR; + +public: + NoUncountedMemberChecker() + : Bug(this, + "Member variable is a raw-poiner/reference to reference-countable " + "type", + "WebKit coding guidelines") {} + + void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, + BugReporter &BRArg) const { + BR = &BRArg; + + // The calls to checkAST* from AnalysisConsumer don't + // visit template instantiations or lambda classes. We + // want to visit those, so we make our own RecursiveASTVisitor. + struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> { + const NoUncountedMemberChecker *Checker; + explicit LocalVisitor(const NoUncountedMemberChecker *Checker) + : Checker(Checker) { + assert(Checker); + } + + bool shouldVisitTemplateInstantiations() const { return true; } + bool shouldVisitImplicitCode() const { return false; } + + bool VisitRecordDecl(const RecordDecl *RD) { + Checker->visitRecordDecl(RD); + return true; + } + }; + + LocalVisitor visitor(this); + visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD)); + } + + void visitRecordDecl(const RecordDecl *RD) const { + if (shouldSkipDecl(RD)) + return; + + for (auto Member : RD->fields()) { + const Type *MemberType = Member->getType().getTypePtrOrNull(); + if (!MemberType) + continue; + + if (auto *MemberCXXRD = MemberType->getPointeeCXXRecordDecl()) { + // If we don't see the definition we just don't know. + if (MemberCXXRD->hasDefinition() && isRefCountable(MemberCXXRD)) + reportBug(Member, MemberType, MemberCXXRD, RD); + } + } + } + + bool shouldSkipDecl(const RecordDecl *RD) const { + if (!RD->isThisDeclarationADefinition()) + return true; + + if (RD->isImplicit()) + return true; + + if (RD->isLambda()) + return true; + + // If the construct doesn't have a source file, then it's not something + // we want to diagnose. + const auto RDLocation = RD->getLocation(); + if (!RDLocation.isValid()) + return true; + + const auto Kind = RD->getTagKind(); + // FIMXE: Should we check union members too? + if (Kind != TTK_Struct && Kind != TTK_Class) + return true; + + // Ignore CXXRecords that come from system headers. + if (BR->getSourceManager().isInSystemHeader(RDLocation)) + return true; + + // Ref-counted smartpointers actually have raw-pointer to uncounted type as + // a member but we trust them to handle it correctly. + auto CXXRD = llvm::dyn_cast_or_null<CXXRecordDecl>(RD); + if (CXXRD) + return isRefCounted(CXXRD); + + return false; + } + + void reportBug(const FieldDecl *Member, const Type *MemberType, + const CXXRecordDecl *MemberCXXRD, + const RecordDecl *ClassCXXRD) const { + assert(Member); + assert(MemberType); + assert(MemberCXXRD); + + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + + Os << "Member variable "; + printQuotedName(Os, Member); + Os << " in "; + printQuotedQualifiedName(Os, ClassCXXRD); + Os << " is a " + << (isa<PointerType>(MemberType) ? "raw pointer" : "reference") + << " to ref-countable type "; + printQuotedQualifiedName(Os, MemberCXXRD); + Os << "; member variables must be ref-counted."; + + PathDiagnosticLocation BSLoc(Member->getSourceRange().getBegin(), + BR->getSourceManager()); + auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc); + Report->addRange(Member->getSourceRange()); + BR->emitReport(std::move(Report)); + } +}; +} // namespace + +void ento::registerNoUncountedMemberChecker(CheckerManager &Mgr) { + Mgr.registerChecker<NoUncountedMemberChecker>(); +} + +bool ento::shouldRegisterNoUncountedMemberChecker( + const CheckerManager &Mgr) { + return true; +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp new file mode 100644 index 000000000000..168cfd511170 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -0,0 +1,172 @@ +//=======- PtrTypesSemantics.cpp ---------------------------------*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "PtrTypesSemantics.h" +#include "ASTUtils.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/ExprCXX.h" + +using llvm::Optional; +using namespace clang; + +namespace { + +bool hasPublicRefAndDeref(const CXXRecordDecl *R) { + assert(R); + + bool hasRef = false; + bool hasDeref = false; + for (const CXXMethodDecl *MD : R->methods()) { + const auto MethodName = safeGetName(MD); + + if (MethodName == "ref" && MD->getAccess() == AS_public) { + if (hasDeref) + return true; + hasRef = true; + } else if (MethodName == "deref" && MD->getAccess() == AS_public) { + if (hasRef) + return true; + hasDeref = true; + } + } + return false; +} + +} // namespace + +namespace clang { + +const CXXRecordDecl *isRefCountable(const CXXBaseSpecifier *Base) { + assert(Base); + + const Type *T = Base->getType().getTypePtrOrNull(); + if (!T) + return nullptr; + + const CXXRecordDecl *R = T->getAsCXXRecordDecl(); + if (!R) + return nullptr; + + return hasPublicRefAndDeref(R) ? R : nullptr; +} + +bool isRefCountable(const CXXRecordDecl *R) { + assert(R); + + R = R->getDefinition(); + assert(R); + + if (hasPublicRefAndDeref(R)) + return true; + + CXXBasePaths Paths; + Paths.setOrigin(const_cast<CXXRecordDecl *>(R)); + + const auto isRefCountableBase = [](const CXXBaseSpecifier *Base, + CXXBasePath &) { + return clang::isRefCountable(Base); + }; + + return R->lookupInBases(isRefCountableBase, Paths, + /*LookupInDependent =*/true); +} + +bool isCtorOfRefCounted(const clang::FunctionDecl *F) { + assert(F); + const auto &FunctionName = safeGetName(F); + + return FunctionName == "Ref" || FunctionName == "makeRef" + + || FunctionName == "RefPtr" || FunctionName == "makeRefPtr" + + || FunctionName == "UniqueRef" || FunctionName == "makeUniqueRef" || + FunctionName == "makeUniqueRefWithoutFastMallocCheck" + + || FunctionName == "String" || FunctionName == "AtomString" || + FunctionName == "UniqueString" + // FIXME: Implement as attribute. + || FunctionName == "Identifier"; +} + +bool isUncounted(const CXXRecordDecl *Class) { + // Keep isRefCounted first as it's cheaper. + return !isRefCounted(Class) && isRefCountable(Class); +} + +bool isUncountedPtr(const Type *T) { + assert(T); + + if (T->isPointerType() || T->isReferenceType()) { + if (auto *CXXRD = T->getPointeeCXXRecordDecl()) { + return isUncounted(CXXRD); + } + } + return false; +} + +bool isGetterOfRefCounted(const CXXMethodDecl *M) { + assert(M); + + if (isa<CXXMethodDecl>(M)) { + const CXXRecordDecl *calleeMethodsClass = M->getParent(); + auto className = safeGetName(calleeMethodsClass); + auto methodName = safeGetName(M); + + if (((className == "Ref" || className == "RefPtr") && + methodName == "get") || + ((className == "String" || className == "AtomString" || + className == "AtomStringImpl" || className == "UniqueString" || + className == "UniqueStringImpl" || className == "Identifier") && + methodName == "impl")) + return true; + + // Ref<T> -> T conversion + // FIXME: Currently allowing any Ref<T> -> whatever cast. + if (className == "Ref" || className == "RefPtr") { + if (auto *maybeRefToRawOperator = dyn_cast<CXXConversionDecl>(M)) { + if (auto *targetConversionType = + maybeRefToRawOperator->getConversionType().getTypePtrOrNull()) { + if (isUncountedPtr(targetConversionType)) { + return true; + } + } + } + } + } + return false; +} + +bool isRefCounted(const CXXRecordDecl *R) { + assert(R); + if (auto *TmplR = R->getTemplateInstantiationPattern()) { + // FIXME: String/AtomString/UniqueString + const auto &ClassName = safeGetName(TmplR); + return ClassName == "RefPtr" || ClassName == "Ref"; + } + return false; +} + +bool isPtrConversion(const FunctionDecl *F) { + assert(F); + if (isCtorOfRefCounted(F)) + return true; + + // FIXME: check # of params == 1 + const auto FunctionName = safeGetName(F); + if (FunctionName == "getPtr" || FunctionName == "WeakPtr" || + FunctionName == "makeWeakPtr" + + || FunctionName == "downcast" || FunctionName == "bitwise_cast") + return true; + + return false; +} + +} // namespace clang diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h new file mode 100644 index 000000000000..83d9c0bcc13b --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h @@ -0,0 +1,59 @@ +//=======- PtrTypesSemantics.cpp ---------------------------------*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYZER_WEBKIT_PTRTYPESEMANTICS_H +#define LLVM_CLANG_ANALYZER_WEBKIT_PTRTYPESEMANTICS_H + +namespace clang { +class CXXBaseSpecifier; +class CXXMethodDecl; +class CXXRecordDecl; +class Expr; +class FunctionDecl; +class Type; + +// Ref-countability of a type is implicitly defined by Ref<T> and RefPtr<T> +// implementation. It can be modeled as: type T having public methods ref() and +// deref() + +// In WebKit there are two ref-counted templated smart pointers: RefPtr<T> and +// Ref<T>. + +/// \returns CXXRecordDecl of the base if the type is ref-countable, nullptr if +/// not. +const clang::CXXRecordDecl *isRefCountable(const clang::CXXBaseSpecifier *Base); + +/// \returns true if \p Class is ref-countable, false if not. +/// Asserts that \p Class IS a definition. +bool isRefCountable(const clang::CXXRecordDecl *Class); + +/// \returns true if \p Class is ref-counted, false if not. +bool isRefCounted(const clang::CXXRecordDecl *Class); + +/// \returns true if \p Class is ref-countable AND not ref-counted, false if +/// not. Asserts that \p Class IS a definition. +bool isUncounted(const clang::CXXRecordDecl *Class); + +/// \returns true if \p T is either a raw pointer or reference to an uncounted +/// class, false if not. +bool isUncountedPtr(const clang::Type *T); + +/// \returns true if \p F creates ref-countable object from uncounted parameter, +/// false if not. +bool isCtorOfRefCounted(const clang::FunctionDecl *F); + +/// \returns true if \p M is getter of a ref-counted class, false if not. +bool isGetterOfRefCounted(const clang::CXXMethodDecl *Method); + +/// \returns true if \p F is a conversion between ref-countable or ref-counted +/// pointer types. +bool isPtrConversion(const FunctionDecl *F); + +} // namespace clang + +#endif diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp new file mode 100644 index 000000000000..81ce284c2dc7 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp @@ -0,0 +1,167 @@ +//=======- RefCntblBaseVirtualDtor.cpp ---------------------------*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "DiagOutputUtils.h" +#include "PtrTypesSemantics.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" + +using namespace clang; +using namespace ento; + +namespace { +class RefCntblBaseVirtualDtorChecker + : public Checker<check::ASTDecl<TranslationUnitDecl>> { +private: + BugType Bug; + mutable BugReporter *BR; + +public: + RefCntblBaseVirtualDtorChecker() + : Bug(this, + "Reference-countable base class doesn't have virtual destructor", + "WebKit coding guidelines") {} + + void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, + BugReporter &BRArg) const { + BR = &BRArg; + + // The calls to checkAST* from AnalysisConsumer don't + // visit template instantiations or lambda classes. We + // want to visit those, so we make our own RecursiveASTVisitor. + struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> { + const RefCntblBaseVirtualDtorChecker *Checker; + explicit LocalVisitor(const RefCntblBaseVirtualDtorChecker *Checker) + : Checker(Checker) { + assert(Checker); + } + + bool shouldVisitTemplateInstantiations() const { return true; } + bool shouldVisitImplicitCode() const { return false; } + + bool VisitCXXRecordDecl(const CXXRecordDecl *RD) { + Checker->visitCXXRecordDecl(RD); + return true; + } + }; + + LocalVisitor visitor(this); + visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD)); + } + + void visitCXXRecordDecl(const CXXRecordDecl *RD) const { + if (shouldSkipDecl(RD)) + return; + + CXXBasePaths Paths; + Paths.setOrigin(RD); + + const CXXBaseSpecifier *ProblematicBaseSpecifier = nullptr; + const CXXRecordDecl *ProblematicBaseClass = nullptr; + + const auto IsPublicBaseRefCntblWOVirtualDtor = + [RD, &ProblematicBaseSpecifier, + &ProblematicBaseClass](const CXXBaseSpecifier *Base, CXXBasePath &) { + const auto AccSpec = Base->getAccessSpecifier(); + if (AccSpec == AS_protected || AccSpec == AS_private || + (AccSpec == AS_none && RD->isClass())) + return false; + + llvm::Optional<const clang::CXXRecordDecl *> MaybeRefCntblBaseRD = + isRefCountable(Base); + if (!MaybeRefCntblBaseRD.hasValue()) + return false; + + const CXXRecordDecl *RefCntblBaseRD = MaybeRefCntblBaseRD.getValue(); + if (!RefCntblBaseRD) + return false; + + const auto *Dtor = RefCntblBaseRD->getDestructor(); + if (!Dtor || !Dtor->isVirtual()) { + ProblematicBaseSpecifier = Base; + ProblematicBaseClass = RefCntblBaseRD; + return true; + } + + return false; + }; + + if (RD->lookupInBases(IsPublicBaseRefCntblWOVirtualDtor, Paths, + /*LookupInDependent =*/true)) { + reportBug(RD, ProblematicBaseSpecifier, ProblematicBaseClass); + } + } + + bool shouldSkipDecl(const CXXRecordDecl *RD) const { + if (!RD->isThisDeclarationADefinition()) + return true; + + if (RD->isImplicit()) + return true; + + if (RD->isLambda()) + return true; + + // If the construct doesn't have a source file, then it's not something + // we want to diagnose. + const auto RDLocation = RD->getLocation(); + if (!RDLocation.isValid()) + return true; + + const auto Kind = RD->getTagKind(); + if (Kind != TTK_Struct && Kind != TTK_Class) + return true; + + // Ignore CXXRecords that come from system headers. + if (BR->getSourceManager().getFileCharacteristic(RDLocation) != + SrcMgr::C_User) + return true; + + return false; + } + + void reportBug(const CXXRecordDecl *DerivedClass, + const CXXBaseSpecifier *BaseSpec, + const CXXRecordDecl *ProblematicBaseClass) const { + assert(DerivedClass); + assert(BaseSpec); + assert(ProblematicBaseClass); + + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + + Os << (ProblematicBaseClass->isClass() ? "Class" : "Struct") << " "; + printQuotedQualifiedName(Os, ProblematicBaseClass); + + Os << " is used as a base of " + << (DerivedClass->isClass() ? "class" : "struct") << " "; + printQuotedQualifiedName(Os, DerivedClass); + + Os << " but doesn't have virtual destructor"; + + PathDiagnosticLocation BSLoc(BaseSpec->getSourceRange().getBegin(), + BR->getSourceManager()); + auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc); + Report->addRange(BaseSpec->getSourceRange()); + BR->emitReport(std::move(Report)); + } +}; +} // namespace + +void ento::registerRefCntblBaseVirtualDtorChecker(CheckerManager &Mgr) { + Mgr.registerChecker<RefCntblBaseVirtualDtorChecker>(); +} + +bool ento::shouldRegisterRefCntblBaseVirtualDtorChecker( + const CheckerManager &mgr) { + return true; +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp new file mode 100644 index 000000000000..940a1f349831 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp @@ -0,0 +1,195 @@ +//=======- UncountedCallArgsChecker.cpp --------------------------*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ASTUtils.h" +#include "DiagOutputUtils.h" +#include "PtrTypesSemantics.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "llvm/ADT/DenseSet.h" + +using namespace clang; +using namespace ento; + +namespace { + +class UncountedCallArgsChecker + : public Checker<check::ASTDecl<TranslationUnitDecl>> { + BugType Bug{this, + "Uncounted call argument for a raw pointer/reference parameter", + "WebKit coding guidelines"}; + mutable BugReporter *BR; + +public: + + void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, + BugReporter &BRArg) const { + BR = &BRArg; + + // The calls to checkAST* from AnalysisConsumer don't + // visit template instantiations or lambda classes. We + // want to visit those, so we make our own RecursiveASTVisitor. + struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> { + const UncountedCallArgsChecker *Checker; + explicit LocalVisitor(const UncountedCallArgsChecker *Checker) + : Checker(Checker) { + assert(Checker); + } + + bool shouldVisitTemplateInstantiations() const { return true; } + bool shouldVisitImplicitCode() const { return false; } + + bool VisitCallExpr(const CallExpr *CE) { + Checker->visitCallExpr(CE); + return true; + } + }; + + LocalVisitor visitor(this); + visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD)); + } + + void visitCallExpr(const CallExpr *CE) const { + if (shouldSkipCall(CE)) + return; + + if (auto *F = CE->getDirectCallee()) { + // Skip the first argument for overloaded member operators (e. g. lambda + // or std::function call operator). + unsigned ArgIdx = + isa<CXXOperatorCallExpr>(CE) && dyn_cast_or_null<CXXMethodDecl>(F); + + for (auto P = F->param_begin(); + // FIXME: Also check variadic function parameters. + // FIXME: Also check default function arguments. Probably a different + // checker. In case there are default arguments the call can have + // fewer arguments than the callee has parameters. + P < F->param_end() && ArgIdx < CE->getNumArgs(); ++P, ++ArgIdx) { + // TODO: attributes. + // if ((*P)->hasAttr<SafeRefCntblRawPtrAttr>()) + // continue; + + const auto *ArgType = (*P)->getType().getTypePtrOrNull(); + if (!ArgType) + continue; // FIXME? Should we bail? + + // FIXME: more complex types (arrays, references to raw pointers, etc) + if (!isUncountedPtr(ArgType)) + continue; + + const auto *Arg = CE->getArg(ArgIdx); + + std::pair<const clang::Expr *, bool> ArgOrigin = + tryToFindPtrOrigin(Arg, true); + + // Temporary ref-counted object created as part of the call argument + // would outlive the call. + if (ArgOrigin.second) + continue; + + if (isa<CXXNullPtrLiteralExpr>(ArgOrigin.first)) { + // foo(nullptr) + continue; + } + if (isa<IntegerLiteral>(ArgOrigin.first)) { + // FIXME: Check the value. + // foo(NULL) + continue; + } + + if (isASafeCallArg(ArgOrigin.first)) + continue; + + reportBug(Arg, *P); + } + } + } + + bool shouldSkipCall(const CallExpr *CE) const { + if (CE->getNumArgs() == 0) + return false; + + // If an assignment is problematic we should warn about the sole existence + // of object on LHS. + if (auto *MemberOp = dyn_cast<CXXOperatorCallExpr>(CE)) { + // Note: assignemnt to built-in type isn't derived from CallExpr. + if (MemberOp->isAssignmentOp()) + return false; + } + + const auto *Callee = CE->getDirectCallee(); + if (!Callee) + return false; + + auto overloadedOperatorType = Callee->getOverloadedOperator(); + if (overloadedOperatorType == OO_EqualEqual || + overloadedOperatorType == OO_ExclaimEqual || + overloadedOperatorType == OO_LessEqual || + overloadedOperatorType == OO_GreaterEqual || + overloadedOperatorType == OO_Spaceship || + overloadedOperatorType == OO_AmpAmp || + overloadedOperatorType == OO_PipePipe) + return true; + + if (isCtorOfRefCounted(Callee)) + return true; + + auto name = safeGetName(Callee); + if (name == "adoptRef" || name == "getPtr" || name == "WeakPtr" || + name == "makeWeakPtr" || name == "downcast" || name == "bitwise_cast" || + name == "is" || name == "equal" || name == "hash" || + name == "isType" + // FIXME: Most/all of these should be implemented via attributes. + || name == "equalIgnoringASCIICase" || + name == "equalIgnoringASCIICaseCommon" || + name == "equalIgnoringNullity") + return true; + + return false; + } + + void reportBug(const Expr *CallArg, const ParmVarDecl *Param) const { + assert(CallArg); + + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + + const std::string paramName = safeGetName(Param); + Os << "Call argument"; + if (!paramName.empty()) { + Os << " for parameter "; + printQuotedQualifiedName(Os, Param); + } + Os << " is uncounted and unsafe."; + + const SourceLocation SrcLocToReport = + isa<CXXDefaultArgExpr>(CallArg) ? Param->getDefaultArg()->getExprLoc() + : CallArg->getSourceRange().getBegin(); + + PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager()); + auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc); + Report->addRange(CallArg->getSourceRange()); + BR->emitReport(std::move(Report)); + } +}; +} // namespace + +void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) { + Mgr.registerChecker<UncountedCallArgsChecker>(); +} + +bool ento::shouldRegisterUncountedCallArgsChecker(const CheckerManager &) { + return true; +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Yaml.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Yaml.h index 968c50e33f6d..ec612dde3b8b 100755 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Yaml.h +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Yaml.h @@ -15,6 +15,7 @@ #define LLVM_CLANG_LIB_STATICANALYZER_CHECKER_YAML_H #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "llvm/Support/VirtualFileSystem.h" #include "llvm/Support/YAMLTraits.h" namespace clang { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/cert/PutenvWithAutoChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/cert/PutenvWithAutoChecker.cpp new file mode 100644 index 000000000000..1c67bbd77ec8 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/cert/PutenvWithAutoChecker.cpp @@ -0,0 +1,66 @@ +//== PutenvWithAutoChecker.cpp --------------------------------- -*- C++ -*--=// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines PutenvWithAutoChecker which finds calls of ``putenv`` +// function with automatic variable as the argument. +// https://wiki.sei.cmu.edu/confluence/x/6NYxBQ +// +//===----------------------------------------------------------------------===// + +#include "../AllocationState.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/MemRegion.h" + +using namespace clang; +using namespace ento; + +namespace { +class PutenvWithAutoChecker : public Checker<check::PostCall> { +private: + BugType BT{this, "'putenv' function should not be called with auto variables", + categories::SecurityError}; + const CallDescription Putenv{"putenv", 1}; + +public: + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; +}; +} // namespace + +void PutenvWithAutoChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + if (!Call.isCalled(Putenv)) + return; + + SVal ArgV = Call.getArgSVal(0); + const Expr *ArgExpr = Call.getArgExpr(0); + const MemSpaceRegion *MSR = ArgV.getAsRegion()->getMemorySpace(); + + if (!isa<StackSpaceRegion>(MSR)) + return; + + StringRef ErrorMsg = "The 'putenv' function should not be called with " + "arguments that have automatic storage"; + ExplodedNode *N = C.generateErrorNode(); + auto Report = std::make_unique<PathSensitiveBugReport>(BT, ErrorMsg, N); + + // Track the argument. + bugreporter::trackExpressionValue(Report->getErrorNode(), ArgExpr, *Report); + + C.emitReport(std::move(Report)); +} + +void ento::registerPutenvWithAuto(CheckerManager &Mgr) { + Mgr.registerChecker<PutenvWithAutoChecker>(); +} + +bool ento::shouldRegisterPutenvWithAuto(const CheckerManager &) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp index fdd03c75920d..ecfc7106560e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp @@ -13,7 +13,7 @@ using namespace ento; void AnalysisManager::anchor() { } -AnalysisManager::AnalysisManager(ASTContext &ASTCtx, +AnalysisManager::AnalysisManager(ASTContext &ASTCtx, Preprocessor &PP, const PathDiagnosticConsumers &PDC, StoreManagerCreator storemgr, ConstraintManagerCreator constraintmgr, @@ -38,7 +38,7 @@ AnalysisManager::AnalysisManager(ASTContext &ASTCtx, Options.ShouldElideConstructors, /*addVirtualBaseBranches=*/true, injector), - Ctx(ASTCtx), LangOpts(ASTCtx.getLangOpts()), + Ctx(ASTCtx), PP(PP), LangOpts(ASTCtx.getLangOpts()), PathConsumers(PDC), CreateStoreMgr(storemgr), CreateConstraintMgr(constraintmgr), CheckerMgr(checkerMgr), options(Options) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp index 7cd48bf44374..73f057f09550 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp @@ -239,7 +239,7 @@ BasicValueFactory::evalAPSInt(BinaryOperator::Opcode Op, if (Amt >= V1.getBitWidth()) return nullptr; - if (!Ctx.getLangOpts().CPlusPlus2a) { + if (!Ctx.getLangOpts().CPlusPlus20) { if (V1.isSigned() && V1.isNegative()) return nullptr; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 1864bcef9b87..72be4e81c83d 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -34,10 +34,12 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/CheckerRegistryData.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConv.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" #include "llvm/ADT/ArrayRef.h" @@ -51,6 +53,7 @@ #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Statistic.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/iterator_range.h" #include "llvm/Support/Casting.h" @@ -2105,6 +2108,53 @@ void BuiltinBug::anchor() {} // Methods for BugReport and subclasses. //===----------------------------------------------------------------------===// +LLVM_ATTRIBUTE_USED static bool +isDependency(const CheckerRegistryData &Registry, StringRef CheckerName) { + for (const std::pair<StringRef, StringRef> &Pair : Registry.Dependencies) { + if (Pair.second == CheckerName) + return true; + } + return false; +} + +LLVM_ATTRIBUTE_USED static bool isHidden(const CheckerRegistryData &Registry, + StringRef CheckerName) { + for (const CheckerInfo &Checker : Registry.Checkers) { + if (Checker.FullName == CheckerName) + return Checker.IsHidden; + } + llvm_unreachable( + "Checker name not found in CheckerRegistry -- did you retrieve it " + "correctly from CheckerManager::getCurrentCheckerName?"); +} + +PathSensitiveBugReport::PathSensitiveBugReport( + const BugType &bt, StringRef shortDesc, StringRef desc, + const ExplodedNode *errorNode, PathDiagnosticLocation LocationToUnique, + const Decl *DeclToUnique) + : BugReport(Kind::PathSensitive, bt, shortDesc, desc), ErrorNode(errorNode), + ErrorNodeRange(getStmt() ? getStmt()->getSourceRange() : SourceRange()), + UniqueingLocation(LocationToUnique), UniqueingDecl(DeclToUnique) { + assert(!isDependency(ErrorNode->getState() + ->getAnalysisManager() + .getCheckerManager() + ->getCheckerRegistryData(), + bt.getCheckerName()) && + "Some checkers depend on this one! We don't allow dependency " + "checkers to emit warnings, because checkers should depend on " + "*modeling*, not *diagnostics*."); + + assert( + (bt.getCheckerName().startswith("debug") || + !isHidden(ErrorNode->getState() + ->getAnalysisManager() + .getCheckerManager() + ->getCheckerRegistryData(), + bt.getCheckerName())) && + "Hidden checkers musn't emit diagnostics as they are by definition " + "non-user facing!"); +} + void PathSensitiveBugReport::addVisitor( std::unique_ptr<BugReporterVisitor> visitor) { if (!visitor) @@ -2193,12 +2243,12 @@ static void insertToInterestingnessMap( return; case bugreporter::TrackingKind::Condition: return; - } + } - llvm_unreachable( - "BugReport::markInteresting currently can only handle 2 different " - "tracking kinds! Please define what tracking kind should this entitiy" - "have, if it was already marked as interesting with a different kind!"); + llvm_unreachable( + "BugReport::markInteresting currently can only handle 2 different " + "tracking kinds! Please define what tracking kind should this entitiy" + "have, if it was already marked as interesting with a different kind!"); } void PathSensitiveBugReport::markInteresting(SymbolRef sym, @@ -2389,6 +2439,7 @@ ProgramStateManager &PathSensitiveBugReporter::getStateManager() const { return Eng.getStateManager(); } +BugReporter::BugReporter(BugReporterData &d) : D(d) {} BugReporter::~BugReporter() { // Make sure reports are flushed. assert(StrBugTypes.empty() && @@ -2409,7 +2460,7 @@ void BugReporter::FlushReports() { // EmitBasicReport. // FIXME: There are leaks from checkers that assume that the BugTypes they // create will be destroyed by the BugReporter. - llvm::DeleteContainerSeconds(StrBugTypes); + StrBugTypes.clear(); } //===----------------------------------------------------------------------===// @@ -2781,7 +2832,7 @@ Optional<PathDiagnosticBuilder> PathDiagnosticBuilder::findValidReport( R->clearVisitors(); R->addVisitor(std::make_unique<FalsePositiveRefutationBRVisitor>()); - // We don't overrite the notes inserted by other visitors because the + // We don't overwrite the notes inserted by other visitors because the // refutation manager does not add any new note to the path generateVisitorsDiagnostics(R, BugPath->ErrorNode, BRC); } @@ -3262,8 +3313,8 @@ BugType *BugReporter::getBugTypeForName(CheckerNameRef CheckName, SmallString<136> fullDesc; llvm::raw_svector_ostream(fullDesc) << CheckName.getName() << ":" << name << ":" << category; - BugType *&BT = StrBugTypes[fullDesc]; + std::unique_ptr<BugType> &BT = StrBugTypes[fullDesc]; if (!BT) - BT = new BugType(CheckName, name, category); - return BT; + BT = std::make_unique<BugType>(CheckName, name, category); + return BT.get(); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 0525b5c41e34..ef4d38ff498f 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -45,7 +45,6 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/SMTConv.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/None.h" #include "llvm/ADT/Optional.h" @@ -358,7 +357,7 @@ class NoStoreFuncVisitor final : public BugReporterVisitor { public: NoStoreFuncVisitor(const SubRegion *R, bugreporter::TrackingKind TKind) - : RegionOfInterest(R), MmrMgr(*R->getMemRegionManager()), + : RegionOfInterest(R), MmrMgr(R->getMemRegionManager()), SM(MmrMgr.getContext().getSourceManager()), PP(MmrMgr.getContext().getPrintingPolicy()), TKind(TKind) {} @@ -813,7 +812,7 @@ public: const SourceManager &SMgr = BRC.getSourceManager(); if (auto Loc = matchAssignment(N)) { if (isFunctionMacroExpansion(*Loc, SMgr)) { - std::string MacroName = getMacroName(*Loc, BRC); + std::string MacroName = std::string(getMacroName(*Loc, BRC)); SourceLocation BugLoc = BugPoint->getStmt()->getBeginLoc(); if (!BugLoc.isMacroID() || getMacroName(BugLoc, BRC) != MacroName) BR.markInvalid(getTag(), MacroName.c_str()); @@ -1735,10 +1734,9 @@ constructDebugPieceForTrackedCondition(const Expr *Cond, !BRC.getAnalyzerOptions().ShouldTrackConditionsDebug) return nullptr; - std::string ConditionText = Lexer::getSourceText( + std::string ConditionText = std::string(Lexer::getSourceText( CharSourceRange::getTokenRange(Cond->getSourceRange()), - BRC.getSourceManager(), - BRC.getASTContext().getLangOpts()); + BRC.getSourceManager(), BRC.getASTContext().getLangOpts())); return std::make_shared<PathDiagnosticEventPiece>( PathDiagnosticLocation::createBegin( @@ -2494,7 +2492,7 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( Out << WillBeUsedForACondition; // Convert 'field ...' to 'Field ...' if it is a MemberExpr. - std::string Message = Out.str(); + std::string Message = std::string(Out.str()); Message[0] = toupper(Message[0]); // If we know the value create a pop-up note to the value part of 'BExpr'. @@ -2821,7 +2819,7 @@ void FalsePositiveRefutationBRVisitor::finalizeVisitor( BugReporterContext &BRC, const ExplodedNode *EndPathNode, PathSensitiveBugReport &BR) { // Collect new constraints - VisitNode(EndPathNode, BRC, BR); + addConstraints(EndPathNode, /*OverwriteConstraintsOnExistingSyms=*/true); // Create a refutation manager llvm::SMTSolverRef RefutationSolver = llvm::CreateZ3Solver(); @@ -2832,30 +2830,30 @@ void FalsePositiveRefutationBRVisitor::finalizeVisitor( const SymbolRef Sym = I.first; auto RangeIt = I.second.begin(); - llvm::SMTExprRef Constraints = SMTConv::getRangeExpr( + llvm::SMTExprRef SMTConstraints = SMTConv::getRangeExpr( RefutationSolver, Ctx, Sym, RangeIt->From(), RangeIt->To(), /*InRange=*/true); while ((++RangeIt) != I.second.end()) { - Constraints = RefutationSolver->mkOr( - Constraints, SMTConv::getRangeExpr(RefutationSolver, Ctx, Sym, - RangeIt->From(), RangeIt->To(), - /*InRange=*/true)); + SMTConstraints = RefutationSolver->mkOr( + SMTConstraints, SMTConv::getRangeExpr(RefutationSolver, Ctx, Sym, + RangeIt->From(), RangeIt->To(), + /*InRange=*/true)); } - RefutationSolver->addConstraint(Constraints); + RefutationSolver->addConstraint(SMTConstraints); } // And check for satisfiability - Optional<bool> isSat = RefutationSolver->check(); - if (!isSat.hasValue()) + Optional<bool> IsSAT = RefutationSolver->check(); + if (!IsSAT.hasValue()) return; - if (!isSat.getValue()) + if (!IsSAT.getValue()) BR.markInvalid("Infeasible constraints", EndPathNode->getLocationContext()); } -PathDiagnosticPieceRef FalsePositiveRefutationBRVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &, PathSensitiveBugReport &) { +void FalsePositiveRefutationBRVisitor::addConstraints( + const ExplodedNode *N, bool OverwriteConstraintsOnExistingSyms) { // Collect new constraints const ConstraintRangeTy &NewCs = N->getState()->get<ConstraintRange>(); ConstraintRangeTy::Factory &CF = @@ -2865,10 +2863,19 @@ PathDiagnosticPieceRef FalsePositiveRefutationBRVisitor::VisitNode( for (auto const &C : NewCs) { const SymbolRef &Sym = C.first; if (!Constraints.contains(Sym)) { + // This symbol is new, just add the constraint. + Constraints = CF.add(Constraints, Sym, C.second); + } else if (OverwriteConstraintsOnExistingSyms) { + // Overwrite the associated constraint of the Symbol. + Constraints = CF.remove(Constraints, Sym); Constraints = CF.add(Constraints, Sym, C.second); } } +} +PathDiagnosticPieceRef FalsePositiveRefutationBRVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &, PathSensitiveBugReport &) { + addConstraints(N, /*OverwriteConstraintsOnExistingSyms=*/false); return nullptr; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 168d6fe6ec48..78d13ddfb773 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -172,23 +172,9 @@ AnalysisDeclContext *CallEvent::getCalleeAnalysisDeclContext() const { if (!D) return nullptr; - // TODO: For now we skip functions without definitions, even if we have - // our own getDecl(), because it's hard to find out which re-declaration - // is going to be used, and usually clients don't really care about this - // situation because there's a loss of precision anyway because we cannot - // inline the call. - RuntimeDefinition RD = getRuntimeDefinition(); - if (!RD.getDecl()) - return nullptr; - AnalysisDeclContext *ADC = LCtx->getAnalysisDeclContext()->getManager()->getContext(D); - // TODO: For now we skip virtual functions, because this also rises - // the problem of which decl to use, but now it's across different classes. - if (RD.mayHaveOtherDefinitions() || RD.getDecl() != ADC->getDecl()) - return nullptr; - return ADC; } @@ -222,39 +208,17 @@ CallEvent::getCalleeStackFrame(unsigned BlockCount) const { return ADC->getManager()->getStackFrame(ADC, LCtx, E, B, BlockCount, Idx); } -const VarRegion *CallEvent::getParameterLocation(unsigned Index, - unsigned BlockCount) const { +const ParamVarRegion +*CallEvent::getParameterLocation(unsigned Index, unsigned BlockCount) const { const StackFrameContext *SFC = getCalleeStackFrame(BlockCount); // We cannot construct a VarRegion without a stack frame. if (!SFC) return nullptr; - // Retrieve parameters of the definition, which are different from - // CallEvent's parameters() because getDecl() isn't necessarily - // the definition. SFC contains the definition that would be used - // during analysis. - const Decl *D = SFC->getDecl(); - - // TODO: Refactor into a virtual method of CallEvent, like parameters(). - const ParmVarDecl *PVD = nullptr; - if (const auto *FD = dyn_cast<FunctionDecl>(D)) - PVD = FD->parameters()[Index]; - else if (const auto *BD = dyn_cast<BlockDecl>(D)) - PVD = BD->parameters()[Index]; - else if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) - PVD = MD->parameters()[Index]; - else if (const auto *CD = dyn_cast<CXXConstructorDecl>(D)) - PVD = CD->parameters()[Index]; - assert(PVD && "Unexpected Decl kind!"); - - const VarRegion *VR = - State->getStateManager().getRegionManager().getVarRegion(PVD, SFC); - - // This sanity check would fail if our parameter declaration doesn't - // correspond to the stack frame's function declaration. - assert(VR->getStackFrame() == SFC); - - return VR; + const ParamVarRegion *PVR = + State->getStateManager().getRegionManager().getParamVarRegion( + getOriginExpr(), Index, SFC); + return PVR; } /// Returns true if a type is a pointer-to-const or reference-to-const @@ -325,8 +289,9 @@ ProgramStateRef CallEvent::invalidateRegions(unsigned BlockCount, if (getKind() != CE_CXXAllocator) if (isArgumentConstructedDirectly(Idx)) if (auto AdjIdx = getAdjustedParameterIndex(Idx)) - if (const VarRegion *VR = getParameterLocation(*AdjIdx, BlockCount)) - ValuesToInvalidate.push_back(loc::MemRegionVal(VR)); + if (const TypedValueRegion *TVR = + getParameterLocation(*AdjIdx, BlockCount)) + ValuesToInvalidate.push_back(loc::MemRegionVal(TVR)); } // Invalidate designated regions using the batch invalidation API. @@ -450,8 +415,7 @@ void CallEvent::dump(raw_ostream &Out) const { return; } - // FIXME: a string representation of the kind would be nice. - Out << "Unknown call (type " << getKind() << ")"; + Out << "Unknown call (type " << getKindAsString() << ")"; } bool CallEvent::isCallStmt(const Stmt *S) { @@ -515,8 +479,7 @@ static void addParameterValuesToBindings(const StackFrameContext *CalleeCtx, unsigned Idx = 0; ArrayRef<ParmVarDecl*>::iterator I = parameters.begin(), E = parameters.end(); for (; I != E && Idx < NumArgs; ++I, ++Idx) { - const ParmVarDecl *ParamDecl = *I; - assert(ParamDecl && "Formal parameter has no decl?"); + assert(*I && "Formal parameter has no decl?"); // TODO: Support allocator calls. if (Call.getKind() != CE_CXXAllocator) @@ -528,7 +491,8 @@ static void addParameterValuesToBindings(const StackFrameContext *CalleeCtx, // which makes getArgSVal() fail and return UnknownVal. SVal ArgVal = Call.getArgSVal(Idx); if (!ArgVal.isUnknown()) { - Loc ParamLoc = SVB.makeLoc(MRMgr.getVarRegion(ParamDecl, CalleeCtx)); + Loc ParamLoc = SVB.makeLoc( + MRMgr.getParamVarRegion(Call.getOriginExpr(), Idx, CalleeCtx)); Bindings.push_back(std::make_pair(ParamLoc, ArgVal)); } } @@ -536,6 +500,37 @@ static void addParameterValuesToBindings(const StackFrameContext *CalleeCtx, // FIXME: Variadic arguments are not handled at all right now. } +const ConstructionContext *CallEvent::getConstructionContext() const { + const StackFrameContext *StackFrame = getCalleeStackFrame(0); + if (!StackFrame) + return nullptr; + + const CFGElement Element = StackFrame->getCallSiteCFGElement(); + if (const auto Ctor = Element.getAs<CFGConstructor>()) { + return Ctor->getConstructionContext(); + } + + if (const auto RecCall = Element.getAs<CFGCXXRecordTypedCall>()) { + return RecCall->getConstructionContext(); + } + + return nullptr; +} + +Optional<SVal> +CallEvent::getReturnValueUnderConstruction() const { + const auto *CC = getConstructionContext(); + if (!CC) + return None; + + EvalCallOptions CallOpts; + ExprEngine &Engine = getState()->getStateManager().getOwningEngine(); + SVal RetVal = + Engine.computeObjectUnderConstruction(getOriginExpr(), getState(), + getLocationContext(), CC, CallOpts); + return RetVal; +} + ArrayRef<ParmVarDecl*> AnyFunctionCall::parameters() const { const FunctionDecl *D = getDecl(); if (!D) @@ -565,7 +560,7 @@ RuntimeDefinition AnyFunctionCall::getRuntimeDefinition() const { return RuntimeDefinition(Decl); } - SubEngine &Engine = getState()->getStateManager().getOwningEngine(); + ExprEngine &Engine = getState()->getStateManager().getOwningEngine(); AnalyzerOptions &Opts = Engine.getAnalysisManager().options; // Try to get CTU definition only if CTUDir is provided. @@ -889,24 +884,22 @@ void BlockCall::getInitialStackFrameContents(const StackFrameContext *CalleeCtx, Params); } -SVal CXXConstructorCall::getCXXThisVal() const { +SVal AnyCXXConstructorCall::getCXXThisVal() const { if (Data) return loc::MemRegionVal(static_cast<const MemRegion *>(Data)); return UnknownVal(); } -void CXXConstructorCall::getExtraInvalidatedValues(ValueList &Values, +void AnyCXXConstructorCall::getExtraInvalidatedValues(ValueList &Values, RegionAndSymbolInvalidationTraits *ETraits) const { - if (Data) { - loc::MemRegionVal MV(static_cast<const MemRegion *>(Data)); - if (SymbolRef Sym = MV.getAsSymbol(true)) - ETraits->setTrait(Sym, - RegionAndSymbolInvalidationTraits::TK_SuppressEscape); - Values.push_back(MV); - } + SVal V = getCXXThisVal(); + if (SymbolRef Sym = V.getAsSymbol(true)) + ETraits->setTrait(Sym, + RegionAndSymbolInvalidationTraits::TK_SuppressEscape); + Values.push_back(V); } -void CXXConstructorCall::getInitialStackFrameContents( +void AnyCXXConstructorCall::getInitialStackFrameContents( const StackFrameContext *CalleeCtx, BindingsTy &Bindings) const { AnyFunctionCall::getInitialStackFrameContents(CalleeCtx, Bindings); @@ -920,6 +913,14 @@ void CXXConstructorCall::getInitialStackFrameContents( } } +const StackFrameContext * +CXXInheritedConstructorCall::getInheritingStackFrame() const { + const StackFrameContext *SFC = getLocationContext()->getStackFrame(); + while (isa<CXXInheritedCtorInitExpr>(SFC->getCallSite())) + SFC = SFC->getParent()->getStackFrame(); + return SFC; +} + SVal CXXDestructorCall::getCXXThisVal() const { if (Data) return loc::MemRegionVal(DtorDataTy::getFromOpaqueValue(Data).getPointer()); @@ -967,14 +968,6 @@ void ObjCMethodCall::getExtraInvalidatedValues( Values.push_back(getReceiverSVal()); } -SVal ObjCMethodCall::getSelfSVal() const { - const LocationContext *LCtx = getLocationContext(); - const ImplicitParamDecl *SelfDecl = LCtx->getSelfDecl(); - if (!SelfDecl) - return SVal(); - return getState()->getSVal(getState()->getRegion(SelfDecl, LCtx)); -} - SVal ObjCMethodCall::getReceiverSVal() const { // FIXME: Is this the best way to handle class receivers? if (!isInstanceMessage()) @@ -986,7 +979,7 @@ SVal ObjCMethodCall::getReceiverSVal() const { // An instance message with no expression means we are sending to super. // In this case the object reference is the same as 'self'. assert(getOriginExpr()->getReceiverKind() == ObjCMessageExpr::SuperInstance); - SVal SelfVal = getSelfSVal(); + SVal SelfVal = getState()->getSelfSVal(getLocationContext()); assert(SelfVal.isValid() && "Calling super but not in ObjC method"); return SelfVal; } @@ -1000,8 +993,9 @@ bool ObjCMethodCall::isReceiverSelfOrSuper() const { return false; SVal RecVal = getSVal(getOriginExpr()->getInstanceReceiver()); + SVal SelfVal = getState()->getSelfSVal(getLocationContext()); - return (RecVal == getSelfSVal()); + return (RecVal == SelfVal); } SourceRange ObjCMethodCall::getSourceRange() const { @@ -1168,23 +1162,77 @@ static const ObjCMethodDecl *findDefiningRedecl(const ObjCMethodDecl *MD) { return MD; } -static bool isCallToSelfClass(const ObjCMessageExpr *ME) { - const Expr* InstRec = ME->getInstanceReceiver(); - if (!InstRec) - return false; - const auto *InstRecIg = dyn_cast<DeclRefExpr>(InstRec->IgnoreParenImpCasts()); +struct PrivateMethodKey { + const ObjCInterfaceDecl *Interface; + Selector LookupSelector; + bool IsClassMethod; +}; - // Check that receiver is called 'self'. - if (!InstRecIg || !InstRecIg->getFoundDecl() || - !InstRecIg->getFoundDecl()->getName().equals("self")) - return false; +namespace llvm { +template <> struct DenseMapInfo<PrivateMethodKey> { + using InterfaceInfo = DenseMapInfo<const ObjCInterfaceDecl *>; + using SelectorInfo = DenseMapInfo<Selector>; - // Check that the method name is 'class'. - if (ME->getSelector().getNumArgs() != 0 || - !ME->getSelector().getNameForSlot(0).equals("class")) - return false; + static inline PrivateMethodKey getEmptyKey() { + return {InterfaceInfo::getEmptyKey(), SelectorInfo::getEmptyKey(), false}; + } - return true; + static inline PrivateMethodKey getTombstoneKey() { + return {InterfaceInfo::getTombstoneKey(), SelectorInfo::getTombstoneKey(), + true}; + } + + static unsigned getHashValue(const PrivateMethodKey &Key) { + return llvm::hash_combine( + llvm::hash_code(InterfaceInfo::getHashValue(Key.Interface)), + llvm::hash_code(SelectorInfo::getHashValue(Key.LookupSelector)), + Key.IsClassMethod); + } + + static bool isEqual(const PrivateMethodKey &LHS, + const PrivateMethodKey &RHS) { + return InterfaceInfo::isEqual(LHS.Interface, RHS.Interface) && + SelectorInfo::isEqual(LHS.LookupSelector, RHS.LookupSelector) && + LHS.IsClassMethod == RHS.IsClassMethod; + } +}; +} // end namespace llvm + +static const ObjCMethodDecl * +lookupRuntimeDefinition(const ObjCInterfaceDecl *Interface, + Selector LookupSelector, bool InstanceMethod) { + // Repeatedly calling lookupPrivateMethod() is expensive, especially + // when in many cases it returns null. We cache the results so + // that repeated queries on the same ObjCIntefaceDecl and Selector + // don't incur the same cost. On some test cases, we can see the + // same query being issued thousands of times. + // + // NOTE: This cache is essentially a "global" variable, but it + // only gets lazily created when we get here. The value of the + // cache probably comes from it being global across ExprEngines, + // where the same queries may get issued. If we are worried about + // concurrency, or possibly loading/unloading ASTs, etc., we may + // need to revisit this someday. In terms of memory, this table + // stays around until clang quits, which also may be bad if we + // need to release memory. + using PrivateMethodCache = + llvm::DenseMap<PrivateMethodKey, Optional<const ObjCMethodDecl *>>; + + static PrivateMethodCache PMC; + Optional<const ObjCMethodDecl *> &Val = + PMC[{Interface, LookupSelector, InstanceMethod}]; + + // Query lookupPrivateMethod() if the cache does not hit. + if (!Val.hasValue()) { + Val = Interface->lookupPrivateMethod(LookupSelector, InstanceMethod); + + if (!*Val) { + // Query 'lookupMethod' as a backup. + Val = Interface->lookupMethod(LookupSelector, InstanceMethod); + } + } + + return Val.getValue(); } RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { @@ -1194,8 +1242,9 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { if (E->isInstanceMessage()) { // Find the receiver type. - const ObjCObjectPointerType *ReceiverT = nullptr; + const ObjCObjectType *ReceiverT = nullptr; bool CanBeSubClassed = false; + bool LookingForInstanceMethod = true; QualType SupersType = E->getSuperType(); const MemRegion *Receiver = nullptr; @@ -1203,7 +1252,7 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { // The receiver is guaranteed to be 'super' in this case. // Super always means the type of immediate predecessor to the method // where the call occurs. - ReceiverT = cast<ObjCObjectPointerType>(SupersType); + ReceiverT = cast<ObjCObjectPointerType>(SupersType)->getObjectType(); } else { Receiver = getReceiverSVal().getAsRegion(); if (!Receiver) @@ -1218,100 +1267,59 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { QualType DynType = DTI.getType(); CanBeSubClassed = DTI.canBeASubClass(); - ReceiverT = dyn_cast<ObjCObjectPointerType>(DynType.getCanonicalType()); - if (ReceiverT && CanBeSubClassed) - if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterfaceDecl()) - if (!canBeOverridenInSubclass(IDecl, Sel)) - CanBeSubClassed = false; - } + const auto *ReceiverDynT = + dyn_cast<ObjCObjectPointerType>(DynType.getCanonicalType()); + + if (ReceiverDynT) { + ReceiverT = ReceiverDynT->getObjectType(); - // Handle special cases of '[self classMethod]' and - // '[[self class] classMethod]', which are treated by the compiler as - // instance (not class) messages. We will statically dispatch to those. - if (auto *PT = dyn_cast_or_null<ObjCObjectPointerType>(ReceiverT)) { - // For [self classMethod], return the compiler visible declaration. - if (PT->getObjectType()->isObjCClass() && - Receiver == getSelfSVal().getAsRegion()) - return RuntimeDefinition(findDefiningRedecl(E->getMethodDecl())); - - // Similarly, handle [[self class] classMethod]. - // TODO: We are currently doing a syntactic match for this pattern with is - // limiting as the test cases in Analysis/inlining/InlineObjCClassMethod.m - // shows. A better way would be to associate the meta type with the symbol - // using the dynamic type info tracking and use it here. We can add a new - // SVal for ObjC 'Class' values that know what interface declaration they - // come from. Then 'self' in a class method would be filled in with - // something meaningful in ObjCMethodCall::getReceiverSVal() and we could - // do proper dynamic dispatch for class methods just like we do for - // instance methods now. - if (E->getInstanceReceiver()) - if (const auto *M = dyn_cast<ObjCMessageExpr>(E->getInstanceReceiver())) - if (isCallToSelfClass(M)) + // It can be actually class methods called with Class object as a + // receiver. This type of messages is treated by the compiler as + // instance (not class). + if (ReceiverT->isObjCClass()) { + + SVal SelfVal = getState()->getSelfSVal(getLocationContext()); + // For [self classMethod], return compiler visible declaration. + if (Receiver == SelfVal.getAsRegion()) { return RuntimeDefinition(findDefiningRedecl(E->getMethodDecl())); + } + + // Otherwise, let's check if we know something about the type + // inside of this class object. + if (SymbolRef ReceiverSym = getReceiverSVal().getAsSymbol()) { + DynamicTypeInfo DTI = + getClassObjectDynamicTypeInfo(getState(), ReceiverSym); + if (DTI.isValid()) { + // Let's use this type for lookup. + ReceiverT = + cast<ObjCObjectType>(DTI.getType().getCanonicalType()); + + CanBeSubClassed = DTI.canBeASubClass(); + // And it should be a class method instead. + LookingForInstanceMethod = false; + } + } + } + + if (CanBeSubClassed) + if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterface()) + // Even if `DynamicTypeInfo` told us that it can be + // not necessarily this type, but its descendants, we still want + // to check again if this selector can be actually overridden. + CanBeSubClassed = canBeOverridenInSubclass(IDecl, Sel); + } } // Lookup the instance method implementation. if (ReceiverT) - if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterfaceDecl()) { - // Repeatedly calling lookupPrivateMethod() is expensive, especially - // when in many cases it returns null. We cache the results so - // that repeated queries on the same ObjCIntefaceDecl and Selector - // don't incur the same cost. On some test cases, we can see the - // same query being issued thousands of times. - // - // NOTE: This cache is essentially a "global" variable, but it - // only gets lazily created when we get here. The value of the - // cache probably comes from it being global across ExprEngines, - // where the same queries may get issued. If we are worried about - // concurrency, or possibly loading/unloading ASTs, etc., we may - // need to revisit this someday. In terms of memory, this table - // stays around until clang quits, which also may be bad if we - // need to release memory. - using PrivateMethodKey = std::pair<const ObjCInterfaceDecl *, Selector>; - using PrivateMethodCache = - llvm::DenseMap<PrivateMethodKey, Optional<const ObjCMethodDecl *>>; - - static PrivateMethodCache PMC; - Optional<const ObjCMethodDecl *> &Val = PMC[std::make_pair(IDecl, Sel)]; - - // Query lookupPrivateMethod() if the cache does not hit. - if (!Val.hasValue()) { - Val = IDecl->lookupPrivateMethod(Sel); - - // If the method is a property accessor, we should try to "inline" it - // even if we don't actually have an implementation. - if (!*Val) - if (const ObjCMethodDecl *CompileTimeMD = E->getMethodDecl()) - if (CompileTimeMD->isPropertyAccessor()) { - if (!CompileTimeMD->getSelfDecl() && - isa<ObjCCategoryDecl>(CompileTimeMD->getDeclContext())) { - // If the method is an accessor in a category, and it doesn't - // have a self declaration, first - // try to find the method in a class extension. This - // works around a bug in Sema where multiple accessors - // are synthesized for properties in class - // extensions that are redeclared in a category and the - // the implicit parameters are not filled in for - // the method on the category. - // This ensures we find the accessor in the extension, which - // has the implicit parameters filled in. - auto *ID = CompileTimeMD->getClassInterface(); - for (auto *CatDecl : ID->visible_extensions()) { - Val = CatDecl->getMethod(Sel, - CompileTimeMD->isInstanceMethod()); - if (*Val) - break; - } - } - if (!*Val) - Val = IDecl->lookupInstanceMethod(Sel); - } - } + if (ObjCInterfaceDecl *IDecl = ReceiverT->getInterface()) { + const ObjCMethodDecl *MD = + lookupRuntimeDefinition(IDecl, Sel, LookingForInstanceMethod); - const ObjCMethodDecl *MD = Val.getValue(); if (MD && !MD->hasBody()) MD = MD->getCanonicalDecl(); + if (CanBeSubClassed) return RuntimeDefinition(MD, Receiver); else @@ -1392,17 +1400,20 @@ CallEventManager::getCaller(const StackFrameContext *CalleeCtx, if (CallEventRef<> Out = getCall(CallSite, State, CallerCtx)) return Out; - // All other cases are handled by getCall. - assert(isa<CXXConstructExpr>(CallSite) && - "This is not an inlineable statement"); - SValBuilder &SVB = State->getStateManager().getSValBuilder(); const auto *Ctor = cast<CXXMethodDecl>(CalleeCtx->getDecl()); Loc ThisPtr = SVB.getCXXThis(Ctor, CalleeCtx); SVal ThisVal = State->getSVal(ThisPtr); - return getCXXConstructorCall(cast<CXXConstructExpr>(CallSite), - ThisVal.getAsRegion(), State, CallerCtx); + if (const auto *CE = dyn_cast<CXXConstructExpr>(CallSite)) + return getCXXConstructorCall(CE, ThisVal.getAsRegion(), State, CallerCtx); + else if (const auto *CIE = dyn_cast<CXXInheritedCtorInitExpr>(CallSite)) + return getCXXInheritedConstructorCall(CIE, ThisVal.getAsRegion(), State, + CallerCtx); + else { + // All other cases are handled by getCall. + llvm_unreachable("This is not an inlineable statement"); + } } // Fall back to the CFG. The only thing we haven't handled yet is diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp index 11693132de68..cae728815b41 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp @@ -13,6 +13,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" #include "clang/AST/Decl.h" #include "clang/AST/Expr.h" +#include "clang/Lex/Preprocessor.h" namespace clang { @@ -109,6 +110,43 @@ Nullability getNullabilityAnnotation(QualType Type) { return Nullability::Unspecified; } +llvm::Optional<int> tryExpandAsInteger(StringRef Macro, + const Preprocessor &PP) { + const auto *MacroII = PP.getIdentifierInfo(Macro); + if (!MacroII) + return llvm::None; + const MacroInfo *MI = PP.getMacroInfo(MacroII); + if (!MI) + return llvm::None; + + // Filter out parens. + std::vector<Token> FilteredTokens; + FilteredTokens.reserve(MI->tokens().size()); + for (auto &T : MI->tokens()) + if (!T.isOneOf(tok::l_paren, tok::r_paren)) + FilteredTokens.push_back(T); + + // Parse an integer at the end of the macro definition. + const Token &T = FilteredTokens.back(); + // FIXME: EOF macro token coming from a PCH file on macOS while marked as + // literal, doesn't contain any literal data + if (!T.isLiteral() || !T.getLiteralData()) + return llvm::None; + StringRef ValueStr = StringRef(T.getLiteralData(), T.getLength()); + llvm::APInt IntValue; + constexpr unsigned AutoSenseRadix = 0; + if (ValueStr.getAsInteger(AutoSenseRadix, IntValue)) + return llvm::None; + + // Parse an optional minus sign. + size_t Size = FilteredTokens.size(); + if (Size >= 2) { + if (FilteredTokens[Size - 2].is(tok::minus)) + IntValue = -IntValue; + } + + return IntValue.getSExtValue(); +} -} // end namespace ento -} // end namespace clang +} // namespace ento +} // namespace clang diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp index a9361837cf68..86cecf6524f0 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -61,12 +61,12 @@ void CheckerManager::finishedCheckerRegistration() { } void CheckerManager::reportInvalidCheckerOptionValue( - const CheckerBase *C, StringRef OptionName, StringRef ExpectedValueDesc) { + const CheckerBase *C, StringRef OptionName, + StringRef ExpectedValueDesc) const { - Context.getDiagnostics() - .Report(diag::err_analyzer_checker_option_invalid_input) - << (llvm::Twine() + C->getTagDescription() + ":" + OptionName).str() - << ExpectedValueDesc; + getDiagnostics().Report(diag::err_analyzer_checker_option_invalid_input) + << (llvm::Twine() + C->getTagDescription() + ":" + OptionName).str() + << ExpectedValueDesc; } //===----------------------------------------------------------------------===// @@ -243,13 +243,13 @@ void CheckerManager::runCheckersForObjCMessage(ObjCMessageVisitKind visitKind, const ObjCMethodCall &msg, ExprEngine &Eng, bool WasInlined) { - auto &checkers = getObjCMessageCheckers(visitKind); + const auto &checkers = getObjCMessageCheckers(visitKind); CheckObjCMessageContext C(visitKind, checkers, msg, Eng, WasInlined); expandGraphWithCheckers(C, Dst, Src); } const std::vector<CheckerManager::CheckObjCMessageFunc> & -CheckerManager::getObjCMessageCheckers(ObjCMessageVisitKind Kind) { +CheckerManager::getObjCMessageCheckers(ObjCMessageVisitKind Kind) const { switch (Kind) { case ObjCMessageVisitKind::Pre: return PreObjCMessageCheckers; @@ -507,35 +507,38 @@ namespace { using CheckersTy = std::vector<CheckerManager::CheckNewAllocatorFunc>; const CheckersTy &Checkers; - const CXXNewExpr *NE; - SVal Target; + const CXXAllocatorCall &Call; bool WasInlined; ExprEngine &Eng; - CheckNewAllocatorContext(const CheckersTy &Checkers, const CXXNewExpr *NE, - SVal Target, bool WasInlined, ExprEngine &Eng) - : Checkers(Checkers), NE(NE), Target(Target), WasInlined(WasInlined), - Eng(Eng) {} + CheckNewAllocatorContext(const CheckersTy &Checkers, + const CXXAllocatorCall &Call, bool WasInlined, + ExprEngine &Eng) + : Checkers(Checkers), Call(Call), WasInlined(WasInlined), Eng(Eng) {} CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } CheckersTy::const_iterator checkers_end() { return Checkers.end(); } void runChecker(CheckerManager::CheckNewAllocatorFunc checkFn, NodeBuilder &Bldr, ExplodedNode *Pred) { - ProgramPoint L = PostAllocatorCall(NE, Pred->getLocationContext()); + ProgramPoint L = + PostAllocatorCall(Call.getOriginExpr(), Pred->getLocationContext()); CheckerContext C(Bldr, Eng, Pred, L, WasInlined); - checkFn(NE, Target, C); + checkFn(cast<CXXAllocatorCall>(*Call.cloneWithState(Pred->getState())), + C); } }; } // namespace -void CheckerManager::runCheckersForNewAllocator( - const CXXNewExpr *NE, SVal Target, ExplodedNodeSet &Dst, ExplodedNode *Pred, - ExprEngine &Eng, bool WasInlined) { +void CheckerManager::runCheckersForNewAllocator(const CXXAllocatorCall &Call, + ExplodedNodeSet &Dst, + ExplodedNode *Pred, + ExprEngine &Eng, + bool WasInlined) { ExplodedNodeSet Src; Src.insert(Pred); - CheckNewAllocatorContext C(NewAllocatorCheckers, NE, Target, WasInlined, Eng); + CheckNewAllocatorContext C(NewAllocatorCheckers, Call, WasInlined, Eng); expandGraphWithCheckers(C, Dst, Src); } @@ -650,8 +653,9 @@ CheckerManager::runCheckersForEvalAssume(ProgramStateRef state, void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, const CallEvent &Call, - ExprEngine &Eng) { - for (const auto Pred : Src) { + ExprEngine &Eng, + const EvalCallOptions &CallOpts) { + for (auto *const Pred : Src) { bool anyEvaluated = false; ExplodedNodeSet checkDst; @@ -662,10 +666,8 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, // TODO: Support the situation when the call doesn't correspond // to any Expr. ProgramPoint L = ProgramPoint::getProgramPoint( - cast<CallExpr>(Call.getOriginExpr()), - ProgramPoint::PostStmtKind, - Pred->getLocationContext(), - EvalCallChecker.Checker); + Call.getOriginExpr(), ProgramPoint::PostStmtKind, + Pred->getLocationContext(), EvalCallChecker.Checker); bool evaluated = false; { // CheckerContext generates transitions(populates checkDest) on // destruction, so introduce the scope to make sure it gets properly @@ -687,7 +689,7 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, // If none of the checkers evaluated the call, ask ExprEngine to handle it. if (!anyEvaluated) { NodeBuilder B(Pred, Dst, Eng.getBuilderContext()); - Eng.defaultEvalCall(B, Pred, Call); + Eng.defaultEvalCall(B, Pred, Call, CallOpts); } } } @@ -902,8 +904,3 @@ CheckerManager::getCachedStmtCheckersFor(const Stmt *S, bool isPreVisit) { Checkers.push_back(Info.CheckFn); return Checkers; } - -CheckerManager::~CheckerManager() { - for (const auto &CheckerDtor : CheckerDtors) - CheckerDtor(); -} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerRegistryData.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerRegistryData.cpp new file mode 100644 index 000000000000..1b3e8b11549d --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerRegistryData.cpp @@ -0,0 +1,241 @@ +//===- CheckerRegistry.h - Maintains all available checkers -----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Core/CheckerRegistryData.h" +#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" +#include "llvm/ADT/Twine.h" +#include <map> + +using namespace clang; +using namespace ento; + +//===----------------------------------------------------------------------===// +// Methods of CmdLineOption, PackageInfo and CheckerInfo. +//===----------------------------------------------------------------------===// + +LLVM_DUMP_METHOD void CmdLineOption::dump() const { + dumpToStream(llvm::errs()); +} + +LLVM_DUMP_METHOD void +CmdLineOption::dumpToStream(llvm::raw_ostream &Out) const { + // The description can be just checked in Checkers.inc, the point here is to + // debug whether we succeeded in parsing it. + Out << OptionName << " (" << OptionType << ", " + << (IsHidden ? "hidden, " : "") << DevelopmentStatus << ") default: \"" + << DefaultValStr; +} + +static StringRef toString(StateFromCmdLine Kind) { + switch (Kind) { + case StateFromCmdLine::State_Disabled: + return "Disabled"; + case StateFromCmdLine::State_Enabled: + return "Enabled"; + case StateFromCmdLine::State_Unspecified: + return "Unspecified"; + } + llvm_unreachable("Unhandled StateFromCmdLine enum"); +} + +LLVM_DUMP_METHOD void CheckerInfo::dump() const { dumpToStream(llvm::errs()); } + +LLVM_DUMP_METHOD void CheckerInfo::dumpToStream(llvm::raw_ostream &Out) const { + // The description can be just checked in Checkers.inc, the point here is to + // debug whether we succeeded in parsing it. Same with documentation uri. + Out << FullName << " (" << toString(State) << (IsHidden ? ", hidden" : "") + << ")\n"; + Out << " Options:\n"; + for (const CmdLineOption &Option : CmdLineOptions) { + Out << " "; + Option.dumpToStream(Out); + Out << '\n'; + } + Out << " Dependencies:\n"; + for (const CheckerInfo *Dependency : Dependencies) { + Out << " " << Dependency->FullName << '\n'; + } + Out << " Weak dependencies:\n"; + for (const CheckerInfo *Dependency : WeakDependencies) { + Out << " " << Dependency->FullName << '\n'; + } +} + +LLVM_DUMP_METHOD void PackageInfo::dump() const { dumpToStream(llvm::errs()); } + +LLVM_DUMP_METHOD void PackageInfo::dumpToStream(llvm::raw_ostream &Out) const { + Out << FullName << "\n"; + Out << " Options:\n"; + for (const CmdLineOption &Option : CmdLineOptions) { + Out << " "; + Option.dumpToStream(Out); + Out << '\n'; + } +} + +static constexpr char PackageSeparator = '.'; + +static bool isInPackage(const CheckerInfo &Checker, StringRef PackageName) { + // Does the checker's full name have the package as a prefix? + if (!Checker.FullName.startswith(PackageName)) + return false; + + // Is the package actually just the name of a specific checker? + if (Checker.FullName.size() == PackageName.size()) + return true; + + // Is the checker in the package (or a subpackage)? + if (Checker.FullName[PackageName.size()] == PackageSeparator) + return true; + + return false; +} + +CheckerInfoListRange +CheckerRegistryData::getMutableCheckersForCmdLineArg(StringRef CmdLineArg) { + auto It = checker_registry::binaryFind(Checkers, CmdLineArg); + + if (!isInPackage(*It, CmdLineArg)) + return {Checkers.end(), Checkers.end()}; + + // See how large the package is. + // If the package doesn't exist, assume the option refers to a single + // checker. + size_t Size = 1; + llvm::StringMap<size_t>::const_iterator PackageSize = + PackageSizes.find(CmdLineArg); + + if (PackageSize != PackageSizes.end()) + Size = PackageSize->getValue(); + + return {It, It + Size}; +} +//===----------------------------------------------------------------------===// +// Printing functions. +//===----------------------------------------------------------------------===// + +void CheckerRegistryData::printCheckerWithDescList( + const AnalyzerOptions &AnOpts, raw_ostream &Out, + size_t MaxNameChars) const { + // FIXME: Print available packages. + + Out << "CHECKERS:\n"; + + // Find the maximum option length. + size_t OptionFieldWidth = 0; + for (const auto &Checker : Checkers) { + // Limit the amount of padding we are willing to give up for alignment. + // Package.Name Description [Hidden] + size_t NameLength = Checker.FullName.size(); + if (NameLength <= MaxNameChars) + OptionFieldWidth = std::max(OptionFieldWidth, NameLength); + } + + const size_t InitialPad = 2; + + auto Print = [=](llvm::raw_ostream &Out, const CheckerInfo &Checker, + StringRef Description) { + AnalyzerOptions::printFormattedEntry(Out, {Checker.FullName, Description}, + InitialPad, OptionFieldWidth); + Out << '\n'; + }; + + for (const auto &Checker : Checkers) { + // The order of this if branches is significant, we wouldn't like to display + // developer checkers even in the alpha output. For example, + // alpha.cplusplus.IteratorModeling is a modeling checker, hence it's hidden + // by default, and users (even when the user is a developer of an alpha + // checker) shouldn't normally tinker with whether they should be enabled. + + if (Checker.IsHidden) { + if (AnOpts.ShowCheckerHelpDeveloper) + Print(Out, Checker, Checker.Desc); + continue; + } + + if (Checker.FullName.startswith("alpha")) { + if (AnOpts.ShowCheckerHelpAlpha) + Print(Out, Checker, + ("(Enable only for development!) " + Checker.Desc).str()); + continue; + } + + if (AnOpts.ShowCheckerHelp) + Print(Out, Checker, Checker.Desc); + } +} + +void CheckerRegistryData::printEnabledCheckerList(raw_ostream &Out) const { + for (const auto *i : EnabledCheckers) + Out << i->FullName << '\n'; +} + +void CheckerRegistryData::printCheckerOptionList(const AnalyzerOptions &AnOpts, + raw_ostream &Out) const { + Out << "OVERVIEW: Clang Static Analyzer Checker and Package Option List\n\n"; + Out << "USAGE: -analyzer-config <OPTION1=VALUE,OPTION2=VALUE,...>\n\n"; + Out << " -analyzer-config OPTION1=VALUE, -analyzer-config " + "OPTION2=VALUE, ...\n\n"; + Out << "OPTIONS:\n\n"; + + // It's usually ill-advised to use multimap, but clang will terminate after + // this function. + std::multimap<StringRef, const CmdLineOption &> OptionMap; + + for (const CheckerInfo &Checker : Checkers) { + for (const CmdLineOption &Option : Checker.CmdLineOptions) { + OptionMap.insert({Checker.FullName, Option}); + } + } + + for (const PackageInfo &Package : Packages) { + for (const CmdLineOption &Option : Package.CmdLineOptions) { + OptionMap.insert({Package.FullName, Option}); + } + } + + auto Print = [](llvm::raw_ostream &Out, StringRef FullOption, + StringRef Desc) { + AnalyzerOptions::printFormattedEntry(Out, {FullOption, Desc}, + /*InitialPad*/ 2, + /*EntryWidth*/ 50, + /*MinLineWidth*/ 90); + Out << "\n\n"; + }; + for (const std::pair<const StringRef, const CmdLineOption &> &Entry : + OptionMap) { + const CmdLineOption &Option = Entry.second; + std::string FullOption = (Entry.first + ":" + Option.OptionName).str(); + + std::string Desc = + ("(" + Option.OptionType + ") " + Option.Description + " (default: " + + (Option.DefaultValStr.empty() ? "\"\"" : Option.DefaultValStr) + ")") + .str(); + + // The list of these if branches is significant, we wouldn't like to + // display hidden alpha checker options for + // -analyzer-checker-option-help-alpha. + + if (Option.IsHidden) { + if (AnOpts.ShowCheckerOptionDeveloperList) + Print(Out, FullOption, Desc); + continue; + } + + if (Option.DevelopmentStatus == "alpha" || + Entry.first.startswith("alpha")) { + if (AnOpts.ShowCheckerOptionAlphaList) + Print(Out, FullOption, + llvm::Twine("(Enable only for development!) " + Desc).str()); + continue; + } + + if (AnOpts.ShowCheckerOptionList) + Print(Out, FullOption, Desc); + } +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp index bdae3e605eff..a601370775b4 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp @@ -9,13 +9,18 @@ #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" // Common strings used for the "category" of many static analyzer issues. -namespace clang { namespace ento { namespace categories { +namespace clang { +namespace ento { +namespace categories { -const char * const CoreFoundationObjectiveC = "Core Foundation/Objective-C"; -const char * const LogicError = "Logic error"; -const char * const MemoryRefCount = - "Memory (Core Foundation/Objective-C/OSObject)"; -const char * const MemoryError = "Memory error"; -const char * const UnixAPI = "Unix API"; -const char * const CXXObjectLifecycle = "C++ object lifecycle"; -}}} +const char *const CoreFoundationObjectiveC = "Core Foundation/Objective-C"; +const char *const LogicError = "Logic error"; +const char *const MemoryRefCount = + "Memory (Core Foundation/Objective-C/OSObject)"; +const char *const MemoryError = "Memory error"; +const char *const UnixAPI = "Unix API"; +const char *const CXXObjectLifecycle = "C++ object lifecycle"; +const char *const SecurityError = "Security error"; +} // namespace categories +} // namespace ento +} // namespace clang diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp index 94cf74de8293..70deb13a8e1a 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp @@ -23,8 +23,8 @@ #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/PathSensitive/BlockCounter.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/FunctionSummary.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/WorkList.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" @@ -52,8 +52,7 @@ STATISTIC(NumPathsExplored, // Core analysis engine. //===----------------------------------------------------------------------===// -static std::unique_ptr<WorkList> generateWorkList(AnalyzerOptions &Opts, - SubEngine &subengine) { +static std::unique_ptr<WorkList> generateWorkList(AnalyzerOptions &Opts) { switch (Opts.getExplorationStrategy()) { case ExplorationStrategyKind::DFS: return WorkList::makeDFS(); @@ -71,9 +70,9 @@ static std::unique_ptr<WorkList> generateWorkList(AnalyzerOptions &Opts, llvm_unreachable("Unknown AnalyzerOptions::ExplorationStrategyKind"); } -CoreEngine::CoreEngine(SubEngine &subengine, FunctionSummariesTy *FS, +CoreEngine::CoreEngine(ExprEngine &exprengine, FunctionSummariesTy *FS, AnalyzerOptions &Opts) - : SubEng(subengine), WList(generateWorkList(Opts, subengine)), + : ExprEng(exprengine), WList(generateWorkList(Opts)), BCounterFactory(G.getAllocator()), FunctionSummaries(FS) {} /// ExecuteWorkList - Run the worklist algorithm for a maximum number of steps. @@ -104,7 +103,7 @@ bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned Steps, WList->setBlockCounter(BCounterFactory.GetEmptyCounter()); if (!InitState) - InitState = SubEng.getInitialState(L); + InitState = ExprEng.getInitialState(L); bool IsNew; ExplodedNode *Node = G.getNode(StartLoc, InitState, false, &IsNew); @@ -113,7 +112,7 @@ bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned Steps, NodeBuilderContext BuilderCtx(*this, StartLoc.getDst(), Node); ExplodedNodeSet DstBegin; - SubEng.processBeginOfFunction(BuilderCtx, Node, DstBegin, StartLoc); + ExprEng.processBeginOfFunction(BuilderCtx, Node, DstBegin, StartLoc); enqueue(DstBegin); } @@ -147,7 +146,7 @@ bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned Steps, dispatchWorkItem(Node, Node->getLocation(), WU); } - SubEng.processEndWorklist(); + ExprEng.processEndWorklist(); return WList->hasWork(); } @@ -172,7 +171,7 @@ void CoreEngine::dispatchWorkItem(ExplodedNode* Pred, ProgramPoint Loc, break; case ProgramPoint::CallExitBeginKind: - SubEng.processCallExit(Pred); + ExprEng.processCallExit(Pred); break; case ProgramPoint::EpsilonKind: { @@ -221,7 +220,7 @@ void CoreEngine::HandleBlockEdge(const BlockEdge &L, ExplodedNode *Pred) { if (L.getSrc()->getTerminator().isVirtualBaseBranch() && L.getDst() == *L.getSrc()->succ_begin()) { ProgramPoint P = L.withTag(getNoteTags().makeNoteTag( - [](BugReporterContext &, BugReport &) -> std::string { + [](BugReporterContext &, PathSensitiveBugReport &) -> std::string { // TODO: Just call out the name of the most derived class // when we know it. return "Virtual base initialization skipped because " @@ -253,17 +252,17 @@ void CoreEngine::HandleBlockEdge(const BlockEdge &L, ExplodedNode *Pred) { } // Process the final state transition. - SubEng.processEndOfFunction(BuilderCtx, Pred, RS); + ExprEng.processEndOfFunction(BuilderCtx, Pred, RS); // This path is done. Don't enqueue any more nodes. return; } - // Call into the SubEngine to process entering the CFGBlock. + // Call into the ExprEngine to process entering the CFGBlock. ExplodedNodeSet dstNodes; BlockEntrance BE(Blk, Pred->getLocationContext()); NodeBuilderWithSinks nodeBuilder(Pred, dstNodes, BuilderCtx, BE); - SubEng.processCFGBlockEntrance(L, nodeBuilder, Pred); + ExprEng.processCFGBlockEntrance(L, nodeBuilder, Pred); // Auto-generate a node. if (!nodeBuilder.hasGeneratedNodes()) { @@ -287,7 +286,7 @@ void CoreEngine::HandleBlockEntrance(const BlockEntrance &L, // Process the entrance of the block. if (Optional<CFGElement> E = L.getFirstElement()) { NodeBuilderContext Ctx(*this, L.getBlock(), Pred); - SubEng.processCFGElement(*E, Pred, 0, &Ctx); + ExprEng.processCFGElement(*E, Pred, 0, &Ctx); } else HandleBlockExit(L.getBlock(), Pred); @@ -367,7 +366,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { builder(Pred, B, cast<IndirectGotoStmt>(Term)->getTarget(), *(B->succ_begin()), this); - SubEng.processIndirectGoto(builder); + ExprEng.processIndirectGoto(builder); return; } @@ -378,7 +377,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { // 'element' variable to a value. // (2) in a terminator, which represents the branch. // - // For (1), subengines will bind a value (i.e., 0 or 1) indicating + // For (1), ExprEngine will bind a value (i.e., 0 or 1) indicating // whether or not collection contains any more elements. We cannot // just test to see if the element is nil because a container can // contain nil elements. @@ -389,7 +388,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { SwitchNodeBuilder builder(Pred, B, cast<SwitchStmt>(Term)->getCond(), this); - SubEng.processSwitch(builder); + ExprEng.processSwitch(builder); return; } @@ -418,7 +417,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { void CoreEngine::HandleCallEnter(const CallEnter &CE, ExplodedNode *Pred) { NodeBuilderContext BuilderCtx(*this, CE.getEntry(), Pred); - SubEng.processCallEnter(BuilderCtx, CE, Pred); + ExprEng.processCallEnter(BuilderCtx, CE, Pred); } void CoreEngine::HandleBranch(const Stmt *Cond, const Stmt *Term, @@ -426,7 +425,7 @@ void CoreEngine::HandleBranch(const Stmt *Cond, const Stmt *Term, assert(B->succ_size() == 2); NodeBuilderContext Ctx(*this, B, Pred); ExplodedNodeSet Dst; - SubEng.processBranch(Cond, Ctx, Pred, Dst, *(B->succ_begin()), + ExprEng.processBranch(Cond, Ctx, Pred, Dst, *(B->succ_begin()), *(B->succ_begin() + 1)); // Enqueue the new frontier onto the worklist. enqueue(Dst); @@ -438,7 +437,7 @@ void CoreEngine::HandleCleanupTemporaryBranch(const CXXBindTemporaryExpr *BTE, assert(B->succ_size() == 2); NodeBuilderContext Ctx(*this, B, Pred); ExplodedNodeSet Dst; - SubEng.processCleanupTemporaryBranch(BTE, Ctx, Pred, Dst, *(B->succ_begin()), + ExprEng.processCleanupTemporaryBranch(BTE, Ctx, Pred, Dst, *(B->succ_begin()), *(B->succ_begin() + 1)); // Enqueue the new frontier onto the worklist. enqueue(Dst); @@ -449,7 +448,7 @@ void CoreEngine::HandleStaticInit(const DeclStmt *DS, const CFGBlock *B, assert(B->succ_size() == 2); NodeBuilderContext Ctx(*this, B, Pred); ExplodedNodeSet Dst; - SubEng.processStaticInitializer(DS, Ctx, Pred, Dst, + ExprEng.processStaticInitializer(DS, Ctx, Pred, Dst, *(B->succ_begin()), *(B->succ_begin()+1)); // Enqueue the new frontier onto the worklist. enqueue(Dst); @@ -464,7 +463,7 @@ void CoreEngine::HandlePostStmt(const CFGBlock *B, unsigned StmtIdx, HandleBlockExit(B, Pred); else { NodeBuilderContext Ctx(*this, B, Pred); - SubEng.processCFGElement((*B)[StmtIdx], Pred, StmtIdx, &Ctx); + ExprEng.processCFGElement((*B)[StmtIdx], Pred, StmtIdx, &Ctx); } } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/DynamicSize.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/DynamicSize.cpp new file mode 100644 index 000000000000..8b2172db445c --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/DynamicSize.cpp @@ -0,0 +1,71 @@ +//===- DynamicSize.cpp - Dynamic size related APIs --------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines APIs that track and query dynamic size information. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" +#include "clang/AST/Expr.h" +#include "clang/Basic/LLVM.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" + +namespace clang { +namespace ento { + +DefinedOrUnknownSVal getDynamicSize(ProgramStateRef State, const MemRegion *MR, + SValBuilder &SVB) { + return MR->getMemRegionManager().getStaticSize(MR, SVB); +} + +DefinedOrUnknownSVal getDynamicElementCount(ProgramStateRef State, + const MemRegion *MR, + SValBuilder &SVB, + QualType ElementTy) { + MemRegionManager &MemMgr = MR->getMemRegionManager(); + ASTContext &Ctx = MemMgr.getContext(); + + DefinedOrUnknownSVal Size = getDynamicSize(State, MR, SVB); + SVal ElementSizeV = SVB.makeIntVal( + Ctx.getTypeSizeInChars(ElementTy).getQuantity(), SVB.getArrayIndexType()); + + SVal DivisionV = + SVB.evalBinOp(State, BO_Div, Size, ElementSizeV, SVB.getArrayIndexType()); + + return DivisionV.castAs<DefinedOrUnknownSVal>(); +} + +SVal getDynamicSizeWithOffset(ProgramStateRef State, const SVal &BufV) { + SValBuilder &SvalBuilder = State->getStateManager().getSValBuilder(); + const MemRegion *MRegion = BufV.getAsRegion(); + if (!MRegion) + return UnknownVal(); + RegionOffset Offset = MRegion->getAsOffset(); + if (Offset.hasSymbolicOffset()) + return UnknownVal(); + const MemRegion *BaseRegion = MRegion->getBaseRegion(); + if (!BaseRegion) + return UnknownVal(); + + NonLoc OffsetInBytes = SvalBuilder.makeArrayIndex( + Offset.getOffset() / + MRegion->getMemRegionManager().getContext().getCharWidth()); + DefinedOrUnknownSVal ExtentInBytes = + getDynamicSize(State, BaseRegion, SvalBuilder); + + return SvalBuilder.evalBinOp(State, BinaryOperator::Opcode::BO_Sub, + ExtentInBytes, OffsetInBytes, + SvalBuilder.getArrayIndexType()); +} + +} // namespace ento +} // namespace clang diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/DynamicType.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/DynamicType.cpp index a78e0e05e903..e9b64fd79614 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/DynamicType.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/DynamicType.cpp @@ -34,6 +34,10 @@ REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(CastSet, clang::ento::DynamicCastInfo) REGISTER_MAP_WITH_PROGRAMSTATE(DynamicCastMap, const clang::ento::MemRegion *, CastSet) +// A map from Class object symbols to the most likely pointed-to type. +REGISTER_MAP_WITH_PROGRAMSTATE(DynamicClassObjectMap, clang::ento::SymbolRef, + clang::ento::DynamicTypeInfo) + namespace clang { namespace ento { @@ -76,6 +80,12 @@ const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State, return nullptr; } +DynamicTypeInfo getClassObjectDynamicTypeInfo(ProgramStateRef State, + SymbolRef Sym) { + const DynamicTypeInfo *DTI = State->get<DynamicClassObjectMap>(Sym); + return DTI ? *DTI : DynamicTypeInfo{}; +} + ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, DynamicTypeInfo NewTy) { State = State->set<DynamicTypeMap>(MR->StripCasts(), NewTy); @@ -118,111 +128,165 @@ ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State, return State; } +ProgramStateRef setClassObjectDynamicTypeInfo(ProgramStateRef State, + SymbolRef Sym, + DynamicTypeInfo NewTy) { + State = State->set<DynamicClassObjectMap>(Sym, NewTy); + return State; +} + +ProgramStateRef setClassObjectDynamicTypeInfo(ProgramStateRef State, + SymbolRef Sym, QualType NewTy, + bool CanBeSubClassed) { + return setClassObjectDynamicTypeInfo(State, Sym, + DynamicTypeInfo(NewTy, CanBeSubClassed)); +} + +static bool isLive(SymbolReaper &SR, const MemRegion *MR) { + return SR.isLiveRegion(MR); +} + +static bool isLive(SymbolReaper &SR, SymbolRef Sym) { return SR.isLive(Sym); } + template <typename MapTy> -ProgramStateRef removeDead(ProgramStateRef State, const MapTy &Map, - SymbolReaper &SR) { +static ProgramStateRef removeDeadImpl(ProgramStateRef State, SymbolReaper &SR) { + const auto &Map = State->get<MapTy>(); + for (const auto &Elem : Map) - if (!SR.isLiveRegion(Elem.first)) - State = State->remove<DynamicCastMap>(Elem.first); + if (!isLive(SR, Elem.first)) + State = State->remove<MapTy>(Elem.first); return State; } ProgramStateRef removeDeadTypes(ProgramStateRef State, SymbolReaper &SR) { - return removeDead(State, State->get<DynamicTypeMap>(), SR); + return removeDeadImpl<DynamicTypeMap>(State, SR); } ProgramStateRef removeDeadCasts(ProgramStateRef State, SymbolReaper &SR) { - return removeDead(State, State->get<DynamicCastMap>(), SR); + return removeDeadImpl<DynamicCastMap>(State, SR); } -static void printDynamicTypesJson(raw_ostream &Out, ProgramStateRef State, - const char *NL, unsigned int Space, - bool IsDot) { - Indent(Out, Space, IsDot) << "\"dynamic_types\": "; +ProgramStateRef removeDeadClassObjectTypes(ProgramStateRef State, + SymbolReaper &SR) { + return removeDeadImpl<DynamicClassObjectMap>(State, SR); +} - const DynamicTypeMapTy &Map = State->get<DynamicTypeMap>(); - if (Map.isEmpty()) { - Out << "null," << NL; - return; - } +//===----------------------------------------------------------------------===// +// Implementation of the 'printer-to-JSON' function +//===----------------------------------------------------------------------===// - ++Space; - Out << '[' << NL; - for (DynamicTypeMapTy::iterator I = Map.begin(); I != Map.end(); ++I) { - const MemRegion *MR = I->first; - const DynamicTypeInfo &DTI = I->second; - Indent(Out, Space, IsDot) - << "{ \"region\": \"" << MR << "\", \"dyn_type\": "; - if (!DTI.isValid()) { - Out << "null"; - } else { - Out << '\"' << DTI.getType()->getPointeeType().getAsString() - << "\", \"sub_classable\": " - << (DTI.canBeASubClass() ? "true" : "false"); - } - Out << " }"; - - if (std::next(I) != Map.end()) - Out << ','; - Out << NL; +static raw_ostream &printJson(const MemRegion *Region, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + return Out << "\"region\": \"" << Region << "\""; +} + +static raw_ostream &printJson(const SymExpr *Symbol, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + return Out << "\"symbol\": \"" << Symbol << "\""; +} + +static raw_ostream &printJson(const DynamicTypeInfo &DTI, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + Out << "\"dyn_type\": "; + if (!DTI.isValid()) { + Out << "null"; + } else { + QualType ToPrint = DTI.getType(); + if (ToPrint->isAnyPointerType()) + ToPrint = ToPrint->getPointeeType(); + + Out << '\"' << ToPrint.getAsString() << "\", \"sub_classable\": " + << (DTI.canBeASubClass() ? "true" : "false"); } + return Out; +} - --Space; - Indent(Out, Space, IsDot) << "]," << NL; +static raw_ostream &printJson(const DynamicCastInfo &DCI, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + return Out << "\"from\": \"" << DCI.from().getAsString() << "\", \"to\": \"" + << DCI.to().getAsString() << "\", \"kind\": \"" + << (DCI.succeeds() ? "success" : "fail") << "\""; } -static void printDynamicCastsJson(raw_ostream &Out, ProgramStateRef State, - const char *NL, unsigned int Space, - bool IsDot) { - Indent(Out, Space, IsDot) << "\"dynamic_casts\": "; +template <class T, class U> +static raw_ostream &printJson(const std::pair<T, U> &Pair, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + printJson(Pair.first, Out, NL, Space, IsDot) << ", "; + return printJson(Pair.second, Out, NL, Space, IsDot); +} - const DynamicCastMapTy &Map = State->get<DynamicCastMap>(); - if (Map.isEmpty()) { - Out << "null," << NL; - return; +template <class ContainerTy> +static raw_ostream &printJsonContainer(const ContainerTy &Container, + raw_ostream &Out, const char *NL, + unsigned int Space, bool IsDot) { + if (Container.isEmpty()) { + return Out << "null"; } ++Space; Out << '[' << NL; - for (DynamicCastMapTy::iterator I = Map.begin(); I != Map.end(); ++I) { - const MemRegion *MR = I->first; - const CastSet &Set = I->second; - - Indent(Out, Space, IsDot) << "{ \"region\": \"" << MR << "\", \"casts\": "; - if (Set.isEmpty()) { - Out << "null "; - } else { - ++Space; - Out << '[' << NL; - for (CastSet::iterator SI = Set.begin(); SI != Set.end(); ++SI) { - Indent(Out, Space, IsDot) - << "{ \"from\": \"" << SI->from().getAsString() << "\", \"to\": \"" - << SI->to().getAsString() << "\", \"kind\": \"" - << (SI->succeeds() ? "success" : "fail") << "\" }"; - - if (std::next(SI) != Set.end()) - Out << ','; - Out << NL; - } - --Space; - Indent(Out, Space, IsDot) << ']'; - } - Out << '}'; - - if (std::next(I) != Map.end()) + for (auto I = Container.begin(); I != Container.end(); ++I) { + const auto &Element = *I; + + Indent(Out, Space, IsDot) << "{ "; + printJson(Element, Out, NL, Space, IsDot) << " }"; + + if (std::next(I) != Container.end()) Out << ','; Out << NL; } --Space; - Indent(Out, Space, IsDot) << "]," << NL; + return Indent(Out, Space, IsDot) << "]"; +} + +static raw_ostream &printJson(const CastSet &Set, raw_ostream &Out, + const char *NL, unsigned int Space, bool IsDot) { + Out << "\"casts\": "; + return printJsonContainer(Set, Out, NL, Space, IsDot); +} + +template <class MapTy> +static void printJsonImpl(raw_ostream &Out, ProgramStateRef State, + const char *Name, const char *NL, unsigned int Space, + bool IsDot, bool PrintEvenIfEmpty = true) { + const auto &Map = State->get<MapTy>(); + if (Map.isEmpty() && !PrintEvenIfEmpty) + return; + + Indent(Out, Space, IsDot) << "\"" << Name << "\": "; + printJsonContainer(Map, Out, NL, Space, IsDot) << "," << NL; +} + +static void printDynamicTypesJson(raw_ostream &Out, ProgramStateRef State, + const char *NL, unsigned int Space, + bool IsDot) { + printJsonImpl<DynamicTypeMap>(Out, State, "dynamic_types", NL, Space, IsDot); +} + +static void printDynamicCastsJson(raw_ostream &Out, ProgramStateRef State, + const char *NL, unsigned int Space, + bool IsDot) { + printJsonImpl<DynamicCastMap>(Out, State, "dynamic_casts", NL, Space, IsDot); +} + +static void printClassObjectDynamicTypesJson(raw_ostream &Out, + ProgramStateRef State, + const char *NL, unsigned int Space, + bool IsDot) { + // Let's print Class object type information only if we have something + // meaningful to print. + printJsonImpl<DynamicClassObjectMap>(Out, State, "class_object_types", NL, + Space, IsDot, + /*PrintEvenIfEmpty=*/false); } void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State, const char *NL, unsigned int Space, bool IsDot) { printDynamicTypesJson(Out, State, NL, Space, IsDot); printDynamicCastsJson(Out, State, NL, Space, IsDot); + printClassObjectDynamicTypesJson(Out, State, NL, Space, IsDot); } } // namespace ento diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Environment.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Environment.cpp index 1ccf4c6104a6..9e6d79bb7dcc 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Environment.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Environment.cpp @@ -183,12 +183,18 @@ EnvironmentManager::removeDeadBindings(Environment Env, F.getTreeFactory()); // Iterate over the block-expr bindings. - for (Environment::iterator I = Env.begin(), E = Env.end(); - I != E; ++I) { + for (Environment::iterator I = Env.begin(), E = Env.end(); I != E; ++I) { const EnvironmentEntry &BlkExpr = I.getKey(); const SVal &X = I.getData(); - if (SymReaper.isLive(BlkExpr.getStmt(), BlkExpr.getLocationContext())) { + const bool IsBlkExprLive = + SymReaper.isLive(BlkExpr.getStmt(), BlkExpr.getLocationContext()); + + assert((isa<Expr>(BlkExpr.getStmt()) || !IsBlkExprLive) && + "Only Exprs can be live, LivenessAnalysis argues about the liveness " + "of *values*!"); + + if (IsBlkExprLive) { // Copy the binding to the new map. EBMapRef = EBMapRef.add(BlkExpr, X); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp index c4838492271c..635495e9bf60 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp @@ -50,9 +50,8 @@ ExplodedGraph::~ExplodedGraph() = default; bool ExplodedGraph::isInterestingLValueExpr(const Expr *Ex) { if (!Ex->isLValue()) return false; - return isa<DeclRefExpr>(Ex) || - isa<MemberExpr>(Ex) || - isa<ObjCIvarRefExpr>(Ex); + return isa<DeclRefExpr>(Ex) || isa<MemberExpr>(Ex) || + isa<ObjCIvarRefExpr>(Ex) || isa<ArraySubscriptExpr>(Ex); } bool ExplodedGraph::shouldCollect(const ExplodedNode *node) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index b542cf2c0303..265dcd134213 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1210,9 +1210,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, switch (S->getStmtClass()) { // C++, OpenMP and ARC stuff we don't support yet. - case Expr::ObjCIndirectCopyRestoreExprClass: case Stmt::CXXDependentScopeMemberExprClass: - case Stmt::CXXInheritedCtorInitExprClass: case Stmt::CXXTryStmtClass: case Stmt::CXXTypeidExprClass: case Stmt::CXXUuidofExprClass: @@ -1226,6 +1224,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::UnresolvedLookupExprClass: case Stmt::UnresolvedMemberExprClass: case Stmt::TypoExprClass: + case Stmt::RecoveryExprClass: case Stmt::CXXNoexceptExprClass: case Stmt::PackExpansionExprClass: case Stmt::SubstNonTypeTemplateParmPackExprClass: @@ -1258,6 +1257,8 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::OMPTaskwaitDirectiveClass: case Stmt::OMPTaskgroupDirectiveClass: case Stmt::OMPFlushDirectiveClass: + case Stmt::OMPDepobjDirectiveClass: + case Stmt::OMPScanDirectiveClass: case Stmt::OMPOrderedDirectiveClass: case Stmt::OMPAtomicDirectiveClass: case Stmt::OMPTargetDirectiveClass: @@ -1411,6 +1412,8 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::SubstNonTypeTemplateParmExprClass: case Stmt::CXXNullPtrLiteralExprClass: case Stmt::OMPArraySectionExprClass: + case Stmt::OMPArrayShapingExprClass: + case Stmt::OMPIteratorExprClass: case Stmt::TypeTraitExprClass: { Bldr.takeNodes(Pred); ExplodedNodeSet preVisit; @@ -1511,6 +1514,10 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, Bldr.addNodes(Dst); break; + case Stmt::MatrixSubscriptExprClass: + llvm_unreachable("Support for MatrixSubscriptExpr is not implemented."); + break; + case Stmt::GCCAsmStmtClass: Bldr.takeNodes(Pred); VisitGCCAsmStmt(cast<GCCAsmStmt>(S), Pred, Dst); @@ -1618,6 +1625,13 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, Bldr.addNodes(Dst); break; + case Stmt::CXXInheritedCtorInitExprClass: + Bldr.takeNodes(Pred); + VisitCXXInheritedCtorInitExpr(cast<CXXInheritedCtorInitExpr>(S), Pred, + Dst); + Bldr.addNodes(Dst); + break; + case Stmt::CXXNewExprClass: { Bldr.takeNodes(Pred); @@ -1638,8 +1652,10 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, ExplodedNodeSet PreVisit; const auto *CDE = cast<CXXDeleteExpr>(S); getCheckerManager().runCheckersForPreStmt(PreVisit, Pred, S, *this); + ExplodedNodeSet PostVisit; + getCheckerManager().runCheckersForPostStmt(PostVisit, PreVisit, S, *this); - for (const auto i : PreVisit) + for (const auto i : PostVisit) VisitCXXDeleteExpr(CDE, i, Dst); Bldr.addNodes(Dst); @@ -1705,7 +1721,8 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::CXXConstCastExprClass: case Stmt::CXXFunctionalCastExprClass: case Stmt::BuiltinBitCastExprClass: - case Stmt::ObjCBridgedCastExprClass: { + case Stmt::ObjCBridgedCastExprClass: + case Stmt::CXXAddrspaceCastExprClass: { Bldr.takeNodes(Pred); const auto *C = cast<CastExpr>(S); ExplodedNodeSet dstExpr; @@ -1852,6 +1869,21 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, Bldr.addNodes(Dst); break; } + + case Expr::ObjCIndirectCopyRestoreExprClass: { + // ObjCIndirectCopyRestoreExpr implies passing a temporary for + // correctness of lifetime management. Due to limited analysis + // of ARC, this is implemented as direct arg passing. + Bldr.takeNodes(Pred); + ProgramStateRef state = Pred->getState(); + const auto *OIE = cast<ObjCIndirectCopyRestoreExpr>(S); + const Expr *E = OIE->getSubExpr(); + SVal V = state->getSVal(E, Pred->getLocationContext()); + Bldr.generateNode(S, Pred, + state->BindExpr(S, Pred->getLocationContext(), V)); + Bldr.addNodes(Dst); + break; + } } } @@ -3161,11 +3193,13 @@ std::string ExprEngine::DumpGraph(bool trim, StringRef Filename) { return DumpGraph(Src, Filename); } else { return llvm::WriteGraph(&G, "ExprEngine", /*ShortNames=*/false, - /*Title=*/"Exploded Graph", /*Filename=*/Filename); + /*Title=*/"Exploded Graph", + /*Filename=*/std::string(Filename)); } -#endif +#else llvm::errs() << "Warning: dumping graph requires assertions" << "\n"; return ""; +#endif } std::string ExprEngine::DumpGraph(ArrayRef<const ExplodedNode*> Nodes, @@ -3179,7 +3213,7 @@ std::string ExprEngine::DumpGraph(ArrayRef<const ExplodedNode*> Nodes, return llvm::WriteGraph(TrimmedG.get(), "TrimmedExprEngine", /*ShortNames=*/false, /*Title=*/"Trimmed Exploded Graph", - /*Filename=*/Filename); + /*Filename=*/std::string(Filename)); } #endif llvm::errs() << "Warning: dumping graph requires assertions" << "\n"; @@ -3190,3 +3224,5 @@ void *ProgramStateTrait<ReplayWithoutInlining>::GDMIndex() { static int index = 0; return &index; } + +void ExprEngine::anchor() { } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp index b17f26aa9c53..c5e38cc7423d 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -218,7 +218,7 @@ void ExprEngine::VisitBlockExpr(const BlockExpr *BE, ExplodedNode *Pred, auto CE = BD->capture_end(); for (; I != E; ++I) { const VarRegion *capturedR = I.getCapturedRegion(); - const VarRegion *originalR = I.getOriginalRegion(); + const TypedValueRegion *originalR = I.getOriginalRegion(); // If the capture had a copy expression, use the result of evaluating // that expression, otherwise use the original value. @@ -573,6 +573,18 @@ void ExprEngine::VisitCompoundLiteralExpr(const CompoundLiteralExpr *CL, void ExprEngine::VisitDeclStmt(const DeclStmt *DS, ExplodedNode *Pred, ExplodedNodeSet &Dst) { + if (isa<TypedefNameDecl>(*DS->decl_begin())) { + // C99 6.7.7 "Any array size expressions associated with variable length + // array declarators are evaluated each time the declaration of the typedef + // name is reached in the order of execution." + // The checkers should know about typedef to be able to handle VLA size + // expressions. + ExplodedNodeSet DstPre; + getCheckerManager().runCheckersForPreStmt(DstPre, Pred, DS, *this); + getCheckerManager().runCheckersForPostStmt(Dst, DstPre, DS, *this); + return; + } + // Assumption: The CFG has one DeclStmt per Decl. const VarDecl *VD = dyn_cast_or_null<VarDecl>(*DS->decl_begin()); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index b816aab7c18f..38a680eb04c0 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -109,15 +109,14 @@ SVal ExprEngine::makeZeroElementRegion(ProgramStateRef State, SVal LValue, return LValue; } -std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( +SVal ExprEngine::computeObjectUnderConstruction( const Expr *E, ProgramStateRef State, const LocationContext *LCtx, const ConstructionContext *CC, EvalCallOptions &CallOpts) { SValBuilder &SVB = getSValBuilder(); MemRegionManager &MRMgr = SVB.getRegionManager(); ASTContext &ACtx = SVB.getContext(); - // See if we're constructing an existing region by looking at the - // current construction context. + // Compute the target region by exploring the construction context. if (CC) { switch (CC->getKind()) { case ConstructionContext::CXX17ElidedCopyVariableKind: @@ -125,13 +124,9 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( const auto *DSCC = cast<VariableConstructionContext>(CC); const auto *DS = DSCC->getDeclStmt(); const auto *Var = cast<VarDecl>(DS->getSingleDecl()); - SVal LValue = State->getLValue(Var, LCtx); QualType Ty = Var->getType(); - LValue = - makeZeroElementRegion(State, LValue, Ty, CallOpts.IsArrayCtorOrDtor); - State = - addObjectUnderConstruction(State, DSCC->getDeclStmt(), LCtx, LValue); - return std::make_pair(State, LValue); + return makeZeroElementRegion(State, State->getLValue(Var, LCtx), Ty, + CallOpts.IsArrayCtorOrDtor); } case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind: case ConstructionContext::SimpleConstructorInitializerKind: { @@ -139,8 +134,7 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( const auto *Init = ICC->getCXXCtorInitializer(); assert(Init->isAnyMemberInitializer()); const CXXMethodDecl *CurCtor = cast<CXXMethodDecl>(LCtx->getDecl()); - Loc ThisPtr = - SVB.getCXXThis(CurCtor, LCtx->getStackFrame()); + Loc ThisPtr = SVB.getCXXThis(CurCtor, LCtx->getStackFrame()); SVal ThisVal = State->getSVal(ThisPtr); const ValueDecl *Field; @@ -154,10 +148,8 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( } QualType Ty = Field->getType(); - FieldVal = makeZeroElementRegion(State, FieldVal, Ty, - CallOpts.IsArrayCtorOrDtor); - State = addObjectUnderConstruction(State, Init, LCtx, FieldVal); - return std::make_pair(State, FieldVal); + return makeZeroElementRegion(State, FieldVal, Ty, + CallOpts.IsArrayCtorOrDtor); } case ConstructionContext::NewAllocatedObjectKind: { if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) { @@ -170,11 +162,10 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( // TODO: In fact, we need to call the constructor for every // allocated element, not just the first one! CallOpts.IsArrayCtorOrDtor = true; - return std::make_pair( - State, loc::MemRegionVal(getStoreManager().GetElementZeroRegion( - MR, NE->getType()->getPointeeType()))); + return loc::MemRegionVal(getStoreManager().GetElementZeroRegion( + MR, NE->getType()->getPointeeType())); } - return std::make_pair(State, V); + return V; } // TODO: Detect when the allocator returns a null pointer. // Constructor shall not be called in this case. @@ -202,7 +193,7 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( CallerLCtx = CallerLCtx->getParent(); assert(!isa<BlockInvocationContext>(CallerLCtx)); } - return prepareForObjectConstruction( + return computeObjectUnderConstruction( cast<Expr>(SFC->getCallSite()), State, CallerLCtx, RTC->getConstructionContext(), CallOpts); } else { @@ -223,64 +214,46 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( assert(RetE && "Void returns should not have a construction context"); QualType ReturnTy = RetE->getType(); QualType RegionTy = ACtx.getPointerType(ReturnTy); - SVal V = SVB.conjureSymbolVal(&TopLevelSymRegionTag, RetE, SFC, - RegionTy, currBldrCtx->blockCount()); - return std::make_pair(State, V); + return SVB.conjureSymbolVal(&TopLevelSymRegionTag, RetE, SFC, RegionTy, + currBldrCtx->blockCount()); } llvm_unreachable("Unhandled return value construction context!"); } case ConstructionContext::ElidedTemporaryObjectKind: { assert(AMgr.getAnalyzerOptions().ShouldElideConstructors); const auto *TCC = cast<ElidedTemporaryObjectConstructionContext>(CC); - const CXXBindTemporaryExpr *BTE = TCC->getCXXBindTemporaryExpr(); - const MaterializeTemporaryExpr *MTE = TCC->getMaterializedTemporaryExpr(); - const CXXConstructExpr *CE = TCC->getConstructorAfterElision(); // Support pre-C++17 copy elision. We'll have the elidable copy // constructor in the AST and in the CFG, but we'll skip it // and construct directly into the final object. This call // also sets the CallOpts flags for us. - SVal V; // If the elided copy/move constructor is not supported, there's still // benefit in trying to model the non-elided constructor. // Stash our state before trying to elide, as it'll get overwritten. ProgramStateRef PreElideState = State; EvalCallOptions PreElideCallOpts = CallOpts; - std::tie(State, V) = prepareForObjectConstruction( - CE, State, LCtx, TCC->getConstructionContextAfterElision(), CallOpts); + SVal V = computeObjectUnderConstruction( + TCC->getConstructorAfterElision(), State, LCtx, + TCC->getConstructionContextAfterElision(), CallOpts); // FIXME: This definition of "copy elision has not failed" is unreliable. // It doesn't indicate that the constructor will actually be inlined - // later; it is still up to evalCall() to decide. - if (!CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion) { - // Remember that we've elided the constructor. - State = addObjectUnderConstruction(State, CE, LCtx, V); - - // Remember that we've elided the destructor. - if (BTE) - State = elideDestructor(State, BTE, LCtx); - - // Instead of materialization, shamelessly return - // the final object destination. - if (MTE) - State = addObjectUnderConstruction(State, MTE, LCtx, V); - - return std::make_pair(State, V); - } else { - // Copy elision failed. Revert the changes and proceed as if we have - // a simple temporary. - State = PreElideState; - CallOpts = PreElideCallOpts; - } + // later; this is still up to evalCall() to decide. + if (!CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion) + return V; + + // Copy elision failed. Revert the changes and proceed as if we have + // a simple temporary. + CallOpts = PreElideCallOpts; + CallOpts.IsElidableCtorThatHasNotBeenElided = true; LLVM_FALLTHROUGH; } case ConstructionContext::SimpleTemporaryObjectKind: { const auto *TCC = cast<TemporaryObjectConstructionContext>(CC); - const CXXBindTemporaryExpr *BTE = TCC->getCXXBindTemporaryExpr(); const MaterializeTemporaryExpr *MTE = TCC->getMaterializedTemporaryExpr(); - SVal V = UnknownVal(); + CallOpts.IsTemporaryCtorOrDtor = true; if (MTE) { if (const ValueDecl *VD = MTE->getExtendingDecl()) { assert(MTE->getStorageDuration() != SD_FullExpression); @@ -296,20 +269,10 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( if (MTE->getStorageDuration() == SD_Static || MTE->getStorageDuration() == SD_Thread) - V = loc::MemRegionVal(MRMgr.getCXXStaticTempObjectRegion(E)); + return loc::MemRegionVal(MRMgr.getCXXStaticTempObjectRegion(E)); } - if (V.isUnknown()) - V = loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)); - - if (BTE) - State = addObjectUnderConstruction(State, BTE, LCtx, V); - - if (MTE) - State = addObjectUnderConstruction(State, MTE, LCtx, V); - - CallOpts.IsTemporaryCtorOrDtor = true; - return std::make_pair(State, V); + return loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)); } case ConstructionContext::ArgumentKind: { // Arguments are technically temporaries. @@ -318,10 +281,8 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( const auto *ACC = cast<ArgumentConstructionContext>(CC); const Expr *E = ACC->getCallLikeExpr(); unsigned Idx = ACC->getIndex(); - const CXXBindTemporaryExpr *BTE = ACC->getCXXBindTemporaryExpr(); CallEventManager &CEMgr = getStateManager().getCallEventManager(); - SVal V = UnknownVal(); auto getArgLoc = [&](CallEventRef<> Caller) -> Optional<SVal> { const LocationContext *FutureSFC = Caller->getCalleeStackFrame(currBldrCtx->blockCount()); @@ -342,76 +303,171 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction( // Operator arguments do not correspond to operator parameters // because this-argument is implemented as a normal argument in // operator call expressions but not in operator declarations. - const VarRegion *VR = Caller->getParameterLocation( + const TypedValueRegion *TVR = Caller->getParameterLocation( *Caller->getAdjustedParameterIndex(Idx), currBldrCtx->blockCount()); - if (!VR) + if (!TVR) return None; - return loc::MemRegionVal(VR); + return loc::MemRegionVal(TVR); }; if (const auto *CE = dyn_cast<CallExpr>(E)) { CallEventRef<> Caller = CEMgr.getSimpleCall(CE, State, LCtx); - if (auto OptV = getArgLoc(Caller)) - V = *OptV; + if (Optional<SVal> V = getArgLoc(Caller)) + return *V; else break; - State = addObjectUnderConstruction(State, {CE, Idx}, LCtx, V); } else if (const auto *CCE = dyn_cast<CXXConstructExpr>(E)) { // Don't bother figuring out the target region for the future // constructor because we won't need it. CallEventRef<> Caller = CEMgr.getCXXConstructorCall(CCE, /*Target=*/nullptr, State, LCtx); - if (auto OptV = getArgLoc(Caller)) - V = *OptV; + if (Optional<SVal> V = getArgLoc(Caller)) + return *V; else break; - State = addObjectUnderConstruction(State, {CCE, Idx}, LCtx, V); } else if (const auto *ME = dyn_cast<ObjCMessageExpr>(E)) { CallEventRef<> Caller = CEMgr.getObjCMethodCall(ME, State, LCtx); - if (auto OptV = getArgLoc(Caller)) - V = *OptV; + if (Optional<SVal> V = getArgLoc(Caller)) + return *V; else break; - State = addObjectUnderConstruction(State, {ME, Idx}, LCtx, V); + } + } + } // switch (CC->getKind()) + } + + // If we couldn't find an existing region to construct into, assume we're + // constructing a temporary. Notify the caller of our failure. + CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true; + return loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)); +} + +ProgramStateRef ExprEngine::updateObjectsUnderConstruction( + SVal V, const Expr *E, ProgramStateRef State, const LocationContext *LCtx, + const ConstructionContext *CC, const EvalCallOptions &CallOpts) { + if (CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion) { + // Sounds like we failed to find the target region and therefore + // copy elision failed. There's nothing we can do about it here. + return State; + } + + // See if we're constructing an existing region by looking at the + // current construction context. + assert(CC && "Computed target region without construction context?"); + switch (CC->getKind()) { + case ConstructionContext::CXX17ElidedCopyVariableKind: + case ConstructionContext::SimpleVariableKind: { + const auto *DSCC = cast<VariableConstructionContext>(CC); + return addObjectUnderConstruction(State, DSCC->getDeclStmt(), LCtx, V); + } + case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind: + case ConstructionContext::SimpleConstructorInitializerKind: { + const auto *ICC = cast<ConstructorInitializerConstructionContext>(CC); + return addObjectUnderConstruction(State, ICC->getCXXCtorInitializer(), + LCtx, V); + } + case ConstructionContext::NewAllocatedObjectKind: { + return State; + } + case ConstructionContext::SimpleReturnedValueKind: + case ConstructionContext::CXX17ElidedCopyReturnedValueKind: { + const StackFrameContext *SFC = LCtx->getStackFrame(); + const LocationContext *CallerLCtx = SFC->getParent(); + if (!CallerLCtx) { + // No extra work is necessary in top frame. + return State; } - assert(!V.isUnknown()); + auto RTC = (*SFC->getCallSiteBlock())[SFC->getIndex()] + .getAs<CFGCXXRecordTypedCall>(); + assert(RTC && "Could not have had a target region without it"); + if (isa<BlockInvocationContext>(CallerLCtx)) { + // Unwrap block invocation contexts. They're mostly part of + // the current stack frame. + CallerLCtx = CallerLCtx->getParent(); + assert(!isa<BlockInvocationContext>(CallerLCtx)); + } - if (BTE) + return updateObjectsUnderConstruction(V, + cast<Expr>(SFC->getCallSite()), State, CallerLCtx, + RTC->getConstructionContext(), CallOpts); + } + case ConstructionContext::ElidedTemporaryObjectKind: { + assert(AMgr.getAnalyzerOptions().ShouldElideConstructors); + if (!CallOpts.IsElidableCtorThatHasNotBeenElided) { + const auto *TCC = cast<ElidedTemporaryObjectConstructionContext>(CC); + State = updateObjectsUnderConstruction( + V, TCC->getConstructorAfterElision(), State, LCtx, + TCC->getConstructionContextAfterElision(), CallOpts); + + // Remember that we've elided the constructor. + State = addObjectUnderConstruction( + State, TCC->getConstructorAfterElision(), LCtx, V); + + // Remember that we've elided the destructor. + if (const auto *BTE = TCC->getCXXBindTemporaryExpr()) + State = elideDestructor(State, BTE, LCtx); + + // Instead of materialization, shamelessly return + // the final object destination. + if (const auto *MTE = TCC->getMaterializedTemporaryExpr()) + State = addObjectUnderConstruction(State, MTE, LCtx, V); + + return State; + } + // If we decided not to elide the constructor, proceed as if + // it's a simple temporary. + LLVM_FALLTHROUGH; + } + case ConstructionContext::SimpleTemporaryObjectKind: { + const auto *TCC = cast<TemporaryObjectConstructionContext>(CC); + if (const auto *BTE = TCC->getCXXBindTemporaryExpr()) State = addObjectUnderConstruction(State, BTE, LCtx, V); - return std::make_pair(State, V); + if (const auto *MTE = TCC->getMaterializedTemporaryExpr()) + State = addObjectUnderConstruction(State, MTE, LCtx, V); + + return State; } + case ConstructionContext::ArgumentKind: { + const auto *ACC = cast<ArgumentConstructionContext>(CC); + if (const auto *BTE = ACC->getCXXBindTemporaryExpr()) + State = addObjectUnderConstruction(State, BTE, LCtx, V); + + return addObjectUnderConstruction( + State, {ACC->getCallLikeExpr(), ACC->getIndex()}, LCtx, V); } } - // If we couldn't find an existing region to construct into, assume we're - // constructing a temporary. Notify the caller of our failure. - CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true; - return std::make_pair( - State, loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx))); + llvm_unreachable("Unhandled construction context!"); } -void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, - ExplodedNode *Pred, - ExplodedNodeSet &destNodes) { +void ExprEngine::handleConstructor(const Expr *E, + ExplodedNode *Pred, + ExplodedNodeSet &destNodes) { + const auto *CE = dyn_cast<CXXConstructExpr>(E); + const auto *CIE = dyn_cast<CXXInheritedCtorInitExpr>(E); + assert(CE || CIE); + const LocationContext *LCtx = Pred->getLocationContext(); ProgramStateRef State = Pred->getState(); SVal Target = UnknownVal(); - if (Optional<SVal> ElidedTarget = - getObjectUnderConstruction(State, CE, LCtx)) { - // We've previously modeled an elidable constructor by pretending that it in - // fact constructs into the correct target. This constructor can therefore - // be skipped. - Target = *ElidedTarget; - StmtNodeBuilder Bldr(Pred, destNodes, *currBldrCtx); - State = finishObjectConstruction(State, CE, LCtx); - if (auto L = Target.getAs<Loc>()) - State = State->BindExpr(CE, LCtx, State->getSVal(*L, CE->getType())); - Bldr.generateNode(CE, Pred, State); - return; + if (CE) { + if (Optional<SVal> ElidedTarget = + getObjectUnderConstruction(State, CE, LCtx)) { + // We've previously modeled an elidable constructor by pretending that it + // in fact constructs into the correct target. This constructor can + // therefore be skipped. + Target = *ElidedTarget; + StmtNodeBuilder Bldr(Pred, destNodes, *currBldrCtx); + State = finishObjectConstruction(State, CE, LCtx); + if (auto L = Target.getAs<Loc>()) + State = State->BindExpr(CE, LCtx, State->getSVal(*L, CE->getType())); + Bldr.generateNode(CE, Pred, State); + return; + } } // FIXME: Handle arrays, which run the same constructor for every element. @@ -423,10 +479,16 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, assert(C || getCurrentCFGElement().getAs<CFGStmt>()); const ConstructionContext *CC = C ? C->getConstructionContext() : nullptr; - switch (CE->getConstructionKind()) { + const CXXConstructExpr::ConstructionKind CK = + CE ? CE->getConstructionKind() : CIE->getConstructionKind(); + switch (CK) { case CXXConstructExpr::CK_Complete: { + // Inherited constructors are always base class constructors. + assert(CE && !CIE && "A complete constructor is inherited?!"); + + // The target region is found from construction context. std::tie(State, Target) = - prepareForObjectConstruction(CE, State, LCtx, CC, CallOpts); + handleConstructionContext(CE, State, LCtx, CC, CallOpts); break; } case CXXConstructExpr::CK_VirtualBase: { @@ -455,9 +517,9 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, // FIXME: Instead of relying on the ParentMap, we should have the // trigger-statement (InitListExpr in this case) passed down from CFG or // otherwise always available during construction. - if (dyn_cast_or_null<InitListExpr>(LCtx->getParentMap().getParent(CE))) { + if (dyn_cast_or_null<InitListExpr>(LCtx->getParentMap().getParent(E))) { MemRegionManager &MRMgr = getSValBuilder().getRegionManager(); - Target = loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(CE, LCtx)); + Target = loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)); CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true; break; } @@ -468,14 +530,13 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, LCtx->getStackFrame()); SVal ThisVal = State->getSVal(ThisPtr); - if (CE->getConstructionKind() == CXXConstructExpr::CK_Delegating) { + if (CK == CXXConstructExpr::CK_Delegating) { Target = ThisVal; } else { // Cast to the base type. - bool IsVirtual = - (CE->getConstructionKind() == CXXConstructExpr::CK_VirtualBase); - SVal BaseVal = getStoreManager().evalDerivedToBase(ThisVal, CE->getType(), - IsVirtual); + bool IsVirtual = (CK == CXXConstructExpr::CK_VirtualBase); + SVal BaseVal = + getStoreManager().evalDerivedToBase(ThisVal, E->getType(), IsVirtual); Target = BaseVal; } break; @@ -487,23 +548,27 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, "Prepare for object construction"); ExplodedNodeSet DstPrepare; StmtNodeBuilder BldrPrepare(Pred, DstPrepare, *currBldrCtx); - BldrPrepare.generateNode(CE, Pred, State, &T, ProgramPoint::PreStmtKind); + BldrPrepare.generateNode(E, Pred, State, &T, ProgramPoint::PreStmtKind); assert(DstPrepare.size() <= 1); if (DstPrepare.size() == 0) return; Pred = *BldrPrepare.begin(); } + const MemRegion *TargetRegion = Target.getAsRegion(); CallEventManager &CEMgr = getStateManager().getCallEventManager(); - CallEventRef<CXXConstructorCall> Call = - CEMgr.getCXXConstructorCall(CE, Target.getAsRegion(), State, LCtx); + CallEventRef<> Call = + CIE ? (CallEventRef<>)CEMgr.getCXXInheritedConstructorCall( + CIE, TargetRegion, State, LCtx) + : (CallEventRef<>)CEMgr.getCXXConstructorCall( + CE, TargetRegion, State, LCtx); ExplodedNodeSet DstPreVisit; - getCheckerManager().runCheckersForPreStmt(DstPreVisit, Pred, CE, *this); + getCheckerManager().runCheckersForPreStmt(DstPreVisit, Pred, E, *this); - // FIXME: Is it possible and/or useful to do this before PreStmt? ExplodedNodeSet PreInitialized; - { + if (CE) { + // FIXME: Is it possible and/or useful to do this before PreStmt? StmtNodeBuilder Bldr(DstPreVisit, PreInitialized, *currBldrCtx); for (ExplodedNodeSet::iterator I = DstPreVisit.begin(), E = DstPreVisit.end(); @@ -528,6 +593,8 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, Bldr.generateNode(CE, *I, State, /*tag=*/nullptr, ProgramPoint::PreStmtKind); } + } else { + PreInitialized = DstPreVisit; } ExplodedNodeSet DstPreCall; @@ -537,7 +604,7 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, ExplodedNodeSet DstEvaluated; StmtNodeBuilder Bldr(DstPreCall, DstEvaluated, *currBldrCtx); - if (CE->getConstructor()->isTrivial() && + if (CE && CE->getConstructor()->isTrivial() && CE->getConstructor()->isCopyOrMoveConstructor() && !CallOpts.IsArrayCtorOrDtor) { // FIXME: Handle other kinds of trivial constructors as well. @@ -548,7 +615,8 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, } else { for (ExplodedNodeSet::iterator I = DstPreCall.begin(), E = DstPreCall.end(); I != E; ++I) - defaultEvalCall(Bldr, *I, *Call, CallOpts); + getCheckerManager().runCheckersForEvalCall(DstEvaluated, *I, *Call, *this, + CallOpts); } // If the CFG was constructed without elements for temporary destructors @@ -560,9 +628,10 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, // paths when no-return temporary destructors are used for assertions. const AnalysisDeclContext *ADC = LCtx->getAnalysisDeclContext(); if (!ADC->getCFGBuildOptions().AddTemporaryDtors) { - const MemRegion *Target = Call->getCXXThisVal().getAsRegion(); - if (Target && isa<CXXTempObjectRegion>(Target) && - Call->getDecl()->getParent()->isAnyDestructorNoReturn()) { + if (llvm::isa_and_nonnull<CXXTempObjectRegion>(TargetRegion) && + cast<CXXConstructorDecl>(Call->getDecl()) + ->getParent() + ->isAnyDestructorNoReturn()) { // If we've inlined the constructor, then DstEvaluated would be empty. // In this case we still want a sink, which could be implemented @@ -575,7 +644,7 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, "We should not have inlined this constructor!"); for (ExplodedNode *N : DstEvaluated) { - Bldr.generateSink(CE, N, N->getState()); + Bldr.generateSink(E, N, N->getState()); } // There is no need to run the PostCall and PostStmt checker @@ -586,7 +655,7 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, } ExplodedNodeSet DstPostArgumentCleanup; - for (auto I : DstEvaluated) + for (ExplodedNode *I : DstEvaluated) finishArgumentConstruction(DstPostArgumentCleanup, I, *Call); // If there were other constructors called for object-type arguments @@ -595,7 +664,19 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, getCheckerManager().runCheckersForPostCall(DstPostCall, DstPostArgumentCleanup, *Call, *this); - getCheckerManager().runCheckersForPostStmt(destNodes, DstPostCall, CE, *this); + getCheckerManager().runCheckersForPostStmt(destNodes, DstPostCall, E, *this); +} + +void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, + ExplodedNode *Pred, + ExplodedNodeSet &Dst) { + handleConstructor(CE, Pred, Dst); +} + +void ExprEngine::VisitCXXInheritedCtorInitExpr( + const CXXInheritedCtorInitExpr *CE, ExplodedNode *Pred, + ExplodedNodeSet &Dst) { + handleConstructor(CE, Pred, Dst); } void ExprEngine::VisitCXXDestructor(QualType ObjectType, @@ -683,7 +764,7 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE, ExplodedNodeSet DstPostCall; StmtNodeBuilder CallBldr(DstPreCall, DstPostCall, *currBldrCtx); - for (auto I : DstPreCall) { + for (ExplodedNode *I : DstPreCall) { // FIXME: Provide evalCall for checkers? defaultEvalCall(CallBldr, I, *Call); } @@ -693,7 +774,7 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE, // CXXNewExpr gets processed. ExplodedNodeSet DstPostValue; StmtNodeBuilder ValueBldr(DstPostCall, DstPostValue, *currBldrCtx); - for (auto I : DstPostCall) { + for (ExplodedNode *I : DstPostCall) { // FIXME: Because CNE serves as the "call site" for the allocator (due to // lack of a better expression in the AST), the conjured return value symbol // is going to be of the same type (C++ object pointer type). Technically @@ -727,10 +808,8 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE, ExplodedNodeSet DstPostPostCallCallback; getCheckerManager().runCheckersForPostCall(DstPostPostCallCallback, DstPostValue, *Call, *this); - for (auto I : DstPostPostCallCallback) { - getCheckerManager().runCheckersForNewAllocator( - CNE, *getObjectUnderConstruction(I->getState(), CNE, LCtx), Dst, I, - *this); + for (ExplodedNode *I : DstPostPostCallCallback) { + getCheckerManager().runCheckersForNewAllocator(*Call, Dst, I, *this); } } @@ -846,13 +925,18 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, void ExprEngine::VisitCXXDeleteExpr(const CXXDeleteExpr *CDE, ExplodedNode *Pred, ExplodedNodeSet &Dst) { - StmtNodeBuilder Bldr(Pred, Dst, *currBldrCtx); - ProgramStateRef state = Pred->getState(); - Bldr.generateNode(CDE, Pred, state); + + CallEventManager &CEMgr = getStateManager().getCallEventManager(); + CallEventRef<CXXDeallocatorCall> Call = CEMgr.getCXXDeallocatorCall( + CDE, Pred->getState(), Pred->getLocationContext()); + + ExplodedNodeSet DstPreCall; + getCheckerManager().runCheckersForPreCall(DstPreCall, Pred, *Call, *this); + + getCheckerManager().runCheckersForPostCall(Dst, DstPreCall, *Call, *this); } -void ExprEngine::VisitCXXCatchStmt(const CXXCatchStmt *CS, - ExplodedNode *Pred, +void ExprEngine::VisitCXXCatchStmt(const CXXCatchStmt *CS, ExplodedNode *Pred, ExplodedNodeSet &Dst) { const VarDecl *VD = CS->getExceptionDecl(); if (!VD) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index 01a371e664b2..52ba17d59ae0 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -10,17 +10,19 @@ // //===----------------------------------------------------------------------===// -#include "clang/AST/Decl.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "PrettyStackTraceLocationContext.h" #include "clang/AST/CXXInheritance.h" +#include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/Analysis/Analyses/LiveVariables.h" #include "clang/Analysis/ConstructionContext.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/Statistic.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Compiler.h" #include "llvm/Support/SaveAndRestore.h" using namespace clang; @@ -324,17 +326,14 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { CallEventRef<> UpdatedCall = Call.cloneWithState(CEEState); ExplodedNodeSet DstPostCall; - if (const CXXNewExpr *CNE = dyn_cast_or_null<CXXNewExpr>(CE)) { + if (llvm::isa_and_nonnull<CXXNewExpr>(CE)) { ExplodedNodeSet DstPostPostCallCallback; getCheckerManager().runCheckersForPostCall(DstPostPostCallCallback, CEENode, *UpdatedCall, *this, /*wasInlined=*/true); - for (auto I : DstPostPostCallCallback) { + for (ExplodedNode *I : DstPostPostCallCallback) { getCheckerManager().runCheckersForNewAllocator( - CNE, - *getObjectUnderConstruction(I->getState(), CNE, - calleeCtx->getParent()), - DstPostCall, I, *this, + cast<CXXAllocatorCall>(*UpdatedCall), DstPostCall, I, *this, /*wasInlined=*/true); } } else { @@ -585,12 +584,12 @@ void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred, // defaultEvalCall if all of them fail. ExplodedNodeSet dstCallEvaluated; getCheckerManager().runCheckersForEvalCall(dstCallEvaluated, dstPreVisit, - Call, *this); + Call, *this, EvalCallOptions()); // If there were other constructors called for object-type arguments // of this call, clean them up. ExplodedNodeSet dstArgumentCleanup; - for (auto I : dstCallEvaluated) + for (ExplodedNode *I : dstCallEvaluated) finishArgumentConstruction(dstArgumentCleanup, I, Call); ExplodedNodeSet dstPostCall; @@ -604,7 +603,7 @@ void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred, // Run pointerEscape callback with the newly conjured symbols. SmallVector<std::pair<SVal, SVal>, 8> Escaped; - for (auto I : dstPostCall) { + for (ExplodedNode *I : dstPostCall) { NodeBuilder B(I, Dst, *currBldrCtx); ProgramStateRef State = I->getState(); Escaped.clear(); @@ -668,8 +667,8 @@ ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call, assert(RTC->getStmt() == Call.getOriginExpr()); EvalCallOptions CallOpts; // FIXME: We won't really need those. std::tie(State, Target) = - prepareForObjectConstruction(Call.getOriginExpr(), State, LCtx, - RTC->getConstructionContext(), CallOpts); + handleConstructionContext(Call.getOriginExpr(), State, LCtx, + RTC->getConstructionContext(), CallOpts); const MemRegion *TargetR = Target.getAsRegion(); assert(TargetR); // Invalidate the region so that it didn't look uninitialized. If this is @@ -718,7 +717,7 @@ void ExprEngine::conservativeEvalCall(const CallEvent &Call, NodeBuilder &Bldr, ExprEngine::CallInlinePolicy ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, AnalyzerOptions &Opts, - const ExprEngine::EvalCallOptions &CallOpts) { + const EvalCallOptions &CallOpts) { const LocationContext *CurLC = Pred->getLocationContext(); const StackFrameContext *CallerSFC = CurLC->getStackFrame(); switch (Call.getKind()) { @@ -742,7 +741,7 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, const ConstructionContext *CC = CCE ? CCE->getConstructionContext() : nullptr; - if (CC && isa<NewAllocatedObjectConstructionContext>(CC) && + if (llvm::isa_and_nonnull<NewAllocatedObjectConstructionContext>(CC) && !Opts.MayInlineCXXAllocator) return CIP_DisallowedOnce; @@ -789,6 +788,11 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, break; } + case CE_CXXInheritedConstructor: { + // This doesn't really increase the cost of inlining ever, because + // the stack frame of the inherited constructor is trivial. + return CIP_Allowed; + } case CE_CXXDestructor: { if (!Opts.mayInlineCXXMemberFunction(CIMK_Destructors)) return CIP_DisallowedAlways; @@ -814,6 +818,8 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, return CIP_DisallowedOnce; break; } + case CE_CXXDeallocator: + LLVM_FALLTHROUGH; case CE_CXXAllocator: if (Opts.MayInlineCXXAllocator) break; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp index 002b6070ddcd..bc7c41d039c4 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -66,11 +66,9 @@ class HTMLDiagnostics : public PathDiagnosticConsumer { const bool SupportsCrossFileDiagnostics; public: - HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, - const std::string& prefix, - const Preprocessor &pp, - bool supportsMultipleFiles) - : Directory(prefix), PP(pp), AnalyzerOpts(AnalyzerOpts), + HTMLDiagnostics(AnalyzerOptions &AnalyzerOpts, const std::string &OutputDir, + const Preprocessor &pp, bool supportsMultipleFiles) + : Directory(OutputDir), PP(pp), AnalyzerOpts(AnalyzerOpts), SupportsCrossFileDiagnostics(supportsMultipleFiles) {} ~HTMLDiagnostics() override { FlushDiagnostics(nullptr); } @@ -136,16 +134,45 @@ private: void ento::createHTMLDiagnosticConsumer( AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, - const std::string &prefix, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &) { - C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, true)); + const std::string &OutputDir, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { + + // FIXME: HTML is currently our default output type, but if the output + // directory isn't specified, it acts like if it was in the minimal text + // output mode. This doesn't make much sense, we should have the minimal text + // as our default. In the case of backward compatibility concerns, this could + // be preserved with -analyzer-config-compatibility-mode=true. + createTextMinimalPathDiagnosticConsumer(AnalyzerOpts, C, OutputDir, PP, CTU); + + // TODO: Emit an error here. + if (OutputDir.empty()) + return; + + C.push_back(new HTMLDiagnostics(AnalyzerOpts, OutputDir, PP, true)); } void ento::createHTMLSingleFileDiagnosticConsumer( AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &OutputDir, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { + + // TODO: Emit an error here. + if (OutputDir.empty()) + return; + + C.push_back(new HTMLDiagnostics(AnalyzerOpts, OutputDir, PP, false)); + createTextMinimalPathDiagnosticConsumer(AnalyzerOpts, C, OutputDir, PP, CTU); +} + +void ento::createPlistHTMLDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, const std::string &prefix, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &) { - C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, false)); + const cross_tu::CrossTranslationUnitContext &CTU) { + createHTMLDiagnosticConsumer( + AnalyzerOpts, C, std::string(llvm::sys::path::parent_path(prefix)), PP, + CTU); + createPlistMultiFileDiagnosticConsumer(AnalyzerOpts, C, prefix, PP, CTU); + createTextMinimalPathDiagnosticConsumer(AnalyzerOpts, C, prefix, PP, CTU); } //===----------------------------------------------------------------------===// @@ -1043,8 +1070,13 @@ StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() { <script type='text/javascript'> var digitMatcher = new RegExp("[0-9]+"); +var querySelectorAllArray = function(selector) { + return Array.prototype.slice.call( + document.querySelectorAll(selector)); +} + document.addEventListener("DOMContentLoaded", function() { - document.querySelectorAll(".PathNav > a").forEach( + querySelectorAllArray(".PathNav > a").forEach( function(currentValue, currentIndex) { var hrefValue = currentValue.getAttribute("href"); currentValue.onclick = function() { @@ -1064,7 +1096,7 @@ var findNum = function() { }; var scrollTo = function(el) { - document.querySelectorAll(".selected").forEach(function(s) { + querySelectorAllArray(".selected").forEach(function(s) { s.classList.remove("selected"); }); el.classList.add("selected"); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp index 1a09a521f116..dc268e562237 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp @@ -130,10 +130,10 @@ static internal::Matcher<Stmt> hasSuspiciousStmt(StringRef NodeName) { // Escaping and not known mutation of the loop counter is handled // by exclusion of assigning and address-of operators and // pass-by-ref function calls on the loop counter from the body. - changeIntBoundNode(equalsBoundNode(NodeName)), - callByRef(equalsBoundNode(NodeName)), - getAddrTo(equalsBoundNode(NodeName)), - assignedToRef(equalsBoundNode(NodeName))))); + changeIntBoundNode(equalsBoundNode(std::string(NodeName))), + callByRef(equalsBoundNode(std::string(NodeName))), + getAddrTo(equalsBoundNode(std::string(NodeName))), + assignedToRef(equalsBoundNode(std::string(NodeName)))))); } static internal::Matcher<Stmt> forLoopMatcher() { @@ -164,6 +164,11 @@ static bool isPossiblyEscaped(const VarDecl *VD, ExplodedNode *N) { if (VD->hasGlobalStorage()) return true; + const bool isParm = isa<ParmVarDecl>(VD); + // Reference parameters are assumed as escaped variables. + if (isParm && VD->getType()->isReferenceType()) + return true; + while (!N->pred_empty()) { // FIXME: getStmtForDiagnostics() does nasty things in order to provide // a valid statement for body farms, do we need this behavior here? @@ -193,6 +198,11 @@ static bool isPossiblyEscaped(const VarDecl *VD, ExplodedNode *N) { N = N->getFirstPred(); } + + // Parameter declaration will not be found. + if (isParm) + return false; + llvm_unreachable("Reached root without finding the declaration of VD"); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp index 9a7b1a24b819..47e34dd84b9a 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp @@ -67,8 +67,10 @@ ProgramStateRef getWidenedLoopState(ProgramStateRef PrevState, } // References should not be invalidated. - auto Matches = match(findAll(stmt(hasDescendant(varDecl(hasType(referenceType())).bind(MatchRef)))), - *LCtx->getDecl()->getBody(), ASTCtx); + auto Matches = match( + findAll(stmt(hasDescendant( + varDecl(hasType(hasCanonicalType(referenceType()))).bind(MatchRef)))), + *LCtx->getDecl()->getBody(), ASTCtx); for (BoundNodes Match : Matches) { const VarDecl *VD = Match.getNodeAs<VarDecl>(MatchRef); assert(VD); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/MemRegion.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/MemRegion.cpp index a10d7e69ad7e..455adf53ac99 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/MemRegion.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -142,7 +142,7 @@ bool SubRegion::isSubRegionOf(const MemRegion* R) const { return false; } -MemRegionManager* SubRegion::getMemRegionManager() const { +MemRegionManager &SubRegion::getMemRegionManager() const { const SubRegion* r = this; do { const MemRegion *superRegion = r->getSuperRegion(); @@ -159,62 +159,10 @@ const StackFrameContext *VarRegion::getStackFrame() const { return SSR ? SSR->getStackFrame() : nullptr; } -//===----------------------------------------------------------------------===// -// Region extents. -//===----------------------------------------------------------------------===// - -DefinedOrUnknownSVal TypedValueRegion::getExtent(SValBuilder &svalBuilder) const { - ASTContext &Ctx = svalBuilder.getContext(); - QualType T = getDesugaredValueType(Ctx); - - if (isa<VariableArrayType>(T)) - return nonloc::SymbolVal(svalBuilder.getSymbolManager().getExtentSymbol(this)); - if (T->isIncompleteType()) - return UnknownVal(); - - CharUnits size = Ctx.getTypeSizeInChars(T); - QualType sizeTy = svalBuilder.getArrayIndexType(); - return svalBuilder.makeIntVal(size.getQuantity(), sizeTy); -} - -DefinedOrUnknownSVal FieldRegion::getExtent(SValBuilder &svalBuilder) const { - // Force callers to deal with bitfields explicitly. - if (getDecl()->isBitField()) - return UnknownVal(); - - DefinedOrUnknownSVal Extent = DeclRegion::getExtent(svalBuilder); - - // A zero-length array at the end of a struct often stands for dynamically- - // allocated extra memory. - if (Extent.isZeroConstant()) { - QualType T = getDesugaredValueType(svalBuilder.getContext()); - - if (isa<ConstantArrayType>(T)) - return UnknownVal(); - } - - return Extent; -} - -DefinedOrUnknownSVal AllocaRegion::getExtent(SValBuilder &svalBuilder) const { - return nonloc::SymbolVal(svalBuilder.getSymbolManager().getExtentSymbol(this)); -} - -DefinedOrUnknownSVal SymbolicRegion::getExtent(SValBuilder &svalBuilder) const { - return nonloc::SymbolVal(svalBuilder.getSymbolManager().getExtentSymbol(this)); -} - -DefinedOrUnknownSVal StringRegion::getExtent(SValBuilder &svalBuilder) const { - return svalBuilder.makeIntVal(getStringLiteral()->getByteLength()+1, - svalBuilder.getArrayIndexType()); -} - ObjCIvarRegion::ObjCIvarRegion(const ObjCIvarDecl *ivd, const SubRegion *sReg) - : DeclRegion(ivd, sReg, ObjCIvarRegionKind) {} + : DeclRegion(sReg, ObjCIvarRegionKind), IVD(ivd) {} -const ObjCIvarDecl *ObjCIvarRegion::getDecl() const { - return cast<ObjCIvarDecl>(D); -} +const ObjCIvarDecl *ObjCIvarRegion::getDecl() const { return IVD; } QualType ObjCIvarRegion::getValueType() const { return getDecl()->getType(); @@ -228,6 +176,33 @@ QualType CXXDerivedObjectRegion::getValueType() const { return QualType(getDecl()->getTypeForDecl(), 0); } +QualType ParamVarRegion::getValueType() const { + assert(getDecl() && + "`ParamVarRegion` support functions without `Decl` not implemented" + " yet."); + return getDecl()->getType(); +} + +const ParmVarDecl *ParamVarRegion::getDecl() const { + const Decl *D = getStackFrame()->getDecl(); + + if (const auto *FD = dyn_cast<FunctionDecl>(D)) { + assert(Index < FD->param_size()); + return FD->parameters()[Index]; + } else if (const auto *BD = dyn_cast<BlockDecl>(D)) { + assert(Index < BD->param_size()); + return BD->parameters()[Index]; + } else if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) { + assert(Index < MD->param_size()); + return MD->parameters()[Index]; + } else if (const auto *CD = dyn_cast<CXXConstructorDecl>(D)) { + assert(Index < CD->param_size()); + return CD->parameters()[Index]; + } else { + llvm_unreachable("Unexpected Decl kind!"); + } +} + //===----------------------------------------------------------------------===// // FoldingSet profiling. //===----------------------------------------------------------------------===// @@ -299,25 +274,44 @@ void CXXThisRegion::Profile(llvm::FoldingSetNodeID &ID) const { CXXThisRegion::ProfileRegion(ID, ThisPointerTy, superRegion); } +void FieldRegion::Profile(llvm::FoldingSetNodeID &ID) const { + ProfileRegion(ID, getDecl(), superRegion); +} + void ObjCIvarRegion::ProfileRegion(llvm::FoldingSetNodeID& ID, const ObjCIvarDecl *ivd, const MemRegion* superRegion) { - DeclRegion::ProfileRegion(ID, ivd, superRegion, ObjCIvarRegionKind); + ID.AddInteger(static_cast<unsigned>(ObjCIvarRegionKind)); + ID.AddPointer(ivd); + ID.AddPointer(superRegion); } -void DeclRegion::ProfileRegion(llvm::FoldingSetNodeID& ID, const Decl *D, - const MemRegion* superRegion, Kind k) { - ID.AddInteger(static_cast<unsigned>(k)); - ID.AddPointer(D); +void ObjCIvarRegion::Profile(llvm::FoldingSetNodeID &ID) const { + ProfileRegion(ID, getDecl(), superRegion); +} + +void NonParamVarRegion::ProfileRegion(llvm::FoldingSetNodeID &ID, + const VarDecl *VD, + const MemRegion *superRegion) { + ID.AddInteger(static_cast<unsigned>(NonParamVarRegionKind)); + ID.AddPointer(VD); ID.AddPointer(superRegion); } -void DeclRegion::Profile(llvm::FoldingSetNodeID& ID) const { - DeclRegion::ProfileRegion(ID, D, superRegion, getKind()); +void NonParamVarRegion::Profile(llvm::FoldingSetNodeID &ID) const { + ProfileRegion(ID, getDecl(), superRegion); } -void VarRegion::Profile(llvm::FoldingSetNodeID &ID) const { - VarRegion::ProfileRegion(ID, getDecl(), superRegion); +void ParamVarRegion::ProfileRegion(llvm::FoldingSetNodeID &ID, const Expr *OE, + unsigned Idx, const MemRegion *SReg) { + ID.AddInteger(static_cast<unsigned>(ParamVarRegionKind)); + ID.AddPointer(OE); + ID.AddInteger(Idx); + ID.AddPointer(SReg); +} + +void ParamVarRegion::Profile(llvm::FoldingSetNodeID &ID) const { + ProfileRegion(ID, getOriginExpr(), getIndex(), superRegion); } void SymbolicRegion::ProfileRegion(llvm::FoldingSetNodeID& ID, SymbolRef sym, @@ -529,12 +523,11 @@ void SymbolicRegion::dumpToStream(raw_ostream &os) const { os << "SymRegion{" << sym << '}'; } -void VarRegion::dumpToStream(raw_ostream &os) const { - const auto *VD = cast<VarDecl>(D); +void NonParamVarRegion::dumpToStream(raw_ostream &os) const { if (const IdentifierInfo *ID = VD->getIdentifier()) os << ID->getName(); else - os << "VarRegion{D" << VD->getID() << '}'; + os << "NonParamVarRegion{D" << VD->getID() << '}'; } LLVM_DUMP_METHOD void RegionRawOffset::dump() const { @@ -581,6 +574,18 @@ void StackLocalsSpaceRegion::dumpToStream(raw_ostream &os) const { os << "StackLocalsSpaceRegion"; } +void ParamVarRegion::dumpToStream(raw_ostream &os) const { + const ParmVarDecl *PVD = getDecl(); + assert(PVD && + "`ParamVarRegion` support functions without `Decl` not implemented" + " yet."); + if (const IdentifierInfo *ID = PVD->getIdentifier()) { + os << ID->getName(); + } else { + os << "ParamVarRegion{P" << PVD->getID() << '}'; + } +} + bool MemRegion::canPrintPretty() const { return canPrintPrettyAsExpr(); } @@ -600,11 +605,18 @@ void MemRegion::printPrettyAsExpr(raw_ostream &) const { llvm_unreachable("This region cannot be printed pretty."); } -bool VarRegion::canPrintPrettyAsExpr() const { - return true; +bool NonParamVarRegion::canPrintPrettyAsExpr() const { return true; } + +void NonParamVarRegion::printPrettyAsExpr(raw_ostream &os) const { + os << getDecl()->getName(); } -void VarRegion::printPrettyAsExpr(raw_ostream &os) const { +bool ParamVarRegion::canPrintPrettyAsExpr() const { return true; } + +void ParamVarRegion::printPrettyAsExpr(raw_ostream &os) const { + assert(getDecl() && + "`ParamVarRegion` support functions without `Decl` not implemented" + " yet."); os << getDecl()->getName(); } @@ -717,11 +729,79 @@ SourceRange MemRegion::sourceRange() const { // MemRegionManager methods. //===----------------------------------------------------------------------===// +static DefinedOrUnknownSVal getTypeSize(QualType Ty, ASTContext &Ctx, + SValBuilder &SVB) { + CharUnits Size = Ctx.getTypeSizeInChars(Ty); + QualType SizeTy = SVB.getArrayIndexType(); + return SVB.makeIntVal(Size.getQuantity(), SizeTy); +} + +DefinedOrUnknownSVal MemRegionManager::getStaticSize(const MemRegion *MR, + SValBuilder &SVB) const { + const auto *SR = cast<SubRegion>(MR); + SymbolManager &SymMgr = SVB.getSymbolManager(); + + switch (SR->getKind()) { + case MemRegion::AllocaRegionKind: + case MemRegion::SymbolicRegionKind: + return nonloc::SymbolVal(SymMgr.getExtentSymbol(SR)); + case MemRegion::StringRegionKind: + return SVB.makeIntVal( + cast<StringRegion>(SR)->getStringLiteral()->getByteLength() + 1, + SVB.getArrayIndexType()); + case MemRegion::CompoundLiteralRegionKind: + case MemRegion::CXXBaseObjectRegionKind: + case MemRegion::CXXDerivedObjectRegionKind: + case MemRegion::CXXTempObjectRegionKind: + case MemRegion::CXXThisRegionKind: + case MemRegion::ObjCIvarRegionKind: + case MemRegion::NonParamVarRegionKind: + case MemRegion::ParamVarRegionKind: + case MemRegion::ElementRegionKind: + case MemRegion::ObjCStringRegionKind: { + QualType Ty = cast<TypedValueRegion>(SR)->getDesugaredValueType(Ctx); + if (isa<VariableArrayType>(Ty)) + return nonloc::SymbolVal(SymMgr.getExtentSymbol(SR)); + + if (Ty->isIncompleteType()) + return UnknownVal(); + + return getTypeSize(Ty, Ctx, SVB); + } + case MemRegion::FieldRegionKind: { + // Force callers to deal with bitfields explicitly. + if (cast<FieldRegion>(SR)->getDecl()->isBitField()) + return UnknownVal(); + + QualType Ty = cast<TypedValueRegion>(SR)->getDesugaredValueType(Ctx); + DefinedOrUnknownSVal Size = getTypeSize(Ty, Ctx, SVB); + + // A zero-length array at the end of a struct often stands for dynamically + // allocated extra memory. + if (Size.isZeroConstant()) { + if (isa<ConstantArrayType>(Ty)) + return UnknownVal(); + } + + return Size; + } + // FIXME: The following are being used in 'SimpleSValBuilder' and in + // 'ArrayBoundChecker::checkLocation' because there is no symbol to + // represent the regions more appropriately. + case MemRegion::BlockDataRegionKind: + case MemRegion::BlockCodeRegionKind: + case MemRegion::FunctionCodeRegionKind: + return nonloc::SymbolVal(SymMgr.getExtentSymbol(SR)); + default: + llvm_unreachable("Unhandled region"); + } +} + template <typename REG> const REG *MemRegionManager::LazyAllocate(REG*& region) { if (!region) { region = A.Allocate<REG>(); - new (region) REG(this); + new (region) REG(*this); } return region; @@ -746,7 +826,7 @@ MemRegionManager::getStackLocalsRegion(const StackFrameContext *STC) { return R; R = A.Allocate<StackLocalsSpaceRegion>(); - new (R) StackLocalsSpaceRegion(this, STC); + new (R) StackLocalsSpaceRegion(*this, STC); return R; } @@ -759,7 +839,7 @@ MemRegionManager::getStackArgumentsRegion(const StackFrameContext *STC) { return R; R = A.Allocate<StackArgumentsSpaceRegion>(); - new (R) StackArgumentsSpaceRegion(this, STC); + new (R) StackArgumentsSpaceRegion(*this, STC); return R; } @@ -781,7 +861,7 @@ const GlobalsSpaceRegion return R; R = A.Allocate<StaticGlobalSpaceRegion>(); - new (R) StaticGlobalSpaceRegion(this, CR); + new (R) StaticGlobalSpaceRegion(*this, CR); return R; } @@ -825,15 +905,16 @@ getStackOrCaptureRegionForDeclContext(const LocationContext *LC, return SFC; } if (const auto *BC = dyn_cast<BlockInvocationContext>(LC)) { - const auto *BR = - static_cast<const BlockDataRegion *>(BC->getContextData()); + const auto *BR = static_cast<const BlockDataRegion *>(BC->getData()); // FIXME: This can be made more efficient. for (BlockDataRegion::referenced_vars_iterator I = BR->referenced_vars_begin(), E = BR->referenced_vars_end(); I != E; ++I) { - const VarRegion *VR = I.getOriginalRegion(); - if (VR->getDecl() == VD) - return cast<VarRegion>(I.getCapturedRegion()); + const TypedValueRegion *OrigR = I.getOriginalRegion(); + if (const auto *VR = dyn_cast<VarRegion>(OrigR)) { + if (VR->getDecl() == VD) + return cast<VarRegion>(I.getCapturedRegion()); + } } } @@ -842,15 +923,37 @@ getStackOrCaptureRegionForDeclContext(const LocationContext *LC, return (const StackFrameContext *)nullptr; } -const VarRegion* MemRegionManager::getVarRegion(const VarDecl *D, +const VarRegion *MemRegionManager::getVarRegion(const VarDecl *D, const LocationContext *LC) { + const auto *PVD = dyn_cast<ParmVarDecl>(D); + if (PVD) { + unsigned Index = PVD->getFunctionScopeIndex(); + const StackFrameContext *SFC = LC->getStackFrame(); + const Stmt *CallSite = SFC->getCallSite(); + if (CallSite) { + const Decl *D = SFC->getDecl(); + if (const auto *FD = dyn_cast<FunctionDecl>(D)) { + if (Index < FD->param_size() && FD->parameters()[Index] == PVD) + return getSubRegion<ParamVarRegion>(cast<Expr>(CallSite), Index, + getStackArgumentsRegion(SFC)); + } else if (const auto *BD = dyn_cast<BlockDecl>(D)) { + if (Index < BD->param_size() && BD->parameters()[Index] == PVD) + return getSubRegion<ParamVarRegion>(cast<Expr>(CallSite), Index, + getStackArgumentsRegion(SFC)); + } else { + return getSubRegion<ParamVarRegion>(cast<Expr>(CallSite), Index, + getStackArgumentsRegion(SFC)); + } + } + } + D = D->getCanonicalDecl(); const MemRegion *sReg = nullptr; if (D->hasGlobalStorage() && !D->isStaticLocal()) { // First handle the globals defined in system headers. - if (C.getSourceManager().isInSystemHeader(D->getLocation())) { + if (Ctx.getSourceManager().isInSystemHeader(D->getLocation())) { // Whitelist the system globals which often DO GET modified, assume the // rest are immutable. if (D->getName().find("errno") != StringRef::npos) @@ -914,7 +1017,7 @@ const VarRegion* MemRegionManager::getVarRegion(const VarDecl *D, T = getContext().getBlockPointerType(T); const BlockCodeRegion *BTR = - getBlockCodeRegion(BD, C.getCanonicalType(T), + getBlockCodeRegion(BD, Ctx.getCanonicalType(T), STC->getAnalysisDeclContext()); sReg = getGlobalsRegion(MemRegion::StaticGlobalSpaceRegionKind, BTR); @@ -926,13 +1029,23 @@ const VarRegion* MemRegionManager::getVarRegion(const VarDecl *D, } } - return getSubRegion<VarRegion>(D, sReg); + return getSubRegion<NonParamVarRegion>(D, sReg); } -const VarRegion *MemRegionManager::getVarRegion(const VarDecl *D, - const MemRegion *superR) { +const NonParamVarRegion * +MemRegionManager::getNonParamVarRegion(const VarDecl *D, + const MemRegion *superR) { D = D->getCanonicalDecl(); - return getSubRegion<VarRegion>(D, superR); + return getSubRegion<NonParamVarRegion>(D, superR); +} + +const ParamVarRegion * +MemRegionManager::getParamVarRegion(const Expr *OriginExpr, unsigned Index, + const LocationContext *LC) { + const StackFrameContext *SFC = LC->getStackFrame(); + assert(SFC); + return getSubRegion<ParamVarRegion>(OriginExpr, Index, + getStackArgumentsRegion(SFC)); } const BlockDataRegion * @@ -1325,7 +1438,8 @@ static RegionOffset calculateOffset(const MemRegion *R) { case MemRegion::CXXThisRegionKind: case MemRegion::StringRegionKind: case MemRegion::ObjCStringRegionKind: - case MemRegion::VarRegionKind: + case MemRegion::NonParamVarRegionKind: + case MemRegion::ParamVarRegionKind: case MemRegion::CXXTempObjectRegionKind: // Usual base regions. goto Finish; @@ -1476,12 +1590,12 @@ RegionOffset MemRegion::getAsOffset() const { std::pair<const VarRegion *, const VarRegion *> BlockDataRegion::getCaptureRegions(const VarDecl *VD) { - MemRegionManager &MemMgr = *getMemRegionManager(); + MemRegionManager &MemMgr = getMemRegionManager(); const VarRegion *VR = nullptr; const VarRegion *OriginalVR = nullptr; if (!VD->hasAttr<BlocksAttr>() && VD->hasLocalStorage()) { - VR = MemMgr.getVarRegion(VD, this); + VR = MemMgr.getNonParamVarRegion(VD, this); OriginalVR = MemMgr.getVarRegion(VD, LC); } else { @@ -1490,7 +1604,7 @@ BlockDataRegion::getCaptureRegions(const VarDecl *VD) { OriginalVR = VR; } else { - VR = MemMgr.getVarRegion(VD, MemMgr.getUnknownRegion()); + VR = MemMgr.getNonParamVarRegion(VD, MemMgr.getUnknownRegion()); OriginalVR = MemMgr.getVarRegion(VD, LC); } } @@ -1511,7 +1625,7 @@ void BlockDataRegion::LazyInitializeReferencedVars() { return; } - MemRegionManager &MemMgr = *getMemRegionManager(); + MemRegionManager &MemMgr = getMemRegionManager(); llvm::BumpPtrAllocator &A = MemMgr.getAllocator(); BumpVectorContext BC(A); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp index 3a3942a8301b..ed62778623a8 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -45,8 +45,8 @@ namespace { AnalyzerOptions &AnOpts; const bool SupportsCrossFileDiagnostics; public: - PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, const std::string &prefix, - const Preprocessor &PP, + PlistDiagnostics(AnalyzerOptions &AnalyzerOpts, + const std::string &OutputFile, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU, bool supportsMultipleFiles); @@ -582,19 +582,32 @@ PlistDiagnostics::PlistDiagnostics( void ento::createPlistDiagnosticConsumer( AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, - const std::string &s, const Preprocessor &PP, + const std::string &OutputFile, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU) { - C.push_back(new PlistDiagnostics(AnalyzerOpts, s, PP, CTU, + + // TODO: Emit an error here. + if (OutputFile.empty()) + return; + + C.push_back(new PlistDiagnostics(AnalyzerOpts, OutputFile, PP, CTU, /*supportsMultipleFiles*/ false)); + createTextMinimalPathDiagnosticConsumer(AnalyzerOpts, C, OutputFile, PP, CTU); } void ento::createPlistMultiFileDiagnosticConsumer( AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, - const std::string &s, const Preprocessor &PP, + const std::string &OutputFile, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU) { - C.push_back(new PlistDiagnostics(AnalyzerOpts, s, PP, CTU, + + // TODO: Emit an error here. + if (OutputFile.empty()) + return; + + C.push_back(new PlistDiagnostics(AnalyzerOpts, OutputFile, PP, CTU, /*supportsMultipleFiles*/ true)); + createTextMinimalPathDiagnosticConsumer(AnalyzerOpts, C, OutputFile, PP, CTU); } + void PlistDiagnostics::FlushDiagnosticsImpl( std::vector<const PathDiagnostic *> &Diags, FilesMade *filesMade) { @@ -939,7 +952,7 @@ getExpandedMacro(SourceLocation MacroLoc, const Preprocessor &PP, std::string MacroName = getMacroNameAndPrintExpansion( Printer, MacroLoc, *PPToUse, MacroArgMap{}, AlreadyProcessedTokens); - return { MacroName, OS.str() }; + return {MacroName, std::string(OS.str())}; } static std::string getMacroNameAndPrintExpansion( @@ -960,9 +973,8 @@ static std::string getMacroNameAndPrintExpansion( // in this case we don't get the full expansion text in the Plist file. See // the test file where "value" is expanded to "garbage_" instead of // "garbage_value". - if (AlreadyProcessedTokens.find(IDInfo) != AlreadyProcessedTokens.end()) + if (!AlreadyProcessedTokens.insert(IDInfo).second) return Info.Name; - AlreadyProcessedTokens.insert(IDInfo); if (!Info.MI) return Info.Name; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ProgramState.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ProgramState.cpp index 14006f79fd0f..006a4006b7fc 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ProgramState.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ProgramState.cpp @@ -16,8 +16,8 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "llvm/Support/raw_ostream.h" using namespace clang; @@ -76,12 +76,12 @@ ProgramStateManager::ProgramStateManager(ASTContext &Ctx, StoreManagerCreator CreateSMgr, ConstraintManagerCreator CreateCMgr, llvm::BumpPtrAllocator &alloc, - SubEngine *SubEng) - : Eng(SubEng), EnvMgr(alloc), GDMFactory(alloc), + ExprEngine *ExprEng) + : Eng(ExprEng), EnvMgr(alloc), GDMFactory(alloc), svalBuilder(createSimpleSValBuilder(alloc, Ctx, *this)), CallEventMgr(new CallEventManager(alloc)), Alloc(alloc) { StoreMgr = (*CreateSMgr)(*this); - ConstraintMgr = (*CreateCMgr)(*this, SubEng); + ConstraintMgr = (*CreateCMgr)(*this, ExprEng); } @@ -189,7 +189,7 @@ ProgramState::invalidateRegionsImpl(ValueList Values, RegionAndSymbolInvalidationTraits *ITraits, const CallEvent *Call) const { ProgramStateManager &Mgr = getStateManager(); - SubEngine &Eng = Mgr.getOwningEngine(); + ExprEngine &Eng = Mgr.getOwningEngine(); InvalidatedSymbols InvalidatedSyms; if (!IS) @@ -240,6 +240,13 @@ ProgramState::enterStackFrame(const CallEvent &Call, return makeWithStore(NewStore); } +SVal ProgramState::getSelfSVal(const LocationContext *LCtx) const { + const ImplicitParamDecl *SelfDecl = LCtx->getSelfDecl(); + if (!SelfDecl) + return SVal(); + return getSVal(getRegion(SelfDecl, LCtx)); +} + SVal ProgramState::getSValAsScalarOrLoc(const MemRegion *R) const { // We only want to do fetches from regions that we can actually bind // values. For example, SymbolicRegions of type 'id<...>' cannot diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp index 9752a0e22832..cb6f61e86ae3 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp @@ -16,6 +16,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/RangedConstraintManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SValVisitor.h" #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/ImmutableSet.h" #include "llvm/Support/raw_ostream.h" @@ -23,10 +24,89 @@ using namespace clang; using namespace ento; +// This class can be extended with other tables which will help to reason +// about ranges more precisely. +class OperatorRelationsTable { + static_assert(BO_LT < BO_GT && BO_GT < BO_LE && BO_LE < BO_GE && + BO_GE < BO_EQ && BO_EQ < BO_NE, + "This class relies on operators order. Rework it otherwise."); + +public: + enum TriStateKind { + False = 0, + True, + Unknown, + }; + +private: + // CmpOpTable holds states which represent the corresponding range for + // branching an exploded graph. We can reason about the branch if there is + // a previously known fact of the existence of a comparison expression with + // operands used in the current expression. + // E.g. assuming (x < y) is true that means (x != y) is surely true. + // if (x previous_operation y) // < | != | > + // if (x operation y) // != | > | < + // tristate // True | Unknown | False + // + // CmpOpTable represents next: + // __|< |> |<=|>=|==|!=|UnknownX2| + // < |1 |0 |* |0 |0 |* |1 | + // > |0 |1 |0 |* |0 |* |1 | + // <=|1 |0 |1 |* |1 |* |0 | + // >=|0 |1 |* |1 |1 |* |0 | + // ==|0 |0 |* |* |1 |0 |1 | + // !=|1 |1 |* |* |0 |1 |0 | + // + // Columns stands for a previous operator. + // Rows stands for a current operator. + // Each row has exactly two `Unknown` cases. + // UnknownX2 means that both `Unknown` previous operators are met in code, + // and there is a special column for that, for example: + // if (x >= y) + // if (x != y) + // if (x <= y) + // False only + static constexpr size_t CmpOpCount = BO_NE - BO_LT + 1; + const TriStateKind CmpOpTable[CmpOpCount][CmpOpCount + 1] = { + // < > <= >= == != UnknownX2 + {True, False, Unknown, False, False, Unknown, True}, // < + {False, True, False, Unknown, False, Unknown, True}, // > + {True, False, True, Unknown, True, Unknown, False}, // <= + {False, True, Unknown, True, True, Unknown, False}, // >= + {False, False, Unknown, Unknown, True, False, True}, // == + {True, True, Unknown, Unknown, False, True, False}, // != + }; + + static size_t getIndexFromOp(BinaryOperatorKind OP) { + return static_cast<size_t>(OP - BO_LT); + } + +public: + constexpr size_t getCmpOpCount() const { return CmpOpCount; } + + static BinaryOperatorKind getOpFromIndex(size_t Index) { + return static_cast<BinaryOperatorKind>(Index + BO_LT); + } + + TriStateKind getCmpOpState(BinaryOperatorKind CurrentOP, + BinaryOperatorKind QueriedOP) const { + return CmpOpTable[getIndexFromOp(CurrentOP)][getIndexFromOp(QueriedOP)]; + } + + TriStateKind getCmpOpStateForUnknownX2(BinaryOperatorKind CurrentOP) const { + return CmpOpTable[getIndexFromOp(CurrentOP)][CmpOpCount]; + } +}; +//===----------------------------------------------------------------------===// +// RangeSet implementation +//===----------------------------------------------------------------------===// + void RangeSet::IntersectInRange(BasicValueFactory &BV, Factory &F, - const llvm::APSInt &Lower, const llvm::APSInt &Upper, - PrimRangeSet &newRanges, PrimRangeSet::iterator &i, - PrimRangeSet::iterator &e) const { + const llvm::APSInt &Lower, + const llvm::APSInt &Upper, + PrimRangeSet &newRanges, + PrimRangeSet::iterator &i, + PrimRangeSet::iterator &e) const { // There are six cases for each range R in the set: // 1. R is entirely before the intersection range. // 2. R is entirely after the intersection range. @@ -62,10 +142,27 @@ void RangeSet::IntersectInRange(BasicValueFactory &BV, Factory &F, const llvm::APSInt &RangeSet::getMinValue() const { assert(!isEmpty()); - return ranges.begin()->From(); + return begin()->From(); +} + +const llvm::APSInt &RangeSet::getMaxValue() const { + assert(!isEmpty()); + // NOTE: It's a shame that we can't implement 'getMaxValue' without scanning + // the whole tree to get to the last element. + // llvm::ImmutableSet should support decrement for 'end' iterators + // or reverse order iteration. + auto It = begin(); + for (auto End = end(); std::next(It) != End; ++It) { + } + return It->To(); } bool RangeSet::pin(llvm::APSInt &Lower, llvm::APSInt &Upper) const { + if (isEmpty()) { + // This range is already infeasible. + return false; + } + // This function has nine cases, the cartesian product of range-testing // both the upper and lower bounds against the symbol's type. // Each case requires a different pinning operation. @@ -155,11 +252,11 @@ bool RangeSet::pin(llvm::APSInt &Lower, llvm::APSInt &Upper) const { // or, alternatively, /removing/ all integers between Upper and Lower. RangeSet RangeSet::Intersect(BasicValueFactory &BV, Factory &F, llvm::APSInt Lower, llvm::APSInt Upper) const { - if (!pin(Lower, Upper)) - return F.getEmptySet(); - PrimRangeSet newRanges = F.getEmptySet(); + if (isEmpty() || !pin(Lower, Upper)) + return newRanges; + PrimRangeSet::iterator i = begin(), e = end(); if (Lower <= Upper) IntersectInRange(BV, F, Lower, Upper, newRanges, i, e); @@ -190,33 +287,78 @@ RangeSet RangeSet::Intersect(BasicValueFactory &BV, Factory &F, return newRanges; } -// Turn all [A, B] ranges to [-B, -A]. Ranges [MIN, B] are turned to range set -// [MIN, MIN] U [-B, MAX], when MIN and MAX are the minimal and the maximal -// signed values of the type. +// Turn all [A, B] ranges to [-B, -A], when "-" is a C-like unary minus +// operation under the values of the type. +// +// We also handle MIN because applying unary minus to MIN does not change it. +// Example 1: +// char x = -128; // -128 is a MIN value in a range of 'char' +// char y = -x; // y: -128 +// Example 2: +// unsigned char x = 0; // 0 is a MIN value in a range of 'unsigned char' +// unsigned char y = -x; // y: 0 +// +// And it makes us to separate the range +// like [MIN, N] to [MIN, MIN] U [-N,MAX]. +// For instance, whole range is {-128..127} and subrange is [-128,-126], +// thus [-128,-127,-126,.....] negates to [-128,.....,126,127]. +// +// Negate restores disrupted ranges on bounds, +// e.g. [MIN, B] => [MIN, MIN] U [-B, MAX] => [MIN, B]. RangeSet RangeSet::Negate(BasicValueFactory &BV, Factory &F) const { PrimRangeSet newRanges = F.getEmptySet(); - for (iterator i = begin(), e = end(); i != e; ++i) { - const llvm::APSInt &from = i->From(), &to = i->To(); - const llvm::APSInt &newTo = (from.isMinSignedValue() ? - BV.getMaxValue(from) : - BV.getValue(- from)); - if (to.isMaxSignedValue() && !newRanges.isEmpty() && - newRanges.begin()->From().isMinSignedValue()) { - assert(newRanges.begin()->To().isMinSignedValue() && - "Ranges should not overlap"); - assert(!from.isMinSignedValue() && "Ranges should not overlap"); - const llvm::APSInt &newFrom = newRanges.begin()->From(); - newRanges = - F.add(F.remove(newRanges, *newRanges.begin()), Range(newFrom, newTo)); - } else if (!to.isMinSignedValue()) { - const llvm::APSInt &newFrom = BV.getValue(- to); - newRanges = F.add(newRanges, Range(newFrom, newTo)); - } - if (from.isMinSignedValue()) { - newRanges = F.add(newRanges, Range(BV.getMinValue(from), - BV.getMinValue(from))); + if (isEmpty()) + return newRanges; + + const llvm::APSInt sampleValue = getMinValue(); + const llvm::APSInt &MIN = BV.getMinValue(sampleValue); + const llvm::APSInt &MAX = BV.getMaxValue(sampleValue); + + // Handle a special case for MIN value. + iterator i = begin(); + const llvm::APSInt &from = i->From(); + const llvm::APSInt &to = i->To(); + if (from == MIN) { + // If [from, to] are [MIN, MAX], then just return the same [MIN, MAX]. + if (to == MAX) { + newRanges = ranges; + } else { + // Add separate range for the lowest value. + newRanges = F.add(newRanges, Range(MIN, MIN)); + // Skip adding the second range in case when [from, to] are [MIN, MIN]. + if (to != MIN) { + newRanges = F.add(newRanges, Range(BV.getValue(-to), MAX)); + } } + // Skip the first range in the loop. + ++i; + } + + // Negate all other ranges. + for (iterator e = end(); i != e; ++i) { + // Negate int values. + const llvm::APSInt &newFrom = BV.getValue(-i->To()); + const llvm::APSInt &newTo = BV.getValue(-i->From()); + // Add a negated range. + newRanges = F.add(newRanges, Range(newFrom, newTo)); + } + + if (newRanges.isSingleton()) + return newRanges; + + // Try to find and unite next ranges: + // [MIN, MIN] & [MIN + 1, N] => [MIN, N]. + iterator iter1 = newRanges.begin(); + iterator iter2 = std::next(iter1); + + if (iter1->To() == MIN && (iter2->From() - 1) == MIN) { + const llvm::APSInt &to = iter2->To(); + // remove adjacent ranges + newRanges = F.remove(newRanges, *iter1); + newRanges = F.remove(newRanges, *newRanges.begin()); + // add united range + newRanges = F.add(newRanges, Range(MIN, to)); } return newRanges; @@ -238,10 +380,534 @@ void RangeSet::print(raw_ostream &os) const { } namespace { + +/// A little component aggregating all of the reasoning we have about +/// the ranges of symbolic expressions. +/// +/// Even when we don't know the exact values of the operands, we still +/// can get a pretty good estimate of the result's range. +class SymbolicRangeInferrer + : public SymExprVisitor<SymbolicRangeInferrer, RangeSet> { +public: + static RangeSet inferRange(BasicValueFactory &BV, RangeSet::Factory &F, + ProgramStateRef State, SymbolRef Sym) { + SymbolicRangeInferrer Inferrer(BV, F, State); + return Inferrer.infer(Sym); + } + + RangeSet VisitSymExpr(SymbolRef Sym) { + // If we got to this function, the actual type of the symbolic + // expression is not supported for advanced inference. + // In this case, we simply backoff to the default "let's simply + // infer the range from the expression's type". + return infer(Sym->getType()); + } + + RangeSet VisitSymIntExpr(const SymIntExpr *Sym) { + return VisitBinaryOperator(Sym); + } + + RangeSet VisitIntSymExpr(const IntSymExpr *Sym) { + return VisitBinaryOperator(Sym); + } + + RangeSet VisitSymSymExpr(const SymSymExpr *Sym) { + return VisitBinaryOperator(Sym); + } + +private: + SymbolicRangeInferrer(BasicValueFactory &BV, RangeSet::Factory &F, + ProgramStateRef S) + : ValueFactory(BV), RangeFactory(F), State(S) {} + + /// Infer range information from the given integer constant. + /// + /// It's not a real "inference", but is here for operating with + /// sub-expressions in a more polymorphic manner. + RangeSet inferAs(const llvm::APSInt &Val, QualType) { + return {RangeFactory, Val}; + } + + /// Infer range information from symbol in the context of the given type. + RangeSet inferAs(SymbolRef Sym, QualType DestType) { + QualType ActualType = Sym->getType(); + // Check that we can reason about the symbol at all. + if (ActualType->isIntegralOrEnumerationType() || + Loc::isLocType(ActualType)) { + return infer(Sym); + } + // Otherwise, let's simply infer from the destination type. + // We couldn't figure out nothing else about that expression. + return infer(DestType); + } + + RangeSet infer(SymbolRef Sym) { + const RangeSet *AssociatedRange = State->get<ConstraintRange>(Sym); + + // If Sym is a difference of symbols A - B, then maybe we have range set + // stored for B - A. + const RangeSet *RangeAssociatedWithNegatedSym = + getRangeForMinusSymbol(State, Sym); + + // If we have range set stored for both A - B and B - A then calculate the + // effective range set by intersecting the range set for A - B and the + // negated range set of B - A. + if (AssociatedRange && RangeAssociatedWithNegatedSym) + return AssociatedRange->Intersect( + ValueFactory, RangeFactory, + RangeAssociatedWithNegatedSym->Negate(ValueFactory, RangeFactory)); + + if (AssociatedRange) + return *AssociatedRange; + + if (RangeAssociatedWithNegatedSym) + return RangeAssociatedWithNegatedSym->Negate(ValueFactory, RangeFactory); + + // If Sym is a comparison expression (except <=>), + // find any other comparisons with the same operands. + // See function description. + const RangeSet CmpRangeSet = getRangeForComparisonSymbol(State, Sym); + if (!CmpRangeSet.isEmpty()) + return CmpRangeSet; + + return Visit(Sym); + } + + /// Infer range information solely from the type. + RangeSet infer(QualType T) { + // Lazily generate a new RangeSet representing all possible values for the + // given symbol type. + RangeSet Result(RangeFactory, ValueFactory.getMinValue(T), + ValueFactory.getMaxValue(T)); + + // References are known to be non-zero. + if (T->isReferenceType()) + return assumeNonZero(Result, T); + + return Result; + } + + template <class BinarySymExprTy> + RangeSet VisitBinaryOperator(const BinarySymExprTy *Sym) { + // TODO #1: VisitBinaryOperator implementation might not make a good + // use of the inferred ranges. In this case, we might be calculating + // everything for nothing. This being said, we should introduce some + // sort of laziness mechanism here. + // + // TODO #2: We didn't go into the nested expressions before, so it + // might cause us spending much more time doing the inference. + // This can be a problem for deeply nested expressions that are + // involved in conditions and get tested continuously. We definitely + // need to address this issue and introduce some sort of caching + // in here. + QualType ResultType = Sym->getType(); + return VisitBinaryOperator(inferAs(Sym->getLHS(), ResultType), + Sym->getOpcode(), + inferAs(Sym->getRHS(), ResultType), ResultType); + } + + RangeSet VisitBinaryOperator(RangeSet LHS, BinaryOperator::Opcode Op, + RangeSet RHS, QualType T) { + switch (Op) { + case BO_Or: + return VisitBinaryOperator<BO_Or>(LHS, RHS, T); + case BO_And: + return VisitBinaryOperator<BO_And>(LHS, RHS, T); + case BO_Rem: + return VisitBinaryOperator<BO_Rem>(LHS, RHS, T); + default: + return infer(T); + } + } + + //===----------------------------------------------------------------------===// + // Ranges and operators + //===----------------------------------------------------------------------===// + + /// Return a rough approximation of the given range set. + /// + /// For the range set: + /// { [x_0, y_0], [x_1, y_1], ... , [x_N, y_N] } + /// it will return the range [x_0, y_N]. + static Range fillGaps(RangeSet Origin) { + assert(!Origin.isEmpty()); + return {Origin.getMinValue(), Origin.getMaxValue()}; + } + + /// Try to convert given range into the given type. + /// + /// It will return llvm::None only when the trivial conversion is possible. + llvm::Optional<Range> convert(const Range &Origin, APSIntType To) { + if (To.testInRange(Origin.From(), false) != APSIntType::RTR_Within || + To.testInRange(Origin.To(), false) != APSIntType::RTR_Within) { + return llvm::None; + } + return Range(ValueFactory.Convert(To, Origin.From()), + ValueFactory.Convert(To, Origin.To())); + } + + template <BinaryOperator::Opcode Op> + RangeSet VisitBinaryOperator(RangeSet LHS, RangeSet RHS, QualType T) { + // We should propagate information about unfeasbility of one of the + // operands to the resulting range. + if (LHS.isEmpty() || RHS.isEmpty()) { + return RangeFactory.getEmptySet(); + } + + Range CoarseLHS = fillGaps(LHS); + Range CoarseRHS = fillGaps(RHS); + + APSIntType ResultType = ValueFactory.getAPSIntType(T); + + // We need to convert ranges to the resulting type, so we can compare values + // and combine them in a meaningful (in terms of the given operation) way. + auto ConvertedCoarseLHS = convert(CoarseLHS, ResultType); + auto ConvertedCoarseRHS = convert(CoarseRHS, ResultType); + + // It is hard to reason about ranges when conversion changes + // borders of the ranges. + if (!ConvertedCoarseLHS || !ConvertedCoarseRHS) { + return infer(T); + } + + return VisitBinaryOperator<Op>(*ConvertedCoarseLHS, *ConvertedCoarseRHS, T); + } + + template <BinaryOperator::Opcode Op> + RangeSet VisitBinaryOperator(Range LHS, Range RHS, QualType T) { + return infer(T); + } + + /// Return a symmetrical range for the given range and type. + /// + /// If T is signed, return the smallest range [-x..x] that covers the original + /// range, or [-min(T), max(T)] if the aforementioned symmetric range doesn't + /// exist due to original range covering min(T)). + /// + /// If T is unsigned, return the smallest range [0..x] that covers the + /// original range. + Range getSymmetricalRange(Range Origin, QualType T) { + APSIntType RangeType = ValueFactory.getAPSIntType(T); + + if (RangeType.isUnsigned()) { + return Range(ValueFactory.getMinValue(RangeType), Origin.To()); + } + + if (Origin.From().isMinSignedValue()) { + // If mini is a minimal signed value, absolute value of it is greater + // than the maximal signed value. In order to avoid these + // complications, we simply return the whole range. + return {ValueFactory.getMinValue(RangeType), + ValueFactory.getMaxValue(RangeType)}; + } + + // At this point, we are sure that the type is signed and we can safely + // use unary - operator. + // + // While calculating absolute maximum, we can use the following formula + // because of these reasons: + // * If From >= 0 then To >= From and To >= -From. + // AbsMax == To == max(To, -From) + // * If To <= 0 then -From >= -To and -From >= From. + // AbsMax == -From == max(-From, To) + // * Otherwise, From <= 0, To >= 0, and + // AbsMax == max(abs(From), abs(To)) + llvm::APSInt AbsMax = std::max(-Origin.From(), Origin.To()); + + // Intersection is guaranteed to be non-empty. + return {ValueFactory.getValue(-AbsMax), ValueFactory.getValue(AbsMax)}; + } + + /// Return a range set subtracting zero from \p Domain. + RangeSet assumeNonZero(RangeSet Domain, QualType T) { + APSIntType IntType = ValueFactory.getAPSIntType(T); + return Domain.Intersect(ValueFactory, RangeFactory, + ++IntType.getZeroValue(), --IntType.getZeroValue()); + } + + // FIXME: Once SValBuilder supports unary minus, we should use SValBuilder to + // obtain the negated symbolic expression instead of constructing the + // symbol manually. This will allow us to support finding ranges of not + // only negated SymSymExpr-type expressions, but also of other, simpler + // expressions which we currently do not know how to negate. + const RangeSet *getRangeForMinusSymbol(ProgramStateRef State, SymbolRef Sym) { + if (const SymSymExpr *SSE = dyn_cast<SymSymExpr>(Sym)) { + if (SSE->getOpcode() == BO_Sub) { + QualType T = Sym->getType(); + SymbolManager &SymMgr = State->getSymbolManager(); + SymbolRef negSym = + SymMgr.getSymSymExpr(SSE->getRHS(), BO_Sub, SSE->getLHS(), T); + + if (const RangeSet *negV = State->get<ConstraintRange>(negSym)) { + // Unsigned range set cannot be negated, unless it is [0, 0]. + if (T->isUnsignedIntegerOrEnumerationType() || + T->isSignedIntegerOrEnumerationType()) + return negV; + } + } + } + return nullptr; + } + + // Returns ranges only for binary comparison operators (except <=>) + // when left and right operands are symbolic values. + // Finds any other comparisons with the same operands. + // Then do logical calculations and refuse impossible branches. + // E.g. (x < y) and (x > y) at the same time are impossible. + // E.g. (x >= y) and (x != y) at the same time makes (x > y) true only. + // E.g. (x == y) and (y == x) are just reversed but the same. + // It covers all possible combinations (see CmpOpTable description). + // Note that `x` and `y` can also stand for subexpressions, + // not only for actual symbols. + RangeSet getRangeForComparisonSymbol(ProgramStateRef State, SymbolRef Sym) { + const RangeSet EmptyRangeSet = RangeFactory.getEmptySet(); + + auto SSE = dyn_cast<SymSymExpr>(Sym); + if (!SSE) + return EmptyRangeSet; + + BinaryOperatorKind CurrentOP = SSE->getOpcode(); + + // We currently do not support <=> (C++20). + if (!BinaryOperator::isComparisonOp(CurrentOP) || (CurrentOP == BO_Cmp)) + return EmptyRangeSet; + + static const OperatorRelationsTable CmpOpTable{}; + + const SymExpr *LHS = SSE->getLHS(); + const SymExpr *RHS = SSE->getRHS(); + QualType T = SSE->getType(); + + SymbolManager &SymMgr = State->getSymbolManager(); + const llvm::APSInt &Zero = ValueFactory.getValue(0, T); + const llvm::APSInt &One = ValueFactory.getValue(1, T); + const RangeSet TrueRangeSet(RangeFactory, One, One); + const RangeSet FalseRangeSet(RangeFactory, Zero, Zero); + + int UnknownStates = 0; + + // Loop goes through all of the columns exept the last one ('UnknownX2'). + // We treat `UnknownX2` column separately at the end of the loop body. + for (size_t i = 0; i < CmpOpTable.getCmpOpCount(); ++i) { + + // Let's find an expression e.g. (x < y). + BinaryOperatorKind QueriedOP = OperatorRelationsTable::getOpFromIndex(i); + const SymSymExpr *SymSym = SymMgr.getSymSymExpr(LHS, QueriedOP, RHS, T); + const RangeSet *QueriedRangeSet = State->get<ConstraintRange>(SymSym); + + // If ranges were not previously found, + // try to find a reversed expression (y > x). + if (!QueriedRangeSet) { + const BinaryOperatorKind ROP = + BinaryOperator::reverseComparisonOp(QueriedOP); + SymSym = SymMgr.getSymSymExpr(RHS, ROP, LHS, T); + QueriedRangeSet = State->get<ConstraintRange>(SymSym); + } + + if (!QueriedRangeSet || QueriedRangeSet->isEmpty()) + continue; + + const llvm::APSInt *ConcreteValue = QueriedRangeSet->getConcreteValue(); + const bool isInFalseBranch = + ConcreteValue ? (*ConcreteValue == 0) : false; + + // If it is a false branch, we shall be guided by opposite operator, + // because the table is made assuming we are in the true branch. + // E.g. when (x <= y) is false, then (x > y) is true. + if (isInFalseBranch) + QueriedOP = BinaryOperator::negateComparisonOp(QueriedOP); + + OperatorRelationsTable::TriStateKind BranchState = + CmpOpTable.getCmpOpState(CurrentOP, QueriedOP); + + if (BranchState == OperatorRelationsTable::Unknown) { + if (++UnknownStates == 2) + // If we met both Unknown states. + // if (x <= y) // assume true + // if (x != y) // assume true + // if (x < y) // would be also true + // Get a state from `UnknownX2` column. + BranchState = CmpOpTable.getCmpOpStateForUnknownX2(CurrentOP); + else + continue; + } + + return (BranchState == OperatorRelationsTable::True) ? TrueRangeSet + : FalseRangeSet; + } + + return EmptyRangeSet; + } + + BasicValueFactory &ValueFactory; + RangeSet::Factory &RangeFactory; + ProgramStateRef State; +}; + +template <> +RangeSet SymbolicRangeInferrer::VisitBinaryOperator<BO_Or>(Range LHS, Range RHS, + QualType T) { + APSIntType ResultType = ValueFactory.getAPSIntType(T); + llvm::APSInt Zero = ResultType.getZeroValue(); + + bool IsLHSPositiveOrZero = LHS.From() >= Zero; + bool IsRHSPositiveOrZero = RHS.From() >= Zero; + + bool IsLHSNegative = LHS.To() < Zero; + bool IsRHSNegative = RHS.To() < Zero; + + // Check if both ranges have the same sign. + if ((IsLHSPositiveOrZero && IsRHSPositiveOrZero) || + (IsLHSNegative && IsRHSNegative)) { + // The result is definitely greater or equal than any of the operands. + const llvm::APSInt &Min = std::max(LHS.From(), RHS.From()); + + // We estimate maximal value for positives as the maximal value for the + // given type. For negatives, we estimate it with -1 (e.g. 0x11111111). + // + // TODO: We basically, limit the resulting range from below, but don't do + // anything with the upper bound. + // + // For positive operands, it can be done as follows: for the upper + // bound of LHS and RHS we calculate the most significant bit set. + // Let's call it the N-th bit. Then we can estimate the maximal + // number to be 2^(N+1)-1, i.e. the number with all the bits up to + // the N-th bit set. + const llvm::APSInt &Max = IsLHSNegative + ? ValueFactory.getValue(--Zero) + : ValueFactory.getMaxValue(ResultType); + + return {RangeFactory, ValueFactory.getValue(Min), Max}; + } + + // Otherwise, let's check if at least one of the operands is negative. + if (IsLHSNegative || IsRHSNegative) { + // This means that the result is definitely negative as well. + return {RangeFactory, ValueFactory.getMinValue(ResultType), + ValueFactory.getValue(--Zero)}; + } + + RangeSet DefaultRange = infer(T); + + // It is pretty hard to reason about operands with different signs + // (and especially with possibly different signs). We simply check if it + // can be zero. In order to conclude that the result could not be zero, + // at least one of the operands should be definitely not zero itself. + if (!LHS.Includes(Zero) || !RHS.Includes(Zero)) { + return assumeNonZero(DefaultRange, T); + } + + // Nothing much else to do here. + return DefaultRange; +} + +template <> +RangeSet SymbolicRangeInferrer::VisitBinaryOperator<BO_And>(Range LHS, + Range RHS, + QualType T) { + APSIntType ResultType = ValueFactory.getAPSIntType(T); + llvm::APSInt Zero = ResultType.getZeroValue(); + + bool IsLHSPositiveOrZero = LHS.From() >= Zero; + bool IsRHSPositiveOrZero = RHS.From() >= Zero; + + bool IsLHSNegative = LHS.To() < Zero; + bool IsRHSNegative = RHS.To() < Zero; + + // Check if both ranges have the same sign. + if ((IsLHSPositiveOrZero && IsRHSPositiveOrZero) || + (IsLHSNegative && IsRHSNegative)) { + // The result is definitely less or equal than any of the operands. + const llvm::APSInt &Max = std::min(LHS.To(), RHS.To()); + + // We conservatively estimate lower bound to be the smallest positive + // or negative value corresponding to the sign of the operands. + const llvm::APSInt &Min = IsLHSNegative + ? ValueFactory.getMinValue(ResultType) + : ValueFactory.getValue(Zero); + + return {RangeFactory, Min, Max}; + } + + // Otherwise, let's check if at least one of the operands is positive. + if (IsLHSPositiveOrZero || IsRHSPositiveOrZero) { + // This makes result definitely positive. + // + // We can also reason about a maximal value by finding the maximal + // value of the positive operand. + const llvm::APSInt &Max = IsLHSPositiveOrZero ? LHS.To() : RHS.To(); + + // The minimal value on the other hand is much harder to reason about. + // The only thing we know for sure is that the result is positive. + return {RangeFactory, ValueFactory.getValue(Zero), + ValueFactory.getValue(Max)}; + } + + // Nothing much else to do here. + return infer(T); +} + +template <> +RangeSet SymbolicRangeInferrer::VisitBinaryOperator<BO_Rem>(Range LHS, + Range RHS, + QualType T) { + llvm::APSInt Zero = ValueFactory.getAPSIntType(T).getZeroValue(); + + Range ConservativeRange = getSymmetricalRange(RHS, T); + + llvm::APSInt Max = ConservativeRange.To(); + llvm::APSInt Min = ConservativeRange.From(); + + if (Max == Zero) { + // It's an undefined behaviour to divide by 0 and it seems like we know + // for sure that RHS is 0. Let's say that the resulting range is + // simply infeasible for that matter. + return RangeFactory.getEmptySet(); + } + + // At this point, our conservative range is closed. The result, however, + // couldn't be greater than the RHS' maximal absolute value. Because of + // this reason, we turn the range into open (or half-open in case of + // unsigned integers). + // + // While we operate on integer values, an open interval (a, b) can be easily + // represented by the closed interval [a + 1, b - 1]. And this is exactly + // what we do next. + // + // If we are dealing with unsigned case, we shouldn't move the lower bound. + if (Min.isSigned()) { + ++Min; + } + --Max; + + bool IsLHSPositiveOrZero = LHS.From() >= Zero; + bool IsRHSPositiveOrZero = RHS.From() >= Zero; + + // Remainder operator results with negative operands is implementation + // defined. Positive cases are much easier to reason about though. + if (IsLHSPositiveOrZero && IsRHSPositiveOrZero) { + // If maximal value of LHS is less than maximal value of RHS, + // the result won't get greater than LHS.To(). + Max = std::min(LHS.To(), Max); + // We want to check if it is a situation similar to the following: + // + // <------------|---[ LHS ]--------[ RHS ]-----> + // -INF 0 +INF + // + // In this situation, we can conclude that (LHS / RHS) == 0 and + // (LHS % RHS) == LHS. + Min = LHS.To() < RHS.From() ? LHS.From() : Zero; + } + + // Nevertheless, the symmetrical range for RHS is a conservative estimate + // for any sign of either LHS, or RHS. + return {RangeFactory, ValueFactory.getValue(Min), ValueFactory.getValue(Max)}; +} + class RangeConstraintManager : public RangedConstraintManager { public: - RangeConstraintManager(SubEngine *SE, SValBuilder &SVB) - : RangedConstraintManager(SE, SVB) {} + RangeConstraintManager(ExprEngine *EE, SValBuilder &SVB) + : RangedConstraintManager(EE, SVB) {} //===------------------------------------------------------------------===// // Implementation for interface from ConstraintManager. @@ -305,8 +971,6 @@ private: RangeSet::Factory F; RangeSet getRange(ProgramStateRef State, SymbolRef Sym); - const RangeSet* getRangeForMinusSymbol(ProgramStateRef State, - SymbolRef Sym); RangeSet getSymLTRange(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, @@ -323,13 +987,13 @@ private: RangeSet getSymGERange(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, const llvm::APSInt &Adjustment); - }; } // end anonymous namespace std::unique_ptr<ConstraintManager> -ento::CreateRangeConstraintManager(ProgramStateManager &StMgr, SubEngine *Eng) { +ento::CreateRangeConstraintManager(ProgramStateManager &StMgr, + ExprEngine *Eng) { return std::make_unique<RangeConstraintManager>(Eng, StMgr.getSValBuilder()); } @@ -429,113 +1093,9 @@ RangeConstraintManager::removeDeadBindings(ProgramStateRef State, return Changed ? State->set<ConstraintRange>(CR) : State; } -/// Return a range set subtracting zero from \p Domain. -static RangeSet assumeNonZero( - BasicValueFactory &BV, - RangeSet::Factory &F, - SymbolRef Sym, - RangeSet Domain) { - APSIntType IntType = BV.getAPSIntType(Sym->getType()); - return Domain.Intersect(BV, F, ++IntType.getZeroValue(), - --IntType.getZeroValue()); -} - -/// Apply implicit constraints for bitwise OR- and AND-. -/// For unsigned types, bitwise OR with a constant always returns -/// a value greater-or-equal than the constant, and bitwise AND -/// returns a value less-or-equal then the constant. -/// -/// Pattern matches the expression \p Sym against those rule, -/// and applies the required constraints. -/// \p Input Previously established expression range set -static RangeSet applyBitwiseConstraints( - BasicValueFactory &BV, - RangeSet::Factory &F, - RangeSet Input, - const SymIntExpr* SIE) { - QualType T = SIE->getType(); - bool IsUnsigned = T->isUnsignedIntegerType(); - const llvm::APSInt &RHS = SIE->getRHS(); - const llvm::APSInt &Zero = BV.getAPSIntType(T).getZeroValue(); - BinaryOperator::Opcode Operator = SIE->getOpcode(); - - // For unsigned types, the output of bitwise-or is bigger-or-equal than RHS. - if (Operator == BO_Or && IsUnsigned) - return Input.Intersect(BV, F, RHS, BV.getMaxValue(T)); - - // Bitwise-or with a non-zero constant is always non-zero. - if (Operator == BO_Or && RHS != Zero) - return assumeNonZero(BV, F, SIE, Input); - - // For unsigned types, or positive RHS, - // bitwise-and output is always smaller-or-equal than RHS (assuming two's - // complement representation of signed types). - if (Operator == BO_And && (IsUnsigned || RHS >= Zero)) - return Input.Intersect(BV, F, BV.getMinValue(T), RHS); - - return Input; -} - RangeSet RangeConstraintManager::getRange(ProgramStateRef State, SymbolRef Sym) { - ConstraintRangeTy::data_type *V = State->get<ConstraintRange>(Sym); - - // If Sym is a difference of symbols A - B, then maybe we have range set - // stored for B - A. - BasicValueFactory &BV = getBasicVals(); - const RangeSet *R = getRangeForMinusSymbol(State, Sym); - - // If we have range set stored for both A - B and B - A then calculate the - // effective range set by intersecting the range set for A - B and the - // negated range set of B - A. - if (V && R) - return V->Intersect(BV, F, R->Negate(BV, F)); - if (V) - return *V; - if (R) - return R->Negate(BV, F); - - // Lazily generate a new RangeSet representing all possible values for the - // given symbol type. - QualType T = Sym->getType(); - - RangeSet Result(F, BV.getMinValue(T), BV.getMaxValue(T)); - - // References are known to be non-zero. - if (T->isReferenceType()) - return assumeNonZero(BV, F, Sym, Result); - - // Known constraints on ranges of bitwise expressions. - if (const SymIntExpr* SIE = dyn_cast<SymIntExpr>(Sym)) - return applyBitwiseConstraints(BV, F, Result, SIE); - - return Result; -} - -// FIXME: Once SValBuilder supports unary minus, we should use SValBuilder to -// obtain the negated symbolic expression instead of constructing the -// symbol manually. This will allow us to support finding ranges of not -// only negated SymSymExpr-type expressions, but also of other, simpler -// expressions which we currently do not know how to negate. -const RangeSet* -RangeConstraintManager::getRangeForMinusSymbol(ProgramStateRef State, - SymbolRef Sym) { - if (const SymSymExpr *SSE = dyn_cast<SymSymExpr>(Sym)) { - if (SSE->getOpcode() == BO_Sub) { - QualType T = Sym->getType(); - SymbolManager &SymMgr = State->getSymbolManager(); - SymbolRef negSym = SymMgr.getSymSymExpr(SSE->getRHS(), BO_Sub, - SSE->getLHS(), T); - if (const RangeSet *negV = State->get<ConstraintRange>(negSym)) { - // Unsigned range set cannot be negated, unless it is [0, 0]. - if ((negV->getConcreteValue() && - (*negV->getConcreteValue() == 0)) || - T->isSignedIntegerOrEnumerationType()) - return negV; - } - } - } - return nullptr; + return SymbolicRangeInferrer::inferRange(getBasicVals(), F, State, Sym); } //===------------------------------------------------------------------------=== diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index 4797f564a837..57fde32bc01d 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -23,10 +23,11 @@ #include "clang/Basic/TargetInfo.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "llvm/ADT/ImmutableMap.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/raw_ostream.h" @@ -381,7 +382,7 @@ public: : StoreManager(mgr), Features(f), RBFactory(mgr.getAllocator()), CBFactory(mgr.getAllocator()), SmallStructLimit(0) { - SubEngine &Eng = StateMgr.getOwningEngine(); + ExprEngine &Eng = StateMgr.getOwningEngine(); AnalyzerOptions &Options = Eng.getAnalysisManager().options; SmallStructLimit = Options.RegionStoreSmallStructLimit; } @@ -622,15 +623,6 @@ public: // Part of public interface to class. SymbolReaper& SymReaper) override; //===------------------------------------------------------------------===// - // Region "extents". - //===------------------------------------------------------------------===// - - // FIXME: This method will soon be eliminated; see the note in Store.h. - DefinedOrUnknownSVal getSizeInElements(ProgramStateRef state, - const MemRegion* R, - QualType EleTy) override; - - //===------------------------------------------------------------------===// // Utility methods. //===------------------------------------------------------------------===// @@ -876,7 +868,7 @@ collectSubRegionBindings(SmallVectorImpl<BindingPair> &Bindings, // Find the length (in bits) of the region being invalidated. uint64_t Length = UINT64_MAX; - SVal Extent = Top->getExtent(SVB); + SVal Extent = Top->getMemRegionManager().getStaticSize(Top, SVB); if (Optional<nonloc::ConcreteInt> ExtentCI = Extent.getAs<nonloc::ConcreteInt>()) { const llvm::APSInt &ExtentInt = ExtentCI->getValue(); @@ -1387,37 +1379,6 @@ RegionStoreManager::invalidateRegions(Store store, } //===----------------------------------------------------------------------===// -// Extents for regions. -//===----------------------------------------------------------------------===// - -DefinedOrUnknownSVal -RegionStoreManager::getSizeInElements(ProgramStateRef state, - const MemRegion *R, - QualType EleTy) { - SVal Size = cast<SubRegion>(R)->getExtent(svalBuilder); - const llvm::APSInt *SizeInt = svalBuilder.getKnownValue(state, Size); - if (!SizeInt) - return UnknownVal(); - - CharUnits RegionSize = CharUnits::fromQuantity(SizeInt->getSExtValue()); - - if (Ctx.getAsVariableArrayType(EleTy)) { - // FIXME: We need to track extra state to properly record the size - // of VLAs. Returning UnknownVal here, however, is a stop-gap so that - // we don't have a divide-by-zero below. - return UnknownVal(); - } - - CharUnits EleSize = Ctx.getTypeSizeInChars(EleTy); - - // If a variable is reinterpreted as a type that doesn't fit into a larger - // type evenly, round it down. - // This is a signed value, since it's used in arithmetic with signed indices. - return svalBuilder.makeIntVal(RegionSize / EleSize, - svalBuilder.getArrayIndexType()); -} - -//===----------------------------------------------------------------------===// // Location and region casting. //===----------------------------------------------------------------------===// @@ -1667,10 +1628,6 @@ RegionStoreManager::findLazyBinding(RegionBindingsConstRef B, SVal RegionStoreManager::getBindingForElement(RegionBindingsConstRef B, const ElementRegion* R) { - // We do not currently model bindings of the CompoundLiteralregion. - if (isa<CompoundLiteralRegion>(R->getBaseRegion())) - return UnknownVal(); - // Check if the region has a binding. if (const Optional<SVal> &V = B.getDirectBinding(R)) return *V; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp index 6ad12ca0a688..7395622a659c 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp @@ -13,6 +13,6 @@ using namespace clang; using namespace ento; std::unique_ptr<ConstraintManager> -ento::CreateZ3ConstraintManager(ProgramStateManager &StMgr, SubEngine *Eng) { +ento::CreateZ3ConstraintManager(ProgramStateManager &StMgr, ExprEngine *Eng) { return std::make_unique<SMTConstraintManager>(Eng, StMgr.getSValBuilder()); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index 3a5841137e1a..c00a2c8ba8a2 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -24,12 +24,12 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/Store.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" #include "llvm/ADT/APSInt.h" diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp index 12332aaf936f..8c2e85601576 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "clang/Analysis/PathDiagnostic.h" +#include "clang/Basic/FileManager.h" #include "clang/Basic/Version.h" #include "clang/Lex/Preprocessor.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" @@ -49,8 +50,14 @@ public: void ento::createSarifDiagnosticConsumer( AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, const std::string &Output, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &) { + const cross_tu::CrossTranslationUnitContext &CTU) { + + // TODO: Emit an error here. + if (Output.empty()) + return; + C.push_back(new SarifDiagnostics(AnalyzerOpts, Output, PP.getLangOpts())); + createTextMinimalPathDiagnosticConsumer(AnalyzerOpts, C, Output, PP, CTU); } static StringRef getFileName(const FileEntry &FE) { @@ -106,7 +113,7 @@ static std::string fileNameToURI(StringRef Filename) { } }); - return Ret.str().str(); + return std::string(Ret); } static json::Object createArtifactLocation(const FileEntry &FE) { @@ -322,7 +329,7 @@ static json::Object createRule(const PathDiagnostic &Diag) { {"name", CheckName}, {"id", CheckName}}; - std::string RuleURI = getRuleHelpURIStr(CheckName); + std::string RuleURI = std::string(getRuleHelpURIStr(CheckName)); if (!RuleURI.empty()) Ret["helpUri"] = RuleURI; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp index 85f60231a276..3709106ad44c 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp @@ -44,8 +44,8 @@ ProgramStateRef SimpleConstraintManager::assume(ProgramStateRef State, ProgramStateRef SimpleConstraintManager::assume(ProgramStateRef State, NonLoc Cond, bool Assumption) { State = assumeAux(State, Cond, Assumption); - if (NotifyAssumeClients && SU) - return SU->processAssume(State, Cond, Assumption); + if (NotifyAssumeClients && EE) + return EE->processAssume(State, Cond, Assumption); return State; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp index 84c52f53ca5e..2e269f6a596e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp @@ -13,8 +13,8 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SValVisitor.h" using namespace clang; @@ -652,6 +652,11 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, if (LHSValue == 0) return evalCastFromNonLoc(lhs, resultTy); return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); + case BO_Rem: + // 0 % x == 0 + if (LHSValue == 0) + return makeZeroVal(resultTy); + LLVM_FALLTHROUGH; default: return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Store.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Store.cpp index b33129c88cea..ea617bbeeba1 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Store.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Store.cpp @@ -134,7 +134,8 @@ const MemRegion *StoreManager::castRegion(const MemRegion *R, QualType CastToTy) case MemRegion::FieldRegionKind: case MemRegion::ObjCIvarRegionKind: case MemRegion::ObjCStringRegionKind: - case MemRegion::VarRegionKind: + case MemRegion::NonParamVarRegionKind: + case MemRegion::ParamVarRegionKind: case MemRegion::CXXTempObjectRegionKind: case MemRegion::CXXBaseObjectRegionKind: case MemRegion::CXXDerivedObjectRegionKind: diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SubEngine.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SubEngine.cpp deleted file mode 100644 index d7ddd9cf4610..000000000000 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SubEngine.cpp +++ /dev/null @@ -1,13 +0,0 @@ -//== SubEngine.cpp - Interface of the subengine of CoreEngine ------*- C++ -*-// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" - -using namespace clang::ento; - -void SubEngine::anchor() { } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp index 675209f6fd7e..6ca7aec9caec 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp @@ -34,45 +34,27 @@ using namespace ento; void SymExpr::anchor() {} -LLVM_DUMP_METHOD void SymExpr::dump() const { - dumpToStream(llvm::errs()); -} +LLVM_DUMP_METHOD void SymExpr::dump() const { dumpToStream(llvm::errs()); } -void SymIntExpr::dumpToStream(raw_ostream &os) const { - os << '('; - getLHS()->dumpToStream(os); - os << ") " - << BinaryOperator::getOpcodeStr(getOpcode()) << ' '; - if (getRHS().isUnsigned()) - os << getRHS().getZExtValue(); - else - os << getRHS().getSExtValue(); - if (getRHS().isUnsigned()) - os << 'U'; +void BinarySymExpr::dumpToStreamImpl(raw_ostream &OS, const SymExpr *Sym) { + OS << '('; + Sym->dumpToStream(OS); + OS << ')'; } -void IntSymExpr::dumpToStream(raw_ostream &os) const { - if (getLHS().isUnsigned()) - os << getLHS().getZExtValue(); +void BinarySymExpr::dumpToStreamImpl(raw_ostream &OS, + const llvm::APSInt &Value) { + if (Value.isUnsigned()) + OS << Value.getZExtValue(); else - os << getLHS().getSExtValue(); - if (getLHS().isUnsigned()) - os << 'U'; - os << ' ' - << BinaryOperator::getOpcodeStr(getOpcode()) - << " ("; - getRHS()->dumpToStream(os); - os << ')'; + OS << Value.getSExtValue(); + if (Value.isUnsigned()) + OS << 'U'; } -void SymSymExpr::dumpToStream(raw_ostream &os) const { - os << '('; - getLHS()->dumpToStream(os); - os << ") " - << BinaryOperator::getOpcodeStr(getOpcode()) - << " ("; - getRHS()->dumpToStream(os); - os << ')'; +void BinarySymExpr::dumpToStreamImpl(raw_ostream &OS, + BinaryOperator::Opcode Op) { + OS << ' ' << BinaryOperator::getOpcodeStr(Op) << ' '; } void SymbolCast::dumpToStream(raw_ostream &os) const { @@ -329,7 +311,7 @@ QualType SymbolDerived::getType() const { } QualType SymbolExtent::getType() const { - ASTContext &Ctx = R->getMemRegionManager()->getContext(); + ASTContext &Ctx = R->getMemRegionManager().getContext(); return Ctx.getSizeType(); } @@ -341,10 +323,6 @@ QualType SymbolRegionValue::getType() const { return R->getValueType(); } -SymbolManager::~SymbolManager() { - llvm::DeleteContainerSeconds(SymbolDependencies); -} - bool SymbolManager::canSymbolicate(QualType T) { T = T.getCanonicalType(); @@ -362,13 +340,9 @@ bool SymbolManager::canSymbolicate(QualType T) { void SymbolManager::addSymbolDependency(const SymbolRef Primary, const SymbolRef Dependent) { - SymbolDependTy::iterator I = SymbolDependencies.find(Primary); - SymbolRefSmallVectorTy *dependencies = nullptr; - if (I == SymbolDependencies.end()) { - dependencies = new SymbolRefSmallVectorTy(); - SymbolDependencies[Primary] = dependencies; - } else { - dependencies = I->second; + auto &dependencies = SymbolDependencies[Primary]; + if (!dependencies) { + dependencies = std::make_unique<SymbolRefSmallVectorTy>(); } dependencies->push_back(Dependent); } @@ -378,7 +352,7 @@ const SymbolRefSmallVectorTy *SymbolManager::getDependentSymbols( SymbolDependTy::const_iterator I = SymbolDependencies.find(Primary); if (I == SymbolDependencies.end()) return nullptr; - return I->second; + return I->second.get(); } void SymbolReaper::markDependentsLive(SymbolRef sym) { @@ -542,6 +516,11 @@ bool SymbolReaper::isLive(const VarRegion *VR, bool includeStoreBindings) const{ if (!Loc) return true; + // Anonymous parameters of an inheriting constructor are live for the entire + // duration of the constructor. + if (isa<CXXInheritedCtorInitExpr>(Loc)) + return true; + if (LCtx->getAnalysis<RelaxedLiveVariables>()->isLive(Loc, VR->getDecl())) return true; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp new file mode 100644 index 000000000000..f4c7e5978e19 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp @@ -0,0 +1,156 @@ +//===--- TextDiagnostics.cpp - Text Diagnostics for Paths -------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines the TextDiagnostics object. +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/PathDiagnostic.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/Version.h" +#include "clang/CrossTU/CrossTranslationUnit.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" +#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Casting.h" + +using namespace clang; +using namespace ento; +using namespace tooling; + +namespace { +/// Emitsd minimal diagnostics (report message + notes) for the 'none' output +/// type to the standard error, or to to compliment many others. Emits detailed +/// diagnostics in textual format for the 'text' output type. +class TextDiagnostics : public PathDiagnosticConsumer { + DiagnosticsEngine &DiagEng; + const LangOptions &LO; + const bool IncludePath = false; + const bool ShouldEmitAsError = false; + const bool ApplyFixIts = false; + const bool ShouldDisplayCheckerName = false; + +public: + TextDiagnostics(DiagnosticsEngine &DiagEng, const LangOptions &LO, + bool ShouldIncludePath, const AnalyzerOptions &AnOpts) + : DiagEng(DiagEng), LO(LO), IncludePath(ShouldIncludePath), + ShouldEmitAsError(AnOpts.AnalyzerWerror), + ApplyFixIts(AnOpts.ShouldApplyFixIts), + ShouldDisplayCheckerName(AnOpts.ShouldDisplayCheckerNameForText) {} + ~TextDiagnostics() override {} + + StringRef getName() const override { return "TextDiagnostics"; } + + bool supportsLogicalOpControlFlow() const override { return true; } + bool supportsCrossFileDiagnostics() const override { return true; } + + PathGenerationScheme getGenerationScheme() const override { + return IncludePath ? Minimal : None; + } + + void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, + FilesMade *filesMade) override { + unsigned WarnID = + ShouldEmitAsError + ? DiagEng.getCustomDiagID(DiagnosticsEngine::Error, "%0") + : DiagEng.getCustomDiagID(DiagnosticsEngine::Warning, "%0"); + unsigned NoteID = DiagEng.getCustomDiagID(DiagnosticsEngine::Note, "%0"); + SourceManager &SM = DiagEng.getSourceManager(); + + Replacements Repls; + auto reportPiece = [&](unsigned ID, FullSourceLoc Loc, StringRef String, + ArrayRef<SourceRange> Ranges, + ArrayRef<FixItHint> Fixits) { + if (!ApplyFixIts) { + DiagEng.Report(Loc, ID) << String << Ranges << Fixits; + return; + } + + DiagEng.Report(Loc, ID) << String << Ranges; + for (const FixItHint &Hint : Fixits) { + Replacement Repl(SM, Hint.RemoveRange, Hint.CodeToInsert); + + if (llvm::Error Err = Repls.add(Repl)) { + llvm::errs() << "Error applying replacement " << Repl.toString() + << ": " << Err << "\n"; + } + } + }; + + for (std::vector<const PathDiagnostic *>::iterator I = Diags.begin(), + E = Diags.end(); + I != E; ++I) { + const PathDiagnostic *PD = *I; + std::string WarningMsg = + (ShouldDisplayCheckerName ? " [" + PD->getCheckerName() + "]" : "") + .str(); + + reportPiece(WarnID, PD->getLocation().asLocation(), + (PD->getShortDescription() + WarningMsg).str(), + PD->path.back()->getRanges(), PD->path.back()->getFixits()); + + // First, add extra notes, even if paths should not be included. + for (const auto &Piece : PD->path) { + if (!isa<PathDiagnosticNotePiece>(Piece.get())) + continue; + + reportPiece(NoteID, Piece->getLocation().asLocation(), + Piece->getString(), Piece->getRanges(), + Piece->getFixits()); + } + + if (!IncludePath) + continue; + + // Then, add the path notes if necessary. + PathPieces FlatPath = PD->path.flatten(/*ShouldFlattenMacros=*/true); + for (const auto &Piece : FlatPath) { + if (isa<PathDiagnosticNotePiece>(Piece.get())) + continue; + + reportPiece(NoteID, Piece->getLocation().asLocation(), + Piece->getString(), Piece->getRanges(), + Piece->getFixits()); + } + } + + if (!ApplyFixIts || Repls.empty()) + return; + + Rewriter Rewrite(SM, LO); + if (!applyAllReplacements(Repls, Rewrite)) { + llvm::errs() << "An error occured during applying fix-it.\n"; + } + + Rewrite.overwriteChangedFiles(); + } +}; +} // end anonymous namespace + +void ento::createTextPathDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &Prefix, const clang::Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { + C.emplace_back(new TextDiagnostics(PP.getDiagnostics(), PP.getLangOpts(), + /*ShouldIncludePath*/ true, AnalyzerOpts)); +} + +void ento::createTextMinimalPathDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &Prefix, const clang::Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { + C.emplace_back(new TextDiagnostics(PP.getDiagnostics(), PP.getLangOpts(), + /*ShouldIncludePath*/ false, + AnalyzerOpts)); +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index fea8100c3b3b..392049e21c6e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -12,7 +12,6 @@ #include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" #include "ModelInjector.h" -#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" @@ -21,10 +20,12 @@ #include "clang/Analysis/CFG.h" #include "clang/Analysis/CallGraph.h" #include "clang/Analysis/CodeInjector.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Basic/SourceManager.h" #include "clang/CrossTU/CrossTranslationUnit.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Lex/Preprocessor.h" +#include "clang/Rewrite/Core/Rewriter.h" #include "clang/StaticAnalyzer/Checkers/LocalCheckers.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" @@ -32,7 +33,6 @@ #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" -#include "clang/StaticAnalyzer/Frontend/CheckerRegistration.h" #include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/Statistic.h" #include "llvm/Support/FileSystem.h" @@ -61,114 +61,6 @@ STATISTIC(PercentReachableBlocks, "The % of reachable basic blocks."); STATISTIC(MaxCFGSize, "The maximum number of basic blocks in a function."); //===----------------------------------------------------------------------===// -// Special PathDiagnosticConsumers. -//===----------------------------------------------------------------------===// - -void ento::createPlistHTMLDiagnosticConsumer( - AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, - const std::string &prefix, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { - createHTMLDiagnosticConsumer(AnalyzerOpts, C, - llvm::sys::path::parent_path(prefix), PP, CTU); - createPlistMultiFileDiagnosticConsumer(AnalyzerOpts, C, prefix, PP, CTU); -} - -void ento::createTextPathDiagnosticConsumer( - AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, - const std::string &Prefix, const clang::Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { - llvm_unreachable("'text' consumer should be enabled on ClangDiags"); -} - -namespace { -class ClangDiagPathDiagConsumer : public PathDiagnosticConsumer { - DiagnosticsEngine &Diag; - bool IncludePath = false, ShouldEmitAsError = false, FixitsAsRemarks = false; - -public: - ClangDiagPathDiagConsumer(DiagnosticsEngine &Diag) - : Diag(Diag) {} - ~ClangDiagPathDiagConsumer() override {} - StringRef getName() const override { return "ClangDiags"; } - - bool supportsLogicalOpControlFlow() const override { return true; } - bool supportsCrossFileDiagnostics() const override { return true; } - - PathGenerationScheme getGenerationScheme() const override { - return IncludePath ? Minimal : None; - } - - void enablePaths() { IncludePath = true; } - void enableWerror() { ShouldEmitAsError = true; } - void enableFixitsAsRemarks() { FixitsAsRemarks = true; } - - void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, - FilesMade *filesMade) override { - unsigned WarnID = - ShouldEmitAsError - ? Diag.getCustomDiagID(DiagnosticsEngine::Error, "%0") - : Diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0"); - unsigned NoteID = Diag.getCustomDiagID(DiagnosticsEngine::Note, "%0"); - unsigned RemarkID = Diag.getCustomDiagID(DiagnosticsEngine::Remark, "%0"); - - auto reportPiece = - [&](unsigned ID, SourceLocation Loc, StringRef String, - ArrayRef<SourceRange> Ranges, ArrayRef<FixItHint> Fixits) { - if (!FixitsAsRemarks) { - Diag.Report(Loc, ID) << String << Ranges << Fixits; - } else { - Diag.Report(Loc, ID) << String << Ranges; - for (const FixItHint &Hint : Fixits) { - SourceManager &SM = Diag.getSourceManager(); - llvm::SmallString<128> Str; - llvm::raw_svector_ostream OS(Str); - // FIXME: Add support for InsertFromRange and - // BeforePreviousInsertion. - assert(!Hint.InsertFromRange.isValid() && "Not implemented yet!"); - assert(!Hint.BeforePreviousInsertions && "Not implemented yet!"); - OS << SM.getSpellingColumnNumber(Hint.RemoveRange.getBegin()) - << "-" << SM.getSpellingColumnNumber(Hint.RemoveRange.getEnd()) - << ": '" << Hint.CodeToInsert << "'"; - Diag.Report(Loc, RemarkID) << OS.str(); - } - } - }; - - for (std::vector<const PathDiagnostic *>::iterator I = Diags.begin(), - E = Diags.end(); - I != E; ++I) { - const PathDiagnostic *PD = *I; - reportPiece(WarnID, PD->getLocation().asLocation(), - PD->getShortDescription(), PD->path.back()->getRanges(), - PD->path.back()->getFixits()); - - // First, add extra notes, even if paths should not be included. - for (const auto &Piece : PD->path) { - if (!isa<PathDiagnosticNotePiece>(Piece.get())) - continue; - - reportPiece(NoteID, Piece->getLocation().asLocation(), - Piece->getString(), Piece->getRanges(), Piece->getFixits()); - } - - if (!IncludePath) - continue; - - // Then, add the path notes if necessary. - PathPieces FlatPath = PD->path.flatten(/*ShouldFlattenMacros=*/true); - for (const auto &Piece : FlatPath) { - if (isa<PathDiagnosticNotePiece>(Piece.get())) - continue; - - reportPiece(NoteID, Piece->getLocation().asLocation(), - Piece->getString(), Piece->getRanges(), Piece->getFixits()); - } - } - } -}; -} // end anonymous namespace - -//===----------------------------------------------------------------------===// // AnalysisConsumer declaration. //===----------------------------------------------------------------------===// @@ -192,7 +84,7 @@ class AnalysisConsumer : public AnalysisASTConsumer, public: ASTContext *Ctx; - const Preprocessor &PP; + Preprocessor &PP; const std::string OutDir; AnalyzerOptionsRef Opts; ArrayRef<std::string> Plugins; @@ -253,31 +145,16 @@ public: } void DigestAnalyzerOptions() { - if (Opts->AnalysisDiagOpt != PD_NONE) { - // Create the PathDiagnosticConsumer. - ClangDiagPathDiagConsumer *clangDiags = - new ClangDiagPathDiagConsumer(PP.getDiagnostics()); - PathConsumers.push_back(clangDiags); - - if (Opts->AnalyzerWerror) - clangDiags->enableWerror(); - - if (Opts->ShouldEmitFixItHintsAsRemarks) - clangDiags->enableFixitsAsRemarks(); - - if (Opts->AnalysisDiagOpt == PD_TEXT) { - clangDiags->enablePaths(); - - } else if (!OutDir.empty()) { - switch (Opts->AnalysisDiagOpt) { - default: + switch (Opts->AnalysisDiagOpt) { + case PD_NONE: + break; #define ANALYSIS_DIAGNOSTICS(NAME, CMDFLAG, DESC, CREATEFN) \ case PD_##NAME: \ CREATEFN(*Opts.get(), PathConsumers, OutDir, PP, CTU); \ break; #include "clang/StaticAnalyzer/Core/Analyses.def" - } - } + default: + llvm_unreachable("Unknown analyzer output type!"); } // Create the analyzer component creators. @@ -313,30 +190,29 @@ public: else if (Mode == AM_Path) { llvm::errs() << " (Path, "; switch (IMode) { - case ExprEngine::Inline_Minimal: - llvm::errs() << " Inline_Minimal"; - break; - case ExprEngine::Inline_Regular: - llvm::errs() << " Inline_Regular"; - break; + case ExprEngine::Inline_Minimal: + llvm::errs() << " Inline_Minimal"; + break; + case ExprEngine::Inline_Regular: + llvm::errs() << " Inline_Regular"; + break; } llvm::errs() << ")"; - } - else + } else assert(Mode == (AM_Syntax | AM_Path) && "Unexpected mode!"); - llvm::errs() << ": " << Loc.getFilename() << ' ' - << getFunctionName(D) << '\n'; + llvm::errs() << ": " << Loc.getFilename() << ' ' << getFunctionName(D) + << '\n'; } } void Initialize(ASTContext &Context) override { Ctx = &Context; - checkerMgr = createCheckerManager( - *Ctx, *Opts, Plugins, CheckerRegistrationFns, PP.getDiagnostics()); + checkerMgr = std::make_unique<CheckerManager>(*Ctx, *Opts, PP, Plugins, + CheckerRegistrationFns); - Mgr = std::make_unique<AnalysisManager>(*Ctx, PathConsumers, CreateStoreMgr, - CreateConstraintMgr, + Mgr = std::make_unique<AnalysisManager>(*Ctx, PP, PathConsumers, + CreateStoreMgr, CreateConstraintMgr, checkerMgr.get(), *Opts, Injector); } @@ -469,7 +345,7 @@ private: /// Print \p S to stderr if \c Opts->AnalyzerDisplayProgress is set. void reportAnalyzerProgress(StringRef S); -}; +}; // namespace } // end anonymous namespace @@ -503,6 +379,13 @@ static bool shouldSkipFunction(const Decl *D, if (VisitedAsTopLevel.count(D)) return true; + // Skip analysis of inheriting constructors as top-level functions. These + // constructors don't even have a body written down in the code, so even if + // we find a bug, we won't be able to display it. + if (const auto *CD = dyn_cast<CXXConstructorDecl>(D)) + if (CD->isInheritingConstructor()) + return true; + // We want to re-analyse the functions as top level in the following cases: // - The 'init' methods should be reanalyzed because // ObjCNonNilReturnValueChecker assumes that '[super init]' never returns diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/AnalyzerHelpFlags.cpp index f4f06e32cd1d..eb6014a0629d 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/AnalyzerHelpFlags.cpp @@ -10,8 +10,9 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Frontend/CheckerRegistration.h" +#include "clang/StaticAnalyzer/Frontend/AnalyzerHelpFlags.h" #include "clang/Basic/Diagnostic.h" +#include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendDiagnostic.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -24,53 +25,36 @@ using namespace clang; using namespace ento; -std::unique_ptr<CheckerManager> ento::createCheckerManager( - ASTContext &context, - AnalyzerOptions &opts, - ArrayRef<std::string> plugins, - ArrayRef<std::function<void(CheckerRegistry &)>> checkerRegistrationFns, - DiagnosticsEngine &diags) { - auto checkerMgr = std::make_unique<CheckerManager>(context, opts); - - CheckerRegistry allCheckers(plugins, diags, opts, context.getLangOpts(), - checkerRegistrationFns); - - allCheckers.initializeManager(*checkerMgr); - allCheckers.validateCheckerOptions(); - checkerMgr->finishedCheckerRegistration(); - - return checkerMgr; -} - -void ento::printCheckerHelp(raw_ostream &out, ArrayRef<std::string> plugins, - AnalyzerOptions &anopts, - DiagnosticsEngine &diags, - const LangOptions &langOpts) { +void ento::printCheckerHelp(raw_ostream &out, CompilerInstance &CI) { out << "OVERVIEW: Clang Static Analyzer Checkers List\n\n"; out << "USAGE: -analyzer-checker <CHECKER or PACKAGE,...>\n\n"; - CheckerRegistry(plugins, diags, anopts, langOpts) - .printCheckerWithDescList(out); + auto CheckerMgr = std::make_unique<CheckerManager>( + *CI.getAnalyzerOpts(), CI.getLangOpts(), CI.getDiagnostics(), + CI.getFrontendOpts().Plugins); + + CheckerMgr->getCheckerRegistryData().printCheckerWithDescList( + *CI.getAnalyzerOpts(), out); } -void ento::printEnabledCheckerList(raw_ostream &out, - ArrayRef<std::string> plugins, - AnalyzerOptions &anopts, - DiagnosticsEngine &diags, - const LangOptions &langOpts) { +void ento::printEnabledCheckerList(raw_ostream &out, CompilerInstance &CI) { out << "OVERVIEW: Clang Static Analyzer Enabled Checkers List\n\n"; - CheckerRegistry(plugins, diags, anopts, langOpts) - .printEnabledCheckerList(out); + auto CheckerMgr = std::make_unique<CheckerManager>( + *CI.getAnalyzerOpts(), CI.getLangOpts(), CI.getDiagnostics(), + CI.getFrontendOpts().Plugins); + + CheckerMgr->getCheckerRegistryData().printEnabledCheckerList(out); } -void ento::printCheckerConfigList(raw_ostream &OS, - ArrayRef<std::string> plugins, - AnalyzerOptions &opts, - DiagnosticsEngine &diags, - const LangOptions &LangOpts) { - CheckerRegistry(plugins, diags, opts, LangOpts) - .printCheckerOptionList(OS); +void ento::printCheckerConfigList(raw_ostream &out, CompilerInstance &CI) { + + auto CheckerMgr = std::make_unique<CheckerManager>( + *CI.getAnalyzerOpts(), CI.getLangOpts(), CI.getDiagnostics(), + CI.getFrontendOpts().Plugins); + + CheckerMgr->getCheckerRegistryData().printCheckerOptionList( + *CI.getAnalyzerOpts(), out); } void ento::printAnalyzerConfigList(raw_ostream &out) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp index f5c05281adab..528284ca8985 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp @@ -25,9 +25,12 @@ using namespace clang; using namespace ento; +using namespace checker_registry; using llvm::sys::DynamicLibrary; -using RegisterCheckersFn = void (*)(CheckerRegistry &); +//===----------------------------------------------------------------------===// +// Utilities. +//===----------------------------------------------------------------------===// static bool isCompatibleAPIVersion(const char *VersionString) { // If the version string is null, its not an analyzer plugin. @@ -39,80 +42,17 @@ static bool isCompatibleAPIVersion(const char *VersionString) { return strcmp(VersionString, CLANG_ANALYZER_API_VERSION_STRING) == 0; } -namespace { -template <class T> struct FullNameLT { - bool operator()(const T &Lhs, const T &Rhs) { - return Lhs.FullName < Rhs.FullName; - } -}; - -using PackageNameLT = FullNameLT<CheckerRegistry::PackageInfo>; -using CheckerNameLT = FullNameLT<CheckerRegistry::CheckerInfo>; -} // end of anonymous namespace - -template <class CheckerOrPackageInfoList> -static - typename std::conditional<std::is_const<CheckerOrPackageInfoList>::value, - typename CheckerOrPackageInfoList::const_iterator, - typename CheckerOrPackageInfoList::iterator>::type - binaryFind(CheckerOrPackageInfoList &Collection, StringRef FullName) { - - using CheckerOrPackage = typename CheckerOrPackageInfoList::value_type; - using CheckerOrPackageFullNameLT = FullNameLT<CheckerOrPackage>; - - assert(std::is_sorted(Collection.begin(), Collection.end(), - CheckerOrPackageFullNameLT{}) && - "In order to efficiently gather checkers/packages, this function " - "expects them to be already sorted!"); - - return llvm::lower_bound(Collection, CheckerOrPackage(FullName), - CheckerOrPackageFullNameLT{}); -} - static constexpr char PackageSeparator = '.'; -static bool isInPackage(const CheckerRegistry::CheckerInfo &Checker, - StringRef PackageName) { - // Does the checker's full name have the package as a prefix? - if (!Checker.FullName.startswith(PackageName)) - return false; - - // Is the package actually just the name of a specific checker? - if (Checker.FullName.size() == PackageName.size()) - return true; - - // Is the checker in the package (or a subpackage)? - if (Checker.FullName[PackageName.size()] == PackageSeparator) - return true; - - return false; -} - -CheckerRegistry::CheckerInfoListRange -CheckerRegistry::getMutableCheckersForCmdLineArg(StringRef CmdLineArg) { - auto It = binaryFind(Checkers, CmdLineArg); - - if (!isInPackage(*It, CmdLineArg)) - return {Checkers.end(), Checkers.end()}; - - // See how large the package is. - // If the package doesn't exist, assume the option refers to a single - // checker. - size_t Size = 1; - llvm::StringMap<size_t>::const_iterator PackageSize = - PackageSizes.find(CmdLineArg); - - if (PackageSize != PackageSizes.end()) - Size = PackageSize->getValue(); - - return {It, It + Size}; -} +//===----------------------------------------------------------------------===// +// Methods of CheckerRegistry. +//===----------------------------------------------------------------------===// CheckerRegistry::CheckerRegistry( - ArrayRef<std::string> Plugins, DiagnosticsEngine &Diags, - AnalyzerOptions &AnOpts, const LangOptions &LangOpts, + CheckerRegistryData &Data, ArrayRef<std::string> Plugins, + DiagnosticsEngine &Diags, AnalyzerOptions &AnOpts, ArrayRef<std::function<void(CheckerRegistry &)>> CheckerRegistrationFns) - : Diags(Diags), AnOpts(AnOpts), LangOpts(LangOpts) { + : Data(Data), Diags(Diags), AnOpts(AnOpts) { // Register builtin checkers. #define GET_CHECKERS @@ -152,9 +92,10 @@ CheckerRegistry::CheckerRegistry( continue; } + using RegisterPluginCheckerFn = void (*)(CheckerRegistry &); // Register its checkers. - RegisterCheckersFn RegisterPluginCheckers = - reinterpret_cast<RegisterCheckersFn>( + RegisterPluginCheckerFn RegisterPluginCheckers = + reinterpret_cast<RegisterPluginCheckerFn>( Lib.getAddressOfSymbol("clang_registerCheckers")); if (RegisterPluginCheckers) RegisterPluginCheckers(*this); @@ -171,38 +112,67 @@ CheckerRegistry::CheckerRegistry( // FIXME: Alphabetical sort puts 'experimental' in the middle. // Would it be better to name it '~experimental' or something else // that's ASCIIbetically last? - llvm::sort(Packages, PackageNameLT{}); - llvm::sort(Checkers, CheckerNameLT{}); + llvm::sort(Data.Packages, checker_registry::PackageNameLT{}); + llvm::sort(Data.Checkers, checker_registry::CheckerNameLT{}); #define GET_CHECKER_DEPENDENCIES #define CHECKER_DEPENDENCY(FULLNAME, DEPENDENCY) \ addDependency(FULLNAME, DEPENDENCY); +#define GET_CHECKER_WEAK_DEPENDENCIES + +#define CHECKER_WEAK_DEPENDENCY(FULLNAME, DEPENDENCY) \ + addWeakDependency(FULLNAME, DEPENDENCY); + #define GET_CHECKER_OPTIONS -#define CHECKER_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL, DEVELOPMENT_STATUS, IS_HIDDEN) \ - addCheckerOption(TYPE, FULLNAME, CMDFLAG, DEFAULT_VAL, DESC, DEVELOPMENT_STATUS, IS_HIDDEN); +#define CHECKER_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL, \ + DEVELOPMENT_STATUS, IS_HIDDEN) \ + addCheckerOption(TYPE, FULLNAME, CMDFLAG, DEFAULT_VAL, DESC, \ + DEVELOPMENT_STATUS, IS_HIDDEN); #define GET_PACKAGE_OPTIONS -#define PACKAGE_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL, DEVELOPMENT_STATUS, IS_HIDDEN) \ - addPackageOption(TYPE, FULLNAME, CMDFLAG, DEFAULT_VAL, DESC, DEVELOPMENT_STATUS, IS_HIDDEN); +#define PACKAGE_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL, \ + DEVELOPMENT_STATUS, IS_HIDDEN) \ + addPackageOption(TYPE, FULLNAME, CMDFLAG, DEFAULT_VAL, DESC, \ + DEVELOPMENT_STATUS, IS_HIDDEN); #include "clang/StaticAnalyzer/Checkers/Checkers.inc" #undef CHECKER_DEPENDENCY #undef GET_CHECKER_DEPENDENCIES +#undef CHECKER_WEAK_DEPENDENCY +#undef GET_CHECKER_WEAK_DEPENDENCIES #undef CHECKER_OPTION #undef GET_CHECKER_OPTIONS #undef PACKAGE_OPTION #undef GET_PACKAGE_OPTIONS - resolveDependencies(); + resolveDependencies<true>(); + resolveDependencies<false>(); + +#ifndef NDEBUG + for (auto &DepPair : Data.Dependencies) { + for (auto &WeakDepPair : Data.WeakDependencies) { + // Some assertions to enforce that strong dependencies are relations in + // between purely modeling checkers, and weak dependencies are about + // diagnostics. + assert(WeakDepPair != DepPair && + "A checker cannot strong and weak depend on the same checker!"); + assert(WeakDepPair.first != DepPair.second && + "A strong dependency mustn't have weak dependencies!"); + assert(WeakDepPair.second != DepPair.second && + "A strong dependency mustn't be a weak dependency as well!"); + } + } +#endif + resolveCheckerAndPackageOptions(); // Parse '-analyzer-checker' and '-analyzer-disable-checker' options from the // command line. for (const std::pair<std::string, bool> &Opt : AnOpts.CheckersAndPackages) { CheckerInfoListRange CheckerForCmdLineArg = - getMutableCheckersForCmdLineArg(Opt.first); + Data.getMutableCheckersForCmdLineArg(Opt.first); if (CheckerForCmdLineArg.begin() == CheckerForCmdLineArg.end()) { Diags.Report(diag::err_unknown_analyzer_checker_or_package) << Opt.first; @@ -214,109 +184,169 @@ CheckerRegistry::CheckerRegistry( : StateFromCmdLine::State_Disabled; } } + validateCheckerOptions(); } -/// Collects dependencies in \p ret, returns false on failure. -static bool -collectDependenciesImpl(const CheckerRegistry::ConstCheckerInfoList &Deps, - const LangOptions &LO, - CheckerRegistry::CheckerInfoSet &Ret); - -/// Collects dependenies in \p enabledCheckers. Return None on failure. -LLVM_NODISCARD -static llvm::Optional<CheckerRegistry::CheckerInfoSet> -collectDependencies(const CheckerRegistry::CheckerInfo &checker, - const LangOptions &LO) { - - CheckerRegistry::CheckerInfoSet Ret; - // Add dependencies to the enabled checkers only if all of them can be - // enabled. - if (!collectDependenciesImpl(checker.Dependencies, LO, Ret)) - return None; - - return Ret; -} - -static bool -collectDependenciesImpl(const CheckerRegistry::ConstCheckerInfoList &Deps, - const LangOptions &LO, - CheckerRegistry::CheckerInfoSet &Ret) { +//===----------------------------------------------------------------------===// +// Dependency resolving. +//===----------------------------------------------------------------------===// - for (const CheckerRegistry::CheckerInfo *Dependency : Deps) { +template <typename IsEnabledFn> +static bool collectStrongDependencies(const ConstCheckerInfoList &Deps, + const CheckerManager &Mgr, + CheckerInfoSet &Ret, + IsEnabledFn IsEnabled); + +/// Collects weak dependencies in \p enabledData.Checkers. +template <typename IsEnabledFn> +static void collectWeakDependencies(const ConstCheckerInfoList &Deps, + const CheckerManager &Mgr, + CheckerInfoSet &Ret, IsEnabledFn IsEnabled); + +void CheckerRegistry::initializeRegistry(const CheckerManager &Mgr) { + // First, we calculate the list of enabled checkers as specified by the + // invocation. Weak dependencies will not enable their unspecified strong + // depenencies, but its only after resolving strong dependencies for all + // checkers when we know whether they will be enabled. + CheckerInfoSet Tmp; + auto IsEnabledFromCmdLine = [&](const CheckerInfo *Checker) { + return !Checker->isDisabled(Mgr); + }; + for (const CheckerInfo &Checker : Data.Checkers) { + if (!Checker.isEnabled(Mgr)) + continue; - if (Dependency->isDisabled(LO)) - return false; + CheckerInfoSet Deps; + if (!collectStrongDependencies(Checker.Dependencies, Mgr, Deps, + IsEnabledFromCmdLine)) { + // If we failed to enable any of the dependencies, don't enable this + // checker. + continue; + } - // Collect dependencies recursively. - if (!collectDependenciesImpl(Dependency->Dependencies, LO, Ret)) - return false; + Tmp.insert(Deps.begin(), Deps.end()); - Ret.insert(Dependency); + // Enable the checker. + Tmp.insert(&Checker); } - return true; -} - -CheckerRegistry::CheckerInfoSet CheckerRegistry::getEnabledCheckers() const { - - CheckerInfoSet EnabledCheckers; - - for (const CheckerInfo &Checker : Checkers) { - if (!Checker.isEnabled(LangOpts)) + // Calculate enabled checkers with the correct registration order. As this is + // done recursively, its arguably cheaper, but for sure less error prone to + // recalculate from scratch. + auto IsEnabled = [&](const CheckerInfo *Checker) { + return llvm::is_contained(Tmp, Checker); + }; + for (const CheckerInfo &Checker : Data.Checkers) { + if (!Checker.isEnabled(Mgr)) continue; - // Recursively enable its dependencies. - llvm::Optional<CheckerInfoSet> Deps = - collectDependencies(Checker, LangOpts); + CheckerInfoSet Deps; - if (!Deps) { + collectWeakDependencies(Checker.WeakDependencies, Mgr, Deps, IsEnabled); + + if (!collectStrongDependencies(Checker.Dependencies, Mgr, Deps, + IsEnabledFromCmdLine)) { // If we failed to enable any of the dependencies, don't enable this // checker. continue; } // Note that set_union also preserves the order of insertion. - EnabledCheckers.set_union(*Deps); + Data.EnabledCheckers.set_union(Deps); + Data.EnabledCheckers.insert(&Checker); + } +} - // Enable the checker. - EnabledCheckers.insert(&Checker); +template <typename IsEnabledFn> +static bool collectStrongDependencies(const ConstCheckerInfoList &Deps, + const CheckerManager &Mgr, + CheckerInfoSet &Ret, + IsEnabledFn IsEnabled) { + + for (const CheckerInfo *Dependency : Deps) { + if (!IsEnabled(Dependency)) + return false; + + // Collect dependencies recursively. + if (!collectStrongDependencies(Dependency->Dependencies, Mgr, Ret, + IsEnabled)) + return false; + Ret.insert(Dependency); } - return EnabledCheckers; + return true; +} + +template <typename IsEnabledFn> +static void collectWeakDependencies(const ConstCheckerInfoList &WeakDeps, + const CheckerManager &Mgr, + CheckerInfoSet &Ret, + IsEnabledFn IsEnabled) { + + for (const CheckerInfo *Dependency : WeakDeps) { + // Don't enable this checker if strong dependencies are unsatisfied, but + // assume that weak dependencies are transitive. + collectWeakDependencies(Dependency->WeakDependencies, Mgr, Ret, IsEnabled); + + if (IsEnabled(Dependency) && + collectStrongDependencies(Dependency->Dependencies, Mgr, Ret, + IsEnabled)) + Ret.insert(Dependency); + } } -void CheckerRegistry::resolveDependencies() { - for (const std::pair<StringRef, StringRef> &Entry : Dependencies) { - auto CheckerIt = binaryFind(Checkers, Entry.first); - assert(CheckerIt != Checkers.end() && CheckerIt->FullName == Entry.first && +template <bool IsWeak> void CheckerRegistry::resolveDependencies() { + for (const std::pair<StringRef, StringRef> &Entry : + (IsWeak ? Data.WeakDependencies : Data.Dependencies)) { + + auto CheckerIt = binaryFind(Data.Checkers, Entry.first); + assert(CheckerIt != Data.Checkers.end() && + CheckerIt->FullName == Entry.first && "Failed to find the checker while attempting to set up its " "dependencies!"); - auto DependencyIt = binaryFind(Checkers, Entry.second); - assert(DependencyIt != Checkers.end() && + auto DependencyIt = binaryFind(Data.Checkers, Entry.second); + assert(DependencyIt != Data.Checkers.end() && DependencyIt->FullName == Entry.second && "Failed to find the dependency of a checker!"); - CheckerIt->Dependencies.emplace_back(&*DependencyIt); + // We do allow diagnostics from unit test/example dependency checkers. + assert((DependencyIt->FullName.startswith("test") || + DependencyIt->FullName.startswith("example") || IsWeak || + DependencyIt->IsHidden) && + "Strong dependencies are modeling checkers, and as such " + "non-user facing! Mark them hidden in Checkers.td!"); + + if (IsWeak) + CheckerIt->WeakDependencies.emplace_back(&*DependencyIt); + else + CheckerIt->Dependencies.emplace_back(&*DependencyIt); } - - Dependencies.clear(); } void CheckerRegistry::addDependency(StringRef FullName, StringRef Dependency) { - Dependencies.emplace_back(FullName, Dependency); + Data.Dependencies.emplace_back(FullName, Dependency); } +void CheckerRegistry::addWeakDependency(StringRef FullName, + StringRef Dependency) { + Data.WeakDependencies.emplace_back(FullName, Dependency); +} + +//===----------------------------------------------------------------------===// +// Checker option resolving and validating. +//===----------------------------------------------------------------------===// + /// Insert the checker/package option to AnalyzerOptions' config table, and /// validate it, if the user supplied it on the command line. -static void insertAndValidate(StringRef FullName, - const CheckerRegistry::CmdLineOption &Option, +static void insertAndValidate(StringRef FullName, const CmdLineOption &Option, AnalyzerOptions &AnOpts, DiagnosticsEngine &Diags) { std::string FullOption = (FullName + ":" + Option.OptionName).str(); - auto It = AnOpts.Config.insert({FullOption, Option.DefaultValStr}); + auto It = + AnOpts.Config.insert({FullOption, std::string(Option.DefaultValStr)}); // Insertation was successful -- CmdLineOption's constructor will validate // whether values received from plugins or TableGen files are correct. @@ -337,7 +367,7 @@ static void insertAndValidate(StringRef FullName, << FullOption << "a boolean value"; } - It.first->setValue(Option.DefaultValStr); + It.first->setValue(std::string(Option.DefaultValStr)); } return; } @@ -351,17 +381,17 @@ static void insertAndValidate(StringRef FullName, << FullOption << "an integer value"; } - It.first->setValue(Option.DefaultValStr); + It.first->setValue(std::string(Option.DefaultValStr)); } return; } } template <class T> -static void -insertOptionToCollection(StringRef FullName, T &Collection, - const CheckerRegistry::CmdLineOption &Option, - AnalyzerOptions &AnOpts, DiagnosticsEngine &Diags) { +static void insertOptionToCollection(StringRef FullName, T &Collection, + const CmdLineOption &Option, + AnalyzerOptions &AnOpts, + DiagnosticsEngine &Diags) { auto It = binaryFind(Collection, FullName); assert(It != Collection.end() && "Failed to find the checker while attempting to add a command line " @@ -374,22 +404,20 @@ insertOptionToCollection(StringRef FullName, T &Collection, void CheckerRegistry::resolveCheckerAndPackageOptions() { for (const std::pair<StringRef, CmdLineOption> &CheckerOptEntry : - CheckerOptions) { - insertOptionToCollection(CheckerOptEntry.first, Checkers, + Data.CheckerOptions) { + insertOptionToCollection(CheckerOptEntry.first, Data.Checkers, CheckerOptEntry.second, AnOpts, Diags); } - CheckerOptions.clear(); for (const std::pair<StringRef, CmdLineOption> &PackageOptEntry : - PackageOptions) { - insertOptionToCollection(PackageOptEntry.first, Packages, + Data.PackageOptions) { + insertOptionToCollection(PackageOptEntry.first, Data.Packages, PackageOptEntry.second, AnOpts, Diags); } - PackageOptions.clear(); } void CheckerRegistry::addPackage(StringRef FullName) { - Packages.emplace_back(PackageInfo(FullName)); + Data.Packages.emplace_back(PackageInfo(FullName)); } void CheckerRegistry::addPackageOption(StringRef OptionType, @@ -399,22 +427,22 @@ void CheckerRegistry::addPackageOption(StringRef OptionType, StringRef Description, StringRef DevelopmentStatus, bool IsHidden) { - PackageOptions.emplace_back( + Data.PackageOptions.emplace_back( PackageFullName, CmdLineOption{OptionType, OptionName, DefaultValStr, Description, DevelopmentStatus, IsHidden}); } -void CheckerRegistry::addChecker(InitializationFunction Rfn, +void CheckerRegistry::addChecker(RegisterCheckerFn Rfn, ShouldRegisterFunction Sfn, StringRef Name, StringRef Desc, StringRef DocsUri, bool IsHidden) { - Checkers.emplace_back(Rfn, Sfn, Name, Desc, DocsUri, IsHidden); + Data.Checkers.emplace_back(Rfn, Sfn, Name, Desc, DocsUri, IsHidden); // Record the presence of the checker in its packages. StringRef PackageName, LeafName; std::tie(PackageName, LeafName) = Name.rsplit(PackageSeparator); while (!LeafName.empty()) { - PackageSizes[PackageName] += 1; + Data.PackageSizes[PackageName] += 1; std::tie(PackageName, LeafName) = PackageName.rsplit(PackageSeparator); } } @@ -426,37 +454,33 @@ void CheckerRegistry::addCheckerOption(StringRef OptionType, StringRef Description, StringRef DevelopmentStatus, bool IsHidden) { - CheckerOptions.emplace_back( + Data.CheckerOptions.emplace_back( CheckerFullName, CmdLineOption{OptionType, OptionName, DefaultValStr, Description, DevelopmentStatus, IsHidden}); } void CheckerRegistry::initializeManager(CheckerManager &CheckerMgr) const { - // Collect checkers enabled by the options. - CheckerInfoSet enabledCheckers = getEnabledCheckers(); - // Initialize the CheckerManager with all enabled checkers. - for (const auto *Checker : enabledCheckers) { + for (const auto *Checker : Data.EnabledCheckers) { CheckerMgr.setCurrentCheckerName(CheckerNameRef(Checker->FullName)); Checker->Initialize(CheckerMgr); } } -static void -isOptionContainedIn(const CheckerRegistry::CmdLineOptionList &OptionList, - StringRef SuppliedChecker, StringRef SuppliedOption, - const AnalyzerOptions &AnOpts, DiagnosticsEngine &Diags) { +static void isOptionContainedIn(const CmdLineOptionList &OptionList, + StringRef SuppliedChecker, + StringRef SuppliedOption, + const AnalyzerOptions &AnOpts, + DiagnosticsEngine &Diags) { if (!AnOpts.ShouldEmitErrorsOnInvalidConfigValue) return; - using CmdLineOption = CheckerRegistry::CmdLineOption; - auto SameOptName = [SuppliedOption](const CmdLineOption &Opt) { return Opt.OptionName == SuppliedOption; }; - auto OptionIt = llvm::find_if(OptionList, SameOptName); + const auto *OptionIt = llvm::find_if(OptionList, SameOptName); if (OptionIt == OptionList.end()) { Diags.Report(diag::err_analyzer_checker_option_unknown) @@ -485,16 +509,16 @@ void CheckerRegistry::validateCheckerOptions() const { // it would return with an iterator to the first checker in the core, so we // we really have to use find here, which uses operator==. auto CheckerIt = - llvm::find(Checkers, CheckerInfo(SuppliedCheckerOrPackage)); - if (CheckerIt != Checkers.end()) { + llvm::find(Data.Checkers, CheckerInfo(SuppliedCheckerOrPackage)); + if (CheckerIt != Data.Checkers.end()) { isOptionContainedIn(CheckerIt->CmdLineOptions, SuppliedCheckerOrPackage, SuppliedOption, AnOpts, Diags); continue; } - auto PackageIt = - llvm::find(Packages, PackageInfo(SuppliedCheckerOrPackage)); - if (PackageIt != Packages.end()) { + const auto *PackageIt = + llvm::find(Data.Packages, PackageInfo(SuppliedCheckerOrPackage)); + if (PackageIt != Data.Packages.end()) { isOptionContainedIn(PackageIt->CmdLineOptions, SuppliedCheckerOrPackage, SuppliedOption, AnOpts, Diags); continue; @@ -505,121 +529,3 @@ void CheckerRegistry::validateCheckerOptions() const { } } -void CheckerRegistry::printCheckerWithDescList(raw_ostream &Out, - size_t MaxNameChars) const { - // FIXME: Print available packages. - - Out << "CHECKERS:\n"; - - // Find the maximum option length. - size_t OptionFieldWidth = 0; - for (const auto &Checker : Checkers) { - // Limit the amount of padding we are willing to give up for alignment. - // Package.Name Description [Hidden] - size_t NameLength = Checker.FullName.size(); - if (NameLength <= MaxNameChars) - OptionFieldWidth = std::max(OptionFieldWidth, NameLength); - } - - const size_t InitialPad = 2; - - auto Print = [=](llvm::raw_ostream &Out, const CheckerInfo &Checker, - StringRef Description) { - AnalyzerOptions::printFormattedEntry(Out, {Checker.FullName, Description}, - InitialPad, OptionFieldWidth); - Out << '\n'; - }; - - for (const auto &Checker : Checkers) { - // The order of this if branches is significant, we wouldn't like to display - // developer checkers even in the alpha output. For example, - // alpha.cplusplus.IteratorModeling is a modeling checker, hence it's hidden - // by default, and users (even when the user is a developer of an alpha - // checker) shouldn't normally tinker with whether they should be enabled. - - if (Checker.IsHidden) { - if (AnOpts.ShowCheckerHelpDeveloper) - Print(Out, Checker, Checker.Desc); - continue; - } - - if (Checker.FullName.startswith("alpha")) { - if (AnOpts.ShowCheckerHelpAlpha) - Print(Out, Checker, - ("(Enable only for development!) " + Checker.Desc).str()); - continue; - } - - if (AnOpts.ShowCheckerHelp) - Print(Out, Checker, Checker.Desc); - } -} - -void CheckerRegistry::printEnabledCheckerList(raw_ostream &Out) const { - // Collect checkers enabled by the options. - CheckerInfoSet EnabledCheckers = getEnabledCheckers(); - - for (const auto *i : EnabledCheckers) - Out << i->FullName << '\n'; -} - -void CheckerRegistry::printCheckerOptionList(raw_ostream &Out) const { - Out << "OVERVIEW: Clang Static Analyzer Checker and Package Option List\n\n"; - Out << "USAGE: -analyzer-config <OPTION1=VALUE,OPTION2=VALUE,...>\n\n"; - Out << " -analyzer-config OPTION1=VALUE, -analyzer-config " - "OPTION2=VALUE, ...\n\n"; - Out << "OPTIONS:\n\n"; - - std::multimap<StringRef, const CmdLineOption &> OptionMap; - - for (const CheckerInfo &Checker : Checkers) { - for (const CmdLineOption &Option : Checker.CmdLineOptions) { - OptionMap.insert({Checker.FullName, Option}); - } - } - - for (const PackageInfo &Package : Packages) { - for (const CmdLineOption &Option : Package.CmdLineOptions) { - OptionMap.insert({Package.FullName, Option}); - } - } - - auto Print = [] (llvm::raw_ostream &Out, StringRef FullOption, StringRef Desc) { - AnalyzerOptions::printFormattedEntry(Out, {FullOption, Desc}, - /*InitialPad*/ 2, - /*EntryWidth*/ 50, - /*MinLineWidth*/ 90); - Out << "\n\n"; - }; - for (const std::pair<const StringRef, const CmdLineOption &> &Entry : - OptionMap) { - const CmdLineOption &Option = Entry.second; - std::string FullOption = (Entry.first + ":" + Option.OptionName).str(); - - std::string Desc = - ("(" + Option.OptionType + ") " + Option.Description + " (default: " + - (Option.DefaultValStr.empty() ? "\"\"" : Option.DefaultValStr) + ")") - .str(); - - // The list of these if branches is significant, we wouldn't like to - // display hidden alpha checker options for - // -analyzer-checker-option-help-alpha. - - if (Option.IsHidden) { - if (AnOpts.ShowCheckerOptionDeveloperList) - Print(Out, FullOption, Desc); - continue; - } - - if (Option.DevelopmentStatus == "alpha" || - Entry.first.startswith("alpha")) { - if (AnOpts.ShowCheckerOptionAlphaList) - Print(Out, FullOption, - llvm::Twine("(Enable only for development!) " + Desc).str()); - continue; - } - - if (AnOpts.ShowCheckerOptionList) - Print(Out, FullOption, Desc); - } -} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/CreateCheckerManager.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/CreateCheckerManager.cpp new file mode 100644 index 000000000000..21a60785eb52 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/CreateCheckerManager.cpp @@ -0,0 +1,50 @@ +//===- CheckerManager.h - Static Analyzer Checker Manager -------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines the Static Analyzer Checker Manager. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" +#include <memory> + +namespace clang { +namespace ento { + +CheckerManager::CheckerManager( + ASTContext &Context, AnalyzerOptions &AOptions, const Preprocessor &PP, + ArrayRef<std::string> plugins, + ArrayRef<std::function<void(CheckerRegistry &)>> checkerRegistrationFns) + : Context(&Context), LangOpts(Context.getLangOpts()), AOptions(AOptions), + PP(&PP), Diags(Context.getDiagnostics()), + RegistryData(std::make_unique<CheckerRegistryData>()) { + CheckerRegistry Registry(*RegistryData, plugins, Context.getDiagnostics(), + AOptions, checkerRegistrationFns); + Registry.initializeRegistry(*this); + Registry.initializeManager(*this); + finishedCheckerRegistration(); +} + +CheckerManager::CheckerManager(AnalyzerOptions &AOptions, + const LangOptions &LangOpts, + DiagnosticsEngine &Diags, + ArrayRef<std::string> plugins) + : LangOpts(LangOpts), AOptions(AOptions), Diags(Diags), + RegistryData(std::make_unique<CheckerRegistryData>()) { + CheckerRegistry Registry(*RegistryData, plugins, Diags, AOptions, {}); + Registry.initializeRegistry(*this); +} + +CheckerManager::~CheckerManager() { + for (const auto &CheckerDtor : CheckerDtors) + CheckerDtor(); +} + +} // namespace ento +} // namespace clang |