diff options
Diffstat (limited to 'contrib/llvm-project/clang/lib/StaticAnalyzer')
181 files changed, 16506 insertions, 8091 deletions
diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp index c06604b6cffe..a54f1b1e71d4 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp @@ -7,18 +7,20 @@ //===----------------------------------------------------------------------===// // This file reports various statistics about analyzer visitation. //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/DeclObjC.h" #include "clang/Basic/SourceManager.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/Statistic.h" #include "llvm/Support/raw_ostream.h" +#include <optional> using namespace clang; using namespace ento; @@ -51,15 +53,14 @@ void AnalyzerStatsChecker::checkEndAnalysis(ExplodedGraph &G, const Decl *D = LC->getDecl(); // Iterate over the exploded graph. - for (ExplodedGraph::node_iterator I = G.nodes_begin(); - I != G.nodes_end(); ++I) { - const ProgramPoint &P = I->getLocation(); + for (const ExplodedNode &N : G.nodes()) { + const ProgramPoint &P = N.getLocation(); // Only check the coverage in the top level function (optimization). if (D != P.getLocationContext()->getDecl()) continue; - if (Optional<BlockEntrance> BE = P.getAs<BlockEntrance>()) { + if (std::optional<BlockEntrance> BE = P.getAs<BlockEntrance>()) { const CFGBlock *CB = BE->getBlock(); reachable.insert(CB); } @@ -93,11 +94,10 @@ void AnalyzerStatsChecker::checkEndAnalysis(ExplodedGraph &G, if (!Loc.isValid()) return; - if (isa<FunctionDecl>(D) || isa<ObjCMethodDecl>(D)) { + if (isa<FunctionDecl, ObjCMethodDecl>(D)) { const NamedDecl *ND = cast<NamedDecl>(D); output << *ND; - } - else if (isa<BlockDecl>(D)) { + } else if (isa<BlockDecl>(D)) { output << "block(line:" << Loc.getLine() << ":col:" << Loc.getColumn(); } @@ -115,16 +115,13 @@ void AnalyzerStatsChecker::checkEndAnalysis(ExplodedGraph &G, output.str(), PathDiagnosticLocation(D, SM)); // Emit warning for each block we bailed out on. - typedef CoreEngine::BlocksExhausted::const_iterator ExhaustedIterator; const CoreEngine &CE = Eng.getCoreEngine(); - for (ExhaustedIterator I = CE.blocks_exhausted_begin(), - E = CE.blocks_exhausted_end(); I != E; ++I) { - const BlockEdge &BE = I->first; + for (const BlockEdge &BE : make_first_range(CE.exhausted_blocks())) { const CFGBlock *Exit = BE.getDst(); if (Exit->empty()) continue; const CFGElement &CE = Exit->front(); - if (Optional<CFGStmt> CS = CE.getAs<CFGStmt>()) { + if (std::optional<CFGStmt> CS = CE.getAs<CFGStmt>()) { SmallString<128> bufI; llvm::raw_svector_ostream outputI(bufI); outputI << "(" << NameOfRootFunction << ")" << diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp index 605b11874ef5..c990ad138f89 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp @@ -25,7 +25,7 @@ using namespace ento; namespace { class ArrayBoundChecker : public Checker<check::Location> { - mutable std::unique_ptr<BuiltinBug> BT; + const BugType BT{this, "Out-of-bound array access"}; public: void checkLocation(SVal l, bool isLoad, const Stmt* S, @@ -58,25 +58,20 @@ void ArrayBoundChecker::checkLocation(SVal l, bool isLoad, const Stmt* LoadS, DefinedOrUnknownSVal ElementCount = getDynamicElementCount( state, ER->getSuperRegion(), C.getSValBuilder(), ER->getValueType()); - ProgramStateRef StInBound = state->assumeInBound(Idx, ElementCount, true); - ProgramStateRef StOutBound = state->assumeInBound(Idx, ElementCount, false); + ProgramStateRef StInBound, StOutBound; + std::tie(StInBound, StOutBound) = state->assumeInBoundDual(Idx, ElementCount); if (StOutBound && !StInBound) { ExplodedNode *N = C.generateErrorNode(StOutBound); if (!N) return; - if (!BT) - BT.reset(new BuiltinBug( - this, "Out-of-bound array access", - "Access out-of-bound array element (buffer overflow)")); - // FIXME: It would be nice to eventually make this diagnostic more clear, // e.g., by referencing the original declaration or by saying *why* this // reference is outside the range. // Generate a report for this bug. - auto report = - std::make_unique<PathSensitiveBugReport>(*BT, BT->getDescription(), N); + auto report = std::make_unique<PathSensitiveBugReport>( + BT, "Access out-of-bound array element (buffer overflow)", N); report->addRange(LoadS->getSourceRange()); C.emitReport(std::move(report)); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp index 2a5fe9d8ed92..6c7a1601402e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp @@ -11,9 +11,10 @@ // //===----------------------------------------------------------------------===// -#include "Taint.h" #include "clang/AST/CharUnits.h" +#include "clang/AST/ParentMapContext.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Checkers/Taint.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -22,77 +23,134 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "llvm/ADT/SmallString.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/raw_ostream.h" +#include <optional> using namespace clang; using namespace ento; using namespace taint; +using llvm::formatv; namespace { -class ArrayBoundCheckerV2 : - public Checker<check::Location> { - mutable std::unique_ptr<BuiltinBug> BT; +enum OOB_Kind { OOB_Precedes, OOB_Exceeds, OOB_Taint }; - enum OOB_Kind { OOB_Precedes, OOB_Excedes, OOB_Tainted }; - - void reportOOB(CheckerContext &C, ProgramStateRef errorState, OOB_Kind kind, - std::unique_ptr<BugReporterVisitor> Visitor = nullptr) const; - -public: - void checkLocation(SVal l, bool isLoad, const Stmt*S, - CheckerContext &C) const; +struct Messages { + std::string Short, Full; }; -// FIXME: Eventually replace RegionRawOffset with this class. -class RegionRawOffsetV2 { -private: - const SubRegion *baseRegion; - SVal byteOffset; +// NOTE: The `ArraySubscriptExpr` and `UnaryOperator` callbacks are `PostStmt` +// instead of `PreStmt` because the current implementation passes the whole +// expression to `CheckerContext::getSVal()` which only works after the +// symbolic evaluation of the expression. (To turn them into `PreStmt` +// callbacks, we'd need to duplicate the logic that evaluates these +// expressions.) The `MemberExpr` callback would work as `PreStmt` but it's +// defined as `PostStmt` for the sake of consistency with the other callbacks. +class ArrayBoundCheckerV2 : public Checker<check::PostStmt<ArraySubscriptExpr>, + check::PostStmt<UnaryOperator>, + check::PostStmt<MemberExpr>> { + BugType BT{this, "Out-of-bound access"}; + BugType TaintBT{this, "Out-of-bound access", categories::TaintedData}; - RegionRawOffsetV2() - : baseRegion(nullptr), byteOffset(UnknownVal()) {} + void performCheck(const Expr *E, CheckerContext &C) const; -public: - RegionRawOffsetV2(const SubRegion* base, SVal offset) - : baseRegion(base), byteOffset(offset) {} + void reportOOB(CheckerContext &C, ProgramStateRef ErrorState, OOB_Kind Kind, + NonLoc Offset, Messages Msgs) const; - NonLoc getByteOffset() const { return byteOffset.castAs<NonLoc>(); } - const SubRegion *getRegion() const { return baseRegion; } + static bool isFromCtypeMacro(const Stmt *S, ASTContext &AC); - static RegionRawOffsetV2 computeOffset(ProgramStateRef state, - SValBuilder &svalBuilder, - SVal location); + static bool isInAddressOf(const Stmt *S, ASTContext &AC); - void dump() const; - void dumpToStream(raw_ostream &os) const; +public: + void checkPostStmt(const ArraySubscriptExpr *E, CheckerContext &C) const { + performCheck(E, C); + } + void checkPostStmt(const UnaryOperator *E, CheckerContext &C) const { + if (E->getOpcode() == UO_Deref) + performCheck(E, C); + } + void checkPostStmt(const MemberExpr *E, CheckerContext &C) const { + if (E->isArrow()) + performCheck(E->getBase(), C); + } }; -} -static SVal computeExtentBegin(SValBuilder &svalBuilder, - const MemRegion *region) { - const MemSpaceRegion *SR = region->getMemorySpace(); - if (SR->getKind() == MemRegion::UnknownSpaceRegionKind) - return UnknownVal(); - else - return svalBuilder.makeZeroArrayIndex(); +} // anonymous namespace + +/// For a given Location that can be represented as a symbolic expression +/// Arr[Idx] (or perhaps Arr[Idx1][Idx2] etc.), return the parent memory block +/// Arr and the distance of Location from the beginning of Arr (expressed in a +/// NonLoc that specifies the number of CharUnits). Returns nullopt when these +/// cannot be determined. +static std::optional<std::pair<const SubRegion *, NonLoc>> +computeOffset(ProgramStateRef State, SValBuilder &SVB, SVal Location) { + QualType T = SVB.getArrayIndexType(); + auto EvalBinOp = [&SVB, State, T](BinaryOperatorKind Op, NonLoc L, NonLoc R) { + // We will use this utility to add and multiply values. + return SVB.evalBinOpNN(State, Op, L, R, T).getAs<NonLoc>(); + }; + + const SubRegion *OwnerRegion = nullptr; + std::optional<NonLoc> Offset = SVB.makeZeroArrayIndex(); + + const ElementRegion *CurRegion = + dyn_cast_or_null<ElementRegion>(Location.getAsRegion()); + + while (CurRegion) { + const auto Index = CurRegion->getIndex().getAs<NonLoc>(); + if (!Index) + return std::nullopt; + + QualType ElemType = CurRegion->getElementType(); + + // FIXME: The following early return was presumably added to safeguard the + // getTypeSizeInChars() call (which doesn't accept an incomplete type), but + // it seems that `ElemType` cannot be incomplete at this point. + if (ElemType->isIncompleteType()) + return std::nullopt; + + // Calculate Delta = Index * sizeof(ElemType). + NonLoc Size = SVB.makeArrayIndex( + SVB.getContext().getTypeSizeInChars(ElemType).getQuantity()); + auto Delta = EvalBinOp(BO_Mul, *Index, Size); + if (!Delta) + return std::nullopt; + + // Perform Offset += Delta. + Offset = EvalBinOp(BO_Add, *Offset, *Delta); + if (!Offset) + return std::nullopt; + + OwnerRegion = CurRegion->getSuperRegion()->getAs<SubRegion>(); + // When this is just another ElementRegion layer, we need to continue the + // offset calculations: + CurRegion = dyn_cast_or_null<ElementRegion>(OwnerRegion); + } + + if (OwnerRegion) + return std::make_pair(OwnerRegion, *Offset); + + return std::nullopt; } // TODO: once the constraint manager is smart enough to handle non simplified // symbolic expressions remove this function. Note that this can not be used in // the constraint manager as is, since this does not handle overflows. It is // safe to assume, however, that memory offsets will not overflow. +// NOTE: callers of this function need to be aware of the effects of overflows +// and signed<->unsigned conversions! static std::pair<NonLoc, nonloc::ConcreteInt> getSimplifiedOffsets(NonLoc offset, nonloc::ConcreteInt extent, SValBuilder &svalBuilder) { - Optional<nonloc::SymbolVal> SymVal = offset.getAs<nonloc::SymbolVal>(); + std::optional<nonloc::SymbolVal> SymVal = offset.getAs<nonloc::SymbolVal>(); if (SymVal && SymVal->isExpression()) { if (const SymIntExpr *SIE = dyn_cast<SymIntExpr>(SymVal->getSymbol())) { llvm::APSInt constant = APSIntType(extent.getValue()).convert(SIE->getRHS()); switch (SIE->getOpcode()) { case BO_Mul: - // The constant should never be 0 here, since it the result of scaling - // based on the size of a type which is never 0. + // The constant should never be 0 here, becasue multiplication by zero + // is simplified by the engine. if ((extent.getValue() % constant) != 0) return std::pair<NonLoc, nonloc::ConcreteInt>(offset, extent); else @@ -113,10 +171,154 @@ getSimplifiedOffsets(NonLoc offset, nonloc::ConcreteInt extent, return std::pair<NonLoc, nonloc::ConcreteInt>(offset, extent); } -void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, - const Stmt* LoadS, - CheckerContext &checkerContext) const { +// Evaluate the comparison Value < Threshold with the help of the custom +// simplification algorithm defined for this checker. Return a pair of states, +// where the first one corresponds to "value below threshold" and the second +// corresponds to "value at or above threshold". Returns {nullptr, nullptr} in +// the case when the evaluation fails. +// If the optional argument CheckEquality is true, then use BO_EQ instead of +// the default BO_LT after consistently applying the same simplification steps. +static std::pair<ProgramStateRef, ProgramStateRef> +compareValueToThreshold(ProgramStateRef State, NonLoc Value, NonLoc Threshold, + SValBuilder &SVB, bool CheckEquality = false) { + if (auto ConcreteThreshold = Threshold.getAs<nonloc::ConcreteInt>()) { + std::tie(Value, Threshold) = getSimplifiedOffsets(Value, *ConcreteThreshold, SVB); + } + if (auto ConcreteThreshold = Threshold.getAs<nonloc::ConcreteInt>()) { + QualType T = Value.getType(SVB.getContext()); + if (T->isUnsignedIntegerType() && ConcreteThreshold->getValue().isNegative()) { + // In this case we reduced the bound check to a comparison of the form + // (symbol or value with unsigned type) < (negative number) + // which is always false. We are handling these cases separately because + // evalBinOpNN can perform a signed->unsigned conversion that turns the + // negative number into a huge positive value and leads to wildly + // inaccurate conclusions. + return {nullptr, State}; + } + } + const BinaryOperatorKind OpKind = CheckEquality ? BO_EQ : BO_LT; + auto BelowThreshold = + SVB.evalBinOpNN(State, OpKind, Value, Threshold, SVB.getConditionType()) + .getAs<NonLoc>(); + + if (BelowThreshold) + return State->assume(*BelowThreshold); + + return {nullptr, nullptr}; +} + +static std::string getRegionName(const SubRegion *Region) { + if (std::string RegName = Region->getDescriptiveName(); !RegName.empty()) + return RegName; + + // Field regions only have descriptive names when their parent has a + // descriptive name; so we provide a fallback representation for them: + if (const auto *FR = Region->getAs<FieldRegion>()) { + if (StringRef Name = FR->getDecl()->getName(); !Name.empty()) + return formatv("the field '{0}'", Name); + return "the unnamed field"; + } + + if (isa<AllocaRegion>(Region)) + return "the memory returned by 'alloca'"; + + if (isa<SymbolicRegion>(Region) && + isa<HeapSpaceRegion>(Region->getMemorySpace())) + return "the heap area"; + + if (isa<StringRegion>(Region)) + return "the string literal"; + + return "the region"; +} + +static std::optional<int64_t> getConcreteValue(NonLoc SV) { + if (auto ConcreteVal = SV.getAs<nonloc::ConcreteInt>()) { + return ConcreteVal->getValue().tryExtValue(); + } + return std::nullopt; +} + +static std::string getShortMsg(OOB_Kind Kind, std::string RegName) { + static const char *ShortMsgTemplates[] = { + "Out of bound access to memory preceding {0}", + "Out of bound access to memory after the end of {0}", + "Potential out of bound access to {0} with tainted offset"}; + + return formatv(ShortMsgTemplates[Kind], RegName); +} + +static Messages getPrecedesMsgs(const SubRegion *Region, NonLoc Offset) { + std::string RegName = getRegionName(Region); + SmallString<128> Buf; + llvm::raw_svector_ostream Out(Buf); + Out << "Access of " << RegName << " at negative byte offset"; + if (auto ConcreteIdx = Offset.getAs<nonloc::ConcreteInt>()) + Out << ' ' << ConcreteIdx->getValue(); + return {getShortMsg(OOB_Precedes, RegName), std::string(Buf)}; +} + +static Messages getExceedsMsgs(ASTContext &ACtx, const SubRegion *Region, + NonLoc Offset, NonLoc Extent, SVal Location) { + std::string RegName = getRegionName(Region); + const auto *EReg = Location.getAsRegion()->getAs<ElementRegion>(); + assert(EReg && "this checker only handles element access"); + QualType ElemType = EReg->getElementType(); + + std::optional<int64_t> OffsetN = getConcreteValue(Offset); + std::optional<int64_t> ExtentN = getConcreteValue(Extent); + + bool UseByteOffsets = true; + if (int64_t ElemSize = ACtx.getTypeSizeInChars(ElemType).getQuantity()) { + const bool OffsetHasRemainder = OffsetN && *OffsetN % ElemSize; + const bool ExtentHasRemainder = ExtentN && *ExtentN % ElemSize; + if (!OffsetHasRemainder && !ExtentHasRemainder) { + UseByteOffsets = false; + if (OffsetN) + *OffsetN /= ElemSize; + if (ExtentN) + *ExtentN /= ElemSize; + } + } + SmallString<256> Buf; + llvm::raw_svector_ostream Out(Buf); + Out << "Access of "; + if (!ExtentN && !UseByteOffsets) + Out << "'" << ElemType.getAsString() << "' element in "; + Out << RegName << " at "; + if (OffsetN) { + Out << (UseByteOffsets ? "byte offset " : "index ") << *OffsetN; + } else { + Out << "an overflowing " << (UseByteOffsets ? "byte offset" : "index"); + } + if (ExtentN) { + Out << ", while it holds only "; + if (*ExtentN != 1) + Out << *ExtentN; + else + Out << "a single"; + if (UseByteOffsets) + Out << " byte"; + else + Out << " '" << ElemType.getAsString() << "' element"; + + if (*ExtentN > 1) + Out << "s"; + } + + return {getShortMsg(OOB_Exceeds, RegName), std::string(Buf)}; +} + +static Messages getTaintMsgs(const SubRegion *Region, const char *OffsetName) { + std::string RegName = getRegionName(Region); + return {formatv("Potential out of bound access to {0} with tainted {1}", + RegName, OffsetName), + formatv("Access of {0} with a tainted {1} that may be too large", + RegName, OffsetName)}; +} + +void ArrayBoundCheckerV2::performCheck(const Expr *E, CheckerContext &C) const { // NOTE: Instead of using ProgramState::assumeInBound(), we are prototyping // some new logic here that reasons directly about memory region extents. // Once that logic is more mature, we can bring it back to assumeInBound() @@ -126,230 +328,152 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, // memory access is within the extent of the base region. Since we // have some flexibility in defining the base region, we can achieve // various levels of conservatism in our buffer overflow checking. - ProgramStateRef state = checkerContext.getState(); - SValBuilder &svalBuilder = checkerContext.getSValBuilder(); - const RegionRawOffsetV2 &rawOffset = - RegionRawOffsetV2::computeOffset(state, svalBuilder, location); + const SVal Location = C.getSVal(E); - if (!rawOffset.getRegion()) + // The header ctype.h (from e.g. glibc) implements the isXXXXX() macros as + // #define isXXXXX(arg) (LOOKUP_TABLE[arg] & BITMASK_FOR_XXXXX) + // and incomplete analysis of these leads to false positives. As even + // accurate reports would be confusing for the users, just disable reports + // from these macros: + if (isFromCtypeMacro(E, C.getASTContext())) return; - NonLoc rawOffsetVal = rawOffset.getByteOffset(); - - // CHECK LOWER BOUND: Is byteOffset < extent begin? - // If so, we are doing a load/store - // before the first valid offset in the memory region. - - SVal extentBegin = computeExtentBegin(svalBuilder, rawOffset.getRegion()); + ProgramStateRef State = C.getState(); + SValBuilder &SVB = C.getSValBuilder(); - if (Optional<NonLoc> NV = extentBegin.getAs<NonLoc>()) { - if (NV->getAs<nonloc::ConcreteInt>()) { - std::pair<NonLoc, nonloc::ConcreteInt> simplifiedOffsets = - getSimplifiedOffsets(rawOffset.getByteOffset(), - NV->castAs<nonloc::ConcreteInt>(), - svalBuilder); - rawOffsetVal = simplifiedOffsets.first; - *NV = simplifiedOffsets.second; - } - - SVal lowerBound = svalBuilder.evalBinOpNN(state, BO_LT, rawOffsetVal, *NV, - svalBuilder.getConditionType()); + const std::optional<std::pair<const SubRegion *, NonLoc>> &RawOffset = + computeOffset(State, SVB, Location); - Optional<NonLoc> lowerBoundToCheck = lowerBound.getAs<NonLoc>(); - if (!lowerBoundToCheck) - return; - - ProgramStateRef state_precedesLowerBound, state_withinLowerBound; - std::tie(state_precedesLowerBound, state_withinLowerBound) = - state->assume(*lowerBoundToCheck); + if (!RawOffset) + return; - // Are we constrained enough to definitely precede the lower bound? - if (state_precedesLowerBound && !state_withinLowerBound) { - reportOOB(checkerContext, state_precedesLowerBound, OOB_Precedes); + auto [Reg, ByteOffset] = *RawOffset; + + // CHECK LOWER BOUND + const MemSpaceRegion *Space = Reg->getMemorySpace(); + if (!(isa<SymbolicRegion>(Reg) && isa<UnknownSpaceRegion>(Space))) { + // A symbolic region in unknown space represents an unknown pointer that + // may point into the middle of an array, so we don't look for underflows. + // Both conditions are significant because we want to check underflows in + // symbolic regions on the heap (which may be introduced by checkers like + // MallocChecker that call SValBuilder::getConjuredHeapSymbolVal()) and + // non-symbolic regions (e.g. a field subregion of a symbolic region) in + // unknown space. + auto [PrecedesLowerBound, WithinLowerBound] = compareValueToThreshold( + State, ByteOffset, SVB.makeZeroArrayIndex(), SVB); + + if (PrecedesLowerBound && !WithinLowerBound) { + // We know that the index definitely precedes the lower bound. + Messages Msgs = getPrecedesMsgs(Reg, ByteOffset); + reportOOB(C, PrecedesLowerBound, OOB_Precedes, ByteOffset, Msgs); return; } - // Otherwise, assume the constraint of the lower bound. - assert(state_withinLowerBound); - state = state_withinLowerBound; + if (WithinLowerBound) + State = WithinLowerBound; } - do { - // CHECK UPPER BOUND: Is byteOffset >= size(baseRegion)? If so, - // we are doing a load/store after the last valid offset. - const MemRegion *MR = rawOffset.getRegion(); - DefinedOrUnknownSVal Size = getDynamicExtent(state, MR, svalBuilder); - if (!Size.getAs<NonLoc>()) - break; - - if (Size.getAs<nonloc::ConcreteInt>()) { - std::pair<NonLoc, nonloc::ConcreteInt> simplifiedOffsets = - getSimplifiedOffsets(rawOffset.getByteOffset(), - Size.castAs<nonloc::ConcreteInt>(), svalBuilder); - rawOffsetVal = simplifiedOffsets.first; - Size = simplifiedOffsets.second; - } - - SVal upperbound = svalBuilder.evalBinOpNN(state, BO_GE, rawOffsetVal, - Size.castAs<NonLoc>(), - svalBuilder.getConditionType()); - - Optional<NonLoc> upperboundToCheck = upperbound.getAs<NonLoc>(); - if (!upperboundToCheck) - break; - - ProgramStateRef state_exceedsUpperBound, state_withinUpperBound; - std::tie(state_exceedsUpperBound, state_withinUpperBound) = - state->assume(*upperboundToCheck); - - // If we are under constrained and the index variables are tainted, report. - if (state_exceedsUpperBound && state_withinUpperBound) { - SVal ByteOffset = rawOffset.getByteOffset(); - if (isTainted(state, ByteOffset)) { - reportOOB(checkerContext, state_exceedsUpperBound, OOB_Tainted, - std::make_unique<TaintBugVisitor>(ByteOffset)); + // CHECK UPPER BOUND + DefinedOrUnknownSVal Size = getDynamicExtent(State, Reg, SVB); + if (auto KnownSize = Size.getAs<NonLoc>()) { + auto [WithinUpperBound, ExceedsUpperBound] = + compareValueToThreshold(State, ByteOffset, *KnownSize, SVB); + + if (ExceedsUpperBound) { + if (!WithinUpperBound) { + // We know that the index definitely exceeds the upper bound. + if (isa<ArraySubscriptExpr>(E) && isInAddressOf(E, C.getASTContext())) { + // ...but this is within an addressof expression, so we need to check + // for the exceptional case that `&array[size]` is valid. + auto [EqualsToThreshold, NotEqualToThreshold] = + compareValueToThreshold(ExceedsUpperBound, ByteOffset, *KnownSize, + SVB, /*CheckEquality=*/true); + if (EqualsToThreshold && !NotEqualToThreshold) { + // We are definitely in the exceptional case, so return early + // instead of reporting a bug. + C.addTransition(EqualsToThreshold); + return; + } + } + Messages Msgs = getExceedsMsgs(C.getASTContext(), Reg, ByteOffset, + *KnownSize, Location); + reportOOB(C, ExceedsUpperBound, OOB_Exceeds, ByteOffset, Msgs); + return; + } + if (isTainted(State, ByteOffset)) { + // Both cases are possible, but the offset is tainted, so report. + std::string RegName = getRegionName(Reg); + + // Diagnostic detail: "tainted offset" is always correct, but the + // common case is that 'idx' is tainted in 'arr[idx]' and then it's + // nicer to say "tainted index". + const char *OffsetName = "offset"; + if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E)) + if (isTainted(State, ASE->getIdx(), C.getLocationContext())) + OffsetName = "index"; + + Messages Msgs = getTaintMsgs(Reg, OffsetName); + reportOOB(C, ExceedsUpperBound, OOB_Taint, ByteOffset, Msgs); return; } - } else if (state_exceedsUpperBound) { - // If we are constrained enough to definitely exceed the upper bound, - // report. - assert(!state_withinUpperBound); - reportOOB(checkerContext, state_exceedsUpperBound, OOB_Excedes); - return; } - assert(state_withinUpperBound); - state = state_withinUpperBound; + if (WithinUpperBound) + State = WithinUpperBound; } - while (false); - checkerContext.addTransition(state); + C.addTransition(State); } -void ArrayBoundCheckerV2::reportOOB( - CheckerContext &checkerContext, ProgramStateRef errorState, OOB_Kind kind, - std::unique_ptr<BugReporterVisitor> Visitor) const { +void ArrayBoundCheckerV2::reportOOB(CheckerContext &C, + ProgramStateRef ErrorState, OOB_Kind Kind, + NonLoc Offset, Messages Msgs) const { - ExplodedNode *errorNode = checkerContext.generateErrorNode(errorState); - if (!errorNode) + ExplodedNode *ErrorNode = C.generateErrorNode(ErrorState); + if (!ErrorNode) return; - if (!BT) - BT.reset(new BuiltinBug(this, "Out-of-bound access")); - - // FIXME: This diagnostics are preliminary. We should get far better - // diagnostics for explaining buffer overruns. - - SmallString<256> buf; - llvm::raw_svector_ostream os(buf); - os << "Out of bound memory access "; - switch (kind) { - case OOB_Precedes: - os << "(accessed memory precedes memory block)"; - break; - case OOB_Excedes: - os << "(access exceeds upper limit of memory block)"; - break; - case OOB_Tainted: - os << "(index is tainted)"; - break; - } + auto BR = std::make_unique<PathSensitiveBugReport>( + Kind == OOB_Taint ? TaintBT : BT, Msgs.Short, Msgs.Full, ErrorNode); - auto BR = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), errorNode); - BR->addVisitor(std::move(Visitor)); - checkerContext.emitReport(std::move(BR)); -} + // Track back the propagation of taintedness. + if (Kind == OOB_Taint) + for (SymbolRef Sym : getTaintedSymbols(ErrorState, Offset)) + BR->markInteresting(Sym); -#ifndef NDEBUG -LLVM_DUMP_METHOD void RegionRawOffsetV2::dump() const { - dumpToStream(llvm::errs()); + C.emitReport(std::move(BR)); } -void RegionRawOffsetV2::dumpToStream(raw_ostream &os) const { - os << "raw_offset_v2{" << getRegion() << ',' << getByteOffset() << '}'; -} -#endif +bool ArrayBoundCheckerV2::isFromCtypeMacro(const Stmt *S, ASTContext &ACtx) { + SourceLocation Loc = S->getBeginLoc(); + if (!Loc.isMacroID()) + return false; -// Lazily computes a value to be used by 'computeOffset'. If 'val' -// is unknown or undefined, we lazily substitute '0'. Otherwise, -// return 'val'. -static inline SVal getValue(SVal val, SValBuilder &svalBuilder) { - return val.getAs<UndefinedVal>() ? svalBuilder.makeArrayIndex(0) : val; -} + StringRef MacroName = Lexer::getImmediateMacroName( + Loc, ACtx.getSourceManager(), ACtx.getLangOpts()); -// Scale a base value by a scaling factor, and return the scaled -// value as an SVal. Used by 'computeOffset'. -static inline SVal scaleValue(ProgramStateRef state, - NonLoc baseVal, CharUnits scaling, - SValBuilder &sb) { - return sb.evalBinOpNN(state, BO_Mul, baseVal, - sb.makeArrayIndex(scaling.getQuantity()), - sb.getArrayIndexType()); -} + if (MacroName.size() < 7 || MacroName[0] != 'i' || MacroName[1] != 's') + return false; -// Add an SVal to another, treating unknown and undefined values as -// summing to UnknownVal. Used by 'computeOffset'. -static SVal addValue(ProgramStateRef state, SVal x, SVal y, - SValBuilder &svalBuilder) { - // We treat UnknownVals and UndefinedVals the same here because we - // only care about computing offsets. - if (x.isUnknownOrUndef() || y.isUnknownOrUndef()) - return UnknownVal(); - - return svalBuilder.evalBinOpNN(state, BO_Add, x.castAs<NonLoc>(), - y.castAs<NonLoc>(), - svalBuilder.getArrayIndexType()); + return ((MacroName == "isalnum") || (MacroName == "isalpha") || + (MacroName == "isblank") || (MacroName == "isdigit") || + (MacroName == "isgraph") || (MacroName == "islower") || + (MacroName == "isnctrl") || (MacroName == "isprint") || + (MacroName == "ispunct") || (MacroName == "isspace") || + (MacroName == "isupper") || (MacroName == "isxdigit")); } -/// Compute a raw byte offset from a base region. Used for array bounds -/// checking. -RegionRawOffsetV2 RegionRawOffsetV2::computeOffset(ProgramStateRef state, - SValBuilder &svalBuilder, - SVal location) -{ - const MemRegion *region = location.getAsRegion(); - SVal offset = UndefinedVal(); - - while (region) { - switch (region->getKind()) { - default: { - if (const SubRegion *subReg = dyn_cast<SubRegion>(region)) { - offset = getValue(offset, svalBuilder); - if (!offset.isUnknownOrUndef()) - return RegionRawOffsetV2(subReg, offset); - } - return RegionRawOffsetV2(); - } - case MemRegion::ElementRegionKind: { - const ElementRegion *elemReg = cast<ElementRegion>(region); - SVal index = elemReg->getIndex(); - if (!index.getAs<NonLoc>()) - return RegionRawOffsetV2(); - QualType elemType = elemReg->getElementType(); - // If the element is an incomplete type, go no further. - ASTContext &astContext = svalBuilder.getContext(); - if (elemType->isIncompleteType()) - return RegionRawOffsetV2(); - - // Update the offset. - offset = addValue(state, - getValue(offset, svalBuilder), - scaleValue(state, - index.castAs<NonLoc>(), - astContext.getTypeSizeInChars(elemType), - svalBuilder), - svalBuilder); - - if (offset.isUnknownOrUndef()) - return RegionRawOffsetV2(); - - region = elemReg->getSuperRegion(); - continue; - } - } - } - return RegionRawOffsetV2(); +bool ArrayBoundCheckerV2::isInAddressOf(const Stmt *S, ASTContext &ACtx) { + ParentMapContext &ParentCtx = ACtx.getParentMapContext(); + do { + const DynTypedNodeList Parents = ParentCtx.getParents(*S); + if (Parents.empty()) + return false; + S = Parents[0].get<Stmt>(); + } while (isa_and_nonnull<ParenExpr, ImplicitCastExpr>(S)); + const auto *UnaryOp = dyn_cast_or_null<UnaryOperator>(S); + return UnaryOp && UnaryOp->getOpcode() == UO_AddrOf; } void ento::registerArrayBoundCheckerV2(CheckerManager &mgr) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp index a86a410ebcbc..c72a97cc01e9 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" @@ -20,18 +19,22 @@ #include "clang/AST/StmtObjC.h" #include "clang/Analysis/DomainSpecific/CocoaConventions.h" #include "clang/Analysis/SelectorExtras.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/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.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 "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringMap.h" #include "llvm/Support/raw_ostream.h" +#include <optional> using namespace clang; using namespace ento; @@ -41,7 +44,7 @@ namespace { class APIMisuse : public BugType { public: APIMisuse(const CheckerBase *checker, const char *name) - : BugType(checker, name, "API Misuse (Apple)") {} + : BugType(checker, name, categories::AppleAPIMisuse) {} }; } // end anonymous namespace @@ -93,56 +96,64 @@ static FoundationClass findKnownClass(const ObjCInterfaceDecl *ID, //===----------------------------------------------------------------------===// namespace { - class NilArgChecker : public Checker<check::PreObjCMessage, - check::PostStmt<ObjCDictionaryLiteral>, - check::PostStmt<ObjCArrayLiteral> > { - mutable std::unique_ptr<APIMisuse> BT; - - mutable llvm::SmallDenseMap<Selector, unsigned, 16> StringSelectors; - mutable Selector ArrayWithObjectSel; - mutable Selector AddObjectSel; - mutable Selector InsertObjectAtIndexSel; - mutable Selector ReplaceObjectAtIndexWithObjectSel; - mutable Selector SetObjectAtIndexedSubscriptSel; - mutable Selector ArrayByAddingObjectSel; - mutable Selector DictionaryWithObjectForKeySel; - mutable Selector SetObjectForKeySel; - mutable Selector SetObjectForKeyedSubscriptSel; - mutable Selector RemoveObjectForKeySel; - - void warnIfNilExpr(const Expr *E, - const char *Msg, - CheckerContext &C) const; - - void warnIfNilArg(CheckerContext &C, - const ObjCMethodCall &msg, unsigned Arg, - FoundationClass Class, - bool CanBeSubscript = false) const; - - void generateBugReport(ExplodedNode *N, - StringRef Msg, - SourceRange Range, - const Expr *Expr, - CheckerContext &C) const; - - public: - void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; - void checkPostStmt(const ObjCDictionaryLiteral *DL, - CheckerContext &C) const; - void checkPostStmt(const ObjCArrayLiteral *AL, - CheckerContext &C) const; - }; +class NilArgChecker : public Checker<check::PreObjCMessage, + check::PostStmt<ObjCDictionaryLiteral>, + check::PostStmt<ObjCArrayLiteral>, + EventDispatcher<ImplicitNullDerefEvent>> { + mutable std::unique_ptr<APIMisuse> BT; + + mutable llvm::SmallDenseMap<Selector, unsigned, 16> StringSelectors; + mutable Selector ArrayWithObjectSel; + mutable Selector AddObjectSel; + mutable Selector InsertObjectAtIndexSel; + mutable Selector ReplaceObjectAtIndexWithObjectSel; + mutable Selector SetObjectAtIndexedSubscriptSel; + mutable Selector ArrayByAddingObjectSel; + mutable Selector DictionaryWithObjectForKeySel; + mutable Selector SetObjectForKeySel; + mutable Selector SetObjectForKeyedSubscriptSel; + mutable Selector RemoveObjectForKeySel; + + void warnIfNilExpr(const Expr *E, const char *Msg, CheckerContext &C) const; + + void warnIfNilArg(CheckerContext &C, const ObjCMethodCall &msg, unsigned Arg, + FoundationClass Class, bool CanBeSubscript = false) const; + + void generateBugReport(ExplodedNode *N, StringRef Msg, SourceRange Range, + const Expr *Expr, CheckerContext &C) const; + +public: + void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; + void checkPostStmt(const ObjCDictionaryLiteral *DL, CheckerContext &C) const; + void checkPostStmt(const ObjCArrayLiteral *AL, CheckerContext &C) const; +}; } // end anonymous namespace void NilArgChecker::warnIfNilExpr(const Expr *E, const char *Msg, CheckerContext &C) const { - ProgramStateRef State = C.getState(); - if (State->isNull(C.getSVal(E)).isConstrainedTrue()) { + auto Location = C.getSVal(E).getAs<Loc>(); + if (!Location) + return; + + auto [NonNull, Null] = C.getState()->assume(*Location); + // If it's known to be null. + if (!NonNull && Null) { if (ExplodedNode *N = C.generateErrorNode()) { generateBugReport(N, Msg, E->getSourceRange(), E, C); + return; + } + } + + // If it might be null, assume that it cannot after this operation. + if (Null) { + // One needs to make sure the pointer is non-null to be used here. + if (ExplodedNode *N = C.generateSink(Null, C.getPredecessor())) { + dispatchEvent({*Location, /*IsLoad=*/false, N, &C.getBugReporter(), + /*IsDirectDereference=*/false}); } + C.addTransition(NonNull); } } @@ -341,15 +352,11 @@ void NilArgChecker::checkPostStmt(const ObjCDictionaryLiteral *DL, namespace { class CFNumberChecker : public Checker< check::PreStmt<CallExpr> > { mutable std::unique_ptr<APIMisuse> BT; - mutable IdentifierInfo *ICreate, *IGetValue; + mutable IdentifierInfo *ICreate = nullptr, *IGetValue = nullptr; public: - CFNumberChecker() : ICreate(nullptr), IGetValue(nullptr) {} + CFNumberChecker() = default; void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; - -private: - void EmitError(const TypedRegion* R, const Expr *Ex, - uint64_t SourceSize, uint64_t TargetSize, uint64_t NumberKind); }; } // end anonymous namespace @@ -372,7 +379,7 @@ enum CFNumberType { kCFNumberCGFloatType = 16 }; -static Optional<uint64_t> GetCFNumberSize(ASTContext &Ctx, uint64_t i) { +static std::optional<uint64_t> GetCFNumberSize(ASTContext &Ctx, uint64_t i) { static const unsigned char FixedSize[] = { 8, 16, 32, 64, 32, 64 }; if (i < kCFNumberCharType) @@ -393,7 +400,7 @@ static Optional<uint64_t> GetCFNumberSize(ASTContext &Ctx, uint64_t i) { case kCFNumberCGFloatType: // FIXME: We need a way to map from names to Type*. default: - return None; + return std::nullopt; } return Ctx.getTypeSize(T); @@ -445,12 +452,13 @@ void CFNumberChecker::checkPreStmt(const CallExpr *CE, // FIXME: We really should allow ranges of valid theType values, and // bifurcate the state appropriately. - Optional<nonloc::ConcreteInt> V = TheTypeVal.getAs<nonloc::ConcreteInt>(); + std::optional<nonloc::ConcreteInt> V = + dyn_cast<nonloc::ConcreteInt>(TheTypeVal); if (!V) return; uint64_t NumberKind = V->getValue().getLimitedValue(); - Optional<uint64_t> OptCFNumberSize = GetCFNumberSize(Ctx, NumberKind); + std::optional<uint64_t> OptCFNumberSize = GetCFNumberSize(Ctx, NumberKind); // FIXME: In some cases we can emit an error. if (!OptCFNumberSize) @@ -465,7 +473,7 @@ void CFNumberChecker::checkPreStmt(const CallExpr *CE, // FIXME: Eventually we should handle arbitrary locations. We can do this // by having an enhanced memory model that does low-level typing. - Optional<loc::MemRegionVal> LV = TheValueExpr.getAs<loc::MemRegionVal>(); + std::optional<loc::MemRegionVal> LV = TheValueExpr.getAs<loc::MemRegionVal>(); if (!LV) return; @@ -533,10 +541,12 @@ void CFNumberChecker::checkPreStmt(const CallExpr *CE, namespace { class CFRetainReleaseChecker : public Checker<check::PreCall> { mutable APIMisuse BT{this, "null passed to CF memory management function"}; - CallDescription CFRetain{"CFRetain", 1}, - CFRelease{"CFRelease", 1}, - CFMakeCollectable{"CFMakeCollectable", 1}, - CFAutorelease{"CFAutorelease", 1}; + const CallDescriptionSet ModelledCalls = { + {{"CFRetain"}, 1}, + {{"CFRelease"}, 1}, + {{"CFMakeCollectable"}, 1}, + {{"CFAutorelease"}, 1}, + }; public: void checkPreCall(const CallEvent &Call, CheckerContext &C) const; @@ -550,13 +560,12 @@ void CFRetainReleaseChecker::checkPreCall(const CallEvent &Call, return; // Check if we called CFRetain/CFRelease/CFMakeCollectable/CFAutorelease. - if (!(Call.isCalled(CFRetain) || Call.isCalled(CFRelease) || - Call.isCalled(CFMakeCollectable) || Call.isCalled(CFAutorelease))) + if (!ModelledCalls.contains(Call)) return; // Get the argument's value. SVal ArgVal = Call.getArgSVal(0); - Optional<DefinedSVal> DefArgVal = ArgVal.getAs<DefinedSVal>(); + std::optional<DefinedSVal> DefArgVal = ArgVal.getAs<DefinedSVal>(); if (!DefArgVal) return; @@ -744,7 +753,7 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg, return; // Verify that all arguments have Objective-C types. - Optional<ExplodedNode*> errorNode; + std::optional<ExplodedNode *> errorNode; for (unsigned I = variadicArgsBegin; I != variadicArgsEnd; ++I) { QualType ArgTy = msg.getArgExpr(I)->getType(); @@ -756,7 +765,7 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg, continue; // Ignore pointer constants. - if (msg.getArgSVal(I).getAs<loc::ConcreteInt>()) + if (isa<loc::ConcreteInt>(msg.getArgSVal(I))) continue; // Ignore pointer types annotated with 'NSObject' attribute. @@ -768,10 +777,10 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg, continue; // Generate only one error node to use for all bug reports. - if (!errorNode.hasValue()) + if (!errorNode) errorNode = C.generateNonFatalErrorNode(); - if (!errorNode.getValue()) + if (!*errorNode) continue; SmallString<128> sbuf; @@ -788,8 +797,8 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg, ArgTy.print(os, C.getLangOpts()); os << "'"; - auto R = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), - errorNode.getValue()); + auto R = + std::make_unique<PathSensitiveBugReport>(*BT, os.str(), *errorNode); R->addRange(msg.getArgSourceRange(I)); C.emitReport(std::move(R)); } @@ -810,13 +819,13 @@ class ObjCLoopChecker check::PostObjCMessage, check::DeadSymbols, check::PointerEscape > { - mutable IdentifierInfo *CountSelectorII; + mutable IdentifierInfo *CountSelectorII = nullptr; bool isCollectionCountMethod(const ObjCMethodCall &M, CheckerContext &C) const; public: - ObjCLoopChecker() : CountSelectorII(nullptr) {} + ObjCLoopChecker() = default; void checkPostStmt(const ObjCForCollectionStmt *FCS, CheckerContext &C) const; void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; @@ -859,7 +868,8 @@ static ProgramStateRef checkCollectionNonNil(CheckerContext &C, return nullptr; SVal CollectionVal = C.getSVal(FCS->getCollection()); - Optional<DefinedSVal> KnownCollection = CollectionVal.getAs<DefinedSVal>(); + std::optional<DefinedSVal> KnownCollection = + CollectionVal.getAs<DefinedSVal>(); if (!KnownCollection) return State; @@ -891,7 +901,7 @@ static ProgramStateRef checkElementNonNil(CheckerContext &C, const Stmt *Element = FCS->getElement(); // FIXME: Copied from ExprEngineObjC. - Optional<Loc> ElementLoc; + std::optional<Loc> ElementLoc; if (const DeclStmt *DS = dyn_cast<DeclStmt>(Element)) { const VarDecl *ElemDecl = cast<VarDecl>(DS->getSingleDecl()); assert(ElemDecl->getInit() == nullptr); @@ -905,7 +915,7 @@ static ProgramStateRef checkElementNonNil(CheckerContext &C, // Go ahead and assume the value is non-nil. SVal Val = State->getSVal(*ElementLoc); - return State->assume(Val.castAs<DefinedOrUnknownSVal>(), true); + return State->assume(cast<DefinedOrUnknownSVal>(Val), true); } /// Returns NULL state if the collection is known to contain elements @@ -930,8 +940,8 @@ assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State, nonloc::SymbolVal(*CountS), SvalBuilder.makeIntVal(0, (*CountS)->getType()), SvalBuilder.getConditionType()); - Optional<DefinedSVal> CountGreaterThanZero = - CountGreaterThanZeroVal.getAs<DefinedSVal>(); + std::optional<DefinedSVal> CountGreaterThanZero = + CountGreaterThanZeroVal.getAs<DefinedSVal>(); if (!CountGreaterThanZero) { // The SValBuilder cannot construct a valid SVal for this condition. // This means we cannot properly reason about it. @@ -959,14 +969,13 @@ static bool alreadyExecutedAtLeastOneLoopIteration(const ExplodedNode *N, return false; ProgramPoint P = N->getLocation(); - if (Optional<BlockEdge> BE = P.getAs<BlockEdge>()) { + if (std::optional<BlockEdge> BE = P.getAs<BlockEdge>()) { return BE->getSrc()->getLoopTarget() == FCS; } // Keep looking for a block edge. - for (ExplodedNode::const_pred_iterator I = N->pred_begin(), - E = N->pred_end(); I != E; ++I) { - if (alreadyExecutedAtLeastOneLoopIteration(*I, FCS)) + for (const ExplodedNode *N : N->preds()) { + if (alreadyExecutedAtLeastOneLoopIteration(N, FCS)) return true; } @@ -1094,12 +1103,8 @@ ObjCLoopChecker::checkPointerEscape(ProgramStateRef State, PointerEscapeKind Kind) const { SymbolRef ImmutableReceiver = getMethodReceiverIfKnownImmutable(Call); - // Remove the invalidated symbols form the collection count map. - for (InvalidatedSymbols::const_iterator I = Escaped.begin(), - E = Escaped.end(); - I != E; ++I) { - SymbolRef Sym = *I; - + // Remove the invalidated symbols from the collection count map. + for (SymbolRef Sym : Escaped) { // Don't invalidate this symbol's count if we know the method being called // is declared on an immutable class. This isn't completely correct if the // receiver is also passed as an argument, but in most uses of NSArray, @@ -1121,9 +1126,7 @@ void ObjCLoopChecker::checkDeadSymbols(SymbolReaper &SymReaper, // Remove the dead symbols from the collection count map. ContainerCountMapTy Tracked = State->get<ContainerCountMap>(); - for (ContainerCountMapTy::iterator I = Tracked.begin(), - E = Tracked.end(); I != E; ++I) { - SymbolRef Sym = I->first; + for (SymbolRef Sym : llvm::make_first_range(Tracked)) { if (SymReaper.isDead(Sym)) { State = State->remove<ContainerCountMap>(Sym); State = State->remove<ContainerNonEmptyMap>(Sym); @@ -1142,13 +1145,13 @@ class ObjCNonNilReturnValueChecker check::PostStmt<ObjCArrayLiteral>, check::PostStmt<ObjCDictionaryLiteral>, check::PostStmt<ObjCBoxedExpr> > { - mutable bool Initialized; + mutable bool Initialized = false; mutable Selector ObjectAtIndex; mutable Selector ObjectAtIndexedSubscript; mutable Selector NullSelector; public: - ObjCNonNilReturnValueChecker() : Initialized(false) {} + ObjCNonNilReturnValueChecker() = default; ProgramStateRef assumeExprIsNonNull(const Expr *NonNullExpr, ProgramStateRef State, @@ -1176,7 +1179,8 @@ ObjCNonNilReturnValueChecker::assumeExprIsNonNull(const Expr *NonNullExpr, ProgramStateRef State, CheckerContext &C) const { SVal Val = C.getSVal(NonNullExpr); - if (Optional<DefinedOrUnknownSVal> DV = Val.getAs<DefinedOrUnknownSVal>()) + if (std::optional<DefinedOrUnknownSVal> DV = + Val.getAs<DefinedOrUnknownSVal>()) return State->assume(*DV, true); return State; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BitwiseShiftChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BitwiseShiftChecker.cpp new file mode 100644 index 000000000000..339927c165fe --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BitwiseShiftChecker.cpp @@ -0,0 +1,370 @@ +//== BitwiseShiftChecker.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 BitwiseShiftChecker, which is a path-sensitive checker +// that looks for undefined behavior when the operands of the bitwise shift +// operators '<<' and '>>' are invalid (negative or too large). +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ASTContext.h" +#include "clang/AST/CharUnits.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 "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "llvm/Support/FormatVariadic.h" +#include <memory> + +using namespace clang; +using namespace ento; +using llvm::formatv; + +namespace { +enum class OperandSide { Left, Right }; + +using BugReportPtr = std::unique_ptr<PathSensitiveBugReport>; + +struct NoteTagTemplate { + llvm::StringLiteral SignInfo; + llvm::StringLiteral UpperBoundIntro; +}; + +constexpr NoteTagTemplate NoteTagTemplates[] = { + {"", "right operand of bit shift is less than "}, + {"left operand of bit shift is non-negative", " and right operand is less than "}, + {"right operand of bit shift is non-negative", " but less than "}, + {"both operands of bit shift are non-negative", " and right operand is less than "} +}; + +/// An implementation detail class which is introduced to split the checker +/// logic into several methods while maintaining a consistently updated state +/// and access to other contextual data. +class BitwiseShiftValidator { + CheckerContext &Ctx; + ProgramStateRef FoldedState; + const BinaryOperator *const Op; + const BugType &BT; + const bool PedanticFlag; + + // The following data members are only used for note tag creation: + enum { NonNegLeft = 1, NonNegRight = 2 }; + unsigned NonNegOperands = 0; + + std::optional<unsigned> UpperBoundBitCount = std::nullopt; + +public: + BitwiseShiftValidator(const BinaryOperator *O, CheckerContext &C, + const BugType &B, bool P) + : Ctx(C), FoldedState(C.getState()), Op(O), BT(B), PedanticFlag(P) {} + void run(); + +private: + const Expr *operandExpr(OperandSide Side) const { + return Side == OperandSide::Left ? Op->getLHS() : Op->getRHS(); + } + + bool shouldPerformPedanticChecks() const { + // The pedantic flag has no effect under C++20 because the affected issues + // are no longer undefined under that version of the standard. + return PedanticFlag && !Ctx.getASTContext().getLangOpts().CPlusPlus20; + } + + bool assumeRequirement(OperandSide Side, BinaryOperator::Opcode Cmp, unsigned Limit); + + void recordAssumption(OperandSide Side, BinaryOperator::Opcode Cmp, unsigned Limit); + const NoteTag *createNoteTag() const; + + BugReportPtr createBugReport(StringRef ShortMsg, StringRef Msg) const; + + BugReportPtr checkOvershift(); + BugReportPtr checkOperandNegative(OperandSide Side); + BugReportPtr checkLeftShiftOverflow(); + + bool isLeftShift() const { return Op->getOpcode() == BO_Shl; } + StringRef shiftDir() const { return isLeftShift() ? "left" : "right"; } + static StringRef pluralSuffix(unsigned n) { return n <= 1 ? "" : "s"; } + static StringRef verbSuffix(unsigned n) { return n <= 1 ? "s" : ""; } +}; + +void BitwiseShiftValidator::run() { + // Report a bug if the right operand is >= the bit width of the type of the + // left operand: + if (BugReportPtr BR = checkOvershift()) { + Ctx.emitReport(std::move(BR)); + return; + } + + // Report a bug if the right operand is negative: + if (BugReportPtr BR = checkOperandNegative(OperandSide::Right)) { + Ctx.emitReport(std::move(BR)); + return; + } + + if (shouldPerformPedanticChecks()) { + // Report a bug if the left operand is negative: + if (BugReportPtr BR = checkOperandNegative(OperandSide::Left)) { + Ctx.emitReport(std::move(BR)); + return; + } + + // Report a bug when left shift of a concrete signed value overflows: + if (BugReportPtr BR = checkLeftShiftOverflow()) { + Ctx.emitReport(std::move(BR)); + return; + } + } + + // No bugs detected, update the state and add a single note tag which + // summarizes the new assumptions. + Ctx.addTransition(FoldedState, createNoteTag()); +} + +/// This method checks a requirement that must be satisfied by the value on the +/// given Side of a bitwise shift operator in well-defined code. If the +/// requirement is incompatible with prior knowledge, this method reports +/// failure by returning false. +bool BitwiseShiftValidator::assumeRequirement(OperandSide Side, + BinaryOperator::Opcode Comparison, + unsigned Limit) { + SValBuilder &SVB = Ctx.getSValBuilder(); + + const SVal OperandVal = Ctx.getSVal(operandExpr(Side)); + const auto LimitVal = SVB.makeIntVal(Limit, Ctx.getASTContext().IntTy); + // Note that the type of `LimitVal` must be a signed, because otherwise a + // negative `Val` could be converted to a large positive value. + + auto ResultVal = SVB.evalBinOp(FoldedState, Comparison, OperandVal, LimitVal, + SVB.getConditionType()); + if (auto DURes = ResultVal.getAs<DefinedOrUnknownSVal>()) { + auto [StTrue, StFalse] = FoldedState->assume(DURes.value()); + if (!StTrue) { + // We detected undefined behavior (the caller will report it). + FoldedState = StFalse; + return false; + } + // The code may be valid, so let's assume that it's valid: + FoldedState = StTrue; + if (StFalse) { + // Record note tag data for the assumption that we made + recordAssumption(Side, Comparison, Limit); + } + } + return true; +} + +BugReportPtr BitwiseShiftValidator::checkOvershift() { + const QualType LHSTy = Op->getLHS()->getType(); + const unsigned LHSBitWidth = Ctx.getASTContext().getIntWidth(LHSTy); + + if (assumeRequirement(OperandSide::Right, BO_LT, LHSBitWidth)) + return nullptr; + + const SVal Right = Ctx.getSVal(operandExpr(OperandSide::Right)); + + std::string RightOpStr = "", LowerBoundStr = ""; + if (auto ConcreteRight = Right.getAs<nonloc::ConcreteInt>()) + RightOpStr = formatv(" '{0}'", ConcreteRight->getValue()); + else { + SValBuilder &SVB = Ctx.getSValBuilder(); + if (const llvm::APSInt *MinRight = SVB.getMinValue(FoldedState, Right)) { + LowerBoundStr = formatv(" >= {0},", MinRight->getExtValue()); + } + } + + std::string ShortMsg = formatv( + "{0} shift{1}{2} overflows the capacity of '{3}'", + isLeftShift() ? "Left" : "Right", RightOpStr.empty() ? "" : " by", + RightOpStr, LHSTy.getAsString()); + std::string Msg = formatv( + "The result of {0} shift is undefined because the right " + "operand{1} is{2} not smaller than {3}, the capacity of '{4}'", + shiftDir(), RightOpStr, LowerBoundStr, LHSBitWidth, LHSTy.getAsString()); + return createBugReport(ShortMsg, Msg); +} + +// Before C++20, at 5.8 [expr.shift] (N4296, 2014-11-19) the standard says +// 1. "... The behaviour is undefined if the right operand is negative..." +// 2. "The value of E1 << E2 ... +// if E1 has a signed type and non-negative value ... +// otherwise, the behavior is undefined." +// 3. "The value of E1 >> E2 ... +// If E1 has a signed type and a negative value, +// the resulting value is implementation-defined." +// However, negative left arguments work in practice and the C++20 standard +// eliminates conditions 2 and 3. +BugReportPtr BitwiseShiftValidator::checkOperandNegative(OperandSide Side) { + // If the type is unsigned, it cannot be negative + if (!operandExpr(Side)->getType()->isSignedIntegerType()) + return nullptr; + + // Main check: determine whether the operand is constrained to be negative + if (assumeRequirement(Side, BO_GE, 0)) + return nullptr; + + std::string ShortMsg = formatv("{0} operand is negative in {1} shift", + Side == OperandSide::Left ? "Left" : "Right", + shiftDir()) + .str(); + std::string Msg = formatv("The result of {0} shift is undefined " + "because the {1} operand is negative", + shiftDir(), + Side == OperandSide::Left ? "left" : "right") + .str(); + + return createBugReport(ShortMsg, Msg); +} + +BugReportPtr BitwiseShiftValidator::checkLeftShiftOverflow() { + // A right shift cannot be an overflowing left shift... + if (!isLeftShift()) + return nullptr; + + // In C++ it's well-defined to shift to the sign bit. In C however, it's UB. + // 5.8.2 [expr.shift] (N4296, 2014-11-19) + const bool ShouldPreserveSignBit = !Ctx.getLangOpts().CPlusPlus; + + const Expr *LHS = operandExpr(OperandSide::Left); + const QualType LHSTy = LHS->getType(); + const unsigned LeftBitWidth = Ctx.getASTContext().getIntWidth(LHSTy); + assert(LeftBitWidth > 0); + + // Quote "For unsigned lhs, the value of LHS << RHS is the value of LHS * + // 2^RHS, reduced modulo maximum value of the return type plus 1." + if (LHSTy->isUnsignedIntegerType()) + return nullptr; + + // We only support concrete integers as left operand. + const auto Left = Ctx.getSVal(LHS).getAs<nonloc::ConcreteInt>(); + if (!Left.has_value()) + return nullptr; + + // We should have already reported a bug if the left operand of the shift was + // negative, so it cannot be negative here. + assert(Left->getValue().isNonNegative()); + + const unsigned LeftAvailableBitWidth = + LeftBitWidth - static_cast<unsigned>(ShouldPreserveSignBit); + const unsigned UsedBitsInLeftOperand = Left->getValue().getActiveBits(); + assert(LeftBitWidth >= UsedBitsInLeftOperand); + const unsigned MaximalAllowedShift = + LeftAvailableBitWidth - UsedBitsInLeftOperand; + + if (assumeRequirement(OperandSide::Right, BO_LT, MaximalAllowedShift + 1)) + return nullptr; + + const std::string CapacityMsg = + formatv("because '{0}' can hold only {1} bits ({2} the sign bit)", + LHSTy.getAsString(), LeftAvailableBitWidth, + ShouldPreserveSignBit ? "excluding" : "including"); + + const SVal Right = Ctx.getSVal(Op->getRHS()); + + std::string ShortMsg, Msg; + if (const auto ConcreteRight = Right.getAs<nonloc::ConcreteInt>()) { + // Here ConcreteRight must contain a small non-negative integer, because + // otherwise one of the earlier checks should've reported a bug. + const unsigned RHS = ConcreteRight->getValue().getExtValue(); + assert(RHS > MaximalAllowedShift); + const unsigned OverflownBits = RHS - MaximalAllowedShift; + ShortMsg = formatv( + "The shift '{0} << {1}' overflows the capacity of '{2}'", + Left->getValue(), ConcreteRight->getValue(), LHSTy.getAsString()); + Msg = formatv( + "The shift '{0} << {1}' is undefined {2}, so {3} bit{4} overflow{5}", + Left->getValue(), ConcreteRight->getValue(), CapacityMsg, OverflownBits, + pluralSuffix(OverflownBits), verbSuffix(OverflownBits)); + } else { + ShortMsg = formatv("Left shift of '{0}' overflows the capacity of '{1}'", + Left->getValue(), LHSTy.getAsString()); + Msg = formatv( + "Left shift of '{0}' is undefined {1}, so some bits overflow", + Left->getValue(), CapacityMsg); + } + + return createBugReport(ShortMsg, Msg); +} + +void BitwiseShiftValidator::recordAssumption(OperandSide Side, + BinaryOperator::Opcode Comparison, + unsigned Limit) { + switch (Comparison) { + case BO_GE: + assert(Limit == 0); + NonNegOperands |= (Side == OperandSide::Left ? NonNegLeft : NonNegRight); + break; + case BO_LT: + assert(Side == OperandSide::Right); + if (!UpperBoundBitCount || Limit < UpperBoundBitCount.value()) + UpperBoundBitCount = Limit; + break; + default: + llvm_unreachable("this checker does not use other comparison operators"); + } +} + +const NoteTag *BitwiseShiftValidator::createNoteTag() const { + if (!NonNegOperands && !UpperBoundBitCount) + return nullptr; + + SmallString<128> Buf; + llvm::raw_svector_ostream Out(Buf); + Out << "Assuming "; + NoteTagTemplate Templ = NoteTagTemplates[NonNegOperands]; + Out << Templ.SignInfo; + if (UpperBoundBitCount) + Out << Templ.UpperBoundIntro << UpperBoundBitCount.value(); + const std::string Msg(Out.str()); + + return Ctx.getNoteTag(Msg, /*isPrunable=*/true); +} + +std::unique_ptr<PathSensitiveBugReport> +BitwiseShiftValidator::createBugReport(StringRef ShortMsg, StringRef Msg) const { + ProgramStateRef State = Ctx.getState(); + if (ExplodedNode *ErrNode = Ctx.generateErrorNode(State)) { + auto BR = + std::make_unique<PathSensitiveBugReport>(BT, ShortMsg, Msg, ErrNode); + bugreporter::trackExpressionValue(ErrNode, Op->getLHS(), *BR); + bugreporter::trackExpressionValue(ErrNode, Op->getRHS(), *BR); + return BR; + } + return nullptr; +} +} // anonymous namespace + +class BitwiseShiftChecker : public Checker<check::PreStmt<BinaryOperator>> { + BugType BT{this, "Bitwise shift", "Suspicious operation"}; + +public: + void checkPreStmt(const BinaryOperator *B, CheckerContext &Ctx) const { + BinaryOperator::Opcode Op = B->getOpcode(); + + if (Op != BO_Shl && Op != BO_Shr) + return; + + BitwiseShiftValidator(B, Ctx, BT, Pedantic).run(); + } + + bool Pedantic = false; +}; + +void ento::registerBitwiseShiftChecker(CheckerManager &Mgr) { + auto *Chk = Mgr.registerChecker<BitwiseShiftChecker>(); + const AnalyzerOptions &Opts = Mgr.getAnalyzerOptions(); + Chk->Pedantic = Opts.getCheckerBooleanOption(Chk, "Pedantic"); +} + +bool ento::shouldRegisterBitwiseShiftChecker(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 2752b37f9b3f..66e080adb138 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp @@ -17,6 +17,7 @@ #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" @@ -24,20 +25,31 @@ using namespace clang; using namespace ento; namespace { - class BlockInCriticalSectionChecker : public Checker<check::PostCall> { - - mutable IdentifierInfo *IILockGuard, *IIUniqueLock; - - CallDescription LockFn, UnlockFn, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn, - PthreadLockFn, PthreadTryLockFn, PthreadUnlockFn, - MtxLock, MtxTimedLock, MtxTryLock, MtxUnlock; - - StringRef ClassLockGuard, ClassUniqueLock; - - mutable bool IdentifierInfoInitialized; - - std::unique_ptr<BugType> BlockInCritSectionBugType; + mutable IdentifierInfo *IILockGuard = nullptr; + mutable IdentifierInfo *IIUniqueLock = nullptr; + mutable bool IdentifierInfoInitialized = false; + + const CallDescription LockFn{{"lock"}}; + const CallDescription UnlockFn{{"unlock"}}; + const CallDescription SleepFn{{"sleep"}}; + const CallDescription GetcFn{{"getc"}}; + const CallDescription FgetsFn{{"fgets"}}; + const CallDescription ReadFn{{"read"}}; + const CallDescription RecvFn{{"recv"}}; + const CallDescription PthreadLockFn{{"pthread_mutex_lock"}}; + const CallDescription PthreadTryLockFn{{"pthread_mutex_trylock"}}; + const CallDescription PthreadUnlockFn{{"pthread_mutex_unlock"}}; + const CallDescription MtxLock{{"mtx_lock"}}; + const CallDescription MtxTimedLock{{"mtx_timedlock"}}; + const CallDescription MtxTryLock{{"mtx_trylock"}}; + const CallDescription MtxUnlock{{"mtx_unlock"}}; + + const llvm::StringLiteral ClassLockGuard{"lock_guard"}; + const llvm::StringLiteral ClassUniqueLock{"unique_lock"}; + + const BugType BlockInCritSectionBugType{ + this, "Call to blocking function in critical section", "Blocking Error"}; void initIdentifierInfo(ASTContext &Ctx) const; @@ -46,8 +58,6 @@ class BlockInCriticalSectionChecker : public Checker<check::PostCall> { CheckerContext &C) const; public: - BlockInCriticalSectionChecker(); - bool isBlockingFunction(const CallEvent &Call) const; bool isLockFunction(const CallEvent &Call) const; bool isUnlockFunction(const CallEvent &Call) const; @@ -62,26 +72,6 @@ public: REGISTER_TRAIT_WITH_PROGRAMSTATE(MutexCounter, unsigned) -BlockInCriticalSectionChecker::BlockInCriticalSectionChecker() - : IILockGuard(nullptr), IIUniqueLock(nullptr), - LockFn("lock"), UnlockFn("unlock"), SleepFn("sleep"), GetcFn("getc"), - FgetsFn("fgets"), ReadFn("read"), RecvFn("recv"), - PthreadLockFn("pthread_mutex_lock"), - PthreadTryLockFn("pthread_mutex_trylock"), - PthreadUnlockFn("pthread_mutex_unlock"), - MtxLock("mtx_lock"), - MtxTimedLock("mtx_timedlock"), - MtxTryLock("mtx_trylock"), - MtxUnlock("mtx_unlock"), - ClassLockGuard("lock_guard"), - ClassUniqueLock("unique_lock"), - IdentifierInfoInitialized(false) { - // Initialize the bug type. - BlockInCritSectionBugType.reset( - new BugType(this, "Call to blocking function in critical section", - "Blocking Error")); -} - void BlockInCriticalSectionChecker::initIdentifierInfo(ASTContext &Ctx) const { if (!IdentifierInfoInitialized) { /* In case of checking C code, or when the corresponding headers are not @@ -96,14 +86,7 @@ void BlockInCriticalSectionChecker::initIdentifierInfo(ASTContext &Ctx) const { } bool BlockInCriticalSectionChecker::isBlockingFunction(const CallEvent &Call) const { - if (Call.isCalled(SleepFn) - || Call.isCalled(GetcFn) - || Call.isCalled(FgetsFn) - || Call.isCalled(ReadFn) - || Call.isCalled(RecvFn)) { - return true; - } - return false; + return matchesAny(Call, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn); } bool BlockInCriticalSectionChecker::isLockFunction(const CallEvent &Call) const { @@ -113,15 +96,8 @@ bool BlockInCriticalSectionChecker::isLockFunction(const CallEvent &Call) const return true; } - if (Call.isCalled(LockFn) - || Call.isCalled(PthreadLockFn) - || Call.isCalled(PthreadTryLockFn) - || Call.isCalled(MtxLock) - || Call.isCalled(MtxTimedLock) - || Call.isCalled(MtxTryLock)) { - return true; - } - return false; + return matchesAny(Call, LockFn, PthreadLockFn, PthreadTryLockFn, MtxLock, + MtxTimedLock, MtxTryLock); } bool BlockInCriticalSectionChecker::isUnlockFunction(const CallEvent &Call) const { @@ -132,12 +108,7 @@ bool BlockInCriticalSectionChecker::isUnlockFunction(const CallEvent &Call) cons return true; } - if (Call.isCalled(UnlockFn) - || Call.isCalled(PthreadUnlockFn) - || Call.isCalled(MtxUnlock)) { - return true; - } - return false; + return matchesAny(Call, UnlockFn, PthreadUnlockFn, MtxUnlock); } void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call, @@ -173,7 +144,7 @@ void BlockInCriticalSectionChecker::reportBlockInCritSection( llvm::raw_string_ostream os(msg); os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName() << "' inside of critical section"; - auto R = std::make_unique<PathSensitiveBugReport>(*BlockInCritSectionBugType, + auto R = std::make_unique<PathSensitiveBugReport>(BlockInCritSectionBugType, os.str(), ErrNode); R->addRange(Call.getSourceRange()); R->markInteresting(BlockDescSym); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp index 6c0caf3c4e78..a09db6d2d0ec 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp @@ -12,31 +12,33 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Checkers/Taint.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 <optional> using namespace clang; using namespace ento; namespace { class BoolAssignmentChecker : public Checker< check::Bind > { - mutable std::unique_ptr<BuiltinBug> BT; - void emitReport(ProgramStateRef state, CheckerContext &C) const; + const BugType BT{this, "Assignment of a non-Boolean value"}; + void emitReport(ProgramStateRef state, CheckerContext &C, + bool IsTainted = false) const; + public: void checkBind(SVal loc, SVal val, const Stmt *S, CheckerContext &C) const; }; } // end anonymous namespace -void BoolAssignmentChecker::emitReport(ProgramStateRef state, - CheckerContext &C) const { +void BoolAssignmentChecker::emitReport(ProgramStateRef state, CheckerContext &C, + bool IsTainted) const { if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) { - if (!BT) - BT.reset(new BuiltinBug(this, "Assignment of a non-Boolean value")); - - C.emitReport( - std::make_unique<PathSensitiveBugReport>(*BT, BT->getDescription(), N)); + StringRef Msg = IsTainted ? "Might assign a tainted non-Boolean value" + : "Assignment of a non-Boolean value"; + C.emitReport(std::make_unique<PathSensitiveBugReport>(BT, Msg, N)); } } @@ -70,7 +72,7 @@ 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<NonLoc> NV = val.getAs<NonLoc>(); + std::optional<NonLoc> NV = val.getAs<NonLoc>(); if (!NV) return; @@ -90,6 +92,8 @@ void BoolAssignmentChecker::checkBind(SVal loc, SVal val, const Stmt *S, if (!StIn) emitReport(StOut, C); + if (StIn && StOut && taint::isTainted(state, *NV)) + emitReport(StOut, C, /*IsTainted=*/true); } void ento::registerBoolAssignmentChecker(CheckerManager &mgr) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp index 13781b336426..61521c259ca9 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp @@ -66,7 +66,8 @@ bool BuiltinFunctionChecker::evalCall(const CallEvent &Call, case Builtin::BI__builtin_expect: case Builtin::BI__builtin_expect_with_probability: case Builtin::BI__builtin_assume_aligned: - case Builtin::BI__builtin_addressof: { + case Builtin::BI__builtin_addressof: + case Builtin::BI__builtin_function_start: { // For __builtin_unpredictable, __builtin_expect, // __builtin_expect_with_probability and __builtin_assume_aligned, // just return the value of the subexpression. @@ -80,22 +81,20 @@ bool BuiltinFunctionChecker::evalCall(const CallEvent &Call, case Builtin::BI__builtin_alloca_with_align: case Builtin::BI__builtin_alloca: { - // FIXME: Refactor into StoreManager itself? - MemRegionManager& RM = C.getStoreManager().getRegionManager(); - const AllocaRegion* R = - RM.getAllocaRegion(CE, C.blockCount(), C.getLocationContext()); - - // Set the extent of the region in bytes. This enables us to use the - // SVal of the argument directly. If we save the extent in bits, we - // cannot represent values like symbol*8. - auto Size = Call.getArgSVal(0); - if (Size.isUndef()) - return true; // Return true to model purity. - - state = setDynamicExtent(state, R, Size.castAs<DefinedOrUnknownSVal>(), - C.getSValBuilder()); + SValBuilder &SVB = C.getSValBuilder(); + const loc::MemRegionVal R = + SVB.getAllocaRegionVal(CE, C.getLocationContext(), C.blockCount()); - C.addTransition(state->BindExpr(CE, LCtx, loc::MemRegionVal(R))); + // Set the extent of the region in bytes. This enables us to use the SVal + // of the argument directly. If we saved the extent in bits, it'd be more + // difficult to reason about values like symbol*8. + auto Size = Call.getArgSVal(0); + if (auto DefSize = Size.getAs<DefinedOrUnknownSVal>()) { + // This `getAs()` is mostly paranoia, because core.CallAndMessage reports + // undefined function arguments (unless it's disabled somehow). + state = setDynamicExtent(state, R.getRegion(), *DefSize, SVB); + } + C.addTransition(state->BindExpr(CE, LCtx, R)); 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 69b90be9aa7e..b7b64c3da4f6 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -12,11 +12,13 @@ //===----------------------------------------------------------------------===// #include "InterCheckerAPI.h" +#include "clang/Basic/Builtins.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/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" @@ -25,30 +27,21 @@ #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/raw_ostream.h" +#include <functional> +#include <optional> using namespace clang; using namespace ento; +using namespace std::placeholders; 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. -}; +struct SourceArgExpr : AnyArgExpr {}; +struct DestinationArgExpr : AnyArgExpr {}; +struct SizeArgExpr : AnyArgExpr {}; using ErrorMessage = SmallString<128>; enum class AccessKind { write, read }; @@ -72,6 +65,16 @@ static ErrorMessage createOutOfBoundErrorMsg(StringRef FunctionDescription, } enum class ConcatFnKind { none = 0, strcat = 1, strlcat = 2 }; + +enum class CharKind { Regular = 0, Wide }; +constexpr CharKind CK_Regular = CharKind::Regular; +constexpr CharKind CK_Wide = CharKind::Wide; + +static QualType getCharPtrType(ASTContext &Ctx, CharKind CK) { + return Ctx.getPointerType(CK == CharKind::Regular ? Ctx.CharTy + : Ctx.WideCharTy); +} + class CStringChecker : public Checker< eval::Call, check::PreStmt<DeclStmt>, check::LiveSymbols, @@ -79,23 +82,25 @@ class CStringChecker : public Checker< eval::Call, check::RegionChanges > { mutable std::unique_ptr<BugType> BT_Null, BT_Bounds, BT_Overlap, - BT_NotCString, BT_AdditionOverflow; + BT_NotCString, BT_AdditionOverflow, BT_UninitRead; - mutable const char *CurrentFunctionDescription; + mutable const char *CurrentFunctionDescription = nullptr; public: /// The filter is used to filter out the diagnostics which are not enabled by /// the user. struct CStringChecksFilter { - DefaultBool CheckCStringNullArg; - DefaultBool CheckCStringOutOfBounds; - DefaultBool CheckCStringBufferOverlap; - DefaultBool CheckCStringNotNullTerm; + bool CheckCStringNullArg = false; + bool CheckCStringOutOfBounds = false; + bool CheckCStringBufferOverlap = false; + bool CheckCStringNotNullTerm = false; + bool CheckCStringUninitializedRead = false; CheckerNameRef CheckNameCStringNullArg; CheckerNameRef CheckNameCStringOutOfBounds; CheckerNameRef CheckNameCStringBufferOverlap; CheckerNameRef CheckNameCStringNotNullTerm; + CheckerNameRef CheckNameCStringUninitializedRead; }; CStringChecksFilter Filter; @@ -115,33 +120,52 @@ public: const LocationContext *LCtx, const CallEvent *Call) const; - typedef void (CStringChecker::*FnCheck)(CheckerContext &, - const CallExpr *) const; + using FnCheck = std::function<void(const CStringChecker *, CheckerContext &, + const CallEvent &)>; + CallDescriptionMap<FnCheck> Callbacks = { - {{CDF_MaybeBuiltin, "memcpy", 3}, &CStringChecker::evalMemcpy}, - {{CDF_MaybeBuiltin, "mempcpy", 3}, &CStringChecker::evalMempcpy}, - {{CDF_MaybeBuiltin, "memcmp", 3}, &CStringChecker::evalMemcmp}, - {{CDF_MaybeBuiltin, "memmove", 3}, &CStringChecker::evalMemmove}, - {{CDF_MaybeBuiltin, "memset", 3}, &CStringChecker::evalMemset}, - {{CDF_MaybeBuiltin, "explicit_memset", 3}, &CStringChecker::evalMemset}, - {{CDF_MaybeBuiltin, "strcpy", 2}, &CStringChecker::evalStrcpy}, - {{CDF_MaybeBuiltin, "strncpy", 3}, &CStringChecker::evalStrncpy}, - {{CDF_MaybeBuiltin, "stpcpy", 2}, &CStringChecker::evalStpcpy}, - {{CDF_MaybeBuiltin, "strlcpy", 3}, &CStringChecker::evalStrlcpy}, - {{CDF_MaybeBuiltin, "strcat", 2}, &CStringChecker::evalStrcat}, - {{CDF_MaybeBuiltin, "strncat", 3}, &CStringChecker::evalStrncat}, - {{CDF_MaybeBuiltin, "strlcat", 3}, &CStringChecker::evalStrlcat}, - {{CDF_MaybeBuiltin, "strlen", 1}, &CStringChecker::evalstrLength}, - {{CDF_MaybeBuiltin, "strnlen", 2}, &CStringChecker::evalstrnLength}, - {{CDF_MaybeBuiltin, "strcmp", 2}, &CStringChecker::evalStrcmp}, - {{CDF_MaybeBuiltin, "strncmp", 3}, &CStringChecker::evalStrncmp}, - {{CDF_MaybeBuiltin, "strcasecmp", 2}, &CStringChecker::evalStrcasecmp}, - {{CDF_MaybeBuiltin, "strncasecmp", 3}, &CStringChecker::evalStrncasecmp}, - {{CDF_MaybeBuiltin, "strsep", 2}, &CStringChecker::evalStrsep}, - {{CDF_MaybeBuiltin, "bcopy", 3}, &CStringChecker::evalBcopy}, - {{CDF_MaybeBuiltin, "bcmp", 3}, &CStringChecker::evalMemcmp}, - {{CDF_MaybeBuiltin, "bzero", 2}, &CStringChecker::evalBzero}, - {{CDF_MaybeBuiltin, "explicit_bzero", 2}, &CStringChecker::evalBzero}, + {{CDF_MaybeBuiltin, {"memcpy"}, 3}, + std::bind(&CStringChecker::evalMemcpy, _1, _2, _3, CK_Regular)}, + {{CDF_MaybeBuiltin, {"wmemcpy"}, 3}, + std::bind(&CStringChecker::evalMemcpy, _1, _2, _3, CK_Wide)}, + {{CDF_MaybeBuiltin, {"mempcpy"}, 3}, + std::bind(&CStringChecker::evalMempcpy, _1, _2, _3, CK_Regular)}, + {{CDF_None, {"wmempcpy"}, 3}, + std::bind(&CStringChecker::evalMempcpy, _1, _2, _3, CK_Wide)}, + {{CDF_MaybeBuiltin, {"memcmp"}, 3}, + std::bind(&CStringChecker::evalMemcmp, _1, _2, _3, CK_Regular)}, + {{CDF_MaybeBuiltin, {"wmemcmp"}, 3}, + std::bind(&CStringChecker::evalMemcmp, _1, _2, _3, CK_Wide)}, + {{CDF_MaybeBuiltin, {"memmove"}, 3}, + std::bind(&CStringChecker::evalMemmove, _1, _2, _3, CK_Regular)}, + {{CDF_MaybeBuiltin, {"wmemmove"}, 3}, + std::bind(&CStringChecker::evalMemmove, _1, _2, _3, CK_Wide)}, + {{CDF_MaybeBuiltin, {"memset"}, 3}, &CStringChecker::evalMemset}, + {{CDF_MaybeBuiltin, {"explicit_memset"}, 3}, &CStringChecker::evalMemset}, + {{CDF_MaybeBuiltin, {"strcpy"}, 2}, &CStringChecker::evalStrcpy}, + {{CDF_MaybeBuiltin, {"strncpy"}, 3}, &CStringChecker::evalStrncpy}, + {{CDF_MaybeBuiltin, {"stpcpy"}, 2}, &CStringChecker::evalStpcpy}, + {{CDF_MaybeBuiltin, {"strlcpy"}, 3}, &CStringChecker::evalStrlcpy}, + {{CDF_MaybeBuiltin, {"strcat"}, 2}, &CStringChecker::evalStrcat}, + {{CDF_MaybeBuiltin, {"strncat"}, 3}, &CStringChecker::evalStrncat}, + {{CDF_MaybeBuiltin, {"strlcat"}, 3}, &CStringChecker::evalStrlcat}, + {{CDF_MaybeBuiltin, {"strlen"}, 1}, &CStringChecker::evalstrLength}, + {{CDF_MaybeBuiltin, {"wcslen"}, 1}, &CStringChecker::evalstrLength}, + {{CDF_MaybeBuiltin, {"strnlen"}, 2}, &CStringChecker::evalstrnLength}, + {{CDF_MaybeBuiltin, {"wcsnlen"}, 2}, &CStringChecker::evalstrnLength}, + {{CDF_MaybeBuiltin, {"strcmp"}, 2}, &CStringChecker::evalStrcmp}, + {{CDF_MaybeBuiltin, {"strncmp"}, 3}, &CStringChecker::evalStrncmp}, + {{CDF_MaybeBuiltin, {"strcasecmp"}, 2}, &CStringChecker::evalStrcasecmp}, + {{CDF_MaybeBuiltin, {"strncasecmp"}, 3}, + &CStringChecker::evalStrncasecmp}, + {{CDF_MaybeBuiltin, {"strsep"}, 2}, &CStringChecker::evalStrsep}, + {{CDF_MaybeBuiltin, {"bcopy"}, 3}, &CStringChecker::evalBcopy}, + {{CDF_MaybeBuiltin, {"bcmp"}, 3}, + std::bind(&CStringChecker::evalMemcmp, _1, _2, _3, CK_Regular)}, + {{CDF_MaybeBuiltin, {"bzero"}, 2}, &CStringChecker::evalBzero}, + {{CDF_MaybeBuiltin, {"explicit_bzero"}, 2}, &CStringChecker::evalBzero}, + {{CDF_MaybeBuiltin, {"sprintf"}, 2}, &CStringChecker::evalSprintf}, + {{CDF_MaybeBuiltin, {"snprintf"}, 2}, &CStringChecker::evalSnprintf}, }; // These require a bit of special handling. @@ -149,51 +173,53 @@ public: StdCopyBackward{{"std", "copy_backward"}, 3}; FnCheck identifyCall(const CallEvent &Call, CheckerContext &C) const; - void evalMemcpy(CheckerContext &C, const CallExpr *CE) const; - void evalMempcpy(CheckerContext &C, const CallExpr *CE) const; - void evalMemmove(CheckerContext &C, const CallExpr *CE) const; - void evalBcopy(CheckerContext &C, const CallExpr *CE) const; - void evalCopyCommon(CheckerContext &C, const CallExpr *CE, + void evalMemcpy(CheckerContext &C, const CallEvent &Call, CharKind CK) const; + void evalMempcpy(CheckerContext &C, const CallEvent &Call, CharKind CK) const; + void evalMemmove(CheckerContext &C, const CallEvent &Call, CharKind CK) const; + void evalBcopy(CheckerContext &C, const CallEvent &Call) const; + void evalCopyCommon(CheckerContext &C, const CallEvent &Call, ProgramStateRef state, SizeArgExpr Size, DestinationArgExpr Dest, SourceArgExpr Source, - bool Restricted, bool IsMempcpy) const; + bool Restricted, bool IsMempcpy, CharKind CK) const; - void evalMemcmp(CheckerContext &C, const CallExpr *CE) const; + void evalMemcmp(CheckerContext &C, const CallEvent &Call, CharKind CK) const; - void evalstrLength(CheckerContext &C, const CallExpr *CE) const; - void evalstrnLength(CheckerContext &C, const CallExpr *CE) const; - void evalstrLengthCommon(CheckerContext &C, - const CallExpr *CE, + void evalstrLength(CheckerContext &C, const CallEvent &Call) const; + void evalstrnLength(CheckerContext &C, const CallEvent &Call) const; + void evalstrLengthCommon(CheckerContext &C, const CallEvent &Call, bool IsStrnlen = false) const; - void evalStrcpy(CheckerContext &C, const CallExpr *CE) const; - void evalStrncpy(CheckerContext &C, const CallExpr *CE) const; - void evalStpcpy(CheckerContext &C, const CallExpr *CE) const; - void evalStrlcpy(CheckerContext &C, const CallExpr *CE) const; - void evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, bool ReturnEnd, - bool IsBounded, ConcatFnKind appendK, + void evalStrcpy(CheckerContext &C, const CallEvent &Call) const; + void evalStrncpy(CheckerContext &C, const CallEvent &Call) const; + void evalStpcpy(CheckerContext &C, const CallEvent &Call) const; + void evalStrlcpy(CheckerContext &C, const CallEvent &Call) const; + void evalStrcpyCommon(CheckerContext &C, const CallEvent &Call, + bool ReturnEnd, bool IsBounded, ConcatFnKind appendK, bool returnPtr = true) const; - void evalStrcat(CheckerContext &C, const CallExpr *CE) const; - void evalStrncat(CheckerContext &C, const CallExpr *CE) const; - void evalStrlcat(CheckerContext &C, const CallExpr *CE) const; + void evalStrcat(CheckerContext &C, const CallEvent &Call) const; + void evalStrncat(CheckerContext &C, const CallEvent &Call) const; + void evalStrlcat(CheckerContext &C, const CallEvent &Call) const; - void evalStrcmp(CheckerContext &C, const CallExpr *CE) const; - void evalStrncmp(CheckerContext &C, const CallExpr *CE) const; - void evalStrcasecmp(CheckerContext &C, const CallExpr *CE) const; - void evalStrncasecmp(CheckerContext &C, const CallExpr *CE) const; - void evalStrcmpCommon(CheckerContext &C, - const CallExpr *CE, - bool IsBounded = false, - bool IgnoreCase = false) const; + void evalStrcmp(CheckerContext &C, const CallEvent &Call) const; + void evalStrncmp(CheckerContext &C, const CallEvent &Call) const; + void evalStrcasecmp(CheckerContext &C, const CallEvent &Call) const; + void evalStrncasecmp(CheckerContext &C, const CallEvent &Call) const; + void evalStrcmpCommon(CheckerContext &C, const CallEvent &Call, + bool IsBounded = false, bool IgnoreCase = false) const; - void evalStrsep(CheckerContext &C, const CallExpr *CE) const; + void evalStrsep(CheckerContext &C, const CallEvent &Call) const; - void evalStdCopy(CheckerContext &C, const CallExpr *CE) const; - void evalStdCopyBackward(CheckerContext &C, const CallExpr *CE) const; - void evalStdCopyCommon(CheckerContext &C, const CallExpr *CE) const; - void evalMemset(CheckerContext &C, const CallExpr *CE) const; - void evalBzero(CheckerContext &C, const CallExpr *CE) const; + void evalStdCopy(CheckerContext &C, const CallEvent &Call) const; + void evalStdCopyBackward(CheckerContext &C, const CallEvent &Call) const; + void evalStdCopyCommon(CheckerContext &C, const CallEvent &Call) const; + void evalMemset(CheckerContext &C, const CallEvent &Call) const; + void evalBzero(CheckerContext &C, const CallEvent &Call) const; + + void evalSprintf(CheckerContext &C, const CallEvent &Call) const; + void evalSnprintf(CheckerContext &C, const CallEvent &Call) const; + void evalSprintfCommon(CheckerContext &C, const CallEvent &Call, + bool IsBounded, bool IsBuiltin) const; // Utility methods std::pair<ProgramStateRef , ProgramStateRef > @@ -219,11 +245,34 @@ public: const Expr *expr, SVal val) const; - static ProgramStateRef InvalidateBuffer(CheckerContext &C, - ProgramStateRef state, - const Expr *Ex, SVal V, - bool IsSourceBuffer, - const Expr *Size); + /// Invalidate the destination buffer determined by characters copied. + static ProgramStateRef + invalidateDestinationBufferBySize(CheckerContext &C, ProgramStateRef S, + const Expr *BufE, SVal BufV, SVal SizeV, + QualType SizeTy); + + /// Operation never overflows, do not invalidate the super region. + static ProgramStateRef invalidateDestinationBufferNeverOverflows( + CheckerContext &C, ProgramStateRef S, const Expr *BufE, SVal BufV); + + /// We do not know whether the operation can overflow (e.g. size is unknown), + /// invalidate the super region and escape related pointers. + static ProgramStateRef invalidateDestinationBufferAlwaysEscapeSuperRegion( + CheckerContext &C, ProgramStateRef S, const Expr *BufE, SVal BufV); + + /// Invalidate the source buffer for escaping pointers. + static ProgramStateRef invalidateSourceBuffer(CheckerContext &C, + ProgramStateRef S, + const Expr *BufE, SVal BufV); + + /// @param InvalidationTraitOperations Determine how to invlidate the + /// MemRegion by setting the invalidation traits. Return true to cause pointer + /// escape, or false otherwise. + static ProgramStateRef invalidateBufferAux( + CheckerContext &C, ProgramStateRef State, const Expr *Ex, SVal V, + llvm::function_ref<bool(RegionAndSymbolInvalidationTraits &, + const MemRegion *)> + InvalidationTraitOperations); static bool SummarizeRegion(raw_ostream &os, ASTContext &Ctx, const MemRegion *MR); @@ -237,13 +286,16 @@ public: AnyArgExpr Arg, SVal l) const; ProgramStateRef CheckLocation(CheckerContext &C, ProgramStateRef state, AnyArgExpr Buffer, SVal Element, - AccessKind Access) const; + AccessKind Access, + CharKind CK = CharKind::Regular) const; ProgramStateRef CheckBufferAccess(CheckerContext &C, ProgramStateRef State, AnyArgExpr Buffer, SizeArgExpr Size, - AccessKind Access) const; + AccessKind Access, + CharKind CK = CharKind::Regular) const; ProgramStateRef CheckOverlap(CheckerContext &C, ProgramStateRef state, SizeArgExpr Size, AnyArgExpr First, - AnyArgExpr Second) const; + AnyArgExpr Second, + CharKind CK = CharKind::Regular) const; void emitOverlapBug(CheckerContext &C, ProgramStateRef state, const Stmt *First, @@ -256,7 +308,8 @@ public: void emitNotCStringBug(CheckerContext &C, ProgramStateRef State, const Stmt *S, StringRef WarningMsg) const; void emitAdditionOverflowBug(CheckerContext &C, ProgramStateRef State) const; - + void emitUninitializedReadBug(CheckerContext &C, ProgramStateRef State, + const Expr *E) const; ProgramStateRef checkAdditionOverflow(CheckerContext &C, ProgramStateRef state, NonLoc left, @@ -265,10 +318,9 @@ public: // Return true if the destination buffer of the copy function may be in bound. // Expects SVal of Size to be positive and unsigned. // Expects SVal of FirstBuf to be a FieldRegion. - static bool IsFirstBufInBound(CheckerContext &C, - ProgramStateRef state, - const Expr *FirstBuf, - const Expr *Size); + static bool isFirstBufInBound(CheckerContext &C, ProgramStateRef State, + SVal BufVal, QualType BufTy, SVal LengthVal, + QualType LengthTy); }; } //end anonymous namespace @@ -282,7 +334,7 @@ REGISTER_MAP_WITH_PROGRAMSTATE(CStringLength, const MemRegion *, SVal) std::pair<ProgramStateRef , ProgramStateRef > CStringChecker::assumeZero(CheckerContext &C, ProgramStateRef state, SVal V, QualType Ty) { - Optional<DefinedSVal> val = V.getAs<DefinedSVal>(); + std::optional<DefinedSVal> val = V.getAs<DefinedSVal>(); if (!val) return std::pair<ProgramStateRef , ProgramStateRef >(state, state); @@ -325,7 +377,8 @@ ProgramStateRef CStringChecker::checkNonNull(CheckerContext &C, ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C, ProgramStateRef state, AnyArgExpr Buffer, SVal Element, - AccessKind Access) const { + AccessKind Access, + CharKind CK) const { // If a previous check has failed, propagate the failure. if (!state) @@ -340,19 +393,38 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C, if (!ER) return state; - if (ER->getValueType() != C.getASTContext().CharTy) - return state; + SValBuilder &svalBuilder = C.getSValBuilder(); + ASTContext &Ctx = svalBuilder.getContext(); + + // Get the index of the accessed element. + NonLoc Idx = ER->getIndex(); + + if (CK == CharKind::Regular) { + if (ER->getValueType() != Ctx.CharTy) + return state; + } else { + if (ER->getValueType() != Ctx.WideCharTy) + return state; + + QualType SizeTy = Ctx.getSizeType(); + NonLoc WideSize = + svalBuilder + .makeIntVal(Ctx.getTypeSizeInChars(Ctx.WideCharTy).getQuantity(), + SizeTy) + .castAs<NonLoc>(); + SVal Offset = svalBuilder.evalBinOpNN(state, BO_Mul, Idx, WideSize, SizeTy); + if (Offset.isUnknown()) + return state; + Idx = Offset.castAs<NonLoc>(); + } // Get the size of the array. const auto *superReg = cast<SubRegion>(ER->getSuperRegion()); DefinedOrUnknownSVal Size = getDynamicExtent(state, superReg, C.getSValBuilder()); - // Get the index of the accessed element. - DefinedOrUnknownSVal Idx = ER->getIndex().castAs<DefinedOrUnknownSVal>(); - - ProgramStateRef StInBound = state->assumeInBound(Idx, Size, true); - ProgramStateRef StOutBound = state->assumeInBound(Idx, Size, false); + ProgramStateRef StInBound, StOutBound; + std::tie(StInBound, StOutBound) = state->assumeInBoundDual(Idx, Size); if (StOutBound && !StInBound) { // These checks are either enabled by the CString out-of-bounds checker // explicitly or implicitly by the Malloc checker. @@ -367,16 +439,24 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C, return nullptr; } + // Ensure that we wouldn't read uninitialized value. + if (Access == AccessKind::read) { + if (Filter.CheckCStringUninitializedRead && + StInBound->getSVal(ER).isUndef()) { + emitUninitializedReadBug(C, StInBound, Buffer.Expression); + return nullptr; + } + } + // Array bound check succeeded. From this point forward the array bound // should always succeed. return StInBound; } -ProgramStateRef CStringChecker::CheckBufferAccess(CheckerContext &C, - ProgramStateRef State, - AnyArgExpr Buffer, - SizeArgExpr Size, - AccessKind Access) const { +ProgramStateRef +CStringChecker::CheckBufferAccess(CheckerContext &C, ProgramStateRef State, + AnyArgExpr Buffer, SizeArgExpr Size, + AccessKind Access, CharKind CK) const { // If a previous check has failed, propagate the failure. if (!State) return nullptr; @@ -385,7 +465,7 @@ ProgramStateRef CStringChecker::CheckBufferAccess(CheckerContext &C, ASTContext &Ctx = svalBuilder.getContext(); QualType SizeTy = Size.Expression->getType(); - QualType PtrTy = Ctx.getPointerType(Ctx.CharTy); + QualType PtrTy = getCharPtrType(Ctx, CK); // Check that the first buffer is non-null. SVal BufVal = C.getSVal(Buffer.Expression); @@ -397,11 +477,19 @@ ProgramStateRef CStringChecker::CheckBufferAccess(CheckerContext &C, if (!Filter.CheckCStringOutOfBounds) return State; + SVal BufStart = + svalBuilder.evalCast(BufVal, PtrTy, Buffer.Expression->getType()); + + // Check if the first byte of the buffer is accessible. + State = CheckLocation(C, State, Buffer, BufStart, Access, CK); + if (!State) + return nullptr; + // 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.Expression); - Optional<NonLoc> Length = LengthVal.getAs<NonLoc>(); + std::optional<NonLoc> Length = LengthVal.getAs<NonLoc>(); if (!Length) return State; @@ -413,14 +501,11 @@ ProgramStateRef CStringChecker::CheckBufferAccess(CheckerContext &C, NonLoc LastOffset = Offset.castAs<NonLoc>(); // Check that the first buffer is sufficiently long. - SVal BufStart = - svalBuilder.evalCast(BufVal, PtrTy, Buffer.Expression->getType()); - if (Optional<Loc> BufLoc = BufStart.getAs<Loc>()) { + if (std::optional<Loc> BufLoc = BufStart.getAs<Loc>()) { SVal BufEnd = svalBuilder.evalBinOpLN(State, BO_Add, *BufLoc, LastOffset, PtrTy); - - State = CheckLocation(C, State, Buffer, BufEnd, Access); + State = CheckLocation(C, State, Buffer, BufEnd, Access, CK); // If the buffer isn't large enough, abort. if (!State) @@ -434,7 +519,8 @@ ProgramStateRef CStringChecker::CheckBufferAccess(CheckerContext &C, ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, ProgramStateRef state, SizeArgExpr Size, AnyArgExpr First, - AnyArgExpr Second) const { + AnyArgExpr Second, + CharKind CK) const { if (!Filter.CheckCStringBufferOverlap) return state; @@ -448,16 +534,21 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, ProgramStateRef stateTrue, stateFalse; + // Assume different address spaces cannot overlap. + if (First.Expression->getType()->getPointeeType().getAddressSpace() != + Second.Expression->getType()->getPointeeType().getAddressSpace()) + return state; + // Get the buffer values and make sure they're known locations. const LocationContext *LCtx = C.getLocationContext(); SVal firstVal = state->getSVal(First.Expression, LCtx); SVal secondVal = state->getSVal(Second.Expression, LCtx); - Optional<Loc> firstLoc = firstVal.getAs<Loc>(); + std::optional<Loc> firstLoc = firstVal.getAs<Loc>(); if (!firstLoc) return state; - Optional<Loc> secondLoc = secondVal.getAs<Loc>(); + std::optional<Loc> secondLoc = secondVal.getAs<Loc>(); if (!secondLoc) return state; @@ -480,7 +571,7 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, QualType cmpTy = svalBuilder.getConditionType(); SVal reverse = svalBuilder.evalBinOpLL(state, BO_GT, *firstLoc, *secondLoc, cmpTy); - Optional<DefinedOrUnknownSVal> reverseTest = + std::optional<DefinedOrUnknownSVal> reverseTest = reverse.getAs<DefinedOrUnknownSVal>(); if (!reverseTest) return state; @@ -501,31 +592,31 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, // Get the length, and make sure it too is known. SVal LengthVal = state->getSVal(Size.Expression, LCtx); - Optional<NonLoc> Length = LengthVal.getAs<NonLoc>(); + std::optional<NonLoc> Length = LengthVal.getAs<NonLoc>(); if (!Length) return state; // Convert the first buffer's start address to char*. // Bail out if the cast fails. ASTContext &Ctx = svalBuilder.getContext(); - QualType CharPtrTy = Ctx.getPointerType(Ctx.CharTy); + QualType CharPtrTy = getCharPtrType(Ctx, CK); SVal FirstStart = svalBuilder.evalCast(*firstLoc, CharPtrTy, First.Expression->getType()); - Optional<Loc> FirstStartLoc = FirstStart.getAs<Loc>(); + std::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); - Optional<Loc> FirstEndLoc = FirstEnd.getAs<Loc>(); + std::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); - Optional<DefinedOrUnknownSVal> OverlapTest = + std::optional<DefinedOrUnknownSVal> OverlapTest = Overlap.getAs<DefinedOrUnknownSVal>(); if (!OverlapTest) return state; @@ -565,13 +656,15 @@ void CStringChecker::emitOverlapBug(CheckerContext &C, ProgramStateRef state, void CStringChecker::emitNullArgBug(CheckerContext &C, ProgramStateRef State, const Stmt *S, StringRef WarningMsg) const { if (ExplodedNode *N = C.generateErrorNode(State)) { - if (!BT_Null) - BT_Null.reset(new BuiltinBug( - Filter.CheckNameCStringNullArg, categories::UnixAPI, - "Null pointer argument in call to byte string function")); + if (!BT_Null) { + // FIXME: This call uses the string constant 'categories::UnixAPI' as the + // description of the bug; it should be replaced by a real description. + BT_Null.reset( + new BugType(Filter.CheckNameCStringNullArg, categories::UnixAPI)); + } - BuiltinBug *BT = static_cast<BuiltinBug *>(BT_Null.get()); - auto Report = std::make_unique<PathSensitiveBugReport>(*BT, WarningMsg, N); + auto Report = + std::make_unique<PathSensitiveBugReport>(*BT_Null, WarningMsg, N); Report->addRange(S->getSourceRange()); if (const auto *Ex = dyn_cast<Expr>(S)) bugreporter::trackExpressionValue(N, Ex, *Report); @@ -579,23 +672,39 @@ void CStringChecker::emitNullArgBug(CheckerContext &C, ProgramStateRef State, } } +void CStringChecker::emitUninitializedReadBug(CheckerContext &C, + ProgramStateRef State, + const Expr *E) const { + if (ExplodedNode *N = C.generateErrorNode(State)) { + const char *Msg = + "Bytes string function accesses uninitialized/garbage values"; + if (!BT_UninitRead) + BT_UninitRead.reset(new BugType(Filter.CheckNameCStringUninitializedRead, + "Accessing unitialized/garbage values")); + + auto Report = + std::make_unique<PathSensitiveBugReport>(*BT_UninitRead, Msg, N); + Report->addRange(E->getSourceRange()); + bugreporter::trackExpressionValue(N, E, *Report); + C.emitReport(std::move(Report)); + } +} + void CStringChecker::emitOutOfBoundsBug(CheckerContext &C, ProgramStateRef State, const Stmt *S, StringRef WarningMsg) const { if (ExplodedNode *N = C.generateErrorNode(State)) { if (!BT_Bounds) - BT_Bounds.reset(new BuiltinBug( - Filter.CheckCStringOutOfBounds ? Filter.CheckNameCStringOutOfBounds - : Filter.CheckNameCStringNullArg, - "Out-of-bound array access", - "Byte string function accesses out-of-bound array element")); - - BuiltinBug *BT = static_cast<BuiltinBug *>(BT_Bounds.get()); + BT_Bounds.reset(new BugType(Filter.CheckCStringOutOfBounds + ? Filter.CheckNameCStringOutOfBounds + : Filter.CheckNameCStringNullArg, + "Out-of-bound array access")); // FIXME: It would be nice to eventually make this diagnostic more clear, // e.g., by referencing the original declaration or by saying *why* this // reference is outside the range. - auto Report = std::make_unique<PathSensitiveBugReport>(*BT, WarningMsg, N); + auto Report = + std::make_unique<PathSensitiveBugReport>(*BT_Bounds, WarningMsg, N); Report->addRange(S->getSourceRange()); C.emitReport(std::move(Report)); } @@ -605,10 +714,12 @@ void CStringChecker::emitNotCStringBug(CheckerContext &C, ProgramStateRef State, const Stmt *S, StringRef WarningMsg) const { if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { - if (!BT_NotCString) - BT_NotCString.reset(new BuiltinBug( - Filter.CheckNameCStringNotNullTerm, categories::UnixAPI, - "Argument is not a null-terminated string.")); + if (!BT_NotCString) { + // FIXME: This call uses the string constant 'categories::UnixAPI' as the + // description of the bug; it should be replaced by a real description. + BT_NotCString.reset( + new BugType(Filter.CheckNameCStringNotNullTerm, categories::UnixAPI)); + } auto Report = std::make_unique<PathSensitiveBugReport>(*BT_NotCString, WarningMsg, N); @@ -621,10 +732,13 @@ void CStringChecker::emitNotCStringBug(CheckerContext &C, ProgramStateRef State, void CStringChecker::emitAdditionOverflowBug(CheckerContext &C, ProgramStateRef State) const { if (ExplodedNode *N = C.generateErrorNode(State)) { - if (!BT_NotCString) - BT_NotCString.reset( - new BuiltinBug(Filter.CheckNameCStringOutOfBounds, "API", - "Sum of expressions causes overflow.")); + if (!BT_AdditionOverflow) { + // FIXME: This call uses the word "API" as the description of the bug; + // it should be replaced by a better error message (if this unlikely + // situation continues to exist as a separate bug type). + BT_AdditionOverflow.reset( + new BugType(Filter.CheckNameCStringOutOfBounds, "API")); + } // This isn't a great error message, but this should never occur in real // code anyway -- you'd have to create a buffer longer than a size_t can @@ -633,8 +747,8 @@ void CStringChecker::emitAdditionOverflowBug(CheckerContext &C, "This expression will create a string whose length is too big to " "be represented as a size_t"; - auto Report = - std::make_unique<PathSensitiveBugReport>(*BT_NotCString, WarningMsg, N); + auto Report = std::make_unique<PathSensitiveBugReport>(*BT_AdditionOverflow, + WarningMsg, N); C.emitReport(std::move(Report)); } } @@ -659,7 +773,7 @@ ProgramStateRef CStringChecker::checkAdditionOverflow(CheckerContext &C, NonLoc maxVal = svalBuilder.makeIntVal(maxValInt); SVal maxMinusRight; - if (right.getAs<nonloc::ConcreteInt>()) { + if (isa<nonloc::ConcreteInt>(right)) { maxMinusRight = svalBuilder.evalBinOpNN(state, BO_Sub, maxVal, right, sizeTy); } else { @@ -670,7 +784,7 @@ ProgramStateRef CStringChecker::checkAdditionOverflow(CheckerContext &C, left = right; } - if (Optional<NonLoc> maxMinusRightNL = maxMinusRight.getAs<NonLoc>()) { + if (std::optional<NonLoc> maxMinusRightNL = maxMinusRight.getAs<NonLoc>()) { QualType cmpTy = svalBuilder.getConditionType(); // If left > max - right, we have an overflow. SVal willOverflow = svalBuilder.evalBinOpNN(state, BO_GT, left, @@ -756,7 +870,7 @@ SVal CStringChecker::getCStringLengthForRegion(CheckerContext &C, C.blockCount()); if (!hypothetical) { - if (Optional<NonLoc> strLn = strLength.getAs<NonLoc>()) { + if (std::optional<NonLoc> strLn = strLength.getAs<NonLoc>()) { // In case of unbounded calls strlen etc bound the range to SIZE_MAX/4 BasicValueFactory &BVF = svalBuilder.getBasicValueFactory(); const llvm::APSInt &maxValInt = BVF.getMaxValue(sizeTy); @@ -764,8 +878,8 @@ SVal CStringChecker::getCStringLengthForRegion(CheckerContext &C, const llvm::APSInt *maxLengthInt = BVF.evalAPSInt(BO_Div, maxValInt, fourInt); NonLoc maxLength = svalBuilder.makeIntVal(*maxLengthInt); - SVal evalLength = svalBuilder.evalBinOpNN(state, BO_LE, *strLn, - maxLength, sizeTy); + SVal evalLength = svalBuilder.evalBinOpNN(state, BO_LE, *strLn, maxLength, + svalBuilder.getConditionType()); state = state->assume(evalLength.castAs<DefinedOrUnknownSVal>(), true); } state = state->set<CStringLength>(MR, strLength); @@ -782,7 +896,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, // If we can't get a region, see if it's something we /know/ isn't a // C string. In the context of locations, the only time we can issue such // a warning is for labels. - if (Optional<loc::GotoLabel> Label = Buf.getAs<loc::GotoLabel>()) { + if (std::optional<loc::GotoLabel> Label = Buf.getAs<loc::GotoLabel>()) { if (Filter.CheckCStringNotNullTerm) { SmallString<120> buf; llvm::raw_svector_ostream os(buf); @@ -811,11 +925,25 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, SValBuilder &svalBuilder = C.getSValBuilder(); QualType sizeTy = svalBuilder.getContext().getSizeType(); const StringLiteral *strLit = cast<StringRegion>(MR)->getStringLiteral(); - return svalBuilder.makeIntVal(strLit->getByteLength(), sizeTy); + return svalBuilder.makeIntVal(strLit->getLength(), sizeTy); + } + case MemRegion::NonParamVarRegionKind: { + // If we have a global constant with a string literal initializer, + // compute the initializer's length. + const VarDecl *Decl = cast<NonParamVarRegion>(MR)->getDecl(); + if (Decl->getType().isConstQualified() && Decl->hasGlobalStorage()) { + if (const Expr *Init = Decl->getInit()) { + if (auto *StrLit = dyn_cast<StringLiteral>(Init)) { + SValBuilder &SvalBuilder = C.getSValBuilder(); + QualType SizeTy = SvalBuilder.getContext().getSizeType(); + return SvalBuilder.makeIntVal(StrLit->getLength(), SizeTy); + } + } + } + [[fallthrough]]; } case MemRegion::SymbolicRegionKind: case MemRegion::AllocaRegionKind: - case MemRegion::NonParamVarRegionKind: case MemRegion::ParamVarRegionKind: case MemRegion::FieldRegionKind: case MemRegion::ObjCIvarRegionKind: @@ -869,43 +997,40 @@ const StringLiteral *CStringChecker::getCStringLiteral(CheckerContext &C, return strRegion->getStringLiteral(); } -bool CStringChecker::IsFirstBufInBound(CheckerContext &C, - ProgramStateRef state, - const Expr *FirstBuf, - const Expr *Size) { +bool CStringChecker::isFirstBufInBound(CheckerContext &C, ProgramStateRef State, + SVal BufVal, QualType BufTy, + SVal LengthVal, QualType LengthTy) { // If we do not know that the buffer is long enough we return 'true'. // Otherwise the parent region of this field region would also get // invalidated, which would lead to warnings based on an unknown state. + if (LengthVal.isUnknown()) + return false; + // Originally copied from CheckBufferAccess and CheckLocation. - SValBuilder &svalBuilder = C.getSValBuilder(); - ASTContext &Ctx = svalBuilder.getContext(); - const LocationContext *LCtx = C.getLocationContext(); + SValBuilder &SB = C.getSValBuilder(); + ASTContext &Ctx = C.getASTContext(); - QualType sizeTy = Size->getType(); QualType PtrTy = Ctx.getPointerType(Ctx.CharTy); - SVal BufVal = state->getSVal(FirstBuf, LCtx); - SVal LengthVal = state->getSVal(Size, LCtx); - Optional<NonLoc> Length = LengthVal.getAs<NonLoc>(); + std::optional<NonLoc> Length = LengthVal.getAs<NonLoc>(); if (!Length) return true; // cf top comment. // 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 = SB.makeIntVal(1, LengthTy).castAs<NonLoc>(); + SVal Offset = SB.evalBinOpNN(State, BO_Sub, *Length, One, LengthTy); if (Offset.isUnknown()) return true; // cf top comment NonLoc LastOffset = Offset.castAs<NonLoc>(); // Check that the first buffer is sufficiently long. - SVal BufStart = svalBuilder.evalCast(BufVal, PtrTy, FirstBuf->getType()); - Optional<Loc> BufLoc = BufStart.getAs<Loc>(); + SVal BufStart = SB.evalCast(BufVal, PtrTy, BufTy); + std::optional<Loc> BufLoc = BufStart.getAs<Loc>(); if (!BufLoc) return true; // cf top comment. - SVal BufEnd = - svalBuilder.evalBinOpLN(state, BO_Add, *BufLoc, LastOffset, PtrTy); + SVal BufEnd = SB.evalBinOpLN(State, BO_Add, *BufLoc, LastOffset, PtrTy); // Check for out of bound array element access. const MemRegion *R = BufEnd.getAsRegion(); @@ -919,33 +1044,95 @@ bool CStringChecker::IsFirstBufInBound(CheckerContext &C, // FIXME: Does this crash when a non-standard definition // of a library function is encountered? assert(ER->getValueType() == C.getASTContext().CharTy && - "IsFirstBufInBound should only be called with char* ElementRegions"); + "isFirstBufInBound should only be called with char* ElementRegions"); // Get the size of the array. const SubRegion *superReg = cast<SubRegion>(ER->getSuperRegion()); - DefinedOrUnknownSVal SizeDV = getDynamicExtent(state, superReg, svalBuilder); + DefinedOrUnknownSVal SizeDV = getDynamicExtent(State, superReg, SB); // Get the index of the accessed element. DefinedOrUnknownSVal Idx = ER->getIndex().castAs<DefinedOrUnknownSVal>(); - ProgramStateRef StInBound = state->assumeInBound(Idx, SizeDV, true); + ProgramStateRef StInBound = State->assumeInBound(Idx, SizeDV, true); return static_cast<bool>(StInBound); } -ProgramStateRef CStringChecker::InvalidateBuffer(CheckerContext &C, - ProgramStateRef state, - const Expr *E, SVal V, - bool IsSourceBuffer, - const Expr *Size) { - Optional<Loc> L = V.getAs<Loc>(); +ProgramStateRef CStringChecker::invalidateDestinationBufferBySize( + CheckerContext &C, ProgramStateRef S, const Expr *BufE, SVal BufV, + SVal SizeV, QualType SizeTy) { + auto InvalidationTraitOperations = + [&C, S, BufTy = BufE->getType(), BufV, SizeV, + SizeTy](RegionAndSymbolInvalidationTraits &ITraits, const MemRegion *R) { + // If destination buffer is a field region and access is in bound, do + // not invalidate its super region. + if (MemRegion::FieldRegionKind == R->getKind() && + isFirstBufInBound(C, S, BufV, BufTy, SizeV, SizeTy)) { + ITraits.setTrait( + R, + RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion); + } + return false; + }; + + return invalidateBufferAux(C, S, BufE, BufV, InvalidationTraitOperations); +} + +ProgramStateRef +CStringChecker::invalidateDestinationBufferAlwaysEscapeSuperRegion( + CheckerContext &C, ProgramStateRef S, const Expr *BufE, SVal BufV) { + auto InvalidationTraitOperations = [](RegionAndSymbolInvalidationTraits &, + const MemRegion *R) { + return isa<FieldRegion>(R); + }; + + return invalidateBufferAux(C, S, BufE, BufV, InvalidationTraitOperations); +} + +ProgramStateRef CStringChecker::invalidateDestinationBufferNeverOverflows( + CheckerContext &C, ProgramStateRef S, const Expr *BufE, SVal BufV) { + auto InvalidationTraitOperations = + [](RegionAndSymbolInvalidationTraits &ITraits, const MemRegion *R) { + if (MemRegion::FieldRegionKind == R->getKind()) + ITraits.setTrait( + R, + RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion); + return false; + }; + + return invalidateBufferAux(C, S, BufE, BufV, InvalidationTraitOperations); +} + +ProgramStateRef CStringChecker::invalidateSourceBuffer(CheckerContext &C, + ProgramStateRef S, + const Expr *BufE, + SVal BufV) { + auto InvalidationTraitOperations = + [](RegionAndSymbolInvalidationTraits &ITraits, const MemRegion *R) { + ITraits.setTrait( + R->getBaseRegion(), + RegionAndSymbolInvalidationTraits::TK_PreserveContents); + ITraits.setTrait(R, + RegionAndSymbolInvalidationTraits::TK_SuppressEscape); + return true; + }; + + return invalidateBufferAux(C, S, BufE, BufV, InvalidationTraitOperations); +} + +ProgramStateRef CStringChecker::invalidateBufferAux( + CheckerContext &C, ProgramStateRef State, const Expr *E, SVal V, + llvm::function_ref<bool(RegionAndSymbolInvalidationTraits &, + const MemRegion *)> + InvalidationTraitOperations) { + std::optional<Loc> L = V.getAs<Loc>(); if (!L) - return state; + return State; // FIXME: This is a simplified version of what's in CFRefCount.cpp -- it makes // some assumptions about the value that CFRefCount can't. Even so, it should // probably be refactored. - if (Optional<loc::MemRegionVal> MR = L->getAs<loc::MemRegionVal>()) { + if (std::optional<loc::MemRegionVal> MR = L->getAs<loc::MemRegionVal>()) { const MemRegion *R = MR->getRegion()->StripCasts(); // Are we dealing with an ElementRegion? If so, we should be invalidating @@ -957,29 +1144,10 @@ ProgramStateRef CStringChecker::InvalidateBuffer(CheckerContext &C, // Invalidate this region. const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); - - bool CausesPointerEscape = false; RegionAndSymbolInvalidationTraits ITraits; - // Invalidate and escape only indirect regions accessible through the source - // buffer. - if (IsSourceBuffer) { - ITraits.setTrait(R->getBaseRegion(), - RegionAndSymbolInvalidationTraits::TK_PreserveContents); - ITraits.setTrait(R, RegionAndSymbolInvalidationTraits::TK_SuppressEscape); - CausesPointerEscape = true; - } else { - const MemRegion::Kind& K = R->getKind(); - if (K == MemRegion::FieldRegionKind) - if (Size && IsFirstBufInBound(C, state, E, Size)) { - // If destination buffer is a field region and access is in bound, - // do not invalidate its super region. - ITraits.setTrait( - R, - RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion); - } - } + bool CausesPointerEscape = InvalidationTraitOperations(ITraits, R); - return state->invalidateRegions(R, E, C.blockCount(), LCtx, + return State->invalidateRegions(R, E, C.blockCount(), LCtx, CausesPointerEscape, nullptr, nullptr, &ITraits); } @@ -987,7 +1155,7 @@ ProgramStateRef CStringChecker::InvalidateBuffer(CheckerContext &C, // If we have a non-region value by chance, just remove the binding. // FIXME: is this necessary or correct? This handles the non-Region // cases. Is it ever valid to store to these? - return state->killBinding(*L); + return State->killBinding(*L); } bool CStringChecker::SummarizeRegion(raw_ostream &os, ASTContext &Ctx, @@ -1009,23 +1177,20 @@ bool CStringChecker::SummarizeRegion(raw_ostream &os, ASTContext &Ctx, case MemRegion::CXXThisRegionKind: case MemRegion::CXXTempObjectRegionKind: os << "a C++ temp object of type " - << cast<TypedValueRegion>(MR)->getValueType().getAsString(); + << cast<TypedValueRegion>(MR)->getValueType(); return true; case MemRegion::NonParamVarRegionKind: - os << "a variable of type" - << cast<TypedValueRegion>(MR)->getValueType().getAsString(); + os << "a variable of type" << cast<TypedValueRegion>(MR)->getValueType(); return true; case MemRegion::ParamVarRegionKind: - os << "a parameter of type" - << cast<TypedValueRegion>(MR)->getValueType().getAsString(); + os << "a parameter of type" << cast<TypedValueRegion>(MR)->getValueType(); return true; case MemRegion::FieldRegionKind: - os << "a field of type " - << cast<TypedValueRegion>(MR)->getValueType().getAsString(); + os << "a field of type " << cast<TypedValueRegion>(MR)->getValueType(); return true; case MemRegion::ObjCIvarRegionKind: os << "an instance variable of type " - << cast<TypedValueRegion>(MR)->getValueType().getAsString(); + << cast<TypedValueRegion>(MR)->getValueType(); return true; default: return false; @@ -1048,7 +1213,7 @@ bool CStringChecker::memsetAux(const Expr *DstBuffer, SVal CharVal, RegionOffset Offset = MR->getAsOffset(); const MemRegion *BR = Offset.getRegion(); - Optional<NonLoc> SizeNL = SizeVal.getAs<NonLoc>(); + std::optional<NonLoc> SizeNL = SizeVal.getAs<NonLoc>(); if (!SizeNL) return false; @@ -1087,8 +1252,8 @@ bool CStringChecker::memsetAux(const Expr *DstBuffer, SVal CharVal, } else { // If the destination buffer's extent is not equal to the value of // third argument, just invalidate buffer. - State = InvalidateBuffer(C, State, DstBuffer, MemVal, - /*IsSourceBuffer*/ false, Size); + State = invalidateDestinationBufferBySize(C, State, DstBuffer, MemVal, + SizeVal, Size->getType()); } if (StateNullChar && !StateNonNullChar) { @@ -1113,8 +1278,8 @@ bool CStringChecker::memsetAux(const Expr *DstBuffer, SVal CharVal, } else { // If the offset is not zero and char value is not concrete, we can do // nothing but invalidate the buffer. - State = InvalidateBuffer(C, State, DstBuffer, MemVal, - /*IsSourceBuffer*/ false, Size); + State = invalidateDestinationBufferBySize(C, State, DstBuffer, MemVal, + SizeVal, Size->getType()); } return true; } @@ -1123,11 +1288,11 @@ bool CStringChecker::memsetAux(const Expr *DstBuffer, SVal CharVal, // evaluation of individual function calls. //===----------------------------------------------------------------------===// -void CStringChecker::evalCopyCommon(CheckerContext &C, const CallExpr *CE, +void CStringChecker::evalCopyCommon(CheckerContext &C, const CallEvent &Call, ProgramStateRef state, SizeArgExpr Size, DestinationArgExpr Dest, SourceArgExpr Source, bool Restricted, - bool IsMempcpy) const { + bool IsMempcpy, CharKind CK) const { CurrentFunctionDescription = "memory copy function"; // See if the size argument is zero. @@ -1145,7 +1310,8 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, const CallExpr *CE, // 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. if (stateZeroSize && !stateNonZeroSize) { - stateZeroSize = stateZeroSize->BindExpr(CE, LCtx, destVal); + stateZeroSize = + stateZeroSize->BindExpr(Call.getOriginExpr(), LCtx, destVal); C.addTransition(stateZeroSize); return; } @@ -1170,11 +1336,11 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, const CallExpr *CE, return; // Ensure the accesses are valid and that the buffers do not overlap. - state = CheckBufferAccess(C, state, Dest, Size, AccessKind::write); - state = CheckBufferAccess(C, state, Source, Size, AccessKind::read); + state = CheckBufferAccess(C, state, Dest, Size, AccessKind::write, CK); + state = CheckBufferAccess(C, state, Source, Size, AccessKind::read, CK); if (Restricted) - state = CheckOverlap(C, state, Size, Dest, Source); + state = CheckOverlap(C, state, Size, Dest, Source, CK); if (!state) return; @@ -1185,7 +1351,7 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, const CallExpr *CE, // Get the byte after the last byte copied. SValBuilder &SvalBuilder = C.getSValBuilder(); ASTContext &Ctx = SvalBuilder.getContext(); - QualType CharPtrTy = Ctx.getPointerType(Ctx.CharTy); + QualType CharPtrTy = getCharPtrType(Ctx, CK); SVal DestRegCharVal = SvalBuilder.evalCast(destVal, CharPtrTy, Dest.Expression->getType()); SVal lastElement = C.getSValBuilder().evalBinOp( @@ -1193,15 +1359,15 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, const CallExpr *CE, // If we don't know how much we copied, we can at least // conjure a return value for later. if (lastElement.isUnknown()) - lastElement = C.getSValBuilder().conjureSymbolVal(nullptr, CE, LCtx, - C.blockCount()); + lastElement = C.getSValBuilder().conjureSymbolVal( + nullptr, Call.getOriginExpr(), LCtx, C.blockCount()); // The byte after the last byte copied is the return value. - state = state->BindExpr(CE, LCtx, lastElement); + state = state->BindExpr(Call.getOriginExpr(), LCtx, lastElement); } else { // All other copies return the destination buffer. // (Well, bcopy() has a void return type, but this won't hurt.) - state = state->BindExpr(CE, LCtx, destVal); + state = state->BindExpr(Call.getOriginExpr(), LCtx, destVal); } // Invalidate the destination (regular invalidation without pointer-escaping @@ -1210,76 +1376,82 @@ void CStringChecker::evalCopyCommon(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 // copied region, but that's still an improvement over blank invalidation. - state = - InvalidateBuffer(C, state, Dest.Expression, C.getSVal(Dest.Expression), - /*IsSourceBuffer*/ false, Size.Expression); + state = invalidateDestinationBufferBySize( + C, state, Dest.Expression, C.getSVal(Dest.Expression), sizeVal, + Size.Expression->getType()); // Invalidate the source (const-invalidation without const-pointer-escaping // the address of the top-level region). - state = InvalidateBuffer(C, state, Source.Expression, - C.getSVal(Source.Expression), - /*IsSourceBuffer*/ true, nullptr); + state = invalidateSourceBuffer(C, state, Source.Expression, + C.getSVal(Source.Expression)); C.addTransition(state); } } -void CStringChecker::evalMemcpy(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalMemcpy(CheckerContext &C, const CallEvent &Call, + CharKind CK) const { // void *memcpy(void *restrict dst, const void *restrict src, size_t n); // The return value is the address of the destination buffer. - DestinationArgExpr Dest = {CE->getArg(0), 0}; - SourceArgExpr Src = {CE->getArg(1), 1}; - SizeArgExpr Size = {CE->getArg(2), 2}; + DestinationArgExpr Dest = {{Call.getArgExpr(0), 0}}; + SourceArgExpr Src = {{Call.getArgExpr(1), 1}}; + SizeArgExpr Size = {{Call.getArgExpr(2), 2}}; ProgramStateRef State = C.getState(); constexpr bool IsRestricted = true; constexpr bool IsMempcpy = false; - evalCopyCommon(C, CE, State, Size, Dest, Src, IsRestricted, IsMempcpy); + evalCopyCommon(C, Call, State, Size, Dest, Src, IsRestricted, IsMempcpy, CK); } -void CStringChecker::evalMempcpy(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalMempcpy(CheckerContext &C, const CallEvent &Call, + CharKind CK) 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. - DestinationArgExpr Dest = {CE->getArg(0), 0}; - SourceArgExpr Src = {CE->getArg(1), 1}; - SizeArgExpr Size = {CE->getArg(2), 2}; + DestinationArgExpr Dest = {{Call.getArgExpr(0), 0}}; + SourceArgExpr Src = {{Call.getArgExpr(1), 1}}; + SizeArgExpr Size = {{Call.getArgExpr(2), 2}}; constexpr bool IsRestricted = true; constexpr bool IsMempcpy = true; - evalCopyCommon(C, CE, C.getState(), Size, Dest, Src, IsRestricted, IsMempcpy); + evalCopyCommon(C, Call, C.getState(), Size, Dest, Src, IsRestricted, + IsMempcpy, CK); } -void CStringChecker::evalMemmove(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalMemmove(CheckerContext &C, const CallEvent &Call, + CharKind CK) const { // void *memmove(void *dst, const void *src, size_t n); // The return value is the address of the destination buffer. - DestinationArgExpr Dest = {CE->getArg(0), 0}; - SourceArgExpr Src = {CE->getArg(1), 1}; - SizeArgExpr Size = {CE->getArg(2), 2}; + DestinationArgExpr Dest = {{Call.getArgExpr(0), 0}}; + SourceArgExpr Src = {{Call.getArgExpr(1), 1}}; + SizeArgExpr Size = {{Call.getArgExpr(2), 2}}; constexpr bool IsRestricted = false; constexpr bool IsMempcpy = false; - evalCopyCommon(C, CE, C.getState(), Size, Dest, Src, IsRestricted, IsMempcpy); + evalCopyCommon(C, Call, C.getState(), Size, Dest, Src, IsRestricted, + IsMempcpy, CK); } -void CStringChecker::evalBcopy(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalBcopy(CheckerContext &C, const CallEvent &Call) const { // void bcopy(const void *src, void *dst, size_t n); - SourceArgExpr Src(CE->getArg(0), 0); - DestinationArgExpr Dest = {CE->getArg(1), 1}; - SizeArgExpr Size = {CE->getArg(2), 2}; + SourceArgExpr Src{{Call.getArgExpr(0), 0}}; + DestinationArgExpr Dest = {{Call.getArgExpr(1), 1}}; + SizeArgExpr Size = {{Call.getArgExpr(2), 2}}; constexpr bool IsRestricted = false; constexpr bool IsMempcpy = false; - evalCopyCommon(C, CE, C.getState(), Size, Dest, Src, IsRestricted, IsMempcpy); + evalCopyCommon(C, Call, C.getState(), Size, Dest, Src, IsRestricted, + IsMempcpy, CharKind::Regular); } -void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalMemcmp(CheckerContext &C, const CallEvent &Call, + CharKind CK) const { // int memcmp(const void *s1, const void *s2, size_t n); CurrentFunctionDescription = "memory comparison function"; - AnyArgExpr Left = {CE->getArg(0), 0}; - AnyArgExpr Right = {CE->getArg(1), 1}; - SizeArgExpr Size = {CE->getArg(2), 2}; + AnyArgExpr Left = {Call.getArgExpr(0), 0}; + AnyArgExpr Right = {Call.getArgExpr(1), 1}; + SizeArgExpr Size = {{Call.getArgExpr(2), 2}}; ProgramStateRef State = C.getState(); SValBuilder &Builder = C.getSValBuilder(); @@ -1297,7 +1469,8 @@ void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { // have to check either of the buffers. if (stateZeroSize) { State = stateZeroSize; - State = State->BindExpr(CE, LCtx, Builder.makeZeroVal(CE->getType())); + State = State->BindExpr(Call.getOriginExpr(), LCtx, + Builder.makeZeroVal(Call.getResultType())); C.addTransition(State); } @@ -1323,8 +1496,8 @@ void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { State = SameBuffer; State = CheckBufferAccess(C, State, Left, Size, AccessKind::read); if (State) { - State = - SameBuffer->BindExpr(CE, LCtx, Builder.makeZeroVal(CE->getType())); + State = SameBuffer->BindExpr(Call.getOriginExpr(), LCtx, + Builder.makeZeroVal(Call.getResultType())); C.addTransition(State); } return; @@ -1333,37 +1506,39 @@ void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { // If the two arguments might be different buffers, we have to check // the size of both of them. assert(NotSameBuffer); - State = CheckBufferAccess(C, State, Right, Size, AccessKind::read); - State = CheckBufferAccess(C, State, Left, Size, AccessKind::read); + State = CheckBufferAccess(C, State, Right, Size, AccessKind::read, CK); + State = CheckBufferAccess(C, State, Left, Size, AccessKind::read, CK); if (State) { // The return value is the comparison result, which we don't know. - SVal CmpV = Builder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()); - State = State->BindExpr(CE, LCtx, CmpV); + SVal CmpV = Builder.conjureSymbolVal(nullptr, Call.getOriginExpr(), LCtx, + C.blockCount()); + State = State->BindExpr(Call.getOriginExpr(), LCtx, CmpV); C.addTransition(State); } } } void CStringChecker::evalstrLength(CheckerContext &C, - const CallExpr *CE) const { + const CallEvent &Call) const { // size_t strlen(const char *s); - evalstrLengthCommon(C, CE, /* IsStrnlen = */ false); + evalstrLengthCommon(C, Call, /* IsStrnlen = */ false); } void CStringChecker::evalstrnLength(CheckerContext &C, - const CallExpr *CE) const { + const CallEvent &Call) const { // size_t strnlen(const char *s, size_t maxlen); - evalstrLengthCommon(C, CE, /* IsStrnlen = */ true); + evalstrLengthCommon(C, Call, /* IsStrnlen = */ true); } -void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, +void CStringChecker::evalstrLengthCommon(CheckerContext &C, + const CallEvent &Call, bool IsStrnlen) const { CurrentFunctionDescription = "string length function"; ProgramStateRef state = C.getState(); const LocationContext *LCtx = C.getLocationContext(); if (IsStrnlen) { - const Expr *maxlenExpr = CE->getArg(1); + const Expr *maxlenExpr = Call.getArgExpr(1); SVal maxlenVal = state->getSVal(maxlenExpr, LCtx); ProgramStateRef stateZeroSize, stateNonZeroSize; @@ -1373,8 +1548,8 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, // If the size can be zero, the result will be 0 in that case, and we don't // have to check the string itself. if (stateZeroSize) { - SVal zero = C.getSValBuilder().makeZeroVal(CE->getType()); - stateZeroSize = stateZeroSize->BindExpr(CE, LCtx, zero); + SVal zero = C.getSValBuilder().makeZeroVal(Call.getResultType()); + stateZeroSize = stateZeroSize->BindExpr(Call.getOriginExpr(), LCtx, zero); C.addTransition(stateZeroSize); } @@ -1387,7 +1562,7 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, } // Check that the string argument is non-null. - AnyArgExpr Arg = {CE->getArg(0), 0}; + AnyArgExpr Arg = {Call.getArgExpr(0), 0}; SVal ArgVal = state->getSVal(Arg.Expression, LCtx); state = checkNonNull(C, state, Arg, ArgVal); @@ -1410,11 +1585,11 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, // It's a little unfortunate to be getting this again, // but it's not that expensive... - const Expr *maxlenExpr = CE->getArg(1); + const Expr *maxlenExpr = Call.getArgExpr(1); SVal maxlenVal = state->getSVal(maxlenExpr, LCtx); - Optional<NonLoc> strLengthNL = strLength.getAs<NonLoc>(); - Optional<NonLoc> maxlenValNL = maxlenVal.getAs<NonLoc>(); + std::optional<NonLoc> strLengthNL = strLength.getAs<NonLoc>(); + std::optional<NonLoc> maxlenValNL = maxlenVal.getAs<NonLoc>(); if (strLengthNL && maxlenValNL) { ProgramStateRef stateStringTooLong, stateStringNotTooLong; @@ -1439,8 +1614,8 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, // no guarantee the full string length will actually be returned. // All we know is the return value is the min of the string length // and the limit. This is better than nothing. - result = C.getSValBuilder().conjureSymbolVal(nullptr, CE, LCtx, - C.blockCount()); + result = C.getSValBuilder().conjureSymbolVal( + nullptr, Call.getOriginExpr(), LCtx, C.blockCount()); NonLoc resultNL = result.castAs<NonLoc>(); if (strLengthNL) { @@ -1463,78 +1638,85 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, // If we don't know the length of the string, conjure a return // value, so it can be used in constraints, at least. if (result.isUnknown()) { - result = C.getSValBuilder().conjureSymbolVal(nullptr, CE, LCtx, - C.blockCount()); + result = C.getSValBuilder().conjureSymbolVal( + nullptr, Call.getOriginExpr(), LCtx, C.blockCount()); } } // Bind the return value. assert(!result.isUnknown() && "Should have conjured a value by now"); - state = state->BindExpr(CE, LCtx, result); + state = state->BindExpr(Call.getOriginExpr(), LCtx, result); C.addTransition(state); } -void CStringChecker::evalStrcpy(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalStrcpy(CheckerContext &C, + const CallEvent &Call) const { // char *strcpy(char *restrict dst, const char *restrict src); - evalStrcpyCommon(C, CE, + evalStrcpyCommon(C, Call, /* ReturnEnd = */ false, /* IsBounded = */ false, /* appendK = */ ConcatFnKind::none); } -void CStringChecker::evalStrncpy(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalStrncpy(CheckerContext &C, + const CallEvent &Call) const { // char *strncpy(char *restrict dst, const char *restrict src, size_t n); - evalStrcpyCommon(C, CE, + evalStrcpyCommon(C, Call, /* ReturnEnd = */ false, /* IsBounded = */ true, /* appendK = */ ConcatFnKind::none); } -void CStringChecker::evalStpcpy(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalStpcpy(CheckerContext &C, + const CallEvent &Call) const { // char *stpcpy(char *restrict dst, const char *restrict src); - evalStrcpyCommon(C, CE, + evalStrcpyCommon(C, Call, /* ReturnEnd = */ true, /* IsBounded = */ false, /* appendK = */ ConcatFnKind::none); } -void CStringChecker::evalStrlcpy(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalStrlcpy(CheckerContext &C, + const CallEvent &Call) const { // size_t strlcpy(char *dest, const char *src, size_t size); - evalStrcpyCommon(C, CE, + evalStrcpyCommon(C, Call, /* ReturnEnd = */ true, /* IsBounded = */ true, /* appendK = */ ConcatFnKind::none, /* returnPtr = */ false); } -void CStringChecker::evalStrcat(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalStrcat(CheckerContext &C, + const CallEvent &Call) const { // char *strcat(char *restrict s1, const char *restrict s2); - evalStrcpyCommon(C, CE, + evalStrcpyCommon(C, Call, /* ReturnEnd = */ false, /* IsBounded = */ false, /* appendK = */ ConcatFnKind::strcat); } -void CStringChecker::evalStrncat(CheckerContext &C, const CallExpr *CE) const { - //char *strncat(char *restrict s1, const char *restrict s2, size_t n); - evalStrcpyCommon(C, CE, +void CStringChecker::evalStrncat(CheckerContext &C, + const CallEvent &Call) const { + // char *strncat(char *restrict s1, const char *restrict s2, size_t n); + evalStrcpyCommon(C, Call, /* ReturnEnd = */ false, /* IsBounded = */ true, /* appendK = */ ConcatFnKind::strcat); } -void CStringChecker::evalStrlcat(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalStrlcat(CheckerContext &C, + const CallEvent &Call) const { // size_t strlcat(char *dst, const char *src, size_t size); // It will append at most size - strlen(dst) - 1 bytes, // NULL-terminating the result. - evalStrcpyCommon(C, CE, + evalStrcpyCommon(C, Call, /* ReturnEnd = */ false, /* IsBounded = */ true, /* appendK = */ ConcatFnKind::strlcat, /* returnPtr = */ false); } -void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, +void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallEvent &Call, bool ReturnEnd, bool IsBounded, ConcatFnKind appendK, bool returnPtr) const { @@ -1547,14 +1729,14 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, const LocationContext *LCtx = C.getLocationContext(); // Check that the destination is non-null. - DestinationArgExpr Dst = {CE->getArg(0), 0}; + DestinationArgExpr Dst = {{Call.getArgExpr(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. - SourceArgExpr srcExpr = {CE->getArg(1), 1}; + SourceArgExpr srcExpr = {{Call.getArgExpr(1), 1}}; SVal srcVal = state->getSVal(srcExpr.Expression, LCtx); state = checkNonNull(C, state, srcExpr, srcVal); if (!state) @@ -1562,11 +1744,11 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // Get the string length of the source. SVal strLength = getCStringLength(C, state, srcExpr.Expression, srcVal); - Optional<NonLoc> strLengthNL = strLength.getAs<NonLoc>(); + std::optional<NonLoc> strLengthNL = strLength.getAs<NonLoc>(); // Get the string length of the destination buffer. SVal dstStrLength = getCStringLength(C, state, Dst.Expression, DstVal); - Optional<NonLoc> dstStrLengthNL = dstStrLength.getAs<NonLoc>(); + std::optional<NonLoc> dstStrLengthNL = dstStrLength.getAs<NonLoc>(); // If the source isn't a valid C string, give up. if (strLength.isUndef()) @@ -1585,11 +1767,12 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // 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}; + SizeArgExpr SrcExprAsSizeDummy = { + {srcExpr.Expression, srcExpr.ArgumentIndex}}; state = CheckOverlap( C, state, - (IsBounded ? SizeArgExpr{CE->getArg(2), 2} : SrcExprAsSizeDummy), Dst, - srcExpr); + (IsBounded ? SizeArgExpr{{Call.getArgExpr(2), 2}} : SrcExprAsSizeDummy), + Dst, srcExpr); if (!state) return; @@ -1597,14 +1780,14 @@ 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. - SizeArgExpr lenExpr = {CE->getArg(2), 2}; + SizeArgExpr lenExpr = {{Call.getArgExpr(2), 2}}; SVal lenVal = state->getSVal(lenExpr.Expression, LCtx); // Protect against misdeclared strncpy(). lenVal = svalBuilder.evalCast(lenVal, sizeTy, lenExpr.Expression->getType()); - Optional<NonLoc> lenValNL = lenVal.getAs<NonLoc>(); + std::optional<NonLoc> lenValNL = lenVal.getAs<NonLoc>(); // If we know both values, we might be able to figure out how much // we're copying. @@ -1641,12 +1824,12 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // amountCopied = min (size - dstLen - 1 , srcLen) SVal freeSpace = svalBuilder.evalBinOpNN(state, BO_Sub, *lenValNL, *dstStrLengthNL, sizeTy); - if (!freeSpace.getAs<NonLoc>()) + if (!isa<NonLoc>(freeSpace)) return; freeSpace = svalBuilder.evalBinOp(state, BO_Sub, freeSpace, svalBuilder.makeIntVal(1, sizeTy), sizeTy); - Optional<NonLoc> freeSpaceNL = freeSpace.getAs<NonLoc>(); + std::optional<NonLoc> freeSpaceNL = freeSpace.getAs<NonLoc>(); // While unlikely, it is possible that the subtraction is // too complex to compute, let's check whether it succeeded. @@ -1711,16 +1894,19 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // If the size is known to be zero, we're done. if (StateZeroSize && !StateNonZeroSize) { if (returnPtr) { - StateZeroSize = StateZeroSize->BindExpr(CE, LCtx, DstVal); + StateZeroSize = + StateZeroSize->BindExpr(Call.getOriginExpr(), LCtx, DstVal); } else { if (appendK == ConcatFnKind::none) { // strlcpy returns strlen(src) - StateZeroSize = StateZeroSize->BindExpr(CE, LCtx, strLength); + StateZeroSize = StateZeroSize->BindExpr(Call.getOriginExpr(), + LCtx, strLength); } else { // strlcat returns strlen(src) + strlen(dst) SVal retSize = svalBuilder.evalBinOp( state, BO_Add, strLength, dstStrLength, sizeTy); - StateZeroSize = StateZeroSize->BindExpr(CE, LCtx, retSize); + StateZeroSize = + StateZeroSize->BindExpr(Call.getOriginExpr(), LCtx, retSize); } } C.addTransition(StateZeroSize); @@ -1771,7 +1957,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, *dstStrLengthNL, sizeTy); } - Optional<NonLoc> amountCopiedNL = amountCopied.getAs<NonLoc>(); + std::optional<NonLoc> amountCopiedNL = amountCopied.getAs<NonLoc>(); // If we know both string lengths, we might know the final string length. if (amountCopiedNL && dstStrLengthNL) { @@ -1789,10 +1975,12 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, if (finalStrLength.isUnknown()) { // Try to get a "hypothetical" string length symbol, which we can later // set as a real value if that turns out to be the case. - finalStrLength = getCStringLength(C, state, CE, DstVal, true); + finalStrLength = + getCStringLength(C, state, Call.getOriginExpr(), DstVal, true); assert(!finalStrLength.isUndef()); - if (Optional<NonLoc> finalStrLengthNL = finalStrLength.getAs<NonLoc>()) { + if (std::optional<NonLoc> finalStrLengthNL = + finalStrLength.getAs<NonLoc>()) { if (amountCopiedNL && appendK == ConcatFnKind::none) { // we overwrite dst string with the src // finalStrLength >= srcStrLength @@ -1843,28 +2031,38 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // If the destination is a MemRegion, try to check for a buffer overflow and // record the new string length. - if (Optional<loc::MemRegionVal> dstRegVal = - DstVal.getAs<loc::MemRegionVal>()) { + if (std::optional<loc::MemRegionVal> dstRegVal = + DstVal.getAs<loc::MemRegionVal>()) { 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 (Optional<NonLoc> maxLastNL = maxLastElementIndex.getAs<NonLoc>()) { + if (std::optional<NonLoc> maxLastNL = maxLastElementIndex.getAs<NonLoc>()) { SVal maxLastElement = svalBuilder.evalBinOpLN(state, BO_Add, *dstRegVal, *maxLastNL, ptrTy); + // Check if the first byte of the destination is writable. + state = CheckLocation(C, state, Dst, DstVal, AccessKind::write); + if (!state) + return; + // Check if the last byte of the destination is writable. state = CheckLocation(C, state, Dst, maxLastElement, AccessKind::write); if (!state) return; } // Then, if the final length is known... - if (Optional<NonLoc> knownStrLength = finalStrLength.getAs<NonLoc>()) { + if (std::optional<NonLoc> knownStrLength = finalStrLength.getAs<NonLoc>()) { SVal lastElement = svalBuilder.evalBinOpLN(state, BO_Add, *dstRegVal, *knownStrLength, ptrTy); // ...and we haven't checked the bound, we'll check the actual copy. if (!boundWarning) { + // Check if the first byte of the destination is writable. + state = CheckLocation(C, state, Dst, DstVal, AccessKind::write); + if (!state) + return; + // Check if the last byte of the destination is writable. state = CheckLocation(C, state, Dst, lastElement, AccessKind::write); if (!state) return; @@ -1882,13 +2080,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.Expression, *dstRegVal, - /*IsSourceBuffer*/ false, nullptr); + state = invalidateDestinationBufferBySize(C, state, Dst.Expression, + *dstRegVal, amountCopied, + C.getASTContext().getSizeType()); // Invalidate the source (const-invalidation without const-pointer-escaping // the address of the top-level region). - state = InvalidateBuffer(C, state, srcExpr.Expression, srcVal, - /*IsSourceBuffer*/ true, nullptr); + state = invalidateSourceBuffer(C, state, srcExpr.Expression, srcVal); // Set the C string length of the destination, if we know it. if (IsBounded && (appendK == ConcatFnKind::none)) { @@ -1908,51 +2106,54 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // If this is a stpcpy-style copy, but we were unable to check for a buffer // overflow, we still need a result. Conjure a return value. if (ReturnEnd && Result.isUnknown()) { - Result = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()); + Result = svalBuilder.conjureSymbolVal(nullptr, Call.getOriginExpr(), LCtx, + C.blockCount()); } } // Set the return value. - state = state->BindExpr(CE, LCtx, Result); + state = state->BindExpr(Call.getOriginExpr(), LCtx, Result); C.addTransition(state); } -void CStringChecker::evalStrcmp(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalStrcmp(CheckerContext &C, + const CallEvent &Call) const { //int strcmp(const char *s1, const char *s2); - evalStrcmpCommon(C, CE, /* IsBounded = */ false, /* IgnoreCase = */ false); + evalStrcmpCommon(C, Call, /* IsBounded = */ false, /* IgnoreCase = */ false); } -void CStringChecker::evalStrncmp(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalStrncmp(CheckerContext &C, + const CallEvent &Call) const { //int strncmp(const char *s1, const char *s2, size_t n); - evalStrcmpCommon(C, CE, /* IsBounded = */ true, /* IgnoreCase = */ false); + evalStrcmpCommon(C, Call, /* IsBounded = */ true, /* IgnoreCase = */ false); } void CStringChecker::evalStrcasecmp(CheckerContext &C, - const CallExpr *CE) const { + const CallEvent &Call) const { //int strcasecmp(const char *s1, const char *s2); - evalStrcmpCommon(C, CE, /* IsBounded = */ false, /* IgnoreCase = */ true); + evalStrcmpCommon(C, Call, /* IsBounded = */ false, /* IgnoreCase = */ true); } void CStringChecker::evalStrncasecmp(CheckerContext &C, - const CallExpr *CE) const { + const CallEvent &Call) const { //int strncasecmp(const char *s1, const char *s2, size_t n); - evalStrcmpCommon(C, CE, /* IsBounded = */ true, /* IgnoreCase = */ true); + evalStrcmpCommon(C, Call, /* IsBounded = */ true, /* IgnoreCase = */ true); } -void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, - bool IsBounded, bool IgnoreCase) const { +void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallEvent &Call, + bool IsBounded, bool IgnoreCase) const { CurrentFunctionDescription = "string comparison function"; ProgramStateRef state = C.getState(); const LocationContext *LCtx = C.getLocationContext(); // Check that the first string is non-null - AnyArgExpr Left = {CE->getArg(0), 0}; + AnyArgExpr Left = {Call.getArgExpr(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. - AnyArgExpr Right = {CE->getArg(1), 1}; + AnyArgExpr Right = {Call.getArgExpr(1), 1}; SVal RightVal = state->getSVal(Right.Expression, LCtx); state = checkNonNull(C, state, Right, RightVal); if (!state) @@ -1983,8 +2184,9 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, // If the two arguments might be the same buffer, we know the result is 0, // and we only need to check one size. if (StSameBuf) { - StSameBuf = StSameBuf->BindExpr(CE, LCtx, - svalBuilder.makeZeroVal(CE->getType())); + StSameBuf = + StSameBuf->BindExpr(Call.getOriginExpr(), LCtx, + svalBuilder.makeZeroVal(Call.getResultType())); C.addTransition(StSameBuf); // If the two arguments are GUARANTEED to be the same, we're done! @@ -2004,8 +2206,8 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, const StringLiteral *RightStrLiteral = getCStringLiteral(C, state, Right.Expression, RightVal); bool canComputeResult = false; - SVal resultVal = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, - C.blockCount()); + SVal resultVal = svalBuilder.conjureSymbolVal(nullptr, Call.getOriginExpr(), + LCtx, C.blockCount()); if (LeftStrLiteral && RightStrLiteral) { StringRef LeftStrRef = LeftStrLiteral->getString(); @@ -2013,7 +2215,7 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, if (IsBounded) { // Get the max number of characters to compare. - const Expr *lenExpr = CE->getArg(2); + const Expr *lenExpr = Call.getArgExpr(2); SVal lenVal = state->getSVal(lenExpr, LCtx); // If the length is known, we can get the right substrings. @@ -2045,13 +2247,13 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, // The strcmp function returns an integer greater than, equal to, or less // than zero, [c11, p7.24.4.2]. if (compareRes == 0) { - resultVal = svalBuilder.makeIntVal(compareRes, CE->getType()); + resultVal = svalBuilder.makeIntVal(compareRes, Call.getResultType()); } else { - DefinedSVal zeroVal = svalBuilder.makeIntVal(0, CE->getType()); + DefinedSVal zeroVal = svalBuilder.makeIntVal(0, Call.getResultType()); // Constrain strcmp's result range based on the result of StringRef's // comparison methods. - BinaryOperatorKind op = (compareRes == 1) ? BO_GT : BO_LT; + BinaryOperatorKind op = (compareRes > 0) ? BO_GT : BO_LT; SVal compareWithZero = svalBuilder.evalBinOp(state, op, resultVal, zeroVal, svalBuilder.getConditionType()); @@ -2061,20 +2263,21 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, } } - state = state->BindExpr(CE, LCtx, resultVal); + state = state->BindExpr(Call.getOriginExpr(), LCtx, resultVal); // Record this as a possible path. C.addTransition(state); } -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? - SourceArgExpr SearchStrPtr = {CE->getArg(0), 0}; +void CStringChecker::evalStrsep(CheckerContext &C, + const CallEvent &Call) const { + // char *strsep(char **stringp, const char *delim); + // Verify whether the search string parameter matches the return type. + SourceArgExpr SearchStrPtr = {{Call.getArgExpr(0), 0}}; QualType CharPtrTy = SearchStrPtr.Expression->getType()->getPointeeType(); - if (CharPtrTy.isNull() || - CE->getType().getUnqualifiedType() != CharPtrTy.getUnqualifiedType()) + if (CharPtrTy.isNull() || Call.getResultType().getUnqualifiedType() != + CharPtrTy.getUnqualifiedType()) return; CurrentFunctionDescription = "strsep()"; @@ -2089,7 +2292,7 @@ void CStringChecker::evalStrsep(CheckerContext &C, const CallExpr *CE) const { return; // Check that the delimiter string is non-null. - AnyArgExpr DelimStr = {CE->getArg(1), 1}; + AnyArgExpr DelimStr = {Call.getArgExpr(1), 1}; SVal DelimStrVal = State->getSVal(DelimStr.Expression, LCtx); State = checkNonNull(C, State, DelimStr, DelimStrVal); if (!State) @@ -2097,48 +2300,49 @@ void CStringChecker::evalStrsep(CheckerContext &C, const CallExpr *CE) const { SValBuilder &SVB = C.getSValBuilder(); SVal Result; - if (Optional<Loc> SearchStrLoc = SearchStrVal.getAs<Loc>()) { + if (std::optional<Loc> SearchStrLoc = SearchStrVal.getAs<Loc>()) { // Get the current value of the search string pointer, as a char*. Result = State->getSVal(*SearchStrLoc, CharPtrTy); // Invalidate the search string, representing the change of one delimiter // character to NUL. - State = InvalidateBuffer(C, State, SearchStrPtr.Expression, Result, - /*IsSourceBuffer*/ false, nullptr); + // As the replacement never overflows, do not invalidate its super region. + State = invalidateDestinationBufferNeverOverflows( + C, State, SearchStrPtr.Expression, Result); // Overwrite the search string pointer. The new value is either an address // further along in the same string, or NULL if there are no more tokens. - State = State->bindLoc(*SearchStrLoc, - SVB.conjureSymbolVal(getTag(), - CE, - LCtx, - CharPtrTy, - C.blockCount()), - LCtx); + State = + State->bindLoc(*SearchStrLoc, + SVB.conjureSymbolVal(getTag(), Call.getOriginExpr(), + LCtx, CharPtrTy, C.blockCount()), + LCtx); } else { assert(SearchStrVal.isUnknown()); // Conjure a symbolic value. It's the best we can do. - Result = SVB.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()); + Result = SVB.conjureSymbolVal(nullptr, Call.getOriginExpr(), LCtx, + C.blockCount()); } // Set the return value, and finish. - State = State->BindExpr(CE, LCtx, Result); + State = State->BindExpr(Call.getOriginExpr(), LCtx, Result); C.addTransition(State); } // These should probably be moved into a C++ standard library checker. -void CStringChecker::evalStdCopy(CheckerContext &C, const CallExpr *CE) const { - evalStdCopyCommon(C, CE); +void CStringChecker::evalStdCopy(CheckerContext &C, + const CallEvent &Call) const { + evalStdCopyCommon(C, Call); } void CStringChecker::evalStdCopyBackward(CheckerContext &C, - const CallExpr *CE) const { - evalStdCopyCommon(C, CE); + const CallEvent &Call) const { + evalStdCopyCommon(C, Call); } void CStringChecker::evalStdCopyCommon(CheckerContext &C, - const CallExpr *CE) const { - if (!CE->getArg(2)->getType()->isPointerType()) + const CallEvent &Call) const { + if (!Call.getArgExpr(2)->getType()->isPointerType()) return; ProgramStateRef State = C.getState(); @@ -2151,26 +2355,30 @@ void CStringChecker::evalStdCopyCommon(CheckerContext &C, // _OutputIterator __result) // Invalidate the destination buffer - const Expr *Dst = CE->getArg(2); + const Expr *Dst = Call.getArgExpr(2); SVal DstVal = State->getSVal(Dst, LCtx); - State = InvalidateBuffer(C, State, Dst, DstVal, /*IsSource=*/false, - /*Size=*/nullptr); + // FIXME: As we do not know how many items are copied, we also invalidate the + // super region containing the target location. + State = + invalidateDestinationBufferAlwaysEscapeSuperRegion(C, State, Dst, DstVal); SValBuilder &SVB = C.getSValBuilder(); - SVal ResultVal = SVB.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()); - State = State->BindExpr(CE, LCtx, ResultVal); + SVal ResultVal = + SVB.conjureSymbolVal(nullptr, Call.getOriginExpr(), LCtx, C.blockCount()); + State = State->BindExpr(Call.getOriginExpr(), LCtx, ResultVal); C.addTransition(State); } -void CStringChecker::evalMemset(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalMemset(CheckerContext &C, + const CallEvent &Call) const { // void *memset(void *s, int c, size_t n); CurrentFunctionDescription = "memory set function"; - DestinationArgExpr Buffer = {CE->getArg(0), 0}; - AnyArgExpr CharE = {CE->getArg(1), 1}; - SizeArgExpr Size = {CE->getArg(2), 2}; + DestinationArgExpr Buffer = {{Call.getArgExpr(0), 0}}; + AnyArgExpr CharE = {Call.getArgExpr(1), 1}; + SizeArgExpr Size = {{Call.getArgExpr(2), 2}}; ProgramStateRef State = C.getState(); @@ -2188,7 +2396,7 @@ void CStringChecker::evalMemset(CheckerContext &C, const CallExpr *CE) const { // If the size is zero, there won't be any actual memory access, so // just bind the return value to the buffer and return. if (ZeroSize && !NonZeroSize) { - ZeroSize = ZeroSize->BindExpr(CE, LCtx, BufferPtrVal); + ZeroSize = ZeroSize->BindExpr(Call.getOriginExpr(), LCtx, BufferPtrVal); C.addTransition(ZeroSize); return; } @@ -2210,15 +2418,15 @@ void CStringChecker::evalMemset(CheckerContext &C, const CallExpr *CE) const { Size.Expression, C, State)) return; - State = State->BindExpr(CE, LCtx, BufferPtrVal); + State = State->BindExpr(Call.getOriginExpr(), LCtx, BufferPtrVal); C.addTransition(State); } -void CStringChecker::evalBzero(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalBzero(CheckerContext &C, const CallEvent &Call) const { CurrentFunctionDescription = "memory clearance function"; - DestinationArgExpr Buffer = {CE->getArg(0), 0}; - SizeArgExpr Size = {CE->getArg(1), 1}; + DestinationArgExpr Buffer = {{Call.getArgExpr(0), 0}}; + SizeArgExpr Size = {{Call.getArgExpr(1), 1}}; SVal Zero = C.getSValBuilder().makeZeroVal(C.getASTContext().IntTy); ProgramStateRef State = C.getState(); @@ -2257,6 +2465,57 @@ void CStringChecker::evalBzero(CheckerContext &C, const CallExpr *CE) const { C.addTransition(State); } +void CStringChecker::evalSprintf(CheckerContext &C, + const CallEvent &Call) const { + CurrentFunctionDescription = "'sprintf'"; + const auto *CE = cast<CallExpr>(Call.getOriginExpr()); + bool IsBI = CE->getBuiltinCallee() == Builtin::BI__builtin___sprintf_chk; + evalSprintfCommon(C, Call, /* IsBounded */ false, IsBI); +} + +void CStringChecker::evalSnprintf(CheckerContext &C, + const CallEvent &Call) const { + CurrentFunctionDescription = "'snprintf'"; + const auto *CE = cast<CallExpr>(Call.getOriginExpr()); + bool IsBI = CE->getBuiltinCallee() == Builtin::BI__builtin___snprintf_chk; + evalSprintfCommon(C, Call, /* IsBounded */ true, IsBI); +} + +void CStringChecker::evalSprintfCommon(CheckerContext &C, const CallEvent &Call, + bool IsBounded, bool IsBuiltin) const { + ProgramStateRef State = C.getState(); + const auto *CE = cast<CallExpr>(Call.getOriginExpr()); + DestinationArgExpr Dest = {{Call.getArgExpr(0), 0}}; + + const auto NumParams = Call.parameters().size(); + assert(CE->getNumArgs() >= NumParams); + + const auto AllArguments = + llvm::make_range(CE->getArgs(), CE->getArgs() + CE->getNumArgs()); + const auto VariadicArguments = drop_begin(enumerate(AllArguments), NumParams); + + for (const auto &[ArgIdx, ArgExpr] : VariadicArguments) { + // We consider only string buffers + if (const QualType type = ArgExpr->getType(); + !type->isAnyPointerType() || + !type->getPointeeType()->isAnyCharacterType()) + continue; + SourceArgExpr Source = {{ArgExpr, unsigned(ArgIdx)}}; + + // Ensure the buffers do not overlap. + SizeArgExpr SrcExprAsSizeDummy = { + {Source.Expression, Source.ArgumentIndex}}; + State = CheckOverlap( + C, State, + (IsBounded ? SizeArgExpr{{Call.getArgExpr(1), 1}} : SrcExprAsSizeDummy), + Dest, Source); + if (!State) + return; + } + + C.addTransition(State); +} + //===----------------------------------------------------------------------===// // The driver method, and other Checker callbacks. //===----------------------------------------------------------------------===// @@ -2271,11 +2530,10 @@ CStringChecker::FnCheck CStringChecker::identifyCall(const CallEvent &Call, if (!FD) return nullptr; - if (Call.isCalled(StdCopy)) { + if (StdCopy.matches(Call)) return &CStringChecker::evalStdCopy; - } else if (Call.isCalled(StdCopyBackward)) { + if (StdCopyBackward.matches(Call)) return &CStringChecker::evalStdCopyBackward; - } // Pro-actively check that argument types are safe to do arithmetic upon. // We do not want to crash if someone accidentally passes a structure @@ -2302,8 +2560,8 @@ bool CStringChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { return false; // Check and evaluate the call. - const auto *CE = cast<CallExpr>(Call.getOriginExpr()); - (this->*Callback)(C, CE); + assert(isa<CallExpr>(Call.getOriginExpr())); + Callback(this, C, Call); // If the evaluate call resulted in no change, chain to the next eval call // handler. @@ -2364,9 +2622,7 @@ CStringChecker::checkRegionChanges(ProgramStateRef state, llvm::SmallPtrSet<const MemRegion *, 32> SuperRegions; // First build sets for the changed regions and their super-regions. - for (ArrayRef<const MemRegion *>::iterator - I = Regions.begin(), E = Regions.end(); I != E; ++I) { - const MemRegion *MR = *I; + for (const MemRegion *MR : Regions) { Invalidated.insert(MR); SuperRegions.insert(MR); @@ -2379,10 +2635,7 @@ CStringChecker::checkRegionChanges(ProgramStateRef state, CStringLengthTy::Factory &F = state->get_context<CStringLength>(); // Then loop over the entries in the current state. - for (CStringLengthTy::iterator I = Entries.begin(), - E = Entries.end(); I != E; ++I) { - const MemRegion *MR = I.getKey(); - + for (const MemRegion *MR : llvm::make_first_range(Entries)) { // Is this entry for a super-region of a changed region? if (SuperRegions.count(MR)) { Entries = F.remove(Entries, MR); @@ -2408,13 +2661,9 @@ void CStringChecker::checkLiveSymbols(ProgramStateRef state, // Mark all symbols in our string length map as valid. CStringLengthTy Entries = state->get<CStringLength>(); - for (CStringLengthTy::iterator I = Entries.begin(), E = Entries.end(); - I != E; ++I) { - SVal Len = I.getData(); - - for (SymExpr::symbol_iterator si = Len.symbol_begin(), - se = Len.symbol_end(); si != se; ++si) - SR.markInUse(*si); + for (SVal Len : llvm::make_second_range(Entries)) { + for (SymbolRef Sym : Len.symbols()) + SR.markInUse(Sym); } } @@ -2426,12 +2675,10 @@ void CStringChecker::checkDeadSymbols(SymbolReaper &SR, return; CStringLengthTy::Factory &F = state->get_context<CStringLength>(); - for (CStringLengthTy::iterator I = Entries.begin(), E = Entries.end(); - I != E; ++I) { - SVal Len = I.getData(); + for (auto [Reg, Len] : Entries) { if (SymbolRef Sym = Len.getAsSymbol()) { if (SR.isDead(Sym)) - Entries = F.remove(Entries, I.getKey()); + Entries = F.remove(Entries, Reg); } } @@ -2460,3 +2707,4 @@ REGISTER_CHECKER(CStringNullArg) REGISTER_CHECKER(CStringOutOfBounds) REGISTER_CHECKER(CStringBufferOverlap) REGISTER_CHECKER(CStringNotNullTerm) +REGISTER_CHECKER(CStringUninitializedRead) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CXXDeleteChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CXXDeleteChecker.cpp new file mode 100644 index 000000000000..b4dee1e300e8 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CXXDeleteChecker.cpp @@ -0,0 +1,238 @@ +//=== CXXDeleteChecker.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 the following new checkers for C++ delete expressions: +// +// * DeleteWithNonVirtualDtorChecker +// Defines a checker for the OOP52-CPP CERT rule: Do not delete a +// polymorphic object without a virtual destructor. +// +// Diagnostic flags -Wnon-virtual-dtor and -Wdelete-non-virtual-dtor +// report if an object with a virtual function but a non-virtual +// destructor exists or is deleted, respectively. +// +// This check exceeds them by comparing the dynamic and static types of +// the object at the point of destruction and only warns if it happens +// through a pointer to a base type without a virtual destructor. The +// check places a note at the last point where the conversion from +// derived to base happened. +// +// * CXXArrayDeleteChecker +// Defines a checker for the EXP51-CPP CERT rule: Do not delete an array +// through a pointer of the incorrect type. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" + +using namespace clang; +using namespace ento; + +namespace { +class CXXDeleteChecker : public Checker<check::PreStmt<CXXDeleteExpr>> { +protected: + class PtrCastVisitor : public BugReporterVisitor { + public: + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + } + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; + }; + + virtual void + checkTypedDeleteExpr(const CXXDeleteExpr *DE, CheckerContext &C, + const TypedValueRegion *BaseClassRegion, + const SymbolicRegion *DerivedClassRegion) const = 0; + +public: + void checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const; +}; + +class DeleteWithNonVirtualDtorChecker : public CXXDeleteChecker { + const BugType BT{ + this, "Destruction of a polymorphic object with no virtual destructor"}; + + void + checkTypedDeleteExpr(const CXXDeleteExpr *DE, CheckerContext &C, + const TypedValueRegion *BaseClassRegion, + const SymbolicRegion *DerivedClassRegion) const override; +}; + +class CXXArrayDeleteChecker : public CXXDeleteChecker { + const BugType BT{this, + "Deleting an array of polymorphic objects is undefined"}; + + void + checkTypedDeleteExpr(const CXXDeleteExpr *DE, CheckerContext &C, + const TypedValueRegion *BaseClassRegion, + const SymbolicRegion *DerivedClassRegion) const override; +}; +} // namespace + +void CXXDeleteChecker::checkPreStmt(const CXXDeleteExpr *DE, + CheckerContext &C) const { + const Expr *DeletedObj = DE->getArgument(); + const MemRegion *MR = C.getSVal(DeletedObj).getAsRegion(); + if (!MR) + return; + + OverloadedOperatorKind DeleteKind = + DE->getOperatorDelete()->getOverloadedOperator(); + + if (DeleteKind != OO_Delete && DeleteKind != OO_Array_Delete) + return; + + const auto *BaseClassRegion = MR->getAs<TypedValueRegion>(); + const auto *DerivedClassRegion = MR->getBaseRegion()->getAs<SymbolicRegion>(); + if (!BaseClassRegion || !DerivedClassRegion) + return; + + checkTypedDeleteExpr(DE, C, BaseClassRegion, DerivedClassRegion); +} + +void DeleteWithNonVirtualDtorChecker::checkTypedDeleteExpr( + const CXXDeleteExpr *DE, CheckerContext &C, + const TypedValueRegion *BaseClassRegion, + const SymbolicRegion *DerivedClassRegion) const { + const auto *BaseClass = BaseClassRegion->getValueType()->getAsCXXRecordDecl(); + const auto *DerivedClass = + DerivedClassRegion->getSymbol()->getType()->getPointeeCXXRecordDecl(); + if (!BaseClass || !DerivedClass) + return; + + if (!BaseClass->hasDefinition() || !DerivedClass->hasDefinition()) + return; + + if (BaseClass->getDestructor()->isVirtual()) + return; + + if (!DerivedClass->isDerivedFrom(BaseClass)) + return; + + 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. + R->markInteresting(BaseClassRegion); + R->addVisitor<PtrCastVisitor>(); + C.emitReport(std::move(R)); +} + +void CXXArrayDeleteChecker::checkTypedDeleteExpr( + const CXXDeleteExpr *DE, CheckerContext &C, + const TypedValueRegion *BaseClassRegion, + const SymbolicRegion *DerivedClassRegion) const { + const auto *BaseClass = BaseClassRegion->getValueType()->getAsCXXRecordDecl(); + const auto *DerivedClass = + DerivedClassRegion->getSymbol()->getType()->getPointeeCXXRecordDecl(); + if (!BaseClass || !DerivedClass) + return; + + if (!BaseClass->hasDefinition() || !DerivedClass->hasDefinition()) + return; + + if (DE->getOperatorDelete()->getOverloadedOperator() != OO_Array_Delete) + return; + + if (!DerivedClass->isDerivedFrom(BaseClass)) + return; + + ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return; + + SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); + + QualType SourceType = BaseClassRegion->getValueType(); + QualType TargetType = + DerivedClassRegion->getSymbol()->getType()->getPointeeType(); + + OS << "Deleting an array of '" << TargetType.getAsString() + << "' objects as their base class '" + << SourceType.getAsString(C.getASTContext().getPrintingPolicy()) + << "' is undefined"; + + auto R = std::make_unique<PathSensitiveBugReport>(BT, OS.str(), N); + + // Mark region of problematic base class for later use in the BugVisitor. + R->markInteresting(BaseClassRegion); + R->addVisitor<PtrCastVisitor>(); + C.emitReport(std::move(R)); +} + +PathDiagnosticPieceRef +CXXDeleteChecker::PtrCastVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { + const Stmt *S = N->getStmtForDiagnostics(); + if (!S) + return nullptr; + + const auto *CastE = dyn_cast<CastExpr>(S); + if (!CastE) + return nullptr; + + // FIXME: This way of getting base types does not support reference types. + QualType SourceType = CastE->getSubExpr()->getType()->getPointeeType(); + QualType TargetType = CastE->getType()->getPointeeType(); + + if (SourceType.isNull() || TargetType.isNull() || SourceType == TargetType) + return nullptr; + + // Region associated with the current cast expression. + const MemRegion *M = N->getSVal(CastE).getAsRegion(); + if (!M) + return nullptr; + + // Check if target region was marked as problematic previously. + if (!BR.isInteresting(M)) + return nullptr; + + SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); + + OS << "Casting from '" << SourceType.getAsString() << "' to '" + << TargetType.getAsString() << "' here"; + + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), + /*addPosRange=*/true); +} + +void ento::registerCXXArrayDeleteChecker(CheckerManager &mgr) { + mgr.registerChecker<CXXArrayDeleteChecker>(); +} + +bool ento::shouldRegisterCXXArrayDeleteChecker(const CheckerManager &mgr) { + return true; +} + +void ento::registerDeleteWithNonVirtualDtorChecker(CheckerManager &mgr) { + mgr.registerChecker<DeleteWithNonVirtualDtorChecker>(); +} + +bool ento::shouldRegisterDeleteWithNonVirtualDtorChecker( + 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 3e46e2372516..f2e1f69c32cf 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp @@ -73,7 +73,7 @@ public: CK_NumCheckKinds }; - DefaultBool ChecksEnabled[CK_NumCheckKinds]; + bool ChecksEnabled[CK_NumCheckKinds] = {false}; // The original core.CallAndMessage checker name. This should rather be an // array, as seen in MallocChecker and CStringChecker. CheckerNameRef OriginalName; @@ -123,11 +123,10 @@ private: void LazyInit_BT(const char *desc, std::unique_ptr<BugType> &BT) const { if (!BT) - BT.reset(new BuiltinBug(OriginalName, desc)); + BT.reset(new BugType(OriginalName, desc)); } - bool uninitRefOrPointer(CheckerContext &C, const SVal &V, - SourceRange ArgRange, const Expr *ArgEx, - std::unique_ptr<BugType> &BT, + bool uninitRefOrPointer(CheckerContext &C, SVal V, SourceRange ArgRange, + const Expr *ArgEx, std::unique_ptr<BugType> &BT, const ParmVarDecl *ParamDecl, const char *BD, int ArgumentNumber) const; }; @@ -185,7 +184,7 @@ static void describeUninitializedArgumentInCall(const CallEvent &Call, } bool CallAndMessageChecker::uninitRefOrPointer( - CheckerContext &C, const SVal &V, SourceRange ArgRange, const Expr *ArgEx, + CheckerContext &C, SVal V, SourceRange ArgRange, const Expr *ArgEx, std::unique_ptr<BugType> &BT, const ParmVarDecl *ParamDecl, const char *BD, int ArgumentNumber) const { @@ -263,7 +262,7 @@ public: if (Find(FR)) return true; } else { - const SVal &V = StoreMgr.getBinding(store, loc::MemRegionVal(FR)); + SVal V = StoreMgr.getBinding(store, loc::MemRegionVal(FR)); if (V.isUndef()) return true; } @@ -379,7 +378,7 @@ ProgramStateRef CallAndMessageChecker::checkFunctionPointerCall( return nullptr; } if (!BT_call_undef) - BT_call_undef.reset(new BuiltinBug( + BT_call_undef.reset(new BugType( OriginalName, "Called function pointer is an uninitialized pointer value")); emitBadCall(BT_call_undef.get(), C, Callee); @@ -395,7 +394,7 @@ ProgramStateRef CallAndMessageChecker::checkFunctionPointerCall( return nullptr; } if (!BT_call_null) - BT_call_null.reset(new BuiltinBug( + BT_call_null.reset(new BugType( OriginalName, "Called function pointer is null (null dereference)")); emitBadCall(BT_call_null.get(), C, Callee); return nullptr; @@ -450,7 +449,7 @@ ProgramStateRef CallAndMessageChecker::checkCXXMethodCall( return nullptr; } if (!BT_cxx_call_undef) - BT_cxx_call_undef.reset(new BuiltinBug( + BT_cxx_call_undef.reset(new BugType( OriginalName, "Called C++ object pointer is uninitialized")); emitBadCall(BT_cxx_call_undef.get(), C, CC->getCXXThisExpr()); return nullptr; @@ -466,7 +465,7 @@ ProgramStateRef CallAndMessageChecker::checkCXXMethodCall( } if (!BT_cxx_call_null) BT_cxx_call_null.reset( - new BuiltinBug(OriginalName, "Called C++ object pointer is null")); + new BugType(OriginalName, "Called C++ object pointer is null")); emitBadCall(BT_cxx_call_null.get(), C, CC->getCXXThisExpr()); return nullptr; } @@ -495,13 +494,13 @@ CallAndMessageChecker::checkCXXDeallocation(const CXXDeallocatorCall *DC, return nullptr; if (!BT_cxx_delete_undef) BT_cxx_delete_undef.reset( - new BuiltinBug(OriginalName, "Uninitialized argument value")); + new BugType(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); + auto R = + std::make_unique<PathSensitiveBugReport>(*BT_cxx_delete_undef, Desc, N); bugreporter::trackExpressionValue(N, DE, *R); C.emitReport(std::move(R)); return nullptr; @@ -585,21 +584,21 @@ void CallAndMessageChecker::checkPreObjCMessage(const ObjCMethodCall &msg, switch (msg.getMessageKind()) { case OCM_Message: if (!BT_msg_undef) - BT_msg_undef.reset(new BuiltinBug(OriginalName, - "Receiver in message expression " - "is an uninitialized value")); + BT_msg_undef.reset(new BugType(OriginalName, + "Receiver in message expression " + "is an uninitialized value")); BT = BT_msg_undef.get(); break; case OCM_PropertyAccess: if (!BT_objc_prop_undef) - BT_objc_prop_undef.reset(new BuiltinBug( + BT_objc_prop_undef.reset(new BugType( 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( + BT_objc_subscript_undef.reset(new BugType( OriginalName, "Subscript access on an uninitialized object pointer")); BT = BT_objc_subscript_undef.get(); @@ -634,8 +633,8 @@ void CallAndMessageChecker::emitNilReceiverBug(CheckerContext &C, } if (!BT_msg_ret) - BT_msg_ret.reset(new BuiltinBug(OriginalName, - "Receiver in message expression is 'nil'")); + BT_msg_ret.reset( + new BugType(OriginalName, "Receiver in message expression is 'nil'")); const ObjCMessageExpr *ME = msg.getOriginExpr(); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp index 2d2e14de3f2b..a50772f881f7 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp @@ -24,7 +24,7 @@ using namespace ento; namespace { class CastSizeChecker : public Checker< check::PreStmt<CastExpr> > { - mutable std::unique_ptr<BuiltinBug> BT; + const BugType BT{this, "Cast region with wrong size."}; public: void checkPreStmt(const CastExpr *CE, CheckerContext &C) const; @@ -131,12 +131,10 @@ void CastSizeChecker::checkPreStmt(const CastExpr *CE,CheckerContext &C) const { return; if (ExplodedNode *errorNode = C.generateErrorNode()) { - if (!BT) - BT.reset(new BuiltinBug(this, "Cast region with wrong size.", - "Cast a region whose size is not a multiple" - " of the destination type size.")); - auto R = std::make_unique<PathSensitiveBugReport>(*BT, BT->getDescription(), - errorNode); + constexpr llvm::StringLiteral Msg = + "Cast a region whose size is not a multiple of the destination type " + "size."; + auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, errorNode); R->addRange(CE->getSourceRange()); C.emitReport(std::move(R)); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp index 131c1345af99..f02d20d45678 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -20,10 +20,11 @@ #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" -#include "llvm/ADT/Optional.h" +#include <optional> #include <utility> using namespace clang; @@ -107,7 +108,7 @@ static const NoteTag *getNoteTag(CheckerContext &C, bool CastSucceeds, bool IsKnownCast) { std::string CastToName = CastInfo ? CastInfo->to()->getAsCXXRecordDecl()->getNameAsString() - : CastToTy->getPointeeCXXRecordDecl()->getNameAsString(); + : CastToTy.getAsString(); Object = Object->IgnoreParenImpCasts(); return C.getNoteTag( @@ -162,9 +163,9 @@ static const NoteTag *getNoteTag(CheckerContext &C, bool First = true; for (QualType CastToTy: CastToTyVec) { std::string CastToName = - CastToTy->getAsCXXRecordDecl() ? - CastToTy->getAsCXXRecordDecl()->getNameAsString() : - CastToTy->getPointeeCXXRecordDecl()->getNameAsString(); + CastToTy->getAsCXXRecordDecl() + ? CastToTy->getAsCXXRecordDecl()->getNameAsString() + : CastToTy.getAsString(); Out << ' ' << ((CastToTyVec.size() == 1) ? "not" : (First ? "neither" : "nor")) << " a '" << CastToName << '\''; @@ -249,7 +250,7 @@ static void addCastTransition(const CallEvent &Call, DefinedOrUnknownSVal DV, CastSucceeds); SVal V = CastSucceeds ? C.getSValBuilder().evalCast(DV, CastToTy, CastFromTy) - : C.getSValBuilder().makeNull(); + : C.getSValBuilder().makeNullWithType(CastToTy); C.addTransition( State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), V, false), getNoteTag(C, CastInfo, CastToTy, Object, CastSucceeds, IsKnownCast)); @@ -358,7 +359,9 @@ static void evalNullParamNullReturn(const CallEvent &Call, if (ProgramStateRef State = C.getState()->assume(DV, false)) C.addTransition(State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), - C.getSValBuilder().makeNull(), false), + C.getSValBuilder().makeNullWithType( + Call.getOriginExpr()->getType()), + false), C.getNoteTag("Assuming null pointer is passed into cast", /*IsPrunable=*/true)); } @@ -469,7 +472,7 @@ bool CastValueChecker::evalCall(const CallEvent &Call, const CastCheck &Check = Lookup->first; CallKind Kind = Lookup->second; - Optional<DefinedOrUnknownSVal> DV; + std::optional<DefinedOrUnknownSVal> DV; switch (Kind) { case CallKind::Function: { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp index 78b3c209ad6b..978bc0bb082f 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp @@ -45,6 +45,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" #include "llvm/Support/raw_ostream.h" +#include <optional> using namespace clang; using namespace ento; @@ -98,18 +99,23 @@ class ObjCDeallocChecker check::PointerEscape, check::PreStmt<ReturnStmt>> { - mutable IdentifierInfo *NSObjectII, *SenTestCaseII, *XCTestCaseII, - *Block_releaseII, *CIFilterII; + mutable const IdentifierInfo *NSObjectII = nullptr; + mutable const IdentifierInfo *SenTestCaseII = nullptr; + mutable const IdentifierInfo *XCTestCaseII = nullptr; + mutable const IdentifierInfo *Block_releaseII = nullptr; + mutable const IdentifierInfo *CIFilterII = nullptr; - mutable Selector DeallocSel, ReleaseSel; + mutable Selector DeallocSel; + mutable Selector ReleaseSel; - std::unique_ptr<BugType> MissingReleaseBugType; - std::unique_ptr<BugType> ExtraReleaseBugType; - std::unique_ptr<BugType> MistakenDeallocBugType; + const BugType MissingReleaseBugType{this, "Missing ivar release (leak)", + categories::MemoryRefCount}; + const BugType ExtraReleaseBugType{this, "Extra ivar release", + categories::MemoryRefCount}; + const BugType MistakenDeallocBugType{this, "Mistaken dealloc", + categories::MemoryRefCount}; public: - ObjCDeallocChecker(); - void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr, BugReporter &BR) const; void checkBeginFunction(CheckerContext &Ctx) const; @@ -282,11 +288,11 @@ void ObjCDeallocChecker::checkBeginFunction( continue; SVal LVal = State->getLValue(PropImpl->getPropertyIvarDecl(), SelfVal); - Optional<Loc> LValLoc = LVal.getAs<Loc>(); + std::optional<Loc> LValLoc = LVal.getAs<Loc>(); if (!LValLoc) continue; - SVal InitialVal = State->getSVal(LValLoc.getValue()); + SVal InitialVal = State->getSVal(*LValLoc); SymbolRef Symbol = InitialVal.getAsSymbol(); if (!Symbol || !isa<SymbolRegionValue>(Symbol)) continue; @@ -320,7 +326,9 @@ ObjCDeallocChecker::getInstanceSymbolFromIvarSymbol(SymbolRef IvarSym) const { if (!IvarRegion) return nullptr; - return IvarRegion->getSymbolicBase()->getSymbol(); + const SymbolicRegion *SR = IvarRegion->getSymbolicBase(); + assert(SR && "Symbolic base should not be nullptr"); + return SR->getSymbol(); } /// If we are in -dealloc or -dealloc is on the stack, handle the call if it is @@ -576,7 +584,7 @@ void ObjCDeallocChecker::diagnoseMissingReleases(CheckerContext &C) const { OS << " by a synthesized property but not released" " before '[super dealloc]'"; - auto BR = std::make_unique<PathSensitiveBugReport>(*MissingReleaseBugType, + auto BR = std::make_unique<PathSensitiveBugReport>(MissingReleaseBugType, OS.str(), ErrNode); C.emitReport(std::move(BR)); } @@ -698,7 +706,7 @@ bool ObjCDeallocChecker::diagnoseExtraRelease(SymbolRef ReleasedValue, OS << " property but was released in 'dealloc'"; } - auto BR = std::make_unique<PathSensitiveBugReport>(*ExtraReleaseBugType, + auto BR = std::make_unique<PathSensitiveBugReport>(ExtraReleaseBugType, OS.str(), ErrNode); BR->addRange(M.getOriginExpr()->getSourceRange()); @@ -740,7 +748,7 @@ bool ObjCDeallocChecker::diagnoseMistakenDealloc(SymbolRef DeallocedValue, OS << "'" << *PropImpl->getPropertyIvarDecl() << "' should be released rather than deallocated"; - auto BR = std::make_unique<PathSensitiveBugReport>(*MistakenDeallocBugType, + auto BR = std::make_unique<PathSensitiveBugReport>(MistakenDeallocBugType, OS.str(), ErrNode); BR->addRange(M.getOriginExpr()->getSourceRange()); @@ -749,23 +757,6 @@ bool ObjCDeallocChecker::diagnoseMistakenDealloc(SymbolRef DeallocedValue, return true; } -ObjCDeallocChecker::ObjCDeallocChecker() - : NSObjectII(nullptr), SenTestCaseII(nullptr), XCTestCaseII(nullptr), - CIFilterII(nullptr) { - - MissingReleaseBugType.reset( - new BugType(this, "Missing ivar release (leak)", - categories::MemoryRefCount)); - - ExtraReleaseBugType.reset( - new BugType(this, "Extra ivar release", - categories::MemoryRefCount)); - - MistakenDeallocBugType.reset( - new BugType(this, "Mistaken dealloc", - categories::MemoryRefCount)); -} - void ObjCDeallocChecker::initIdentifierInfoAndSelectors( ASTContext &Ctx) const { if (NSObjectII) @@ -817,8 +808,8 @@ const ObjCPropertyDecl *ObjCDeallocChecker::findShadowedPropertyDecl( IdentifierInfo *ID = PropDecl->getIdentifier(); DeclContext::lookup_result R = CatDecl->getClassInterface()->lookup(ID); - for (DeclContext::lookup_iterator I = R.begin(), E = R.end(); I != E; ++I) { - auto *ShadowedPropDecl = dyn_cast<ObjCPropertyDecl>(*I); + for (const NamedDecl *D : R) { + auto *ShadowedPropDecl = dyn_cast<ObjCPropertyDecl>(D); if (!ShadowedPropDecl) continue; @@ -953,11 +944,11 @@ ObjCDeallocChecker::getValueReleasedByNillingOut(const ObjCMethodCall &M, ProgramStateRef State = C.getState(); SVal LVal = State->getLValue(PropIvarDecl, ReceiverVal); - Optional<Loc> LValLoc = LVal.getAs<Loc>(); + std::optional<Loc> LValLoc = LVal.getAs<Loc>(); if (!LValLoc) return nullptr; - SVal CurrentValInIvar = State->getSVal(LValLoc.getValue()); + SVal CurrentValInIvar = State->getSVal(*LValLoc); return CurrentValInIvar.getAsSymbol(); } @@ -1004,7 +995,7 @@ bool ObjCDeallocChecker::instanceDeallocIsOnStack(const CheckerContext &C, return false; } -/// Returns true if the ID is a class in which which is known to have +/// Returns true if the ID is a class in which is known to have /// a separate teardown lifecycle. In this case, -dealloc warnings /// about missing releases should be suppressed. bool ObjCDeallocChecker::classHasSeparateTeardown( @@ -1042,8 +1033,8 @@ bool ObjCDeallocChecker::isReleasedByCIFilterDealloc( StringRef IvarName = PropImpl->getPropertyIvarDecl()->getName(); const char *ReleasePrefix = "input"; - if (!(PropName.startswith(ReleasePrefix) || - IvarName.startswith(ReleasePrefix))) { + if (!(PropName.starts_with(ReleasePrefix) || + IvarName.starts_with(ReleasePrefix))) { return false; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp index 175dfcef0df4..c8fe5c2ccf38 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// // -// This file defines a CheckObjCInstMethSignature, a flow-insenstive check +// This file defines a CheckObjCInstMethSignature, a flow-insensitive check // that determines if an Objective-C class interface incorrectly redefines // the method signature in a subclass. // @@ -55,13 +55,11 @@ static void CompareReturnTypes(const ObjCMethodDecl *MethDerived, << *MethAncestor->getClassInterface() << "', defines the instance method '"; MethDerived->getSelector().print(os); - os << "' whose return type is '" - << ResDerived.getAsString() + os << "' whose return type is '" << ResDerived << "'. A method with the same name (same selector) is also defined in " "class '" - << *MethAncestor->getClassInterface() - << "' and has a return type of '" - << ResAncestor.getAsString() + << *MethAncestor->getClassInterface() << "' and has a return type of '" + << ResAncestor << "'. These two types are incompatible, and may result in undefined " "behavior for clients of these classes."; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp index d06c87631bfb..17af1aebd6d2 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp @@ -27,7 +27,6 @@ using namespace ento; static bool isArc4RandomAvailable(const ASTContext &Ctx) { const llvm::Triple &T = Ctx.getTargetInfo().getTriple(); return T.getVendor() == llvm::Triple::Apple || - T.getOS() == llvm::Triple::CloudABI || T.isOSFreeBSD() || T.isOSNetBSD() || T.isOSOpenBSD() || @@ -36,20 +35,20 @@ static bool isArc4RandomAvailable(const ASTContext &Ctx) { namespace { struct ChecksFilter { - DefaultBool check_bcmp; - DefaultBool check_bcopy; - DefaultBool check_bzero; - DefaultBool check_gets; - DefaultBool check_getpw; - DefaultBool check_mktemp; - DefaultBool check_mkstemp; - DefaultBool check_strcpy; - DefaultBool check_DeprecatedOrUnsafeBufferHandling; - DefaultBool check_rand; - DefaultBool check_vfork; - DefaultBool check_FloatLoopCounter; - DefaultBool check_UncheckedReturn; - DefaultBool check_decodeValueOfObjCType; + bool check_bcmp = false; + bool check_bcopy = false; + bool check_bzero = false; + bool check_gets = false; + bool check_getpw = false; + bool check_mktemp = false; + bool check_mkstemp = false; + bool check_strcpy = false; + bool check_DeprecatedOrUnsafeBufferHandling = false; + bool check_rand = false; + bool check_vfork = false; + bool check_FloatLoopCounter = false; + bool check_UncheckedReturn = false; + bool check_decodeValueOfObjCType = false; CheckerNameRef checkName_bcmp; CheckerNameRef checkName_bcopy; @@ -141,42 +140,42 @@ void WalkAST::VisitCallExpr(CallExpr *CE) { if (!II) // if no identifier, not a simple C function return; StringRef Name = II->getName(); - if (Name.startswith("__builtin_")) - Name = Name.substr(10); + Name.consume_front("__builtin_"); // Set the evaluation function by switching on the callee name. - FnCheck evalFunction = llvm::StringSwitch<FnCheck>(Name) - .Case("bcmp", &WalkAST::checkCall_bcmp) - .Case("bcopy", &WalkAST::checkCall_bcopy) - .Case("bzero", &WalkAST::checkCall_bzero) - .Case("gets", &WalkAST::checkCall_gets) - .Case("getpw", &WalkAST::checkCall_getpw) - .Case("mktemp", &WalkAST::checkCall_mktemp) - .Case("mkstemp", &WalkAST::checkCall_mkstemp) - .Case("mkdtemp", &WalkAST::checkCall_mkstemp) - .Case("mkstemps", &WalkAST::checkCall_mkstemp) - .Cases("strcpy", "__strcpy_chk", &WalkAST::checkCall_strcpy) - .Cases("strcat", "__strcat_chk", &WalkAST::checkCall_strcat) - .Cases("sprintf", "vsprintf", "scanf", "wscanf", "fscanf", "fwscanf", - "vscanf", "vwscanf", "vfscanf", "vfwscanf", - &WalkAST::checkDeprecatedOrUnsafeBufferHandling) - .Cases("sscanf", "swscanf", "vsscanf", "vswscanf", "swprintf", - "snprintf", "vswprintf", "vsnprintf", "memcpy", "memmove", - &WalkAST::checkDeprecatedOrUnsafeBufferHandling) - .Cases("strncpy", "strncat", "memset", - &WalkAST::checkDeprecatedOrUnsafeBufferHandling) - .Case("drand48", &WalkAST::checkCall_rand) - .Case("erand48", &WalkAST::checkCall_rand) - .Case("jrand48", &WalkAST::checkCall_rand) - .Case("lrand48", &WalkAST::checkCall_rand) - .Case("mrand48", &WalkAST::checkCall_rand) - .Case("nrand48", &WalkAST::checkCall_rand) - .Case("lcong48", &WalkAST::checkCall_rand) - .Case("rand", &WalkAST::checkCall_rand) - .Case("rand_r", &WalkAST::checkCall_rand) - .Case("random", &WalkAST::checkCall_random) - .Case("vfork", &WalkAST::checkCall_vfork) - .Default(nullptr); + FnCheck evalFunction = + llvm::StringSwitch<FnCheck>(Name) + .Case("bcmp", &WalkAST::checkCall_bcmp) + .Case("bcopy", &WalkAST::checkCall_bcopy) + .Case("bzero", &WalkAST::checkCall_bzero) + .Case("gets", &WalkAST::checkCall_gets) + .Case("getpw", &WalkAST::checkCall_getpw) + .Case("mktemp", &WalkAST::checkCall_mktemp) + .Case("mkstemp", &WalkAST::checkCall_mkstemp) + .Case("mkdtemp", &WalkAST::checkCall_mkstemp) + .Case("mkstemps", &WalkAST::checkCall_mkstemp) + .Cases("strcpy", "__strcpy_chk", &WalkAST::checkCall_strcpy) + .Cases("strcat", "__strcat_chk", &WalkAST::checkCall_strcat) + .Cases("sprintf", "vsprintf", "scanf", "wscanf", "fscanf", "fwscanf", + "vscanf", "vwscanf", "vfscanf", "vfwscanf", + &WalkAST::checkDeprecatedOrUnsafeBufferHandling) + .Cases("sscanf", "swscanf", "vsscanf", "vswscanf", "swprintf", + "snprintf", "vswprintf", "vsnprintf", "memcpy", "memmove", + &WalkAST::checkDeprecatedOrUnsafeBufferHandling) + .Cases("strncpy", "strncat", "memset", "fprintf", + &WalkAST::checkDeprecatedOrUnsafeBufferHandling) + .Case("drand48", &WalkAST::checkCall_rand) + .Case("erand48", &WalkAST::checkCall_rand) + .Case("jrand48", &WalkAST::checkCall_rand) + .Case("lrand48", &WalkAST::checkCall_rand) + .Case("mrand48", &WalkAST::checkCall_rand) + .Case("nrand48", &WalkAST::checkCall_rand) + .Case("lcong48", &WalkAST::checkCall_rand) + .Case("rand", &WalkAST::checkCall_rand) + .Case("rand_r", &WalkAST::checkCall_rand) + .Case("random", &WalkAST::checkCall_random) + .Case("vfork", &WalkAST::checkCall_vfork) + .Default(nullptr); // If the callee isn't defined, it is not of security concern. // Check and evaluate the call. @@ -219,7 +218,6 @@ void WalkAST::VisitForStmt(ForStmt *FS) { //===----------------------------------------------------------------------===// // Check: floating point variable used as loop counter. -// Originally: <rdar://problem/6336718> // Implements: CERT security coding advisory FLP-30. //===----------------------------------------------------------------------===// @@ -325,7 +323,7 @@ void WalkAST::checkLoopConditionForFloat(const ForStmt *FS) { llvm::raw_svector_ostream os(sbuf); os << "Variable '" << drCond->getDecl()->getName() - << "' with floating point type '" << drCond->getType().getAsString() + << "' with floating point type '" << drCond->getType() << "' should not be used as a loop counter"; ranges.push_back(drCond->getSourceRange()); @@ -467,8 +465,8 @@ void WalkAST::checkCall_bzero(const CallExpr *CE, const FunctionDecl *FD) { //===----------------------------------------------------------------------===// -// Check: Any use of 'gets' is insecure. -// Originally: <rdar://problem/6335715> +// Check: Any use of 'gets' is insecure. Most man pages literally says this. +// // Implements (part of): 300-BSI (buildsecurityin.us-cert.gov) // CWE-242: Use of Inherently Dangerous Function //===----------------------------------------------------------------------===// @@ -739,10 +737,10 @@ void WalkAST::checkCall_strcat(const CallExpr *CE, const FunctionDecl *FD) { // Check: Any use of 'sprintf', 'vsprintf', 'scanf', 'wscanf', 'fscanf', // 'fwscanf', 'vscanf', 'vwscanf', 'vfscanf', 'vfwscanf', 'sscanf', // 'swscanf', 'vsscanf', 'vswscanf', 'swprintf', 'snprintf', 'vswprintf', -// 'vsnprintf', 'memcpy', 'memmove', 'strncpy', 'strncat', 'memset' -// is deprecated since C11. +// 'vsnprintf', 'memcpy', 'memmove', 'strncpy', 'strncat', 'memset', +// 'fprintf' is deprecated since C11. // -// Use of 'sprintf', 'vsprintf', 'scanf', 'wscanf','fscanf', +// Use of 'sprintf', 'fprintf', 'vsprintf', 'scanf', 'wscanf', 'fscanf', // 'fwscanf', 'vscanf', 'vwscanf', 'vfscanf', 'vfwscanf', 'sscanf', // 'swscanf', 'vsscanf', 'vswscanf' without buffer limitations // is insecure. @@ -764,14 +762,14 @@ void WalkAST::checkDeprecatedOrUnsafeBufferHandling(const CallExpr *CE, enum { DEPR_ONLY = -1, UNKNOWN_CALL = -2 }; StringRef Name = FD->getIdentifier()->getName(); - if (Name.startswith("__builtin_")) - Name = Name.substr(10); + Name.consume_front("__builtin_"); int ArgIndex = llvm::StringSwitch<int>(Name) .Cases("scanf", "wscanf", "vscanf", "vwscanf", 0) - .Cases("sprintf", "vsprintf", "fscanf", "fwscanf", "vfscanf", - "vfwscanf", "sscanf", "swscanf", "vsscanf", "vswscanf", 1) + .Cases("fscanf", "fwscanf", "vfscanf", "vfwscanf", "sscanf", + "swscanf", "vsscanf", "vswscanf", 1) + .Cases("sprintf", "vsprintf", "fprintf", 1) .Cases("swprintf", "snprintf", "vswprintf", "vsnprintf", "memcpy", "memmove", "memset", "strncpy", "strncat", DEPR_ONLY) .Default(UNKNOWN_CALL); @@ -785,9 +783,8 @@ void WalkAST::checkDeprecatedOrUnsafeBufferHandling(const CallExpr *CE, // real flow analysis. auto FormatString = dyn_cast<StringLiteral>(CE->getArg(ArgIndex)->IgnoreParenImpCasts()); - if (FormatString && - FormatString->getString().find("%s") == StringRef::npos && - FormatString->getString().find("%[") == StringRef::npos) + if (FormatString && !FormatString->getString().contains("%s") && + !FormatString->getString().contains("%[")) BoundsProvided = true; } @@ -848,8 +845,13 @@ bool WalkAST::checkCall_strCommon(const CallExpr *CE, const FunctionDecl *FD) { } //===----------------------------------------------------------------------===// -// Check: Linear congruent random number generators should not be used -// Originally: <rdar://problem/63371000> +// Check: Linear congruent random number generators should not be used, +// i.e. rand(), random(). +// +// E. Bach, "Efficient prediction of Marsaglia-Zaman random number generators," +// in IEEE Transactions on Information Theory, vol. 44, no. 3, pp. 1253-1257, +// May 1998, https://doi.org/10.1109/18.669305 +// // CWE-338: Use of cryptographically weak prng //===----------------------------------------------------------------------===// @@ -891,11 +893,7 @@ void WalkAST::checkCall_rand(const CallExpr *CE, const FunctionDecl *FD) { CE->getCallee()->getSourceRange()); } -//===----------------------------------------------------------------------===// -// Check: 'random' should not be used -// Originally: <rdar://problem/63371000> -//===----------------------------------------------------------------------===// - +// See justification for rand(). void WalkAST::checkCall_random(const CallExpr *CE, const FunctionDecl *FD) { if (!CheckRand || !filter.check_rand) return; @@ -975,6 +973,8 @@ void WalkAST::checkMsg_decodeValueOfObjCType(const ObjCMessageExpr *ME) { if (VT < VersionTuple(11, 0)) return; break; + case llvm::Triple::XROS: + break; default: return; } @@ -991,8 +991,18 @@ void WalkAST::checkMsg_decodeValueOfObjCType(const ObjCMessageExpr *ME) { } //===----------------------------------------------------------------------===// -// Check: Should check whether privileges are dropped successfully. -// Originally: <rdar://problem/6337132> +// Check: The caller should always verify that the privileges +// were dropped successfully. +// +// Some library functions, like setuid() and setgid(), should always be used +// with a check of the return value to verify that the function completed +// successfully. If the drop fails, the software will continue to run +// with the raised privileges, which might provide additional access +// to unprivileged users. +// +// (Note that this check predates __attribute__((warn_unused_result)). +// Do we still need it now that we have a compiler warning for this? +// Are these standard functions already annotated this way?) //===----------------------------------------------------------------------===// void WalkAST::checkUncheckedReturnValue(CallExpr *CE) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp index fd53c04f4bbf..be7be15022d3 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp @@ -14,6 +14,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" @@ -40,9 +41,9 @@ bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; } // bug<--foo()-- JAIL_ENTERED<--foo()-- class ChrootChecker : public Checker<eval::Call, check::PreCall> { // This bug refers to possibly break out of a chroot() jail. - mutable std::unique_ptr<BuiltinBug> BT_BreakJail; + const BugType BT_BreakJail{this, "Break out of jail"}; - const CallDescription Chroot{"chroot", 1}, Chdir{"chdir", 1}; + const CallDescription Chroot{{"chroot"}, 1}, Chdir{{"chdir"}, 1}; public: ChrootChecker() {} @@ -63,11 +64,11 @@ private: } // end anonymous namespace bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { - if (Call.isCalled(Chroot)) { + if (Chroot.matches(Call)) { evalChroot(Call, C); return true; } - if (Call.isCalled(Chdir)) { + if (Chdir.matches(Call)) { evalChdir(Call, C); return true; } @@ -115,7 +116,7 @@ void ChrootChecker::evalChdir(const CallEvent &Call, CheckerContext &C) const { void ChrootChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { // Ignore chroot and chdir. - if (Call.isCalled(Chroot) || Call.isCalled(Chdir)) + if (matchesAny(Call, Chroot, Chdir)) return; // If jail state is ROOT_CHANGED, generate BugReport. @@ -123,12 +124,10 @@ void ChrootChecker::checkPreCall(const CallEvent &Call, if (k) if (isRootChanged((intptr_t) *k)) if (ExplodedNode *N = C.generateNonFatalErrorNode()) { - if (!BT_BreakJail) - BT_BreakJail.reset(new BuiltinBug( - this, "Break out of jail", "No call of chdir(\"/\") immediately " - "after chroot")); - C.emitReport(std::make_unique<PathSensitiveBugReport>( - *BT_BreakJail, BT_BreakJail->getDescription(), N)); + constexpr llvm::StringLiteral Msg = + "No call of chdir(\"/\") immediately after chroot"; + C.emitReport( + std::make_unique<PathSensitiveBugReport>(BT_BreakJail, Msg, N)); } } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp index 7968aed85e1b..6692a45a09f7 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp @@ -30,12 +30,13 @@ class CloneChecker public: // Checker options. int MinComplexity; - bool ReportNormalClones; + bool ReportNormalClones = false; StringRef IgnoredFilesPattern; private: mutable CloneDetector Detector; - mutable std::unique_ptr<BugType> BT_Exact, BT_Suspicious; + const BugType BT_Exact{this, "Exact code clone", "Code clone"}; + const BugType BT_Suspicious{this, "Suspicious code clone", "Code clone"}; public: void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr, @@ -107,15 +108,11 @@ static PathDiagnosticLocation makeLocation(const StmtSequence &S, void CloneChecker::reportClones( BugReporter &BR, AnalysisManager &Mgr, std::vector<CloneDetector::CloneGroup> &CloneGroups) const { - - if (!BT_Exact) - BT_Exact.reset(new BugType(this, "Exact code clone", "Code clone")); - for (const CloneDetector::CloneGroup &Group : CloneGroups) { // We group the clones by printing the first as a warning and all others // as a note. auto R = std::make_unique<BasicBugReport>( - *BT_Exact, "Duplicate code detected", makeLocation(Group.front(), Mgr)); + BT_Exact, "Duplicate code detected", makeLocation(Group.front(), Mgr)); R->addRange(Group.front().getSourceRange()); for (unsigned i = 1; i < Group.size(); ++i) @@ -154,10 +151,6 @@ void CloneChecker::reportSuspiciousClones( } } - if (!BT_Suspicious) - BT_Suspicious.reset( - new BugType(this, "Suspicious code clone", "Code clone")); - ASTContext &ACtx = BR.getContext(); SourceManager &SM = ACtx.getSourceManager(); AnalysisDeclContext *ADC = @@ -170,7 +163,7 @@ void CloneChecker::reportSuspiciousClones( // Think how to perform more accurate suggestions? auto R = std::make_unique<BasicBugReport>( - *BT_Suspicious, + BT_Suspicious, "Potential copy-paste error; did you really mean to use '" + Pair.FirstCloneInfo.Variable->getNameAsString() + "' here?", PathDiagnosticLocation::createBegin(Pair.FirstCloneInfo.Mention, SM, diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ContainerModeling.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ContainerModeling.cpp index 1a7f0d5ab74c..65a2ec4076fd 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ContainerModeling.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ContainerModeling.cpp @@ -10,11 +10,12 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/DeclTemplate.h" #include "clang/Driver/DriverDiagnostic.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" @@ -71,42 +72,27 @@ public: 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}, + {{{"clear"}, 0}, &ContainerModeling::handleClear}, + {{{"assign"}, 2}, &ContainerModeling::handleAssign}, + {{{"push_back"}, 1}, &ContainerModeling::handlePushBack}, + {{{"emplace_back"}, 1}, &ContainerModeling::handlePushBack}, + {{{"pop_back"}, 0}, &ContainerModeling::handlePopBack}, + {{{"push_front"}, 1}, &ContainerModeling::handlePushFront}, + {{{"emplace_front"}, 1}, &ContainerModeling::handlePushFront}, + {{{"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}, + {{{"insert"}, 2}, &ContainerModeling::handleInsert}, + {{{"emplace"}, 2}, &ContainerModeling::handleInsert}, + {{{"erase"}, 1}, &ContainerModeling::handleErase}, + {{{"erase_after"}, 1}, &ContainerModeling::handleEraseAfter}, }; - + CallDescriptionMap<TwoItParamFn> TwoIterParamFunctions = { - {{0, "erase", 2}, - &ContainerModeling::handleErase}, - {{0, "erase_after", 2}, - &ContainerModeling::handleEraseAfter}, + {{{"erase"}, 2}, &ContainerModeling::handleErase}, + {{{"erase_after"}, 2}, &ContainerModeling::handleEraseAfter}, }; - }; bool isBeginCall(const FunctionDecl *Func); @@ -241,7 +227,7 @@ 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)) { @@ -763,14 +749,14 @@ bool isBeginCall(const FunctionDecl *Func) { const auto *IdInfo = Func->getIdentifier(); if (!IdInfo) return false; - return IdInfo->getName().endswith_insensitive("begin"); + return IdInfo->getName().ends_with_insensitive("begin"); } bool isEndCall(const FunctionDecl *Func) { const auto *IdInfo = Func->getIdentifier(); if (!IdInfo) return false; - return IdInfo->getName().endswith_insensitive("end"); + return IdInfo->getName().ends_with_insensitive("end"); } const CXXRecordDecl *getCXXRecordDecl(ProgramStateRef State, @@ -1035,7 +1021,7 @@ SymbolRef rebaseSymbol(ProgramStateRef State, SValBuilder &SVB, SymbolRef NewSym) { auto &SymMgr = SVB.getSymbolManager(); auto Diff = SVB.evalBinOpNN(State, BO_Sub, nonloc::SymbolVal(OrigExpr), - nonloc::SymbolVal(OldExpr), + nonloc::SymbolVal(OldExpr), SymMgr.getType(OrigExpr)); const auto DiffInt = Diff.getAs<nonloc::ConcreteInt>(); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp index 4216a6883119..eca8d3cc0722 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp @@ -42,22 +42,22 @@ public: void checkPreStmt(const ImplicitCastExpr *Cast, CheckerContext &C) const; private: - mutable std::unique_ptr<BuiltinBug> BT; + const BugType BT{this, "Conversion"}; bool isLossOfPrecision(const ImplicitCastExpr *Cast, QualType DestType, CheckerContext &C) const; bool isLossOfSign(const ImplicitCastExpr *Cast, CheckerContext &C) const; - void reportBug(ExplodedNode *N, CheckerContext &C, const char Msg[]) const; + void reportBug(ExplodedNode *N, const Expr *E, CheckerContext &C, + const char Msg[]) const; }; } void ConversionChecker::checkPreStmt(const ImplicitCastExpr *Cast, CheckerContext &C) const { - // TODO: For now we only warn about DeclRefExpr, to avoid noise. Warn for - // calculations also. - if (!isa<DeclRefExpr>(Cast->IgnoreParenImpCasts())) + // Don't warn for implicit conversions to bool + if (Cast->getType()->isBooleanType()) return; // Don't warn for loss of sign/precision in macros. @@ -69,6 +69,9 @@ void ConversionChecker::checkPreStmt(const ImplicitCastExpr *Cast, const Stmt *Parent = PM.getParent(Cast); if (!Parent) return; + // Dont warn if this is part of an explicit cast + if (isa<ExplicitCastExpr>(Parent)) + return; bool LossOfSign = false; bool LossOfPrecision = false; @@ -77,8 +80,10 @@ void ConversionChecker::checkPreStmt(const ImplicitCastExpr *Cast, if (const auto *B = dyn_cast<BinaryOperator>(Parent)) { BinaryOperator::Opcode Opc = B->getOpcode(); if (Opc == BO_Assign) { - LossOfSign = isLossOfSign(Cast, C); - LossOfPrecision = isLossOfPrecision(Cast, Cast->getType(), C); + if (!Cast->IgnoreParenImpCasts()->isEvaluatable(C.getASTContext())) { + LossOfSign = isLossOfSign(Cast, C); + LossOfPrecision = isLossOfPrecision(Cast, Cast->getType(), C); + } } else if (Opc == BO_AddAssign || Opc == BO_SubAssign) { // No loss of sign. LossOfPrecision = isLossOfPrecision(Cast, B->getLHS()->getType(), C); @@ -97,7 +102,12 @@ void ConversionChecker::checkPreStmt(const ImplicitCastExpr *Cast, } else if (B->isRelationalOp() || B->isMultiplicativeOp()) { LossOfSign = isLossOfSign(Cast, C); } - } else if (isa<DeclStmt>(Parent)) { + } else if (isa<DeclStmt, ReturnStmt>(Parent)) { + if (!Cast->IgnoreParenImpCasts()->isEvaluatable(C.getASTContext())) { + LossOfSign = isLossOfSign(Cast, C); + LossOfPrecision = isLossOfPrecision(Cast, Cast->getType(), C); + } + } else { LossOfSign = isLossOfSign(Cast, C); LossOfPrecision = isLossOfPrecision(Cast, Cast->getType(), C); } @@ -108,20 +118,17 @@ void ConversionChecker::checkPreStmt(const ImplicitCastExpr *Cast, if (!N) return; if (LossOfSign) - reportBug(N, C, "Loss of sign in implicit conversion"); + reportBug(N, Cast, C, "Loss of sign in implicit conversion"); if (LossOfPrecision) - reportBug(N, C, "Loss of precision in implicit conversion"); + reportBug(N, Cast, C, "Loss of precision in implicit conversion"); } } -void ConversionChecker::reportBug(ExplodedNode *N, CheckerContext &C, - const char Msg[]) const { - if (!BT) - BT.reset( - new BuiltinBug(this, "Conversion", "Possible loss of sign/precision.")); - +void ConversionChecker::reportBug(ExplodedNode *N, const Expr *E, + CheckerContext &C, const char Msg[]) const { // Generate a report for this bug. - auto R = std::make_unique<PathSensitiveBugReport>(*BT, Msg, N); + auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N); + bugreporter::trackExpressionValue(N, E, *R); C.emitReport(std::move(R)); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp index 8070d869f678..86f446fc411c 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp @@ -38,17 +38,17 @@ public: llvm::DenseSet<const VarDecl *> &S; bool TraverseObjCAtFinallyStmt(ObjCAtFinallyStmt *S) { - SaveAndRestore<bool> inFinally(inEH, true); + SaveAndRestore inFinally(inEH, true); return ::RecursiveASTVisitor<EHCodeVisitor>::TraverseObjCAtFinallyStmt(S); } bool TraverseObjCAtCatchStmt(ObjCAtCatchStmt *S) { - SaveAndRestore<bool> inCatch(inEH, true); + SaveAndRestore inCatch(inEH, true); return ::RecursiveASTVisitor<EHCodeVisitor>::TraverseObjCAtCatchStmt(S); } bool TraverseCXXCatchStmt(CXXCatchStmt *S) { - SaveAndRestore<bool> inCatch(inEH, true); + SaveAndRestore inCatch(inEH, true); return TraverseStmt(S->getHandlerBlock()); } @@ -93,9 +93,9 @@ void ReachableCode::computeReachableBlocks() { if (isReachable) continue; isReachable = true; - for (CFGBlock::const_succ_iterator i = block->succ_begin(), - e = block->succ_end(); i != e; ++i) - if (const CFGBlock *succ = *i) + + for (const CFGBlock *succ : block->succs()) + if (succ) worklist.push_back(succ); } } @@ -103,15 +103,12 @@ void ReachableCode::computeReachableBlocks() { static const Expr * LookThroughTransitiveAssignmentsAndCommaOperators(const Expr *Ex) { while (Ex) { - const BinaryOperator *BO = - dyn_cast<BinaryOperator>(Ex->IgnoreParenCasts()); + Ex = Ex->IgnoreParenCasts(); + const BinaryOperator *BO = dyn_cast<BinaryOperator>(Ex); if (!BO) break; - if (BO->getOpcode() == BO_Assign) { - Ex = BO->getRHS(); - continue; - } - if (BO->getOpcode() == BO_Comma) { + BinaryOperatorKind Op = BO->getOpcode(); + if (Op == BO_Assign || Op == BO_Comma) { Ex = BO->getRHS(); continue; } @@ -186,7 +183,7 @@ public: // Files autogenerated by DriverKit IIG contain some dead stores that // we don't want to report. - if (Data.startswith("/* iig")) + if (Data.starts_with("/* iig")) return true; return false; @@ -243,7 +240,7 @@ public: case DeadIncrement: BugType = "Dead increment"; - LLVM_FALLTHROUGH; + [[fallthrough]]; case Standard: if (!BugType) BugType = "Dead assignment"; os << "Value stored to '" << *V << "' is never read"; @@ -334,8 +331,7 @@ public: // Special case: check for assigning null to a pointer. // This is a common form of defensive programming. const Expr *RHS = - LookThroughTransitiveAssignmentsAndCommaOperators(B->getRHS()); - RHS = RHS->IgnoreParenCasts(); + LookThroughTransitiveAssignmentsAndCommaOperators(B->getRHS()); QualType T = VD->getType(); if (T.isVolatileQualified()) @@ -418,8 +414,7 @@ public: if (isConstant(E)) return; - if (const DeclRefExpr *DRE = - dyn_cast<DeclRefExpr>(E->IgnoreParenCasts())) + if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) { // Special case: check for initialization from constant // variables. @@ -441,7 +436,7 @@ public: PathDiagnosticLocation Loc = PathDiagnosticLocation::create(V, BR.getSourceManager()); - Report(V, DeadInit, Loc, E->getSourceRange()); + Report(V, DeadInit, Loc, V->getInit()->getSourceRange()); } } } @@ -453,8 +448,9 @@ private: bool isConstant(const InitListExpr *Candidate) const { // We consider init list to be constant if each member of the list can be // interpreted as constant. - return llvm::all_of(Candidate->inits(), - [this](const Expr *Init) { return isConstant(Init); }); + return llvm::all_of(Candidate->inits(), [this](const Expr *Init) { + return isConstant(Init->IgnoreParenCasts()); + }); } /// Return true if the given expression can be interpreted as constant @@ -464,7 +460,7 @@ private: return true; // We should also allow defensive initialization of structs, i.e. { 0 } - if (const auto *ILE = dyn_cast<InitListExpr>(E->IgnoreParenCasts())) { + if (const auto *ILE = dyn_cast<InitListExpr>(E)) { return isConstant(ILE); } @@ -507,7 +503,7 @@ public: // Treat local variables captured by reference in C++ lambdas as escaped. void findLambdaReferenceCaptures(const LambdaExpr *LE) { const CXXRecordDecl *LambdaClass = LE->getLambdaClass(); - llvm::DenseMap<const VarDecl *, FieldDecl *> CaptureFields; + llvm::DenseMap<const ValueDecl *, FieldDecl *> CaptureFields; FieldDecl *ThisCaptureField; LambdaClass->getCaptureFields(CaptureFields, ThisCaptureField); @@ -515,14 +511,14 @@ public: if (!C.capturesVariable()) continue; - VarDecl *VD = C.getCapturedVar(); + ValueDecl *VD = C.getCapturedVar(); const FieldDecl *FD = CaptureFields[VD]; - if (!FD) + if (!FD || !isa<VarDecl>(VD)) continue; // If the capture field is a reference type, it is capture-by-reference. if (FD->getType()->isReferenceType()) - Escaped.insert(VD); + Escaped.insert(cast<VarDecl>(VD)); } } }; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp index 7cdd78b8adfb..04bbe85473c0 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp @@ -271,9 +271,8 @@ public: const Table &Config = mgr.options.Config; SmallVector<const Table::MapEntryTy *, 32> Keys; - for (Table::const_iterator I = Config.begin(), E = Config.end(); I != E; - ++I) - Keys.push_back(&*I); + for (const auto &Entry : Config) + Keys.push_back(&Entry); llvm::array_pod_sort(Keys.begin(), Keys.end(), compareEntry); llvm::errs() << "[config]\n"; @@ -302,7 +301,7 @@ class ExplodedGraphViewer : public Checker< check::EndAnalysis > { public: ExplodedGraphViewer() {} void checkEndAnalysis(ExplodedGraph &G, BugReporter &B,ExprEngine &Eng) const { - Eng.ViewGraph(0); + Eng.ViewGraph(false); } }; @@ -323,7 +322,7 @@ bool ento::shouldRegisterExplodedGraphViewer(const CheckerManager &mgr) { namespace { class ReportStmts : public Checker<check::PreStmt<Stmt>> { - BuiltinBug BT_stmtLoc{this, "Statement"}; + BugType BT_stmtLoc{this, "Statement"}; public: void checkPreStmt(const Stmt *S, CheckerContext &C) const { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugContainerModeling.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugContainerModeling.cpp index 6fed999ffc80..97f769b1c451 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugContainerModeling.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugContainerModeling.cpp @@ -13,6 +13,7 @@ #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" @@ -27,7 +28,8 @@ namespace { class DebugContainerModeling : public Checker<eval::Call> { - std::unique_ptr<BugType> DebugMsgBugType; + const BugType DebugMsgBugType{this, "Checking analyzer assumptions", "debug", + /*SuppressOnSink=*/true}; template <typename Getter> void analyzerContainerDataField(const CallExpr *CE, CheckerContext &C, @@ -40,26 +42,18 @@ class DebugContainerModeling CheckerContext &) const; CallDescriptionMap<FnCheck> Callbacks = { - {{0, "clang_analyzer_container_begin", 1}, - &DebugContainerModeling::analyzerContainerBegin}, - {{0, "clang_analyzer_container_end", 1}, - &DebugContainerModeling::analyzerContainerEnd}, + {{{"clang_analyzer_container_begin"}, 1}, + &DebugContainerModeling::analyzerContainerBegin}, + {{{"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()); @@ -136,8 +130,8 @@ ExplodedNode *DebugContainerModeling::reportDebugMsg(llvm::StringRef Msg, return nullptr; auto &BR = C.getBugReporter(); - BR.emitReport(std::make_unique<PathSensitiveBugReport>(*DebugMsgBugType, - Msg, N)); + BR.emitReport( + std::make_unique<PathSensitiveBugReport>(DebugMsgBugType, Msg, N)); return N; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugIteratorModeling.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugIteratorModeling.cpp index 5833eea56da8..ff479c7b0ac8 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugIteratorModeling.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DebugIteratorModeling.cpp @@ -13,6 +13,7 @@ #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" @@ -27,7 +28,8 @@ namespace { class DebugIteratorModeling : public Checker<eval::Call> { - std::unique_ptr<BugType> DebugMsgBugType; + const BugType DebugMsgBugType{this, "Checking analyzer assumptions", "debug", + /*SuppressOnSink=*/true}; template <typename Getter> void analyzerIteratorDataField(const CallExpr *CE, CheckerContext &C, @@ -41,28 +43,20 @@ class DebugIteratorModeling CheckerContext &) const; CallDescriptionMap<FnCheck> Callbacks = { - {{0, "clang_analyzer_iterator_position", 1}, - &DebugIteratorModeling::analyzerIteratorPosition}, - {{0, "clang_analyzer_iterator_container", 1}, - &DebugIteratorModeling::analyzerIteratorContainer}, - {{0, "clang_analyzer_iterator_validity", 1}, - &DebugIteratorModeling::analyzerIteratorValidity}, + {{{"clang_analyzer_iterator_position"}, 1}, + &DebugIteratorModeling::analyzerIteratorPosition}, + {{{"clang_analyzer_iterator_container"}, 1}, + &DebugIteratorModeling::analyzerIteratorContainer}, + {{{"clang_analyzer_iterator_validity"}, 1}, + &DebugIteratorModeling::analyzerIteratorValidity}, }; public: - DebugIteratorModeling(); - bool evalCall(const CallEvent &Call, CheckerContext &C) const; }; } //namespace -DebugIteratorModeling::DebugIteratorModeling() { - DebugMsgBugType.reset( - new BugType(this, "Checking analyzer assumptions", "debug", - /*SuppressOnSink=*/true)); -} - bool DebugIteratorModeling::evalCall(const CallEvent &Call, CheckerContext &C) const { const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); @@ -130,8 +124,8 @@ ExplodedNode *DebugIteratorModeling::reportDebugMsg(llvm::StringRef Msg, return nullptr; auto &BR = C.getBugReporter(); - BR.emitReport(std::make_unique<PathSensitiveBugReport>(*DebugMsgBugType, - Msg, N)); + BR.emitReport( + std::make_unique<PathSensitiveBugReport>(DebugMsgBugType, Msg, N)); return N; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp deleted file mode 100644 index 7c5833762008..000000000000 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp +++ /dev/null @@ -1,155 +0,0 @@ -//===-- DeleteWithNonVirtualDtorChecker.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 the OOP52-CPP CERT rule: Do not delete a polymorphic -// object without a virtual destructor. -// -// Diagnostic flags -Wnon-virtual-dtor and -Wdelete-non-virtual-dtor report if -// an object with a virtual function but a non-virtual destructor exists or is -// deleted, respectively. -// -// This check exceeds them by comparing the dynamic and static types of the -// object at the point of destruction and only warns if it happens through a -// pointer to a base type without a virtual destructor. The check places a note -// at the last point where the conversion from derived to base happened. -// -//===----------------------------------------------------------------------===// - -#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 "clang/StaticAnalyzer/Core/CheckerManager.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" - -using namespace clang; -using namespace ento; - -namespace { -class DeleteWithNonVirtualDtorChecker - : public Checker<check::PreStmt<CXXDeleteExpr>> { - mutable std::unique_ptr<BugType> BT; - - class DeleteBugVisitor : public BugReporterVisitor { - public: - DeleteBugVisitor() : Satisfied(false) {} - void Profile(llvm::FoldingSetNodeID &ID) const override { - static int X = 0; - ID.AddPointer(&X); - } - PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - PathSensitiveBugReport &BR) override; - - private: - bool Satisfied; - }; - -public: - void checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const; -}; -} // end anonymous namespace - -void DeleteWithNonVirtualDtorChecker::checkPreStmt(const CXXDeleteExpr *DE, - CheckerContext &C) const { - const Expr *DeletedObj = DE->getArgument(); - const MemRegion *MR = C.getSVal(DeletedObj).getAsRegion(); - if (!MR) - return; - - const auto *BaseClassRegion = MR->getAs<TypedValueRegion>(); - const auto *DerivedClassRegion = MR->getBaseRegion()->getAs<SymbolicRegion>(); - if (!BaseClassRegion || !DerivedClassRegion) - return; - - const auto *BaseClass = BaseClassRegion->getValueType()->getAsCXXRecordDecl(); - const auto *DerivedClass = - DerivedClassRegion->getSymbol()->getType()->getPointeeCXXRecordDecl(); - if (!BaseClass || !DerivedClass) - return; - - if (!BaseClass->hasDefinition() || !DerivedClass->hasDefinition()) - return; - - if (BaseClass->getDestructor()->isVirtual()) - return; - - if (!DerivedClass->isDerivedFrom(BaseClass)) - return; - - if (!BT) - BT.reset(new BugType(this, - "Destruction of a polymorphic object with no " - "virtual destructor", - "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. - R->markInteresting(BaseClassRegion); - R->addVisitor(std::make_unique<DeleteBugVisitor>()); - C.emitReport(std::move(R)); -} - -PathDiagnosticPieceRef -DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, - PathSensitiveBugReport &BR) { - // Stop traversal after the first conversion was found on a path. - if (Satisfied) - return nullptr; - - const Stmt *S = N->getStmtForDiagnostics(); - if (!S) - return nullptr; - - const auto *CastE = dyn_cast<CastExpr>(S); - if (!CastE) - return nullptr; - - // Only interested in DerivedToBase implicit casts. - // Explicit casts can have different CastKinds. - if (const auto *ImplCastE = dyn_cast<ImplicitCastExpr>(CastE)) { - if (ImplCastE->getCastKind() != CK_DerivedToBase) - return nullptr; - } - - // Region associated with the current cast expression. - const MemRegion *M = N->getSVal(CastE).getAsRegion(); - if (!M) - return nullptr; - - // Check if target region was marked as problematic previously. - if (!BR.isInteresting(M)) - return nullptr; - - // Stop traversal on this path. - Satisfied = true; - - SmallString<256> Buf; - llvm::raw_svector_ostream OS(Buf); - OS << "Conversion from derived to base happened here"; - PathDiagnosticLocation Pos(S, BRC.getSourceManager(), - N->getLocationContext()); - return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true); -} - -void ento::registerDeleteWithNonVirtualDtorChecker(CheckerManager &mgr) { - mgr.registerChecker<DeleteWithNonVirtualDtorChecker>(); -} - -bool ento::shouldRegisterDeleteWithNonVirtualDtorChecker( - 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 4a9c7ce3c66d..a678c3827e7f 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp @@ -11,9 +11,10 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/ExprOpenMP.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" @@ -39,6 +40,8 @@ class DereferenceChecker void reportBug(DerefKind K, ProgramStateRef State, const Stmt *S, CheckerContext &C) const; + bool suppressReport(CheckerContext &C, const Expr *E) const; + public: void checkLocation(SVal location, bool isLoad, const Stmt* S, CheckerContext &C) const; @@ -49,6 +52,8 @@ public: const Expr *Ex, const ProgramState *state, const LocationContext *LCtx, bool loadedFrom = false); + + bool SuppressAddressSpaces = false; }; } // end anonymous namespace @@ -109,9 +114,35 @@ static const Expr *getDereferenceExpr(const Stmt *S, bool IsBind=false){ return E; } -static bool suppressReport(const Expr *E) { - // Do not report dereferences on memory in non-default address spaces. - return E->getType().hasAddressSpace(); +bool DereferenceChecker::suppressReport(CheckerContext &C, + const Expr *E) const { + // Do not report dereferences on memory that use address space #256, #257, + // and #258. Those address spaces are used when dereferencing address spaces + // relative to the GS, FS, and SS segments on x86/x86-64 targets. + // Dereferencing a null pointer in these address spaces is not defined + // as an error. All other null dereferences in other address spaces + // are defined as an error unless explicitly defined. + // See https://clang.llvm.org/docs/LanguageExtensions.html, the section + // "X86/X86-64 Language Extensions" + + QualType Ty = E->getType(); + if (!Ty.hasAddressSpace()) + return false; + if (SuppressAddressSpaces) + return true; + + const llvm::Triple::ArchType Arch = + C.getASTContext().getTargetInfo().getTriple().getArch(); + + if ((Arch == llvm::Triple::x86) || (Arch == llvm::Triple::x86_64)) { + switch (toTargetAddressSpace(E->getType().getAddressSpace())) { + case 256: + case 257: + case 258: + return true; + } + } + return false; } static bool isDeclRefExprToReference(const Expr *E) { @@ -209,7 +240,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, // Check for dereference of an undefined value. if (l.isUndef()) { const Expr *DerefExpr = getDereferenceExpr(S); - if (!suppressReport(DerefExpr)) + if (!suppressReport(C, DerefExpr)) reportBug(DerefKind::UndefinedPointerValue, C.getState(), DerefExpr, C); return; } @@ -217,7 +248,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, DefinedOrUnknownSVal location = l.castAs<DefinedOrUnknownSVal>(); // Check for null dereferences. - if (!location.getAs<Loc>()) + if (!isa<Loc>(location)) return; ProgramStateRef state = C.getState(); @@ -230,7 +261,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, // We know that 'location' can only be null. This is what // we call an "explicit" null dereference. const Expr *expr = getDereferenceExpr(S); - if (!suppressReport(expr)) { + if (!suppressReport(C, expr)) { reportBug(DerefKind::NullPointer, nullState, expr, C); return; } @@ -272,7 +303,7 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S, if (StNull) { if (!StNonNull) { const Expr *expr = getDereferenceExpr(S, /*IsBind=*/true); - if (!suppressReport(expr)) { + if (!suppressReport(C, expr)) { reportBug(DerefKind::NullPointer, StNull, expr, C); return; } @@ -308,7 +339,9 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S, } void ento::registerDereferenceChecker(CheckerManager &mgr) { - mgr.registerChecker<DereferenceChecker>(); + auto *Chk = mgr.registerChecker<DereferenceChecker>(); + Chk->SuppressAddressSpaces = mgr.getAnalyzerOptions().getCheckerBooleanOption( + mgr.getCurrentCheckerName(), "SuppressAddressSpaces"); } bool ento::shouldRegisterDereferenceChecker(const CheckerManager &mgr) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp index df88b71ff063..49486ea796c2 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp @@ -44,8 +44,8 @@ static bool DefaultMethodFilter(const ObjCMethodDecl *M) { M->getMethodFamily() == OMF_dealloc || M->getMethodFamily() == OMF_copy || M->getMethodFamily() == OMF_mutableCopy || - M->getSelector().getNameForSlot(0).find("init") != StringRef::npos || - M->getSelector().getNameForSlot(0).find("Init") != StringRef::npos; + M->getSelector().getNameForSlot(0).contains("init") || + M->getSelector().getNameForSlot(0).contains("Init"); } class DirectIvarAssignment : diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp index 2b3164ba4a2c..5496f087447f 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp @@ -11,12 +11,14 @@ // //===----------------------------------------------------------------------===// -#include "Taint.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Checkers/Taint.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/CheckerContext.h" +#include <optional> using namespace clang; using namespace ento; @@ -24,9 +26,13 @@ using namespace taint; namespace { class DivZeroChecker : public Checker< check::PreStmt<BinaryOperator> > { - mutable std::unique_ptr<BuiltinBug> BT; - void reportBug(const char *Msg, ProgramStateRef StateZero, CheckerContext &C, - std::unique_ptr<BugReporterVisitor> Visitor = nullptr) const; + const BugType BT{this, "Division by zero"}; + const BugType TaintBT{this, "Division by zero", categories::TaintedData}; + void reportBug(StringRef Msg, ProgramStateRef StateZero, + CheckerContext &C) const; + void reportTaintBug(StringRef Msg, ProgramStateRef StateZero, + CheckerContext &C, + llvm::ArrayRef<SymbolRef> TaintedSyms) const; public: void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const; @@ -40,16 +46,23 @@ static const Expr *getDenomExpr(const ExplodedNode *N) { return nullptr; } -void DivZeroChecker::reportBug( - const char *Msg, ProgramStateRef StateZero, CheckerContext &C, - std::unique_ptr<BugReporterVisitor> Visitor) const { +void DivZeroChecker::reportBug(StringRef Msg, ProgramStateRef StateZero, + CheckerContext &C) const { if (ExplodedNode *N = C.generateErrorNode(StateZero)) { - if (!BT) - BT.reset(new BuiltinBug(this, "Division by zero")); + auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N); + bugreporter::trackExpressionValue(N, getDenomExpr(N), *R); + C.emitReport(std::move(R)); + } +} - auto R = std::make_unique<PathSensitiveBugReport>(*BT, Msg, N); - R->addVisitor(std::move(Visitor)); +void DivZeroChecker::reportTaintBug( + StringRef Msg, ProgramStateRef StateZero, CheckerContext &C, + llvm::ArrayRef<SymbolRef> TaintedSyms) const { + if (ExplodedNode *N = C.generateErrorNode(StateZero)) { + auto R = std::make_unique<PathSensitiveBugReport>(TaintBT, Msg, N); bugreporter::trackExpressionValue(N, getDenomExpr(N), *R); + for (auto Sym : TaintedSyms) + R->markInteresting(Sym); C.emitReport(std::move(R)); } } @@ -67,7 +80,7 @@ void DivZeroChecker::checkPreStmt(const BinaryOperator *B, return; SVal Denom = C.getSVal(B->getRHS()); - Optional<DefinedSVal> DV = Denom.getAs<DefinedSVal>(); + std::optional<DefinedSVal> DV = Denom.getAs<DefinedSVal>(); // Divide-by-undefined handled in the generic checking for uses of // undefined values. @@ -85,11 +98,13 @@ void DivZeroChecker::checkPreStmt(const BinaryOperator *B, return; } - bool TaintedD = isTainted(C.getState(), *DV); - if ((stateNotZero && stateZero && TaintedD)) { - reportBug("Division by a tainted value, possibly zero", stateZero, C, - std::make_unique<taint::TaintBugVisitor>(*DV)); - return; + if ((stateNotZero && stateZero)) { + std::vector<SymbolRef> taintedSyms = getTaintedSymbols(C.getState(), *DV); + if (!taintedSyms.empty()) { + reportTaintBug("Division by a tainted value, possibly zero", stateZero, C, + taintedSyms); + return; + } } // If we get here, then the denom should not be zero. We abandon the implicit diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp index dbc930d7d37b..0ad307d3ebd5 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp @@ -30,12 +30,7 @@ using namespace ento; namespace { class DynamicTypeChecker : public Checker<check::PostStmt<ImplicitCastExpr>> { - mutable std::unique_ptr<BugType> BT; - void initBugType() const { - if (!BT) - BT.reset( - new BugType(this, "Dynamic and static type mismatch", "Type Error")); - } + const BugType BT{this, "Dynamic and static type mismatch", "Type Error"}; class DynamicTypeBugVisitor : public BugReporterVisitor { public: @@ -70,7 +65,6 @@ void DynamicTypeChecker::reportTypeError(QualType DynamicType, const MemRegion *Reg, const Stmt *ReportedNode, CheckerContext &C) const { - initBugType(); SmallString<192> Buf; llvm::raw_svector_ostream OS(Buf); OS << "Object has a dynamic type '"; @@ -81,7 +75,7 @@ void DynamicTypeChecker::reportTypeError(QualType DynamicType, llvm::Twine()); OS << "'"; auto R = std::make_unique<PathSensitiveBugReport>( - *BT, OS.str(), C.generateNonFatalErrorNode()); + BT, OS.str(), C.generateNonFatalErrorNode()); R->markInteresting(Reg); R->addVisitor(std::make_unique<DynamicTypeBugVisitor>(Reg)); R->addRange(ReportedNode->getSourceRange()); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index 14ba5d769969..034774a252b1 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -31,6 +31,8 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "llvm/ADT/STLExtras.h" +#include <optional> using namespace clang; using namespace ento; @@ -56,9 +58,6 @@ class DynamicTypePropagation: check::PreObjCMessage, check::PostObjCMessage > { - const ObjCObjectType *getObjectTypeForAllocAndNew(const ObjCMessageExpr *MsgE, - CheckerContext &C) const; - /// Return a better dynamic type if one can be derived from the cast. const ObjCObjectPointerType *getBetterObjCType(const Expr *CastE, CheckerContext &C) const; @@ -108,7 +107,7 @@ public: void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; /// This value is set to true, when the Generics checker is turned on. - DefaultBool CheckGenerics; + bool CheckGenerics = false; CheckerNameRef GenericCheckName; }; @@ -235,11 +234,9 @@ void DynamicTypePropagation::checkDeadSymbols(SymbolReaper &SR, MostSpecializedTypeArgsMapTy TyArgMap = State->get<MostSpecializedTypeArgsMap>(); - for (MostSpecializedTypeArgsMapTy::iterator I = TyArgMap.begin(), - E = TyArgMap.end(); - I != E; ++I) { - if (SR.isDead(I->first)) { - State = State->remove<MostSpecializedTypeArgsMap>(I->first); + for (SymbolRef Sym : llvm::make_first_range(TyArgMap)) { + if (SR.isDead(Sym)) { + State = State->remove<MostSpecializedTypeArgsMap>(Sym); } } @@ -271,12 +268,12 @@ void DynamicTypePropagation::checkPreCall(const CallEvent &Call, // a more-derived class. switch (Ctor->getOriginExpr()->getConstructionKind()) { - case CXXConstructExpr::CK_Complete: - case CXXConstructExpr::CK_Delegating: + case CXXConstructionKind::Complete: + case CXXConstructionKind::Delegating: // No additional type info necessary. return; - case CXXConstructExpr::CK_NonVirtualBase: - case CXXConstructExpr::CK_VirtualBase: + case CXXConstructionKind::NonVirtualBase: + case CXXConstructionKind::VirtualBase: if (const MemRegion *Target = Ctor->getCXXThisVal().getAsRegion()) recordFixedType(Target, Ctor->getDecl(), C); return; @@ -363,16 +360,16 @@ void DynamicTypePropagation::checkPostCall(const CallEvent &Call, if (const CXXConstructorCall *Ctor = dyn_cast<CXXConstructorCall>(&Call)) { // We may need to undo the effects of our pre-call check. switch (Ctor->getOriginExpr()->getConstructionKind()) { - case CXXConstructExpr::CK_Complete: - case CXXConstructExpr::CK_Delegating: + case CXXConstructionKind::Complete: + case CXXConstructionKind::Delegating: // No additional work necessary. // Note: This will leave behind the actual type of the object for // complete constructors, but arguably that's a good thing, since it // means the dynamic type info will be correct even for objects // constructed with operator new. return; - case CXXConstructExpr::CK_NonVirtualBase: - case CXXConstructExpr::CK_VirtualBase: + case CXXConstructionKind::NonVirtualBase: + case CXXConstructionKind::VirtualBase: if (const MemRegion *Target = Ctor->getCXXThisVal().getAsRegion()) { // We just finished a base constructor. Now we can use the subclass's // type when resolving virtual calls. @@ -384,7 +381,7 @@ void DynamicTypePropagation::checkPostCall(const CallEvent &Call, // FIXME: Instead of relying on the ParentMap, we should have the // trigger-statement (InitListExpr in this case) available in this // callback, ideally as part of CallEvent. - if (dyn_cast_or_null<InitListExpr>( + if (isa_and_nonnull<InitListExpr>( LCtx->getParentMap().getParent(Ctor->getOriginExpr()))) return; @@ -716,7 +713,7 @@ static bool isObjCTypeParamDependent(QualType Type) { class IsObjCTypeParamDependentTypeVisitor : public RecursiveASTVisitor<IsObjCTypeParamDependentTypeVisitor> { public: - IsObjCTypeParamDependentTypeVisitor() : Result(false) {} + IsObjCTypeParamDependentTypeVisitor() = default; bool VisitObjCTypeParamType(const ObjCTypeParamType *Type) { if (isa<ObjCTypeParamDecl>(Type->getDecl())) { Result = true; @@ -725,7 +722,7 @@ static bool isObjCTypeParamDependent(QualType Type) { return true; } - bool Result; + bool Result = false; }; IsObjCTypeParamDependentTypeVisitor Visitor; @@ -744,8 +741,6 @@ findMethodDecl(const ObjCMessageExpr *MessageExpr, const ObjCMethodDecl *Method = nullptr; QualType ReceiverType = MessageExpr->getReceiverType(); - const auto *ReceiverObjectPtrType = - ReceiverType->getAs<ObjCObjectPointerType>(); // Do this "devirtualization" on instance and class methods only. Trust the // static type on super and super class calls. @@ -755,7 +750,8 @@ findMethodDecl(const ObjCMessageExpr *MessageExpr, // type, look up the method in the tracked type, not in the receiver type. // This way we preserve more information. if (ReceiverType->isObjCIdType() || ReceiverType->isObjCClassType() || - ASTCtxt.canAssignObjCInterfaces(ReceiverObjectPtrType, TrackedType)) { + ASTCtxt.canAssignObjCInterfaces( + ReceiverType->castAs<ObjCObjectPointerType>(), TrackedType)) { const ObjCInterfaceDecl *InterfaceDecl = TrackedType->getInterfaceDecl(); // The method might not be found. Selector Sel = MessageExpr->getSelector(); @@ -849,7 +845,7 @@ void DynamicTypePropagation::checkPreObjCMessage(const ObjCMethodCall &M, return; } - Optional<ArrayRef<QualType>> TypeArgs = + std::optional<ArrayRef<QualType>> TypeArgs = (*TrackedType)->getObjCSubstitutions(Method->getDeclContext()); // This case might happen when there is an unspecialized override of a // specialized method. @@ -982,7 +978,7 @@ void DynamicTypePropagation::checkPostObjCMessage(const ObjCMethodCall &M, if (!Method) return; - Optional<ArrayRef<QualType>> TypeArgs = + std::optional<ArrayRef<QualType>> TypeArgs = (*TrackedType)->getObjCSubstitutions(Method->getDeclContext()); if (!TypeArgs) return; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp index 0e94b915a468..0fa20428c1b5 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp @@ -22,9 +22,12 @@ #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/Support/FormatVariadic.h" +#include <optional> using namespace clang; using namespace ento; +using llvm::formatv; namespace { // This evaluator checks two SVals for equality. The first SVal is provided via @@ -57,8 +60,9 @@ public: // Being conservative, it does not warn if there is slight possibility the // value can be matching. class EnumCastOutOfRangeChecker : public Checker<check::PreStmt<CastExpr>> { - mutable std::unique_ptr<BuiltinBug> EnumValueCastOutOfRange; - void reportWarning(CheckerContext &C) const; + const BugType EnumValueCastOutOfRange{this, "Enum cast out of range"}; + void reportWarning(CheckerContext &C, const CastExpr *CE, + const EnumDecl *E) const; public: void checkPreStmt(const CastExpr *CE, CheckerContext &C) const; @@ -71,21 +75,39 @@ EnumValueVector getDeclValuesForEnum(const EnumDecl *ED) { EnumValueVector DeclValues( std::distance(ED->enumerator_begin(), ED->enumerator_end())); llvm::transform(ED->enumerators(), DeclValues.begin(), - [](const EnumConstantDecl *D) { return D->getInitVal(); }); + [](const EnumConstantDecl *D) { return D->getInitVal(); }); return DeclValues; } } // namespace -void EnumCastOutOfRangeChecker::reportWarning(CheckerContext &C) const { +void EnumCastOutOfRangeChecker::reportWarning(CheckerContext &C, + const CastExpr *CE, + const EnumDecl *E) const { + assert(E && "valid EnumDecl* is expected"); if (const ExplodedNode *N = C.generateNonFatalErrorNode()) { - if (!EnumValueCastOutOfRange) - EnumValueCastOutOfRange.reset( - new BuiltinBug(this, "Enum cast out of range", - "The value provided to the cast expression is not in " - "the valid range of values for the enum")); - C.emitReport(std::make_unique<PathSensitiveBugReport>( - *EnumValueCastOutOfRange, EnumValueCastOutOfRange->getDescription(), - N)); + std::string ValueStr = "", NameStr = "the enum"; + + // Try to add details to the message: + const auto ConcreteValue = + C.getSVal(CE->getSubExpr()).getAs<nonloc::ConcreteInt>(); + if (ConcreteValue) { + ValueStr = formatv(" '{0}'", ConcreteValue->getValue()); + } + if (StringRef EnumName{E->getName()}; !EnumName.empty()) { + NameStr = formatv("'{0}'", EnumName); + } + + std::string Msg = formatv("The value{0} provided to the cast expression is " + "not in the valid range of values for {1}", + ValueStr, NameStr); + + auto BR = std::make_unique<PathSensitiveBugReport>(EnumValueCastOutOfRange, + Msg, N); + bugreporter::trackExpressionValue(N, CE->getSubExpr(), *BR); + BR->addNote("enum declared here", + PathDiagnosticLocation::create(E, C.getSourceManager()), + {E->getSourceRange()}); + C.emitReport(std::move(BR)); } } @@ -94,10 +116,10 @@ void EnumCastOutOfRangeChecker::checkPreStmt(const CastExpr *CE, // Only perform enum range check on casts where such checks are valid. For // all other cast kinds (where enum range checks are unnecessary or invalid), - // just return immediately. TODO: The set of casts whitelisted for enum - // range checking may be incomplete. Better to add a missing cast kind to - // enable a missing check than to generate false negatives and have to remove - // those later. + // just return immediately. TODO: The set of casts allowed for enum range + // checking may be incomplete. Better to add a missing cast kind to enable a + // missing check than to generate false negatives and have to remove those + // later. switch (CE->getCastKind()) { case CK_IntegralCast: break; @@ -108,7 +130,7 @@ void EnumCastOutOfRangeChecker::checkPreStmt(const CastExpr *CE, } // Get the value of the expression to cast. - const llvm::Optional<DefinedOrUnknownSVal> ValueToCast = + const std::optional<DefinedOrUnknownSVal> ValueToCast = C.getSVal(CE->getSubExpr()).getAs<DefinedOrUnknownSVal>(); // If the value cannot be reasoned about (not even a DefinedOrUnknownSVal), @@ -128,14 +150,25 @@ void EnumCastOutOfRangeChecker::checkPreStmt(const CastExpr *CE, const EnumDecl *ED = T->castAs<EnumType>()->getDecl(); EnumValueVector DeclValues = getDeclValuesForEnum(ED); + + // If the declarator list is empty, bail out. + // Every initialization an enum with a fixed underlying type but without any + // enumerators would produce a warning if we were to continue at this point. + // The most notable example is std::byte in the C++17 standard library. + // TODO: Create heuristics to bail out when the enum type is intended to be + // used to store combinations of flag values (to mitigate the limitation + // described in the docs). + if (DeclValues.size() == 0) + return; + // Check if any of the enum values possibly match. - bool PossibleValueMatch = llvm::any_of( - DeclValues, ConstraintBasedEQEvaluator(C, *ValueToCast)); + bool PossibleValueMatch = + llvm::any_of(DeclValues, ConstraintBasedEQEvaluator(C, *ValueToCast)); // If there is no value that can possibly match any of the enum values, then // warn. if (!PossibleValueMatch) - reportWarning(C); + reportWarning(C, CE, ED); } void ento::registerEnumCastOutOfRangeChecker(CheckerManager &mgr) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp new file mode 100644 index 000000000000..265185e64107 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp @@ -0,0 +1,250 @@ +//=== ErrnoChecker.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 defines an "errno checker" that can detect some invalid use of the +// system-defined value 'errno'. This checker works together with the +// ErrnoModeling checker and other checkers like StdCLibraryFunctions. +// +//===----------------------------------------------------------------------===// + +#include "ErrnoModeling.h" +#include "clang/AST/ParentMapContext.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "llvm/ADT/STLExtras.h" +#include <optional> + +using namespace clang; +using namespace ento; +using namespace errno_modeling; + +namespace { + +class ErrnoChecker + : public Checker<check::Location, check::PreCall, check::RegionChanges> { +public: + void checkLocation(SVal Loc, bool IsLoad, const Stmt *S, + CheckerContext &) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + ProgramStateRef + checkRegionChanges(ProgramStateRef State, + const InvalidatedSymbols *Invalidated, + ArrayRef<const MemRegion *> ExplicitRegions, + ArrayRef<const MemRegion *> Regions, + const LocationContext *LCtx, const CallEvent *Call) const; + void checkBranchCondition(const Stmt *Condition, CheckerContext &Ctx) const; + + /// Indicates if a read (load) of \c errno is allowed in a non-condition part + /// of \c if, \c switch, loop and conditional statements when the errno + /// value may be undefined. + bool AllowErrnoReadOutsideConditions = true; + +private: + void generateErrnoNotCheckedBug(CheckerContext &C, ProgramStateRef State, + const MemRegion *ErrnoRegion, + const CallEvent *CallMayChangeErrno) const; + + BugType BT_InvalidErrnoRead{this, "Value of 'errno' could be undefined", + "Error handling"}; + BugType BT_ErrnoNotChecked{this, "Value of 'errno' was not checked", + "Error handling"}; +}; + +} // namespace + +static ProgramStateRef setErrnoStateIrrelevant(ProgramStateRef State) { + return setErrnoState(State, Irrelevant); +} + +/// Check if a statement (expression) or an ancestor of it is in a condition +/// part of a (conditional, loop, switch) statement. +static bool isInCondition(const Stmt *S, CheckerContext &C) { + ParentMapContext &ParentCtx = C.getASTContext().getParentMapContext(); + bool CondFound = false; + while (S && !CondFound) { + const DynTypedNodeList Parents = ParentCtx.getParents(*S); + if (Parents.empty()) + break; + const auto *ParentS = Parents[0].get<Stmt>(); + if (!ParentS || isa<CallExpr>(ParentS)) + break; + switch (ParentS->getStmtClass()) { + case Expr::IfStmtClass: + CondFound = (S == cast<IfStmt>(ParentS)->getCond()); + break; + case Expr::ForStmtClass: + CondFound = (S == cast<ForStmt>(ParentS)->getCond()); + break; + case Expr::DoStmtClass: + CondFound = (S == cast<DoStmt>(ParentS)->getCond()); + break; + case Expr::WhileStmtClass: + CondFound = (S == cast<WhileStmt>(ParentS)->getCond()); + break; + case Expr::SwitchStmtClass: + CondFound = (S == cast<SwitchStmt>(ParentS)->getCond()); + break; + case Expr::ConditionalOperatorClass: + CondFound = (S == cast<ConditionalOperator>(ParentS)->getCond()); + break; + case Expr::BinaryConditionalOperatorClass: + CondFound = (S == cast<BinaryConditionalOperator>(ParentS)->getCommon()); + break; + default: + break; + } + S = ParentS; + } + return CondFound; +} + +void ErrnoChecker::generateErrnoNotCheckedBug( + CheckerContext &C, ProgramStateRef State, const MemRegion *ErrnoRegion, + const CallEvent *CallMayChangeErrno) const { + if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { + SmallString<100> StrBuf; + llvm::raw_svector_ostream OS(StrBuf); + if (CallMayChangeErrno) { + OS << "Value of 'errno' was not checked and may be overwritten by " + "function '"; + const auto *CallD = + dyn_cast_or_null<FunctionDecl>(CallMayChangeErrno->getDecl()); + assert(CallD && CallD->getIdentifier()); + OS << CallD->getIdentifier()->getName() << "'"; + } else { + OS << "Value of 'errno' was not checked and is overwritten here"; + } + auto BR = std::make_unique<PathSensitiveBugReport>(BT_ErrnoNotChecked, + OS.str(), N); + BR->markInteresting(ErrnoRegion); + C.emitReport(std::move(BR)); + } +} + +void ErrnoChecker::checkLocation(SVal Loc, bool IsLoad, const Stmt *S, + CheckerContext &C) const { + std::optional<ento::Loc> ErrnoLoc = getErrnoLoc(C.getState()); + if (!ErrnoLoc) + return; + + auto L = Loc.getAs<ento::Loc>(); + if (!L || *ErrnoLoc != *L) + return; + + ProgramStateRef State = C.getState(); + ErrnoCheckState EState = getErrnoState(State); + + if (IsLoad) { + switch (EState) { + case MustNotBeChecked: + // Read of 'errno' when it may have undefined value. + if (!AllowErrnoReadOutsideConditions || isInCondition(S, C)) { + if (ExplodedNode *N = C.generateErrorNode()) { + auto BR = std::make_unique<PathSensitiveBugReport>( + BT_InvalidErrnoRead, + "An undefined value may be read from 'errno'", N); + BR->markInteresting(ErrnoLoc->getAsRegion()); + C.emitReport(std::move(BR)); + } + } + break; + case MustBeChecked: + // 'errno' has to be checked. A load is required for this, with no more + // information we can assume that it is checked somehow. + // After this place 'errno' is allowed to be read and written. + State = setErrnoStateIrrelevant(State); + C.addTransition(State); + break; + default: + break; + } + } else { + switch (EState) { + case MustBeChecked: + // 'errno' is overwritten without a read before but it should have been + // checked. + generateErrnoNotCheckedBug(C, setErrnoStateIrrelevant(State), + ErrnoLoc->getAsRegion(), nullptr); + break; + case MustNotBeChecked: + // Write to 'errno' when it is not allowed to be read. + // After this place 'errno' is allowed to be read and written. + State = setErrnoStateIrrelevant(State); + C.addTransition(State); + break; + default: + break; + } + } +} + +void ErrnoChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + const auto *CallF = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + if (!CallF) + return; + + CallF = CallF->getCanonicalDecl(); + // If 'errno' must be checked, it should be done as soon as possible, and + // before any other call to a system function (something in a system header). + // To avoid use of a long list of functions that may change 'errno' + // (which may be different with standard library versions) assume that any + // function can change it. + // A list of special functions can be used that are allowed here without + // generation of diagnostic. For now the only such case is 'errno' itself. + // Probably 'strerror'? + if (CallF->isExternC() && CallF->isGlobal() && + C.getSourceManager().isInSystemHeader(CallF->getLocation()) && + !isErrno(CallF)) { + if (getErrnoState(C.getState()) == MustBeChecked) { + std::optional<ento::Loc> ErrnoLoc = getErrnoLoc(C.getState()); + assert(ErrnoLoc && "ErrnoLoc should exist if an errno state is set."); + generateErrnoNotCheckedBug(C, setErrnoStateIrrelevant(C.getState()), + ErrnoLoc->getAsRegion(), &Call); + } + } +} + +ProgramStateRef ErrnoChecker::checkRegionChanges( + ProgramStateRef State, const InvalidatedSymbols *Invalidated, + ArrayRef<const MemRegion *> ExplicitRegions, + ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx, + const CallEvent *Call) const { + std::optional<ento::Loc> ErrnoLoc = getErrnoLoc(State); + if (!ErrnoLoc) + return State; + const MemRegion *ErrnoRegion = ErrnoLoc->getAsRegion(); + + // If 'errno' is invalidated we can not know if it is checked or written into, + // allow read and write without bug reports. + if (llvm::is_contained(Regions, ErrnoRegion)) + return clearErrnoState(State); + + // Always reset errno state when the system memory space is invalidated. + // The ErrnoRegion is not always found in the list in this case. + if (llvm::is_contained(Regions, ErrnoRegion->getMemorySpace())) + return clearErrnoState(State); + + return State; +} + +void ento::registerErrnoChecker(CheckerManager &mgr) { + const AnalyzerOptions &Opts = mgr.getAnalyzerOptions(); + auto *Checker = mgr.registerChecker<ErrnoChecker>(); + Checker->AllowErrnoReadOutsideConditions = Opts.getCheckerBooleanOption( + Checker, "AllowErrnoReadOutsideConditionExpressions"); +} + +bool ento::shouldRegisterErrnoChecker(const CheckerManager &mgr) { + return true; +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp new file mode 100644 index 000000000000..1b34ea0e056e --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp @@ -0,0 +1,325 @@ +//=== ErrnoModeling.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 defines a checker `ErrnoModeling`, which is used to make the system +// value 'errno' available to other checkers. +// The 'errno' value is stored at a special memory region that is accessible +// through the `errno_modeling` namespace. The memory region is either the +// region of `errno` itself if it is a variable, otherwise an artifically +// created region (in the system memory space). If `errno` is defined by using +// a function which returns the address of it (this is always the case if it is +// not a variable) this function is recognized and evaluated. In this way +// `errno` becomes visible to the analysis and checkers can change its value. +// +//===----------------------------------------------------------------------===// + +#include "ErrnoModeling.h" +#include "clang/AST/ParentMapContext.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/FormatVariadic.h" +#include <optional> + +using namespace clang; +using namespace ento; + +namespace { + +// Name of the "errno" variable. +// FIXME: Is there a system where it is not called "errno" but is a variable? +const char *ErrnoVarName = "errno"; +// Names of functions that return a location of the "errno" value. +// FIXME: Are there other similar function names? +const char *ErrnoLocationFuncNames[] = {"__errno_location", "___errno", + "__errno", "_errno", "__error"}; + +class ErrnoModeling + : public Checker<check::ASTDecl<TranslationUnitDecl>, check::BeginFunction, + check::LiveSymbols, eval::Call> { +public: + void checkASTDecl(const TranslationUnitDecl *D, AnalysisManager &Mgr, + BugReporter &BR) const; + void checkBeginFunction(CheckerContext &C) const; + void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; + + // The declaration of an "errno" variable or "errno location" function. + mutable const Decl *ErrnoDecl = nullptr; + +private: + // FIXME: Names from `ErrnoLocationFuncNames` are used to build this set. + CallDescriptionSet ErrnoLocationCalls{{{"__errno_location"}, 0, 0}, + {{"___errno"}, 0, 0}, + {{"__errno"}, 0, 0}, + {{"_errno"}, 0, 0}, + {{"__error"}, 0, 0}}; +}; + +} // namespace + +/// Store a MemRegion that contains the 'errno' integer value. +/// The value is null if the 'errno' value was not recognized in the AST. +REGISTER_TRAIT_WITH_PROGRAMSTATE(ErrnoRegion, const MemRegion *) + +REGISTER_TRAIT_WITH_PROGRAMSTATE(ErrnoState, errno_modeling::ErrnoCheckState) + +/// Search for a variable called "errno" in the AST. +/// Return nullptr if not found. +static const VarDecl *getErrnoVar(ASTContext &ACtx) { + IdentifierInfo &II = ACtx.Idents.get(ErrnoVarName); + auto LookupRes = ACtx.getTranslationUnitDecl()->lookup(&II); + auto Found = llvm::find_if(LookupRes, [&ACtx](const Decl *D) { + if (auto *VD = dyn_cast<VarDecl>(D)) + return ACtx.getSourceManager().isInSystemHeader(VD->getLocation()) && + VD->hasExternalStorage() && + VD->getType().getCanonicalType() == ACtx.IntTy; + return false; + }); + if (Found == LookupRes.end()) + return nullptr; + + return cast<VarDecl>(*Found); +} + +/// Search for a function with a specific name that is used to return a pointer +/// to "errno". +/// Return nullptr if no such function was found. +static const FunctionDecl *getErrnoFunc(ASTContext &ACtx) { + SmallVector<const Decl *> LookupRes; + for (StringRef ErrnoName : ErrnoLocationFuncNames) { + IdentifierInfo &II = ACtx.Idents.get(ErrnoName); + llvm::append_range(LookupRes, ACtx.getTranslationUnitDecl()->lookup(&II)); + } + + auto Found = llvm::find_if(LookupRes, [&ACtx](const Decl *D) { + if (auto *FD = dyn_cast<FunctionDecl>(D)) + return ACtx.getSourceManager().isInSystemHeader(FD->getLocation()) && + FD->isExternC() && FD->getNumParams() == 0 && + FD->getReturnType().getCanonicalType() == + ACtx.getPointerType(ACtx.IntTy); + return false; + }); + if (Found == LookupRes.end()) + return nullptr; + + return cast<FunctionDecl>(*Found); +} + +void ErrnoModeling::checkASTDecl(const TranslationUnitDecl *D, + AnalysisManager &Mgr, BugReporter &BR) const { + // Try to find an usable `errno` value. + // It can be an external variable called "errno" or a function that returns a + // pointer to the "errno" value. This function can have different names. + // The actual case is dependent on the C library implementation, we + // can only search for a match in one of these variations. + // We assume that exactly one of these cases might be true. + ErrnoDecl = getErrnoVar(Mgr.getASTContext()); + if (!ErrnoDecl) + ErrnoDecl = getErrnoFunc(Mgr.getASTContext()); +} + +void ErrnoModeling::checkBeginFunction(CheckerContext &C) const { + if (!C.inTopFrame()) + return; + + ASTContext &ACtx = C.getASTContext(); + ProgramStateRef State = C.getState(); + + if (const auto *ErrnoVar = dyn_cast_or_null<VarDecl>(ErrnoDecl)) { + // There is an external 'errno' variable. + // Use its memory region. + // The memory region for an 'errno'-like variable is allocated in system + // space by MemRegionManager. + const MemRegion *ErrnoR = + State->getRegion(ErrnoVar, C.getLocationContext()); + assert(ErrnoR && "Memory region should exist for the 'errno' variable."); + State = State->set<ErrnoRegion>(ErrnoR); + State = + errno_modeling::setErrnoValue(State, C, 0, errno_modeling::Irrelevant); + C.addTransition(State); + } else if (ErrnoDecl) { + assert(isa<FunctionDecl>(ErrnoDecl) && "Invalid errno location function."); + // There is a function that returns the location of 'errno'. + // We must create a memory region for it in system space. + // Currently a symbolic region is used with an artifical symbol. + // FIXME: It is better to have a custom (new) kind of MemRegion for such + // cases. + SValBuilder &SVB = C.getSValBuilder(); + MemRegionManager &RMgr = C.getStateManager().getRegionManager(); + + const MemSpaceRegion *GlobalSystemSpace = + RMgr.getGlobalsRegion(MemRegion::GlobalSystemSpaceRegionKind); + + // Create an artifical symbol for the region. + // It is not possible to associate a statement or expression in this case. + const SymbolConjured *Sym = SVB.conjureSymbol( + nullptr, C.getLocationContext(), + ACtx.getLValueReferenceType(ACtx.IntTy), C.blockCount(), &ErrnoDecl); + + // The symbolic region is untyped, create a typed sub-region in it. + // The ElementRegion is used to make the errno region a typed region. + const MemRegion *ErrnoR = RMgr.getElementRegion( + ACtx.IntTy, SVB.makeZeroArrayIndex(), + RMgr.getSymbolicRegion(Sym, GlobalSystemSpace), C.getASTContext()); + State = State->set<ErrnoRegion>(ErrnoR); + State = + errno_modeling::setErrnoValue(State, C, 0, errno_modeling::Irrelevant); + C.addTransition(State); + } +} + +bool ErrnoModeling::evalCall(const CallEvent &Call, CheckerContext &C) const { + // Return location of "errno" at a call to an "errno address returning" + // function. + if (ErrnoLocationCalls.contains(Call)) { + ProgramStateRef State = C.getState(); + + const MemRegion *ErrnoR = State->get<ErrnoRegion>(); + if (!ErrnoR) + return false; + + State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), + loc::MemRegionVal{ErrnoR}); + C.addTransition(State); + return true; + } + + return false; +} + +void ErrnoModeling::checkLiveSymbols(ProgramStateRef State, + SymbolReaper &SR) const { + // The special errno region should never garbage collected. + if (const auto *ErrnoR = State->get<ErrnoRegion>()) + SR.markLive(ErrnoR); +} + +namespace clang { +namespace ento { +namespace errno_modeling { + +std::optional<SVal> getErrnoValue(ProgramStateRef State) { + const MemRegion *ErrnoR = State->get<ErrnoRegion>(); + if (!ErrnoR) + return {}; + QualType IntTy = State->getAnalysisManager().getASTContext().IntTy; + return State->getSVal(ErrnoR, IntTy); +} + +ProgramStateRef setErrnoValue(ProgramStateRef State, + const LocationContext *LCtx, SVal Value, + ErrnoCheckState EState) { + const MemRegion *ErrnoR = State->get<ErrnoRegion>(); + if (!ErrnoR) + return State; + // First set the errno value, the old state is still available at 'checkBind' + // or 'checkLocation' for errno value. + State = State->bindLoc(loc::MemRegionVal{ErrnoR}, Value, LCtx); + return State->set<ErrnoState>(EState); +} + +ProgramStateRef setErrnoValue(ProgramStateRef State, CheckerContext &C, + uint64_t Value, ErrnoCheckState EState) { + const MemRegion *ErrnoR = State->get<ErrnoRegion>(); + if (!ErrnoR) + return State; + State = State->bindLoc( + loc::MemRegionVal{ErrnoR}, + C.getSValBuilder().makeIntVal(Value, C.getASTContext().IntTy), + C.getLocationContext()); + return State->set<ErrnoState>(EState); +} + +std::optional<Loc> getErrnoLoc(ProgramStateRef State) { + const MemRegion *ErrnoR = State->get<ErrnoRegion>(); + if (!ErrnoR) + return {}; + return loc::MemRegionVal{ErrnoR}; +} + +ErrnoCheckState getErrnoState(ProgramStateRef State) { + return State->get<ErrnoState>(); +} + +ProgramStateRef setErrnoState(ProgramStateRef State, ErrnoCheckState EState) { + return State->set<ErrnoState>(EState); +} + +ProgramStateRef clearErrnoState(ProgramStateRef State) { + return setErrnoState(State, Irrelevant); +} + +bool isErrno(const Decl *D) { + if (const auto *VD = dyn_cast_or_null<VarDecl>(D)) + if (const IdentifierInfo *II = VD->getIdentifier()) + return II->getName() == ErrnoVarName; + if (const auto *FD = dyn_cast_or_null<FunctionDecl>(D)) + if (const IdentifierInfo *II = FD->getIdentifier()) + return llvm::is_contained(ErrnoLocationFuncNames, II->getName()); + return false; +} + +const NoteTag *getErrnoNoteTag(CheckerContext &C, const std::string &Message) { + return C.getNoteTag([Message](PathSensitiveBugReport &BR) -> std::string { + const MemRegion *ErrnoR = BR.getErrorNode()->getState()->get<ErrnoRegion>(); + if (ErrnoR && BR.isInteresting(ErrnoR)) { + BR.markNotInteresting(ErrnoR); + return Message; + } + return ""; + }); +} + +ProgramStateRef setErrnoForStdSuccess(ProgramStateRef State, + CheckerContext &C) { + return setErrnoState(State, MustNotBeChecked); +} + +ProgramStateRef setErrnoForStdFailure(ProgramStateRef State, CheckerContext &C, + NonLoc ErrnoSym) { + SValBuilder &SVB = C.getSValBuilder(); + NonLoc ZeroVal = SVB.makeZeroVal(C.getASTContext().IntTy).castAs<NonLoc>(); + DefinedOrUnknownSVal Cond = + SVB.evalBinOp(State, BO_NE, ErrnoSym, ZeroVal, SVB.getConditionType()) + .castAs<DefinedOrUnknownSVal>(); + State = State->assume(Cond, true); + if (!State) + return nullptr; + return setErrnoValue(State, C.getLocationContext(), ErrnoSym, Irrelevant); +} + +ProgramStateRef setErrnoStdMustBeChecked(ProgramStateRef State, + CheckerContext &C, + const Expr *InvalE) { + const MemRegion *ErrnoR = State->get<ErrnoRegion>(); + if (!ErrnoR) + return State; + State = State->invalidateRegions(ErrnoR, InvalE, C.blockCount(), + C.getLocationContext(), false); + if (!State) + return nullptr; + return setErrnoState(State, MustBeChecked); +} + +} // namespace errno_modeling +} // namespace ento +} // namespace clang + +void ento::registerErrnoModeling(CheckerManager &mgr) { + mgr.registerChecker<ErrnoModeling>(); +} + +bool ento::shouldRegisterErrnoModeling(const CheckerManager &mgr) { + return true; +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h new file mode 100644 index 000000000000..6b53572fe5e2 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h @@ -0,0 +1,110 @@ +//=== ErrnoModeling.h - Tracking value of 'errno'. -----------------*- 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 using the system value 'errno'. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ERRNOMODELING_H +#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ERRNOMODELING_H + +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include <optional> + +namespace clang { +namespace ento { +namespace errno_modeling { + +/// Describe how reads and writes of \c errno are handled by the checker. +enum ErrnoCheckState : unsigned { + /// We do not know anything about 'errno'. + /// Read and write is always allowed. + Irrelevant = 0, + + /// Value of 'errno' should be checked to find out if a previous function call + /// has failed. + /// When this state is set \c errno must be read by the program before a next + /// standard function call or other overwrite of \c errno follows, otherwise + /// a bug report is emitted. + MustBeChecked = 1, + + /// Value of 'errno' is not allowed to be read, it can contain an unspecified + /// value. + /// When this state is set \c errno is not allowed to be read by the program + /// until it is overwritten or invalidated. + MustNotBeChecked = 2 +}; + +/// Returns the value of 'errno', if 'errno' was found in the AST. +std::optional<SVal> getErrnoValue(ProgramStateRef State); + +/// Returns the errno check state, \c Errno_Irrelevant if 'errno' was not found +/// (this is not the only case for that value). +ErrnoCheckState getErrnoState(ProgramStateRef State); + +/// Returns the location that points to the \c MemoryRegion where the 'errno' +/// value is stored. Returns \c std::nullopt if 'errno' was not found. Otherwise +/// it always returns a valid memory region in the system global memory space. +std::optional<Loc> getErrnoLoc(ProgramStateRef State); + +/// Set value of 'errno' to any SVal, if possible. +/// The errno check state is set always when the 'errno' value is set. +ProgramStateRef setErrnoValue(ProgramStateRef State, + const LocationContext *LCtx, SVal Value, + ErrnoCheckState EState); + +/// Set value of 'errno' to a concrete (signed) integer, if possible. +/// The errno check state is set always when the 'errno' value is set. +ProgramStateRef setErrnoValue(ProgramStateRef State, CheckerContext &C, + uint64_t Value, ErrnoCheckState EState); + +/// Set the errno check state, do not modify the errno value. +ProgramStateRef setErrnoState(ProgramStateRef State, ErrnoCheckState EState); + +/// Clear state of errno (make it irrelevant). +ProgramStateRef clearErrnoState(ProgramStateRef State); + +/// Determine if a `Decl` node related to 'errno'. +/// This is true if the declaration is the errno variable or a function +/// that returns a pointer to the 'errno' value (usually the 'errno' macro is +/// defined with this function). \p D is not required to be a canonical +/// declaration. +bool isErrno(const Decl *D); + +/// Create a NoteTag that displays the message if the 'errno' memory region is +/// marked as interesting, and resets the interestingness. +const NoteTag *getErrnoNoteTag(CheckerContext &C, const std::string &Message); + +/// Set errno state for the common case when a standard function is successful. +/// Set \c ErrnoCheckState to \c MustNotBeChecked (the \c errno value is not +/// affected). +ProgramStateRef setErrnoForStdSuccess(ProgramStateRef State, CheckerContext &C); + +/// Set errno state for the common case when a standard function fails. +/// Set \c errno value to be not equal to zero and \c ErrnoCheckState to +/// \c Irrelevant . The irrelevant errno state ensures that no related bug +/// report is emitted later and no note tag is needed. +/// \arg \c ErrnoSym Value to be used for \c errno and constrained to be +/// non-zero. +ProgramStateRef setErrnoForStdFailure(ProgramStateRef State, CheckerContext &C, + NonLoc ErrnoSym); + +/// Set errno state for the common case when a standard function indicates +/// failure only by \c errno. Sets \c ErrnoCheckState to \c MustBeChecked, and +/// invalidates the errno region (clear of previous value). +/// \arg \c InvalE Expression that causes invalidation of \c errno. +ProgramStateRef setErrnoStdMustBeChecked(ProgramStateRef State, + CheckerContext &C, const Expr *InvalE); + +} // namespace errno_modeling +} // namespace ento +} // namespace clang + +#endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ERRNOMODELING_H diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ErrnoTesterChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ErrnoTesterChecker.cpp new file mode 100644 index 000000000000..c46ebee0c94f --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ErrnoTesterChecker.cpp @@ -0,0 +1,185 @@ +//=== ErrnoTesterChecker.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 defines ErrnoTesterChecker, which is used to test functionality of the +// errno_check API. +// +//===----------------------------------------------------------------------===// + +#include "ErrnoModeling.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include <optional> + +using namespace clang; +using namespace ento; +using namespace errno_modeling; + +namespace { + +class ErrnoTesterChecker : public Checker<eval::Call> { +public: + bool evalCall(const CallEvent &Call, CheckerContext &C) const; + +private: + /// Evaluate function \code void ErrnoTesterChecker_setErrno(int) \endcode. + /// Set value of \c errno to the argument. + static void evalSetErrno(CheckerContext &C, const CallEvent &Call); + /// Evaluate function \code int ErrnoTesterChecker_getErrno() \endcode. + /// Return the value of \c errno. + static void evalGetErrno(CheckerContext &C, const CallEvent &Call); + /// Evaluate function \code int ErrnoTesterChecker_setErrnoIfError() \endcode. + /// Simulate a standard library function tha returns 0 on success and 1 on + /// failure. On the success case \c errno is not allowed to be used (may be + /// undefined). On the failure case \c errno is set to a fixed value 11 and + /// is not needed to be checked. + static void evalSetErrnoIfError(CheckerContext &C, const CallEvent &Call); + /// Evaluate function \code int ErrnoTesterChecker_setErrnoIfErrorRange() + /// \endcode. Same as \c ErrnoTesterChecker_setErrnoIfError but \c errno is + /// set to a range (to be nonzero) at the failure case. + static void evalSetErrnoIfErrorRange(CheckerContext &C, + const CallEvent &Call); + /// Evaluate function \code int ErrnoTesterChecker_setErrnoCheckState() + /// \endcode. This function simulates the following: + /// - Return 0 and leave \c errno with undefined value. + /// This is the case of a successful standard function call. + /// For example if \c ftell returns not -1. + /// - Return 1 and sets \c errno to a specific error code (1). + /// This is the case of a failed standard function call. + /// The function indicates the failure by a special return value + /// that is returned only at failure. + /// \c errno can be checked but it is not required. + /// For example if \c ftell returns -1. + /// - Return 2 and may set errno to a value (actually it does not set it). + /// This is the case of a standard function call where the failure can only + /// be checked by reading from \c errno. The value of \c errno is changed by + /// the function only at failure, the user should set \c errno to 0 before + /// the call (\c ErrnoChecker does not check for this rule). + /// \c strtol is an example of this case, if it returns \c LONG_MIN (or + /// \c LONG_MAX). This case applies only if \c LONG_MIN or \c LONG_MAX is + /// returned, otherwise the first case in this list applies. + static void evalSetErrnoCheckState(CheckerContext &C, const CallEvent &Call); + + using EvalFn = std::function<void(CheckerContext &, const CallEvent &)>; + const CallDescriptionMap<EvalFn> TestCalls{ + {{{"ErrnoTesterChecker_setErrno"}, 1}, &ErrnoTesterChecker::evalSetErrno}, + {{{"ErrnoTesterChecker_getErrno"}, 0}, &ErrnoTesterChecker::evalGetErrno}, + {{{"ErrnoTesterChecker_setErrnoIfError"}, 0}, + &ErrnoTesterChecker::evalSetErrnoIfError}, + {{{"ErrnoTesterChecker_setErrnoIfErrorRange"}, 0}, + &ErrnoTesterChecker::evalSetErrnoIfErrorRange}, + {{{"ErrnoTesterChecker_setErrnoCheckState"}, 0}, + &ErrnoTesterChecker::evalSetErrnoCheckState}}; +}; + +} // namespace + +void ErrnoTesterChecker::evalSetErrno(CheckerContext &C, + const CallEvent &Call) { + C.addTransition(setErrnoValue(C.getState(), C.getLocationContext(), + Call.getArgSVal(0), Irrelevant)); +} + +void ErrnoTesterChecker::evalGetErrno(CheckerContext &C, + const CallEvent &Call) { + ProgramStateRef State = C.getState(); + + std::optional<SVal> ErrnoVal = getErrnoValue(State); + assert(ErrnoVal && "Errno value should be available."); + State = + State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), *ErrnoVal); + + C.addTransition(State); +} + +void ErrnoTesterChecker::evalSetErrnoIfError(CheckerContext &C, + const CallEvent &Call) { + ProgramStateRef State = C.getState(); + SValBuilder &SVB = C.getSValBuilder(); + + ProgramStateRef StateSuccess = State->BindExpr( + Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(0, true)); + StateSuccess = setErrnoState(StateSuccess, MustNotBeChecked); + + ProgramStateRef StateFailure = State->BindExpr( + Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(1, true)); + StateFailure = setErrnoValue(StateFailure, C, 11, Irrelevant); + + C.addTransition(StateSuccess); + C.addTransition(StateFailure); +} + +void ErrnoTesterChecker::evalSetErrnoIfErrorRange(CheckerContext &C, + const CallEvent &Call) { + ProgramStateRef State = C.getState(); + SValBuilder &SVB = C.getSValBuilder(); + + ProgramStateRef StateSuccess = State->BindExpr( + Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(0, true)); + StateSuccess = setErrnoState(StateSuccess, MustNotBeChecked); + + ProgramStateRef StateFailure = State->BindExpr( + Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(1, true)); + DefinedOrUnknownSVal ErrnoVal = SVB.conjureSymbolVal( + nullptr, Call.getOriginExpr(), C.getLocationContext(), C.blockCount()); + StateFailure = StateFailure->assume(ErrnoVal, true); + assert(StateFailure && "Failed to assume on an initial value."); + StateFailure = + setErrnoValue(StateFailure, C.getLocationContext(), ErrnoVal, Irrelevant); + + C.addTransition(StateSuccess); + C.addTransition(StateFailure); +} + +void ErrnoTesterChecker::evalSetErrnoCheckState(CheckerContext &C, + const CallEvent &Call) { + ProgramStateRef State = C.getState(); + SValBuilder &SVB = C.getSValBuilder(); + + ProgramStateRef StateSuccess = State->BindExpr( + Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(0, true)); + StateSuccess = setErrnoState(StateSuccess, MustNotBeChecked); + + ProgramStateRef StateFailure1 = State->BindExpr( + Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(1, true)); + StateFailure1 = setErrnoValue(StateFailure1, C, 1, Irrelevant); + + ProgramStateRef StateFailure2 = State->BindExpr( + Call.getOriginExpr(), C.getLocationContext(), SVB.makeIntVal(2, true)); + StateFailure2 = setErrnoValue(StateFailure2, C, 2, MustBeChecked); + + C.addTransition(StateSuccess, + getErrnoNoteTag(C, "Assuming that this function succeeds but " + "sets 'errno' to an unspecified value.")); + C.addTransition(StateFailure1); + C.addTransition( + StateFailure2, + getErrnoNoteTag(C, "Assuming that this function returns 2. 'errno' " + "should be checked to test for failure.")); +} + +bool ErrnoTesterChecker::evalCall(const CallEvent &Call, + CheckerContext &C) const { + const EvalFn *Fn = TestCalls.lookup(Call); + if (Fn) { + (*Fn)(C, Call); + return C.isDifferent(); + } + return false; +} + +void ento::registerErrnoTesterChecker(CheckerManager &Mgr) { + Mgr.registerChecker<ErrnoTesterChecker>(); +} + +bool ento::shouldRegisterErrnoTesterChecker(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 2ce1bef6d228..3096999e9fd1 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp @@ -6,10 +6,10 @@ // //===----------------------------------------------------------------------===// -#include "Taint.h" #include "clang/Analysis/IssueHash.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Checkers/SValExplainer.h" +#include "clang/StaticAnalyzer/Checkers/Taint.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" @@ -17,6 +17,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/ScopedPrinter.h" +#include <optional> using namespace clang; using namespace ento; @@ -24,7 +25,7 @@ using namespace ento; namespace { class ExprInspectionChecker : public Checker<eval::Call, check::DeadSymbols, check::EndAnalysis> { - mutable std::unique_ptr<BugType> BT; + const BugType BT{this, "Checking analyzer assumptions", "debug"}; // These stats are per-analysis, not per-branch, hence they shouldn't // stay inside the program state. @@ -40,6 +41,8 @@ class ExprInspectionChecker void analyzerNumTimesReached(const CallExpr *CE, CheckerContext &C) const; void analyzerCrash(const CallExpr *CE, CheckerContext &C) const; void analyzerWarnOnDeadSymbol(const CallExpr *CE, CheckerContext &C) const; + void analyzerValue(const CallExpr *CE, CheckerContext &C) const; + void analyzerDumpSValType(const CallExpr *CE, CheckerContext &C) const; void analyzerDump(const CallExpr *CE, CheckerContext &C) const; void analyzerExplain(const CallExpr *CE, CheckerContext &C) const; void analyzerPrintState(const CallExpr *CE, CheckerContext &C) const; @@ -56,9 +59,10 @@ class ExprInspectionChecker // Optional parameter `ExprVal` for expression value to be marked interesting. ExplodedNode *reportBug(llvm::StringRef Msg, CheckerContext &C, - Optional<SVal> ExprVal = None) const; + std::optional<SVal> ExprVal = std::nullopt) const; ExplodedNode *reportBug(llvm::StringRef Msg, BugReporter &BR, ExplodedNode *N, - Optional<SVal> ExprVal = None) const; + std::optional<SVal> ExprVal = std::nullopt) const; + template <typename T> void printAndReport(CheckerContext &C, T What) const; const Expr *getArgExpr(const CallExpr *CE, CheckerContext &C) const; const MemRegion *getArgRegion(const CallExpr *CE, CheckerContext &C) const; @@ -98,6 +102,9 @@ bool ExprInspectionChecker::evalCall(const CallEvent &Call, &ExprInspectionChecker::analyzerDumpExtent) .Case("clang_analyzer_dumpElementCount", &ExprInspectionChecker::analyzerDumpElementCount) + .Case("clang_analyzer_value", &ExprInspectionChecker::analyzerValue) + .StartsWith("clang_analyzer_dumpSvalType", + &ExprInspectionChecker::analyzerDumpSValType) .StartsWith("clang_analyzer_dump", &ExprInspectionChecker::analyzerDump) .Case("clang_analyzer_getExtent", @@ -109,7 +116,8 @@ bool ExprInspectionChecker::evalCall(const CallEvent &Call, .Case("clang_analyzer_hashDump", &ExprInspectionChecker::analyzerHashDump) .Case("clang_analyzer_denote", &ExprInspectionChecker::analyzerDenote) - .Case("clang_analyzer_express", + .Case("clang_analyzer_express", // This also marks the argument as + // interesting. &ExprInspectionChecker::analyzerExpress) .StartsWith("clang_analyzer_isTainted", &ExprInspectionChecker::analyzerIsTainted) @@ -154,24 +162,21 @@ static const char *getArgumentValueString(const CallExpr *CE, } } -ExplodedNode *ExprInspectionChecker::reportBug(llvm::StringRef Msg, - CheckerContext &C, - Optional<SVal> ExprVal) const { +ExplodedNode * +ExprInspectionChecker::reportBug(llvm::StringRef Msg, CheckerContext &C, + std::optional<SVal> ExprVal) const { ExplodedNode *N = C.generateNonFatalErrorNode(); reportBug(Msg, C.getBugReporter(), N, ExprVal); return N; } -ExplodedNode *ExprInspectionChecker::reportBug(llvm::StringRef Msg, - BugReporter &BR, ExplodedNode *N, - Optional<SVal> ExprVal) const { +ExplodedNode * +ExprInspectionChecker::reportBug(llvm::StringRef Msg, BugReporter &BR, + ExplodedNode *N, + std::optional<SVal> ExprVal) const { if (!N) return nullptr; - - if (!BT) - BT.reset(new BugType(this, "Checking analyzer assumptions", "debug")); - - auto R = std::make_unique<PathSensitiveBugReport>(*BT, Msg, N); + auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N); if (ExprVal) { R->markInteresting(*ExprVal); } @@ -255,6 +260,55 @@ void ExprInspectionChecker::analyzerExplain(const CallExpr *CE, reportBug(Ex.Visit(V), C); } +static void printHelper(llvm::raw_svector_ostream &Out, CheckerContext &C, + const llvm::APSInt &I) { + Out << I.getBitWidth() << (I.isUnsigned() ? "u:" : "s:"); + Out << I; +} + +static void printHelper(llvm::raw_svector_ostream &Out, CheckerContext &C, + SymbolRef Sym) { + C.getConstraintManager().printValue(Out, C.getState(), Sym); +} + +static void printHelper(llvm::raw_svector_ostream &Out, CheckerContext &C, + SVal V) { + Out << V; +} + +template <typename T> +void ExprInspectionChecker::printAndReport(CheckerContext &C, T What) const { + llvm::SmallString<64> Str; + llvm::raw_svector_ostream OS(Str); + printHelper(OS, C, What); + reportBug(OS.str(), C); +} + +void ExprInspectionChecker::analyzerValue(const CallExpr *CE, + CheckerContext &C) const { + const Expr *Arg = getArgExpr(CE, C); + if (!Arg) + return; + + SVal V = C.getSVal(Arg); + if (const SymbolRef Sym = V.getAsSymbol()) + printAndReport(C, Sym); + else if (const llvm::APSInt *I = V.getAsInteger()) + printAndReport(C, *I); + else + reportBug("n/a", C); +} + +void ExprInspectionChecker::analyzerDumpSValType(const CallExpr *CE, + CheckerContext &C) const { + const Expr *Arg = getArgExpr(CE, C); + if (!Arg) + return; + + QualType Ty = C.getSVal(Arg).getType(C.getASTContext()); + reportBug(Ty.getAsString(), C); +} + void ExprInspectionChecker::analyzerDump(const CallExpr *CE, CheckerContext &C) const { const Expr *Arg = getArgExpr(CE, C); @@ -262,21 +316,17 @@ void ExprInspectionChecker::analyzerDump(const CallExpr *CE, return; SVal V = C.getSVal(Arg); - - llvm::SmallString<32> Str; - llvm::raw_svector_ostream OS(Str); - V.dumpToStream(OS); - reportBug(OS.str(), C); + printAndReport(C, V); } void ExprInspectionChecker::analyzerGetExtent(const CallExpr *CE, CheckerContext &C) const { - const MemRegion *MR = getArgRegion(CE, C); - if (!MR) + const Expr *Arg = getArgExpr(CE, C); + if (!Arg) return; ProgramStateRef State = C.getState(); - DefinedOrUnknownSVal Size = getDynamicExtent(State, MR, C.getSValBuilder()); + SVal Size = getDynamicExtentWithOffset(State, C.getSVal(Arg)); State = State->BindExpr(CE, C.getLocationContext(), Size); C.addTransition(State); @@ -284,17 +334,13 @@ void ExprInspectionChecker::analyzerGetExtent(const CallExpr *CE, void ExprInspectionChecker::analyzerDumpExtent(const CallExpr *CE, CheckerContext &C) const { - const MemRegion *MR = getArgRegion(CE, C); - if (!MR) + const Expr *Arg = getArgExpr(CE, C); + if (!Arg) return; - DefinedOrUnknownSVal Size = - getDynamicExtent(C.getState(), MR, C.getSValBuilder()); - - SmallString<64> Msg; - llvm::raw_svector_ostream Out(Msg); - Out << Size; - reportBug(Out.str(), C); + ProgramStateRef State = C.getState(); + SVal Size = getDynamicExtentWithOffset(State, C.getSVal(Arg)); + printAndReport(C, Size); } void ExprInspectionChecker::analyzerDumpElementCount(const CallExpr *CE, @@ -307,19 +353,14 @@ void ExprInspectionChecker::analyzerDumpElementCount(const CallExpr *CE, if (const auto *TVR = MR->getAs<TypedValueRegion>()) { ElementTy = TVR->getValueType(); } else { - ElementTy = - MR->castAs<SymbolicRegion>()->getSymbol()->getType()->getPointeeType(); + ElementTy = MR->castAs<SymbolicRegion>()->getPointeeStaticType(); } assert(!ElementTy->isPointerType()); - DefinedOrUnknownSVal ElementCount = - getDynamicElementCount(C.getState(), MR, C.getSValBuilder(), ElementTy); - - SmallString<128> Msg; - llvm::raw_svector_ostream Out(Msg); - Out << ElementCount; - reportBug(Out.str(), C); + DefinedOrUnknownSVal ElementCount = getDynamicElementCountWithOffset( + C.getState(), C.getSVal(getArgExpr(CE, C)), ElementTy); + printAndReport(C, ElementCount); } void ExprInspectionChecker::analyzerPrintState(const CallExpr *CE, @@ -348,8 +389,7 @@ void ExprInspectionChecker::checkDeadSymbols(SymbolReaper &SymReaper, ProgramStateRef State = C.getState(); const MarkedSymbolsTy &Syms = State->get<MarkedSymbols>(); ExplodedNode *N = C.getPredecessor(); - for (auto I = Syms.begin(), E = Syms.end(); I != E; ++I) { - SymbolRef Sym = *I; + for (SymbolRef Sym : Syms) { if (!SymReaper.isDead(Sym)) continue; @@ -423,50 +463,60 @@ void ExprInspectionChecker::analyzerDenote(const CallExpr *CE, namespace { class SymbolExpressor - : public SymExprVisitor<SymbolExpressor, Optional<std::string>> { + : public SymExprVisitor<SymbolExpressor, std::optional<std::string>> { ProgramStateRef State; public: SymbolExpressor(ProgramStateRef State) : State(State) {} - Optional<std::string> lookup(const SymExpr *S) { + std::optional<std::string> lookup(const SymExpr *S) { if (const StringLiteral *const *SLPtr = State->get<DenotedSymbols>(S)) { const StringLiteral *SL = *SLPtr; return std::string(SL->getBytes()); } - return None; + return std::nullopt; } - Optional<std::string> VisitSymExpr(const SymExpr *S) { return lookup(S); } + std::optional<std::string> VisitSymExpr(const SymExpr *S) { + return lookup(S); + } - Optional<std::string> VisitSymIntExpr(const SymIntExpr *S) { - if (Optional<std::string> Str = lookup(S)) + std::optional<std::string> VisitSymIntExpr(const SymIntExpr *S) { + if (std::optional<std::string> Str = lookup(S)) return Str; - if (Optional<std::string> Str = Visit(S->getLHS())) + if (std::optional<std::string> Str = Visit(S->getLHS())) return (*Str + " " + BinaryOperator::getOpcodeStr(S->getOpcode()) + " " + std::to_string(S->getRHS().getLimitedValue()) + (S->getRHS().isUnsigned() ? "U" : "")) .str(); - return None; + return std::nullopt; } - Optional<std::string> VisitSymSymExpr(const SymSymExpr *S) { - if (Optional<std::string> Str = lookup(S)) + std::optional<std::string> VisitSymSymExpr(const SymSymExpr *S) { + if (std::optional<std::string> Str = lookup(S)) return Str; - if (Optional<std::string> Str1 = Visit(S->getLHS())) - if (Optional<std::string> Str2 = Visit(S->getRHS())) + if (std::optional<std::string> Str1 = Visit(S->getLHS())) + if (std::optional<std::string> Str2 = Visit(S->getRHS())) return (*Str1 + " " + BinaryOperator::getOpcodeStr(S->getOpcode()) + " " + *Str2) .str(); - return None; + return std::nullopt; } - Optional<std::string> VisitSymbolCast(const SymbolCast *S) { - if (Optional<std::string> Str = lookup(S)) + std::optional<std::string> VisitUnarySymExpr(const UnarySymExpr *S) { + if (std::optional<std::string> Str = lookup(S)) return Str; - if (Optional<std::string> Str = Visit(S->getOperand())) + if (std::optional<std::string> Str = Visit(S->getOperand())) + return (UnaryOperator::getOpcodeStr(S->getOpcode()) + *Str).str(); + return std::nullopt; + } + + std::optional<std::string> VisitSymbolCast(const SymbolCast *S) { + if (std::optional<std::string> Str = lookup(S)) + return Str; + if (std::optional<std::string> Str = Visit(S->getOperand())) return (Twine("(") + S->getType().getAsString() + ")" + *Str).str(); - return None; + return std::nullopt; } }; } // namespace @@ -480,14 +530,14 @@ void ExprInspectionChecker::analyzerExpress(const CallExpr *CE, SVal ArgVal = C.getSVal(CE->getArg(0)); SymbolRef Sym = ArgVal.getAsSymbol(); if (!Sym) { - reportBug("Not a symbol", C); + reportBug("Not a symbol", C, ArgVal); return; } SymbolExpressor V(C.getState()); auto Str = V.Visit(Sym); if (!Str) { - reportBug("Unable to express", C); + reportBug("Unable to express", C, ArgVal); return; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp index 6275e49e51ae..7aefcdc6d358 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp @@ -24,7 +24,7 @@ using namespace ento; namespace { class FixedAddressChecker : public Checker< check::PreStmt<BinaryOperator> > { - mutable std::unique_ptr<BuiltinBug> BT; + const BugType BT{this, "Use fixed address"}; public: void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const; @@ -49,14 +49,11 @@ void FixedAddressChecker::checkPreStmt(const BinaryOperator *B, return; if (ExplodedNode *N = C.generateNonFatalErrorNode()) { - if (!BT) - BT.reset( - new BuiltinBug(this, "Use fixed address", - "Using a fixed address is not portable because that " - "address will probably not be valid in all " - "environments or platforms.")); - auto R = - std::make_unique<PathSensitiveBugReport>(*BT, BT->getDescription(), N); + // FIXME: improve grammar in the following strings: + constexpr llvm::StringLiteral Msg = + "Using a fixed address is not portable because that address will " + "probably not be valid in all environments or platforms."; + auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N); R->addRange(B->getRHS()->getSourceRange()); C.emitReport(std::move(R)); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp index e3f4be0726c8..079bc61a87d9 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp @@ -101,6 +101,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" #include "llvm/ADT/StringExtras.h" +#include <optional> using namespace clang; using namespace ento; @@ -254,9 +255,6 @@ static const ExplodedNode *getAcquireSite(const ExplodedNode *N, SymbolRef Sym, namespace { class FuchsiaHandleSymbolVisitor final : public SymbolVisitor { public: - FuchsiaHandleSymbolVisitor(ProgramStateRef State) : State(std::move(State)) {} - ProgramStateRef getState() const { return State; } - bool VisitSymbol(SymbolRef S) override { if (const auto *HandleType = S->getType()->getAs<TypedefType>()) if (HandleType->getDecl()->getName() == HandleTypeName) @@ -268,7 +266,6 @@ public: private: SmallVector<SymbolRef, 1024> Symbols; - ProgramStateRef State; }; } // end anonymous namespace @@ -284,7 +281,7 @@ getFuchsiaHandleSymbols(QualType QT, SVal Arg, ProgramStateRef State) { if (QT->isStructureType()) { // If we see a structure, see if there is any handle referenced by the // structure. - FuchsiaHandleSymbolVisitor Visitor(State); + FuchsiaHandleSymbolVisitor Visitor; State->scanReachableSymbols(Arg, Visitor); return Visitor.GetSymbols(); } @@ -304,7 +301,7 @@ getFuchsiaHandleSymbols(QualType QT, SVal Arg, ProgramStateRef State) { } } else { assert(PtrToHandleLevel == 1); - if (Optional<Loc> ArgLoc = Arg.getAs<Loc>()) { + if (std::optional<Loc> ArgLoc = Arg.getAs<Loc>()) { SymbolRef Sym = State->getSVal(*ArgLoc).getAsSymbol(); if (Sym) { return {Sym}; @@ -384,12 +381,12 @@ void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call, 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)) { + if (PathBR->getInterestingnessKind(RetSym)) { std::string SBuf; llvm::raw_string_ostream OS(SBuf); OS << "Function '" << FuncDecl->getDeclName() << "' returns an open handle"; - return OS.str(); + return SBuf; } else return ""; }); @@ -400,12 +397,12 @@ void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call, 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)) { + if (PathBR->getInterestingnessKind(RetSym)) { std::string SBuf; llvm::raw_string_ostream OS(SBuf); OS << "Function '" << FuncDecl->getDeclName() << "' returns an unowned handle"; - return OS.str(); + return SBuf; } else return ""; }); @@ -434,12 +431,12 @@ void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call, } else { Notes.push_back([Handle, ParamDiagIdx](BugReport &BR) -> std::string { auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR); - if (auto IsInteresting = PathBR->getInterestingnessKind(Handle)) { + if (PathBR->getInterestingnessKind(Handle)) { std::string SBuf; llvm::raw_string_ostream OS(SBuf); OS << "Handle released through " << ParamDiagIdx << llvm::getOrdinalSuffix(ParamDiagIdx) << " parameter"; - return OS.str(); + return SBuf; } else return ""; }); @@ -448,12 +445,12 @@ void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call, } else if (hasFuchsiaAttr<AcquireHandleAttr>(PVD)) { Notes.push_back([Handle, ParamDiagIdx](BugReport &BR) -> std::string { auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR); - if (auto IsInteresting = PathBR->getInterestingnessKind(Handle)) { + if (PathBR->getInterestingnessKind(Handle)) { std::string SBuf; llvm::raw_string_ostream OS(SBuf); OS << "Handle allocated through " << ParamDiagIdx << llvm::getOrdinalSuffix(ParamDiagIdx) << " parameter"; - return OS.str(); + return SBuf; } else return ""; }); @@ -462,12 +459,12 @@ void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call, } else if (hasFuchsiaUnownedAttr<AcquireHandleAttr>(PVD)) { Notes.push_back([Handle, ParamDiagIdx](BugReport &BR) -> std::string { auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR); - if (auto IsInteresting = PathBR->getInterestingnessKind(Handle)) { + if (PathBR->getInterestingnessKind(Handle)) { std::string SBuf; llvm::raw_string_ostream OS(SBuf); OS << "Unowned handle allocated through " << ParamDiagIdx << llvm::getOrdinalSuffix(ParamDiagIdx) << " parameter"; - return OS.str(); + return SBuf; } else return ""; }); @@ -656,10 +653,11 @@ void FuchsiaHandleChecker::reportBug(SymbolRef Sym, ExplodedNode *ErrorNode, if (Type.isSuppressOnSink()) { const ExplodedNode *AcquireNode = getAcquireSite(ErrorNode, Sym, C); if (AcquireNode) { + const Stmt *S = AcquireNode->getStmtForDiagnostics(); + assert(S && "Statement cannot be null."); PathDiagnosticLocation LocUsedForUniqueing = PathDiagnosticLocation::createBegin( - AcquireNode->getStmtForDiagnostics(), C.getSourceManager(), - AcquireNode->getLocationContext()); + S, C.getSourceManager(), AcquireNode->getLocationContext()); R = std::make_unique<PathSensitiveBugReport>( Type, Msg, ErrorNode, LocUsedForUniqueing, @@ -689,11 +687,10 @@ void FuchsiaHandleChecker::printState(raw_ostream &Out, ProgramStateRef State, if (!StateMap.isEmpty()) { Out << Sep << "FuchsiaHandleChecker :" << NL; - for (HStateMapTy::iterator I = StateMap.begin(), E = StateMap.end(); I != E; - ++I) { - I.getKey()->dumpToStream(Out); + for (const auto &[Sym, HandleState] : StateMap) { + Sym->dumpToStream(Out); Out << " : "; - I.getData().dump(Out); + HandleState.dump(Out); Out << NL; } } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp index 8e02ef74c668..5637941a58f0 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp @@ -73,7 +73,7 @@ decltype(auto) bindAssignmentToDecl(const char *DeclName) { static bool isTest(const Decl *D) { if (const auto* ND = dyn_cast<NamedDecl>(D)) { std::string DeclName = ND->getNameAsString(); - if (StringRef(DeclName).startswith("test")) + if (StringRef(DeclName).starts_with("test")) return true; } if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GTestChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GTestChecker.cpp index 8d9afbe88aa8..6c32a8dec844 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GTestChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GTestChecker.cpp @@ -20,6 +20,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "llvm/Support/raw_ostream.h" +#include <optional> using namespace clang; using namespace ento; @@ -91,11 +92,11 @@ using namespace ento; namespace { class GTestChecker : public Checker<check::PostCall> { - mutable IdentifierInfo *AssertionResultII; - mutable IdentifierInfo *SuccessII; + mutable IdentifierInfo *AssertionResultII = nullptr; + mutable IdentifierInfo *SuccessII = nullptr; public: - GTestChecker(); + GTestChecker() = default; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; @@ -119,8 +120,6 @@ private: }; } // End anonymous namespace. -GTestChecker::GTestChecker() : AssertionResultII(nullptr), SuccessII(nullptr) {} - /// Model a call to an un-inlined AssertionResult(bool) or /// AssertionResult(bool &, ...). /// To do so, constrain the value of the newly-constructed instance's 'success_' @@ -135,7 +134,7 @@ void GTestChecker::modelAssertionResultBoolConstructor( SVal BooleanArgVal = Call->getArgSVal(0); if (IsRef) { // The argument is a reference, so load from it to get the boolean value. - if (!BooleanArgVal.getAs<Loc>()) + if (!isa<Loc>(BooleanArgVal)) return; BooleanArgVal = C.getState()->getSVal(BooleanArgVal.castAs<Loc>()); } @@ -258,9 +257,9 @@ SVal GTestChecker::getAssertionResultSuccessFieldValue( if (!SuccessField) return UnknownVal(); - Optional<Loc> FieldLoc = + std::optional<Loc> FieldLoc = State->getLValue(SuccessField, Instance).getAs<Loc>(); - if (!FieldLoc.hasValue()) + if (!FieldLoc) return UnknownVal(); return State->getSVal(*FieldLoc); @@ -270,20 +269,17 @@ SVal GTestChecker::getAssertionResultSuccessFieldValue( ProgramStateRef GTestChecker::assumeValuesEqual(SVal Val1, SVal Val2, ProgramStateRef State, CheckerContext &C) { - if (!Val1.getAs<DefinedOrUnknownSVal>() || - !Val2.getAs<DefinedOrUnknownSVal>()) + auto DVal1 = Val1.getAs<DefinedOrUnknownSVal>(); + auto DVal2 = Val2.getAs<DefinedOrUnknownSVal>(); + if (!DVal1 || !DVal2) return State; auto ValuesEqual = - C.getSValBuilder().evalEQ(State, Val1.castAs<DefinedOrUnknownSVal>(), - Val2.castAs<DefinedOrUnknownSVal>()); - - if (!ValuesEqual.getAs<DefinedSVal>()) + C.getSValBuilder().evalEQ(State, *DVal1, *DVal2).getAs<DefinedSVal>(); + if (!ValuesEqual) return State; - State = C.getConstraintManager().assume( - State, ValuesEqual.castAs<DefinedSVal>(), true); - + State = C.getConstraintManager().assume(State, *ValuesEqual, true); return State; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp index 42c777eb2c52..4ceaf933d0bf 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -14,838 +14,1065 @@ // //===----------------------------------------------------------------------===// -#include "Taint.h" #include "Yaml.h" #include "clang/AST/Attr.h" #include "clang/Basic/Builtins.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Checkers/Taint.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/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/Support/YAMLTraits.h" -#include <algorithm> #include <limits> #include <memory> -#include <unordered_map> +#include <optional> #include <utility> +#include <vector> + +#define DEBUG_TYPE "taint-checker" using namespace clang; using namespace ento; using namespace taint; +using llvm::ImmutableSet; + namespace { -class GenericTaintChecker : public Checker<check::PreCall, check::PostCall> { -public: - static void *getTag() { - static int Tag; - return &Tag; + +class GenericTaintChecker; + +/// Check for CWE-134: Uncontrolled Format String. +constexpr llvm::StringLiteral MsgUncontrolledFormatString = + "Untrusted data is used as a format string " + "(CWE-134: Uncontrolled Format String)"; + +/// Check for: +/// CERT/STR02-C. "Sanitize data passed to complex subsystems" +/// CWE-78, "Failure to Sanitize Data into an OS Command" +constexpr llvm::StringLiteral MsgSanitizeSystemArgs = + "Untrusted data is passed to a system call " + "(CERT/STR02-C. Sanitize data passed to complex subsystems)"; + +/// Check if tainted data is used as a buffer size in strn.. functions, +/// and allocators. +constexpr llvm::StringLiteral MsgTaintedBufferSize = + "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)"; + +/// Check if tainted data is used as a custom sink's parameter. +constexpr llvm::StringLiteral MsgCustomSink = + "Untrusted data is passed to a user-defined sink"; + +using ArgIdxTy = int; +using ArgVecTy = llvm::SmallVector<ArgIdxTy, 2>; + +/// Denotes the return value. +constexpr ArgIdxTy ReturnValueIndex{-1}; + +static ArgIdxTy fromArgumentCount(unsigned Count) { + assert(Count <= + static_cast<std::size_t>(std::numeric_limits<ArgIdxTy>::max()) && + "ArgIdxTy is not large enough to represent the number of arguments."); + return Count; +} + +/// Check if the region the expression evaluates to is the standard input, +/// and thus, is tainted. +/// FIXME: Move this to Taint.cpp. +bool isStdin(SVal Val, const ASTContext &ACtx) { + // FIXME: What if Val is NonParamVarRegion? + + // The region should be symbolic, we do not know it's value. + const auto *SymReg = dyn_cast_or_null<SymbolicRegion>(Val.getAsRegion()); + if (!SymReg) + return false; + + // Get it's symbol and find the declaration region it's pointing to. + const auto *DeclReg = + dyn_cast_or_null<DeclRegion>(SymReg->getSymbol()->getOriginRegion()); + if (!DeclReg) + return false; + + // This region corresponds to a declaration, find out if it's a global/extern + // variable named stdin with the proper type. + if (const auto *D = dyn_cast_or_null<VarDecl>(DeclReg->getDecl())) { + D = D->getCanonicalDecl(); + if (D->getName() == "stdin" && D->hasExternalStorage() && D->isExternC()) { + const QualType FILETy = ACtx.getFILEType().getCanonicalType(); + const QualType Ty = D->getType().getCanonicalType(); + + if (Ty->isPointerType()) + return Ty->getPointeeType() == FILETy; + } } + return false; +} - void checkPreCall(const CallEvent &Call, CheckerContext &C) const; - void checkPostCall(const CallEvent &Call, CheckerContext &C) const; +SVal getPointeeOf(ProgramStateRef State, Loc LValue) { + const QualType ArgTy = LValue.getType(State->getStateManager().getContext()); + if (!ArgTy->isPointerType() || !ArgTy->getPointeeType()->isVoidType()) + return State->getSVal(LValue); - void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, - const char *Sep) const override; + // Do not dereference void pointers. Treat them as byte pointers instead. + // FIXME: we might want to consider more than just the first byte. + return State->getSVal(LValue, State->getStateManager().getContext().CharTy); +} - using ArgVector = SmallVector<unsigned, 2>; - using SignedArgVector = SmallVector<int, 2>; +/// Given a pointer/reference argument, return the value it refers to. +std::optional<SVal> getPointeeOf(ProgramStateRef State, SVal Arg) { + if (auto LValue = Arg.getAs<Loc>()) + return getPointeeOf(State, *LValue); + return std::nullopt; +} - enum class VariadicType { None, Src, Dst }; +/// Given a pointer, return the SVal of its pointee or if it is tainted, +/// otherwise return the pointer's SVal if tainted. +/// Also considers stdin as a taint source. +std::optional<SVal> getTaintedPointeeOrPointer(ProgramStateRef State, + SVal Arg) { + if (auto Pointee = getPointeeOf(State, Arg)) + if (isTainted(State, *Pointee)) // FIXME: isTainted(...) ? Pointee : None; + return Pointee; + + if (isTainted(State, Arg)) + return Arg; + return std::nullopt; +} - /// Used to parse the configuration file. - struct TaintConfiguration { - using NameScopeArgs = std::tuple<std::string, std::string, ArgVector>; - - struct Propagation { - std::string Name; - std::string Scope; - ArgVector SrcArgs; - SignedArgVector DstArgs; - VariadicType VarType; - unsigned VarIndex; - }; - - std::vector<Propagation> Propagations; - std::vector<NameScopeArgs> Filters; - std::vector<NameScopeArgs> Sinks; - - TaintConfiguration() = default; - TaintConfiguration(const TaintConfiguration &) = default; - TaintConfiguration(TaintConfiguration &&) = default; - TaintConfiguration &operator=(const TaintConfiguration &) = default; - TaintConfiguration &operator=(TaintConfiguration &&) = default; - }; +bool isTaintedOrPointsToTainted(ProgramStateRef State, SVal ExprSVal) { + return getTaintedPointeeOrPointer(State, ExprSVal).has_value(); +} - /// Convert SignedArgVector to ArgVector. - ArgVector convertToArgVector(CheckerManager &Mgr, const std::string &Option, - const SignedArgVector &Args); +/// Helps in printing taint diagnostics. +/// Marks the incoming parameters of a function interesting (to be printed) +/// when the return value, or the outgoing parameters are tainted. +const NoteTag *taintOriginTrackerTag(CheckerContext &C, + std::vector<SymbolRef> TaintedSymbols, + std::vector<ArgIdxTy> TaintedArgs, + const LocationContext *CallLocation) { + return C.getNoteTag([TaintedSymbols = std::move(TaintedSymbols), + TaintedArgs = std::move(TaintedArgs), CallLocation]( + PathSensitiveBugReport &BR) -> std::string { + SmallString<256> Msg; + // We give diagnostics only for taint related reports + if (!BR.isInteresting(CallLocation) || + BR.getBugType().getCategory() != categories::TaintedData) { + return ""; + } + if (TaintedSymbols.empty()) + return "Taint originated here"; - /// Parse the config. - void parseConfiguration(CheckerManager &Mgr, const std::string &Option, - TaintConfiguration &&Config); + for (auto Sym : TaintedSymbols) { + BR.markInteresting(Sym); + } + LLVM_DEBUG(for (auto Arg + : TaintedArgs) { + llvm::dbgs() << "Taint Propagated from argument " << Arg + 1 << "\n"; + }); + return ""; + }); +} + +/// Helps in printing taint diagnostics. +/// Marks the function interesting (to be printed) +/// when the return value, or the outgoing parameters are tainted. +const NoteTag *taintPropagationExplainerTag( + CheckerContext &C, std::vector<SymbolRef> TaintedSymbols, + std::vector<ArgIdxTy> TaintedArgs, const LocationContext *CallLocation) { + assert(TaintedSymbols.size() == TaintedArgs.size()); + return C.getNoteTag([TaintedSymbols = std::move(TaintedSymbols), + TaintedArgs = std::move(TaintedArgs), CallLocation]( + PathSensitiveBugReport &BR) -> std::string { + SmallString<256> Msg; + llvm::raw_svector_ostream Out(Msg); + // We give diagnostics only for taint related reports + if (TaintedSymbols.empty() || + BR.getBugType().getCategory() != categories::TaintedData) { + return ""; + } + int nofTaintedArgs = 0; + for (auto [Idx, Sym] : llvm::enumerate(TaintedSymbols)) { + if (BR.isInteresting(Sym)) { + BR.markInteresting(CallLocation); + if (TaintedArgs[Idx] != ReturnValueIndex) { + LLVM_DEBUG(llvm::dbgs() << "Taint Propagated to argument " + << TaintedArgs[Idx] + 1 << "\n"); + if (nofTaintedArgs == 0) + Out << "Taint propagated to the "; + else + Out << ", "; + Out << TaintedArgs[Idx] + 1 + << llvm::getOrdinalSuffix(TaintedArgs[Idx] + 1) << " argument"; + nofTaintedArgs++; + } else { + LLVM_DEBUG(llvm::dbgs() << "Taint Propagated to return value.\n"); + Out << "Taint propagated to the return value"; + } + } + } + return std::string(Out.str()); + }); +} + +/// ArgSet is used to describe arguments relevant for taint detection or +/// taint application. A discrete set of argument indexes and a variadic +/// argument list signified by a starting index are supported. +class ArgSet { +public: + ArgSet() = default; + ArgSet(ArgVecTy &&DiscreteArgs, + std::optional<ArgIdxTy> VariadicIndex = std::nullopt) + : DiscreteArgs(std::move(DiscreteArgs)), + VariadicIndex(std::move(VariadicIndex)) {} + + bool contains(ArgIdxTy ArgIdx) const { + if (llvm::is_contained(DiscreteArgs, ArgIdx)) + return true; + + return VariadicIndex && ArgIdx >= *VariadicIndex; + } - static const unsigned InvalidArgIndex{std::numeric_limits<unsigned>::max()}; - /// Denotes the return vale. - static const unsigned ReturnValueIndex{std::numeric_limits<unsigned>::max() - - 1}; + bool isEmpty() const { return DiscreteArgs.empty() && !VariadicIndex; } private: - mutable std::unique_ptr<BugType> BT; - void initBugType() const { - if (!BT) - BT = std::make_unique<BugType>(this, "Use of Untrusted Data", - "Untrusted Data"); + ArgVecTy DiscreteArgs; + std::optional<ArgIdxTy> VariadicIndex; +}; + +/// A struct used to specify taint propagation rules for a function. +/// +/// If any of the possible taint source arguments is tainted, all of the +/// destination arguments should also be tainted. If ReturnValueIndex is added +/// to the dst list, the return value will be tainted. +class GenericTaintRule { + /// Arguments which are taints sinks and should be checked, and a report + /// should be emitted if taint reaches these. + ArgSet SinkArgs; + /// Arguments which should be sanitized on function return. + ArgSet FilterArgs; + /// Arguments which can participate in taint propagation. If any of the + /// arguments in PropSrcArgs is tainted, all arguments in PropDstArgs should + /// be tainted. + ArgSet PropSrcArgs; + ArgSet PropDstArgs; + + /// A message that explains why the call is sensitive to taint. + std::optional<StringRef> SinkMsg; + + GenericTaintRule() = default; + + GenericTaintRule(ArgSet &&Sink, ArgSet &&Filter, ArgSet &&Src, ArgSet &&Dst, + std::optional<StringRef> SinkMsg = std::nullopt) + : SinkArgs(std::move(Sink)), FilterArgs(std::move(Filter)), + PropSrcArgs(std::move(Src)), PropDstArgs(std::move(Dst)), + SinkMsg(SinkMsg) {} + +public: + /// Make a rule that reports a warning if taint reaches any of \p FilterArgs + /// arguments. + static GenericTaintRule Sink(ArgSet &&SinkArgs, + std::optional<StringRef> Msg = std::nullopt) { + return {std::move(SinkArgs), {}, {}, {}, Msg}; } - struct FunctionData { - FunctionData() = delete; - FunctionData(const FunctionDecl *FDecl, StringRef Name, - std::string FullName) - : FDecl(FDecl), Name(Name), FullName(std::move(FullName)) {} - FunctionData(const FunctionData &) = default; - FunctionData(FunctionData &&) = default; - FunctionData &operator=(const FunctionData &) = delete; - FunctionData &operator=(FunctionData &&) = delete; - - static Optional<FunctionData> create(const CallEvent &Call, - const CheckerContext &C) { - if (!Call.getDecl()) - return None; - - const FunctionDecl *FDecl = Call.getDecl()->getAsFunction(); - if (!FDecl || (FDecl->getKind() != Decl::Function && - FDecl->getKind() != Decl::CXXMethod)) - return None; - - StringRef Name = C.getCalleeName(FDecl); - std::string FullName = FDecl->getQualifiedNameAsString(); - if (Name.empty() || FullName.empty()) - return None; - - return FunctionData{FDecl, Name, std::move(FullName)}; - } + /// Make a rule that sanitizes all FilterArgs arguments. + static GenericTaintRule Filter(ArgSet &&FilterArgs) { + return {{}, std::move(FilterArgs), {}, {}}; + } - bool isInScope(StringRef Scope) const { - return StringRef(FullName).startswith(Scope); - } + /// Make a rule that unconditionally taints all Args. + /// If Func is provided, it must also return true for taint to propagate. + static GenericTaintRule Source(ArgSet &&SourceArgs) { + return {{}, {}, {}, std::move(SourceArgs)}; + } - const FunctionDecl *const FDecl; - const StringRef Name; - const std::string FullName; - }; + /// Make a rule that taints all PropDstArgs if any of PropSrcArgs is tainted. + static GenericTaintRule Prop(ArgSet &&SrcArgs, ArgSet &&DstArgs) { + return {{}, {}, std::move(SrcArgs), std::move(DstArgs)}; + } - /// Catch taint related bugs. Check if tainted data is passed to a - /// system call etc. Returns true on matching. - bool checkPre(const CallEvent &Call, const FunctionData &FData, - CheckerContext &C) const; + /// Make a rule that taints all PropDstArgs if any of PropSrcArgs is tainted. + static GenericTaintRule + SinkProp(ArgSet &&SinkArgs, ArgSet &&SrcArgs, ArgSet &&DstArgs, + std::optional<StringRef> Msg = std::nullopt) { + return { + std::move(SinkArgs), {}, std::move(SrcArgs), std::move(DstArgs), Msg}; + } - /// Add taint sources on a pre-visit. Returns true on matching. - bool addSourcesPre(const CallEvent &Call, const FunctionData &FData, - CheckerContext &C) const; + /// Process a function which could either be a taint source, a taint sink, a + /// taint filter or a taint propagator. + void process(const GenericTaintChecker &Checker, const CallEvent &Call, + CheckerContext &C) const; - /// Mark filter's arguments not tainted on a pre-visit. Returns true on - /// matching. - bool addFiltersPre(const CallEvent &Call, const FunctionData &FData, - CheckerContext &C) const; + /// Handles the resolution of indexes of type ArgIdxTy to Expr*-s. + static const Expr *GetArgExpr(ArgIdxTy ArgIdx, const CallEvent &Call) { + return ArgIdx == ReturnValueIndex ? Call.getOriginExpr() + : Call.getArgExpr(ArgIdx); + }; - /// Propagate taint generated at pre-visit. Returns true on matching. - static bool propagateFromPre(const CallEvent &Call, CheckerContext &C); + /// Functions for custom taintedness propagation. + static bool UntrustedEnv(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); +using RuleLookupTy = CallDescriptionMap<GenericTaintRule>; - /// Given a pointer argument, return the value it points to. - static Optional<SVal> getPointeeOf(CheckerContext &C, const Expr *Arg); +/// Used to parse the configuration file. +struct TaintConfiguration { + using NameScopeArgs = std::tuple<std::string, std::string, ArgVecTy>; + enum class VariadicType { None, Src, Dst }; - /// 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 CallEvent &Call, - CheckerContext &C) const; + struct Common { + std::string Name; + std::string Scope; + }; - /// Check for: - /// CERT/STR02-C. "Sanitize data passed to complex subsystems" - /// CWE-78, "Failure to Sanitize Data into an OS Command" - 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 CallEvent &Call, StringRef Name, - CheckerContext &C) const; - - /// Check if tainted data is used as a buffer size ins strn.. functions, - /// and allocators. - static constexpr llvm::StringLiteral MsgTaintedBufferSize = - "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 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 CallEvent &Call, const FunctionData &FData, - CheckerContext &C) const; + struct Sink : Common { + ArgVecTy SinkArgs; + }; - /// Generate a report if the expression is tainted or points to tainted data. - bool generateReportIfTainted(const Expr *E, StringRef Msg, - CheckerContext &C) const; + struct Filter : Common { + ArgVecTy FilterArgs; + }; - struct TaintPropagationRule; - template <typename T> - using ConfigDataMap = - std::unordered_multimap<std::string, std::pair<std::string, T>>; - using NameRuleMap = ConfigDataMap<TaintPropagationRule>; - using NameArgMap = ConfigDataMap<ArgVector>; - - /// Find a function with the given name and scope. Returns the first match - /// or the end of the map. - template <typename T> - static auto findFunctionInConfig(const ConfigDataMap<T> &Map, - const FunctionData &FData); - - /// A struct used to specify taint propagation rules for a function. - /// - /// If any of the possible taint source arguments is tainted, all of the - /// destination arguments should also be tainted. Use InvalidArgIndex in the - /// src list to specify that all of the arguments can introduce taint. Use - /// InvalidArgIndex in the dst arguments to signify that all the non-const - /// pointer and reference arguments might be tainted on return. If - /// ReturnValueIndex is added to the dst list, the return value will be - /// tainted. - struct TaintPropagationRule { - using PropagationFuncType = bool (*)(bool IsTainted, const CallEvent &Call, - CheckerContext &C); - - /// List of arguments which can be taint sources and should be checked. - ArgVector SrcArgs; - /// List of arguments which should be tainted on function return. - ArgVector DstArgs; - /// Index for the first variadic parameter if exist. - unsigned VariadicIndex; - /// Show when a function has variadic parameters. If it has, it marks all - /// of them as source or destination. + struct Propagation : Common { + ArgVecTy SrcArgs; + ArgVecTy DstArgs; VariadicType VarType; - /// Special function for tainted source determination. If defined, it can - /// override the default behavior. - PropagationFuncType PropagationFunc; - - TaintPropagationRule() - : VariadicIndex(InvalidArgIndex), VarType(VariadicType::None), - PropagationFunc(nullptr) {} - - TaintPropagationRule(ArgVector &&Src, ArgVector &&Dst, - VariadicType Var = VariadicType::None, - unsigned VarIndex = InvalidArgIndex, - PropagationFuncType Func = nullptr) - : SrcArgs(std::move(Src)), DstArgs(std::move(Dst)), - VariadicIndex(VarIndex), VarType(Var), PropagationFunc(Func) {} - - /// Get the propagation rule for a given function. - static TaintPropagationRule - getTaintPropagationRule(const NameRuleMap &CustomPropagations, - const FunctionData &FData, CheckerContext &C); - - void addSrcArg(unsigned A) { SrcArgs.push_back(A); } - void addDstArg(unsigned A) { DstArgs.push_back(A); } - - bool isNull() const { - return SrcArgs.empty() && DstArgs.empty() && - VariadicType::None == VarType; - } + ArgIdxTy VarIndex; + }; - bool isDestinationArgument(unsigned ArgNum) const { - return (llvm::find(DstArgs, ArgNum) != DstArgs.end()); - } + std::vector<Propagation> Propagations; + std::vector<Filter> Filters; + std::vector<Sink> Sinks; - static bool isTaintedOrPointsToTainted(const Expr *E, - const ProgramStateRef &State, - CheckerContext &C) { - if (isTainted(State, E, C.getLocationContext()) || isStdin(E, C)) - return true; + TaintConfiguration() = default; + TaintConfiguration(const TaintConfiguration &) = default; + TaintConfiguration(TaintConfiguration &&) = default; + TaintConfiguration &operator=(const TaintConfiguration &) = default; + TaintConfiguration &operator=(TaintConfiguration &&) = default; +}; - if (!E->getType().getTypePtr()->isPointerType()) - return false; +struct GenericTaintRuleParser { + GenericTaintRuleParser(CheckerManager &Mgr) : Mgr(Mgr) {} + /// Container type used to gather call identification objects grouped into + /// pairs with their corresponding taint rules. It is temporary as it is used + /// to finally initialize RuleLookupTy, which is considered to be immutable. + using RulesContTy = std::vector<std::pair<CallDescription, GenericTaintRule>>; + RulesContTy parseConfiguration(const std::string &Option, + TaintConfiguration &&Config) const; - Optional<SVal> V = getPointeeOf(C, E); - return (V && isTainted(State, *V)); - } +private: + using NamePartsTy = llvm::SmallVector<StringRef, 2>; - /// Pre-process a function which propagates taint according to the - /// taint rule. - ProgramStateRef process(const CallEvent &Call, CheckerContext &C) const; + /// Validate part of the configuration, which contains a list of argument + /// indexes. + void validateArgVector(const std::string &Option, const ArgVecTy &Args) const; - // Functions for custom taintedness propagation. - static bool postSocket(bool IsTainted, const CallEvent &Call, - CheckerContext &C); - }; + template <typename Config> static NamePartsTy parseNameParts(const Config &C); - /// Defines a map between the propagation function's name, scope - /// and TaintPropagationRule. - NameRuleMap CustomPropagations; + // Takes the config and creates a CallDescription for it and associates a Rule + // with that. + template <typename Config> + static void consumeRulesFromConfig(const Config &C, GenericTaintRule &&Rule, + RulesContTy &Rules); - /// Defines a map between the filter function's name, scope and filtering - /// args. - NameArgMap CustomFilters; + void parseConfig(const std::string &Option, TaintConfiguration::Sink &&P, + RulesContTy &Rules) const; + void parseConfig(const std::string &Option, TaintConfiguration::Filter &&P, + RulesContTy &Rules) const; + void parseConfig(const std::string &Option, + TaintConfiguration::Propagation &&P, + RulesContTy &Rules) const; - /// Defines a map between the sink function's name, scope and sinking args. - NameArgMap CustomSinks; + CheckerManager &Mgr; }; -const unsigned GenericTaintChecker::ReturnValueIndex; -const unsigned GenericTaintChecker::InvalidArgIndex; +class GenericTaintChecker : public Checker<check::PreCall, check::PostCall> { +public: + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; -// FIXME: these lines can be removed in C++17 -constexpr llvm::StringLiteral GenericTaintChecker::MsgUncontrolledFormatString; -constexpr llvm::StringLiteral GenericTaintChecker::MsgSanitizeSystemArgs; -constexpr llvm::StringLiteral GenericTaintChecker::MsgTaintedBufferSize; -constexpr llvm::StringLiteral GenericTaintChecker::MsgCustomSink; -} // end of anonymous namespace + void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, + const char *Sep) const override; -using TaintConfig = GenericTaintChecker::TaintConfiguration; + /// Generate a report if the expression is tainted or points to tainted data. + bool generateReportIfTainted(const Expr *E, StringRef Msg, + CheckerContext &C) const; -LLVM_YAML_IS_SEQUENCE_VECTOR(TaintConfig::Propagation) -LLVM_YAML_IS_SEQUENCE_VECTOR(TaintConfig::NameScopeArgs) +private: + const BugType BT{this, "Use of Untrusted Data", categories::TaintedData}; + + bool checkUncontrolledFormatString(const CallEvent &Call, + CheckerContext &C) const; + + void taintUnsafeSocketProtocol(const CallEvent &Call, + CheckerContext &C) const; + + /// Default taint rules are initalized with the help of a CheckerContext to + /// access the names of built-in functions like memcpy. + void initTaintRules(CheckerContext &C) const; + + /// CallDescription currently cannot restrict matches to the global namespace + /// only, which is why multiple CallDescriptionMaps are used, as we want to + /// disambiguate global C functions from functions inside user-defined + /// namespaces. + // TODO: Remove separation to simplify matching logic once CallDescriptions + // are more expressive. + + mutable std::optional<RuleLookupTy> StaticTaintRules; + mutable std::optional<RuleLookupTy> DynamicTaintRules; +}; +} // end of anonymous namespace + +/// YAML serialization mapping. +LLVM_YAML_IS_SEQUENCE_VECTOR(TaintConfiguration::Sink) +LLVM_YAML_IS_SEQUENCE_VECTOR(TaintConfiguration::Filter) +LLVM_YAML_IS_SEQUENCE_VECTOR(TaintConfiguration::Propagation) namespace llvm { namespace yaml { -template <> struct MappingTraits<TaintConfig> { - static void mapping(IO &IO, TaintConfig &Config) { +template <> struct MappingTraits<TaintConfiguration> { + static void mapping(IO &IO, TaintConfiguration &Config) { IO.mapOptional("Propagations", Config.Propagations); IO.mapOptional("Filters", Config.Filters); IO.mapOptional("Sinks", Config.Sinks); } }; -template <> struct MappingTraits<TaintConfig::Propagation> { - static void mapping(IO &IO, TaintConfig::Propagation &Propagation) { +template <> struct MappingTraits<TaintConfiguration::Sink> { + static void mapping(IO &IO, TaintConfiguration::Sink &Sink) { + IO.mapRequired("Name", Sink.Name); + IO.mapOptional("Scope", Sink.Scope); + IO.mapRequired("Args", Sink.SinkArgs); + } +}; + +template <> struct MappingTraits<TaintConfiguration::Filter> { + static void mapping(IO &IO, TaintConfiguration::Filter &Filter) { + IO.mapRequired("Name", Filter.Name); + IO.mapOptional("Scope", Filter.Scope); + IO.mapRequired("Args", Filter.FilterArgs); + } +}; + +template <> struct MappingTraits<TaintConfiguration::Propagation> { + static void mapping(IO &IO, TaintConfiguration::Propagation &Propagation) { IO.mapRequired("Name", Propagation.Name); IO.mapOptional("Scope", Propagation.Scope); IO.mapOptional("SrcArgs", Propagation.SrcArgs); IO.mapOptional("DstArgs", Propagation.DstArgs); - IO.mapOptional("VariadicType", Propagation.VarType, - GenericTaintChecker::VariadicType::None); - IO.mapOptional("VariadicIndex", Propagation.VarIndex, - GenericTaintChecker::InvalidArgIndex); + IO.mapOptional("VariadicType", Propagation.VarType); + IO.mapOptional("VariadicIndex", Propagation.VarIndex); } }; -template <> struct ScalarEnumerationTraits<GenericTaintChecker::VariadicType> { - static void enumeration(IO &IO, GenericTaintChecker::VariadicType &Value) { - IO.enumCase(Value, "None", GenericTaintChecker::VariadicType::None); - IO.enumCase(Value, "Src", GenericTaintChecker::VariadicType::Src); - IO.enumCase(Value, "Dst", GenericTaintChecker::VariadicType::Dst); - } -}; - -template <> struct MappingTraits<TaintConfig::NameScopeArgs> { - static void mapping(IO &IO, TaintConfig::NameScopeArgs &NSA) { - IO.mapRequired("Name", std::get<0>(NSA)); - IO.mapOptional("Scope", std::get<1>(NSA)); - IO.mapRequired("Args", std::get<2>(NSA)); +template <> struct ScalarEnumerationTraits<TaintConfiguration::VariadicType> { + static void enumeration(IO &IO, TaintConfiguration::VariadicType &Value) { + IO.enumCase(Value, "None", TaintConfiguration::VariadicType::None); + IO.enumCase(Value, "Src", TaintConfiguration::VariadicType::Src); + IO.enumCase(Value, "Dst", TaintConfiguration::VariadicType::Dst); } }; } // namespace yaml } // namespace llvm /// A set which is used to pass information from call pre-visit instruction -/// to the call post-visit. The values are unsigned integers, which are either +/// to the call post-visit. The values are signed integers, which are either /// ReturnValueIndex, or indexes of the pointer/reference argument, which /// 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, - const SignedArgVector &Args) { - ArgVector Result; - for (int Arg : Args) { - if (Arg == -1) - Result.push_back(ReturnValueIndex); - else if (Arg < -1) { - Result.push_back(InvalidArgIndex); +REGISTER_MAP_WITH_PROGRAMSTATE(TaintArgsOnPostVisit, const LocationContext *, + ImmutableSet<ArgIdxTy>) +REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(ArgIdxFactory, ArgIdxTy) + +void GenericTaintRuleParser::validateArgVector(const std::string &Option, + const ArgVecTy &Args) const { + for (ArgIdxTy Arg : Args) { + if (Arg < ReturnValueIndex) { Mgr.reportInvalidCheckerOptionValue( - this, Option, + Mgr.getChecker<GenericTaintChecker>(), Option, "an argument number for propagation rules greater or equal to -1"); - } else - Result.push_back(static_cast<unsigned>(Arg)); + } } - return Result; } -void GenericTaintChecker::parseConfiguration(CheckerManager &Mgr, - const std::string &Option, - TaintConfiguration &&Config) { - for (auto &P : Config.Propagations) { - GenericTaintChecker::CustomPropagations.emplace( - P.Name, - std::make_pair(P.Scope, TaintPropagationRule{ - std::move(P.SrcArgs), - convertToArgVector(Mgr, Option, P.DstArgs), - P.VarType, P.VarIndex})); +template <typename Config> +GenericTaintRuleParser::NamePartsTy +GenericTaintRuleParser::parseNameParts(const Config &C) { + NamePartsTy NameParts; + if (!C.Scope.empty()) { + // If the Scope argument contains multiple "::" parts, those are considered + // namespace identifiers. + StringRef{C.Scope}.split(NameParts, "::", /*MaxSplit*/ -1, + /*KeepEmpty*/ false); } + NameParts.emplace_back(C.Name); + return NameParts; +} - for (auto &F : Config.Filters) { - GenericTaintChecker::CustomFilters.emplace( - std::get<0>(F), - std::make_pair(std::move(std::get<1>(F)), std::move(std::get<2>(F)))); - } +template <typename Config> +void GenericTaintRuleParser::consumeRulesFromConfig(const Config &C, + GenericTaintRule &&Rule, + RulesContTy &Rules) { + NamePartsTy NameParts = parseNameParts(C); + Rules.emplace_back(CallDescription(NameParts), std::move(Rule)); +} - for (auto &S : Config.Sinks) { - GenericTaintChecker::CustomSinks.emplace( - std::get<0>(S), - std::make_pair(std::move(std::get<1>(S)), std::move(std::get<2>(S)))); - } +void GenericTaintRuleParser::parseConfig(const std::string &Option, + TaintConfiguration::Sink &&S, + RulesContTy &Rules) const { + validateArgVector(Option, S.SinkArgs); + consumeRulesFromConfig(S, GenericTaintRule::Sink(std::move(S.SinkArgs)), + Rules); } -template <typename T> -auto GenericTaintChecker::findFunctionInConfig(const ConfigDataMap<T> &Map, - const FunctionData &FData) { - 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; - StringRef Scope = Value.first; - return Scope.empty() || FData.isInScope(Scope); - }); - return It != Range.second ? It : Map.end(); +void GenericTaintRuleParser::parseConfig(const std::string &Option, + TaintConfiguration::Filter &&S, + RulesContTy &Rules) const { + validateArgVector(Option, S.FilterArgs); + consumeRulesFromConfig(S, GenericTaintRule::Filter(std::move(S.FilterArgs)), + Rules); } -GenericTaintChecker::TaintPropagationRule -GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( - const NameRuleMap &CustomPropagations, const FunctionData &FData, - CheckerContext &C) { - // TODO: Currently, we might lose precision here: we always mark a return - // value as tainted even if it's just a pointer, pointing to tainted data. +void GenericTaintRuleParser::parseConfig(const std::string &Option, + TaintConfiguration::Propagation &&P, + RulesContTy &Rules) const { + validateArgVector(Option, P.SrcArgs); + validateArgVector(Option, P.DstArgs); + bool IsSrcVariadic = P.VarType == TaintConfiguration::VariadicType::Src; + bool IsDstVariadic = P.VarType == TaintConfiguration::VariadicType::Dst; + std::optional<ArgIdxTy> JustVarIndex = P.VarIndex; + + ArgSet SrcDesc(std::move(P.SrcArgs), + IsSrcVariadic ? JustVarIndex : std::nullopt); + ArgSet DstDesc(std::move(P.DstArgs), + IsDstVariadic ? JustVarIndex : std::nullopt); + + consumeRulesFromConfig( + P, GenericTaintRule::Prop(std::move(SrcDesc), std::move(DstDesc)), Rules); +} + +GenericTaintRuleParser::RulesContTy +GenericTaintRuleParser::parseConfiguration(const std::string &Option, + TaintConfiguration &&Config) const { + + RulesContTy Rules; + + for (auto &F : Config.Filters) + parseConfig(Option, std::move(F), Rules); + + for (auto &S : Config.Sinks) + parseConfig(Option, std::move(S), Rules); + for (auto &P : Config.Propagations) + parseConfig(Option, std::move(P), Rules); + + return Rules; +} + +void GenericTaintChecker::initTaintRules(CheckerContext &C) const { // Check for exact name match for functions without builtin substitutes. // Use qualified name, because these are C functions without namespace. - TaintPropagationRule Rule = - llvm::StringSwitch<TaintPropagationRule>(FData.FullName) - // Source functions - // TODO: Add support for vfscanf & family. - .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", {{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())) { - switch (BId) { - case Builtin::BImemcpy: - case Builtin::BImemmove: - case Builtin::BIstrncpy: - case Builtin::BIstrncat: - return {{1, 2}, {0, ReturnValueIndex}}; - case Builtin::BIstrlcpy: - case Builtin::BIstrlcat: - return {{1, 2}, {0}}; - case Builtin::BIstrndup: - return {{0, 1}, {ReturnValueIndex}}; - - default: - break; - } + + if (StaticTaintRules || DynamicTaintRules) + return; + + using RulesConstructionTy = + std::vector<std::pair<CallDescription, GenericTaintRule>>; + using TR = GenericTaintRule; + + const Builtin::Context &BI = C.getASTContext().BuiltinInfo; + + RulesConstructionTy GlobalCRules{ + // Sources + {{{"fdopen"}}, TR::Source({{ReturnValueIndex}})}, + {{{"fopen"}}, TR::Source({{ReturnValueIndex}})}, + {{{"freopen"}}, TR::Source({{ReturnValueIndex}})}, + {{{"getch"}}, TR::Source({{ReturnValueIndex}})}, + {{{"getchar"}}, TR::Source({{ReturnValueIndex}})}, + {{{"getchar_unlocked"}}, TR::Source({{ReturnValueIndex}})}, + {{{"gets"}}, TR::Source({{0}, ReturnValueIndex})}, + {{{"gets_s"}}, TR::Source({{0}, ReturnValueIndex})}, + {{{"scanf"}}, TR::Source({{}, 1})}, + {{{"scanf_s"}}, TR::Source({{}, {1}})}, + {{{"wgetch"}}, TR::Source({{}, ReturnValueIndex})}, + // Sometimes the line between taint sources and propagators is blurry. + // _IO_getc is choosen to be a source, but could also be a propagator. + // This way it is simpler, as modeling it as a propagator would require + // to model the possible sources of _IO_FILE * values, which the _IO_getc + // function takes as parameters. + {{{"_IO_getc"}}, TR::Source({{ReturnValueIndex}})}, + {{{"getcwd"}}, TR::Source({{0, ReturnValueIndex}})}, + {{{"getwd"}}, TR::Source({{0, ReturnValueIndex}})}, + {{{"readlink"}}, TR::Source({{1, ReturnValueIndex}})}, + {{{"readlinkat"}}, TR::Source({{2, ReturnValueIndex}})}, + {{{"get_current_dir_name"}}, TR::Source({{ReturnValueIndex}})}, + {{{"gethostname"}}, TR::Source({{0}})}, + {{{"getnameinfo"}}, TR::Source({{2, 4}})}, + {{{"getseuserbyname"}}, TR::Source({{1, 2}})}, + {{{"getgroups"}}, TR::Source({{1, ReturnValueIndex}})}, + {{{"getlogin"}}, TR::Source({{ReturnValueIndex}})}, + {{{"getlogin_r"}}, TR::Source({{0}})}, + + // Props + {{{"accept"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"atoi"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"atol"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"atoll"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"fgetc"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"fgetln"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"fgets"}}, TR::Prop({{2}}, {{0, ReturnValueIndex}})}, + {{{"fgetws"}}, TR::Prop({{2}}, {{0, ReturnValueIndex}})}, + {{{"fscanf"}}, TR::Prop({{0}}, {{}, 2})}, + {{{"fscanf_s"}}, TR::Prop({{0}}, {{}, {2}})}, + {{{"sscanf"}}, TR::Prop({{0}}, {{}, 2})}, + + {{{"getc"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"getc_unlocked"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"getdelim"}}, TR::Prop({{3}}, {{0}})}, + {{{"getline"}}, TR::Prop({{2}}, {{0}})}, + {{{"getw"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"pread"}}, TR::Prop({{0, 1, 2, 3}}, {{1, ReturnValueIndex}})}, + {{{"read"}}, TR::Prop({{0, 2}}, {{1, ReturnValueIndex}})}, + {{{"strchr"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"strrchr"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"tolower"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"toupper"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"fread"}}, TR::Prop({{3}}, {{0, ReturnValueIndex}})}, + {{{"recv"}}, TR::Prop({{0}}, {{1, ReturnValueIndex}})}, + {{{"recvfrom"}}, TR::Prop({{0}}, {{1, ReturnValueIndex}})}, + + {{{"ttyname"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"ttyname_r"}}, TR::Prop({{0}}, {{1, ReturnValueIndex}})}, + + {{{"basename"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"dirname"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"fnmatch"}}, TR::Prop({{1}}, {{ReturnValueIndex}})}, + {{{"memchr"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"memrchr"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"rawmemchr"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + + {{{"mbtowc"}}, TR::Prop({{1}}, {{0, ReturnValueIndex}})}, + {{{"wctomb"}}, TR::Prop({{1}}, {{0, ReturnValueIndex}})}, + {{{"wcwidth"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + + {{{"memcmp"}}, TR::Prop({{0, 1}}, {{ReturnValueIndex}})}, + {{{"memcpy"}}, TR::Prop({{1}}, {{0, ReturnValueIndex}})}, + {{{"memmove"}}, TR::Prop({{1}}, {{0, ReturnValueIndex}})}, + // If memmem was called with a tainted needle and the search was + // successful, that would mean that the value pointed by the return value + // has the same content as the needle. If we choose to go by the policy of + // content equivalence implies taintedness equivalence, that would mean + // haystack should be considered a propagation source argument. + {{{"memmem"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + + // The comment for memmem above also applies to strstr. + {{{"strstr"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"strcasestr"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + + {{{"strchrnul"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + + {{{"index"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"rindex"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + + // FIXME: In case of arrays, only the first element of the array gets + // tainted. + {{{"qsort"}}, TR::Prop({{0}}, {{0}})}, + {{{"qsort_r"}}, TR::Prop({{0}}, {{0}})}, + + {{{"strcmp"}}, TR::Prop({{0, 1}}, {{ReturnValueIndex}})}, + {{{"strcasecmp"}}, TR::Prop({{0, 1}}, {{ReturnValueIndex}})}, + {{{"strncmp"}}, TR::Prop({{0, 1, 2}}, {{ReturnValueIndex}})}, + {{{"strncasecmp"}}, TR::Prop({{0, 1, 2}}, {{ReturnValueIndex}})}, + {{{"strspn"}}, TR::Prop({{0, 1}}, {{ReturnValueIndex}})}, + {{{"strcspn"}}, TR::Prop({{0, 1}}, {{ReturnValueIndex}})}, + {{{"strpbrk"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"strndup"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"strndupa"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + + // strlen, wcslen, strnlen and alike intentionally don't propagate taint. + // See the details here: https://github.com/llvm/llvm-project/pull/66086 + + {{{"strtol"}}, TR::Prop({{0}}, {{1, ReturnValueIndex}})}, + {{{"strtoll"}}, TR::Prop({{0}}, {{1, ReturnValueIndex}})}, + {{{"strtoul"}}, TR::Prop({{0}}, {{1, ReturnValueIndex}})}, + {{{"strtoull"}}, TR::Prop({{0}}, {{1, ReturnValueIndex}})}, + + {{{"isalnum"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"isalpha"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"isascii"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"isblank"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"iscntrl"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"isdigit"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"isgraph"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"islower"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"isprint"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"ispunct"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"isspace"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"isupper"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{{"isxdigit"}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + + {{CDF_MaybeBuiltin, {BI.getName(Builtin::BIstrncat)}}, + TR::Prop({{1, 2}}, {{0, ReturnValueIndex}})}, + {{CDF_MaybeBuiltin, {BI.getName(Builtin::BIstrlcpy)}}, + TR::Prop({{1, 2}}, {{0}})}, + {{CDF_MaybeBuiltin, {BI.getName(Builtin::BIstrlcat)}}, + TR::Prop({{1, 2}}, {{0}})}, + {{CDF_MaybeBuiltin, {{"snprintf"}}}, + TR::Prop({{1}, 3}, {{0, ReturnValueIndex}})}, + {{CDF_MaybeBuiltin, {{"sprintf"}}}, + TR::Prop({{1}, 2}, {{0, ReturnValueIndex}})}, + {{CDF_MaybeBuiltin, {{"strcpy"}}}, + TR::Prop({{1}}, {{0, ReturnValueIndex}})}, + {{CDF_MaybeBuiltin, {{"stpcpy"}}}, + TR::Prop({{1}}, {{0, ReturnValueIndex}})}, + {{CDF_MaybeBuiltin, {{"strcat"}}}, + TR::Prop({{1}}, {{0, ReturnValueIndex}})}, + {{CDF_MaybeBuiltin, {{"wcsncat"}}}, + TR::Prop({{1}}, {{0, ReturnValueIndex}})}, + {{CDF_MaybeBuiltin, {{"strdup"}}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{CDF_MaybeBuiltin, {{"strdupa"}}}, + TR::Prop({{0}}, {{ReturnValueIndex}})}, + {{CDF_MaybeBuiltin, {{"wcsdup"}}}, TR::Prop({{0}}, {{ReturnValueIndex}})}, + + // Sinks + {{{"system"}}, TR::Sink({{0}}, MsgSanitizeSystemArgs)}, + {{{"popen"}}, TR::Sink({{0}}, MsgSanitizeSystemArgs)}, + {{{"execl"}}, TR::Sink({{}, {0}}, MsgSanitizeSystemArgs)}, + {{{"execle"}}, TR::Sink({{}, {0}}, MsgSanitizeSystemArgs)}, + {{{"execlp"}}, TR::Sink({{}, {0}}, MsgSanitizeSystemArgs)}, + {{{"execv"}}, TR::Sink({{0, 1}}, MsgSanitizeSystemArgs)}, + {{{"execve"}}, TR::Sink({{0, 1, 2}}, MsgSanitizeSystemArgs)}, + {{{"fexecve"}}, TR::Sink({{0, 1, 2}}, MsgSanitizeSystemArgs)}, + {{{"execvp"}}, TR::Sink({{0, 1}}, MsgSanitizeSystemArgs)}, + {{{"execvpe"}}, TR::Sink({{0, 1, 2}}, MsgSanitizeSystemArgs)}, + {{{"dlopen"}}, TR::Sink({{0}}, MsgSanitizeSystemArgs)}, + {{CDF_MaybeBuiltin, {{"malloc"}}}, TR::Sink({{0}}, MsgTaintedBufferSize)}, + {{CDF_MaybeBuiltin, {{"calloc"}}}, TR::Sink({{0}}, MsgTaintedBufferSize)}, + {{CDF_MaybeBuiltin, {{"alloca"}}}, TR::Sink({{0}}, MsgTaintedBufferSize)}, + {{CDF_MaybeBuiltin, {{"memccpy"}}}, + TR::Sink({{3}}, MsgTaintedBufferSize)}, + {{CDF_MaybeBuiltin, {{"realloc"}}}, + TR::Sink({{1}}, MsgTaintedBufferSize)}, + {{{{"setproctitle"}}}, TR::Sink({{0}, 1}, MsgUncontrolledFormatString)}, + {{{{"setproctitle_fast"}}}, + TR::Sink({{0}, 1}, MsgUncontrolledFormatString)}, + + // SinkProps + {{CDF_MaybeBuiltin, BI.getName(Builtin::BImemcpy)}, + TR::SinkProp({{2}}, {{1, 2}}, {{0, ReturnValueIndex}}, + MsgTaintedBufferSize)}, + {{CDF_MaybeBuiltin, {BI.getName(Builtin::BImemmove)}}, + TR::SinkProp({{2}}, {{1, 2}}, {{0, ReturnValueIndex}}, + MsgTaintedBufferSize)}, + {{CDF_MaybeBuiltin, {BI.getName(Builtin::BIstrncpy)}}, + TR::SinkProp({{2}}, {{1, 2}}, {{0, ReturnValueIndex}}, + MsgTaintedBufferSize)}, + {{CDF_MaybeBuiltin, {BI.getName(Builtin::BIstrndup)}}, + TR::SinkProp({{1}}, {{0, 1}}, {{ReturnValueIndex}}, + MsgTaintedBufferSize)}, + {{CDF_MaybeBuiltin, {{"bcopy"}}}, + TR::SinkProp({{2}}, {{0, 2}}, {{1}}, MsgTaintedBufferSize)}}; + + // `getenv` returns taint only in untrusted environments. + if (TR::UntrustedEnv(C)) { + // void setproctitle_init(int argc, char *argv[], char *envp[]) + GlobalCRules.push_back( + {{{"setproctitle_init"}}, TR::Sink({{1, 2}}, MsgCustomSink)}); + GlobalCRules.push_back({{{"getenv"}}, TR::Source({{ReturnValueIndex}})}); } - // Process all other functions which could be defined as builtins. - if (Rule.isNull()) { - 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}}; + StaticTaintRules.emplace(std::make_move_iterator(GlobalCRules.begin()), + std::make_move_iterator(GlobalCRules.end())); + + // User-provided taint configuration. + CheckerManager *Mgr = C.getAnalysisManager().getCheckerManager(); + assert(Mgr); + GenericTaintRuleParser ConfigParser{*Mgr}; + std::string Option{"Config"}; + StringRef ConfigFile = + Mgr->getAnalyzerOptions().getCheckerStringOption(this, Option); + std::optional<TaintConfiguration> Config = + getConfiguration<TaintConfiguration>(*Mgr, this, Option, ConfigFile); + if (!Config) { + // We don't have external taint config, no parsing required. + DynamicTaintRules = RuleLookupTy{}; + return; } - // Skipping the following functions, since they might be used for cleansing or - // smart memory copy: - // - memccpy - copying until hitting a special character. + GenericTaintRuleParser::RulesContTy Rules{ + ConfigParser.parseConfiguration(Option, std::move(*Config))}; - auto It = findFunctionInConfig(CustomPropagations, FData); - if (It != CustomPropagations.end()) - return It->second.second; - return {}; + DynamicTaintRules.emplace(std::make_move_iterator(Rules.begin()), + std::make_move_iterator(Rules.end())); } void GenericTaintChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { - 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(Call, *FData, C)) - return; - - // Marks the function's arguments and/or return value tainted if it present in - // the list. - if (addSourcesPre(Call, *FData, C)) - return; - - addFiltersPre(Call, *FData, C); + initTaintRules(C); + + // FIXME: this should be much simpler. + if (const auto *Rule = + Call.isGlobalCFunction() ? StaticTaintRules->lookup(Call) : nullptr) + Rule->process(*this, Call, C); + else if (const auto *Rule = DynamicTaintRules->lookup(Call)) + Rule->process(*this, Call, C); + + // FIXME: These edge cases are to be eliminated from here eventually. + // + // Additional check that is not supported by CallDescription. + // TODO: Make CallDescription be able to match attributes such as printf-like + // arguments. + checkUncontrolledFormatString(Call, C); + + // TODO: Modeling sockets should be done in a specific checker. + // Socket is a source, which taints the return value. + taintUnsafeSocketProtocol(Call, C); } void GenericTaintChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { // Set the marked values as tainted. The return value only accessible from // checkPostStmt. - propagateFromPre(Call, C); -} - -void GenericTaintChecker::printState(raw_ostream &Out, ProgramStateRef State, - const char *NL, const char *Sep) const { - printTaint(State, Out, NL, Sep); -} - -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(Call, C); - if (State) { - C.addTransition(State); - return true; - } - } - return false; -} - -bool GenericTaintChecker::addFiltersPre(const CallEvent &Call, - const FunctionData &FData, - CheckerContext &C) const { - auto It = findFunctionInConfig(CustomFilters, FData); - if (It == CustomFilters.end()) - return false; - - ProgramStateRef State = C.getState(); - const auto &Value = It->second; - const ArgVector &Args = Value.second; - for (unsigned ArgNum : Args) { - if (ArgNum >= Call.getNumArgs()) - continue; - - const Expr *Arg = Call.getArgExpr(ArgNum); - Optional<SVal> V = getPointeeOf(C, Arg); - if (V) - State = removeTaint(State, *V); - } - - if (State != C.getState()) { - C.addTransition(State); - return true; - } - return false; -} - -bool GenericTaintChecker::propagateFromPre(const CallEvent &Call, - CheckerContext &C) { ProgramStateRef State = C.getState(); + const StackFrameContext *CurrentFrame = C.getStackFrame(); // Depending on what was tainted at pre-visit, we determined a set of // arguments which should be tainted after the function returns. These are // stored in the state as TaintArgsOnPostVisit set. - TaintArgsOnPostVisitTy TaintArgs = State->get<TaintArgsOnPostVisit>(); - if (TaintArgs.isEmpty()) - return false; + TaintArgsOnPostVisitTy TaintArgsMap = State->get<TaintArgsOnPostVisit>(); - for (unsigned ArgNum : TaintArgs) { + const ImmutableSet<ArgIdxTy> *TaintArgs = TaintArgsMap.lookup(CurrentFrame); + if (!TaintArgs) + return; + assert(!TaintArgs->isEmpty()); + + LLVM_DEBUG(for (ArgIdxTy I + : *TaintArgs) { + llvm::dbgs() << "PostCall<"; + Call.dump(llvm::dbgs()); + llvm::dbgs() << "> actually wants to taint arg index: " << I << '\n'; + }); + + const NoteTag *InjectionTag = nullptr; + std::vector<SymbolRef> TaintedSymbols; + std::vector<ArgIdxTy> TaintedIndexes; + for (ArgIdxTy ArgNum : *TaintArgs) { // Special handling for the tainted return value. if (ArgNum == ReturnValueIndex) { State = addTaint(State, Call.getReturnValue()); + std::vector<SymbolRef> TaintedSyms = + getTaintedSymbols(State, Call.getReturnValue()); + if (!TaintedSyms.empty()) { + TaintedSymbols.push_back(TaintedSyms[0]); + TaintedIndexes.push_back(ArgNum); + } continue; } - // The arguments are pointer arguments. The data they are pointing at is // tainted after the call. - if (Call.getNumArgs() < (ArgNum + 1)) - return false; - const Expr *Arg = Call.getArgExpr(ArgNum); - Optional<SVal> V = getPointeeOf(C, Arg); - if (V) + if (auto V = getPointeeOf(State, Call.getArgSVal(ArgNum))) { State = addTaint(State, *V); + std::vector<SymbolRef> TaintedSyms = getTaintedSymbols(State, *V); + if (!TaintedSyms.empty()) { + TaintedSymbols.push_back(TaintedSyms[0]); + TaintedIndexes.push_back(ArgNum); + } + } } - + // Create a NoteTag callback, which prints to the user where the taintedness + // was propagated to. + InjectionTag = taintPropagationExplainerTag(C, TaintedSymbols, TaintedIndexes, + Call.getCalleeStackFrame(0)); // Clear up the taint info from the state. - State = State->remove<TaintArgsOnPostVisit>(); - - if (State != C.getState()) { - C.addTransition(State); - return true; - } - return false; -} - -bool GenericTaintChecker::checkPre(const CallEvent &Call, - const FunctionData &FData, - CheckerContext &C) const { - if (checkUncontrolledFormatString(Call, C)) - return true; - - if (checkSystemCall(Call, FData.Name, C)) - return true; - - if (checkTaintedBufferSize(Call, C)) - return true; - - return checkCustomSinks(Call, FData, C); + State = State->remove<TaintArgsOnPostVisit>(CurrentFrame); + C.addTransition(State, InjectionTag); } -Optional<SVal> GenericTaintChecker::getPointeeOf(CheckerContext &C, - const Expr *Arg) { - ProgramStateRef State = C.getState(); - SVal AddrVal = C.getSVal(Arg->IgnoreParens()); - if (AddrVal.isUnknownOrUndef()) - return None; - - Optional<Loc> AddrLoc = AddrVal.getAs<Loc>(); - if (!AddrLoc) - return None; - - QualType ArgTy = Arg->getType().getCanonicalType(); - if (!ArgTy->isPointerType()) - return State->getSVal(*AddrLoc); - - QualType ValTy = ArgTy->getPointeeType(); - - // Do not dereference void pointers. Treat them as byte pointers instead. - // FIXME: we might want to consider more than just the first byte. - if (ValTy->isVoidType()) - ValTy = C.getASTContext().CharTy; - - return State->getSVal(*AddrLoc, ValTy); +void GenericTaintChecker::printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const { + printTaint(State, Out, NL, Sep); } -ProgramStateRef -GenericTaintChecker::TaintPropagationRule::process(const CallEvent &Call, - CheckerContext &C) const { +void GenericTaintRule::process(const GenericTaintChecker &Checker, + const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); + const ArgIdxTy CallNumArgs = fromArgumentCount(Call.getNumArgs()); - // Check for taint in arguments. - bool IsTainted = true; - for (unsigned ArgNum : SrcArgs) { - if (ArgNum >= Call.getNumArgs()) - continue; - - 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 < Call.getNumArgs(); ++i) { - if ((IsTainted = - isTaintedOrPointsToTainted(Call.getArgExpr(i), State, C))) - break; + /// Iterate every call argument, and get their corresponding Expr and SVal. + const auto ForEachCallArg = [&C, &Call, CallNumArgs](auto &&Fun) { + for (ArgIdxTy I = ReturnValueIndex; I < CallNumArgs; ++I) { + const Expr *E = GetArgExpr(I, Call); + Fun(I, E, C.getSVal(E)); } - } + }; - if (PropagationFunc) - IsTainted = PropagationFunc(IsTainted, Call, C); + /// Check for taint sinks. + ForEachCallArg([this, &Checker, &C, &State](ArgIdxTy I, const Expr *E, SVal) { + // Add taintedness to stdin parameters + if (isStdin(C.getSVal(E), C.getASTContext())) { + State = addTaint(State, C.getSVal(E)); + } + if (SinkArgs.contains(I) && isTaintedOrPointsToTainted(State, C.getSVal(E))) + Checker.generateReportIfTainted(E, SinkMsg.value_or(MsgCustomSink), C); + }); + + /// Check for taint filters. + ForEachCallArg([this, &State](ArgIdxTy I, const Expr *E, SVal S) { + if (FilterArgs.contains(I)) { + State = removeTaint(State, S); + if (auto P = getPointeeOf(State, S)) + State = removeTaint(State, *P); + } + }); + + /// Check for taint propagation sources. + /// A rule will make the destination variables tainted if PropSrcArgs + /// is empty (taints the destination + /// arguments unconditionally), or if any of its signified + /// args are tainted in context of the current CallEvent. + bool IsMatching = PropSrcArgs.isEmpty(); + std::vector<SymbolRef> TaintedSymbols; + std::vector<ArgIdxTy> TaintedIndexes; + ForEachCallArg([this, &C, &IsMatching, &State, &TaintedSymbols, + &TaintedIndexes](ArgIdxTy I, const Expr *E, SVal) { + std::optional<SVal> TaintedSVal = + getTaintedPointeeOrPointer(State, C.getSVal(E)); + IsMatching = + IsMatching || (PropSrcArgs.contains(I) && TaintedSVal.has_value()); + + // We track back tainted arguments except for stdin + if (TaintedSVal && !isStdin(*TaintedSVal, C.getASTContext())) { + std::vector<SymbolRef> TaintedArgSyms = + getTaintedSymbols(State, *TaintedSVal); + if (!TaintedArgSyms.empty()) { + llvm::append_range(TaintedSymbols, TaintedArgSyms); + TaintedIndexes.push_back(I); + } + } + }); - if (!IsTainted) - return State; + // Early return for propagation rules which dont match. + // Matching propagations, Sinks and Filters will pass this point. + if (!IsMatching) + return; - // Mark the arguments which should be tainted after the function returns. - for (unsigned ArgNum : DstArgs) { - // Should mark the return value? - if (ArgNum == ReturnValueIndex) { - State = State->add<TaintArgsOnPostVisit>(ReturnValueIndex); - continue; - } + const auto WouldEscape = [](SVal V, QualType Ty) -> bool { + if (!isa<Loc>(V)) + return false; - if (ArgNum >= Call.getNumArgs()) - continue; + const bool IsNonConstRef = Ty->isReferenceType() && !Ty.isConstQualified(); + const bool IsNonConstPtr = + Ty->isPointerType() && !Ty->getPointeeType().isConstQualified(); - // Mark the given argument. - State = State->add<TaintArgsOnPostVisit>(ArgNum); - } + return IsNonConstRef || IsNonConstPtr; + }; - // Mark all variadic arguments tainted if present. - if (VariadicType::Dst == VarType) { - // For all pointer and references that were passed in: - // 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 < 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())) { - State = State->add<TaintArgsOnPostVisit>(i); - } - } - } + /// Propagate taint where it is necessary. + auto &F = State->getStateManager().get_context<ArgIdxFactory>(); + ImmutableSet<ArgIdxTy> Result = F.getEmptySet(); + ForEachCallArg( + [&](ArgIdxTy I, const Expr *E, SVal V) { + if (PropDstArgs.contains(I)) { + LLVM_DEBUG(llvm::dbgs() << "PreCall<"; Call.dump(llvm::dbgs()); + llvm::dbgs() + << "> prepares tainting arg index: " << I << '\n';); + Result = F.add(Result, I); + } + + // Taint property gets lost if the variable is passed as a + // non-const pointer or reference to a function which is + // not inlined. For matching rules we want to preserve the taintedness. + // TODO: We should traverse all reachable memory regions via the + // escaping parameter. Instead of doing that we simply mark only the + // referred memory region as tainted. + if (WouldEscape(V, E->getType()) && getTaintedPointeeOrPointer(State, V)) { + LLVM_DEBUG(if (!Result.contains(I)) { + llvm::dbgs() << "PreCall<"; + Call.dump(llvm::dbgs()); + llvm::dbgs() << "> prepares tainting arg index: " << I << '\n'; + }); + Result = F.add(Result, I); + } + }); - return State; + if (!Result.isEmpty()) + State = State->set<TaintArgsOnPostVisit>(C.getStackFrame(), Result); + const NoteTag *InjectionTag = taintOriginTrackerTag( + C, std::move(TaintedSymbols), std::move(TaintedIndexes), + Call.getCalleeStackFrame(0)); + C.addTransition(State, InjectionTag); } -// If argument 0(protocol domain) is network, the return value should get taint. -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; +bool GenericTaintRule::UntrustedEnv(CheckerContext &C) { + return !C.getAnalysisManager() + .getAnalyzerOptions() + .ShouldAssumeControlledEnvironment; } -bool GenericTaintChecker::isStdin(const Expr *E, CheckerContext &C) { - ProgramStateRef State = C.getState(); - SVal Val = C.getSVal(E); - - // stdin is a pointer, so it would be a region. - const MemRegion *MemReg = Val.getAsRegion(); - - // The region should be symbolic, we do not know it's value. - const auto *SymReg = dyn_cast_or_null<SymbolicRegion>(MemReg); - if (!SymReg) - return false; +bool GenericTaintChecker::generateReportIfTainted(const Expr *E, StringRef Msg, + CheckerContext &C) const { + assert(E); + std::optional<SVal> TaintedSVal = + getTaintedPointeeOrPointer(C.getState(), C.getSVal(E)); - // Get it's symbol and find the declaration region it's pointing to. - const auto *Sm = dyn_cast<SymbolRegionValue>(SymReg->getSymbol()); - if (!Sm) - return false; - const auto *DeclReg = dyn_cast_or_null<DeclRegion>(Sm->getRegion()); - if (!DeclReg) + if (!TaintedSVal) return false; - // This region corresponds to a declaration, find out if it's a global/extern - // variable named stdin with the proper type. - if (const auto *D = dyn_cast_or_null<VarDecl>(DeclReg->getDecl())) { - D = D->getCanonicalDecl(); - if ((D->getName().find("stdin") != StringRef::npos) && D->isExternC()) { - const auto *PtrTy = dyn_cast<PointerType>(D->getType().getTypePtr()); - if (PtrTy && PtrTy->getPointeeType().getCanonicalType() == - C.getASTContext().getFILEType().getCanonicalType()) - return true; + // Generate diagnostic. + if (ExplodedNode *N = C.generateNonFatalErrorNode()) { + auto report = std::make_unique<PathSensitiveBugReport>(BT, Msg, N); + report->addRange(E->getSourceRange()); + for (auto TaintedSym : getTaintedSymbols(C.getState(), *TaintedSVal)) { + report->markInteresting(TaintedSym); } + + C.emitReport(std::move(report)); + return true; } return false; } +/// TODO: remove checking for printf format attributes and socket whitelisting +/// from GenericTaintChecker, and that means the following functions: +/// getPrintfFormatArgumentNum, +/// GenericTaintChecker::checkUncontrolledFormatString, +/// GenericTaintChecker::taintUnsafeSocketProtocol + static bool getPrintfFormatArgumentNum(const CallEvent &Call, const CheckerContext &C, - unsigned &ArgNum) { + ArgIdxTy &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 = Call.getDecl()->getAsFunction(); + const Decl *CallDecl = Call.getDecl(); + if (!CallDecl) + return false; + const FunctionDecl *FDecl = CallDecl->getAsFunction(); if (!FDecl) return false; + + const ArgIdxTy CallNumArgs = fromArgumentCount(Call.getNumArgs()); + for (const auto *Format : FDecl->specific_attrs<FormatAttr>()) { ArgNum = Format->getFormatIdx() - 1; - if ((Format->getType()->getName() == "printf") && - Call.getNumArgs() > ArgNum) + if ((Format->getType()->getName() == "printf") && CallNumArgs > ArgNum) return true; } - // Or if a function is named setproctitle (this is a heuristic). - if (C.getCalleeName(FDecl).find("setproctitle") != StringRef::npos) { - ArgNum = 0; - return true; - } - - return false; -} - -bool GenericTaintChecker::generateReportIfTainted(const Expr *E, StringRef Msg, - CheckerContext &C) const { - assert(E); - - // Check for taint. - ProgramStateRef State = C.getState(); - Optional<SVal> PointedToSVal = getPointeeOf(C, E); - SVal TaintedSVal; - if (PointedToSVal && isTainted(State, *PointedToSVal)) - TaintedSVal = *PointedToSVal; - else if (isTainted(State, E, C.getLocationContext())) - TaintedSVal = C.getSVal(E); - else - return false; - - // Generate diagnostic. - if (ExplodedNode *N = C.generateNonFatalErrorNode()) { - initBugType(); - auto report = std::make_unique<PathSensitiveBugReport>(*BT, Msg, N); - report->addRange(E->getSourceRange()); - report->addVisitor(std::make_unique<TaintBugVisitor>(TaintedSVal)); - C.emitReport(std::move(report)); - return true; - } return false; } bool GenericTaintChecker::checkUncontrolledFormatString( const CallEvent &Call, CheckerContext &C) const { // Check if the function contains a format string argument. - unsigned ArgNum = 0; + ArgIdxTy ArgNum = 0; if (!getPrintfFormatArgumentNum(Call, C, ArgNum)) return false; @@ -855,102 +1082,35 @@ bool GenericTaintChecker::checkUncontrolledFormatString( MsgUncontrolledFormatString, C); } -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 - // need to know if the user was reset before these calls(seteuid). - unsigned ArgNum = llvm::StringSwitch<unsigned>(Name) - .Case("system", 0) - .Case("popen", 0) - .Case("execl", 0) - .Case("execle", 0) - .Case("execlp", 0) - .Case("execv", 0) - .Case("execvp", 0) - .Case("execvP", 0) - .Case("execve", 0) - .Case("dlopen", 0) - .Default(InvalidArgIndex); - - if (ArgNum == InvalidArgIndex || Call.getNumArgs() < (ArgNum + 1)) - return false; - - 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 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())) { - switch (BId) { - case Builtin::BImemcpy: - case Builtin::BImemmove: - case Builtin::BIstrncpy: - ArgNum = 2; - break; - case Builtin::BIstrndup: - ArgNum = 1; - break; - default: - break; - } - } - - if (ArgNum == InvalidArgIndex) { - using CCtx = CheckerContext; - if (CCtx::isCLibraryFunction(FDecl, "malloc") || - CCtx::isCLibraryFunction(FDecl, "calloc") || - CCtx::isCLibraryFunction(FDecl, "alloca")) - ArgNum = 0; - else if (CCtx::isCLibraryFunction(FDecl, "memccpy")) - ArgNum = 3; - else if (CCtx::isCLibraryFunction(FDecl, "realloc")) - ArgNum = 1; - else if (CCtx::isCLibraryFunction(FDecl, "bcopy")) - ArgNum = 2; - } - - return ArgNum != InvalidArgIndex && Call.getNumArgs() > ArgNum && - generateReportIfTainted(Call.getArgExpr(ArgNum), MsgTaintedBufferSize, - C); -} - -bool GenericTaintChecker::checkCustomSinks(const CallEvent &Call, - const FunctionData &FData, - CheckerContext &C) const { - auto It = findFunctionInConfig(CustomSinks, FData); - if (It == CustomSinks.end()) - return false; - - const auto &Value = It->second; - const GenericTaintChecker::ArgVector &Args = Value.second; - for (unsigned ArgNum : Args) { - if (ArgNum >= Call.getNumArgs()) - continue; +void GenericTaintChecker::taintUnsafeSocketProtocol(const CallEvent &Call, + CheckerContext &C) const { + if (Call.getNumArgs() < 1) + return; + const IdentifierInfo *ID = Call.getCalleeIdentifier(); + if (!ID) + return; + if (!ID->getName().equals("socket")) + return; - if (generateReportIfTainted(Call.getArgExpr(ArgNum), MsgCustomSink, C)) - return true; - } + SourceLocation DomLoc = Call.getArgExpr(0)->getExprLoc(); + StringRef DomName = C.getMacroNameOrSpelling(DomLoc); + // Allow internal communication protocols. + bool SafeProtocol = DomName.equals("AF_SYSTEM") || + DomName.equals("AF_LOCAL") || DomName.equals("AF_UNIX") || + DomName.equals("AF_RESERVED_36"); + if (SafeProtocol) + return; - return false; + ProgramStateRef State = C.getState(); + auto &F = State->getStateManager().get_context<ArgIdxFactory>(); + ImmutableSet<ArgIdxTy> Result = F.add(F.getEmptySet(), ReturnValueIndex); + State = State->set<TaintArgsOnPostVisit>(C.getStackFrame(), Result); + C.addTransition(State); } +/// Checker registration void ento::registerGenericTaintChecker(CheckerManager &Mgr) { - auto *Checker = Mgr.registerChecker<GenericTaintChecker>(); - std::string Option{"Config"}; - StringRef ConfigFile = - Mgr.getAnalyzerOptions().getCheckerStringOption(Checker, Option); - llvm::Optional<TaintConfig> Config = - getConfiguration<TaintConfig>(Mgr, Checker, Option, ConfigFile); - if (Config) - Checker->parseConfiguration(Mgr, Option, std::move(Config.getValue())); + Mgr.registerChecker<GenericTaintChecker>(); } bool ento::shouldRegisterGenericTaintChecker(const CheckerManager &mgr) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp index bcae73378028..b673b51c4623 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp @@ -13,11 +13,12 @@ //===----------------------------------------------------------------------===// #include "AllocationState.h" -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "InterCheckerAPI.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/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" @@ -34,9 +35,9 @@ namespace { class InnerPointerChecker : public Checker<check::DeadSymbols, check::PostCall> { - CallDescription AppendFn, AssignFn, AddressofFn, ClearFn, CStrFn, DataFn, - DataMemberFn, EraseFn, InsertFn, PopBackFn, PushBackFn, ReplaceFn, - ReserveFn, ResizeFn, ShrinkToFitFn, SwapFn; + CallDescription AppendFn, AssignFn, AddressofFn, AddressofFn_, ClearFn, + CStrFn, DataFn, DataMemberFn, EraseFn, InsertFn, PopBackFn, PushBackFn, + ReplaceFn, ReserveFn, ResizeFn, ShrinkToFitFn, SwapFn; public: class InnerPointerBRVisitor : public BugReporterVisitor { @@ -54,9 +55,9 @@ public: ID.AddPointer(getTag()); } - virtual PathDiagnosticPieceRef - VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - PathSensitiveBugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; // FIXME: Scan the map once in the visitor's constructor and do a direct // lookup by region. @@ -73,7 +74,7 @@ public: InnerPointerChecker() : AppendFn({"std", "basic_string", "append"}), AssignFn({"std", "basic_string", "assign"}), - AddressofFn({"std", "addressof"}), + AddressofFn({"std", "addressof"}), AddressofFn_({"std", "__addressof"}), ClearFn({"std", "basic_string", "clear"}), CStrFn({"std", "basic_string", "c_str"}), DataFn({"std", "data"}, 1), DataMemberFn({"std", "basic_string", "data"}), @@ -125,19 +126,15 @@ bool InnerPointerChecker::isInvalidatingMemberFunction( return true; return false; } - return (isa<CXXDestructorCall>(Call) || Call.isCalled(AppendFn) || - Call.isCalled(AssignFn) || Call.isCalled(ClearFn) || - Call.isCalled(EraseFn) || Call.isCalled(InsertFn) || - Call.isCalled(PopBackFn) || Call.isCalled(PushBackFn) || - Call.isCalled(ReplaceFn) || Call.isCalled(ReserveFn) || - Call.isCalled(ResizeFn) || Call.isCalled(ShrinkToFitFn) || - Call.isCalled(SwapFn)); + return isa<CXXDestructorCall>(Call) || + matchesAny(Call, AppendFn, AssignFn, ClearFn, EraseFn, InsertFn, + PopBackFn, PushBackFn, ReplaceFn, ReserveFn, ResizeFn, + ShrinkToFitFn, SwapFn); } bool InnerPointerChecker::isInnerPointerAccessFunction( const CallEvent &Call) const { - return (Call.isCalled(CStrFn) || Call.isCalled(DataFn) || - Call.isCalled(DataMemberFn)); + return matchesAny(Call, CStrFn, DataFn, DataMemberFn); } void InnerPointerChecker::markPtrSymbolsReleased(const CallEvent &Call, @@ -182,9 +179,9 @@ void InnerPointerChecker::checkFunctionArguments(const CallEvent &Call, if (!ArgRegion) continue; - // std::addressof function accepts a non-const reference as an argument, + // std::addressof functions accepts a non-const reference as an argument, // but doesn't modify it. - if (Call.isCalled(AddressofFn)) + if (matchesAny(Call, AddressofFn, AddressofFn_)) continue; markPtrSymbolsReleased(Call, State, ArgRegion, C); @@ -323,8 +320,7 @@ PathDiagnosticPieceRef InnerPointerChecker::InnerPointerBRVisitor::VisitNode( SmallString<256> Buf; llvm::raw_svector_ostream OS(Buf); - OS << "Pointer to inner buffer of '" << ObjTy.getAsString() - << "' obtained here"; + OS << "Pointer to inner buffer of '" << ObjTy << "' obtained here"; PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InvalidatedIteratorChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InvalidatedIteratorChecker.cpp index 6955ba11a28f..3f5856a3efbe 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InvalidatedIteratorChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/InvalidatedIteratorChecker.cpp @@ -31,14 +31,14 @@ class InvalidatedIteratorChecker check::PreStmt<ArraySubscriptExpr>, check::PreStmt<MemberExpr>> { - std::unique_ptr<BugType> InvalidatedBugType; + const BugType InvalidatedBugType{this, "Iterator invalidated", + "Misuse of STL APIs"}; - void verifyAccess(CheckerContext &C, const SVal &Val) const; - void reportBug(const StringRef &Message, const SVal &Val, - CheckerContext &C, ExplodedNode *ErrNode) const; -public: - InvalidatedIteratorChecker(); + void verifyAccess(CheckerContext &C, SVal Val) const; + void reportBug(StringRef Message, SVal Val, CheckerContext &C, + ExplodedNode *ErrNode) const; +public: void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPreStmt(const UnaryOperator *UO, CheckerContext &C) const; void checkPreStmt(const BinaryOperator *BO, CheckerContext &C) const; @@ -49,11 +49,6 @@ public: } //namespace -InvalidatedIteratorChecker::InvalidatedIteratorChecker() { - InvalidatedBugType.reset( - new BugType(this, "Iterator invalidated", "Misuse of STL APIs")); -} - void InvalidatedIteratorChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { // Check for access of invalidated position @@ -114,7 +109,8 @@ void InvalidatedIteratorChecker::checkPreStmt(const MemberExpr *ME, verifyAccess(C, BaseVal); } -void InvalidatedIteratorChecker::verifyAccess(CheckerContext &C, const SVal &Val) const { +void InvalidatedIteratorChecker::verifyAccess(CheckerContext &C, + SVal Val) const { auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Val); if (Pos && !Pos->isValid()) { @@ -126,11 +122,11 @@ void InvalidatedIteratorChecker::verifyAccess(CheckerContext &C, const SVal &Val } } -void InvalidatedIteratorChecker::reportBug(const StringRef &Message, - const SVal &Val, CheckerContext &C, +void InvalidatedIteratorChecker::reportBug(StringRef Message, SVal Val, + CheckerContext &C, ExplodedNode *ErrNode) const { - auto R = std::make_unique<PathSensitiveBugReport>(*InvalidatedBugType, - Message, ErrNode); + auto R = std::make_unique<PathSensitiveBugReport>(InvalidatedBugType, Message, + ErrNode); R->markInteresting(Val); C.emitReport(std::move(R)); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.cpp index 496190149991..e8d35aac2efd 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.cpp @@ -29,8 +29,8 @@ bool isIterator(const CXXRecordDecl *CRD) { return false; const auto Name = CRD->getName(); - if (!(Name.endswith_insensitive("iterator") || - Name.endswith_insensitive("iter") || Name.endswith_insensitive("it"))) + if (!(Name.ends_with_insensitive("iterator") || + Name.ends_with_insensitive("iter") || Name.ends_with_insensitive("it"))) return false; bool HasCopyCtor = false, HasCopyAssign = true, HasDtor = false, @@ -181,8 +181,7 @@ const ContainerData *getContainerData(ProgramStateRef State, return State->get<ContainerMap>(Cont); } -const IteratorPosition *getIteratorPosition(ProgramStateRef State, - const SVal &Val) { +const IteratorPosition *getIteratorPosition(ProgramStateRef State, SVal Val) { if (auto Reg = Val.getAsRegion()) { Reg = Reg->getMostDerivedObjectRegion(); return State->get<IteratorRegionMap>(Reg); @@ -194,7 +193,7 @@ const IteratorPosition *getIteratorPosition(ProgramStateRef State, return nullptr; } -ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, +ProgramStateRef setIteratorPosition(ProgramStateRef State, SVal Val, const IteratorPosition &Pos) { if (auto Reg = Val.getAsRegion()) { Reg = Reg->getMostDerivedObjectRegion(); @@ -207,8 +206,8 @@ ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, return nullptr; } -ProgramStateRef createIteratorPosition(ProgramStateRef State, const SVal &Val, - const MemRegion *Cont, const Stmt* S, +ProgramStateRef createIteratorPosition(ProgramStateRef State, SVal Val, + const MemRegion *Cont, const Stmt *S, const LocationContext *LCtx, unsigned blockCount) { auto &StateMgr = State->getStateManager(); @@ -221,9 +220,8 @@ ProgramStateRef createIteratorPosition(ProgramStateRef State, const SVal &Val, IteratorPosition::getPosition(Cont, Sym)); } -ProgramStateRef advancePosition(ProgramStateRef State, const SVal &Iter, - OverloadedOperatorKind Op, - const SVal &Distance) { +ProgramStateRef advancePosition(ProgramStateRef State, SVal Iter, + OverloadedOperatorKind Op, SVal Distance) { const auto *Pos = getIteratorPosition(State, Iter); if (!Pos) return nullptr; @@ -308,8 +306,8 @@ bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2, const auto comparison = SVB.evalBinOp(State, Opc, NL1, NL2, SVB.getConditionType()); - assert(comparison.getAs<DefinedSVal>() && - "Symbol comparison must be a `DefinedSVal`"); + assert(isa<DefinedSVal>(comparison) && + "Symbol comparison must be a `DefinedSVal`"); return !State->assume(comparison.castAs<DefinedSVal>(), false); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.h index 37157492fe3e..46de8ea01d77 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.h +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Iterator.h @@ -63,9 +63,7 @@ public: return Cont == X.Cont && Valid == X.Valid && Offset == X.Offset; } - bool operator!=(const IteratorPosition &X) const { - return Cont != X.Cont || Valid != X.Valid || Offset != X.Offset; - } + bool operator!=(const IteratorPosition &X) const { return !(*this == X); } void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddPointer(Cont); @@ -101,9 +99,7 @@ public: return Begin == X.Begin && End == X.End; } - bool operator!=(const ContainerData &X) const { - return Begin != X.Begin || End != X.End; - } + bool operator!=(const ContainerData &X) const { return !(*this == X); } void Profile(llvm::FoldingSetNodeID &ID) const { ID.Add(Begin); @@ -165,18 +161,15 @@ 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 *getIteratorPosition(ProgramStateRef State, SVal Val); +ProgramStateRef setIteratorPosition(ProgramStateRef State, SVal Val, const IteratorPosition &Pos); -ProgramStateRef createIteratorPosition(ProgramStateRef State, const SVal &Val, - const MemRegion *Cont, const Stmt* S, +ProgramStateRef createIteratorPosition(ProgramStateRef State, 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 advancePosition(ProgramStateRef State, SVal Iter, + OverloadedOperatorKind Op, SVal Distance); ProgramStateRef assumeNoOverflow(ProgramStateRef State, SymbolRef Sym, long Scale); bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp index ab5e6a1c9991..a95e811c2a41 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp @@ -64,13 +64,15 @@ // making an assumption e.g. `S1 + n == S2 + m` we store `S1 - S2 == m - n` as // a constraint which we later retrieve when doing an actual comparison. -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/DeclTemplate.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" +#include "llvm/ADT/STLExtras.h" #include "Iterator.h" @@ -98,18 +100,17 @@ class IteratorModeling const AdvanceFn *Handler) const; void handleComparison(CheckerContext &C, const Expr *CE, SVal RetVal, - const SVal &LVal, const SVal &RVal, - OverloadedOperatorKind Op) const; + SVal LVal, SVal RVal, OverloadedOperatorKind Op) const; void processComparison(CheckerContext &C, ProgramStateRef State, - SymbolRef Sym1, SymbolRef Sym2, const SVal &RetVal, + SymbolRef Sym1, SymbolRef Sym2, SVal RetVal, OverloadedOperatorKind Op) const; - void handleIncrement(CheckerContext &C, const SVal &RetVal, const SVal &Iter, + void handleIncrement(CheckerContext &C, SVal RetVal, SVal Iter, bool Postfix) const; - void handleDecrement(CheckerContext &C, const SVal &RetVal, const SVal &Iter, + void handleDecrement(CheckerContext &C, SVal RetVal, SVal Iter, bool Postfix) const; void handleRandomIncrOrDecr(CheckerContext &C, const Expr *CE, - OverloadedOperatorKind Op, const SVal &RetVal, - const SVal &Iterator, const SVal &Amount) const; + OverloadedOperatorKind Op, SVal RetVal, + SVal Iterator, SVal Amount) const; void handlePtrIncrOrDecr(CheckerContext &C, const Expr *Iterator, OverloadedOperatorKind OK, SVal Offset) const; void handleAdvance(CheckerContext &C, const Expr *CE, SVal RetVal, SVal Iter, @@ -118,7 +119,7 @@ class IteratorModeling 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, + void assignToContainer(CheckerContext &C, const Expr *CE, SVal RetVal, const MemRegion *Cont) const; bool noChangeInAdvance(CheckerContext &C, SVal Iter, const Expr *CE) const; void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, @@ -150,8 +151,6 @@ public: 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, CheckerContext &C) const; void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const; @@ -160,7 +159,7 @@ public: bool isSimpleComparisonOperator(OverloadedOperatorKind OK); bool isSimpleComparisonOperator(BinaryOperatorKind OK); -ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val); +ProgramStateRef removeIteratorPosition(ProgramStateRef State, SVal Val); ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, bool Equal); bool isBoundThroughLazyCompoundVal(const Environment &Env, @@ -283,7 +282,7 @@ void IteratorModeling::checkPostStmt(const BinaryOperator *BO, // The non-iterator side must have an integral or enumeration type. if (!AmountExpr->getType()->isIntegralOrEnumerationType()) return; - const SVal &AmountVal = IsIterOnLHS ? RVal : LVal; + SVal AmountVal = IsIterOnLHS ? RVal : LVal; handlePtrIncrOrDecr(C, IterExpr, BinaryOperator::getOverloadedOperator(OK), AmountVal); } @@ -304,21 +303,18 @@ void IteratorModeling::checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const { // Keep symbolic expressions of iterator positions alive auto RegionMap = State->get<IteratorRegionMap>(); - for (const auto &Reg : RegionMap) { - const auto Offset = Reg.second.getOffset(); - for (auto i = Offset->symbol_begin(); i != Offset->symbol_end(); ++i) - if (isa<SymbolData>(*i)) - SR.markLive(*i); + for (const IteratorPosition &Pos : llvm::make_second_range(RegionMap)) { + for (SymbolRef Sym : Pos.getOffset()->symbols()) + if (isa<SymbolData>(Sym)) + SR.markLive(Sym); } auto SymbolMap = State->get<IteratorSymbolMap>(); - for (const auto &Sym : SymbolMap) { - const auto Offset = Sym.second.getOffset(); - for (auto i = Offset->symbol_begin(); i != Offset->symbol_end(); ++i) - if (isa<SymbolData>(*i)) - SR.markLive(*i); + for (const IteratorPosition &Pos : llvm::make_second_range(SymbolMap)) { + for (SymbolRef Sym : Pos.getOffset()->symbols()) + if (isa<SymbolData>(Sym)) + SR.markLive(Sym); } - } void IteratorModeling::checkDeadSymbols(SymbolReaper &SR, @@ -391,8 +387,8 @@ IteratorModeling::handleOverloadedOperator(CheckerContext &C, const bool IsIterFirst = FirstType->isStructureOrClassType(); const SVal FirstArg = Call.getArgSVal(0); const SVal SecondArg = Call.getArgSVal(1); - const SVal &Iterator = IsIterFirst ? FirstArg : SecondArg; - const SVal &Amount = IsIterFirst ? SecondArg : FirstArg; + SVal Iterator = IsIterFirst ? FirstArg : SecondArg; + SVal Amount = IsIterFirst ? SecondArg : FirstArg; handleRandomIncrOrDecr(C, OrigExpr, Op, Call.getReturnValue(), Iterator, Amount); @@ -447,14 +443,13 @@ IteratorModeling::handleAdvanceLikeFunction(CheckerContext &C, } void IteratorModeling::handleComparison(CheckerContext &C, const Expr *CE, - SVal RetVal, const SVal &LVal, - const SVal &RVal, - OverloadedOperatorKind Op) const { + SVal RetVal, SVal LVal, SVal RVal, + OverloadedOperatorKind Op) const { // Record the operands and the operator of the comparison for the next // evalAssume, if the result is a symbolic expression. If it is a concrete // value (only one branch is possible), then transfer the state between // the operands according to the operator and the result - auto State = C.getState(); + auto State = C.getState(); const auto *LPos = getIteratorPosition(State, LVal); const auto *RPos = getIteratorPosition(State, RVal); const MemRegion *Cont = nullptr; @@ -507,7 +502,7 @@ void IteratorModeling::handleComparison(CheckerContext &C, const Expr *CE, void IteratorModeling::processComparison(CheckerContext &C, ProgramStateRef State, SymbolRef Sym1, - SymbolRef Sym2, const SVal &RetVal, + SymbolRef Sym2, SVal RetVal, OverloadedOperatorKind Op) const { if (const auto TruthVal = RetVal.getAs<nonloc::ConcreteInt>()) { if ((State = relateSymbols(State, Sym1, Sym2, @@ -535,8 +530,8 @@ void IteratorModeling::processComparison(CheckerContext &C, } } -void IteratorModeling::handleIncrement(CheckerContext &C, const SVal &RetVal, - const SVal &Iter, bool Postfix) const { +void IteratorModeling::handleIncrement(CheckerContext &C, SVal RetVal, + SVal Iter, bool Postfix) const { // Increment the symbolic expressions which represents the position of the // iterator auto State = C.getState(); @@ -561,8 +556,8 @@ void IteratorModeling::handleIncrement(CheckerContext &C, const SVal &RetVal, C.addTransition(State); } -void IteratorModeling::handleDecrement(CheckerContext &C, const SVal &RetVal, - const SVal &Iter, bool Postfix) const { +void IteratorModeling::handleDecrement(CheckerContext &C, SVal RetVal, + SVal Iter, bool Postfix) const { // Decrement the symbolic expressions which represents the position of the // iterator auto State = C.getState(); @@ -589,9 +584,8 @@ void IteratorModeling::handleDecrement(CheckerContext &C, const SVal &RetVal, void IteratorModeling::handleRandomIncrOrDecr(CheckerContext &C, const Expr *CE, OverloadedOperatorKind Op, - const SVal &RetVal, - const SVal &Iterator, - const SVal &Amount) const { + SVal RetVal, SVal Iterator, + SVal Amount) const { // Increment or decrement the symbolic expressions which represents the // position of the iterator auto State = C.getState(); @@ -630,7 +624,7 @@ void IteratorModeling::handlePtrIncrOrDecr(CheckerContext &C, const Expr *Iterator, OverloadedOperatorKind OK, SVal Offset) const { - if (!Offset.getAs<DefinedSVal>()) + if (!isa<DefinedSVal>(Offset)) return; QualType PtrType = Iterator->getType(); @@ -687,7 +681,7 @@ void IteratorModeling::handleNext(CheckerContext &C, const Expr *CE, } void IteratorModeling::assignToContainer(CheckerContext &C, const Expr *CE, - const SVal &RetVal, + SVal RetVal, const MemRegion *Cont) const { Cont = Cont->getMostDerivedObjectRegion(); @@ -775,7 +769,7 @@ bool isSimpleComparisonOperator(BinaryOperatorKind OK) { return OK == BO_EQ || OK == BO_NE; } -ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val) { +ProgramStateRef removeIteratorPosition(ProgramStateRef State, SVal Val) { if (auto Reg = Val.getAsRegion()) { Reg = Reg->getMostDerivedObjectRegion(); return State->remove<IteratorRegionMap>(Reg); @@ -800,8 +794,8 @@ ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1, SVB.evalBinOp(State, BO_EQ, nonloc::SymbolVal(Sym1), nonloc::SymbolVal(Sym2), SVB.getConditionType()); - assert(comparison.getAs<DefinedSVal>() && - "Symbol comparison must be a `DefinedSVal`"); + assert(isa<DefinedSVal>(comparison) && + "Symbol comparison must be a `DefinedSVal`"); auto NewState = State->assume(comparison.castAs<DefinedSVal>(), Equal); if (!NewState) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp index a47484497771..c8828219dd73 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp @@ -14,10 +14,10 @@ #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" - #include "Iterator.h" using namespace clang; @@ -32,7 +32,8 @@ class IteratorRangeChecker check::PreStmt<ArraySubscriptExpr>, check::PreStmt<MemberExpr>> { - std::unique_ptr<BugType> OutOfRangeBugType; + const BugType OutOfRangeBugType{this, "Iterator out of range", + "Misuse of STL APIs"}; void verifyDereference(CheckerContext &C, SVal Val) const; void verifyIncrement(CheckerContext &C, SVal Iter) const; @@ -42,12 +43,10 @@ class IteratorRangeChecker 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, + void reportBug(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; @@ -67,15 +66,10 @@ public: bool isPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos); bool isAheadOfRange(ProgramStateRef State, const IteratorPosition &Pos); bool isBehindPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos); -bool isZero(ProgramStateRef State, const NonLoc &Val); +bool isZero(ProgramStateRef State, NonLoc Val); } //namespace -IteratorRangeChecker::IteratorRangeChecker() { - OutOfRangeBugType.reset( - new BugType(this, "Iterator out of range", "Misuse of STL APIs")); -} - void IteratorRangeChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { // Check for out of range access @@ -228,7 +222,7 @@ void IteratorRangeChecker::verifyRandomIncrOrDecr(CheckerContext &C, Value = State->getRawSVal(*ValAsLoc); } - if (Value.isUnknownOrUndef()) + if (Value.isUnknownOrUndef() || !isa<NonLoc>(Value)) return; // Incremention or decremention by 0 is never a bug. @@ -275,10 +269,10 @@ void IteratorRangeChecker::verifyNext(CheckerContext &C, SVal LHS, verifyRandomIncrOrDecr(C, OO_Plus, LHS, RHS); } -void IteratorRangeChecker::reportBug(const StringRef &Message, SVal Val, +void IteratorRangeChecker::reportBug(StringRef Message, SVal Val, CheckerContext &C, ExplodedNode *ErrNode) const { - auto R = std::make_unique<PathSensitiveBugReport>(*OutOfRangeBugType, Message, + auto R = std::make_unique<PathSensitiveBugReport>(OutOfRangeBugType, Message, ErrNode); const auto *Pos = getIteratorPosition(C.getState(), Val); @@ -295,7 +289,7 @@ bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); bool isGreater(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); bool isEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); -bool isZero(ProgramStateRef State, const NonLoc &Val) { +bool isZero(ProgramStateRef State, NonLoc Val) { auto &BVF = State->getBasicVals(); return compare(State, Val, nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(0))), diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp index 3e6756efe0e6..f0276a57bdf9 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp @@ -27,14 +27,15 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/StmtVisitor.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SetVector.h" #include "llvm/ADT/SmallString.h" @@ -44,9 +45,9 @@ using namespace ento; namespace { struct ChecksFilter { /// Check for missing invalidation method declarations. - DefaultBool check_MissingInvalidationMethod; + bool check_MissingInvalidationMethod = false; /// Check that all ivars are invalidated. - DefaultBool check_InstanceVariableInvalidation; + bool check_InstanceVariableInvalidation = false; CheckerNameRef checkName_MissingInvalidationMethod; CheckerNameRef checkName_InstanceVariableInvalidation; @@ -63,12 +64,12 @@ class IvarInvalidationCheckerImpl { struct InvalidationInfo { /// Has the ivar been invalidated? - bool IsInvalidated; + bool IsInvalidated = false; /// The methods which can be used to invalidate the ivar. MethodSet InvalidationMethods; - InvalidationInfo() : IsInvalidated(false) {} + InvalidationInfo() = default; void addInvalidationMethod(const ObjCMethodDecl *MD) { InvalidationMethods.insert(MD); } @@ -80,9 +81,8 @@ class IvarInvalidationCheckerImpl { bool hasMethod(const ObjCMethodDecl *MD) { if (IsInvalidated) return true; - for (MethodSet::iterator I = InvalidationMethods.begin(), - E = InvalidationMethods.end(); I != E; ++I) { - if (*I == MD) { + for (const ObjCMethodDecl *Curr : InvalidationMethods) { + if (Curr == MD) { IsInvalidated = true; return true; } @@ -318,9 +318,7 @@ const ObjCIvarDecl *IvarInvalidationCheckerImpl::findPropertyBackingIvar( // Lookup IVars named "_PropName"or "PropName" among the tracked Ivars. StringRef PropName = Prop->getIdentifier()->getName(); - for (IvarSet::const_iterator I = TrackedIvars.begin(), - E = TrackedIvars.end(); I != E; ++I) { - const ObjCIvarDecl *Iv = I->first; + for (const ObjCIvarDecl *Iv : llvm::make_first_range(TrackedIvars)) { StringRef IvarName = Iv->getName(); if (IvarName == PropName) @@ -379,12 +377,9 @@ visit(const ObjCImplementationDecl *ImplD) const { IvarToPropMapTy IvarToPopertyMap; ObjCInterfaceDecl::PropertyMap PropMap; - ObjCInterfaceDecl::PropertyDeclOrder PropOrder; - InterfaceD->collectPropertiesToImplement(PropMap, PropOrder); + InterfaceD->collectPropertiesToImplement(PropMap); - for (ObjCInterfaceDecl::PropertyMap::iterator - I = PropMap.begin(), E = PropMap.end(); I != E; ++I) { - const ObjCPropertyDecl *PD = I->second; + for (const ObjCPropertyDecl *PD : llvm::make_second_range(PropMap)) { if (PD->isClassProperty()) continue; @@ -423,11 +418,7 @@ visit(const ObjCImplementationDecl *ImplD) const { // Remove ivars invalidated by the partial invalidation methods. They do not // need to be invalidated in the regular invalidation methods. bool AtImplementationContainsAtLeastOnePartialInvalidationMethod = false; - for (MethodSet::iterator - I = PartialInfo.InvalidationMethods.begin(), - E = PartialInfo.InvalidationMethods.end(); I != E; ++I) { - const ObjCMethodDecl *InterfD = *I; - + for (const ObjCMethodDecl *InterfD : PartialInfo.InvalidationMethods) { // Get the corresponding method in the @implementation. const ObjCMethodDecl *D = ImplD->getMethod(InterfD->getSelector(), InterfD->isInstanceMethod()); @@ -476,10 +467,7 @@ visit(const ObjCImplementationDecl *ImplD) const { // Check that all ivars are invalidated by the invalidation methods. bool AtImplementationContainsAtLeastOneInvalidationMethod = false; - for (MethodSet::iterator I = Info.InvalidationMethods.begin(), - E = Info.InvalidationMethods.end(); I != E; ++I) { - const ObjCMethodDecl *InterfD = *I; - + for (const ObjCMethodDecl *InterfD : Info.InvalidationMethods) { // Get the corresponding method in the @implementation. const ObjCMethodDecl *D = ImplD->getMethod(InterfD->getSelector(), InterfD->isInstanceMethod()); @@ -502,9 +490,8 @@ visit(const ObjCImplementationDecl *ImplD) const { continue; // Warn on the ivars that were not invalidated by the method. - for (IvarSet::const_iterator - I = IvarsI.begin(), E = IvarsI.end(); I != E; ++I) - reportIvarNeedsInvalidation(I->first, IvarToPopertyMap, D); + for (const ObjCIvarDecl *Ivar : llvm::make_first_range(IvarsI)) + reportIvarNeedsInvalidation(Ivar, IvarToPopertyMap, D); } } @@ -513,9 +500,8 @@ visit(const ObjCImplementationDecl *ImplD) const { if (AtImplementationContainsAtLeastOnePartialInvalidationMethod) { // Warn on the ivars that were not invalidated by the prrtial // invalidation methods. - for (IvarSet::const_iterator - I = Ivars.begin(), E = Ivars.end(); I != E; ++I) - reportIvarNeedsInvalidation(I->first, IvarToPopertyMap, nullptr); + for (const ObjCIvarDecl *Ivar : llvm::make_first_range(Ivars)) + reportIvarNeedsInvalidation(Ivar, IvarToPopertyMap, nullptr); } else { // Otherwise, no invalidation methods were implemented. reportNoInvalidationMethod(Filter.checkName_InstanceVariableInvalidation, diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp index 1f3d8844d330..fa51aa80216b 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp @@ -273,7 +273,7 @@ void ASTFieldVisitor::ReportError(QualType T) { os << (*I)->getName(); } } - os << " (type " << FieldChain.back()->getType().getAsString() << ")"; + os << " (type " << FieldChain.back()->getType() << ")"; // Note that this will fire for every translation unit that uses this // class. This is suboptimal, but at least scan-build will merge diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp index 28d3e058fee2..812d787e2e37 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp @@ -14,13 +14,13 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/StmtVisitor.h" #include "clang/Lex/Lexer.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" @@ -28,7 +28,9 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/Support/Unicode.h" +#include <optional> using namespace clang; using namespace ento; @@ -60,7 +62,8 @@ class NonLocalizedStringChecker check::PostObjCMessage, check::PostStmt<ObjCStringLiteral>> { - mutable std::unique_ptr<BugType> BT; + const BugType BT{this, "Unlocalizable string", + "Localizability Issue (Apple)"}; // Methods that require a localized string mutable llvm::DenseMap<const IdentifierInfo *, @@ -87,12 +90,10 @@ class NonLocalizedStringChecker Selector S) const; public: - NonLocalizedStringChecker(); - // When this parameter is set to true, the checker assumes all // methods that return NSStrings are unlocalized. Thus, more false // positives will be reported. - DefaultBool IsAggressive; + bool IsAggressive = false; void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const; void checkPostObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const; @@ -106,11 +107,6 @@ public: REGISTER_MAP_WITH_PROGRAMSTATE(LocalizedMemMap, const MemRegion *, LocalizedState) -NonLocalizedStringChecker::NonLocalizedStringChecker() { - BT.reset(new BugType(this, "Unlocalizable string", - "Localizability Issue (Apple)")); -} - namespace { class NonLocalizedStringBRVisitor final : public BugReporterVisitor { @@ -716,7 +712,7 @@ void NonLocalizedStringChecker::setNonLocalizedState(const SVal S, static bool isDebuggingName(std::string name) { - return StringRef(name).lower().find("debug") != StringRef::npos; + return StringRef(name).contains_insensitive("debug"); } /// Returns true when, heuristically, the analyzer may be analyzing debugging @@ -762,7 +758,7 @@ void NonLocalizedStringChecker::reportLocalizationError( // Generate the bug report. auto R = std::make_unique<PathSensitiveBugReport>( - *BT, "User-facing text should use localized string macro", ErrNode); + BT, "User-facing text should use localized string macro", ErrNode); if (argumentNumber) { R->addRange(M.getArgExpr(argumentNumber - 1)->getSourceRange()); } else { @@ -815,9 +811,9 @@ void NonLocalizedStringChecker::checkPreObjCMessage(const ObjCMethodCall &msg, // Handle the case where the receiver is an NSString // These special NSString methods draw to the screen - if (!(SelectorName.startswith("drawAtPoint") || - SelectorName.startswith("drawInRect") || - SelectorName.startswith("drawWithRect"))) + if (!(SelectorName.starts_with("drawAtPoint") || + SelectorName.starts_with("drawInRect") || + SelectorName.starts_with("drawWithRect"))) return; SVal svTitle = msg.getReceiverSVal(); @@ -846,10 +842,9 @@ void NonLocalizedStringChecker::checkPreObjCMessage(const ObjCMethodCall &msg, if (argumentNumber < 0) { // There was no match in UIMethods if (const Decl *D = msg.getDecl()) { if (const ObjCMethodDecl *OMD = dyn_cast_or_null<ObjCMethodDecl>(D)) { - auto formals = OMD->parameters(); - for (unsigned i = 0, ei = formals.size(); i != ei; ++i) { - if (isAnnotatedAsTakingLocalized(formals[i])) { - argumentNumber = i; + for (auto [Idx, FormalParam] : llvm::enumerate(OMD->parameters())) { + if (isAnnotatedAsTakingLocalized(FormalParam)) { + argumentNumber = Idx; break; } } @@ -949,7 +944,7 @@ void NonLocalizedStringChecker::checkPostCall(const CallEvent &Call, const IdentifierInfo *Identifier = Call.getCalleeIdentifier(); SVal sv = Call.getReturnValue(); - if (isAnnotatedAsReturningLocalized(D) || LSF.count(Identifier) != 0) { + if (isAnnotatedAsReturningLocalized(D) || LSF.contains(Identifier)) { setLocalizedState(sv, C); } else if (isNSStringType(RT, C.getASTContext()) && !hasLocalizedState(sv, C)) { @@ -1004,8 +999,8 @@ NonLocalizedStringBRVisitor::VisitNode(const ExplodedNode *Succ, if (Satisfied) return nullptr; - Optional<StmtPoint> Point = Succ->getLocation().getAs<StmtPoint>(); - if (!Point.hasValue()) + std::optional<StmtPoint> Point = Succ->getLocation().getAs<StmtPoint>(); + if (!Point) return nullptr; auto *LiteralExpr = dyn_cast<ObjCStringLiteral>(Point->getStmt()); @@ -1141,12 +1136,12 @@ void EmptyLocalizationContextChecker::MethodCrawler::VisitObjCMessageExpr( SE = Mgr.getSourceManager().getSLocEntry(SLInfo.first); } - llvm::Optional<llvm::MemoryBufferRef> BF = + std::optional<llvm::MemoryBufferRef> BF = Mgr.getSourceManager().getBufferOrNone(SLInfo.first, SL); if (!BF) return; - - Lexer TheLexer(SL, LangOptions(), BF->getBufferStart(), + LangOptions LangOpts; + Lexer TheLexer(SL, LangOpts, BF->getBufferStart(), BF->getBufferStart() + SLInfo.second, BF->getBufferEnd()); Token I; @@ -1253,8 +1248,8 @@ bool PluralMisuseChecker::MethodCrawler::isCheckingPlurality( BO = B; } } - if (VD->getName().lower().find("plural") != StringRef::npos || - VD->getName().lower().find("singular") != StringRef::npos) { + if (VD->getName().contains_insensitive("plural") || + VD->getName().contains_insensitive("singular")) { return true; } } @@ -1339,7 +1334,10 @@ bool PluralMisuseChecker::MethodCrawler::EndVisitIfStmt(IfStmt *I) { } bool PluralMisuseChecker::MethodCrawler::VisitIfStmt(const IfStmt *I) { - const Expr *Condition = I->getCond()->IgnoreParenImpCasts(); + const Expr *Condition = I->getCond(); + if (!Condition) + return true; + Condition = Condition->IgnoreParenImpCasts(); if (isCheckingPlurality(Condition)) { MatchingStatements.push_back(I); InMatchingStatement = true; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp index b72d72580c28..153a0a51e980 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp @@ -27,8 +27,10 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include <optional> using namespace clang; using namespace ento; @@ -85,7 +87,7 @@ class MIGChecker : public Checker<check::PostCall, check::PreStmt<ReturnStmt>, #undef CALL }; - CallDescription OsRefRetain{"os_ref_retain", 1}; + CallDescription OsRefRetain{{"os_ref_retain"}, 1}; void checkReturnAux(const ReturnStmt *RS, CheckerContext &C) const; @@ -156,10 +158,10 @@ static bool isInMIGCall(CheckerContext &C) { const Decl *D = SFC->getDecl(); - if (Optional<AnyCall> AC = AnyCall::forDecl(D)) { + if (std::optional<AnyCall> AC = AnyCall::forDecl(D)) { // Even though there's a Sema warning when the return type of an annotated // function is not a kern_return_t, this warning isn't an error, so we need - // an extra sanity check here. + // an extra check here. // FIXME: AnyCall doesn't support blocks yet, so they remain unchecked // for now. if (!AC->getReturnType(C.getASTContext()) @@ -180,7 +182,7 @@ static bool isInMIGCall(CheckerContext &C) { } void MIGChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { - if (Call.isCalled(OsRefRetain)) { + if (OsRefRetain.matches(Call)) { // If the code is doing reference counting over the parameter, // it opens up an opportunity for safely calling a destructor function. // TODO: We should still check for over-releases. @@ -198,7 +200,7 @@ void MIGChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { auto I = llvm::find_if(Deallocators, [&](const std::pair<CallDescription, unsigned> &Item) { - return Call.isCalled(Item.first); + return Item.first.matches(Call); }); if (I == Deallocators.end()) return; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp index bbf2ddec5762..3e374e6c240e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp @@ -31,7 +31,7 @@ void MPIBugReporter::reportDoubleNonblocking( RequestRegion->getDescriptiveName() + ". "; auto Report = std::make_unique<PathSensitiveBugReport>( - *DoubleNonblockingBugType, ErrorText, ExplNode); + DoubleNonblockingBugType, ErrorText, ExplNode); Report->addRange(MPICallEvent.getSourceRange()); SourceRange Range = RequestRegion->sourceRange(); @@ -53,7 +53,7 @@ void MPIBugReporter::reportMissingWait( std::string ErrorText{"Request " + RequestRegion->getDescriptiveName() + " has no matching wait. "}; - auto Report = std::make_unique<PathSensitiveBugReport>(*MissingWaitBugType, + auto Report = std::make_unique<PathSensitiveBugReport>(MissingWaitBugType, ErrorText, ExplNode); SourceRange Range = RequestRegion->sourceRange(); @@ -73,7 +73,7 @@ void MPIBugReporter::reportUnmatchedWait( std::string ErrorText{"Request " + RequestRegion->getDescriptiveName() + " has no matching nonblocking call. "}; - auto Report = std::make_unique<PathSensitiveBugReport>(*UnmatchedWaitBugType, + auto Report = std::make_unique<PathSensitiveBugReport>(UnmatchedWaitBugType, ErrorText, ExplNode); Report->addRange(CE.getSourceRange()); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h index 9871da026b04..0222a2120b34 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h @@ -17,6 +17,7 @@ #include "MPITypes.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "llvm/ADT/StringRef.h" namespace clang { namespace ento { @@ -24,12 +25,10 @@ namespace mpi { class MPIBugReporter { public: - MPIBugReporter(const CheckerBase &CB) { - UnmatchedWaitBugType.reset(new BugType(&CB, "Unmatched wait", MPIError)); - DoubleNonblockingBugType.reset( - new BugType(&CB, "Double nonblocking", MPIError)); - MissingWaitBugType.reset(new BugType(&CB, "Missing wait", MPIError)); - } + MPIBugReporter(const CheckerBase &CB) + : UnmatchedWaitBugType(&CB, "Unmatched wait", MPIError), + MissingWaitBugType(&CB, "Missing wait", MPIError), + DoubleNonblockingBugType(&CB, "Double nonblocking", MPIError) {} /// Report duplicate request use by nonblocking calls without intermediate /// wait. @@ -68,12 +67,10 @@ public: BugReporter &BReporter) const; private: - const std::string MPIError = "MPI Error"; - - // path-sensitive bug types - std::unique_ptr<BugType> UnmatchedWaitBugType; - std::unique_ptr<BugType> MissingWaitBugType; - std::unique_ptr<BugType> DoubleNonblockingBugType; + const llvm::StringLiteral MPIError = "MPI Error"; + const BugType UnmatchedWaitBugType; + const BugType MissingWaitBugType; + const BugType DoubleNonblockingBugType; /// Bug visitor class to find the node where the request region was previously /// used in order to include it into the BugReport path. 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 5d6bd381d3cc..4c0a8ba2c7c0 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 @@ -165,7 +165,7 @@ void MPIChecker::allRegionsUsedByWait( Ctx.getState(), SuperRegion, Ctx.getSValBuilder(), CE.getArgExpr(1)->getType()->getPointeeType()); const llvm::APSInt &ArrSize = - ElementCount.getAs<nonloc::ConcreteInt>()->getValue(); + ElementCount.castAs<nonloc::ConcreteInt>().getValue(); for (size_t i = 0; i < ArrSize; ++i) { const NonLoc Idx = Ctx.getSValBuilder().makeArrayIndex(i); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp index a157ee2da5df..12bf12a0b232 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp @@ -19,8 +19,10 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/raw_ostream.h" +#include <optional> using namespace clang; using namespace ento; @@ -31,7 +33,8 @@ class MacOSKeychainAPIChecker : public Checker<check::PreStmt<CallExpr>, check::DeadSymbols, check::PointerEscape, eval::Assume> { - mutable std::unique_ptr<BugType> BT; + const BugType BT{this, "Improper use of SecKeychain API", + categories::AppleAPIMisuse}; public: /// AllocationState is a part of the checker specific state together with the @@ -99,12 +102,6 @@ private: /// function. static unsigned getTrackedFunctionIndex(StringRef Name, bool IsAllocator); - inline void initBugType() const { - if (!BT) - BT.reset(new BugType(this, "Improper use of SecKeychain API", - "API Misuse (Apple)")); - } - void generateDeallocatorMismatchReport(const AllocationPair &AP, const Expr *ArgExpr, CheckerContext &C) const; @@ -160,7 +157,7 @@ static bool isEnclosingFunctionParam(const Expr *E) { E = E->IgnoreParenCasts(); if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) { const ValueDecl *VD = DRE->getDecl(); - if (isa<ImplicitParamDecl>(VD) || isa<ParmVarDecl>(VD)) + if (isa<ImplicitParamDecl, ParmVarDecl>(VD)) return true; } return false; @@ -199,8 +196,7 @@ unsigned MacOSKeychainAPIChecker::getTrackedFunctionIndex(StringRef Name, static bool isBadDeallocationArgument(const MemRegion *Arg) { if (!Arg) return false; - return isa<AllocaRegion>(Arg) || isa<BlockDataRegion>(Arg) || - isa<TypedRegion>(Arg); + return isa<AllocaRegion, BlockDataRegion, TypedRegion>(Arg); } /// Given the address expression, retrieve the value it's pointing to. Assume @@ -210,7 +206,7 @@ static SymbolRef getAsPointeeSymbol(const Expr *Expr, ProgramStateRef State = C.getState(); SVal ArgV = C.getSVal(Expr); - if (Optional<loc::MemRegionVal> X = ArgV.getAs<loc::MemRegionVal>()) { + if (std::optional<loc::MemRegionVal> X = ArgV.getAs<loc::MemRegionVal>()) { StoreManager& SM = C.getStoreManager(); SymbolRef sym = SM.getBinding(State->getStore(), *X).getAsLocSymbol(); if (sym) @@ -231,7 +227,6 @@ void MacOSKeychainAPIChecker:: if (!N) return; - initBugType(); SmallString<80> sbuf; llvm::raw_svector_ostream os(sbuf); unsigned int PDeallocIdx = @@ -239,7 +234,7 @@ void MacOSKeychainAPIChecker:: os << "Deallocator doesn't match the allocator: '" << FunctionsToTrack[PDeallocIdx].Name << "' should be used."; - auto Report = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N); + auto Report = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N); Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(AP.first)); Report->addRange(ArgExpr->getSourceRange()); markInteresting(Report.get(), AP); @@ -275,7 +270,6 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, ExplodedNode *N = C.generateNonFatalErrorNode(State); if (!N) return; - initBugType(); SmallString<128> sbuf; llvm::raw_svector_ostream os(sbuf); unsigned int DIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx; @@ -283,8 +277,7 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, << "the allocator: missing a call to '" << FunctionsToTrack[DIdx].Name << "'."; - auto Report = - std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N); + auto Report = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N); Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(V)); Report->addRange(ArgExpr->getSourceRange()); Report->markInteresting(AS->Region); @@ -337,9 +330,8 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, ExplodedNode *N = C.generateNonFatalErrorNode(State); if (!N) return; - initBugType(); auto Report = std::make_unique<PathSensitiveBugReport>( - *BT, "Trying to free data which has not been allocated.", N); + BT, "Trying to free data which has not been allocated.", N); Report->addRange(ArgExpr->getSourceRange()); if (AS) Report->markInteresting(AS->Region); @@ -473,7 +465,6 @@ std::unique_ptr<PathSensitiveBugReport> MacOSKeychainAPIChecker::generateAllocatedDataNotReleasedReport( const AllocationPair &AP, ExplodedNode *N, CheckerContext &C) const { const ADFunctionInfo &FI = FunctionsToTrack[AP.second->AllocatorIdx]; - initBugType(); SmallString<70> sbuf; llvm::raw_svector_ostream os(sbuf); os << "Allocated data is not released: missing a call to '" @@ -492,7 +483,7 @@ MacOSKeychainAPIChecker::generateAllocatedDataNotReleasedReport( AllocNode->getLocationContext()); auto Report = std::make_unique<PathSensitiveBugReport>( - *BT, os.str(), N, LocUsedForUniqueing, + BT, os.str(), N, LocUsedForUniqueing, AllocNode->getLocationContext()->getDecl()); Report->addVisitor(std::make_unique<SecKeychainBugVisitor>(AP.first)); @@ -530,9 +521,9 @@ ProgramStateRef MacOSKeychainAPIChecker::evalAssume(ProgramStateRef State, } if (ReturnSymbol) - for (auto I = AMap.begin(), E = AMap.end(); I != E; ++I) { - if (ReturnSymbol == I->second.Region) - State = State->remove<AllocatedData>(I->first); + for (auto [Sym, AllocState] : AMap) { + if (ReturnSymbol == AllocState.Region) + State = State->remove<AllocatedData>(Sym); } return State; @@ -547,18 +538,18 @@ void MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR, bool Changed = false; AllocationPairVec Errors; - for (auto I = AMap.begin(), E = AMap.end(); I != E; ++I) { - if (!SR.isDead(I->first)) + for (const auto &[Sym, AllocState] : AMap) { + if (!SR.isDead(Sym)) continue; Changed = true; - State = State->remove<AllocatedData>(I->first); + State = State->remove<AllocatedData>(Sym); // If the allocated symbol is null do not report. ConstraintManager &CMgr = State->getConstraintManager(); - ConditionTruthVal AllocFailed = CMgr.isNull(State, I.getKey()); + ConditionTruthVal AllocFailed = CMgr.isNull(State, Sym); if (AllocFailed.isConstrainedTrue()) continue; - Errors.push_back(std::make_pair(I->first, &I->second)); + Errors.push_back(std::make_pair(Sym, &AllocState)); } if (!Changed) { // Generate the new, cleaned up state. @@ -656,8 +647,8 @@ void MacOSKeychainAPIChecker::printState(raw_ostream &Out, if (!AMap.isEmpty()) { Out << Sep << "KeychainAPIChecker :" << NL; - for (auto I = AMap.begin(), E = AMap.end(); I != E; ++I) { - I.getKey()->dumpToStream(Out); + for (SymbolRef Sym : llvm::make_first_range(AMap)) { + Sym->dumpToStream(Out); } } } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp index 04e7f8dec8d7..754b16764296 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp @@ -18,6 +18,7 @@ #include "clang/Basic/TargetInfo.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/CheckerContext.h" @@ -31,7 +32,8 @@ using namespace ento; namespace { class MacOSXAPIChecker : public Checker< check::PreStmt<CallExpr> > { - mutable std::unique_ptr<BugType> BT_dispatchOnce; + const BugType BT_dispatchOnce{this, "Improper use of 'dispatch_once'", + categories::AppleAPIMisuse}; static const ObjCIvarRegion *getParentIvarRegion(const MemRegion *R); @@ -136,12 +138,8 @@ void MacOSXAPIChecker::CheckDispatchOnce(CheckerContext &C, const CallExpr *CE, if (!N) return; - if (!BT_dispatchOnce) - BT_dispatchOnce.reset(new BugType(this, "Improper use of 'dispatch_once'", - "API Misuse (Apple)")); - auto report = - std::make_unique<PathSensitiveBugReport>(*BT_dispatchOnce, os.str(), N); + std::make_unique<PathSensitiveBugReport>(BT_dispatchOnce, os.str(), N); report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(report)); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index a6470da09c45..79ab05f2c786 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -48,9 +48,13 @@ #include "InterCheckerAPI.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclTemplate.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/ParentMap.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Analysis/ProgramPoint.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TargetInfo.h" @@ -60,22 +64,29 @@ #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.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/DynamicExtent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.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/StoreRef.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SetOperations.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Casting.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/raw_ostream.h" #include <climits> #include <functional> +#include <optional> #include <utility> using namespace clang; @@ -210,10 +221,10 @@ static bool isReleased(SymbolRef Sym, CheckerContext &C); /// Update the RefState to reflect the new memory allocation. /// The optional \p RetVal parameter specifies the newly allocated pointer /// value; if unspecified, the value of expression \p E is used. -static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, - ProgramStateRef State, - AllocationFamily Family, - Optional<SVal> RetVal = None); +static ProgramStateRef +MallocUpdateRefState(CheckerContext &C, const Expr *E, ProgramStateRef State, + AllocationFamily Family, + std::optional<SVal> RetVal = std::nullopt); //===----------------------------------------------------------------------===// // The modeling of memory reallocation. @@ -296,7 +307,9 @@ public: /// 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; + bool ShouldIncludeOwnershipAnnotatedFunctions = false; + + bool ShouldRegisterNoOwnershipChangeVisitor = false; /// Many checkers are essentially built into this one, so enabling them will /// make MallocChecker perform additional modeling and reporting. @@ -314,7 +327,7 @@ public: using LeakInfo = std::pair<const ExplodedNode *, const MemRegion *>; - DefaultBool ChecksEnabled[CK_NumCheckKinds]; + bool ChecksEnabled[CK_NumCheckKinds] = {false}; CheckerNameRef CheckNames[CK_NumCheckKinds]; void checkPreCall(const CallEvent &Call, CheckerContext &C) const; @@ -379,70 +392,72 @@ private: 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}, + {{{"free"}, 1}, &MallocChecker::checkFree}, + {{{"if_freenameindex"}, 1}, &MallocChecker::checkIfFreeNameIndex}, + {{{"kfree"}, 1}, &MallocChecker::checkFree}, + {{{"g_free"}, 1}, &MallocChecker::checkFree}, }; bool isFreeingCall(const CallEvent &Call) const; + static bool isFreeingOwnershipAttrCall(const FunctionDecl *Func); + + friend class NoOwnershipChangeVisitor; 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}, + {{{"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}, + {{{"realloc"}, 2}, std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)}, - {{"reallocf", 2}, + {{{"reallocf"}, 2}, std::bind(&MallocChecker::checkRealloc, _1, _2, _3, true)}, - {{"g_realloc", 2}, + {{{"g_realloc"}, 2}, std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)}, - {{"g_try_realloc", 2}, + {{{"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}, + {{{"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; + mutable std::optional<uint64_t> KernelZeroFlagVal; - using KernelZeroSizePtrValueTy = Optional<int>; + using KernelZeroSizePtrValueTy = std::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; + mutable std::optional<KernelZeroSizePtrValueTy> KernelZeroSizePtrValue; /// Process C++ operator new()'s allocation, which is the part of C++ /// new-expression that goes before the constructor. - LLVM_NODISCARD - ProgramStateRef processNewAllocation(const CXXAllocatorCall &Call, - CheckerContext &C, - AllocationFamily Family) const; + [[nodiscard]] ProgramStateRef + processNewAllocation(const CXXAllocatorCall &Call, CheckerContext &C, + AllocationFamily Family) const; /// Perform a zero-allocation check. /// @@ -452,11 +467,10 @@ private: /// 0. /// \param [in] RetVal Specifies the newly allocated pointer value; /// if unspecified, the value of expression \p E is used. - LLVM_NODISCARD - static ProgramStateRef ProcessZeroAllocCheck(const CallEvent &Call, - const unsigned IndexOfSizeArg, - ProgramStateRef State, - Optional<SVal> RetVal = None); + [[nodiscard]] static ProgramStateRef + ProcessZeroAllocCheck(const CallEvent &Call, const unsigned IndexOfSizeArg, + ProgramStateRef State, + std::optional<SVal> RetVal = std::nullopt); /// Model functions with the ownership_returns attribute. /// @@ -474,10 +488,9 @@ private: /// \param [in] Att The ownership_returns attribute. /// \param [in] State The \c ProgramState right before allocation. /// \returns The ProgramState right after allocation. - LLVM_NODISCARD - ProgramStateRef MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call, - const OwnershipAttr *Att, - ProgramStateRef State) const; + [[nodiscard]] ProgramStateRef + MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call, + const OwnershipAttr *Att, ProgramStateRef State) const; /// Models memory allocation. /// @@ -488,11 +501,9 @@ private: /// malloc leaves it undefined. /// \param [in] State The \c ProgramState right before allocation. /// \returns The ProgramState right after allocation. - LLVM_NODISCARD - static ProgramStateRef MallocMemAux(CheckerContext &C, const CallEvent &Call, - const Expr *SizeEx, SVal Init, - ProgramStateRef State, - AllocationFamily Family); + [[nodiscard]] static ProgramStateRef + MallocMemAux(CheckerContext &C, const CallEvent &Call, const Expr *SizeEx, + SVal Init, ProgramStateRef State, AllocationFamily Family); /// Models memory allocation. /// @@ -503,16 +514,13 @@ private: /// malloc leaves it undefined. /// \param [in] State The \c ProgramState right before allocation. /// \returns The ProgramState right after allocation. - LLVM_NODISCARD - static ProgramStateRef MallocMemAux(CheckerContext &C, const CallEvent &Call, - SVal Size, SVal Init, - ProgramStateRef State, - AllocationFamily Family); + [[nodiscard]] static ProgramStateRef + MallocMemAux(CheckerContext &C, const CallEvent &Call, SVal Size, SVal Init, + ProgramStateRef State, AllocationFamily Family); // 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> + [[nodiscard]] std::optional<ProgramStateRef> performKernelMalloc(const CallEvent &Call, CheckerContext &C, const ProgramStateRef &State) const; @@ -533,10 +541,10 @@ private: /// \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. - LLVM_NODISCARD - ProgramStateRef FreeMemAttr(CheckerContext &C, const CallEvent &Call, - const OwnershipAttr *Att, - ProgramStateRef State) const; + [[nodiscard]] ProgramStateRef FreeMemAttr(CheckerContext &C, + const CallEvent &Call, + const OwnershipAttr *Att, + ProgramStateRef State) const; /// Models memory deallocation. /// @@ -557,12 +565,10 @@ 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 CallEvent &Call, - ProgramStateRef State, unsigned Num, bool Hold, - bool &IsKnownToBeAllocated, - AllocationFamily Family, - bool ReturnsNullOnFailure = false) const; + [[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. /// @@ -583,12 +589,10 @@ 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 CallEvent &Call, ProgramStateRef State, - bool Hold, bool &IsKnownToBeAllocated, - AllocationFamily Family, - bool ReturnsNullOnFailure = false) const; + [[nodiscard]] ProgramStateRef + FreeMemAux(CheckerContext &C, const Expr *ArgExpr, const CallEvent &Call, + ProgramStateRef State, bool Hold, bool &IsKnownToBeAllocated, + AllocationFamily Family, bool ReturnsNullOnFailure = false) const; // TODO: Needs some refactoring, as all other deallocation modeling // functions are suffering from out parameters and messy code due to how @@ -603,29 +607,27 @@ private: /// \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. - LLVM_NODISCARD - ProgramStateRef ReallocMemAux(CheckerContext &C, const CallEvent &Call, - bool ShouldFreeOnFail, ProgramStateRef State, - AllocationFamily Family, - bool SuffixWithN = false) const; + [[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. /// /// \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); + [[nodiscard]] static SVal evalMulForBufferSize(CheckerContext &C, + const Expr *Blocks, + const Expr *BlockBytes); /// Models zero initialized array allocation. /// /// \param [in] Call The expression that reallocated memory /// \param [in] State The \c ProgramState right before reallocation. /// \returns The ProgramState right after allocation. - LLVM_NODISCARD - static ProgramStateRef CallocMem(CheckerContext &C, const CallEvent &Call, - ProgramStateRef State); + [[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. @@ -658,12 +660,11 @@ private: SymbolRef &EscapingSymbol) const; /// Implementation of the checkPointerEscape callbacks. - LLVM_NODISCARD - ProgramStateRef checkPointerEscapeAux(ProgramStateRef State, - const InvalidatedSymbols &Escaped, - const CallEvent *Call, - PointerEscapeKind Kind, - bool IsConstPointerEscape) const; + [[nodiscard]] ProgramStateRef + checkPointerEscapeAux(ProgramStateRef State, + const InvalidatedSymbols &Escaped, + const CallEvent *Call, PointerEscapeKind Kind, + bool IsConstPointerEscape) const; // Implementation of the checkPreStmt and checkEndFunction callbacks. void checkEscapeOnReturn(const ReturnStmt *S, CheckerContext &C) const; @@ -672,11 +673,11 @@ private: /// Tells if a given family/call/symbol is tracked by the current checker. /// Sets CheckKind to the kind of the checker responsible for this /// family/call/symbol. - Optional<CheckKind> getCheckIfTracked(AllocationFamily Family, - bool IsALeakCheck = false) const; + std::optional<CheckKind> getCheckIfTracked(AllocationFamily Family, + bool IsALeakCheck = false) const; - Optional<CheckKind> getCheckIfTracked(CheckerContext &C, SymbolRef Sym, - bool IsALeakCheck = false) const; + std::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); @@ -722,11 +723,204 @@ private: bool isArgZERO_SIZE_PTR(ProgramStateRef State, CheckerContext &C, SVal ArgVal) const; }; +} // end anonymous namespace + +//===----------------------------------------------------------------------===// +// Definition of NoOwnershipChangeVisitor. +//===----------------------------------------------------------------------===// + +namespace { +class NoOwnershipChangeVisitor final : public NoStateChangeFuncVisitor { + // The symbol whose (lack of) ownership change we are interested in. + SymbolRef Sym; + const MallocChecker &Checker; + using OwnerSet = llvm::SmallPtrSet<const MemRegion *, 8>; + + // Collect which entities point to the allocated memory, and could be + // responsible for deallocating it. + class OwnershipBindingsHandler : public StoreManager::BindingsHandler { + SymbolRef Sym; + OwnerSet &Owners; + + public: + OwnershipBindingsHandler(SymbolRef Sym, OwnerSet &Owners) + : Sym(Sym), Owners(Owners) {} + + bool HandleBinding(StoreManager &SMgr, Store Store, const MemRegion *Region, + SVal Val) override { + if (Val.getAsSymbol() == Sym) + Owners.insert(Region); + return true; + } + + LLVM_DUMP_METHOD void dump() const { dumpToStream(llvm::errs()); } + LLVM_DUMP_METHOD void dumpToStream(llvm::raw_ostream &out) const { + out << "Owners: {\n"; + for (const MemRegion *Owner : Owners) { + out << " "; + Owner->dumpToStream(out); + out << ",\n"; + } + out << "}\n"; + } + }; + +protected: + OwnerSet getOwnersAtNode(const ExplodedNode *N) { + OwnerSet Ret; + + ProgramStateRef State = N->getState(); + OwnershipBindingsHandler Handler{Sym, Ret}; + State->getStateManager().getStoreManager().iterBindings(State->getStore(), + Handler); + return Ret; + } + + LLVM_DUMP_METHOD static std::string + getFunctionName(const ExplodedNode *CallEnterN) { + if (const CallExpr *CE = llvm::dyn_cast_or_null<CallExpr>( + CallEnterN->getLocationAs<CallEnter>()->getCallExpr())) + if (const FunctionDecl *FD = CE->getDirectCallee()) + return FD->getQualifiedNameAsString(); + return ""; + } + + /// Syntactically checks whether the callee is a deallocating function. Since + /// we have no path-sensitive information on this call (we would need a + /// CallEvent instead of a CallExpr for that), its possible that a + /// deallocation function was called indirectly through a function pointer, + /// but we are not able to tell, so this is a best effort analysis. + /// See namespace `memory_passed_to_fn_call_free_through_fn_ptr` in + /// clang/test/Analysis/NewDeleteLeaks.cpp. + bool isFreeingCallAsWritten(const CallExpr &Call) const { + if (Checker.FreeingMemFnMap.lookupAsWritten(Call) || + Checker.ReallocatingMemFnMap.lookupAsWritten(Call)) + return true; + + if (const auto *Func = + llvm::dyn_cast_or_null<FunctionDecl>(Call.getCalleeDecl())) + return MallocChecker::isFreeingOwnershipAttrCall(Func); + + return false; + } + + /// Heuristically guess whether the callee intended to free memory. This is + /// done syntactically, because we are trying to argue about alternative + /// paths of execution, and as a consequence we don't have path-sensitive + /// information. + bool doesFnIntendToHandleOwnership(const Decl *Callee, ASTContext &ACtx) { + using namespace clang::ast_matchers; + const FunctionDecl *FD = dyn_cast<FunctionDecl>(Callee); + + // Given that the stack frame was entered, the body should always be + // theoretically obtainable. In case of body farms, the synthesized body + // is not attached to declaration, thus triggering the '!FD->hasBody()' + // branch. That said, would a synthesized body ever intend to handle + // ownership? As of today they don't. And if they did, how would we + // put notes inside it, given that it doesn't match any source locations? + if (!FD || !FD->hasBody()) + return false; + + auto Matches = match(findAll(stmt(anyOf(cxxDeleteExpr().bind("delete"), + callExpr().bind("call")))), + *FD->getBody(), ACtx); + for (BoundNodes Match : Matches) { + if (Match.getNodeAs<CXXDeleteExpr>("delete")) + return true; + + if (const auto *Call = Match.getNodeAs<CallExpr>("call")) + if (isFreeingCallAsWritten(*Call)) + return true; + } + // TODO: Ownership might change with an attempt to store the allocated + // memory, not only through deallocation. Check for attempted stores as + // well. + return false; + } + + bool wasModifiedInFunction(const ExplodedNode *CallEnterN, + const ExplodedNode *CallExitEndN) override { + if (!doesFnIntendToHandleOwnership( + CallExitEndN->getFirstPred()->getLocationContext()->getDecl(), + CallExitEndN->getState()->getAnalysisManager().getASTContext())) + return true; + + if (CallEnterN->getState()->get<RegionState>(Sym) != + CallExitEndN->getState()->get<RegionState>(Sym)) + return true; + + OwnerSet CurrOwners = getOwnersAtNode(CallEnterN); + OwnerSet ExitOwners = getOwnersAtNode(CallExitEndN); + + // Owners in the current set may be purged from the analyzer later on. + // If a variable is dead (is not referenced directly or indirectly after + // some point), it will be removed from the Store before the end of its + // actual lifetime. + // This means that if the ownership status didn't change, CurrOwners + // must be a superset of, but not necessarily equal to ExitOwners. + return !llvm::set_is_subset(ExitOwners, CurrOwners); + } + + static PathDiagnosticPieceRef emitNote(const ExplodedNode *N) { + PathDiagnosticLocation L = PathDiagnosticLocation::create( + N->getLocation(), + N->getState()->getStateManager().getContext().getSourceManager()); + return std::make_shared<PathDiagnosticEventPiece>( + L, "Returning without deallocating memory or storing the pointer for " + "later deallocation"); + } + + PathDiagnosticPieceRef + maybeEmitNoteForObjCSelf(PathSensitiveBugReport &R, + const ObjCMethodCall &Call, + const ExplodedNode *N) override { + // TODO: Implement. + return nullptr; + } + + PathDiagnosticPieceRef + maybeEmitNoteForCXXThis(PathSensitiveBugReport &R, + const CXXConstructorCall &Call, + const ExplodedNode *N) override { + // TODO: Implement. + return nullptr; + } + + PathDiagnosticPieceRef + maybeEmitNoteForParameters(PathSensitiveBugReport &R, const CallEvent &Call, + const ExplodedNode *N) override { + // TODO: Factor the logic of "what constitutes as an entity being passed + // into a function call" out by reusing the code in + // NoStoreFuncVisitor::maybeEmitNoteForParameters, maybe by incorporating + // the printing technology in UninitializedObject's FieldChainInfo. + ArrayRef<ParmVarDecl *> Parameters = Call.parameters(); + for (unsigned I = 0; I < Call.getNumArgs() && I < Parameters.size(); ++I) { + SVal V = Call.getArgSVal(I); + if (V.getAsSymbol() == Sym) + return emitNote(N); + } + return nullptr; + } + +public: + NoOwnershipChangeVisitor(SymbolRef Sym, const MallocChecker *Checker) + : NoStateChangeFuncVisitor(bugreporter::TrackingKind::Thorough), Sym(Sym), + Checker(*Checker) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int Tag = 0; + ID.AddPointer(&Tag); + ID.AddPointer(Sym); + } +}; + +} // end anonymous namespace //===----------------------------------------------------------------------===// // Definition of MallocBugVisitor. //===----------------------------------------------------------------------===// +namespace { /// The bug visitor which allows us to print extra diagnostics along the /// BugReport path. For example, showing the allocation site of the leaked /// region. @@ -767,7 +961,7 @@ public: /// Did not track -> allocated. Other state (released) -> allocated. static inline bool isAllocated(const RefState *RSCurr, const RefState *RSPrev, const Stmt *Stmt) { - return (Stmt && (isa<CallExpr>(Stmt) || isa<CXXNewExpr>(Stmt)) && + return (isa_and_nonnull<CallExpr, CXXNewExpr>(Stmt) && (RSCurr && (RSCurr->isAllocated() || RSCurr->isAllocatedOfSizeZero())) && (!RSPrev || @@ -780,8 +974,7 @@ public: const Stmt *Stmt) { bool IsReleased = (RSCurr && RSCurr->isReleased()) && (!RSPrev || !RSPrev->isReleased()); - assert(!IsReleased || - (Stmt && (isa<CallExpr>(Stmt) || isa<CXXDeleteExpr>(Stmt))) || + assert(!IsReleased || (isa_and_nonnull<CallExpr, CXXDeleteExpr>(Stmt)) || (!Stmt && RSCurr->getAllocationFamily() == AF_InnerBuffer)); return IsReleased; } @@ -789,11 +982,10 @@ public: /// Did not track -> relinquished. Other state (allocated) -> relinquished. static inline bool isRelinquished(const RefState *RSCurr, const RefState *RSPrev, const Stmt *Stmt) { - return (Stmt && - (isa<CallExpr>(Stmt) || isa<ObjCMessageExpr>(Stmt) || - isa<ObjCPropertyRefExpr>(Stmt)) && - (RSCurr && RSCurr->isRelinquished()) && - (!RSPrev || !RSPrev->isRelinquished())); + return ( + isa_and_nonnull<CallExpr, ObjCMessageExpr, ObjCPropertyRefExpr>(Stmt) && + (RSCurr && RSCurr->isRelinquished()) && + (!RSPrev || !RSPrev->isRelinquished())); } /// If the expression is not a call, and the state change is @@ -803,7 +995,7 @@ public: static inline bool hasReallocFailed(const RefState *RSCurr, const RefState *RSPrev, const Stmt *Stmt) { - return ((!Stmt || !isa<CallExpr>(Stmt)) && + return ((!isa_and_nonnull<CallExpr>(Stmt)) && (RSCurr && (RSCurr->isAllocated() || RSCurr->isAllocatedOfSizeZero())) && (RSPrev && @@ -851,7 +1043,6 @@ private: } }; }; - } // end anonymous namespace // A map from the freed symbol to the symbol representing the return value of @@ -894,12 +1085,8 @@ static bool isStandardNewDelete(const FunctionDecl *FD) { // Methods of MallocChecker and MallocBugVisitor. //===----------------------------------------------------------------------===// -bool MallocChecker::isFreeingCall(const CallEvent &Call) const { - if (FreeingMemFnMap.lookup(Call) || ReallocatingMemFnMap.lookup(Call)) - return true; - - const auto *Func = dyn_cast<FunctionDecl>(Call.getDecl()); - if (Func && Func->hasAttrs()) { +bool MallocChecker::isFreeingOwnershipAttrCall(const FunctionDecl *Func) { + if (Func->hasAttrs()) { for (const auto *I : Func->specific_attrs<OwnershipAttr>()) { OwnershipAttr::OwnershipKind OwnKind = I->getOwnKind(); if (OwnKind == OwnershipAttr::Takes || OwnKind == OwnershipAttr::Holds) @@ -909,6 +1096,16 @@ bool MallocChecker::isFreeingCall(const CallEvent &Call) const { return false; } +bool MallocChecker::isFreeingCall(const CallEvent &Call) const { + if (FreeingMemFnMap.lookup(Call) || ReallocatingMemFnMap.lookup(Call)) + return true; + + if (const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl())) + return isFreeingOwnershipAttrCall(Func); + + return false; +} + bool MallocChecker::isMemCall(const CallEvent &Call) const { if (FreeingMemFnMap.lookup(Call) || AllocatingMemFnMap.lookup(Call) || ReallocatingMemFnMap.lookup(Call)) @@ -921,7 +1118,7 @@ bool MallocChecker::isMemCall(const CallEvent &Call) const { return Func && Func->hasAttr<OwnershipAttr>(); } -llvm::Optional<ProgramStateRef> +std::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: @@ -943,48 +1140,54 @@ MallocChecker::performKernelMalloc(const CallEvent &Call, CheckerContext &C, ASTContext &Ctx = C.getASTContext(); llvm::Triple::OSType OS = Ctx.getTargetInfo().getTriple().getOS(); - if (!KernelZeroFlagVal.hasValue()) { - if (OS == llvm::Triple::FreeBSD) + if (!KernelZeroFlagVal) { + switch (OS) { + case llvm::Triple::FreeBSD: KernelZeroFlagVal = 0x0100; - else if (OS == llvm::Triple::NetBSD) + break; + case llvm::Triple::NetBSD: KernelZeroFlagVal = 0x0002; - else if (OS == llvm::Triple::OpenBSD) + break; + case llvm::Triple::OpenBSD: KernelZeroFlagVal = 0x0008; - else if (OS == llvm::Triple::Linux) + break; + case llvm::Triple::Linux: // __GFP_ZERO KernelZeroFlagVal = 0x8000; - else + break; + default: // FIXME: We need a more general way of getting the M_ZERO value. // See also: O_CREAT in UnixAPIChecker.cpp. // Fall back to normal malloc behavior on platforms where we don't // know M_ZERO. - return None; + return std::nullopt; + } } // 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 (Call.getNumArgs() < 2) - return None; + return std::nullopt; const Expr *FlagsEx = Call.getArgExpr(Call.getNumArgs() - 1); const SVal V = C.getSVal(FlagsEx); - if (!V.getAs<NonLoc>()) { + if (!isa<NonLoc>(V)) { // The case where 'V' can be a location can only be due to a bad header, // so in this case bail out. - return None; + return std::nullopt; } NonLoc Flags = V.castAs<NonLoc>(); NonLoc ZeroFlag = C.getSValBuilder() - .makeIntVal(KernelZeroFlagVal.getValue(), FlagsEx->getType()) - .castAs<NonLoc>(); + .makeIntVal(*KernelZeroFlagVal, FlagsEx->getType()) + .castAs<NonLoc>(); SVal MaskedFlagsUC = C.getSValBuilder().evalBinOpNN(State, BO_And, Flags, ZeroFlag, FlagsEx->getType()); if (MaskedFlagsUC.isUnknownOrUndef()) - return None; + return std::nullopt; DefinedSVal MaskedFlags = MaskedFlagsUC.castAs<DefinedSVal>(); // Check if maskedFlags is non-zero. @@ -998,7 +1201,7 @@ MallocChecker::performKernelMalloc(const CallEvent &Call, CheckerContext &C, AF_Malloc); } - return None; + return std::nullopt; } SVal MallocChecker::evalMulForBufferSize(CheckerContext &C, const Expr *Blocks, @@ -1024,10 +1227,10 @@ void MallocChecker::checkBasicAlloc(const CallEvent &Call, void MallocChecker::checkKernelMalloc(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); - llvm::Optional<ProgramStateRef> MaybeState = + std::optional<ProgramStateRef> MaybeState = performKernelMalloc(Call, C, State); - if (MaybeState.hasValue()) - State = MaybeState.getValue(); + if (MaybeState) + State = *MaybeState; else State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State, AF_Malloc); @@ -1191,8 +1394,8 @@ void MallocChecker::checkGMalloc0(const CallEvent &Call, 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 = + MallocMemAux(C, Call, Call.getArgExpr(1), UnknownVal(), State, AF_Malloc); State = ProcessZeroAllocCheck(Call, 1, State); C.addTransition(State); } @@ -1294,7 +1497,7 @@ void MallocChecker::checkPostCall(const CallEvent &Call, // Performs a 0-sized allocations check. ProgramStateRef MallocChecker::ProcessZeroAllocCheck( const CallEvent &Call, const unsigned IndexOfSizeArg, ProgramStateRef State, - Optional<SVal> RetVal) { + std::optional<SVal> RetVal) { if (!State) return nullptr; @@ -1446,7 +1649,7 @@ static bool isKnownDeallocObjCMethodName(const ObjCMethodCall &Call) { FirstSlot == "initWithCharactersNoCopy"; } -static Optional<bool> getFreeWhenDoneArg(const ObjCMethodCall &Call) { +static std::optional<bool> getFreeWhenDoneArg(const ObjCMethodCall &Call) { Selector S = Call.getSelector(); // FIXME: We should not rely on fully-constrained symbols being folded. @@ -1454,7 +1657,7 @@ static Optional<bool> getFreeWhenDoneArg(const ObjCMethodCall &Call) { if (S.getNameForSlot(i).equals("freeWhenDone")) return !Call.getArgSVal(i).isZeroConstant(); - return None; + return std::nullopt; } void MallocChecker::checkPostObjCMessage(const ObjCMethodCall &Call, @@ -1465,7 +1668,7 @@ void MallocChecker::checkPostObjCMessage(const ObjCMethodCall &Call, if (!isKnownDeallocObjCMethodName(Call)) return; - if (Optional<bool> FreeWhenDone = getFreeWhenDoneArg(Call)) + if (std::optional<bool> FreeWhenDone = getFreeWhenDoneArg(Call)) if (!*FreeWhenDone) return; @@ -1476,7 +1679,7 @@ void MallocChecker::checkPostObjCMessage(const ObjCMethodCall &Call, ProgramStateRef State = FreeMemAux(C, Call.getArgExpr(0), Call, C.getState(), /*Hold=*/true, IsKnownToBeAllocatedMemory, AF_Malloc, - /*RetNullOnFailure=*/true); + /*ReturnsNullOnFailure=*/true); C.addTransition(State); } @@ -1491,9 +1694,9 @@ MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call, if (Att->getModule()->getName() != "malloc") return nullptr; - OwnershipAttr::args_iterator I = Att->args_begin(), E = Att->args_end(); - if (I != E) { - return MallocMemAux(C, Call, Call.getArgExpr(I->getASTIndex()), + if (!Att->args().empty()) { + return MallocMemAux(C, Call, + Call.getArgExpr(Att->args_begin()->getASTIndex()), UndefinedVal(), State, AF_Malloc); } return MallocMemAux(C, Call, UnknownVal(), UndefinedVal(), State, AF_Malloc); @@ -1525,21 +1728,27 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, return nullptr; // Bind the return value to the symbolic value from the heap region. - // TODO: We could rewrite post visit to eval call; 'malloc' does not have - // side effects other than what we model here. + // TODO: move use of this functions to an EvalCall callback, becasue + // BindExpr() should'nt be used elsewhere. unsigned Count = C.blockCount(); - SValBuilder &svalBuilder = C.getSValBuilder(); + SValBuilder &SVB = C.getSValBuilder(); const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); - DefinedSVal RetVal = svalBuilder.getConjuredHeapSymbolVal(CE, LCtx, Count) - .castAs<DefinedSVal>(); + DefinedSVal RetVal = + ((Family == AF_Alloca) ? SVB.getAllocaRegionVal(CE, LCtx, Count) + : SVB.getConjuredHeapSymbolVal(CE, LCtx, Count) + .castAs<DefinedSVal>()); State = State->BindExpr(CE, C.getLocationContext(), RetVal); // Fill the region with the initialization value. State = State->bindDefaultInitial(RetVal, Init, LCtx); + // If Size is somehow undefined at this point, this line prevents a crash. + if (Size.isUndef()) + Size = UnknownVal(); + // Set the region's extent. State = setDynamicExtent(State, RetVal.getAsRegion(), - Size.castAs<DefinedOrUnknownSVal>(), svalBuilder); + Size.castAs<DefinedOrUnknownSVal>(), SVB); return MallocUpdateRefState(C, CE, State, Family); } @@ -1547,7 +1756,7 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, ProgramStateRef State, AllocationFamily Family, - Optional<SVal> RetVal) { + std::optional<SVal> RetVal) { if (!State) return nullptr; @@ -1695,12 +1904,12 @@ ProgramStateRef MallocChecker::FreeMemAux( return nullptr; SVal ArgVal = C.getSVal(ArgExpr); - if (!ArgVal.getAs<DefinedOrUnknownSVal>()) + if (!isa<DefinedOrUnknownSVal>(ArgVal)) return nullptr; DefinedOrUnknownSVal location = ArgVal.castAs<DefinedOrUnknownSVal>(); // Check for null dereferences. - if (!location.getAs<Loc>()) + if (!isa<Loc>(location)) return nullptr; // The explicit NULL case, no operation is performed. @@ -1753,14 +1962,11 @@ ProgramStateRef MallocChecker::FreeMemAux( // Parameters, locals, statics, globals, and memory returned by // __builtin_alloca() shouldn't be freed. - if (!(isa<UnknownSpaceRegion>(MS) || isa<HeapSpaceRegion>(MS))) { - // FIXME: at the time this code was written, malloc() regions were - // represented by conjured symbols, which are all in UnknownSpaceRegion. - // This means that there isn't actually anything from HeapSpaceRegion - // that should be freed, even though we allow it here. - // Of course, free() can work on memory allocated outside the current - // function, so UnknownSpaceRegion is always a possibility. - // False negatives are better than false positives. + if (!isa<UnknownSpaceRegion, HeapSpaceRegion>(MS)) { + // Regions returned by malloc() are represented by SymbolicRegion objects + // within HeapSpaceRegion. Of course, free() can work on memory allocated + // outside the current function, so UnknownSpaceRegion is also a + // possibility here. if (isa<AllocaRegion>(R)) HandleFreeAlloca(C, ArgVal, ArgExpr->getSourceRange()); @@ -1862,7 +2068,7 @@ ProgramStateRef MallocChecker::FreeMemAux( RefState::getReleased(Family, ParentExpr)); } -Optional<MallocChecker::CheckKind> +std::optional<MallocChecker::CheckKind> MallocChecker::getCheckIfTracked(AllocationFamily Family, bool IsALeakCheck) const { switch (Family) { @@ -1871,7 +2077,7 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family, case AF_IfNameIndex: { if (ChecksEnabled[CK_MallocChecker]) return CK_MallocChecker; - return None; + return std::nullopt; } case AF_CXXNew: case AF_CXXNewArray: { @@ -1883,12 +2089,12 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family, if (ChecksEnabled[CK_NewDeleteChecker]) return CK_NewDeleteChecker; } - return None; + return std::nullopt; } case AF_InnerBuffer: { if (ChecksEnabled[CK_InnerPointerChecker]) return CK_InnerPointerChecker; - return None; + return std::nullopt; } case AF_None: { llvm_unreachable("no family"); @@ -1897,7 +2103,7 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family, llvm_unreachable("unhandled family"); } -Optional<MallocChecker::CheckKind> +std::optional<MallocChecker::CheckKind> MallocChecker::getCheckIfTracked(CheckerContext &C, SymbolRef Sym, bool IsALeakCheck) const { if (C.getState()->contains<ReallocSizeZeroSymbols>(Sym)) @@ -1909,11 +2115,13 @@ MallocChecker::getCheckIfTracked(CheckerContext &C, SymbolRef Sym, } bool MallocChecker::SummarizeValue(raw_ostream &os, SVal V) { - if (Optional<nonloc::ConcreteInt> IntVal = V.getAs<nonloc::ConcreteInt>()) + if (std::optional<nonloc::ConcreteInt> IntVal = + V.getAs<nonloc::ConcreteInt>()) os << "an integer (" << IntVal->getValue() << ")"; - else if (Optional<loc::ConcreteInt> ConstAddr = V.getAs<loc::ConcreteInt>()) + else if (std::optional<loc::ConcreteInt> ConstAddr = + V.getAs<loc::ConcreteInt>()) os << "a constant address (" << ConstAddr->getValue() << ")"; - else if (Optional<loc::GotoLabel> Label = V.getAs<loc::GotoLabel>()) + else if (std::optional<loc::GotoLabel> Label = V.getAs<loc::GotoLabel>()) os << "the address of the label '" << Label->getLabel()->getName() << "'"; else return false; @@ -2005,8 +2213,8 @@ void MallocChecker::HandleNonHeapDealloc(CheckerContext &C, SVal ArgVal, return; } - Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family); - if (!CheckKind.hasValue()) + std::optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family); + if (!CheckKind) return; if (ExplodedNode *N = C.generateErrorNode()) { @@ -2046,7 +2254,7 @@ void MallocChecker::HandleNonHeapDealloc(CheckerContext &C, SVal ArgVal, void MallocChecker::HandleFreeAlloca(CheckerContext &C, SVal ArgVal, SourceRange Range) const { - Optional<MallocChecker::CheckKind> CheckKind; + std::optional<MallocChecker::CheckKind> CheckKind; if (ChecksEnabled[CK_MallocChecker]) CheckKind = CK_MallocChecker; @@ -2138,8 +2346,8 @@ void MallocChecker::HandleOffsetFree(CheckerContext &C, SVal ArgVal, return; } - Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family); - if (!CheckKind.hasValue()) + std::optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family); + if (!CheckKind) return; ExplodedNode *N = C.generateErrorNode(); @@ -2195,8 +2403,8 @@ void MallocChecker::HandleUseAfterFree(CheckerContext &C, SourceRange Range, return; } - Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); - if (!CheckKind.hasValue()) + std::optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); + if (!CheckKind) return; if (ExplodedNode *N = C.generateErrorNode()) { @@ -2234,8 +2442,8 @@ void MallocChecker::HandleDoubleFree(CheckerContext &C, SourceRange Range, return; } - Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); - if (!CheckKind.hasValue()) + std::optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); + if (!CheckKind) return; if (ExplodedNode *N = C.generateErrorNode()) { @@ -2264,8 +2472,8 @@ void MallocChecker::HandleDoubleDelete(CheckerContext &C, SymbolRef Sym) const { return; } - Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); - if (!CheckKind.hasValue()) + std::optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); + if (!CheckKind) return; if (ExplodedNode *N = C.generateErrorNode()) { @@ -2291,9 +2499,9 @@ void MallocChecker::HandleUseZeroAlloc(CheckerContext &C, SourceRange Range, return; } - Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); + std::optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); - if (!CheckKind.hasValue()) + if (!CheckKind) return; if (ExplodedNode *N = C.generateErrorNode()) { @@ -2303,7 +2511,8 @@ void MallocChecker::HandleUseZeroAlloc(CheckerContext &C, SourceRange Range, categories::MemoryError)); auto R = std::make_unique<PathSensitiveBugReport>( - *BT_UseZerroAllocated[*CheckKind], "Use of zero-allocated memory", N); + *BT_UseZerroAllocated[*CheckKind], + "Use of memory allocated with size zero", N); R->addRange(Range); if (Sym) { @@ -2323,8 +2532,8 @@ void MallocChecker::HandleFunctionPtrFree(CheckerContext &C, SVal ArgVal, return; } - Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family); - if (!CheckKind.hasValue()) + std::optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family); + if (!CheckKind) return; if (ExplodedNode *N = C.generateErrorNode()) { @@ -2369,14 +2578,14 @@ MallocChecker::ReallocMemAux(CheckerContext &C, const CallEvent &Call, const Expr *arg0Expr = CE->getArg(0); SVal Arg0Val = C.getSVal(arg0Expr); - if (!Arg0Val.getAs<DefinedOrUnknownSVal>()) + if (!isa<DefinedOrUnknownSVal>(Arg0Val)) return nullptr; DefinedOrUnknownSVal arg0Val = Arg0Val.castAs<DefinedOrUnknownSVal>(); SValBuilder &svalBuilder = C.getSValBuilder(); - DefinedOrUnknownSVal PtrEQ = - svalBuilder.evalEQ(State, arg0Val, svalBuilder.makeNull()); + DefinedOrUnknownSVal PtrEQ = svalBuilder.evalEQ( + State, arg0Val, svalBuilder.makeNullWithType(arg0Expr->getType())); // Get the size argument. const Expr *Arg1 = CE->getArg(1); @@ -2385,13 +2594,14 @@ MallocChecker::ReallocMemAux(CheckerContext &C, const CallEvent &Call, SVal TotalSize = C.getSVal(Arg1); if (SuffixWithN) TotalSize = evalMulForBufferSize(C, Arg1, CE->getArg(2)); - if (!TotalSize.getAs<DefinedOrUnknownSVal>()) + if (!isa<DefinedOrUnknownSVal>(TotalSize)) return nullptr; // Compare the size argument to 0. DefinedOrUnknownSVal SizeZero = - svalBuilder.evalEQ(State, TotalSize.castAs<DefinedOrUnknownSVal>(), - svalBuilder.makeIntValWithPtrWidth(0, false)); + svalBuilder.evalEQ(State, TotalSize.castAs<DefinedOrUnknownSVal>(), + svalBuilder.makeIntValWithWidth( + svalBuilder.getContext().getSizeType(), 0)); ProgramStateRef StatePtrIsNull, StatePtrNotNull; std::tie(StatePtrIsNull, StatePtrNotNull) = State->assume(PtrEQ); @@ -2533,10 +2743,10 @@ void MallocChecker::HandleLeak(SymbolRef Sym, ExplodedNode *N, if (Family == AF_Alloca) return; - Optional<MallocChecker::CheckKind> - CheckKind = getCheckIfTracked(Family, true); + std::optional<MallocChecker::CheckKind> CheckKind = + getCheckIfTracked(Family, true); - if (!CheckKind.hasValue()) + if (!CheckKind) return; assert(N); @@ -2579,6 +2789,8 @@ void MallocChecker::HandleLeak(SymbolRef Sym, ExplodedNode *N, AllocNode->getLocationContext()->getDecl()); R->markInteresting(Sym); R->addVisitor<MallocBugVisitor>(Sym, true); + if (ShouldRegisterNoOwnershipChangeVisitor) + R->addVisitor<NoOwnershipChangeVisitor>(Sym, this); C.emitReport(std::move(R)); } @@ -2591,12 +2803,12 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, RegionStateTy RS = OldRS; SmallVector<SymbolRef, 2> Errors; - for (RegionStateTy::iterator I = RS.begin(), E = RS.end(); I != E; ++I) { - if (SymReaper.isDead(I->first)) { - if (I->second.isAllocated() || I->second.isAllocatedOfSizeZero()) - Errors.push_back(I->first); + for (auto [Sym, State] : RS) { + if (SymReaper.isDead(Sym)) { + if (State.isAllocated() || State.isAllocatedOfSizeZero()) + Errors.push_back(Sym); // Remove the dead symbol from the map. - RS = F.remove(RS, I->first); + RS = F.remove(RS, Sym); } } @@ -2611,19 +2823,17 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, // Cleanup the Realloc Pairs Map. ReallocPairsTy RP = state->get<ReallocPairs>(); - for (ReallocPairsTy::iterator I = RP.begin(), E = RP.end(); I != E; ++I) { - if (SymReaper.isDead(I->first) || - SymReaper.isDead(I->second.ReallocatedSym)) { - state = state->remove<ReallocPairs>(I->first); + for (auto [Sym, ReallocPair] : RP) { + if (SymReaper.isDead(Sym) || SymReaper.isDead(ReallocPair.ReallocatedSym)) { + state = state->remove<ReallocPairs>(Sym); } } // Cleanup the FreeReturnValue Map. FreeReturnValueTy FR = state->get<FreeReturnValue>(); - for (FreeReturnValueTy::iterator I = FR.begin(), E = FR.end(); I != E; ++I) { - if (SymReaper.isDead(I->first) || - SymReaper.isDead(I->second)) { - state = state->remove<FreeReturnValue>(I->first); + for (auto [Sym, RetSym] : FR) { + if (SymReaper.isDead(Sym) || SymReaper.isDead(RetSym)) { + state = state->remove<FreeReturnValue>(Sym); } } @@ -2633,9 +2843,8 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, static CheckerProgramPointTag Tag("MallocChecker", "DeadSymbolsLeak"); N = C.generateNonFatalErrorNode(C.getState(), &Tag); if (N) { - for (SmallVectorImpl<SymbolRef>::iterator - I = Errors.begin(), E = Errors.end(); I != E; ++I) { - HandleLeak(*I, N, C); + for (SymbolRef Sym : Errors) { + HandleLeak(Sym, N, C); } } } @@ -2692,7 +2901,7 @@ void MallocChecker::checkPreCall(const CallEvent &Call, // Check arguments for being used after free. for (unsigned I = 0, E = Call.getNumArgs(); I != E; ++I) { SVal ArgSVal = Call.getArgSVal(I); - if (ArgSVal.getAs<Loc>()) { + if (isa<Loc>(ArgSVal)) { SymbolRef Sym = ArgSVal.getAsSymbol(); if (!Sym) continue; @@ -2733,7 +2942,7 @@ void MallocChecker::checkEscapeOnReturn(const ReturnStmt *S, // the callee could still free the memory. // TODO: This logic should be a part of generic symbol escape callback. if (const MemRegion *MR = RetVal.getAsRegion()) - if (isa<FieldRegion>(MR) || isa<ElementRegion>(MR)) + if (isa<FieldRegion, ElementRegion>(MR)) if (const SymbolicRegion *BMR = dyn_cast<SymbolicRegion>(MR->getBaseRegion())) Sym = BMR->getSymbol(); @@ -2758,18 +2967,16 @@ void MallocChecker::checkPostStmt(const BlockExpr *BE, const BlockDataRegion *R = cast<BlockDataRegion>(C.getSVal(BE).getAsRegion()); - BlockDataRegion::referenced_vars_iterator I = R->referenced_vars_begin(), - E = R->referenced_vars_end(); - - if (I == E) + auto ReferencedVars = R->referenced_vars(); + if (ReferencedVars.empty()) return; SmallVector<const MemRegion*, 10> Regions; const LocationContext *LC = C.getLocationContext(); MemRegionManager &MemMgr = C.getSValBuilder().getRegionManager(); - for ( ; I != E; ++I) { - const VarRegion *VR = I.getCapturedRegion(); + for (const auto &Var : ReferencedVars) { + const VarRegion *VR = Var.getCapturedRegion(); if (VR->getSuperRegion() == R) { VR = MemMgr.getVarRegion(VR->getDecl(), LC); } @@ -2865,28 +3072,28 @@ ProgramStateRef MallocChecker::evalAssume(ProgramStateRef state, SVal Cond, bool Assumption) const { RegionStateTy RS = state->get<RegionState>(); - for (RegionStateTy::iterator I = RS.begin(), E = RS.end(); I != E; ++I) { + for (SymbolRef Sym : llvm::make_first_range(RS)) { // If the symbol is assumed to be NULL, remove it from consideration. ConstraintManager &CMgr = state->getConstraintManager(); - ConditionTruthVal AllocFailed = CMgr.isNull(state, I.getKey()); + ConditionTruthVal AllocFailed = CMgr.isNull(state, Sym); if (AllocFailed.isConstrainedTrue()) - state = state->remove<RegionState>(I.getKey()); + state = state->remove<RegionState>(Sym); } // Realloc returns 0 when reallocation fails, which means that we should // restore the state of the pointer being reallocated. ReallocPairsTy RP = state->get<ReallocPairs>(); - for (ReallocPairsTy::iterator I = RP.begin(), E = RP.end(); I != E; ++I) { + for (auto [Sym, ReallocPair] : RP) { // If the symbol is assumed to be NULL, remove it from consideration. ConstraintManager &CMgr = state->getConstraintManager(); - ConditionTruthVal AllocFailed = CMgr.isNull(state, I.getKey()); + ConditionTruthVal AllocFailed = CMgr.isNull(state, Sym); if (!AllocFailed.isConstrainedTrue()) continue; - SymbolRef ReallocSym = I.getData().ReallocatedSym; + SymbolRef ReallocSym = ReallocPair.ReallocatedSym; if (const RefState *RS = state->get<RegionState>(ReallocSym)) { if (RS->isReleased()) { - switch (I.getData().Kind) { + switch (ReallocPair.Kind) { case OAR_ToBeFreedAfterFailure: state = state->set<RegionState>(ReallocSym, RefState::getAllocated(RS->getAllocationFamily(), RS->getStmt())); @@ -2895,11 +3102,11 @@ ProgramStateRef MallocChecker::evalAssume(ProgramStateRef state, state = state->remove<RegionState>(ReallocSym); break; default: - assert(I.getData().Kind == OAR_FreeOnFailure); + assert(ReallocPair.Kind == OAR_FreeOnFailure); } } } - state = state->remove<ReallocPairs>(I.getKey()); + state = state->remove<ReallocPairs>(Sym); } return state; @@ -2916,7 +3123,7 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly( // TODO: If we want to be more optimistic here, we'll need to make sure that // regions escape to C++ containers. They seem to do that even now, but for // mysterious reasons. - if (!(isa<SimpleFunctionCall>(Call) || isa<ObjCMethodCall>(Call))) + if (!isa<SimpleFunctionCall, ObjCMethodCall>(Call)) return true; // Check Objective-C messages by selector name. @@ -2935,7 +3142,7 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly( // about, we can't be sure that the object will use free() to deallocate the // memory, so we can't model it explicitly. The best we can do is use it to // decide whether the pointer escapes. - if (Optional<bool> FreeWhenDone = getFreeWhenDoneArg(*Msg)) + if (std::optional<bool> FreeWhenDone = getFreeWhenDoneArg(*Msg)) return *FreeWhenDone; // If the first selector piece ends with "NoCopy", and there is no @@ -2943,16 +3150,16 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly( // transferred. Again, though, we can't be sure that the object will use // free() to deallocate the memory, so we can't model it explicitly. StringRef FirstSlot = Msg->getSelector().getNameForSlot(0); - if (FirstSlot.endswith("NoCopy")) + if (FirstSlot.ends_with("NoCopy")) return true; // If the first selector starts with addPointer, insertPointer, // or replacePointer, assume we are dealing with NSPointerArray or similar. // This is similar to C++ containers (vector); we still might want to check // that the pointers get freed by following the container itself. - if (FirstSlot.startswith("addPointer") || - FirstSlot.startswith("insertPointer") || - FirstSlot.startswith("replacePointer") || + if (FirstSlot.starts_with("addPointer") || + FirstSlot.starts_with("insertPointer") || + FirstSlot.starts_with("replacePointer") || FirstSlot.equals("valueWithPointer")) { return true; } @@ -2992,7 +3199,7 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly( // White list the 'XXXNoCopy' CoreFoundation functions. // We specifically check these before - if (FName.endswith("NoCopy")) { + if (FName.ends_with("NoCopy")) { // Look for the deallocator argument. We know that the memory ownership // is not transferred only if the deallocator argument is // 'kCFAllocatorNull'. @@ -3024,7 +3231,7 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly( const Expr *ArgE = Call->getArgExpr(0)->IgnoreParenCasts(); if (const DeclRefExpr *ArgDRE = dyn_cast<DeclRefExpr>(ArgE)) if (const VarDecl *D = dyn_cast<VarDecl>(ArgDRE->getDecl())) - if (D->getCanonicalDecl()->getName().find("std") != StringRef::npos) + if (D->getCanonicalDecl()->getName().contains("std")) return true; } } @@ -3052,6 +3259,11 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly( return true; } + if (FName == "singleShotImpl" && + FD->getQualifiedNameAsString() == "QTimer::singleShotImpl") { + return true; + } + // Handle cases where we know a buffer's /address/ can escape. // Note that the above checks handle some special cases where we know that // even though the address escapes, it's still our responsibility to free the @@ -3100,11 +3312,7 @@ ProgramStateRef MallocChecker::checkPointerEscapeAux( return State; } - for (InvalidatedSymbols::const_iterator I = Escaped.begin(), - E = Escaped.end(); - I != E; ++I) { - SymbolRef sym = *I; - + for (SymbolRef sym : Escaped) { if (EscapingSymbol && EscapingSymbol != sym) continue; @@ -3224,7 +3432,7 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, allocation_state::getContainerObjRegion(statePrev, Sym); const auto *TypedRegion = cast<TypedValueRegion>(ObjRegion); QualType ObjTy = TypedRegion->getValueType(); - OS << "Inner buffer of '" << ObjTy.getAsString() << "' "; + OS << "Inner buffer of '" << ObjTy << "' "; if (N->getLocation().getKind() == ProgramPoint::PostImplicitCallKind) { OS << "deallocated by call to destructor"; @@ -3239,7 +3447,8 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, OS << OpCallE->getDirectCallee()->getDeclName(); } else if (const auto *CallE = dyn_cast<CallExpr>(S)) { auto &CEMgr = BRC.getStateManager().getCallEventManager(); - CallEventRef<> Call = CEMgr.getSimpleCall(CallE, state, CurrentLC); + CallEventRef<> Call = + CEMgr.getSimpleCall(CallE, state, CurrentLC, {nullptr, 0}); if (const auto *D = dyn_cast_or_null<NamedDecl>(Call->getDecl())) OS << D->getDeclName(); else @@ -3351,17 +3560,18 @@ void MallocChecker::printState(raw_ostream &Out, ProgramStateRef State, if (!RS.isEmpty()) { Out << Sep << "MallocChecker :" << NL; - for (RegionStateTy::iterator I = RS.begin(), E = RS.end(); I != E; ++I) { - const RefState *RefS = State->get<RegionState>(I.getKey()); + for (auto [Sym, Data] : RS) { + const RefState *RefS = State->get<RegionState>(Sym); AllocationFamily Family = RefS->getAllocationFamily(); - Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family); - if (!CheckKind.hasValue()) - CheckKind = getCheckIfTracked(Family, true); + std::optional<MallocChecker::CheckKind> CheckKind = + getCheckIfTracked(Family); + if (!CheckKind) + CheckKind = getCheckIfTracked(Family, true); - I.getKey()->dumpToStream(Out); + Sym->dumpToStream(Out); Out << " : "; - I.getData().dump(Out); - if (CheckKind.hasValue()) + Data.dump(Out); + if (CheckKind) Out << " (" << CheckNames[*CheckKind].getName() << ")"; Out << NL; } @@ -3395,6 +3605,9 @@ void ento::registerDynamicMemoryModeling(CheckerManager &mgr) { auto *checker = mgr.registerChecker<MallocChecker>(); checker->ShouldIncludeOwnershipAnnotatedFunctions = mgr.getAnalyzerOptions().getCheckerBooleanOption(checker, "Optimistic"); + checker->ShouldRegisterNoOwnershipChangeVisitor = + mgr.getAnalyzerOptions().getCheckerBooleanOption( + checker, "AddNoOwnershipChangeNotes"); } bool ento::shouldRegisterDynamicMemoryModeling(const CheckerManager &mgr) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp index e31630f63b5a..3c8b38973c6b 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp @@ -24,6 +24,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "llvm/ADT/APSInt.h" #include "llvm/ADT/SmallVector.h" +#include <optional> #include <utility> using namespace clang; @@ -32,12 +33,14 @@ using llvm::APSInt; namespace { struct MallocOverflowCheck { + const CallExpr *call; const BinaryOperator *mulop; const Expr *variable; APSInt maxVal; - MallocOverflowCheck(const BinaryOperator *m, const Expr *v, APSInt val) - : mulop(m), variable(v), maxVal(std::move(val)) {} + MallocOverflowCheck(const CallExpr *call, const BinaryOperator *m, + const Expr *v, APSInt val) + : call(call), mulop(m), variable(v), maxVal(std::move(val)) {} }; class MallocOverflowSecurityChecker : public Checker<check::ASTCodeBody> { @@ -46,8 +49,8 @@ public: BugReporter &BR) const; void CheckMallocArgument( - SmallVectorImpl<MallocOverflowCheck> &PossibleMallocOverflows, - const Expr *TheArgument, ASTContext &Context) const; + SmallVectorImpl<MallocOverflowCheck> &PossibleMallocOverflows, + const CallExpr *TheCall, ASTContext &Context) const; void OutputPossibleOverflows( SmallVectorImpl<MallocOverflowCheck> &PossibleMallocOverflows, @@ -62,16 +65,15 @@ static inline bool EvaluatesToZero(APSInt &Val, BinaryOperatorKind op) { } void MallocOverflowSecurityChecker::CheckMallocArgument( - SmallVectorImpl<MallocOverflowCheck> &PossibleMallocOverflows, - const Expr *TheArgument, - ASTContext &Context) const { + SmallVectorImpl<MallocOverflowCheck> &PossibleMallocOverflows, + const CallExpr *TheCall, ASTContext &Context) const { /* Look for a linear combination with a single variable, and at least one multiplication. Reject anything that applies to the variable: an explicit cast, conditional expression, an operation that could reduce the range of the result, or anything too complicated :-). */ - const Expr *e = TheArgument; + const Expr *e = TheCall->getArg(0); const BinaryOperator * mulop = nullptr; APSInt maxVal; @@ -101,8 +103,7 @@ void MallocOverflowSecurityChecker::CheckMallocArgument( e = rhs; } else return; - } - else if (isa<DeclRefExpr>(e) || isa<MemberExpr>(e)) + } else if (isa<DeclRefExpr, MemberExpr>(e)) break; else return; @@ -115,9 +116,8 @@ void MallocOverflowSecurityChecker::CheckMallocArgument( // the data so when the body of the function is completely available // we can check for comparisons. - // TODO: Could push this into the innermost scope where 'e' is - // defined, rather than the whole function. - PossibleMallocOverflows.push_back(MallocOverflowCheck(mulop, e, maxVal)); + PossibleMallocOverflows.push_back( + MallocOverflowCheck(TheCall, mulop, e, maxVal)); } namespace { @@ -153,17 +153,19 @@ private: return getDecl(CheckDR) == getDecl(DR) && Pred(Check); return false; }; - toScanFor.erase(std::remove_if(toScanFor.begin(), toScanFor.end(), P), - toScanFor.end()); + llvm::erase_if(toScanFor, P); } void CheckExpr(const Expr *E_p) { - auto PredTrue = [](const MallocOverflowCheck &) { return true; }; const Expr *E = E_p->IgnoreParenImpCasts(); + const auto PrecedesMalloc = [E, this](const MallocOverflowCheck &c) { + return Context.getSourceManager().isBeforeInTranslationUnit( + E->getExprLoc(), c.call->getExprLoc()); + }; if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(E)) - Erase<DeclRefExpr>(DR, PredTrue); + Erase<DeclRefExpr>(DR, PrecedesMalloc); else if (const auto *ME = dyn_cast<MemberExpr>(E)) { - Erase<MemberExpr>(ME, PredTrue); + Erase<MemberExpr>(ME, PrecedesMalloc); } } @@ -278,17 +280,13 @@ void MallocOverflowSecurityChecker::OutputPossibleOverflows( c.Visit(mgr.getAnalysisDeclContext(D)->getBody()); // Output warnings for all overflows that are left. - for (CheckOverflowOps::theVecType::iterator - i = PossibleMallocOverflows.begin(), - e = PossibleMallocOverflows.end(); - i != e; - ++i) { + for (const MallocOverflowCheck &Check : PossibleMallocOverflows) { BR.EmitBasicReport( D, this, "malloc() size overflow", categories::UnixAPI, "the computation of the size of the memory allocation may overflow", - PathDiagnosticLocation::createOperatorLoc(i->mulop, + PathDiagnosticLocation::createOperatorLoc(Check.mulop, BR.getSourceManager()), - i->mulop->getSourceRange()); + Check.mulop->getSourceRange()); } } @@ -307,26 +305,27 @@ void MallocOverflowSecurityChecker::checkASTCodeBody(const Decl *D, CFGBlock *block = *it; for (CFGBlock::iterator bi = block->begin(), be = block->end(); bi != be; ++bi) { - if (Optional<CFGStmt> CS = bi->getAs<CFGStmt>()) { - if (const CallExpr *TheCall = dyn_cast<CallExpr>(CS->getStmt())) { - // Get the callee. - const FunctionDecl *FD = TheCall->getDirectCallee(); - - if (!FD) - continue; - - // Get the name of the callee. If it's a builtin, strip off the prefix. - IdentifierInfo *FnInfo = FD->getIdentifier(); - if (!FnInfo) - continue; - - if (FnInfo->isStr ("malloc") || FnInfo->isStr ("_MALLOC")) { - if (TheCall->getNumArgs() == 1) - CheckMallocArgument(PossibleMallocOverflows, TheCall->getArg(0), - mgr.getASTContext()); + if (std::optional<CFGStmt> CS = bi->getAs<CFGStmt>()) { + if (const CallExpr *TheCall = dyn_cast<CallExpr>(CS->getStmt())) { + // Get the callee. + const FunctionDecl *FD = TheCall->getDirectCallee(); + + if (!FD) + continue; + + // Get the name of the callee. If it's a builtin, strip off the + // prefix. + IdentifierInfo *FnInfo = FD->getIdentifier(); + if (!FnInfo) + continue; + + if (FnInfo->isStr("malloc") || FnInfo->isStr("_MALLOC")) { + if (TheCall->getNumArgs() == 1) + CheckMallocArgument(PossibleMallocOverflows, TheCall, + mgr.getASTContext()); + } } } - } } } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp index 4b5206a102b8..9e81a6bd19fc 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp @@ -12,14 +12,15 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/StmtVisitor.h" #include "clang/AST/TypeLoc.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/iterator_range.h" #include "llvm/Support/raw_ostream.h" using namespace clang; @@ -182,22 +183,20 @@ public: AnalysisDeclContext *ADC = mgr.getAnalysisDeclContext(D); CastedAllocFinder Finder(&BR.getContext()); Finder.Visit(D->getBody()); - for (CastedAllocFinder::CallVec::iterator i = Finder.Calls.begin(), - e = Finder.Calls.end(); i != e; ++i) { - QualType CastedType = i->CastedExpr->getType(); + for (const auto &CallRec : Finder.Calls) { + QualType CastedType = CallRec.CastedExpr->getType(); if (!CastedType->isPointerType()) continue; QualType PointeeType = CastedType->getPointeeType(); if (PointeeType->isVoidType()) continue; - for (CallExpr::const_arg_iterator ai = i->AllocCall->arg_begin(), - ae = i->AllocCall->arg_end(); ai != ae; ++ai) { - if (!(*ai)->getType()->isIntegralOrUnscopedEnumerationType()) + for (const Expr *Arg : CallRec.AllocCall->arguments()) { + if (!Arg->getType()->isIntegralOrUnscopedEnumerationType()) continue; SizeofFinder SFinder; - SFinder.Visit(*ai); + SFinder.Visit(Arg); if (SFinder.Sizeofs.size() != 1) continue; @@ -212,34 +211,33 @@ public: continue; const TypeSourceInfo *TSI = nullptr; - if (i->CastedExprParent.is<const VarDecl *>()) { - TSI = - i->CastedExprParent.get<const VarDecl *>()->getTypeSourceInfo(); + if (CallRec.CastedExprParent.is<const VarDecl *>()) { + TSI = CallRec.CastedExprParent.get<const VarDecl *>() + ->getTypeSourceInfo(); } else { - TSI = i->ExplicitCastType; + TSI = CallRec.ExplicitCastType; } SmallString<64> buf; llvm::raw_svector_ostream OS(buf); OS << "Result of "; - const FunctionDecl *Callee = i->AllocCall->getDirectCallee(); + const FunctionDecl *Callee = CallRec.AllocCall->getDirectCallee(); if (Callee && Callee->getIdentifier()) OS << '\'' << Callee->getIdentifier()->getName() << '\''; else OS << "call"; - OS << " is converted to a pointer of type '" - << PointeeType.getAsString() << "', which is incompatible with " - << "sizeof operand type '" << SizeofType.getAsString() << "'"; + OS << " is converted to a pointer of type '" << PointeeType + << "', which is incompatible with " + << "sizeof operand type '" << SizeofType << "'"; SmallVector<SourceRange, 4> Ranges; - Ranges.push_back(i->AllocCall->getCallee()->getSourceRange()); + Ranges.push_back(CallRec.AllocCall->getCallee()->getSourceRange()); Ranges.push_back(SFinder.Sizeofs[0]->getSourceRange()); if (TSI) Ranges.push_back(TSI->getTypeLoc().getSourceRange()); - PathDiagnosticLocation L = - PathDiagnosticLocation::createBegin(i->AllocCall->getCallee(), - BR.getSourceManager(), ADC); + PathDiagnosticLocation L = PathDiagnosticLocation::createBegin( + CallRec.AllocCall->getCallee(), BR.getSourceManager(), ADC); BR.EmitBasicReport(D, this, "Allocator sizeof operand mismatch", categories::UnixAPI, OS.str(), L, Ranges); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp index 1960873599f7..82a622831817 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp @@ -30,22 +30,18 @@ namespace { class MismatchedIteratorChecker : public Checker<check::PreCall, check::PreStmt<BinaryOperator>> { - std::unique_ptr<BugType> MismatchedBugType; - - void verifyMatch(CheckerContext &C, const SVal &Iter, - const MemRegion *Cont) const; - void verifyMatch(CheckerContext &C, const SVal &Iter1, - const SVal &Iter2) const; - void reportBug(const StringRef &Message, const SVal &Val1, - const SVal &Val2, CheckerContext &C, - ExplodedNode *ErrNode) const; - void reportBug(const StringRef &Message, const SVal &Val, - const MemRegion *Reg, CheckerContext &C, + const BugType MismatchedBugType{this, "Iterator(s) mismatched", + "Misuse of STL APIs", + /*SuppressOnSink=*/true}; + + void verifyMatch(CheckerContext &C, SVal Iter, const MemRegion *Cont) const; + void verifyMatch(CheckerContext &C, SVal Iter1, SVal Iter2) const; + void reportBug(StringRef Message, SVal Val1, SVal Val2, CheckerContext &C, ExplodedNode *ErrNode) const; + void reportBug(StringRef Message, SVal Val, const MemRegion *Reg, + CheckerContext &C, ExplodedNode *ErrNode) const; public: - MismatchedIteratorChecker(); - void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPreStmt(const BinaryOperator *BO, CheckerContext &C) const; @@ -53,12 +49,6 @@ public: } // namespace -MismatchedIteratorChecker::MismatchedIteratorChecker() { - MismatchedBugType.reset( - new BugType(this, "Iterator(s) mismatched", "Misuse of STL APIs", - /*SuppressOnSink=*/true)); -} - void MismatchedIteratorChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { // Check for iterator mismatches @@ -176,8 +166,10 @@ void MismatchedIteratorChecker::checkPreCall(const CallEvent &Call, const auto *Param = Func->getParamDecl(J); const auto *ParamType = Param->getType()->getAs<SubstTemplateTypeParmType>(); - if (!ParamType || - ParamType->getReplacedParameter()->getDecl() != TPDecl) + if (!ParamType) + continue; + const TemplateTypeParmDecl *D = ParamType->getReplacedParameter(); + if (D != TPDecl) continue; if (LHS.isUndef()) { LHS = Call.getArgSVal(J); @@ -200,7 +192,7 @@ void MismatchedIteratorChecker::checkPreStmt(const BinaryOperator *BO, verifyMatch(C, LVal, RVal); } -void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, const SVal &Iter, +void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, SVal Iter, const MemRegion *Cont) const { // Verify match between a container and the container of an iterator Cont = Cont->getMostDerivedObjectRegion(); @@ -236,9 +228,8 @@ void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, const SVal &Iter, } } -void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, - const SVal &Iter1, - const SVal &Iter2) const { +void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, SVal Iter1, + SVal Iter2) const { // Verify match between the containers of two iterators auto State = C.getState(); const auto *Pos1 = getIteratorPosition(State, Iter1); @@ -275,23 +266,21 @@ void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, } } -void MismatchedIteratorChecker::reportBug(const StringRef &Message, - const SVal &Val1, - const SVal &Val2, - CheckerContext &C, +void MismatchedIteratorChecker::reportBug(StringRef Message, SVal Val1, + SVal Val2, CheckerContext &C, ExplodedNode *ErrNode) const { - auto R = std::make_unique<PathSensitiveBugReport>(*MismatchedBugType, Message, + auto R = std::make_unique<PathSensitiveBugReport>(MismatchedBugType, Message, ErrNode); R->markInteresting(Val1); R->markInteresting(Val2); C.emitReport(std::move(R)); } -void MismatchedIteratorChecker::reportBug(const StringRef &Message, - const SVal &Val, const MemRegion *Reg, +void MismatchedIteratorChecker::reportBug(StringRef Message, SVal Val, + const MemRegion *Reg, CheckerContext &C, ExplodedNode *ErrNode) const { - auto R = std::make_unique<PathSensitiveBugReport>(*MismatchedBugType, Message, + auto R = std::make_unique<PathSensitiveBugReport>(MismatchedBugType, Message, ErrNode); R->markInteresting(Val); R->markInteresting(Reg); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp index 5d63d6efd234..2e31c16e457c 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp @@ -15,14 +15,15 @@ #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/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" using namespace clang; using namespace ento; -using llvm::APSInt; namespace { class MmapWriteExecChecker : public Checker<check::PreCall> { @@ -31,9 +32,11 @@ class MmapWriteExecChecker : public Checker<check::PreCall> { static int ProtWrite; static int ProtExec; static int ProtRead; - mutable std::unique_ptr<BugType> BT; + const BugType BT{this, "W^X check fails, Write Exec prot flags set", + "Security"}; + public: - MmapWriteExecChecker() : MmapFn("mmap", 6), MprotectFn("mprotect", 3) {} + MmapWriteExecChecker() : MmapFn({"mmap"}, 6), MprotectFn({"mprotect"}, 3) {} void checkPreCall(const CallEvent &Call, CheckerContext &C) const; int ProtExecOv; int ProtReadOv; @@ -46,9 +49,11 @@ int MmapWriteExecChecker::ProtRead = 0x01; void MmapWriteExecChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { - if (Call.isCalled(MmapFn) || Call.isCalled(MprotectFn)) { + if (matchesAny(Call, MmapFn, MprotectFn)) { SVal ProtVal = Call.getArgSVal(2); - Optional<nonloc::ConcreteInt> ProtLoc = ProtVal.getAs<nonloc::ConcreteInt>(); + auto ProtLoc = ProtVal.getAs<nonloc::ConcreteInt>(); + if (!ProtLoc) + return; int64_t Prot = ProtLoc->getValue().getSExtValue(); if (ProtExecOv != ProtExec) ProtExec = ProtExecOv; @@ -60,17 +65,16 @@ void MmapWriteExecChecker::checkPreCall(const CallEvent &Call, return; if ((Prot & (ProtWrite | ProtExec)) == (ProtWrite | ProtExec)) { - if (!BT) - BT.reset(new BugType(this, "W^X check fails, Write Exec prot flags set", "Security")); - ExplodedNode *N = C.generateNonFatalErrorNode(); if (!N) return; auto Report = std::make_unique<PathSensitiveBugReport>( - *BT, "Both PROT_WRITE and PROT_EXEC flags are set. This can " - "lead to exploitable memory regions, which could be overwritten " - "with malicious code", N); + BT, + "Both PROT_WRITE and PROT_EXEC flags are set. This can " + "lead to exploitable memory regions, which could be overwritten " + "with malicious code", + N); Report->addRange(Call.getArgSourceRange(2)); C.emitReport(std::move(Report)); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp index cbe938982000..5240352a9bd2 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp @@ -49,7 +49,6 @@ class MoveChecker : public Checker<check::PreCall, check::PostCall, check::DeadSymbols, check::RegionChanges> { public: - void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; void checkPreCall(const CallEvent &MC, CheckerContext &C) const; void checkPostCall(const CallEvent &MC, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; @@ -185,7 +184,7 @@ private: bool Found; }; - AggressivenessKind Aggressiveness; + AggressivenessKind Aggressiveness = AK_KnownsAndLocals; public: void setAggressiveness(StringRef Str, CheckerManager &Mgr) { @@ -214,8 +213,9 @@ private: // Returns the exploded node against which the report was emitted. // The caller *must* add any further transitions against this node. - ExplodedNode *reportBug(const MemRegion *Region, const CXXRecordDecl *RD, - CheckerContext &C, MisuseKind MK) const; + // Returns nullptr and does not report if such node already exists. + ExplodedNode *tryToReportBug(const MemRegion *Region, const CXXRecordDecl *RD, + CheckerContext &C, MisuseKind MK) const; bool isInMoveSafeContext(const LocationContext *LC) const; bool isStateResetMethod(const CXXMethodDecl *MethodDec) const; @@ -310,7 +310,7 @@ MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N, // If it's not a dereference, we don't care if it was reset to null // or that it is even a smart pointer. - LLVM_FALLTHROUGH; + [[fallthrough]]; case SK_NonStd: case SK_Safe: OS << "Object"; @@ -378,19 +378,20 @@ void MoveChecker::modelUse(ProgramStateRef State, const MemRegion *Region, return; } - ExplodedNode *N = reportBug(Region, RD, C, MK); + ExplodedNode *N = tryToReportBug(Region, RD, C, MK); // If the program has already crashed on this path, don't bother. - if (N->isSink()) + if (!N || N->isSink()) return; State = State->set<TrackedRegionMap>(Region, RegionState::getReported()); C.addTransition(State, N); } -ExplodedNode *MoveChecker::reportBug(const MemRegion *Region, - const CXXRecordDecl *RD, CheckerContext &C, - MisuseKind MK) const { +ExplodedNode *MoveChecker::tryToReportBug(const MemRegion *Region, + const CXXRecordDecl *RD, + CheckerContext &C, + MisuseKind MK) const { if (ExplodedNode *N = misuseCausesCrash(MK) ? C.generateErrorNode() : C.generateNonFatalErrorNode()) { // Uniqueing report to the same object. @@ -554,7 +555,8 @@ MoveChecker::classifyObject(const MemRegion *MR, // as not-"STL" types, because that's how the checker treats them. MR = unwrapRValueReferenceIndirection(MR); bool IsLocal = - MR && isa<VarRegion>(MR) && isa<StackSpaceRegion>(MR->getMemorySpace()); + isa_and_nonnull<VarRegion, CXXLifetimeExtendedObjectRegion>(MR) && + isa<StackSpaceRegion>(MR->getMemorySpace()); if (!RD || !RD->getDeclContext()->isStdNamespace()) return { IsLocal, SK_NonStd }; @@ -588,7 +590,7 @@ void MoveChecker::explainObject(llvm::raw_ostream &OS, const MemRegion *MR, break; // We only care about the type if it's a dereference. - LLVM_FALLTHROUGH; + [[fallthrough]]; case SK_Unsafe: OS << " of type '" << RD->getQualifiedNameAsString() << "'"; break; @@ -619,10 +621,6 @@ void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { if (!IC) return; - // Calling a destructor on a moved object is fine. - if (isa<CXXDestructorCall>(IC)) - return; - const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); if (!ThisRegion) return; @@ -632,6 +630,10 @@ void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { if (!MethodDecl) return; + // Calling a destructor on a moved object is fine. + if (isa<CXXDestructorDecl>(MethodDecl)) + return; + // We want to investigate the whole object, not only sub-object of a parent // class in which the encountered method defined. ThisRegion = ThisRegion->getMostDerivedObjectRegion(); @@ -712,12 +714,9 @@ ProgramStateRef MoveChecker::checkRegionChanges( // directly, but not all of them end up being invalidated. // But when they do, they appear in the InvalidatedRegions array as well. for (const auto *Region : RequestedRegions) { - if (ThisRegion != Region) { - if (llvm::find(InvalidatedRegions, Region) != - std::end(InvalidatedRegions)) { - State = removeFromState(State, Region); - } - } + if (ThisRegion != Region && + llvm::is_contained(InvalidatedRegions, Region)) + State = removeFromState(State, Region); } } else { // For invalidations that aren't caused by calls, assume nothing. In diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp index be17e401fb53..0648084a7d39 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp @@ -31,7 +31,8 @@ using namespace ento; namespace { class NSAutoreleasePoolChecker : public Checker<check::PreObjCMessage> { - mutable std::unique_ptr<BugType> BT; + const BugType BT{this, "Use -drain instead of -release", + "API Upgrade (Apple)"}; mutable Selector releaseS; public: @@ -57,10 +58,6 @@ void NSAutoreleasePoolChecker::checkPreObjCMessage(const ObjCMethodCall &msg, if (msg.getSelector() != releaseS) return; - if (!BT) - BT.reset(new BugType(this, "Use -drain instead of -release", - "API Upgrade (Apple)")); - ExplodedNode *N = C.generateNonFatalErrorNode(); if (!N) { assert(0); @@ -68,7 +65,7 @@ void NSAutoreleasePoolChecker::checkPreObjCMessage(const ObjCMethodCall &msg, } auto Report = std::make_unique<PathSensitiveBugReport>( - *BT, + BT, "Use -drain instead of -release when using NSAutoreleasePool and " "garbage collection", N); @@ -80,7 +77,7 @@ void ento::registerNSAutoreleasePoolChecker(CheckerManager &mgr) { mgr.registerChecker<NSAutoreleasePoolChecker>(); } -bool ento::shouldRegisterNSAutoreleasePoolChecker(const CheckerManager &mgr) { +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 90c5583d8969..54870bcb4bb2 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// // -// This file defines a CheckNSError, a flow-insenstive check +// This file defines a CheckNSError, a flow-insensitive check // that determines if an Objective-C class interface correctly returns // a non-void return type. // @@ -24,6 +24,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/raw_ostream.h" +#include <optional> using namespace clang; using namespace ento; @@ -38,10 +39,10 @@ static bool IsCFError(QualType T, IdentifierInfo *II); namespace { class NSErrorMethodChecker : public Checker< check::ASTDecl<ObjCMethodDecl> > { - mutable IdentifierInfo *II; + mutable IdentifierInfo *II = nullptr; public: - NSErrorMethodChecker() : II(nullptr) {} + NSErrorMethodChecker() = default; void checkASTDecl(const ObjCMethodDecl *D, AnalysisManager &mgr, BugReporter &BR) const; @@ -118,7 +119,7 @@ void CFErrorFunctionChecker::checkASTDecl(const FunctionDecl *D, II = &D->getASTContext().Idents.get("CFErrorRef"); bool hasCFError = false; - for (auto I : D->parameters()) { + for (auto *I : D->parameters()) { if (IsCFError(I->getType(), II)) { hasCFError = true; break; @@ -166,7 +167,7 @@ class NSOrCFErrorDerefChecker mutable std::unique_ptr<NSErrorDerefBug> NSBT; mutable std::unique_ptr<CFErrorDerefBug> CFBT; public: - DefaultBool ShouldCheckNSError, ShouldCheckCFError; + bool ShouldCheckNSError = false, ShouldCheckCFError = false; CheckerNameRef NSErrorName, CFErrorName; NSOrCFErrorDerefChecker() : NSErrorII(nullptr), CFErrorII(nullptr) {} @@ -197,7 +198,7 @@ static void setFlag(ProgramStateRef state, SVal val, CheckerContext &C) { static QualType parameterTypeFromSVal(SVal val, CheckerContext &C) { const StackFrameContext * SFC = C.getStackFrame(); - if (Optional<loc::MemRegionVal> X = val.getAs<loc::MemRegionVal>()) { + if (std::optional<loc::MemRegionVal> X = val.getAs<loc::MemRegionVal>()) { const MemRegion* R = X->getRegion(); if (const VarRegion *VR = R->getAs<VarRegion>()) if (const StackArgumentsSpaceRegion * @@ -214,7 +215,7 @@ void NSOrCFErrorDerefChecker::checkLocation(SVal loc, bool isLoad, CheckerContext &C) const { if (!isLoad) return; - if (loc.isUndef() || !loc.getAs<Loc>()) + if (loc.isUndef() || !isa<Loc>(loc)) return; ASTContext &Ctx = C.getASTContext(); @@ -266,7 +267,7 @@ void NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event) const { SmallString<128> Buf; llvm::raw_svector_ostream os(Buf); - os << "Potential null dereference. According to coding standards "; + os << "Potential null dereference. According to coding standards "; os << (isNSError ? "in 'Creating and Returning NSError Objects' the parameter" : "documented in CoreFoundation/CFError.h the parameter"); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp index af208e867318..17c3cb4e9e04 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp @@ -44,9 +44,11 @@ void NoReturnFunctionChecker::checkPostCall(const CallEvent &CE, if (const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(CE.getDecl())) BuildSinks = FD->hasAttr<AnalyzerNoReturnAttr>() || FD->isNoReturn(); - const Expr *Callee = CE.getOriginExpr(); - if (!BuildSinks && Callee) - BuildSinks = getFunctionExtInfo(Callee->getType()).getNoReturn(); + if (const CallExpr *CExpr = dyn_cast_or_null<CallExpr>(CE.getOriginExpr()); + CExpr && !BuildSinks) { + if (const Expr *C = CExpr->getCallee()) + BuildSinks = getFunctionExtInfo(C->getType()).getNoReturn(); + } if (!BuildSinks && CE.isGlobalCFunction()) { if (const IdentifierInfo *II = CE.getCalleeIdentifier()) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp index 534b5d68434f..a9002ee7c966 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp @@ -18,6 +18,7 @@ #include "clang/Analysis/AnyCall.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" @@ -31,8 +32,9 @@ namespace { class NonNullParamChecker : public Checker<check::PreCall, check::BeginFunction, EventDispatcher<ImplicitNullDerefEvent>> { - mutable std::unique_ptr<BugType> BTAttrNonNull; - mutable std::unique_ptr<BugType> BTNullRefArg; + const BugType BTAttrNonNull{ + this, "Argument with 'nonnull' attribute passed null", "API"}; + const BugType BTNullRefArg{this, "Dereference of null pointer"}; public: void checkPreCall(const CallEvent &Call, CheckerContext &C) const; @@ -136,10 +138,10 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, if (!DV) continue; - assert(!HasRefTypeParam || DV->getAs<Loc>()); + assert(!HasRefTypeParam || isa<Loc>(*DV)); // Process the case when the argument is not a location. - if (ExpectedToBeNonNull && !DV->getAs<Loc>()) { + if (ExpectedToBeNonNull && !isa<Loc>(*DV)) { // If the argument is a union type, we want to handle a potential // transparent_union GCC extension. if (!ArgE) @@ -161,7 +163,7 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, assert(++CSV->begin() == CSV->end()); // FIXME: Handle (some_union){ some_other_union_val }, which turns into // a LazyCompoundVal inside a CompoundVal. - if (!V.getAs<Loc>()) + if (!isa<Loc>(V)) continue; // Retrieve the corresponding expression. @@ -278,13 +280,6 @@ std::unique_ptr<PathSensitiveBugReport> NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode, const Expr *ArgE, unsigned IdxOfArg) const { - // Lazily allocate the BugType object if it hasn't already been - // created. Ownership is transferred to the BugReporter object once - // the BugReport is passed to 'EmitWarning'. - if (!BTAttrNonNull) - BTAttrNonNull.reset(new BugType( - this, "Argument with 'nonnull' attribute passed null", "API")); - llvm::SmallString<256> SBuf; llvm::raw_svector_ostream OS(SBuf); OS << "Null pointer passed to " @@ -292,7 +287,7 @@ NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode, << " parameter expecting 'nonnull'"; auto R = - std::make_unique<PathSensitiveBugReport>(*BTAttrNonNull, SBuf, ErrorNode); + std::make_unique<PathSensitiveBugReport>(BTAttrNonNull, SBuf, ErrorNode); if (ArgE) bugreporter::trackExpressionValue(ErrorNode, ArgE, *R); @@ -302,11 +297,8 @@ NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode, std::unique_ptr<PathSensitiveBugReport> NonNullParamChecker::genReportReferenceToNullPointer( const ExplodedNode *ErrorNode, const Expr *ArgE) const { - if (!BTNullRefArg) - BTNullRefArg.reset(new BuiltinBug(this, "Dereference of null pointer")); - auto R = std::make_unique<PathSensitiveBugReport>( - *BTNullRefArg, "Forming reference to null pointer", ErrorNode); + BTNullRefArg, "Forming reference to null pointer", ErrorNode); if (ArgE) { const Expr *ArgEDeref = bugreporter::getDerefExpr(ArgE); if (!ArgEDeref) @@ -314,7 +306,6 @@ NonNullParamChecker::genReportReferenceToNullPointer( bugreporter::trackExpressionValue(ErrorNode, ArgEDeref, *R); } return R; - } void ento::registerNonNullParamChecker(CheckerManager &mgr) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp index c5437b16c688..72c6a869d225 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp @@ -26,6 +26,7 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" +#include <optional> using namespace clang; using namespace ento; @@ -77,7 +78,8 @@ void NonnullGlobalConstantsChecker::checkLocation(SVal location, bool isLoad, if (isGlobalConstString(location)) { SVal V = State->getSVal(location.castAs<Loc>()); - Optional<DefinedOrUnknownSVal> Constr = V.getAs<DefinedOrUnknownSVal>(); + std::optional<DefinedOrUnknownSVal> Constr = + V.getAs<DefinedOrUnknownSVal>(); if (Constr) { @@ -91,7 +93,7 @@ void NonnullGlobalConstantsChecker::checkLocation(SVal location, bool isLoad, /// \param V loaded lvalue. /// \return whether @c val is a string-like const global. bool NonnullGlobalConstantsChecker::isGlobalConstString(SVal V) const { - Optional<loc::MemRegionVal> RegionVal = V.getAs<loc::MemRegionVal>(); + std::optional<loc::MemRegionVal> RegionVal = V.getAs<loc::MemRegionVal>(); if (!RegionVal) return false; auto *Region = dyn_cast<VarRegion>(RegionVal->getAsRegion()); @@ -109,17 +111,20 @@ bool NonnullGlobalConstantsChecker::isGlobalConstString(SVal V) const { // Look through the typedefs. while (const Type *T = Ty.getTypePtr()) { - if (const auto *TT = dyn_cast<TypedefType>(T)) { + if (const auto *AT = dyn_cast<AttributedType>(T)) { + if (AT->getAttrKind() == attr::TypeNonNull) + return true; + Ty = AT->getModifiedType(); + } else if (const auto *ET = dyn_cast<ElaboratedType>(T)) { + const auto *TT = dyn_cast<TypedefType>(ET->getNamedType()); + if (!TT) + return false; Ty = TT->getDecl()->getUnderlyingType(); // It is sufficient for any intermediate typedef // to be classified const. HasConst = HasConst || Ty.isConstQualified(); if (isNonnullType(Ty) && HasConst) return true; - } else if (const auto *AT = dyn_cast<AttributedType>(T)) { - if (AT->getAttrKind() == attr::TypeNonNull) - return true; - Ty = AT->getModifiedType(); } else { return false; } @@ -136,7 +141,7 @@ bool NonnullGlobalConstantsChecker::isNonnullType(QualType Ty) const { if (auto *T = dyn_cast<ObjCObjectPointerType>(Ty)) { return T->getInterfaceDecl() && T->getInterfaceDecl()->getIdentifier() == NSStringII; - } else if (auto *T = dyn_cast<TypedefType>(Ty)) { + } else if (auto *T = Ty->getAs<TypedefType>()) { IdentifierInfo* II = T->getDecl()->getIdentifier(); return II == CFStringRefII || II == CFBooleanRefII || II == CFNullRefII; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp index fe8f7e7bf69e..06f1ad00eaf2 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -26,13 +26,15 @@ #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/Analysis/AnyCall.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/CheckerHelpers.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Path.h" @@ -80,8 +82,9 @@ enum class ErrorKind : int { class NullabilityChecker : public Checker<check::Bind, check::PreCall, check::PreStmt<ReturnStmt>, check::PostCall, check::PostStmt<ExplicitCastExpr>, - check::PostObjCMessage, check::DeadSymbols, - check::Location, check::Event<ImplicitNullDerefEvent>> { + check::PostObjCMessage, check::DeadSymbols, eval::Assume, + check::Location, check::Event<ImplicitNullDerefEvent>, + check::BeginFunction> { public: // If true, the checker will not diagnose nullabilility issues for calls @@ -90,7 +93,7 @@ public: // find warnings about nullability annotations that they have explicitly // added themselves higher priority to fix than warnings on calls to system // libraries. - DefaultBool NoDiagnoseCallsToSystemHeaders; + bool NoDiagnoseCallsToSystemHeaders = false; void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; void checkPostStmt(const ExplicitCastExpr *CE, CheckerContext &C) const; @@ -102,6 +105,9 @@ public: void checkEvent(ImplicitNullDerefEvent Event) const; void checkLocation(SVal Location, bool IsLoad, const Stmt *S, CheckerContext &C) const; + void checkBeginFunction(CheckerContext &Ctx) const; + ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond, + bool Assumption) const; void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const override; @@ -115,7 +121,7 @@ public: CK_NumCheckKinds }; - DefaultBool ChecksEnabled[CK_NumCheckKinds]; + bool ChecksEnabled[CK_NumCheckKinds] = {false}; CheckerNameRef CheckNames[CK_NumCheckKinds]; mutable std::unique_ptr<BugType> BTs[CK_NumCheckKinds]; @@ -129,8 +135,8 @@ public: // 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 - // stroed in the NullabilityMap. This is an optimization. - DefaultBool NeedTracking; + // stored in the NullabilityMap. This is an optimization. + bool NeedTracking = false; private: class NullabilityBugVisitor : public BugReporterVisitor { @@ -230,10 +236,41 @@ bool operator==(NullabilityState Lhs, NullabilityState Rhs) { Lhs.getNullabilitySource() == Rhs.getNullabilitySource(); } +// For the purpose of tracking historical property accesses, the key for lookup +// is an object pointer (could be an instance or a class) paired with the unique +// identifier for the property being invoked on that object. +using ObjectPropPair = std::pair<const MemRegion *, const IdentifierInfo *>; + +// Metadata associated with the return value from a recorded property access. +struct ConstrainedPropertyVal { + // This will reference the conjured return SVal for some call + // of the form [object property] + DefinedOrUnknownSVal Value; + + // If the SVal has been determined to be nonnull, that is recorded here + bool isConstrainedNonnull; + + ConstrainedPropertyVal(DefinedOrUnknownSVal SV) + : Value(SV), isConstrainedNonnull(false) {} + + void Profile(llvm::FoldingSetNodeID &ID) const { + Value.Profile(ID); + ID.AddInteger(isConstrainedNonnull ? 1 : 0); + } +}; + +bool operator==(const ConstrainedPropertyVal &Lhs, + const ConstrainedPropertyVal &Rhs) { + return Lhs.Value == Rhs.Value && + Lhs.isConstrainedNonnull == Rhs.isConstrainedNonnull; +} + } // end anonymous namespace REGISTER_MAP_WITH_PROGRAMSTATE(NullabilityMap, const MemRegion *, NullabilityState) +REGISTER_MAP_WITH_PROGRAMSTATE(PropertyAccessesMap, ObjectPropPair, + ConstrainedPropertyVal) // We say "the nullability type invariant is violated" when a location with a // non-null type contains NULL or a function with a non-null return type returns @@ -273,6 +310,10 @@ static NullConstraint getNullConstraint(DefinedOrUnknownSVal Val, return NullConstraint::Unknown; } +static bool isValidPointerType(QualType T) { + return T->isAnyPointerType() || T->isBlockPointerType(); +} + const SymbolicRegion * NullabilityChecker::getTrackRegion(SVal Val, bool CheckSuperRegion) const { if (!NeedTracking) @@ -285,8 +326,11 @@ NullabilityChecker::getTrackRegion(SVal Val, bool CheckSuperRegion) const { const MemRegion *Region = RegionSVal->getRegion(); if (CheckSuperRegion) { - if (auto FieldReg = Region->getAs<FieldRegion>()) + if (const SubRegion *FieldReg = Region->getAs<FieldRegion>()) { + if (const auto *ER = dyn_cast<ElementRegion>(FieldReg->getSuperRegion())) + FieldReg = ER; return dyn_cast<SymbolicRegion>(FieldReg->getSuperRegion()); + } if (auto ElementReg = Region->getAs<ElementRegion>()) return dyn_cast<SymbolicRegion>(ElementReg->getSuperRegion()); } @@ -455,15 +499,24 @@ void NullabilityChecker::checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const { ProgramStateRef State = C.getState(); NullabilityMapTy Nullabilities = State->get<NullabilityMap>(); - for (NullabilityMapTy::iterator I = Nullabilities.begin(), - E = Nullabilities.end(); - I != E; ++I) { - const auto *Region = I->first->getAs<SymbolicRegion>(); + for (const MemRegion *Reg : llvm::make_first_range(Nullabilities)) { + const auto *Region = Reg->getAs<SymbolicRegion>(); assert(Region && "Non-symbolic region is tracked."); if (SR.isDead(Region->getSymbol())) { - State = State->remove<NullabilityMap>(I->first); + State = State->remove<NullabilityMap>(Reg); } } + + // When an object goes out of scope, we can free the history associated + // with any property accesses on that object + PropertyAccessesMapTy PropertyAccesses = State->get<PropertyAccessesMap>(); + for (ObjectPropPair PropKey : llvm::make_first_range(PropertyAccesses)) { + const MemRegion *ReceiverRegion = PropKey.first; + if (!SR.isLiveRegion(ReceiverRegion)) { + State = State->remove<PropertyAccessesMap>(PropKey); + } + } + // When one of the nonnull arguments are constrained to be null, nullability // preconditions are violated. It is not enough to check this only when we // actually report an error, because at that time interesting symbols might be @@ -510,6 +563,37 @@ void NullabilityChecker::checkEvent(ImplicitNullDerefEvent Event) const { } } +void NullabilityChecker::checkBeginFunction(CheckerContext &C) const { + if (!C.inTopFrame()) + return; + + const LocationContext *LCtx = C.getLocationContext(); + auto AbstractCall = AnyCall::forDecl(LCtx->getDecl()); + if (!AbstractCall || AbstractCall->parameters().empty()) + return; + + ProgramStateRef State = C.getState(); + for (const ParmVarDecl *Param : AbstractCall->parameters()) { + if (!isValidPointerType(Param->getType())) + continue; + + Nullability RequiredNullability = + getNullabilityAnnotation(Param->getType()); + if (RequiredNullability != Nullability::Nullable) + continue; + + const VarRegion *ParamRegion = State->getRegion(Param, LCtx); + const MemRegion *ParamPointeeRegion = + State->getSVal(ParamRegion).getAsRegion(); + if (!ParamPointeeRegion) + continue; + + State = State->set<NullabilityMap>(ParamPointeeRegion, + NullabilityState(RequiredNullability)); + } + C.addTransition(State); +} + // 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. @@ -572,7 +656,7 @@ void NullabilityChecker::checkPreStmt(const ReturnStmt *S, if (!RetExpr) return; - if (!RetExpr->getType()->isAnyPointerType()) + if (!isValidPointerType(RetExpr->getType())) return; ProgramStateRef State = C.getState(); @@ -705,7 +789,7 @@ void NullabilityChecker::checkPreCall(const CallEvent &Call, if (!ArgSVal) continue; - if (!Param->getType()->isAnyPointerType() && + if (!isValidPointerType(Param->getType()) && !Param->getType()->isReferenceType()) continue; @@ -714,7 +798,7 @@ void NullabilityChecker::checkPreCall(const CallEvent &Call, Nullability RequiredNullability = getNullabilityAnnotation(Param->getType()); Nullability ArgExprTypeLevelNullability = - getNullabilityAnnotation(ArgExpr->getType()); + getNullabilityAnnotation(lookThroughImplicitCasts(ArgExpr)->getType()); unsigned ParamIdx = Param->getFunctionScopeIndex() + 1; @@ -792,7 +876,7 @@ void NullabilityChecker::checkPostCall(const CallEvent &Call, if (!FuncType) return; QualType ReturnType = FuncType->getReturnType(); - if (!ReturnType->isAnyPointerType()) + if (!isValidPointerType(ReturnType)) return; ProgramStateRef State = C.getState(); if (State->get<InvariantViolated>()) @@ -806,7 +890,7 @@ void NullabilityChecker::checkPostCall(const CallEvent &Call, // of CG calls. const SourceManager &SM = C.getSourceManager(); StringRef FilePath = SM.getFilename(SM.getSpellingLoc(Decl->getBeginLoc())); - if (llvm::sys::path::filename(FilePath).startswith("CG")) { + if (llvm::sys::path::filename(FilePath).starts_with("CG")) { State = State->set<NullabilityMap>(Region, Nullability::Contradicted); C.addTransition(State); return; @@ -815,6 +899,14 @@ void NullabilityChecker::checkPostCall(const CallEvent &Call, const NullabilityState *TrackedNullability = State->get<NullabilityMap>(Region); + // ObjCMessageExpr gets the actual type through + // Sema::getMessageSendResultType, instead of using the return type of + // MethodDecl directly. The final type is generated by considering the + // nullability of receiver and MethodDecl together. Thus, The type of + // ObjCMessageExpr is prefer. + if (const Expr *E = Call.getOriginExpr()) + ReturnType = E->getType(); + if (!TrackedNullability && getNullabilityAnnotation(ReturnType) == Nullability::Nullable) { State = State->set<NullabilityMap>(Region, Nullability::Nullable); @@ -851,6 +943,30 @@ static Nullability getReceiverNullability(const ObjCMethodCall &M, return Nullability::Unspecified; } +// The return value of a property access is typically a temporary value which +// will not be tracked in a persistent manner by the analyzer. We use +// evalAssume() in order to immediately record constraints on those temporaries +// at the time they are imposed (e.g. by a nil-check conditional). +ProgramStateRef NullabilityChecker::evalAssume(ProgramStateRef State, SVal Cond, + bool Assumption) const { + PropertyAccessesMapTy PropertyAccesses = State->get<PropertyAccessesMap>(); + for (auto [PropKey, PropVal] : PropertyAccesses) { + if (!PropVal.isConstrainedNonnull) { + ConditionTruthVal IsNonNull = State->isNonNull(PropVal.Value); + if (IsNonNull.isConstrainedTrue()) { + ConstrainedPropertyVal Replacement = PropVal; + Replacement.isConstrainedNonnull = true; + State = State->set<PropertyAccessesMap>(PropKey, Replacement); + } else if (IsNonNull.isConstrainedFalse()) { + // Space optimization: no point in tracking constrained-null cases + State = State->remove<PropertyAccessesMap>(PropKey); + } + } + } + + return State; +} + /// Calculate the nullability of the result of a message expr based on the /// nullability of the receiver, the nullability of the return value, and the /// constraints. @@ -860,7 +976,7 @@ void NullabilityChecker::checkPostObjCMessage(const ObjCMethodCall &M, if (!Decl) return; QualType RetType = Decl->getReturnType(); - if (!RetType->isAnyPointerType()) + if (!isValidPointerType(RetType)) return; ProgramStateRef State = C.getState(); @@ -876,7 +992,7 @@ void NullabilityChecker::checkPostObjCMessage(const ObjCMethodCall &M, // In order to reduce the noise in the diagnostics generated by this checker, // some framework and programming style based heuristics are used. These // heuristics are for Cocoa APIs which have NS prefix. - if (Name.startswith("NS")) { + if (Name.starts_with("NS")) { // Developers rely on dynamic invariants such as an item should be available // in a collection, or a collection is not empty often. Those invariants can // not be inferred by any static analysis tool. To not to bother the users @@ -907,7 +1023,7 @@ void NullabilityChecker::checkPostObjCMessage(const ObjCMethodCall &M, // this class of methods reduced the emitted diagnostics by about 30% on // some projects (and all of that was false positives). if (Name.contains("String")) { - for (auto Param : M.parameters()) { + for (auto *Param : M.parameters()) { if (Param->getName() == "encoding") { State = State->set<NullabilityMap>(ReturnRegion, Nullability::Contradicted); @@ -945,14 +1061,55 @@ void NullabilityChecker::checkPostObjCMessage(const ObjCMethodCall &M, } // No tracked information. Use static type information for return value. - Nullability RetNullability = getNullabilityAnnotation(RetType); + Nullability RetNullability = getNullabilityAnnotation(Message->getType()); - // Properties might be computed. For this reason the static analyzer creates a - // new symbol each time an unknown property is read. To avoid false pozitives - // do not treat unknown properties as nullable, even when they explicitly - // marked nullable. - if (M.getMessageKind() == OCM_PropertyAccess && !C.wasInlined) - RetNullability = Nullability::Nonnull; + // Properties might be computed, which means the property value could + // theoretically change between calls even in commonly-observed cases like + // this: + // + // if (foo.prop) { // ok, it's nonnull here... + // [bar doStuffWithNonnullVal:foo.prop]; // ...but what about + // here? + // } + // + // If the property is nullable-annotated, a naive analysis would lead to many + // false positives despite the presence of probably-correct nil-checks. To + // reduce the false positive rate, we maintain a history of the most recently + // observed property value. For each property access, if the prior value has + // been constrained to be not nil then we will conservatively assume that the + // next access can be inferred as nonnull. + if (RetNullability != Nullability::Nonnull && + M.getMessageKind() == OCM_PropertyAccess && !C.wasInlined) { + bool LookupResolved = false; + if (const MemRegion *ReceiverRegion = getTrackRegion(M.getReceiverSVal())) { + if (IdentifierInfo *Ident = M.getSelector().getIdentifierInfoForSlot(0)) { + LookupResolved = true; + ObjectPropPair Key = std::make_pair(ReceiverRegion, Ident); + const ConstrainedPropertyVal *PrevPropVal = + State->get<PropertyAccessesMap>(Key); + if (PrevPropVal && PrevPropVal->isConstrainedNonnull) { + RetNullability = Nullability::Nonnull; + } else { + // If a previous property access was constrained as nonnull, we hold + // on to that constraint (effectively inferring that all subsequent + // accesses on that code path can be inferred as nonnull). If the + // previous property access was *not* constrained as nonnull, then + // let's throw it away in favor of keeping the SVal associated with + // this more recent access. + if (auto ReturnSVal = + M.getReturnValue().getAs<DefinedOrUnknownSVal>()) { + State = State->set<PropertyAccessesMap>( + Key, ConstrainedPropertyVal(*ReturnSVal)); + } + } + } + } + + if (!LookupResolved) { + // Fallback: err on the side of suppressing the false positive. + RetNullability = Nullability::Nonnull; + } + } Nullability ComputedNullab = getMostNullable(RetNullability, SelfNullability); if (ComputedNullab == Nullability::Nullable) { @@ -973,9 +1130,9 @@ void NullabilityChecker::checkPostStmt(const ExplicitCastExpr *CE, CheckerContext &C) const { QualType OriginType = CE->getSubExpr()->getType(); QualType DestType = CE->getType(); - if (!OriginType->isAnyPointerType()) + if (!isValidPointerType(OriginType)) return; - if (!DestType->isAnyPointerType()) + if (!isValidPointerType(DestType)) return; ProgramStateRef State = C.getState(); @@ -1099,7 +1256,7 @@ void NullabilityChecker::checkBind(SVal L, SVal V, const Stmt *S, return; QualType LocType = TVR->getValueType(); - if (!LocType->isAnyPointerType()) + if (!isValidPointerType(LocType)) return; ProgramStateRef State = C.getState(); @@ -1221,9 +1378,9 @@ void NullabilityChecker::printState(raw_ostream &Out, ProgramStateRef State, if (!State->get<InvariantViolated>()) Out << Sep << NL; - for (NullabilityMapTy::iterator I = B.begin(), E = B.end(); I != E; ++I) { - Out << I->first << " : "; - I->second.print(Out); + for (auto [Region, State] : B) { + Out << Region << " : "; + State.print(Out); Out << NL; } } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp index df01cc760e7e..f217520d8f4a 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp @@ -143,7 +143,7 @@ void Callback::run(const MatchFinder::MatchResult &Result) { else OS << "Converting "; - OS << "a pointer value of type '" << ObjT.getAsString() << "' to a "; + OS << "a pointer value of type '" << ObjT << "' to a "; std::string EuphemismForPlain = "primitive"; std::string SuggestedApi = IsObjC ? (IsInteger ? "" : "-boolValue") @@ -196,12 +196,10 @@ void NumberObjectConversionChecker::checkASTCodeBody(const Decl *D, AnalysisManager &AM, BugReporter &BR) const { // Currently this matches CoreFoundation opaque pointer typedefs. - auto CSuspiciousNumberObjectExprM = - expr(ignoringParenImpCasts( - expr(hasType( - typedefType(hasDeclaration(anyOf( - typedefDecl(hasName("CFNumberRef")), - typedefDecl(hasName("CFBooleanRef"))))))) + auto CSuspiciousNumberObjectExprM = expr(ignoringParenImpCasts( + expr(hasType(elaboratedType(namesType(typedefType( + hasDeclaration(anyOf(typedefDecl(hasName("CFNumberRef")), + typedefDecl(hasName("CFBooleanRef"))))))))) .bind("c_object"))); // Currently this matches XNU kernel number-object pointers. @@ -240,8 +238,9 @@ void NumberObjectConversionChecker::checkASTCodeBody(const Decl *D, // The .bind here is in order to compose the error message more accurately. auto ObjCSuspiciousScalarBooleanTypeM = - qualType(typedefType(hasDeclaration( - typedefDecl(hasName("BOOL"))))).bind("objc_bool_type"); + qualType(elaboratedType(namesType( + typedefType(hasDeclaration(typedefDecl(hasName("BOOL"))))))) + .bind("objc_bool_type"); // The .bind here is in order to compose the error message more accurately. auto SuspiciousScalarBooleanTypeM = @@ -253,9 +252,9 @@ void NumberObjectConversionChecker::checkASTCodeBody(const Decl *D, // for storing pointers. auto SuspiciousScalarNumberTypeM = qualType(hasCanonicalType(isInteger()), - unless(typedefType(hasDeclaration( - typedefDecl(matchesName("^::u?intptr_t$")))))) - .bind("int_type"); + unless(elaboratedType(namesType(typedefType(hasDeclaration( + typedefDecl(matchesName("^::u?intptr_t$")))))))) + .bind("int_type"); auto SuspiciousScalarTypeM = qualType(anyOf(SuspiciousScalarBooleanTypeM, diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp index 43af4bb14286..552c222a251a 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp @@ -25,8 +25,10 @@ using namespace ento; namespace { class ObjCAtSyncChecker : public Checker< check::PreStmt<ObjCAtSynchronizedStmt> > { - mutable std::unique_ptr<BuiltinBug> BT_null; - mutable std::unique_ptr<BuiltinBug> BT_undef; + const BugType BT_null{this, "Nil value used as mutex for @synchronized() " + "(no synchronization will occur)"}; + const BugType BT_undef{this, "Uninitialized value used as mutex " + "for @synchronized"}; public: void checkPreStmt(const ObjCAtSynchronizedStmt *S, CheckerContext &C) const; @@ -41,13 +43,10 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, SVal V = C.getSVal(Ex); // Uninitialized value used for the mutex? - if (V.getAs<UndefinedVal>()) { + if (isa<UndefinedVal>(V)) { if (ExplodedNode *N = C.generateErrorNode()) { - if (!BT_undef) - BT_undef.reset(new BuiltinBug(this, "Uninitialized value used as mutex " - "for @synchronized")); auto report = std::make_unique<PathSensitiveBugReport>( - *BT_undef, BT_undef->getDescription(), N); + BT_undef, BT_undef.getDescription(), N); bugreporter::trackExpressionValue(N, Ex, *report); C.emitReport(std::move(report)); } @@ -66,12 +65,8 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, // Generate an error node. This isn't a sink since // a null mutex just means no synchronization occurs. if (ExplodedNode *N = C.generateNonFatalErrorNode(nullState)) { - if (!BT_null) - BT_null.reset(new BuiltinBug( - this, "Nil value used as mutex for @synchronized() " - "(no synchronization will occur)")); auto report = std::make_unique<PathSensitiveBugReport>( - *BT_null, BT_null->getDescription(), N); + BT_null, BT_null.getDescription(), N); bugreporter::trackExpressionValue(N, Ex, *report); C.emitReport(std::move(report)); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp index c8eab3288094..514f53b4804f 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp @@ -98,11 +98,13 @@ private: }; } -static inline std::vector<llvm::StringRef> toRefs(std::vector<std::string> V) { +static inline std::vector<llvm::StringRef> +toRefs(const std::vector<std::string> &V) { return std::vector<llvm::StringRef>(V.begin(), V.end()); } -static decltype(auto) callsNames(std::vector<std::string> FunctionNames) { +static decltype(auto) +callsNames(const std::vector<std::string> &FunctionNames) { return callee(functionDecl(hasAnyName(toRefs(FunctionNames)))); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp index 8428b2294ba6..2b008d1c775a 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp @@ -72,7 +72,7 @@ class WalkAST : public StmtVisitor<WalkAST> { public: WalkAST(BugReporter &br, const CheckerBase *checker, AnalysisDeclContext *ac) : BR(br), Checker(checker), AC(ac), ASTC(AC->getASTContext()), - PtrWidth(ASTC.getTargetInfo().getPointerWidth(0)) {} + PtrWidth(ASTC.getTargetInfo().getPointerWidth(LangAS::Default)) {} // Statement visitor methods. void VisitChildren(Stmt *S); @@ -135,9 +135,9 @@ void WalkAST::VisitCallExpr(CallExpr *CE) { llvm::raw_svector_ostream Os(Buf); // Use "second" and "third" since users will expect 1-based indexing // for parameter names when mentioned in prose. - Os << " The "<< ((ArgNum == 1) ? "second" : "third") << " argument to '" - << Name << "' must be a C array of pointer-sized values, not '" - << Arg->getType().getAsString() << "'"; + Os << " The " << ((ArgNum == 1) ? "second" : "third") << " argument to '" + << Name << "' must be a C array of pointer-sized values, not '" + << Arg->getType() << "'"; PathDiagnosticLocation CELoc = PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp index 13985af76b00..28e88245ca95 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp @@ -30,12 +30,7 @@ namespace { class ObjCContainersChecker : public Checker< check::PreStmt<CallExpr>, check::PostStmt<CallExpr>, check::PointerEscape> { - mutable std::unique_ptr<BugType> BT; - inline void initBugType() const { - if (!BT) - BT.reset(new BugType(this, "CFArray API", - categories::CoreFoundationObjectiveC)); - } + const BugType BT{this, "CFArray API", categories::CoreFoundationObjectiveC}; inline SymbolRef getArraySym(const Expr *E, CheckerContext &C) const { SVal ArrayRef = C.getSVal(E); @@ -47,9 +42,6 @@ class ObjCContainersChecker : public Checker< check::PreStmt<CallExpr>, CheckerContext &C) const; public: - /// A tag to id this checker. - static void *getTag() { static int Tag; return &Tag; } - void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; ProgramStateRef checkPointerEscape(ProgramStateRef State, @@ -137,15 +129,15 @@ void ObjCContainersChecker::checkPreStmt(const CallExpr *CE, // Now, check if 'Idx in [0, Size-1]'. const QualType T = IdxExpr->getType(); - ProgramStateRef StInBound = State->assumeInBound(Idx, *Size, true, T); - ProgramStateRef StOutBound = State->assumeInBound(Idx, *Size, false, T); + ProgramStateRef StInBound, StOutBound; + std::tie(StInBound, StOutBound) = State->assumeInBoundDual(Idx, *Size, T); if (StOutBound && !StInBound) { ExplodedNode *N = C.generateErrorNode(StOutBound); if (!N) return; - initBugType(); + auto R = std::make_unique<PathSensitiveBugReport>( - *BT, "Index is out of bounds", N); + BT, "Index is out of bounds", N); R->addRange(IdxExpr->getSourceRange()); bugreporter::trackExpressionValue(N, IdxExpr, *R, {bugreporter::TrackingKind::Thorough, diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp index 35a600f2d7b8..598b368e74d4 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp @@ -64,7 +64,7 @@ private: class ObjCSuperCallChecker : public Checker< check::ASTDecl<ObjCImplementationDecl> > { public: - ObjCSuperCallChecker() : IsInitialized(false) {} + ObjCSuperCallChecker() = default; void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr, BugReporter &BR) const; @@ -75,7 +75,7 @@ private: void fillSelectors(ASTContext &Ctx, ArrayRef<SelectorDescriptor> Sel, StringRef ClassName) const; mutable llvm::StringMap<llvm::SmallPtrSet<Selector, 16>> SelectorsForClass; - mutable bool IsInitialized; + mutable bool IsInitialized = false; }; } @@ -103,9 +103,7 @@ void ObjCSuperCallChecker::fillSelectors(ASTContext &Ctx, llvm::SmallPtrSet<Selector, 16> &ClassSelectors = SelectorsForClass[ClassName]; // Fill the Selectors SmallSet with all selectors we want to check. - for (ArrayRef<SelectorDescriptor>::iterator I = Sel.begin(), E = Sel.end(); - I != E; ++I) { - SelectorDescriptor Descriptor = *I; + for (SelectorDescriptor Descriptor : Sel) { assert(Descriptor.ArgumentCount <= 1); // No multi-argument selectors yet. // Get the selector. diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp index 4636fd160511..08ad6877cbe6 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp @@ -50,7 +50,7 @@ void ObjCPropertyChecker::checkCopyMutable(const ObjCPropertyDecl *D, const std::string &PropTypeName(T->getPointeeType().getCanonicalType() .getUnqualifiedType() .getAsString()); - if (!StringRef(PropTypeName).startswith("NSMutable")) + if (!StringRef(PropTypeName).starts_with("NSMutable")) return; const ObjCImplDecl *ImplD = nullptr; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp index 17d3c042ac40..217c46451f80 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp @@ -61,13 +61,13 @@ class ObjCSelfInitChecker : public Checker< check::PostObjCMessage, check::PostCall, check::Location, check::Bind > { - mutable std::unique_ptr<BugType> BT; + const BugType BT{this, "Missing \"self = [(super or self) init...]\"", + categories::CoreFoundationObjectiveC}; void checkForInvalidSelf(const Expr *E, CheckerContext &C, const char *errorStr) const; public: - ObjCSelfInitChecker() {} void checkPostObjCMessage(const ObjCMethodCall &Msg, CheckerContext &C) const; void checkPostStmt(const ObjCIvarRefExpr *E, CheckerContext &C) const; void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; @@ -94,19 +94,19 @@ enum SelfFlagEnum { }; } -REGISTER_MAP_WITH_PROGRAMSTATE(SelfFlag, SymbolRef, unsigned) +REGISTER_MAP_WITH_PROGRAMSTATE(SelfFlag, SymbolRef, SelfFlagEnum) REGISTER_TRAIT_WITH_PROGRAMSTATE(CalledInit, bool) /// A call receiving a reference to 'self' invalidates the object that /// 'self' contains. This keeps the "self flags" assigned to the 'self' /// object before the call so we can assign them to the new object that 'self' /// points to after the call. -REGISTER_TRAIT_WITH_PROGRAMSTATE(PreCallSelfFlags, unsigned) +REGISTER_TRAIT_WITH_PROGRAMSTATE(PreCallSelfFlags, SelfFlagEnum) static SelfFlagEnum getSelfFlags(SVal val, ProgramStateRef state) { if (SymbolRef sym = val.getAsSymbol()) - if (const unsigned *attachedFlags = state->get<SelfFlag>(sym)) - return (SelfFlagEnum)*attachedFlags; + if (const SelfFlagEnum *attachedFlags = state->get<SelfFlag>(sym)) + return *attachedFlags; return SelfFlag_None; } @@ -118,7 +118,8 @@ static void addSelfFlag(ProgramStateRef state, SVal val, SelfFlagEnum flag, CheckerContext &C) { // We tag the symbol that the SVal wraps. if (SymbolRef sym = val.getAsSymbol()) { - state = state->set<SelfFlag>(sym, getSelfFlags(val, state) | flag); + state = state->set<SelfFlag>(sym, + SelfFlagEnum(getSelfFlags(val, state) | flag)); C.addTransition(state); } } @@ -156,10 +157,7 @@ void ObjCSelfInitChecker::checkForInvalidSelf(const Expr *E, CheckerContext &C, if (!N) return; - if (!BT) - BT.reset(new BugType(this, "Missing \"self = [(super or self) init...]\"", - categories::CoreFoundationObjectiveC)); - C.emitReport(std::make_unique<PathSensitiveBugReport>(*BT, errorStr, N)); + C.emitReport(std::make_unique<PathSensitiveBugReport>(BT, errorStr, N)); } void ObjCSelfInitChecker::checkPostObjCMessage(const ObjCMethodCall &Msg, @@ -251,11 +249,12 @@ void ObjCSelfInitChecker::checkPreCall(const CallEvent &CE, for (unsigned i = 0; i < NumArgs; ++i) { SVal argV = CE.getArgSVal(i); if (isSelfVar(argV, C)) { - unsigned selfFlags = getSelfFlags(state->getSVal(argV.castAs<Loc>()), C); + SelfFlagEnum selfFlags = + getSelfFlags(state->getSVal(argV.castAs<Loc>()), C); C.addTransition(state->set<PreCallSelfFlags>(selfFlags)); return; } else if (hasSelfFlag(argV, SelfFlag_Self, C)) { - unsigned selfFlags = getSelfFlags(argV, C); + SelfFlagEnum selfFlags = getSelfFlags(argV, C); C.addTransition(state->set<PreCallSelfFlags>(selfFlags)); return; } @@ -270,7 +269,7 @@ void ObjCSelfInitChecker::checkPostCall(const CallEvent &CE, return; ProgramStateRef state = C.getState(); - SelfFlagEnum prevFlags = (SelfFlagEnum)state->get<PreCallSelfFlags>(); + SelfFlagEnum prevFlags = state->get<PreCallSelfFlags>(); if (!prevFlags) return; state = state->remove<PreCallSelfFlags>(); @@ -338,7 +337,7 @@ void ObjCSelfInitChecker::printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const { SelfFlagTy FlagMap = State->get<SelfFlag>(); bool DidCallInit = State->get<CalledInit>(); - SelfFlagEnum PreCallFlags = (SelfFlagEnum)State->get<PreCallSelfFlags>(); + SelfFlagEnum PreCallFlags = State->get<PreCallSelfFlags>(); if (FlagMap.isEmpty() && !DidCallInit && !PreCallFlags) return; @@ -360,18 +359,17 @@ void ObjCSelfInitChecker::printState(raw_ostream &Out, ProgramStateRef State, } Out << NL; - for (SelfFlagTy::iterator I = FlagMap.begin(), E = FlagMap.end(); - I != E; ++I) { - Out << I->first << " : "; + for (auto [Sym, Flag] : FlagMap) { + Out << Sym << " : "; - if (I->second == SelfFlag_None) + if (Flag == SelfFlag_None) Out << "none"; - if (I->second & SelfFlag_Self) + if (Flag & SelfFlag_Self) Out << "self variable"; - if (I->second & SelfFlag_InitRes) { - if (I->second != SelfFlag_InitRes) + if (Flag & SelfFlag_InitRes) { + if (Flag != SelfFlag_InitRes) Out << " | "; Out << "result of init method"; } @@ -411,7 +409,7 @@ static bool isSelfVar(SVal location, CheckerContext &C) { AnalysisDeclContext *analCtx = C.getCurrentAnalysisDeclContext(); if (!analCtx->getSelfDecl()) return false; - if (!location.getAs<loc::MemRegionVal>()) + if (!isa<loc::MemRegionVal>(location)) return false; loc::MemRegionVal MRV = location.castAs<loc::MemRegionVal>(); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp index 3547b7bb61a2..eb40711812e1 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp @@ -26,18 +26,19 @@ namespace { class ObjCSuperDeallocChecker : public Checker<check::PostObjCMessage, check::PreObjCMessage, check::PreCall, check::Location> { - - mutable IdentifierInfo *IIdealloc, *IINSObject; + mutable IdentifierInfo *IIdealloc = nullptr; + mutable IdentifierInfo *IINSObject = nullptr; mutable Selector SELdealloc; - std::unique_ptr<BugType> DoubleSuperDeallocBugType; + const BugType DoubleSuperDeallocBugType{ + this, "[super dealloc] should not be called more than once", + categories::CoreFoundationObjectiveC}; void initIdentifierInfoAndSelectors(ASTContext &Ctx) const; bool isSuperDeallocMessage(const ObjCMethodCall &M) const; public: - ObjCSuperDeallocChecker(); void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; @@ -188,7 +189,7 @@ void ObjCSuperDeallocChecker::reportUseAfterDealloc(SymbolRef Sym, Desc = "Use of 'self' after it has been deallocated"; // Generate the report. - auto BR = std::make_unique<PathSensitiveBugReport>(*DoubleSuperDeallocBugType, + auto BR = std::make_unique<PathSensitiveBugReport>(DoubleSuperDeallocBugType, Desc, ErrNode); BR->addRange(S->getSourceRange()); BR->addVisitor(std::make_unique<SuperDeallocBRVisitor>(Sym)); @@ -213,14 +214,6 @@ void ObjCSuperDeallocChecker::diagnoseCallArguments(const CallEvent &CE, } } -ObjCSuperDeallocChecker::ObjCSuperDeallocChecker() - : IIdealloc(nullptr), IINSObject(nullptr) { - - DoubleSuperDeallocBugType.reset( - new BugType(this, "[super dealloc] should not be called more than once", - categories::CoreFoundationObjectiveC)); -} - void ObjCSuperDeallocChecker::initIdentifierInfoAndSelectors(ASTContext &Ctx) const { if (IIdealloc) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp index c9828c36a06a..1c2d84254d46 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp @@ -12,16 +12,17 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" -#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprObjC.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceManager.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/Checker.h" +#include "llvm/ADT/STLExtras.h" using namespace clang; using namespace ento; @@ -48,9 +49,7 @@ static void Scan(IvarUsageMap& M, const Stmt *S) { } if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(S)) - for (PseudoObjectExpr::const_semantics_iterator - i = POE->semantics_begin(), e = POE->semantics_end(); i != e; ++i) { - const Expr *sub = *i; + for (const Expr *sub : POE->semantics()) { if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(sub)) sub = OVE->getSourceExpr(); Scan(M, sub); @@ -134,8 +133,8 @@ static void checkObjCUnusedIvar(const ObjCImplementationDecl *D, // Any potentially unused ivars? bool hasUnused = false; - for (IvarUsageMap::iterator I = M.begin(), E = M.end(); I!=E; ++I) - if (I->second == Unused) { + for (IVarState State : llvm::make_second_range(M)) + if (State == Unused) { hasUnused = true; break; } @@ -152,16 +151,16 @@ static void checkObjCUnusedIvar(const ObjCImplementationDecl *D, Scan(M, D->getDeclContext(), SM.getFileID(D->getLocation()), SM); // Find ivars that are unused. - for (IvarUsageMap::iterator I = M.begin(), E = M.end(); I!=E; ++I) - if (I->second == Unused) { + for (auto [Ivar, State] : M) + if (State == Unused) { std::string sbuf; llvm::raw_string_ostream os(sbuf); - os << "Instance variable '" << *I->first << "' in class '" << *ID + os << "Instance variable '" << *Ivar << "' in class '" << *ID << "' is never used by the methods in its @implementation " "(although it may be used by category methods)."; PathDiagnosticLocation L = - PathDiagnosticLocation::create(I->first, BR.getSourceManager()); + PathDiagnosticLocation::create(Ivar, BR.getSourceManager()); BR.EmitBasicReport(D, Checker, "Unused instance variable", "Optimization", os.str(), L); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp index 40472ccfe7e6..eee9449f3180 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp @@ -32,7 +32,7 @@ using namespace ento; namespace { class PaddingChecker : public Checker<check::ASTDecl<TranslationUnitDecl>> { private: - mutable std::unique_ptr<BugType> PaddingBug; + const BugType PaddingBug{this, "Excessive Padding", "Performance"}; mutable BugReporter *BR; public: @@ -182,7 +182,7 @@ public: return false; }; - if (std::any_of(RD->field_begin(), RD->field_end(), IsTrickyField)) + if (llvm::any_of(RD->fields(), IsTrickyField)) return true; return false; } @@ -273,7 +273,7 @@ public: SmallVector<const FieldDecl *, 20> OptimalFieldsOrder; while (!Fields.empty()) { unsigned TrailingZeros = - llvm::countTrailingZeros((unsigned long long)NewOffset.getQuantity()); + llvm::countr_zero((unsigned long long)NewOffset.getQuantity()); // If NewOffset is zero, then countTrailingZeros will be 64. Shifting // 64 will overflow our unsigned long long. Shifting 63 will turn // our long long (and CharUnits internal type) negative. So shift 62. @@ -310,10 +310,6 @@ public: void reportRecord( const RecordDecl *RD, CharUnits BaselinePad, CharUnits OptimalPad, const SmallVector<const FieldDecl *, 20> &OptimalFieldsOrder) const { - if (!PaddingBug) - PaddingBug = - std::make_unique<BugType>(this, "Excessive Padding", "Performance"); - SmallString<100> Buf; llvm::raw_svector_ostream Os(Buf); Os << "Excessive padding in '"; @@ -332,17 +328,16 @@ public: } Os << " (" << BaselinePad.getQuantity() << " padding bytes, where " - << OptimalPad.getQuantity() << " is optimal). \n" - << "Optimal fields order: \n"; + << OptimalPad.getQuantity() << " is optimal). " + << "Optimal fields order: "; for (const auto *FD : OptimalFieldsOrder) - Os << FD->getName() << ", \n"; + Os << FD->getName() << ", "; Os << "consider reordering the fields or adding explicit padding " "members."; PathDiagnosticLocation CELoc = PathDiagnosticLocation::create(RD, BR->getSourceManager()); - auto Report = - std::make_unique<BasicBugReport>(*PaddingBug, Os.str(), CELoc); + auto Report = std::make_unique<BasicBugReport>(PaddingBug, Os.str(), CELoc); Report->setDeclWithIssue(RD); Report->addRange(RD->getSourceRange()); BR->emitReport(std::move(Report)); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp index d3e2849a0ce6..1141f07428b4 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp @@ -11,13 +11,14 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/ExprCXX.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 "llvm/ADT/StringRef.h" using namespace clang; using namespace ento; @@ -55,8 +56,8 @@ class PointerArithChecker bool PointedNeeded = false) const; void initAllocIdentifiers(ASTContext &C) const; - mutable std::unique_ptr<BuiltinBug> BT_pointerArith; - mutable std::unique_ptr<BuiltinBug> BT_polyArray; + const BugType BT_pointerArith{this, "Dangerous pointer arithmetic"}; + const BugType BT_polyArray{this, "Dangerous pointer arithmetic"}; mutable llvm::SmallSet<IdentifierInfo *, 8> AllocFunctions; public: @@ -79,10 +80,9 @@ void PointerArithChecker::checkDeadSymbols(SymbolReaper &SR, // see http://reviews.llvm.org/D14203 for further information. /*ProgramStateRef State = C.getState(); RegionStateTy RegionStates = State->get<RegionState>(); - for (RegionStateTy::iterator I = RegionStates.begin(), E = RegionStates.end(); - I != E; ++I) { - if (!SR.isLiveRegion(I->first)) - State = State->remove<RegionState>(I->first); + for (const MemRegion *Reg: llvm::make_first_range(RegionStates)) { + if (!SR.isLiveRegion(Reg)) + State = State->remove<RegionState>(Reg); } C.addTransition(State);*/ } @@ -168,13 +168,10 @@ void PointerArithChecker::reportPointerArithMisuse(const Expr *E, if (!IsPolymorphic) return; if (ExplodedNode *N = C.generateNonFatalErrorNode()) { - if (!BT_polyArray) - BT_polyArray.reset(new BuiltinBug( - this, "Dangerous pointer arithmetic", - "Pointer arithmetic on a pointer to base class is dangerous " - "because derived and base class may have different size.")); - auto R = std::make_unique<PathSensitiveBugReport>( - *BT_polyArray, BT_polyArray->getDescription(), N); + constexpr llvm::StringLiteral Msg = + "Pointer arithmetic on a pointer to base class is dangerous " + "because derived and base class may have different size."; + auto R = std::make_unique<PathSensitiveBugReport>(BT_polyArray, Msg, N); R->addRange(E->getSourceRange()); R->markInteresting(ArrayRegion); C.emitReport(std::move(R)); @@ -191,13 +188,10 @@ void PointerArithChecker::reportPointerArithMisuse(const Expr *E, return; if (ExplodedNode *N = C.generateNonFatalErrorNode()) { - if (!BT_pointerArith) - BT_pointerArith.reset(new BuiltinBug(this, "Dangerous pointer arithmetic", - "Pointer arithmetic on non-array " - "variables relies on memory layout, " - "which is dangerous.")); - auto R = std::make_unique<PathSensitiveBugReport>( - *BT_pointerArith, BT_pointerArith->getDescription(), N); + constexpr llvm::StringLiteral Msg = + "Pointer arithmetic on non-array variables relies on memory layout, " + "which is dangerous."; + auto R = std::make_unique<PathSensitiveBugReport>(BT_pointerArith, Msg, N); R->addRange(SR); R->markInteresting(Region); C.emitReport(std::move(R)); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp index 81c19d9a0940..2438cf30b39b 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp @@ -17,6 +17,7 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/ADT/StringRef.h" using namespace clang; using namespace ento; @@ -24,7 +25,7 @@ using namespace ento; namespace { class PointerSubChecker : public Checker< check::PreStmt<BinaryOperator> > { - mutable std::unique_ptr<BuiltinBug> BT; + const BugType BT{this, "Pointer subtraction"}; public: void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const; @@ -58,13 +59,10 @@ void PointerSubChecker::checkPreStmt(const BinaryOperator *B, return; if (ExplodedNode *N = C.generateNonFatalErrorNode()) { - if (!BT) - BT.reset( - new BuiltinBug(this, "Pointer subtraction", - "Subtraction of two pointers that do not point to " - "the same memory chunk may cause incorrect result.")); - auto R = - std::make_unique<PathSensitiveBugReport>(*BT, BT->getDescription(), N); + constexpr llvm::StringLiteral Msg = + "Subtraction of two pointers that do not point to the same memory " + "chunk may cause incorrect result."; + auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N); R->addRange(B->getSourceRange()); C.emitReport(std::move(R)); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp index ee71b55a39e6..fa8572cf85ed 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp @@ -21,6 +21,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" @@ -77,7 +78,7 @@ public: CK_C11LockChecker, CK_NumCheckKinds }; - DefaultBool ChecksEnabled[CK_NumCheckKinds]; + bool ChecksEnabled[CK_NumCheckKinds] = {false}; CheckerNameRef CheckNames[CK_NumCheckKinds]; private: @@ -86,7 +87,7 @@ private: CheckerKind CheckKind) const; CallDescriptionMap<FnCheck> PThreadCallbacks = { // Init. - {{"pthread_mutex_init", 2}, &PthreadLockChecker::InitAnyLock}, + {{{"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. @@ -94,74 +95,74 @@ private: // 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}, + {{{"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}, + {{{"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}, + {{{"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}, + {{{"pthread_mutex_destroy"}, 1}, &PthreadLockChecker::DestroyPthreadLock}, + {{{"lck_mtx_destroy"}, 2}, &PthreadLockChecker::DestroyXNULock}, // TODO: pthread_rwlock_destroy(1 argument). // TODO: lck_rw_destroy(2 arguments). }; CallDescriptionMap<FnCheck> FuchsiaCallbacks = { // Init. - {{"spin_lock_init", 1}, &PthreadLockChecker::InitAnyLock}, + {{{"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}, + {{{"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}, + {{{"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}, + {{{"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}, + {{{"mtx_init"}, 2}, &PthreadLockChecker::InitAnyLock}, // Acquire. - {{"mtx_lock", 1}, &PthreadLockChecker::AcquirePthreadLock}, + {{{"mtx_lock"}, 1}, &PthreadLockChecker::AcquirePthreadLock}, // Try. - {{"mtx_trylock", 1}, &PthreadLockChecker::TryC11Lock}, - {{"mtx_timedlock", 2}, &PthreadLockChecker::TryC11Lock}, + {{{"mtx_trylock"}, 1}, &PthreadLockChecker::TryC11Lock}, + {{{"mtx_timedlock"}, 2}, &PthreadLockChecker::TryC11Lock}, // Release. - {{"mtx_unlock", 1}, &PthreadLockChecker::ReleaseAnyLock}, + {{{"mtx_unlock"}, 1}, &PthreadLockChecker::ReleaseAnyLock}, // Destroy - {{"mtx_destroy", 1}, &PthreadLockChecker::DestroyPthreadLock}, + {{{"mtx_destroy"}, 1}, &PthreadLockChecker::DestroyPthreadLock}, }; ProgramStateRef resolvePossiblyDestroyedMutex(ProgramStateRef state, @@ -290,6 +291,7 @@ ProgramStateRef PthreadLockChecker::resolvePossiblyDestroyedMutex( // Existence in DestroyRetVal ensures existence in LockMap. // Existence in Destroyed also ensures that the lock state for lockR is either // UntouchedAndPossiblyDestroyed or UnlockedAndPossiblyDestroyed. + assert(lstate); assert(lstate->isUntouchedAndPossiblyDestroyed() || lstate->isUnlockedAndPossiblyDestroyed()); @@ -681,9 +683,7 @@ ProgramStateRef PthreadLockChecker::checkRegionChanges( // 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()) + if (IsLibraryFunction && !llvm::is_contained(ExplicitRegions, R)) continue; State = State->remove<LockMap>(R); 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 3f3267ff9391..7e74b418b335 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp @@ -14,6 +14,7 @@ #include "RetainCountChecker.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include <optional> using namespace clang; using namespace ento; @@ -45,7 +46,7 @@ static ProgramStateRef removeRefBinding(ProgramStateRef State, SymbolRef Sym) { void RefVal::print(raw_ostream &Out) const { if (!T.isNull()) - Out << "Tracked " << T.getAsString() << " | "; + Out << "Tracked " << T << " | "; switch (getKind()) { default: llvm_unreachable("Invalid RefVal kind"); @@ -154,10 +155,8 @@ void RetainCountChecker::checkPostStmt(const BlockExpr *BE, ProgramStateRef state = C.getState(); auto *R = cast<BlockDataRegion>(C.getSVal(BE).getAsRegion()); - BlockDataRegion::referenced_vars_iterator I = R->referenced_vars_begin(), - E = R->referenced_vars_end(); - - if (I == E) + auto ReferencedVars = R->referenced_vars(); + if (ReferencedVars.empty()) return; // FIXME: For now we invalidate the tracking of all symbols passed to blocks @@ -167,8 +166,8 @@ void RetainCountChecker::checkPostStmt(const BlockExpr *BE, const LocationContext *LC = C.getLocationContext(); MemRegionManager &MemMgr = C.getSValBuilder().getRegionManager(); - for ( ; I != E; ++I) { - const VarRegion *VR = I.getCapturedRegion(); + for (auto Var : ReferencedVars) { + const VarRegion *VR = Var.getCapturedRegion(); if (VR->getSuperRegion() == R) { VR = MemMgr.getVarRegion(VR->getDecl(), LC); } @@ -284,13 +283,13 @@ void RetainCountChecker::checkPostStmt(const ObjCBoxedExpr *Ex, void RetainCountChecker::checkPostStmt(const ObjCIvarRefExpr *IRE, CheckerContext &C) const { - Optional<Loc> IVarLoc = C.getSVal(IRE).getAs<Loc>(); + std::optional<Loc> IVarLoc = C.getSVal(IRE).getAs<Loc>(); if (!IVarLoc) return; ProgramStateRef State = C.getState(); SymbolRef Sym = State->getSVal(*IVarLoc).getAsSymbol(); - if (!Sym || !dyn_cast_or_null<ObjCIvarRegion>(Sym->getOriginRegion())) + if (!Sym || !isa_and_nonnull<ObjCIvarRegion>(Sym->getOriginRegion())) return; // Accessing an ivar directly is unusual. If we've done that, be more @@ -412,15 +411,15 @@ static QualType GetReturnType(const Expr *RetE, ASTContext &Ctx) { return RetTy; } -static Optional<RefVal> refValFromRetEffect(RetEffect RE, - QualType ResultTy) { +static std::optional<RefVal> refValFromRetEffect(RetEffect RE, + QualType ResultTy) { if (RE.isOwned()) { return RefVal::makeOwned(RE.getObjKind(), ResultTy); } else if (RE.notOwned()) { return RefVal::makeNotOwned(RE.getObjKind(), ResultTy); } - return None; + return std::nullopt; } static bool isPointerToObject(QualType QT) { @@ -692,7 +691,7 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ, assert(Ex); ResultTy = GetReturnType(Ex, C.getASTContext()); } - if (Optional<RefVal> updatedRefVal = refValFromRetEffect(RE, ResultTy)) + if (std::optional<RefVal> updatedRefVal = refValFromRetEffect(RE, ResultTy)) state = setRefBinding(state, Sym, *updatedRefVal); } @@ -767,7 +766,7 @@ ProgramStateRef RetainCountChecker::updateSymbol(ProgramStateRef state, break; } - LLVM_FALLTHROUGH; + [[fallthrough]]; case DoNothing: return state; @@ -907,7 +906,7 @@ bool RetainCountChecker::evalCall(const CallEvent &Call, const LocationContext *LCtx = C.getLocationContext(); using BehaviorSummary = RetainSummaryManager::BehaviorSummary; - Optional<BehaviorSummary> BSmr = + std::optional<BehaviorSummary> BSmr = SmrMgr.canEval(CE, FD, hasTrustedImplementationAnnotation); // See if it's one of the specific functions we know how to eval. @@ -945,7 +944,8 @@ 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); + CE, LCtx, C.getSValBuilder().makeNullWithType(ResultTy), + /*Invalidate=*/false); C.addTransition(NullOutputState, &getCastFailTag()); // And on the original branch assume that both input and @@ -1188,14 +1188,14 @@ ProgramStateRef RetainCountChecker::checkRegionChanges( if (!invalidated) return state; - llvm::SmallPtrSet<SymbolRef, 8> WhitelistedSymbols; + llvm::SmallPtrSet<SymbolRef, 8> AllowedSymbols; for (const MemRegion *I : ExplicitRegions) if (const SymbolicRegion *SR = I->StripCasts()->getAs<SymbolicRegion>()) - WhitelistedSymbols.insert(SR->getSymbol()); + AllowedSymbols.insert(SR->getSymbol()); for (SymbolRef sym : *invalidated) { - if (WhitelistedSymbols.count(sym)) + if (AllowedSymbols.count(sym)) continue; // Remove any existing reference-count binding. state = removeRefBinding(state, sym); @@ -1335,7 +1335,7 @@ void RetainCountChecker::checkBeginFunction(CheckerContext &Ctx) const { RetainSummaryManager &SmrMgr = getSummaryManager(Ctx); const LocationContext *LCtx = Ctx.getLocationContext(); const Decl *D = LCtx->getDecl(); - Optional<AnyCall> C = AnyCall::forDecl(D); + std::optional<AnyCall> C = AnyCall::forDecl(D); if (!C || SmrMgr.isTrustedReferenceCountImplementation(D)) return; 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 223e28c2c5b8..d4d7c4c74c56 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h @@ -36,7 +36,6 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/ImmutableList.h" -#include "llvm/ADT/ImmutableMap.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" 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 64ac6bc4c06b..c3acb73ba717 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp @@ -15,6 +15,7 @@ #include "RetainCountChecker.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" +#include <optional> using namespace clang; using namespace ento; @@ -73,11 +74,8 @@ RefCountBug::RefCountBug(CheckerNameRef Checker, RefCountBugKind BT) static bool isNumericLiteralExpression(const Expr *E) { // FIXME: This set of cases was copied from SemaExprObjC. - return isa<IntegerLiteral>(E) || - isa<CharacterLiteral>(E) || - isa<FloatingLiteral>(E) || - isa<ObjCBoolLiteralExpr>(E) || - isa<CXXBoolLiteralExpr>(E); + return isa<IntegerLiteral, CharacterLiteral, FloatingLiteral, + ObjCBoolLiteralExpr, CXXBoolLiteralExpr>(E); } /// If type represents a pointer to CXXRecordDecl, @@ -168,13 +166,12 @@ static bool shouldGenerateNote(llvm::raw_string_ostream &os, /// Finds argument index of the out paramter in the call @c S /// corresponding to the symbol @c Sym. -/// If none found, returns None. -static Optional<unsigned> findArgIdxOfSymbol(ProgramStateRef CurrSt, - const LocationContext *LCtx, - SymbolRef &Sym, - Optional<CallEventRef<>> CE) { +/// If none found, returns std::nullopt. +static std::optional<unsigned> +findArgIdxOfSymbol(ProgramStateRef CurrSt, const LocationContext *LCtx, + SymbolRef &Sym, std::optional<CallEventRef<>> CE) { if (!CE) - return None; + return std::nullopt; for (unsigned Idx = 0; Idx < (*CE)->getNumArgs(); Idx++) if (const MemRegion *MR = (*CE)->getArgSVal(Idx).getAsRegion()) @@ -182,25 +179,25 @@ static Optional<unsigned> findArgIdxOfSymbol(ProgramStateRef CurrSt, if (CurrSt->getSVal(MR, TR->getValueType()).getAsSymbol() == Sym) return Idx; - return None; + return std::nullopt; } -static Optional<std::string> findMetaClassAlloc(const Expr *Callee) { +static std::optional<std::string> findMetaClassAlloc(const Expr *Callee) { if (const auto *ME = dyn_cast<MemberExpr>(Callee)) { if (ME->getMemberDecl()->getNameAsString() != "alloc") - return None; + return std::nullopt; const Expr *This = ME->getBase()->IgnoreParenImpCasts(); if (const auto *DRE = dyn_cast<DeclRefExpr>(This)) { const ValueDecl *VD = DRE->getDecl(); if (VD->getNameAsString() != "metaClass") - return None; + return std::nullopt; if (const auto *RD = dyn_cast<CXXRecordDecl>(VD->getDeclContext())) return RD->getNameAsString(); } } - return None; + return std::nullopt; } static std::string findAllocatedObjectName(const Stmt *S, QualType QT) { @@ -237,8 +234,8 @@ static void generateDiagnosticsForCallLike(ProgramStateRef CurrSt, os << "Operator 'new'"; } else { assert(isa<ObjCMessageExpr>(S)); - CallEventRef<ObjCMethodCall> Call = - Mgr.getObjCMethodCall(cast<ObjCMessageExpr>(S), CurrSt, LCtx); + CallEventRef<ObjCMethodCall> Call = Mgr.getObjCMethodCall( + cast<ObjCMessageExpr>(S), CurrSt, LCtx, {nullptr, 0}); switch (Call->getMessageKind()) { case OCM_Message: @@ -253,7 +250,7 @@ static void generateDiagnosticsForCallLike(ProgramStateRef CurrSt, } } - Optional<CallEventRef<>> CE = Mgr.getCall(S, CurrSt, LCtx); + std::optional<CallEventRef<>> CE = Mgr.getCall(S, CurrSt, LCtx, {nullptr, 0}); auto Idx = findArgIdxOfSymbol(CurrSt, LCtx, Sym, CE); // If index is not found, we assume that the symbol was returned. @@ -264,14 +261,12 @@ static void generateDiagnosticsForCallLike(ProgramStateRef CurrSt, } if (CurrV.getObjKind() == ObjKind::CF) { - os << "a Core Foundation object of type '" - << Sym->getType().getAsString() << "' with a "; + os << "a Core Foundation object of type '" << Sym->getType() << "' with a "; } else if (CurrV.getObjKind() == ObjKind::OS) { os << "an OSObject of type '" << findAllocatedObjectName(S, Sym->getType()) << "' with a "; } else if (CurrV.getObjKind() == ObjKind::Generalized) { - os << "an object of type '" << Sym->getType().getAsString() - << "' with a "; + os << "an object of type '" << Sym->getType() << "' with a "; } else { assert(CurrV.getObjKind() == ObjKind::ObjC); QualType T = Sym->getType(); @@ -279,8 +274,7 @@ static void generateDiagnosticsForCallLike(ProgramStateRef CurrSt, os << "an Objective-C object with a "; } else { const ObjCObjectPointerType *PT = cast<ObjCObjectPointerType>(T); - os << "an instance of " << PT->getPointeeType().getAsString() - << " with a "; + os << "an instance of " << PT->getPointeeType() << " with a "; } } @@ -608,16 +602,17 @@ RefCountReportVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, return std::move(P); } -static Optional<std::string> describeRegion(const MemRegion *MR) { +static std::optional<std::string> describeRegion(const MemRegion *MR) { if (const auto *VR = dyn_cast_or_null<VarRegion>(MR)) return std::string(VR->getDecl()->getName()); // Once we support more storage locations for bindings, // this would need to be improved. - return None; + return std::nullopt; } using Bindings = llvm::SmallVector<std::pair<const MemRegion *, SVal>, 4>; +namespace { class VarBindingsCollector : public StoreManager::BindingsHandler { SymbolRef Sym; Bindings &Result; @@ -638,6 +633,7 @@ public: return true; } }; +} // namespace Bindings getAllVarBindingsForSymbol(ProgramStateManager &Manager, const ExplodedNode *Node, SymbolRef Sym) { @@ -734,7 +730,7 @@ static AllocationInfo GetAllocationSite(ProgramStateManager &StateMgr, const LocationContext *InterestingMethodContext = nullptr; if (InitMethodContext) { const ProgramPoint AllocPP = AllocationNode->getLocation(); - if (Optional<StmtPoint> SP = AllocPP.getAs<StmtPoint>()) + if (std::optional<StmtPoint> SP = AllocPP.getAs<StmtPoint>()) if (const ObjCMessageExpr *ME = SP->getStmtAs<ObjCMessageExpr>()) if (ME->getMethodFamily() == OMF_alloc) InterestingMethodContext = InitMethodContext; @@ -777,7 +773,7 @@ RefLeakReportVisitor::getEndPath(BugReporterContext &BRC, os << "Object leaked: "; - Optional<std::string> RegionDescription = describeRegion(LastBinding); + std::optional<std::string> RegionDescription = describeRegion(LastBinding); if (RegionDescription) { os << "object allocated and stored into '" << *RegionDescription << '\''; } else { @@ -790,9 +786,6 @@ RefLeakReportVisitor::getEndPath(BugReporterContext &BRC, assert(RV); if (RV->getKind() == RefVal::ErrorLeakReturned) { - // FIXME: Per comments in rdar://6320065, "create" only applies to CF - // objects. Only "copy", "alloc", "retain" and "new" transfer ownership - // to the caller for NS objects. const Decl *D = &EndN->getCodeDecl(); os << (isa<ObjCMethodDecl>(D) ? " is returned from a method " @@ -923,7 +916,7 @@ void RefLeakReport::createDescription(CheckerContext &Ctx) { llvm::raw_string_ostream os(Description); os << "Potential leak of an object"; - Optional<std::string> RegionDescription = + std::optional<std::string> RegionDescription = describeRegion(AllocBindingToReport); if (RegionDescription) { os << " stored into '" << *RegionDescription << '\''; @@ -975,7 +968,7 @@ void RefLeakReport::findBindingToReport(CheckerContext &Ctx, // Let's pick one of them at random (if there is something to pick from). AllocBindingToReport = AllVarBindings[0].first; - // Because 'AllocBindingToReport' is not the the same as + // Because 'AllocBindingToReport' is not the same as // 'AllocFirstBinding', we need to explain how the leaking object // got from one to another. // diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp index 885750218b9e..09d82ebabd4c 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -25,7 +26,9 @@ using namespace ento; namespace { class ReturnPointerRangeChecker : public Checker< check::PreStmt<ReturnStmt> > { - mutable std::unique_ptr<BuiltinBug> BT; + // FIXME: This bug correspond to CWE-466. Eventually we should have bug + // types explicitly reference such exploit categories (when applicable). + const BugType BT{this, "Buffer overflow"}; public: void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const; @@ -40,6 +43,10 @@ void ReturnPointerRangeChecker::checkPreStmt(const ReturnStmt *RS, if (!RetE) return; + // Skip "body farmed" functions. + if (RetE->getSourceRange().isInvalid()) + return; + SVal V = C.getSVal(RetE); const MemRegion *R = V.getAsRegion(); @@ -63,32 +70,55 @@ void ReturnPointerRangeChecker::checkPreStmt(const ReturnStmt *RS, if (Idx == ElementCount) return; - ProgramStateRef StInBound = state->assumeInBound(Idx, ElementCount, true); - ProgramStateRef StOutBound = state->assumeInBound(Idx, ElementCount, false); + ProgramStateRef StInBound, StOutBound; + std::tie(StInBound, StOutBound) = state->assumeInBoundDual(Idx, ElementCount); if (StOutBound && !StInBound) { ExplodedNode *N = C.generateErrorNode(StOutBound); if (!N) return; - // FIXME: This bug correspond to CWE-466. Eventually we should have bug - // types explicitly reference such exploit categories (when applicable). - if (!BT) - BT.reset(new BuiltinBug( - this, "Buffer overflow", - "Returned pointer value points outside the original object " - "(potential buffer overflow)")); - - // FIXME: It would be nice to eventually make this diagnostic more clear, - // e.g., by referencing the original declaration or by saying *why* this - // reference is outside the range. + constexpr llvm::StringLiteral Msg = + "Returned pointer value points outside the original object " + "(potential buffer overflow)"; // Generate a report for this bug. - auto report = - std::make_unique<PathSensitiveBugReport>(*BT, BT->getDescription(), N); - - report->addRange(RetE->getSourceRange()); - C.emitReport(std::move(report)); + auto Report = std::make_unique<PathSensitiveBugReport>(BT, Msg, N); + Report->addRange(RetE->getSourceRange()); + + const auto ConcreteElementCount = ElementCount.getAs<nonloc::ConcreteInt>(); + const auto ConcreteIdx = Idx.getAs<nonloc::ConcreteInt>(); + + const auto *DeclR = ER->getSuperRegion()->getAs<DeclRegion>(); + + if (DeclR) + Report->addNote("Original object declared here", + {DeclR->getDecl(), C.getSourceManager()}); + + if (ConcreteElementCount) { + SmallString<128> SBuf; + llvm::raw_svector_ostream OS(SBuf); + OS << "Original object "; + if (DeclR) { + OS << "'"; + DeclR->getDecl()->printName(OS); + OS << "' "; + } + OS << "is an array of " << ConcreteElementCount->getValue() << " '"; + ER->getValueType().print(OS, + PrintingPolicy(C.getASTContext().getLangOpts())); + OS << "' objects"; + if (ConcreteIdx) { + OS << ", returned pointer points at index " << ConcreteIdx->getValue(); + } + + Report->addNote(SBuf, + {RetE, C.getSourceManager(), C.getLocationContext()}); + } + + bugreporter::trackExpressionValue(N, RetE, *Report); + + C.emitReport(std::move(Report)); } } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp index 5266cbf86b44..efffbf2ee755 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp @@ -24,8 +24,8 @@ using namespace ento; namespace { class ReturnUndefChecker : public Checker< check::PreStmt<ReturnStmt> > { - mutable std::unique_ptr<BuiltinBug> BT_Undef; - mutable std::unique_ptr<BuiltinBug> BT_NullReference; + const BugType BT_Undef{this, "Garbage return value"}; + const BugType BT_NullReference{this, "Returning null reference"}; void emitUndef(CheckerContext &C, const Expr *RetE) const; void checkReference(CheckerContext &C, const Expr *RetE, @@ -77,14 +77,13 @@ void ReturnUndefChecker::checkPreStmt(const ReturnStmt *RS, } } -static void emitBug(CheckerContext &C, BuiltinBug &BT, const Expr *RetE, - const Expr *TrackingE = nullptr) { +static void emitBug(CheckerContext &C, const BugType &BT, StringRef Msg, + const Expr *RetE, const Expr *TrackingE = nullptr) { ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto Report = - std::make_unique<PathSensitiveBugReport>(BT, BT.getDescription(), N); + auto Report = std::make_unique<PathSensitiveBugReport>(BT, Msg, N); Report->addRange(RetE->getSourceRange()); bugreporter::trackExpressionValue(N, TrackingE ? TrackingE : RetE, *Report); @@ -93,11 +92,7 @@ static void emitBug(CheckerContext &C, BuiltinBug &BT, const Expr *RetE, } void ReturnUndefChecker::emitUndef(CheckerContext &C, const Expr *RetE) const { - if (!BT_Undef) - BT_Undef.reset( - new BuiltinBug(this, "Garbage return value", - "Undefined or garbage value returned to caller")); - emitBug(C, *BT_Undef, RetE); + emitBug(C, BT_Undef, "Undefined or garbage value returned to caller", RetE); } void ReturnUndefChecker::checkReference(CheckerContext &C, const Expr *RetE, @@ -112,10 +107,8 @@ void ReturnUndefChecker::checkReference(CheckerContext &C, const Expr *RetE, } // The return value is known to be null. Emit a bug report. - if (!BT_NullReference) - BT_NullReference.reset(new BuiltinBug(this, "Returning null reference")); - - emitBug(C, *BT_NullReference, RetE, bugreporter::getDerefExpr(RetE)); + emitBug(C, BT_NullReference, BT_NullReference.getDescription(), RetE, + bugreporter::getDerefExpr(RetE)); } void ento::registerReturnUndefChecker(CheckerManager &mgr) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp index 14ecede17083..c3112ebe4e79 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp @@ -14,10 +14,11 @@ #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" +#include <optional> using namespace clang; using namespace ento; @@ -58,7 +59,7 @@ private: } // namespace static std::string getName(const CallEvent &Call) { - std::string Name = ""; + std::string Name; if (const auto *MD = dyn_cast<CXXMethodDecl>(Call.getDecl())) if (const CXXRecordDecl *RD = MD->getParent()) Name += RD->getNameAsString() + "::"; @@ -69,11 +70,11 @@ static std::string getName(const CallEvent &Call) { // The predefinitions ('CDM') could break due to the ever growing code base. // Check for the expected invariants and see whether they apply. -static Optional<bool> isInvariantBreak(bool ExpectedValue, SVal ReturnV, - CheckerContext &C) { +static std::optional<bool> isInvariantBreak(bool ExpectedValue, SVal ReturnV, + CheckerContext &C) { auto ReturnDV = ReturnV.getAs<DefinedOrUnknownSVal>(); if (!ReturnDV) - return None; + return std::nullopt; if (ExpectedValue) return C.getState()->isNull(*ReturnDV).isConstrainedTrue(); @@ -89,7 +90,8 @@ void ReturnValueChecker::checkPostCall(const CallEvent &Call, SVal ReturnV = Call.getReturnValue(); bool ExpectedValue = *RawExpectedValue; - Optional<bool> IsInvariantBreak = isInvariantBreak(ExpectedValue, ReturnV, C); + std::optional<bool> IsInvariantBreak = + isInvariantBreak(ExpectedValue, ReturnV, C); if (!IsInvariantBreak) return; @@ -136,7 +138,8 @@ void ReturnValueChecker::checkEndFunction(const ReturnStmt *RS, SVal ReturnV = State->getSVal(RS->getRetValue(), C.getLocationContext()); bool ExpectedValue = *RawExpectedValue; - Optional<bool> IsInvariantBreak = isInvariantBreak(ExpectedValue, ReturnV, C); + std::optional<bool> IsInvariantBreak = + isInvariantBreak(ExpectedValue, ReturnV, C); if (!IsInvariantBreak) return; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/STLAlgorithmModeling.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/STLAlgorithmModeling.cpp index 933e0146ff59..788f2875863c 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/STLAlgorithmModeling.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/STLAlgorithmModeling.cpp @@ -12,6 +12,7 @@ #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" @@ -60,7 +61,7 @@ class STLAlgorithmModeling : public Checker<eval::Call> { public: STLAlgorithmModeling() = default; - bool AggressiveStdFindModeling; + bool AggressiveStdFindModeling = false; bool evalCall(const CallEvent &Call, CheckerContext &C) const; }; // @@ -130,7 +131,7 @@ void STLAlgorithmModeling::Find(CheckerContext &C, const CallExpr *CE, nonloc::SymbolVal(NewPos->getOffset()), nonloc::SymbolVal(Pos->getOffset()), SVB.getConditionType()); - assert(GreaterOrEqual.getAs<DefinedSVal>() && + assert(isa<DefinedSVal>(GreaterOrEqual) && "Symbol comparison must be a `DefinedSVal`"); StateFound = StateFound->assume(GreaterOrEqual.castAs<DefinedSVal>(), true); } @@ -152,7 +153,7 @@ void STLAlgorithmModeling::Find(CheckerContext &C, const CallExpr *CE, nonloc::SymbolVal(NewPos->getOffset()), nonloc::SymbolVal(Pos->getOffset()), SVB.getConditionType()); - assert(Less.getAs<DefinedSVal>() && + assert(isa<DefinedSVal>(Less) && "Symbol comparison must be a `DefinedSVal`"); StateFound = StateFound->assume(Less.castAs<DefinedSVal>(), true); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp index 8d380ed1b93d..7cbe271dfbf9 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp @@ -17,6 +17,7 @@ #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include <utility> @@ -51,10 +52,13 @@ class SimpleStreamChecker : public Checker<check::PostCall, check::PreCall, check::DeadSymbols, check::PointerEscape> { - CallDescription OpenFn, CloseFn; + const CallDescription OpenFn{{"fopen"}, 2}; + const CallDescription CloseFn{{"fclose"}, 1}; - std::unique_ptr<BugType> DoubleCloseBugType; - std::unique_ptr<BugType> LeakBugType; + const BugType DoubleCloseBugType{this, "Double fclose", + "Unix Stream API Error"}; + const BugType LeakBugType{this, "Resource Leak", "Unix Stream API Error", + /*SuppressOnSink=*/true}; void reportDoubleClose(SymbolRef FileDescSym, const CallEvent &Call, @@ -66,8 +70,6 @@ class SimpleStreamChecker : public Checker<check::PostCall, bool guaranteedNotToCloseFile(const CallEvent &Call) const; public: - SimpleStreamChecker(); - /// Process fopen. void checkPostCall(const CallEvent &Call, CheckerContext &C) const; /// Process fclose. @@ -88,38 +90,12 @@ public: /// state. Let's store it in the ProgramState. REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) -namespace { -class StopTrackingCallback final : public SymbolVisitor { - ProgramStateRef state; -public: - StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {} - ProgramStateRef getState() const { return state; } - - bool VisitSymbol(SymbolRef sym) override { - state = state->remove<StreamMap>(sym); - return true; - } -}; -} // end anonymous namespace - -SimpleStreamChecker::SimpleStreamChecker() - : OpenFn("fopen"), CloseFn("fclose", 1) { - // Initialize the bug types. - DoubleCloseBugType.reset( - new BugType(this, "Double fclose", "Unix Stream API Error")); - - // Sinks are higher importance bugs as well as calls to assert() or exit(0). - LeakBugType.reset( - new BugType(this, "Resource Leak", "Unix Stream API Error", - /*SuppressOnSink=*/true)); -} - void SimpleStreamChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { if (!Call.isGlobalCFunction()) return; - if (!Call.isCalled(OpenFn)) + if (!OpenFn.matches(Call)) return; // Get the symbolic value corresponding to the file handle. @@ -138,7 +114,7 @@ void SimpleStreamChecker::checkPreCall(const CallEvent &Call, if (!Call.isGlobalCFunction()) return; - if (!Call.isCalled(CloseFn)) + if (!CloseFn.matches(Call)) return; // Get the symbolic value corresponding to the file handle. @@ -176,13 +152,11 @@ void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, ProgramStateRef State = C.getState(); SymbolVector LeakedStreams; StreamMapTy TrackedStreams = State->get<StreamMap>(); - for (StreamMapTy::iterator I = TrackedStreams.begin(), - E = TrackedStreams.end(); I != E; ++I) { - SymbolRef Sym = I->first; + for (auto [Sym, StreamStatus] : TrackedStreams) { bool IsSymDead = SymReaper.isDead(Sym); // Collect leaked symbols. - if (isLeaked(Sym, I->second, IsSymDead, State)) + if (isLeaked(Sym, StreamStatus, IsSymDead, State)) LeakedStreams.push_back(Sym); // Remove the dead symbol from the streams map. @@ -207,7 +181,7 @@ void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, // Generate the report. auto R = std::make_unique<PathSensitiveBugReport>( - *DoubleCloseBugType, "Closing a previously closed file stream", ErrNode); + DoubleCloseBugType, "Closing a previously closed file stream", ErrNode); R->addRange(Call.getSourceRange()); R->markInteresting(FileDescSym); C.emitReport(std::move(R)); @@ -220,7 +194,7 @@ void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams, // TODO: Identify the leaked file descriptor. for (SymbolRef LeakedStream : LeakedStreams) { auto R = std::make_unique<PathSensitiveBugReport>( - *LeakBugType, "Opened file is never closed; potential resource leak", + LeakBugType, "Opened file is never closed; potential resource leak", ErrNode); R->markInteresting(LeakedStream); C.emitReport(std::move(R)); @@ -254,11 +228,7 @@ SimpleStreamChecker::checkPointerEscape(ProgramStateRef State, return State; } - for (InvalidatedSymbols::const_iterator I = Escaped.begin(), - E = Escaped.end(); - I != E; ++I) { - SymbolRef Sym = *I; - + for (SymbolRef Sym : Escaped) { // The symbol escaped. Optimistically, assume that the corresponding file // handle will be closed somewhere else. State = State->remove<StreamMap>(Sym); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtr.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtr.h index 6a40f8eda5fa..b4352b450c7f 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtr.h +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtr.h @@ -25,8 +25,6 @@ bool isStdSmartPtrCall(const CallEvent &Call); bool isStdSmartPtr(const CXXRecordDecl *RD); bool isStdSmartPtr(const Expr *E); -bool isStdSmartPtr(const CXXRecordDecl *RD); - /// Returns whether the smart pointer is null or not. bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp index 09e885e8133f..268fc742f050 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp @@ -23,6 +23,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" @@ -30,8 +31,9 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" -#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/Support/ErrorHandling.h" +#include <optional> #include <string> using namespace clang; @@ -47,9 +49,8 @@ class SmartPtrModeling public: // Whether the checker should model for null dereferences of smart pointers. - DefaultBool ModelSmartPtrDereference; + bool ModelSmartPtrDereference = false; bool evalCall(const CallEvent &Call, CheckerContext &C) const; - void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; ProgramStateRef checkRegionChanges(ProgramStateRef State, @@ -70,7 +71,8 @@ private: bool handleMoveCtr(const CallEvent &Call, CheckerContext &C, const MemRegion *ThisRegion) const; bool updateMovedSmartPointers(CheckerContext &C, const MemRegion *ThisRegion, - const MemRegion *OtherSmartPtrRegion) const; + const MemRegion *OtherSmartPtrRegion, + const CallEvent &Call) const; void handleBoolConversion(const CallEvent &Call, CheckerContext &C) const; bool handleComparisionOp(const CallEvent &Call, CheckerContext &C) const; bool handleOstreamOperator(const CallEvent &Call, CheckerContext &C) const; @@ -84,10 +86,10 @@ private: using SmartPtrMethodHandlerFn = void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const; CallDescriptionMap<SmartPtrMethodHandlerFn> SmartPtrMethodHandlers{ - {{"reset"}, &SmartPtrModeling::handleReset}, - {{"release"}, &SmartPtrModeling::handleRelease}, - {{"swap", 1}, &SmartPtrModeling::handleSwapMethod}, - {{"get"}, &SmartPtrModeling::handleGet}}; + {{{"reset"}}, &SmartPtrModeling::handleReset}, + {{{"release"}}, &SmartPtrModeling::handleRelease}, + {{{"swap"}, 1}, &SmartPtrModeling::handleSwapMethod}, + {{{"get"}}, &SmartPtrModeling::handleGet}}; const CallDescription StdSwapCall{{"std", "swap"}, 2}; const CallDescription StdMakeUniqueCall{{"std", "make_unique"}}; const CallDescription StdMakeUniqueForOverwriteCall{ @@ -102,12 +104,8 @@ static bool hasStdClassWithName(const CXXRecordDecl *RD, ArrayRef<llvm::StringLiteral> Names) { if (!RD || !RD->getDeclContext()->isStdNamespace()) return false; - if (RD->getDeclName().isIdentifier()) { - StringRef Name = RD->getName(); - return llvm::any_of(Names, [&Name](StringRef GivenName) -> bool { - return Name == GivenName; - }); - } + if (RD->getDeclName().isIdentifier()) + return llvm::is_contained(Names, RD->getName()); return false; } @@ -203,7 +201,7 @@ static QualType getInnerPointerType(CheckerContext C, const CXXRecordDecl *RD) { static QualType getPointerTypeFromTemplateArg(const CallEvent &Call, CheckerContext &C) { const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); - if (!FD || !FD->isFunctionTemplateSpecialization()) + if (!FD || !FD->getPrimaryTemplate()) return {}; const auto &TemplateArgs = FD->getTemplateSpecializationArgs()->asArray(); if (TemplateArgs.size() == 0) @@ -289,7 +287,7 @@ bool SmartPtrModeling::evalCall(const CallEvent &Call, if (ModelSmartPtrDereference && isStdOstreamOperatorCall(Call)) return handleOstreamOperator(Call, C); - if (Call.isCalled(StdSwapCall)) { + if (StdSwapCall.matches(Call)) { // Check the first arg, if it is of std::unique_ptr type. assert(Call.getNumArgs() == 2 && "std::swap should have two arguments"); const Expr *FirstArg = Call.getArgExpr(0); @@ -298,12 +296,12 @@ bool SmartPtrModeling::evalCall(const CallEvent &Call, return handleSwap(State, Call.getArgSVal(0), Call.getArgSVal(1), C); } - if (Call.isCalled(StdMakeUniqueCall) || - Call.isCalled(StdMakeUniqueForOverwriteCall)) { + if (matchesAny(Call, StdMakeUniqueCall, StdMakeUniqueForOverwriteCall)) { if (!ModelSmartPtrDereference) return false; - - const Optional<SVal> ThisRegionOpt = Call.getReturnValueUnderConstruction(); + + const std::optional<SVal> ThisRegionOpt = + Call.getReturnValueUnderConstruction(); if (!ThisRegionOpt) return false; @@ -383,11 +381,13 @@ bool SmartPtrModeling::evalCall(const CallEvent &Call, if (!ThisRegion) return false; + QualType ThisType = cast<CXXMethodDecl>(Call.getDecl())->getThisType(); + if (CC->getDecl()->isMoveConstructor()) return handleMoveCtr(Call, C, ThisRegion); if (Call.getNumArgs() == 0) { - auto NullVal = C.getSValBuilder().makeNull(); + auto NullVal = C.getSValBuilder().makeNullWithType(ThisType); State = State->set<TrackedRegionMap>(ThisRegion, NullVal); C.addTransition( @@ -588,10 +588,9 @@ void SmartPtrModeling::checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const { // Marking tracked symbols alive TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>(); - for (auto I = TrackedRegions.begin(), E = TrackedRegions.end(); I != E; ++I) { - SVal Val = I->second; - for (auto si = Val.symbol_begin(), se = Val.symbol_end(); si != se; ++si) { - SR.markLive(*si); + for (SVal Val : llvm::make_second_range(TrackedRegions)) { + for (SymbolRef Sym : Val.symbols()) { + SR.markLive(Sym); } } } @@ -644,7 +643,8 @@ void SmartPtrModeling::handleRelease(const CallEvent &Call, *InnerPointVal); } - auto ValueToUpdate = C.getSValBuilder().makeNull(); + QualType ThisType = cast<CXXMethodDecl>(Call.getDecl())->getThisType(); + auto ValueToUpdate = C.getSValBuilder().makeNullWithType(ThisType); State = State->set<TrackedRegionMap>(ThisRegion, ValueToUpdate); C.addTransition(State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR, @@ -742,13 +742,15 @@ bool SmartPtrModeling::handleAssignOp(const CallEvent &Call, if (!ThisRegion) return false; + QualType ThisType = cast<CXXMethodDecl>(Call.getDecl())->getThisType(); + const MemRegion *OtherSmartPtrRegion = OC->getArgSVal(0).getAsRegion(); // In case of 'nullptr' or '0' assigned if (!OtherSmartPtrRegion) { bool AssignedNull = Call.getArgSVal(0).isZeroConstant(); if (!AssignedNull) return false; - auto NullVal = C.getSValBuilder().makeNull(); + auto NullVal = C.getSValBuilder().makeNullWithType(ThisType); State = State->set<TrackedRegionMap>(ThisRegion, NullVal); C.addTransition(State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR, llvm::raw_ostream &OS) { @@ -762,7 +764,7 @@ bool SmartPtrModeling::handleAssignOp(const CallEvent &Call, return true; } - return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion); + return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion, Call); } bool SmartPtrModeling::handleMoveCtr(const CallEvent &Call, CheckerContext &C, @@ -771,17 +773,19 @@ bool SmartPtrModeling::handleMoveCtr(const CallEvent &Call, CheckerContext &C, if (!OtherSmartPtrRegion) return false; - return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion); + return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion, Call); } bool SmartPtrModeling::updateMovedSmartPointers( CheckerContext &C, const MemRegion *ThisRegion, - const MemRegion *OtherSmartPtrRegion) const { + const MemRegion *OtherSmartPtrRegion, const CallEvent &Call) const { ProgramStateRef State = C.getState(); + QualType ThisType = cast<CXXMethodDecl>(Call.getDecl())->getThisType(); const auto *OtherInnerPtr = State->get<TrackedRegionMap>(OtherSmartPtrRegion); if (OtherInnerPtr) { State = State->set<TrackedRegionMap>(ThisRegion, *OtherInnerPtr); - auto NullVal = C.getSValBuilder().makeNull(); + + auto NullVal = C.getSValBuilder().makeNullWithType(ThisType); State = State->set<TrackedRegionMap>(OtherSmartPtrRegion, NullVal); bool IsArgValNull = OtherInnerPtr->isZeroConstant(); @@ -807,7 +811,8 @@ bool SmartPtrModeling::updateMovedSmartPointers( } else { // In case we dont know anything about value we are moving from // remove the entry from map for which smart pointer got moved to. - auto NullVal = C.getSValBuilder().makeNull(); + // For unique_ptr<A>, Ty will be 'A*'. + auto NullVal = C.getSValBuilder().makeNullWithType(ThisType); State = State->remove<TrackedRegionMap>(ThisRegion); State = State->set<TrackedRegionMap>(OtherSmartPtrRegion, NullVal); C.addTransition(State, C.getNoteTag([OtherSmartPtrRegion, @@ -834,6 +839,8 @@ void SmartPtrModeling::handleBoolConversion(const CallEvent &Call, const MemRegion *ThisRegion = cast<CXXInstanceCall>(&Call)->getCXXThisVal().getAsRegion(); + QualType ThisType = cast<CXXMethodDecl>(Call.getDecl())->getThisType(); + SVal InnerPointerVal; if (const auto *InnerValPtr = State->get<TrackedRegionMap>(ThisRegion)) { InnerPointerVal = *InnerValPtr; @@ -872,7 +879,7 @@ void SmartPtrModeling::handleBoolConversion(const CallEvent &Call, std::tie(NotNullState, NullState) = State->assume(InnerPointerVal.castAs<DefinedOrUnknownSVal>()); - auto NullVal = C.getSValBuilder().makeNull(); + auto NullVal = C.getSValBuilder().makeNullWithType(ThisType); // Explicitly tracking the region as null. NullState = NullState->set<TrackedRegionMap>(ThisRegion, NullVal); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp index b5c9356322fc..ea09c43cc5ce 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp @@ -11,9 +11,9 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ExprCXX.h" #include "clang/Basic/SourceManager.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" @@ -29,11 +29,11 @@ namespace { class StackAddrEscapeChecker : public Checker<check::PreCall, check::PreStmt<ReturnStmt>, check::EndFunction> { - mutable IdentifierInfo *dispatch_semaphore_tII; - mutable std::unique_ptr<BuiltinBug> BT_stackleak; - mutable std::unique_ptr<BuiltinBug> BT_returnstack; - mutable std::unique_ptr<BuiltinBug> BT_capturedstackasync; - mutable std::unique_ptr<BuiltinBug> BT_capturedstackret; + mutable IdentifierInfo *dispatch_semaphore_tII = nullptr; + mutable std::unique_ptr<BugType> BT_stackleak; + mutable std::unique_ptr<BugType> BT_returnstack; + mutable std::unique_ptr<BugType> BT_capturedstackasync; + mutable std::unique_ptr<BugType> BT_capturedstackret; public: enum CheckKind { @@ -42,7 +42,7 @@ public: CK_NumCheckKinds }; - DefaultBool ChecksEnabled[CK_NumCheckKinds]; + bool ChecksEnabled[CK_NumCheckKinds] = {false}; CheckerNameRef CheckNames[CK_NumCheckKinds]; void checkPreCall(const CallEvent &Call, CheckerContext &C) const; @@ -61,7 +61,6 @@ private: ASTContext &Ctx); static SmallVector<const MemRegion *, 4> getCapturedStackRegions(const BlockDataRegion &B, CheckerContext &C); - static bool isArcManagedBlock(const MemRegion *R, CheckerContext &C); static bool isNotInCurrentFrame(const MemRegion *R, CheckerContext &C); }; } // namespace @@ -97,6 +96,14 @@ SourceRange StackAddrEscapeChecker::genName(raw_ostream &os, const MemRegion *R, os << "stack memory associated with local variable '" << VR->getString() << '\''; range = VR->getDecl()->getSourceRange(); + } else if (const auto *LER = dyn_cast<CXXLifetimeExtendedObjectRegion>(R)) { + QualType Ty = LER->getValueType().getLocalUnqualifiedType(); + os << "stack memory associated with temporary object of type '"; + Ty.print(os, Ctx.getPrintingPolicy()); + os << "' lifetime extended by local variable"; + if (const IdentifierInfo *ID = LER->getExtendingDecl()->getIdentifier()) + os << " '" << ID->getName() << '\''; + range = LER->getExpr()->getSourceRange(); } else if (const auto *TOR = dyn_cast<CXXTempObjectRegion>(R)) { QualType Ty = TOR->getValueType().getLocalUnqualifiedType(); os << "stack memory associated with temporary object of type '"; @@ -110,13 +117,6 @@ SourceRange StackAddrEscapeChecker::genName(raw_ostream &os, const MemRegion *R, return range; } -bool StackAddrEscapeChecker::isArcManagedBlock(const MemRegion *R, - CheckerContext &C) { - assert(R && "MemRegion should not be null"); - return C.getASTContext().getLangOpts().ObjCAutoRefCount && - isa<BlockDataRegion>(R); -} - bool StackAddrEscapeChecker::isNotInCurrentFrame(const MemRegion *R, CheckerContext &C) { const StackSpaceRegion *S = cast<StackSpaceRegion>(R->getMemorySpace()); @@ -138,10 +138,8 @@ SmallVector<const MemRegion *, 4> StackAddrEscapeChecker::getCapturedStackRegions(const BlockDataRegion &B, CheckerContext &C) { SmallVector<const MemRegion *, 4> Regions; - BlockDataRegion::referenced_vars_iterator I = B.referenced_vars_begin(); - BlockDataRegion::referenced_vars_iterator E = B.referenced_vars_end(); - for (; I != E; ++I) { - SVal Val = C.getState()->getSVal(I.getCapturedRegion()); + for (auto Var : B.referenced_vars()) { + SVal Val = C.getState()->getSVal(Var.getCapturedRegion()); const MemRegion *Region = Val.getAsRegion(); if (Region && isa<StackSpaceRegion>(Region->getMemorySpace())) Regions.push_back(Region); @@ -156,7 +154,7 @@ void StackAddrEscapeChecker::EmitStackError(CheckerContext &C, if (!N) return; if (!BT_returnstack) - BT_returnstack = std::make_unique<BuiltinBug>( + BT_returnstack = std::make_unique<BugType>( CheckNames[CK_StackAddrEscapeChecker], "Return of address to stack-allocated memory"); // Generate a report for this bug. @@ -196,7 +194,7 @@ void StackAddrEscapeChecker::checkAsyncExecutedBlockCaptures( if (!N) continue; if (!BT_capturedstackasync) - BT_capturedstackasync = std::make_unique<BuiltinBug>( + BT_capturedstackasync = std::make_unique<BugType>( CheckNames[CK_StackAddrAsyncEscapeChecker], "Address of stack-allocated memory is captured"); SmallString<128> Buf; @@ -214,13 +212,13 @@ void StackAddrEscapeChecker::checkAsyncExecutedBlockCaptures( void StackAddrEscapeChecker::checkReturnedBlockCaptures( const BlockDataRegion &B, CheckerContext &C) const { for (const MemRegion *Region : getCapturedStackRegions(B, C)) { - if (isArcManagedBlock(Region, C) || isNotInCurrentFrame(Region, C)) + if (isNotInCurrentFrame(Region, C)) continue; ExplodedNode *N = C.generateNonFatalErrorNode(); if (!N) continue; if (!BT_capturedstackret) - BT_capturedstackret = std::make_unique<BuiltinBug>( + BT_capturedstackret = std::make_unique<BugType>( CheckNames[CK_StackAddrEscapeChecker], "Address of stack-allocated memory is captured"); SmallString<128> Buf; @@ -267,8 +265,7 @@ void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS, if (const BlockDataRegion *B = dyn_cast<BlockDataRegion>(R)) checkReturnedBlockCaptures(*B, C); - if (!isa<StackSpaceRegion>(R->getMemorySpace()) || - isNotInCurrentFrame(R, C) || isArcManagedBlock(R, C)) + if (!isa<StackSpaceRegion>(R->getMemorySpace()) || isNotInCurrentFrame(R, C)) return; // Returning a record by value is fine. (In this case, the returned @@ -303,21 +300,52 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, class CallBack : public StoreManager::BindingsHandler { private: CheckerContext &Ctx; - const StackFrameContext *CurSFC; + const StackFrameContext *PoppedFrame; + + /// Look for stack variables referring to popped stack variables. + /// Returns true only if it found some dangling stack variables + /// referred by an other stack variable from different stack frame. + bool checkForDanglingStackVariable(const MemRegion *Referrer, + const MemRegion *Referred) { + const auto *ReferrerMemSpace = + Referrer->getMemorySpace()->getAs<StackSpaceRegion>(); + const auto *ReferredMemSpace = + Referred->getMemorySpace()->getAs<StackSpaceRegion>(); + + if (!ReferrerMemSpace || !ReferredMemSpace) + return false; + + const auto *ReferrerFrame = ReferrerMemSpace->getStackFrame(); + const auto *ReferredFrame = ReferredMemSpace->getStackFrame(); + + if (ReferrerMemSpace && ReferredMemSpace) { + if (ReferredFrame == PoppedFrame && + ReferrerFrame->isParentOf(PoppedFrame)) { + V.emplace_back(Referrer, Referred); + return true; + } + } + return false; + } public: SmallVector<std::pair<const MemRegion *, const MemRegion *>, 10> V; - CallBack(CheckerContext &CC) : Ctx(CC), CurSFC(CC.getStackFrame()) {} + CallBack(CheckerContext &CC) : Ctx(CC), PoppedFrame(CC.getStackFrame()) {} bool HandleBinding(StoreManager &SMgr, Store S, const MemRegion *Region, SVal Val) override { + const MemRegion *VR = Val.getAsRegion(); + if (!VR) + return true; + if (checkForDanglingStackVariable(Region, VR)) + return true; + + // Check the globals for the same. if (!isa<GlobalsSpaceRegion>(Region->getMemorySpace())) return true; - const MemRegion *VR = Val.getAsRegion(); - if (VR && isa<StackSpaceRegion>(VR->getMemorySpace()) && - !isArcManagedBlock(VR, Ctx) && !isNotInCurrentFrame(VR, Ctx)) + if (VR && VR->hasStackStorage() && !isNotInCurrentFrame(VR, Ctx)) V.emplace_back(Region, VR); return true; } @@ -336,27 +364,54 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, return; if (!BT_stackleak) - BT_stackleak = std::make_unique<BuiltinBug>( - 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"); + BT_stackleak = + std::make_unique<BugType>(CheckNames[CK_StackAddrEscapeChecker], + "Stack address stored into global variable"); for (const auto &P : Cb.V) { + const MemRegion *Referrer = P.first->getBaseRegion(); + const MemRegion *Referred = P.second; + // Generate a report for this bug. + const StringRef CommonSuffix = + "upon returning to the caller. This will be a dangling reference"; SmallString<128> Buf; llvm::raw_svector_ostream Out(Buf); - SourceRange Range = genName(Out, P.second, Ctx.getASTContext()); - Out << " is still referred to by the "; - if (isa<StaticGlobalSpaceRegion>(P.first->getMemorySpace())) - Out << "static"; - else - Out << "global"; - Out << " variable '"; - const VarRegion *VR = cast<VarRegion>(P.first->getBaseRegion()); - Out << *VR->getDecl() - << "' upon returning to the caller. This will be a dangling reference"; + const SourceRange Range = genName(Out, Referred, Ctx.getASTContext()); + + if (isa<CXXTempObjectRegion, CXXLifetimeExtendedObjectRegion>(Referrer)) { + Out << " is still referred to by a temporary object on the stack " + << CommonSuffix; + auto Report = + std::make_unique<PathSensitiveBugReport>(*BT_stackleak, Out.str(), N); + if (Range.isValid()) + Report->addRange(Range); + Ctx.emitReport(std::move(Report)); + return; + } + + const StringRef ReferrerMemorySpace = [](const MemSpaceRegion *Space) { + if (isa<StaticGlobalSpaceRegion>(Space)) + return "static"; + if (isa<GlobalsSpaceRegion>(Space)) + return "global"; + assert(isa<StackSpaceRegion>(Space)); + return "stack"; + }(Referrer->getMemorySpace()); + + // We should really only have VarRegions here. + // Anything else is really surprising, and we should get notified if such + // ever happens. + const auto *ReferrerVar = dyn_cast<VarRegion>(Referrer); + if (!ReferrerVar) { + assert(false && "We should have a VarRegion here"); + continue; // Defensively skip this one. + } + const std::string ReferrerVarName = + ReferrerVar->getDecl()->getDeclName().getAsString(); + + Out << " is still referred to by the " << ReferrerMemorySpace + << " variable '" << ReferrerVarName << "' " << CommonSuffix; auto Report = std::make_unique<PathSensitiveBugReport>(*BT_stackleak, Out.str(), N); if (Range.isValid()) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp index e758b465af1b..fcd907a9bb0d 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp @@ -38,17 +38,9 @@ // 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: -// -// fgetc getline isdigit isupper toascii -// fread isalnum isgraph isxdigit -// fwrite isalpha islower read -// getc isascii isprint write -// getchar isblank ispunct toupper -// getdelim iscntrl isspace tolower -// //===----------------------------------------------------------------------===// +#include "ErrnoModeling.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -57,9 +49,12 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/FormatVariadic.h" +#include <optional> #include <string> using namespace clang; @@ -72,58 +67,165 @@ class StdLibraryFunctionsChecker class Summary; /// Specify how much the analyzer engine should entrust modeling this function - /// to us. If he doesn't, he performs additional invalidations. - enum InvalidationKind { NoEvalCall, EvalCallAsPure }; + /// to us. + enum InvalidationKind { + /// No \c eval::Call for the function, it can be modeled elsewhere. + /// This checker checks only pre and post conditions. + NoEvalCall, + /// The function is modeled completely in this checker. + EvalCallAsPure + }; + + /// Given a range, should the argument stay inside or outside this range? + enum RangeKind { OutOfRange, WithinRange }; - // The universal integral type to use in value range descriptions. - // Unsigned to make sure overflows are well-defined. + static RangeKind negateKind(RangeKind K) { + switch (K) { + case OutOfRange: + return WithinRange; + case WithinRange: + return OutOfRange; + } + llvm_unreachable("Unknown range kind"); + } + + /// The universal integral type to use in value range descriptions. + /// Unsigned to make sure overflows are well-defined. 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. + /// Describes a single range constraint. Eg. {{0, 1}, {3, 4}} is + /// a non-negative integer, which less than 5 and not equal to 2. 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 ArgNo; + /// Special argument number for specifying the return value. static const ArgNo Ret; - /// Returns the string representation of an argument index. + /// Get a string representation of an argument index. /// E.g.: (1) -> '1st arg', (2) - > '2nd arg' - static SmallString<8> getArgDesc(ArgNo); + static void printArgDesc(ArgNo, llvm::raw_ostream &Out); + /// Print value X of the argument in form " (which is X)", + /// if the value is a fixed known value, otherwise print nothing. + /// This is used as simple explanation of values if possible. + static void printArgValueInfo(ArgNo ArgN, ProgramStateRef State, + const CallEvent &Call, llvm::raw_ostream &Out); + /// Append textual description of a numeric range [RMin,RMax] to + /// \p Out. + static void appendInsideRangeDesc(llvm::APSInt RMin, llvm::APSInt RMax, + QualType ArgT, BasicValueFactory &BVF, + llvm::raw_ostream &Out); + /// Append textual description of a numeric range out of [RMin,RMax] to + /// \p Out. + static void appendOutOfRangeDesc(llvm::APSInt RMin, llvm::APSInt RMax, + QualType ArgT, BasicValueFactory &BVF, + llvm::raw_ostream &Out); 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. + /// Pointer to the ValueConstraint. We need a copyable, polymorphic and + /// default initializable 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. + /// Mind that a pointer to a new value constraint is created when the negate + /// function is used. 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. + /// These are used as argument constraints (preconditions) of functions, in + /// which case a bug report may be emitted if the constraint is not satisfied. + /// Another use is as conditions for summary cases, to create different + /// classes of behavior for a function. In this case no description of the + /// constraint is needed because the summary cases have an own (not generated) + /// description string. class ValueConstraint { public: 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; + + /// Represents that in which context do we require a description of the + /// constraint. + enum DescriptionKind { + /// Describe a constraint that was violated. + /// Description should start with something like "should be". + Violation, + /// Describe a constraint that was assumed to be true. + /// This can be used when a precondition is satisfied, or when a summary + /// case is applied. + /// Description should start with something like "is". + Assumption + }; + + /// Give a description that explains the constraint to the user. Used when + /// a bug is reported or when the constraint is applied and displayed as a + /// note. The description should not mention the argument (getArgNo). + /// See StdLibraryFunctionsChecker::reportBug about how this function is + /// used (this function is used not only there). + virtual void describe(DescriptionKind DK, const CallEvent &Call, + ProgramStateRef State, const Summary &Summary, + llvm::raw_ostream &Out) const { + // There are some descendant classes that are not used as argument + // constraints, e.g. ComparisonConstraint. In that case we can safely + // ignore the implementation of this function. + llvm_unreachable( + "Description not implemented for summary case constraints"); + } + + /// Give a description that explains the actual argument value (where the + /// current ValueConstraint applies to) to the user. This function should be + /// called only when the current constraint is satisfied by the argument. + /// It should produce a more precise description than the constraint itself. + /// The actual value of the argument and the program state can be used to + /// make the description more precise. In the most simple case, if the + /// argument has a fixed known value this value can be printed into \p Out, + /// this is done by default. + /// The function should return true if a description was printed to \p Out, + /// otherwise false. + /// See StdLibraryFunctionsChecker::reportBug about how this function is + /// used. + virtual bool describeArgumentValue(const CallEvent &Call, + ProgramStateRef State, + const Summary &Summary, + llvm::raw_ostream &Out) const { + if (auto N = getArgSVal(Call, getArgNo()).getAs<NonLoc>()) { + if (const llvm::APSInt *Int = N->getAsInteger()) { + Out << *Int; + return true; + } + } + return false; + } + + /// Return those arguments that should be tracked when we report a bug about + /// argument constraint violation. By default it is the argument that is + /// constrained, however, in some special cases we need to track other + /// arguments as well. E.g. a buffer size might be encoded in another + /// argument. + /// The "return value" argument number can not occur as returned value. + virtual std::vector<ArgNo> getArgsToTrack() const { return {ArgN}; } + + /// Get a constraint that represents exactly the opposite of the current. 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). + /// 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). + /// This condition can indicate if a probably wrong or unexpected function + /// was found where the constraint is to be applied. bool checkValidity(const FunctionDecl *FD) const { const bool ValidArg = ArgN == Ret || ArgN < FD->getNumParams(); assert(ValidArg && "Arg out of range!"); @@ -132,95 +234,75 @@ class StdLibraryFunctionsChecker // Subclasses may further refine the validation. return checkSpecificValidity(FD); } - ArgNo getArgNo() const { return ArgN; } - // Return those arguments that should be tracked when we report a bug. By - // default it is the argument that is constrained, however, in some special - // cases we need to track other arguments as well. E.g. a buffer size might - // be encoded in another argument. - virtual std::vector<ArgNo> getArgsToTrack() const { return {ArgN}; } - - virtual StringRef getName() const = 0; - - // Give a description that explains the constraint to the user. Used when - // the bug is reported. - virtual std::string describe(ProgramStateRef State, - const Summary &Summary) const { - // There are some descendant classes that are not used as argument - // constraints, e.g. ComparisonConstraint. In that case we can safely - // ignore the implementation of this function. - llvm_unreachable("Not implemented"); - } + /// Return the argument number (may be placeholder for "return value"). + ArgNo getArgNo() const { return ArgN; } protected: - ArgNo ArgN; // Argument to which we apply the constraint. - - /// Do polymorphic sanity check on the constraint. + /// Argument to which to apply the constraint. It can be a real argument of + /// the function to check, or a special value to indicate the return value + /// of the function. + /// Every constraint is assigned to one main argument, even if other + /// arguments are involved. + ArgNo ArgN; + + /// Do constraint-specific validation check. 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 range on a single symbol. + /// Check if a single argument falls into a specific "range". + /// A range is formed as a set of intervals. + /// E.g. \code {['A', 'Z'], ['a', 'z'], ['_', '_']} \endcode + /// The intervals are closed intervals that contain one or more values. + /// + /// The default constructed RangeConstraint has an empty range, applying + /// such constraint does not involve any assumptions, thus the State remains + /// unchanged. This is meaningful, if the range is dependent on a looked up + /// type (e.g. [0, Socklen_tMax]). If the type is not found, then the range + /// is default initialized to be empty. class RangeConstraint : public ValueConstraint { + /// The constraint can be specified by allowing or disallowing the range. + /// WithinRange indicates allowing the range, OutOfRange indicates + /// disallowing it (allowing the complementary range). RangeKind Kind; - // A range is formed as a set of intervals (sub-ranges). - // E.g. {['A', 'Z'], ['a', 'z']} - // - // The default constructed RangeConstraint has an empty range set, applying - // such constraint does not involve any assumptions, thus the State remains - // unchanged. This is meaningful, if the range is dependent on a looked up - // type (e.g. [0, Socklen_tMax]). If the type is not found, then the range - // is default initialized to be empty. + + /// A set of intervals. IntRangeVector Ranges; - public: - StringRef getName() const override { return "Range"; } - RangeConstraint(ArgNo ArgN, RangeKind Kind, const IntRangeVector &Ranges) - : ValueConstraint(ArgN), Kind(Kind), Ranges(Ranges) {} + /// A textual description of this constraint for the specific case where the + /// constraint is used. If empty a generated description will be used that + /// is built from the range of the constraint. + StringRef Description; - std::string describe(ProgramStateRef State, - const Summary &Summary) const override; + public: + RangeConstraint(ArgNo ArgN, RangeKind Kind, const IntRangeVector &Ranges, + StringRef Desc = "") + : ValueConstraint(ArgN), Kind(Kind), Ranges(Ranges), Description(Desc) { + } const IntRangeVector &getRanges() const { return Ranges; } - private: - 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 Summary &Summary, - CheckerContext &C) const override { - switch (Kind) { - case OutOfRange: - return applyAsOutOfRange(State, Call, Summary); - case WithinRange: - return applyAsWithinRange(State, Call, Summary); - } - llvm_unreachable("Unknown range kind!"); - } + CheckerContext &C) const override; + + void describe(DescriptionKind DK, const CallEvent &Call, + ProgramStateRef State, const Summary &Summary, + llvm::raw_ostream &Out) const override; + + bool describeArgumentValue(const CallEvent &Call, ProgramStateRef State, + const Summary &Summary, + llvm::raw_ostream &Out) const override; ValueConstraintPtr negate() const override { RangeConstraint Tmp(*this); - switch (Kind) { - case OutOfRange: - Tmp.Kind = WithinRange; - break; - case WithinRange: - Tmp.Kind = OutOfRange; - break; - } + Tmp.Kind = negateKind(Kind); return std::make_shared<RangeConstraint>(Tmp); } + protected: bool checkSpecificValidity(const FunctionDecl *FD) const override { const bool ValidArg = getArgType(FD, ArgN)->isIntegralType(FD->getASTContext()); @@ -228,14 +310,52 @@ class StdLibraryFunctionsChecker "This constraint should be applied on an integral type"); return ValidArg; } + + private: + /// A callback function that is used when iterating over the range + /// intervals. It gets the begin and end (inclusive) of one interval. + /// This is used to make any kind of task possible that needs an iteration + /// over the intervals. + using RangeApplyFunction = + std::function<bool(const llvm::APSInt &Min, const llvm::APSInt &Max)>; + + /// Call a function on the intervals of the range. + /// The function is called with all intervals in the range. + void applyOnWithinRange(BasicValueFactory &BVF, QualType ArgT, + const RangeApplyFunction &F) const; + /// Call a function on all intervals in the complementary range. + /// The function is called with all intervals that fall out of the range. + /// E.g. consider an interval list [A, B] and [C, D] + /// \code + /// -------+--------+------------------+------------+-----------> + /// A B C D + /// \endcode + /// We get the ranges [-inf, A - 1], [D + 1, +inf], [B + 1, C - 1]. + /// The \p ArgT is used to determine the min and max of the type that is + /// used as "-inf" and "+inf". + void applyOnOutOfRange(BasicValueFactory &BVF, QualType ArgT, + const RangeApplyFunction &F) const; + /// Call a function on the intervals of the range or the complementary + /// range. + void applyOnRange(RangeKind Kind, BasicValueFactory &BVF, QualType ArgT, + const RangeApplyFunction &F) const { + switch (Kind) { + case OutOfRange: + applyOnOutOfRange(BVF, ArgT, F); + break; + case WithinRange: + applyOnWithinRange(BVF, ArgT, F); + break; + }; + } }; + /// Check relation of an argument to another. class ComparisonConstraint : public ValueConstraint { BinaryOperator::Opcode Opcode; ArgNo OtherArgN; public: - virtual StringRef getName() const override { return "Comparison"; }; ComparisonConstraint(ArgNo ArgN, BinaryOperator::Opcode Opcode, ArgNo OtherArgN) : ValueConstraint(ArgN), Opcode(Opcode), OtherArgN(OtherArgN) {} @@ -246,28 +366,27 @@ class StdLibraryFunctionsChecker CheckerContext &C) const override; }; + /// Check null or non-null-ness of an argument that is of pointer type. class NotNullConstraint : public ValueConstraint { using ValueConstraint::ValueConstraint; // This variable has a role when we negate the constraint. bool CannotBeNull = true; public: - std::string describe(ProgramStateRef State, - const Summary &Summary) const override; - StringRef getName() const override { return "NonNull"; } + NotNullConstraint(ArgNo ArgN, bool CannotBeNull = true) + : ValueConstraint(ArgN), CannotBeNull(CannotBeNull) {} + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, const Summary &Summary, - CheckerContext &C) const override { - SVal V = getArgSVal(Call, getArgNo()); - if (V.isUndef()) - return State; + CheckerContext &C) const override; - DefinedOrUnknownSVal L = V.castAs<DefinedOrUnknownSVal>(); - if (!L.getAs<Loc>()) - return State; + void describe(DescriptionKind DK, const CallEvent &Call, + ProgramStateRef State, const Summary &Summary, + llvm::raw_ostream &Out) const override; - return State->assume(L, CannotBeNull); - } + bool describeArgumentValue(const CallEvent &Call, ProgramStateRef State, + const Summary &Summary, + llvm::raw_ostream &Out) const override; ValueConstraintPtr negate() const override { NotNullConstraint Tmp(*this); @@ -275,6 +394,54 @@ class StdLibraryFunctionsChecker return std::make_shared<NotNullConstraint>(Tmp); } + protected: + 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; + } + }; + + /// Check null or non-null-ness of an argument that is of pointer type. + /// The argument is meant to be a buffer that has a size constraint, and it + /// is allowed to have a NULL value if the size is 0. The size can depend on + /// 1 or 2 additional arguments, if one of these is 0 the buffer is allowed to + /// be NULL. This is useful for functions like `fread` which have this special + /// property. + class NotNullBufferConstraint : public ValueConstraint { + using ValueConstraint::ValueConstraint; + ArgNo SizeArg1N; + std::optional<ArgNo> SizeArg2N; + // This variable has a role when we negate the constraint. + bool CannotBeNull = true; + + public: + NotNullBufferConstraint(ArgNo ArgN, ArgNo SizeArg1N, + std::optional<ArgNo> SizeArg2N, + bool CannotBeNull = true) + : ValueConstraint(ArgN), SizeArg1N(SizeArg1N), SizeArg2N(SizeArg2N), + CannotBeNull(CannotBeNull) {} + + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override; + + void describe(DescriptionKind DK, const CallEvent &Call, + ProgramStateRef State, const Summary &Summary, + llvm::raw_ostream &Out) const override; + + bool describeArgumentValue(const CallEvent &Call, ProgramStateRef State, + const Summary &Summary, + llvm::raw_ostream &Out) const override; + + ValueConstraintPtr negate() const override { + NotNullBufferConstraint Tmp(*this); + Tmp.CannotBeNull = !this->CannotBeNull; + return std::make_shared<NotNullBufferConstraint>(Tmp); + } + + protected: bool checkSpecificValidity(const FunctionDecl *FD) const override { const bool ValidArg = getArgType(FD, ArgN)->isPointerType(); assert(ValidArg && @@ -295,18 +462,17 @@ class StdLibraryFunctionsChecker // // Here, ptr is the buffer, and its minimum size is `size * nmemb`. class BufferSizeConstraint : public ValueConstraint { // The concrete value which is the minimum size for the buffer. - llvm::Optional<llvm::APSInt> ConcreteSize; + std::optional<llvm::APSInt> ConcreteSize; // The argument which holds the size of the buffer. - llvm::Optional<ArgNo> SizeArgN; + std::optional<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; + std::optional<ArgNo> SizeMultiplierArgN; // The operator we use in apply. This is negated in negate(). BinaryOperator::Opcode Op = BO_LE; public: - StringRef getName() const override { return "BufferSize"; } BufferSizeConstraint(ArgNo Buffer, llvm::APSInt BufMinSize) : ValueConstraint(Buffer), ConcreteSize(BufMinSize) {} BufferSizeConstraint(ArgNo Buffer, ArgNo BufSize) @@ -315,6 +481,18 @@ class StdLibraryFunctionsChecker : ValueConstraint(Buffer), SizeArgN(BufSize), SizeMultiplierArgN(BufSizeMultiplier) {} + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override; + + void describe(DescriptionKind DK, const CallEvent &Call, + ProgramStateRef State, const Summary &Summary, + llvm::raw_ostream &Out) const override; + + bool describeArgumentValue(const CallEvent &Call, ProgramStateRef State, + const Summary &Summary, + llvm::raw_ostream &Out) const override; + std::vector<ArgNo> getArgsToTrack() const override { std::vector<ArgNo> Result{ArgN}; if (SizeArgN) @@ -324,57 +502,13 @@ class StdLibraryFunctionsChecker return Result; } - std::string describe(ProgramStateRef State, - const Summary &Summary) const override; - - 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()); - - // Get the size constraint. - const SVal SizeV = [this, &State, &Call, &Summary, &SvalBuilder]() { - if (ConcreteSize) { - return SVal(SvalBuilder.makeIntVal(*ConcreteSize)); - } - assert(SizeArgN && "The constraint must be either a concrete value or " - "encoded in an argument."); - // 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)); - } - return SizeV; - }(); - - // The dynamic size of the buffer argument, got from the analyzer engine. - SVal BufDynSize = getDynamicExtentWithOffset(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); } + protected: bool checkSpecificValidity(const FunctionDecl *FD) const override { const bool ValidArg = getArgType(FD, ArgN)->isPointerType(); assert(ValidArg && @@ -384,10 +518,162 @@ class StdLibraryFunctionsChecker }; /// The complete list of constraints that defines a single branch. - typedef std::vector<ValueConstraintPtr> ConstraintSet; + using ConstraintSet = std::vector<ValueConstraintPtr>; + + /// Define how a function affects the system variable 'errno'. + /// This works together with the \c ErrnoModeling and \c ErrnoChecker classes. + /// Currently 3 use cases exist: success, failure, irrelevant. + /// In the future the failure case can be customized to set \c errno to a + /// more specific constraint (for example > 0), or new case can be added + /// for functions which require check of \c errno in both success and failure + /// case. + class ErrnoConstraintBase { + public: + /// Apply specific state changes related to the errno variable. + virtual ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const = 0; + /// Get a description about what happens with 'errno' here and how it causes + /// a later bug report created by ErrnoChecker. + /// Empty return value means that 'errno' related bug may not happen from + /// the current analyzed function. + virtual const std::string describe(CheckerContext &C) const { return ""; } + + virtual ~ErrnoConstraintBase() {} + + protected: + ErrnoConstraintBase() = default; + + /// This is used for conjure symbol for errno to differentiate from the + /// original call expression (same expression is used for the errno symbol). + static int Tag; + }; + + /// Reset errno constraints to irrelevant. + /// This is applicable to functions that may change 'errno' and are not + /// modeled elsewhere. + class ResetErrnoConstraint : public ErrnoConstraintBase { + public: + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override { + return errno_modeling::setErrnoState(State, errno_modeling::Irrelevant); + } + }; + + /// Do not change errno constraints. + /// This is applicable to functions that are modeled in another checker + /// and the already set errno constraints should not be changed in the + /// post-call event. + class NoErrnoConstraint : public ErrnoConstraintBase { + public: + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override { + return State; + } + }; + + /// Set errno constraint at failure cases of standard functions. + /// Failure case: 'errno' becomes not equal to 0 and may or may not be checked + /// by the program. \c ErrnoChecker does not emit a bug report after such a + /// function call. + class FailureErrnoConstraint : public ErrnoConstraintBase { + public: + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override { + SValBuilder &SVB = C.getSValBuilder(); + NonLoc ErrnoSVal = + SVB.conjureSymbolVal(&Tag, Call.getOriginExpr(), + C.getLocationContext(), C.getASTContext().IntTy, + C.blockCount()) + .castAs<NonLoc>(); + return errno_modeling::setErrnoForStdFailure(State, C, ErrnoSVal); + } + }; + + /// Set errno constraint at success cases of standard functions. + /// Success case: 'errno' is not allowed to be used because the value is + /// undefined after successful call. + /// \c ErrnoChecker can emit bug report after such a function call if errno + /// is used. + class SuccessErrnoConstraint : public ErrnoConstraintBase { + public: + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override { + return errno_modeling::setErrnoForStdSuccess(State, C); + } + + const std::string describe(CheckerContext &C) const override { + return "'errno' becomes undefined after the call"; + } + }; + + /// Set errno constraint at functions that indicate failure only with 'errno'. + /// In this case 'errno' is required to be observed. + /// \c ErrnoChecker can emit bug report after such a function call if errno + /// is overwritten without a read before. + class ErrnoMustBeCheckedConstraint : public ErrnoConstraintBase { + public: + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override { + return errno_modeling::setErrnoStdMustBeChecked(State, C, + Call.getOriginExpr()); + } + + const std::string describe(CheckerContext &C) const override { + return "reading 'errno' is required to find out if the call has failed"; + } + }; + + /// A single branch of a function summary. + /// + /// A branch is defined by a series of constraints - "assumptions" - + /// that together form a single possible outcome of invoking the function. + /// When static analyzer considers a branch, it tries to introduce + /// a child node in the Exploded Graph. The child node has to include + /// constraints that define the branch. If the constraints contradict + /// existing constraints in the state, the node is not created and the branch + /// is dropped; otherwise it's queued for future exploration. + /// The branch is accompanied by a note text that may be displayed + /// to the user when a bug is found on a path that takes this branch. + /// + /// 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]) + /// and the note may say "Assuming the character is alphabetical" + /// Branch 2) + /// x is out-of-range ['A', 'Z'] and out-of-range ['a', 'z'] + /// then the return value is 0 + /// and the note may say "Assuming the character is non-alphabetical". + class SummaryCase { + ConstraintSet Constraints; + const ErrnoConstraintBase &ErrnoConstraint; + StringRef Note; + + public: + SummaryCase(ConstraintSet &&Constraints, const ErrnoConstraintBase &ErrnoC, + StringRef Note) + : Constraints(std::move(Constraints)), ErrnoConstraint(ErrnoC), + Note(Note) {} + + SummaryCase(const ConstraintSet &Constraints, + const ErrnoConstraintBase &ErrnoC, StringRef Note) + : Constraints(Constraints), ErrnoConstraint(ErrnoC), Note(Note) {} + + const ConstraintSet &getConstraints() const { return Constraints; } + const ErrnoConstraintBase &getErrnoConstraint() const { + return ErrnoConstraint; + } + StringRef getNote() const { return Note; } + }; - using ArgTypes = std::vector<Optional<QualType>>; - using RetType = Optional<QualType>; + using ArgTypes = std::vector<std::optional<QualType>>; + using RetType = std::optional<QualType>; // A placeholder type, we use it whenever we do not care about the concrete // type in a Signature. @@ -409,7 +695,7 @@ class StdLibraryFunctionsChecker // Construct a signature from optional types. If any of the optional types // are not set then the signature will be invalid. Signature(ArgTypes ArgTys, RetType RetTy) { - for (Optional<QualType> Arg : ArgTys) { + for (std::optional<QualType> Arg : ArgTys) { if (!Arg) { Invalid = true; return; @@ -451,23 +737,12 @@ class StdLibraryFunctionsChecker return T; } - using Cases = std::vector<ConstraintSet>; + using SummaryCases = std::vector<SummaryCase>; /// 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 branches - so, a list of list of ranges, /// * 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. @@ -482,7 +757,7 @@ class StdLibraryFunctionsChecker /// signature is matched. class Summary { const InvalidationKind InvalidationKd; - Cases CaseConstraints; + SummaryCases Cases; ConstraintSet ArgConstraints; // The function to which the summary applies. This is set after lookup and @@ -492,12 +767,14 @@ class StdLibraryFunctionsChecker public: Summary(InvalidationKind InvalidationKd) : InvalidationKd(InvalidationKd) {} - Summary &Case(ConstraintSet &&CS) { - CaseConstraints.push_back(std::move(CS)); + Summary &Case(ConstraintSet &&CS, const ErrnoConstraintBase &ErrnoC, + StringRef Note = "") { + Cases.push_back(SummaryCase(std::move(CS), ErrnoC, Note)); return *this; } - Summary &Case(const ConstraintSet &CS) { - CaseConstraints.push_back(CS); + Summary &Case(const ConstraintSet &CS, const ErrnoConstraintBase &ErrnoC, + StringRef Note = "") { + Cases.push_back(SummaryCase(CS, ErrnoC, Note)); return *this; } Summary &ArgConstraint(ValueConstraintPtr VC) { @@ -508,7 +785,7 @@ class StdLibraryFunctionsChecker } InvalidationKind getInvalidationKd() const { return InvalidationKd; } - const Cases &getCaseConstraints() const { return CaseConstraints; } + const SummaryCases &getCases() const { return Cases; } const ConstraintSet &getArgConstraints() const { return ArgConstraints; } QualType getArgType(ArgNo ArgN) const { @@ -527,11 +804,11 @@ class StdLibraryFunctionsChecker } private: - // Once we know the exact type of the function then do sanity check on all - // the given constraints. + // Once we know the exact type of the function then do validation check on + // all the given constraints. bool validateByConstraints(const FunctionDecl *FD) const { - for (const ConstraintSet &Case : CaseConstraints) - for (const ValueConstraintPtr &Constraint : Case) + for (const SummaryCase &Case : Cases) + for (const ValueConstraintPtr &Constraint : Case.getConstraints()) if (!Constraint->checkValidity(FD)) return false; for (const ValueConstraintPtr &Constraint : ArgConstraints) @@ -546,236 +823,310 @@ class StdLibraryFunctionsChecker using FunctionSummaryMapType = llvm::DenseMap<const FunctionDecl *, Summary>; mutable FunctionSummaryMapType FunctionSummaryMap; - mutable std::unique_ptr<BugType> BT_InvalidArg; + const BugType BT_InvalidArg{this, "Function call with invalid argument"}; mutable bool SummariesInitialized = false; static SVal getArgSVal(const CallEvent &Call, ArgNo ArgN) { return ArgN == Ret ? Call.getReturnValue() : Call.getArgSVal(ArgN); } + static std::string getFunctionName(const CallEvent &Call) { + assert(Call.getDecl() && + "Call was found by a summary, should have declaration"); + return cast<NamedDecl>(Call.getDecl())->getNameAsString(); + } 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; - enum CheckKind { - CK_StdCLibraryFunctionArgsChecker, - CK_StdCLibraryFunctionsTesterChecker, - CK_NumCheckKinds - }; - DefaultBool ChecksEnabled[CK_NumCheckKinds]; - CheckerNameRef CheckNames[CK_NumCheckKinds]; + CheckerNameRef CheckName; + bool AddTestFunctions = false; bool DisplayLoadedSummaries = false; bool ModelPOSIX = false; + bool ShouldAssumeControlledEnvironment = false; private: - Optional<Summary> findFunctionSummary(const FunctionDecl *FD, - CheckerContext &C) const; - Optional<Summary> findFunctionSummary(const CallEvent &Call, - CheckerContext &C) const; + std::optional<Summary> findFunctionSummary(const FunctionDecl *FD, + CheckerContext &C) const; + std::optional<Summary> findFunctionSummary(const CallEvent &Call, + CheckerContext &C) const; void initFunctionSummaries(CheckerContext &C) const; void reportBug(const CallEvent &Call, ExplodedNode *N, - const ValueConstraint *VC, const Summary &Summary, - CheckerContext &C) const { - if (!ChecksEnabled[CK_StdCLibraryFunctionArgsChecker]) - return; - std::string Msg = - (Twine("Function argument constraint is not satisfied, constraint: ") + - VC->getName().data()) - .str(); - 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); - - for (ArgNo ArgN : VC->getArgsToTrack()) + const ValueConstraint *VC, const ValueConstraint *NegatedVC, + const Summary &Summary, CheckerContext &C) const { + assert(Call.getDecl() && + "Function found in summary must have a declaration available"); + SmallString<256> Msg; + llvm::raw_svector_ostream MsgOs(Msg); + + MsgOs << "The "; + printArgDesc(VC->getArgNo(), MsgOs); + MsgOs << " to '" << getFunctionName(Call) << "' "; + bool ValuesPrinted = + NegatedVC->describeArgumentValue(Call, N->getState(), Summary, MsgOs); + if (ValuesPrinted) + MsgOs << " but "; + else + MsgOs << "is out of the accepted range; It "; + VC->describe(ValueConstraint::Violation, Call, C.getState(), Summary, + MsgOs); + Msg[0] = toupper(Msg[0]); + auto R = std::make_unique<PathSensitiveBugReport>(BT_InvalidArg, Msg, N); + + for (ArgNo ArgN : VC->getArgsToTrack()) { bugreporter::trackExpressionValue(N, Call.getArgExpr(ArgN), *R); - - // Highlight the range of the argument that was violated. - R->addRange(Call.getArgSourceRange(VC->getArgNo())); - - // Describe the argument constraint in a note. - R->addNote(VC->describe(C.getState(), Summary), R->getLocation(), - Call.getArgSourceRange(VC->getArgNo())); + R->markInteresting(Call.getArgSVal(ArgN)); + // All tracked arguments are important, highlight them. + R->addRange(Call.getArgSourceRange(ArgN)); + } C.emitReport(std::move(R)); } + + /// These are the errno constraints that can be passed to summary cases. + /// One of these should fit for a single summary case. + /// Usually if a failure return value exists for function, that function + /// needs different cases for success and failure with different errno + /// constraints (and different return value constraints). + const NoErrnoConstraint ErrnoUnchanged{}; + const ResetErrnoConstraint ErrnoIrrelevant{}; + const ErrnoMustBeCheckedConstraint ErrnoMustBeChecked{}; + const SuccessErrnoConstraint ErrnoMustNotBeChecked{}; + const FailureErrnoConstraint ErrnoNEZeroIrrelevant{}; }; +int StdLibraryFunctionsChecker::ErrnoConstraintBase::Tag = 0; + const StdLibraryFunctionsChecker::ArgNo StdLibraryFunctionsChecker::Ret = std::numeric_limits<ArgNo>::max(); -} // end of anonymous namespace - static BasicValueFactory &getBVF(ProgramStateRef State) { ProgramStateManager &Mgr = State->getStateManager(); SValBuilder &SVB = Mgr.getSValBuilder(); return SVB.getBasicValueFactory(); } -std::string StdLibraryFunctionsChecker::NotNullConstraint::describe( - ProgramStateRef State, const Summary &Summary) const { - SmallString<48> Result; - Result += "The "; - Result += getArgDesc(ArgN); - Result += " should not be NULL"; - return Result.c_str(); -} +} // end of anonymous namespace -std::string StdLibraryFunctionsChecker::RangeConstraint::describe( - ProgramStateRef State, const Summary &Summary) const { +void StdLibraryFunctionsChecker::printArgDesc( + StdLibraryFunctionsChecker::ArgNo ArgN, llvm::raw_ostream &Out) { + Out << std::to_string(ArgN + 1); + Out << llvm::getOrdinalSuffix(ArgN + 1); + Out << " argument"; +} - BasicValueFactory &BVF = getBVF(State); +void StdLibraryFunctionsChecker::printArgValueInfo(ArgNo ArgN, + ProgramStateRef State, + const CallEvent &Call, + llvm::raw_ostream &Out) { + if (const llvm::APSInt *Val = + State->getStateManager().getSValBuilder().getKnownValue( + State, getArgSVal(Call, ArgN))) + Out << " (which is " << *Val << ")"; +} - QualType T = Summary.getArgType(getArgNo()); - SmallString<48> Result; - Result += "The "; - Result += getArgDesc(ArgN); - Result += " should be "; - - // Range kind as a string. - Kind == OutOfRange ? Result += "out of" : Result += "within"; - - // Get the range values as a string. - Result += " the range "; - if (Ranges.size() > 1) - Result += "["; - unsigned I = Ranges.size(); - for (const std::pair<RangeInt, RangeInt> &R : Ranges) { - Result += "["; - const llvm::APSInt &Min = BVF.getValue(R.first, T); - const llvm::APSInt &Max = BVF.getValue(R.second, T); - Min.toString(Result); - Result += ", "; - Max.toString(Result); - Result += "]"; - if (--I > 0) - Result += ", "; +void StdLibraryFunctionsChecker::appendInsideRangeDesc(llvm::APSInt RMin, + llvm::APSInt RMax, + QualType ArgT, + BasicValueFactory &BVF, + llvm::raw_ostream &Out) { + if (RMin.isZero() && RMax.isZero()) + Out << "zero"; + else if (RMin == RMax) + Out << RMin; + else if (RMin == BVF.getMinValue(ArgT)) { + if (RMax == -1) + Out << "< 0"; + else + Out << "<= " << RMax; + } else if (RMax == BVF.getMaxValue(ArgT)) { + if (RMin.isOne()) + Out << "> 0"; + else + Out << ">= " << RMin; + } else if (RMin.isNegative() == RMax.isNegative() && + RMin.getLimitedValue() == RMax.getLimitedValue() - 1) { + Out << RMin << " or " << RMax; + } else { + Out << "between " << RMin << " and " << RMax; } - if (Ranges.size() > 1) - Result += "]"; - - return Result.c_str(); } -SmallString<8> -StdLibraryFunctionsChecker::getArgDesc(StdLibraryFunctionsChecker::ArgNo ArgN) { - SmallString<8> Result; - Result += std::to_string(ArgN + 1); - Result += llvm::getOrdinalSuffix(ArgN + 1); - Result += " arg"; - return Result; +void StdLibraryFunctionsChecker::appendOutOfRangeDesc(llvm::APSInt RMin, + llvm::APSInt RMax, + QualType ArgT, + BasicValueFactory &BVF, + llvm::raw_ostream &Out) { + if (RMin.isZero() && RMax.isZero()) + Out << "nonzero"; + else if (RMin == RMax) { + Out << "not equal to " << RMin; + } else if (RMin == BVF.getMinValue(ArgT)) { + if (RMax == -1) + Out << ">= 0"; + else + Out << "> " << RMax; + } else if (RMax == BVF.getMaxValue(ArgT)) { + if (RMin.isOne()) + Out << "<= 0"; + else + Out << "< " << RMin; + } else if (RMin.isNegative() == RMax.isNegative() && + RMin.getLimitedValue() == RMax.getLimitedValue() - 1) { + Out << "not " << RMin << " and not " << RMax; + } else { + Out << "not between " << RMin << " and " << RMax; + } } -std::string StdLibraryFunctionsChecker::BufferSizeConstraint::describe( - ProgramStateRef State, const Summary &Summary) const { - SmallString<96> Result; - Result += "The size of the "; - Result += getArgDesc(ArgN); - Result += " should be equal to or less than the value of "; - if (ConcreteSize) { - ConcreteSize->toString(Result); - } else if (SizeArgN) { - Result += "the "; - Result += getArgDesc(*SizeArgN); - if (SizeMultiplierArgN) { - Result += " times the "; - Result += getArgDesc(*SizeMultiplierArgN); - } +void StdLibraryFunctionsChecker::RangeConstraint::applyOnWithinRange( + BasicValueFactory &BVF, QualType ArgT, const RangeApplyFunction &F) const { + if (Ranges.empty()) + return; + + for (auto [Start, End] : getRanges()) { + const llvm::APSInt &Min = BVF.getValue(Start, ArgT); + const llvm::APSInt &Max = BVF.getValue(End, ArgT); + assert(Min <= Max); + if (!F(Min, Max)) + return; } - return Result.c_str(); } -ProgramStateRef StdLibraryFunctionsChecker::RangeConstraint::applyAsOutOfRange( - ProgramStateRef State, const CallEvent &Call, - const Summary &Summary) const { +void StdLibraryFunctionsChecker::RangeConstraint::applyOnOutOfRange( + BasicValueFactory &BVF, QualType ArgT, const RangeApplyFunction &F) const { if (Ranges.empty()) - return State; + return; - ProgramStateManager &Mgr = State->getStateManager(); - SValBuilder &SVB = Mgr.getSValBuilder(); - BasicValueFactory &BVF = SVB.getBasicValueFactory(); - ConstraintManager &CM = Mgr.getConstraintManager(); - QualType T = Summary.getArgType(getArgNo()); + const IntRangeVector &R = getRanges(); + size_t E = R.size(); + + const llvm::APSInt &MinusInf = BVF.getMinValue(ArgT); + const llvm::APSInt &PlusInf = BVF.getMaxValue(ArgT); + + const llvm::APSInt &RangeLeft = BVF.getValue(R[0].first - 1ULL, ArgT); + const llvm::APSInt &RangeRight = BVF.getValue(R[E - 1].second + 1ULL, ArgT); + + // Iterate over the "holes" between intervals. + for (size_t I = 1; I != E; ++I) { + const llvm::APSInt &Min = BVF.getValue(R[I - 1].second + 1ULL, ArgT); + const llvm::APSInt &Max = BVF.getValue(R[I].first - 1ULL, ArgT); + if (Min <= Max) { + if (!F(Min, Max)) + return; + } + } + // Check the interval [T_MIN, min(R) - 1]. + if (RangeLeft != PlusInf) { + assert(MinusInf <= RangeLeft); + if (!F(MinusInf, RangeLeft)) + return; + } + // Check the interval [max(R) + 1, T_MAX], + if (RangeRight != MinusInf) { + assert(RangeRight <= PlusInf); + if (!F(RangeRight, PlusInf)) + return; + } +} + +ProgramStateRef StdLibraryFunctionsChecker::RangeConstraint::apply( + ProgramStateRef State, const CallEvent &Call, const Summary &Summary, + CheckerContext &C) const { + ConstraintManager &CM = C.getConstraintManager(); SVal V = getArgSVal(Call, getArgNo()); + QualType T = Summary.getArgType(getArgNo()); if (auto N = V.getAs<NonLoc>()) { - 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); - const llvm::APSInt &Max = BVF.getValue(R[I].second, T); - assert(Min <= Max); + auto ExcludeRangeFromArg = [&](const llvm::APSInt &Min, + const llvm::APSInt &Max) { State = CM.assumeInclusiveRange(State, *N, Min, Max, false); - if (!State) - break; - } + return static_cast<bool>(State); + }; + // "OutOfRange R" is handled by excluding all ranges in R. + // "WithinRange R" is treated as "OutOfRange [T_MIN, T_MAX] \ R". + applyOnRange(negateKind(Kind), C.getSValBuilder().getBasicValueFactory(), T, + ExcludeRangeFromArg); } return State; } -ProgramStateRef StdLibraryFunctionsChecker::RangeConstraint::applyAsWithinRange( - ProgramStateRef State, const CallEvent &Call, - const Summary &Summary) const { - if (Ranges.empty()) - return State; +void StdLibraryFunctionsChecker::RangeConstraint::describe( + DescriptionKind DK, const CallEvent &Call, ProgramStateRef State, + const Summary &Summary, llvm::raw_ostream &Out) const { + + BasicValueFactory &BVF = getBVF(State); + QualType T = Summary.getArgType(getArgNo()); + + Out << ((DK == Violation) ? "should be " : "is "); + if (!Description.empty()) { + Out << Description; + } else { + unsigned I = Ranges.size(); + if (Kind == WithinRange) { + for (const std::pair<RangeInt, RangeInt> &R : Ranges) { + appendInsideRangeDesc(BVF.getValue(R.first, T), + BVF.getValue(R.second, T), T, BVF, Out); + if (--I > 0) + Out << " or "; + } + } else { + for (const std::pair<RangeInt, RangeInt> &R : Ranges) { + appendOutOfRangeDesc(BVF.getValue(R.first, T), + BVF.getValue(R.second, T), T, BVF, Out); + if (--I > 0) + Out << " and "; + } + } + } +} + +bool StdLibraryFunctionsChecker::RangeConstraint::describeArgumentValue( + const CallEvent &Call, ProgramStateRef State, const Summary &Summary, + llvm::raw_ostream &Out) const { + unsigned int NRanges = 0; + bool HaveAllRanges = true; ProgramStateManager &Mgr = State->getStateManager(); - SValBuilder &SVB = Mgr.getSValBuilder(); - BasicValueFactory &BVF = SVB.getBasicValueFactory(); + BasicValueFactory &BVF = Mgr.getSValBuilder().getBasicValueFactory(); ConstraintManager &CM = Mgr.getConstraintManager(); - 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 IntRangeVector &R = getRanges(); - size_t E = R.size(); - - const llvm::APSInt &MinusInf = BVF.getMinValue(T); - const llvm::APSInt &PlusInf = BVF.getMaxValue(T); - - const llvm::APSInt &Left = BVF.getValue(R[0].first - 1ULL, T); - if (Left != PlusInf) { - assert(MinusInf <= Left); - State = CM.assumeInclusiveRange(State, *N, MinusInf, Left, false); - if (!State) - return nullptr; - } - - const llvm::APSInt &Right = BVF.getValue(R[E - 1].second + 1ULL, T); - if (Right != MinusInf) { - assert(Right <= PlusInf); - State = CM.assumeInclusiveRange(State, *N, Right, PlusInf, false); - if (!State) - return nullptr; + if (const llvm::APSInt *Int = N->getAsInteger()) { + Out << "is "; + Out << *Int; + return true; } - - 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); - if (Min <= Max) { - State = CM.assumeInclusiveRange(State, *N, Min, Max, false); - if (!State) - return nullptr; + QualType T = Summary.getArgType(getArgNo()); + SmallString<128> MoreInfo; + llvm::raw_svector_ostream MoreInfoOs(MoreInfo); + auto ApplyF = [&](const llvm::APSInt &Min, const llvm::APSInt &Max) { + if (CM.assumeInclusiveRange(State, *N, Min, Max, true)) { + if (NRanges > 0) + MoreInfoOs << " or "; + appendInsideRangeDesc(Min, Max, T, BVF, MoreInfoOs); + ++NRanges; + } else { + HaveAllRanges = false; } + return true; + }; + + applyOnRange(Kind, BVF, T, ApplyF); + assert(NRanges > 0); + if (!HaveAllRanges || NRanges == 1) { + Out << "is "; + Out << MoreInfo; + return true; } } - - return State; + return false; } ProgramStateRef StdLibraryFunctionsChecker::ComparisonConstraint::apply( @@ -800,9 +1151,165 @@ ProgramStateRef StdLibraryFunctionsChecker::ComparisonConstraint::apply( return State; } +ProgramStateRef StdLibraryFunctionsChecker::NotNullConstraint::apply( + ProgramStateRef State, const CallEvent &Call, const Summary &Summary, + CheckerContext &C) const { + SVal V = getArgSVal(Call, getArgNo()); + if (V.isUndef()) + return State; + + DefinedOrUnknownSVal L = V.castAs<DefinedOrUnknownSVal>(); + if (!isa<Loc>(L)) + return State; + + return State->assume(L, CannotBeNull); +} + +void StdLibraryFunctionsChecker::NotNullConstraint::describe( + DescriptionKind DK, const CallEvent &Call, ProgramStateRef State, + const Summary &Summary, llvm::raw_ostream &Out) const { + assert(CannotBeNull && + "Describe should not be used when the value must be NULL"); + if (DK == Violation) + Out << "should not be NULL"; + else + Out << "is not NULL"; +} + +bool StdLibraryFunctionsChecker::NotNullConstraint::describeArgumentValue( + const CallEvent &Call, ProgramStateRef State, const Summary &Summary, + llvm::raw_ostream &Out) const { + assert(!CannotBeNull && "This function is used when the value is NULL"); + Out << "is NULL"; + return true; +} + +ProgramStateRef StdLibraryFunctionsChecker::NotNullBufferConstraint::apply( + ProgramStateRef State, const CallEvent &Call, const Summary &Summary, + CheckerContext &C) const { + SVal V = getArgSVal(Call, getArgNo()); + if (V.isUndef()) + return State; + DefinedOrUnknownSVal L = V.castAs<DefinedOrUnknownSVal>(); + if (!isa<Loc>(L)) + return State; + + std::optional<DefinedOrUnknownSVal> SizeArg1 = + getArgSVal(Call, SizeArg1N).getAs<DefinedOrUnknownSVal>(); + std::optional<DefinedOrUnknownSVal> SizeArg2; + if (SizeArg2N) + SizeArg2 = getArgSVal(Call, *SizeArg2N).getAs<DefinedOrUnknownSVal>(); + + auto IsArgZero = [State](std::optional<DefinedOrUnknownSVal> Val) { + if (!Val) + return false; + auto [IsNonNull, IsNull] = State->assume(*Val); + return IsNull && !IsNonNull; + }; + + if (IsArgZero(SizeArg1) || IsArgZero(SizeArg2)) + return State; + + return State->assume(L, CannotBeNull); +} + +void StdLibraryFunctionsChecker::NotNullBufferConstraint::describe( + DescriptionKind DK, const CallEvent &Call, ProgramStateRef State, + const Summary &Summary, llvm::raw_ostream &Out) const { + assert(CannotBeNull && + "Describe should not be used when the value must be NULL"); + if (DK == Violation) + Out << "should not be NULL"; + else + Out << "is not NULL"; +} + +bool StdLibraryFunctionsChecker::NotNullBufferConstraint::describeArgumentValue( + const CallEvent &Call, ProgramStateRef State, const Summary &Summary, + llvm::raw_ostream &Out) const { + assert(!CannotBeNull && "This function is used when the value is NULL"); + Out << "is NULL"; + return true; +} + +ProgramStateRef StdLibraryFunctionsChecker::BufferSizeConstraint::apply( + ProgramStateRef State, const CallEvent &Call, const Summary &Summary, + CheckerContext &C) const { + SValBuilder &SvalBuilder = C.getSValBuilder(); + // The buffer argument. + SVal BufV = getArgSVal(Call, getArgNo()); + + // Get the size constraint. + const SVal SizeV = [this, &State, &Call, &Summary, &SvalBuilder]() { + if (ConcreteSize) { + return SVal(SvalBuilder.makeIntVal(*ConcreteSize)); + } + assert(SizeArgN && "The constraint must be either a concrete value or " + "encoded in an argument."); + // 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)); + } + return SizeV; + }(); + + // The dynamic size of the buffer argument, got from the analyzer engine. + SVal BufDynSize = getDynamicExtentWithOffset(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"); +} + +void StdLibraryFunctionsChecker::BufferSizeConstraint::describe( + DescriptionKind DK, const CallEvent &Call, ProgramStateRef State, + const Summary &Summary, llvm::raw_ostream &Out) const { + Out << ((DK == Violation) ? "should be " : "is "); + Out << "a buffer with size equal to or greater than "; + if (ConcreteSize) { + Out << *ConcreteSize; + } else if (SizeArgN) { + Out << "the value of the "; + printArgDesc(*SizeArgN, Out); + printArgValueInfo(*SizeArgN, State, Call, Out); + if (SizeMultiplierArgN) { + Out << " times the "; + printArgDesc(*SizeMultiplierArgN, Out); + printArgValueInfo(*SizeMultiplierArgN, State, Call, Out); + } + } +} + +bool StdLibraryFunctionsChecker::BufferSizeConstraint::describeArgumentValue( + const CallEvent &Call, ProgramStateRef State, const Summary &Summary, + llvm::raw_ostream &Out) const { + SVal BufV = getArgSVal(Call, getArgNo()); + SVal BufDynSize = getDynamicExtentWithOffset(State, BufV); + if (const llvm::APSInt *Val = + State->getStateManager().getSValBuilder().getKnownValue(State, + BufDynSize)) { + Out << "is a buffer with size " << *Val; + return true; + } + return false; +} + void StdLibraryFunctionsChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { - Optional<Summary> FoundSummary = findFunctionSummary(Call, C); + std::optional<Summary> FoundSummary = findFunctionSummary(Call, C); if (!FoundSummary) return; @@ -810,55 +1317,155 @@ void StdLibraryFunctionsChecker::checkPreCall(const CallEvent &Call, ProgramStateRef State = C.getState(); ProgramStateRef NewState = State; + ExplodedNode *NewNode = C.getPredecessor(); for (const ValueConstraintPtr &Constraint : Summary.getArgConstraints()) { + ValueConstraintPtr NegatedConstraint = Constraint->negate(); ProgramStateRef SuccessSt = Constraint->apply(NewState, Call, Summary, C); ProgramStateRef FailureSt = - Constraint->negate()->apply(NewState, Call, Summary, C); + NegatedConstraint->apply(NewState, Call, Summary, C); // The argument constraint is not satisfied. if (FailureSt && !SuccessSt) { - if (ExplodedNode *N = C.generateErrorNode(NewState)) - reportBug(Call, N, Constraint.get(), Summary, C); + if (ExplodedNode *N = C.generateErrorNode(State, NewNode)) + reportBug(Call, N, Constraint.get(), NegatedConstraint.get(), Summary, + 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; + } + // 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 != State) { + SmallString<128> Msg; + llvm::raw_svector_ostream Os(Msg); + Os << "Assuming that the "; + printArgDesc(Constraint->getArgNo(), Os); + Os << " to '"; + Os << getFunctionName(Call); + Os << "' "; + Constraint->describe(ValueConstraint::Assumption, Call, NewState, Summary, + Os); + const auto ArgSVal = Call.getArgSVal(Constraint->getArgNo()); + NewNode = C.addTransition( + NewState, NewNode, + C.getNoteTag([Msg = std::move(Msg), ArgSVal]( + PathSensitiveBugReport &BR, llvm::raw_ostream &OS) { + if (BR.isInteresting(ArgSVal)) + OS << Msg; + })); } } - if (NewState && NewState != State) - C.addTransition(NewState); } void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { - Optional<Summary> FoundSummary = findFunctionSummary(Call, C); + std::optional<Summary> FoundSummary = findFunctionSummary(Call, C); if (!FoundSummary) return; // Now apply the constraints. const Summary &Summary = *FoundSummary; ProgramStateRef State = C.getState(); + ExplodedNode *Node = C.getPredecessor(); // Apply case/branch specifications. - for (const ConstraintSet &Case : Summary.getCaseConstraints()) { + for (const SummaryCase &Case : Summary.getCases()) { ProgramStateRef NewState = State; - for (const ValueConstraintPtr &Constraint : Case) { + for (const ValueConstraintPtr &Constraint : Case.getConstraints()) { NewState = Constraint->apply(NewState, Call, Summary, C); if (!NewState) break; } - if (NewState && NewState != State) + if (NewState) + NewState = Case.getErrnoConstraint().apply(NewState, Call, Summary, C); + + if (!NewState) + continue; + + // Here it's possible that NewState == State, e.g. when other checkers + // already applied the same constraints (or stricter ones). + // Still add these note tags, the other checker should add only its + // specialized note tags. These general note tags are handled always by + // StdLibraryFunctionsChecker. + + ExplodedNode *Pred = Node; + DeclarationName FunctionName = + cast<NamedDecl>(Call.getDecl())->getDeclName(); + + std::string ErrnoNote = Case.getErrnoConstraint().describe(C); + std::string CaseNote; + if (Case.getNote().empty()) { + if (!ErrnoNote.empty()) + ErrnoNote = + llvm::formatv("After calling '{0}' {1}", FunctionName, ErrnoNote); + } else { + CaseNote = llvm::formatv(Case.getNote().str().c_str(), FunctionName); + } + const SVal RV = Call.getReturnValue(); + + if (Summary.getInvalidationKd() == EvalCallAsPure) { + // Do not expect that errno is interesting (the "pure" functions do not + // affect it). + if (!CaseNote.empty()) { + const NoteTag *Tag = C.getNoteTag( + [Node, CaseNote, RV](PathSensitiveBugReport &BR) -> std::string { + // Try to omit the note if we know in advance which branch is + // taken (this means, only one branch exists). + // This check is performed inside the lambda, after other + // (or this) checkers had a chance to add other successors. + // Dereferencing the saved node object is valid because it's part + // of a bug report call sequence. + // FIXME: This check is not exact. We may be here after a state + // split that was performed by another checker (and can not find + // the successors). This is why this check is only used in the + // EvalCallAsPure case. + if (BR.isInteresting(RV) && Node->succ_size() > 1) + return CaseNote; + return ""; + }); + Pred = C.addTransition(NewState, Pred, Tag); + } + } else { + if (!CaseNote.empty() || !ErrnoNote.empty()) { + const NoteTag *Tag = + C.getNoteTag([CaseNote, ErrnoNote, + RV](PathSensitiveBugReport &BR) -> std::string { + // If 'errno' is interesting, show the user a note about the case + // (what happened at the function call) and about how 'errno' + // causes the problem. ErrnoChecker sets the errno (but not RV) to + // interesting. + // If only the return value is interesting, show only the case + // note. + std::optional<Loc> ErrnoLoc = + errno_modeling::getErrnoLoc(BR.getErrorNode()->getState()); + bool ErrnoImportant = !ErrnoNote.empty() && ErrnoLoc && + BR.isInteresting(ErrnoLoc->getAsRegion()); + if (ErrnoImportant) { + BR.markNotInteresting(ErrnoLoc->getAsRegion()); + if (CaseNote.empty()) + return ErrnoNote; + return llvm::formatv("{0}; {1}", CaseNote, ErrnoNote); + } else { + if (BR.isInteresting(RV)) + return CaseNote; + } + return ""; + }); + Pred = C.addTransition(NewState, Pred, Tag); + } + } + + // Add the transition if no note tag was added. + if (Pred == Node && NewState != State) C.addTransition(NewState); } } bool StdLibraryFunctionsChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { - Optional<Summary> FoundSummary = findFunctionSummary(Call, C); + std::optional<Summary> FoundSummary = findFunctionSummary(Call, C); if (!FoundSummary) return false; @@ -871,7 +1478,9 @@ bool StdLibraryFunctionsChecker::evalCall(const CallEvent &Call, SVal V = C.getSValBuilder().conjureSymbolVal( CE, LC, CE->getType().getCanonicalType(), C.blockCount()); State = State->BindExpr(CE, LC, V); + C.addTransition(State); + return true; } case NoEvalCall: @@ -910,12 +1519,11 @@ bool StdLibraryFunctionsChecker::Signature::matches( } // Check the argument types. - for (size_t I = 0, E = ArgTys.size(); I != E; ++I) { - QualType ArgTy = ArgTys[I]; + for (auto [Idx, ArgTy] : llvm::enumerate(ArgTys)) { if (isIrrelevant(ArgTy)) continue; QualType FDArgTy = - RemoveRestrict(FD->getParamDecl(I)->getType().getCanonicalType()); + RemoveRestrict(FD->getParamDecl(Idx)->getType().getCanonicalType()); if (ArgTy != FDArgTy) return false; } @@ -923,26 +1531,26 @@ bool StdLibraryFunctionsChecker::Signature::matches( return true; } -Optional<StdLibraryFunctionsChecker::Summary> +std::optional<StdLibraryFunctionsChecker::Summary> StdLibraryFunctionsChecker::findFunctionSummary(const FunctionDecl *FD, CheckerContext &C) const { if (!FD) - return None; + return std::nullopt; initFunctionSummaries(C); auto FSMI = FunctionSummaryMap.find(FD->getCanonicalDecl()); if (FSMI == FunctionSummaryMap.end()) - return None; + return std::nullopt; return FSMI->second; } -Optional<StdLibraryFunctionsChecker::Summary> +std::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 std::nullopt; return findFunctionSummary(FD, C); } @@ -950,10 +1558,12 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( CheckerContext &C) const { if (SummariesInitialized) return; + SummariesInitialized = true; SValBuilder &SVB = C.getSValBuilder(); BasicValueFactory &BVF = SVB.getBasicValueFactory(); const ASTContext &ACtx = BVF.getContext(); + Preprocessor &PP = C.getPreprocessor(); // Helper class to lookup a type by its name. class LookupType { @@ -963,11 +1573,11 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( LookupType(const ASTContext &ACtx) : ACtx(ACtx) {} // Find the type. If not found then the optional is not set. - llvm::Optional<QualType> operator()(StringRef Name) { + std::optional<QualType> operator()(StringRef Name) { IdentifierInfo &II = ACtx.Idents.get(Name); auto LookupRes = ACtx.getTranslationUnitDecl()->lookup(&II); if (LookupRes.empty()) - return None; + return std::nullopt; // Prioritze typedef declarations. // This is needed in case of C struct typedefs. E.g.: @@ -985,7 +1595,7 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( for (Decl *D : LookupRes) if (auto *TD = dyn_cast<TypeDecl>(D)) return ACtx.getTypeDeclType(TD).getCanonicalType(); - return None; + return std::nullopt; } } lookupTy(ACtx); @@ -999,10 +1609,10 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( QualType operator()(QualType Ty) { return ACtx.getLangOpts().C99 ? ACtx.getRestrictType(Ty) : Ty; } - Optional<QualType> operator()(Optional<QualType> Ty) { + std::optional<QualType> operator()(std::optional<QualType> Ty) { if (Ty) return operator()(*Ty); - return None; + return std::nullopt; } } getRestrictTy(ACtx); class GetPointerTy { @@ -1011,16 +1621,16 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( public: GetPointerTy(const ASTContext &ACtx) : ACtx(ACtx) {} QualType operator()(QualType Ty) { return ACtx.getPointerType(Ty); } - Optional<QualType> operator()(Optional<QualType> Ty) { + std::optional<QualType> operator()(std::optional<QualType> Ty) { if (Ty) return operator()(*Ty); - return None; + return std::nullopt; } } getPointerTy(ACtx); class { public: - Optional<QualType> operator()(Optional<QualType> Ty) { - return Ty ? Optional<QualType>(Ty->withConst()) : None; + std::optional<QualType> operator()(std::optional<QualType> Ty) { + return Ty ? std::optional<QualType>(Ty->withConst()) : std::nullopt; } QualType operator()(QualType Ty) { return Ty.withConst(); } } getConstTy; @@ -1029,14 +1639,14 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( public: GetMaxValue(BasicValueFactory &BVF) : BVF(BVF) {} - Optional<RangeInt> operator()(QualType Ty) { + std::optional<RangeInt> operator()(QualType Ty) { return BVF.getMaxValue(Ty).getLimitedValue(); } - Optional<RangeInt> operator()(Optional<QualType> Ty) { + std::optional<RangeInt> operator()(std::optional<QualType> Ty) { if (Ty) { return operator()(*Ty); } - return None; + return std::nullopt; } } getMaxValue(BVF); @@ -1089,14 +1699,11 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( 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; - }(); + // Get platform dependent values of some macros. + // Try our best to parse this from the Preprocessor, otherwise fallback to a + // default value (what is found in a library header). + const auto EOFv = tryExpandAsInteger("EOF", PP).value_or(-1); + const auto AT_FDCWDv = tryExpandAsInteger("AT_FDCWD", PP).value_or(-100); // Auxiliary class to aid adding summaries to the summary map. struct AddToFunctionSummaryMap { @@ -1146,9 +1753,9 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( } addToFunctionSummaryMap(ACtx, FunctionSummaryMap, DisplayLoadedSummaries); // 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 ArgumentCondition = [](ArgNo ArgN, RangeKind Kind, IntRangeVector Ranges, + StringRef Desc = "") { + return std::make_shared<RangeConstraint>(ArgN, Kind, Ranges, Desc); }; auto BufferSize = [](auto... Args) { return std::make_shared<BufferSizeConstraint>(Args...); @@ -1165,13 +1772,13 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( auto operator()(RangeInt b, RangeInt e) { return IntRangeVector{std::pair<RangeInt, RangeInt>{b, e}}; } - auto operator()(RangeInt b, Optional<RangeInt> e) { + auto operator()(RangeInt b, std::optional<RangeInt> e) { if (e) return IntRangeVector{std::pair<RangeInt, RangeInt>{b, *e}}; return IntRangeVector{}; } auto operator()(std::pair<RangeInt, RangeInt> i0, - std::pair<RangeInt, Optional<RangeInt>> i1) { + std::pair<RangeInt, std::optional<RangeInt>> i1) { if (i1.second) return IntRangeVector{i0, {i1.first, *(i1.second)}}; return IntRangeVector{i0}; @@ -1184,10 +1791,26 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( auto NotNull = [&](ArgNo ArgN) { return std::make_shared<NotNullConstraint>(ArgN); }; + auto IsNull = [&](ArgNo ArgN) { + return std::make_shared<NotNullConstraint>(ArgN, false); + }; + auto NotNullBuffer = [&](ArgNo ArgN, ArgNo SizeArg1N, ArgNo SizeArg2N) { + return std::make_shared<NotNullBufferConstraint>(ArgN, SizeArg1N, + SizeArg2N); + }; - Optional<QualType> FileTy = lookupTy("FILE"); - Optional<QualType> FilePtrTy = getPointerTy(FileTy); - Optional<QualType> FilePtrRestrictTy = getRestrictTy(FilePtrTy); + std::optional<QualType> FileTy = lookupTy("FILE"); + std::optional<QualType> FilePtrTy = getPointerTy(FileTy); + std::optional<QualType> FilePtrRestrictTy = getRestrictTy(FilePtrTy); + + std::optional<QualType> FPosTTy = lookupTy("fpos_t"); + std::optional<QualType> FPosTPtrTy = getPointerTy(FPosTTy); + std::optional<QualType> ConstFPosTPtrTy = getPointerTy(getConstTy(FPosTTy)); + std::optional<QualType> FPosTPtrRestrictTy = getRestrictTy(FPosTPtrTy); + + constexpr llvm::StringLiteral GenericSuccessMsg( + "Assuming that '{0}' is successful"); + constexpr llvm::StringLiteral GenericFailureMsg("Assuming that '{0}' fails"); // We are finally ready to define specifications for all supported functions. // @@ -1210,163 +1833,227 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( // Boils down to isupper() or islower() or isdigit(). .Case({ArgumentCondition(0U, WithinRange, {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}}), - ReturnValueCondition(OutOfRange, SingleValue(0))}) + ReturnValueCondition(OutOfRange, SingleValue(0))}, + ErrnoIrrelevant, "Assuming the character is alphanumeric") // 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, WithinRange, {{128, UCharRangeMax}})}, + ErrnoIrrelevant) .Case( {ArgumentCondition( 0U, OutOfRange, {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}, {128, UCharRangeMax}}), - ReturnValueCondition(WithinRange, SingleValue(0))}) - .ArgConstraint(ArgumentCondition( - 0U, WithinRange, {{EOFv, EOFv}, {0, UCharRangeMax}}))); + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant, "Assuming the character is non-alphanumeric") + .ArgConstraint(ArgumentCondition(0U, WithinRange, + {{EOFv, EOFv}, {0, UCharRangeMax}}, + "an unsigned char value or EOF"))); addToFunctionSummaryMap( "isalpha", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) .Case({ArgumentCondition(0U, WithinRange, {{'A', 'Z'}, {'a', 'z'}}), - ReturnValueCondition(OutOfRange, SingleValue(0))}) + ReturnValueCondition(OutOfRange, SingleValue(0))}, + ErrnoIrrelevant, "Assuming the character is alphabetical") // The locale-specific range. - .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}) + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}, + ErrnoIrrelevant) .Case({ArgumentCondition( 0U, OutOfRange, {{'A', 'Z'}, {'a', 'z'}, {128, UCharRangeMax}}), - ReturnValueCondition(WithinRange, SingleValue(0))})); + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant, "Assuming the character is non-alphabetical")); addToFunctionSummaryMap( "isascii", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) .Case({ArgumentCondition(0U, WithinRange, Range(0, 127)), - ReturnValueCondition(OutOfRange, SingleValue(0))}) + ReturnValueCondition(OutOfRange, SingleValue(0))}, + ErrnoIrrelevant, "Assuming the character is an ASCII character") .Case({ArgumentCondition(0U, OutOfRange, Range(0, 127)), - ReturnValueCondition(WithinRange, SingleValue(0))})); + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant, + "Assuming the character is not an ASCII character")); addToFunctionSummaryMap( "isblank", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) .Case({ArgumentCondition(0U, WithinRange, {{'\t', '\t'}, {' ', ' '}}), - ReturnValueCondition(OutOfRange, SingleValue(0))}) + ReturnValueCondition(OutOfRange, SingleValue(0))}, + ErrnoIrrelevant, "Assuming the character is a blank character") .Case({ArgumentCondition(0U, OutOfRange, {{'\t', '\t'}, {' ', ' '}}), - ReturnValueCondition(WithinRange, SingleValue(0))})); + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant, + "Assuming the character is not a blank character")); addToFunctionSummaryMap( "iscntrl", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) .Case({ArgumentCondition(0U, WithinRange, {{0, 32}, {127, 127}}), - ReturnValueCondition(OutOfRange, SingleValue(0))}) + ReturnValueCondition(OutOfRange, SingleValue(0))}, + ErrnoIrrelevant, + "Assuming the character is a control character") .Case({ArgumentCondition(0U, OutOfRange, {{0, 32}, {127, 127}}), - ReturnValueCondition(WithinRange, SingleValue(0))})); + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant, + "Assuming the character is not a control character")); addToFunctionSummaryMap( "isdigit", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) .Case({ArgumentCondition(0U, WithinRange, Range('0', '9')), - ReturnValueCondition(OutOfRange, SingleValue(0))}) + ReturnValueCondition(OutOfRange, SingleValue(0))}, + ErrnoIrrelevant, "Assuming the character is a digit") .Case({ArgumentCondition(0U, OutOfRange, Range('0', '9')), - ReturnValueCondition(WithinRange, SingleValue(0))})); + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant, "Assuming the character is not a digit")); addToFunctionSummaryMap( "isgraph", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) .Case({ArgumentCondition(0U, WithinRange, Range(33, 126)), - ReturnValueCondition(OutOfRange, SingleValue(0))}) - .Case({ArgumentCondition(0U, OutOfRange, Range(33, 126)), - ReturnValueCondition(WithinRange, SingleValue(0))})); + ReturnValueCondition(OutOfRange, SingleValue(0))}, + ErrnoIrrelevant, + "Assuming the character has graphical representation") + .Case( + {ArgumentCondition(0U, OutOfRange, Range(33, 126)), + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant, + "Assuming the character does not have graphical representation")); addToFunctionSummaryMap( "islower", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) // Is certainly lowercase. .Case({ArgumentCondition(0U, WithinRange, Range('a', 'z')), - ReturnValueCondition(OutOfRange, SingleValue(0))}) + ReturnValueCondition(OutOfRange, SingleValue(0))}, + ErrnoIrrelevant, "Assuming the character is a lowercase letter") // Is ascii but not lowercase. .Case({ArgumentCondition(0U, WithinRange, Range(0, 127)), ArgumentCondition(0U, OutOfRange, Range('a', 'z')), - ReturnValueCondition(WithinRange, SingleValue(0))}) + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant, + "Assuming the character is not a lowercase letter") // The locale-specific range. - .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}) + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}, + ErrnoIrrelevant) // Is not an unsigned char. .Case({ArgumentCondition(0U, OutOfRange, Range(0, UCharRangeMax)), - ReturnValueCondition(WithinRange, SingleValue(0))})); + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant)); addToFunctionSummaryMap( "isprint", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) .Case({ArgumentCondition(0U, WithinRange, Range(32, 126)), - ReturnValueCondition(OutOfRange, SingleValue(0))}) + ReturnValueCondition(OutOfRange, SingleValue(0))}, + ErrnoIrrelevant, "Assuming the character is printable") .Case({ArgumentCondition(0U, OutOfRange, Range(32, 126)), - ReturnValueCondition(WithinRange, SingleValue(0))})); + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant, "Assuming the character is non-printable")); addToFunctionSummaryMap( "ispunct", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) .Case({ArgumentCondition( 0U, WithinRange, {{'!', '/'}, {':', '@'}, {'[', '`'}, {'{', '~'}}), - ReturnValueCondition(OutOfRange, SingleValue(0))}) + ReturnValueCondition(OutOfRange, SingleValue(0))}, + ErrnoIrrelevant, "Assuming the character is a punctuation mark") .Case({ArgumentCondition( 0U, OutOfRange, {{'!', '/'}, {':', '@'}, {'[', '`'}, {'{', '~'}}), - ReturnValueCondition(WithinRange, SingleValue(0))})); + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant, + "Assuming the character is not a punctuation mark")); addToFunctionSummaryMap( "isspace", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) // Space, '\f', '\n', '\r', '\t', '\v'. .Case({ArgumentCondition(0U, WithinRange, {{9, 13}, {' ', ' '}}), - ReturnValueCondition(OutOfRange, SingleValue(0))}) + ReturnValueCondition(OutOfRange, SingleValue(0))}, + ErrnoIrrelevant, + "Assuming the character is a whitespace character") // The locale-specific range. - .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}) + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}, + ErrnoIrrelevant) .Case({ArgumentCondition(0U, OutOfRange, {{9, 13}, {' ', ' '}, {128, UCharRangeMax}}), - ReturnValueCondition(WithinRange, SingleValue(0))})); + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant, + "Assuming the character is not a whitespace character")); addToFunctionSummaryMap( "isupper", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) // Is certainly uppercase. .Case({ArgumentCondition(0U, WithinRange, Range('A', 'Z')), - ReturnValueCondition(OutOfRange, SingleValue(0))}) + ReturnValueCondition(OutOfRange, SingleValue(0))}, + ErrnoIrrelevant, + "Assuming the character is an uppercase letter") // The locale-specific range. - .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}) + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}, + ErrnoIrrelevant) // Other. .Case({ArgumentCondition(0U, OutOfRange, {{'A', 'Z'}, {128, UCharRangeMax}}), - ReturnValueCondition(WithinRange, SingleValue(0))})); + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant, + "Assuming the character is not an uppercase letter")); addToFunctionSummaryMap( "isxdigit", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) .Case({ArgumentCondition(0U, WithinRange, {{'0', '9'}, {'A', 'F'}, {'a', 'f'}}), - ReturnValueCondition(OutOfRange, SingleValue(0))}) + ReturnValueCondition(OutOfRange, SingleValue(0))}, + ErrnoIrrelevant, + "Assuming the character is a hexadecimal digit") .Case({ArgumentCondition(0U, OutOfRange, {{'0', '9'}, {'A', 'F'}, {'a', 'f'}}), - ReturnValueCondition(WithinRange, SingleValue(0))})); + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant, + "Assuming the character is not a hexadecimal digit")); addToFunctionSummaryMap( "toupper", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) - .ArgConstraint(ArgumentCondition( - 0U, WithinRange, {{EOFv, EOFv}, {0, UCharRangeMax}}))); + .ArgConstraint(ArgumentCondition(0U, WithinRange, + {{EOFv, EOFv}, {0, UCharRangeMax}}, + "an unsigned char value or EOF"))); addToFunctionSummaryMap( "tolower", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) - .ArgConstraint(ArgumentCondition( - 0U, WithinRange, {{EOFv, EOFv}, {0, UCharRangeMax}}))); + .ArgConstraint(ArgumentCondition(0U, WithinRange, + {{EOFv, EOFv}, {0, UCharRangeMax}}, + "an unsigned char value or EOF"))); addToFunctionSummaryMap( "toascii", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) - .ArgConstraint(ArgumentCondition( - 0U, WithinRange, {{EOFv, EOFv}, {0, UCharRangeMax}}))); + .ArgConstraint(ArgumentCondition(0U, WithinRange, + {{EOFv, EOFv}, {0, UCharRangeMax}}, + "an unsigned char value or EOF"))); // The getc() family of functions that returns either a char or an EOF. addToFunctionSummaryMap( {"getc", "fgetc"}, Signature(ArgTypes{FilePtrTy}, RetType{IntTy}), Summary(NoEvalCall) .Case({ReturnValueCondition(WithinRange, - {{EOFv, EOFv}, {0, UCharRangeMax}})})); + {{EOFv, EOFv}, {0, UCharRangeMax}})}, + ErrnoIrrelevant)); addToFunctionSummaryMap( "getchar", Signature(ArgTypes{}, RetType{IntTy}), Summary(NoEvalCall) .Case({ReturnValueCondition(WithinRange, - {{EOFv, EOFv}, {0, UCharRangeMax}})})); + {{EOFv, EOFv}, {0, UCharRangeMax}})}, + ErrnoIrrelevant)); // read()-like functions that never return more than buffer size. auto FreadSummary = Summary(NoEvalCall) - .Case({ReturnValueCondition(LessThanOrEq, ArgNo(2)), - ReturnValueCondition(WithinRange, Range(0, SizeMax))}) - .ArgConstraint(NotNull(ArgNo(0))) + .Case({ArgumentCondition(1U, WithinRange, Range(1, SizeMax)), + ArgumentCondition(2U, WithinRange, Range(1, SizeMax)), + ReturnValueCondition(BO_LT, ArgNo(2)), + ReturnValueCondition(WithinRange, Range(0, SizeMax))}, + ErrnoNEZeroIrrelevant, GenericFailureMsg) + .Case({ArgumentCondition(1U, WithinRange, Range(1, SizeMax)), + ReturnValueCondition(BO_EQ, ArgNo(2)), + ReturnValueCondition(WithinRange, Range(0, SizeMax))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({ArgumentCondition(1U, WithinRange, SingleValue(0)), + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoMustNotBeChecked, + "Assuming that argument 'size' to '{0}' is 0") + .ArgConstraint(NotNullBuffer(ArgNo(0), ArgNo(1), ArgNo(2))) .ArgConstraint(NotNull(ArgNo(3))) .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(0), /*BufSize=*/ArgNo(1), /*BufSizeMultiplier=*/ArgNo(2))); @@ -1386,13 +2073,14 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( RetType{SizeTy}), FreadSummary); - Optional<QualType> Ssize_tTy = lookupTy("ssize_t"); - Optional<RangeInt> Ssize_tMax = getMaxValue(Ssize_tTy); + std::optional<QualType> Ssize_tTy = lookupTy("ssize_t"); + std::optional<RangeInt> Ssize_tMax = getMaxValue(Ssize_tTy); auto ReadSummary = Summary(NoEvalCall) .Case({ReturnValueCondition(LessThanOrEq, ArgNo(2)), - ReturnValueCondition(WithinRange, Range(-1, Ssize_tMax))}); + ReturnValueCondition(WithinRange, Range(-1, Ssize_tMax))}, + ErrnoIrrelevant); // 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. @@ -1409,7 +2097,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( auto GetLineSummary = Summary(NoEvalCall) .Case({ReturnValueCondition(WithinRange, - Range({-1, -1}, {1, Ssize_tMax}))}); + Range({-1, -1}, {1, Ssize_tMax}))}, + ErrnoIrrelevant); QualType CharPtrPtrRestrictTy = getRestrictTy(getPointerTy(CharPtrTy)); @@ -1433,7 +2122,226 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( RetType{Ssize_tTy}), GetLineSummary); + { + Summary GetenvSummary = + Summary(NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .Case({NotNull(Ret)}, ErrnoIrrelevant, + "Assuming the environment variable exists"); + // In untrusted environments the envvar might not exist. + if (!ShouldAssumeControlledEnvironment) + GetenvSummary.Case({NotNull(Ret)->negate()}, ErrnoIrrelevant, + "Assuming the environment variable does not exist"); + + // char *getenv(const char *name); + addToFunctionSummaryMap( + "getenv", Signature(ArgTypes{ConstCharPtrTy}, RetType{CharPtrTy}), + std::move(GetenvSummary)); + } + if (ModelPOSIX) { + const auto ReturnsZeroOrMinusOne = + ConstraintSet{ReturnValueCondition(WithinRange, Range(-1, 0))}; + const auto ReturnsZero = + ConstraintSet{ReturnValueCondition(WithinRange, SingleValue(0))}; + const auto ReturnsMinusOne = + ConstraintSet{ReturnValueCondition(WithinRange, SingleValue(-1))}; + const auto ReturnsEOF = + ConstraintSet{ReturnValueCondition(WithinRange, SingleValue(EOFv))}; + const auto ReturnsNonnegative = + ConstraintSet{ReturnValueCondition(WithinRange, Range(0, IntMax))}; + const auto ReturnsNonZero = + ConstraintSet{ReturnValueCondition(OutOfRange, SingleValue(0))}; + const auto ReturnsFileDescriptor = + ConstraintSet{ReturnValueCondition(WithinRange, Range(-1, IntMax))}; + const auto &ReturnsValidFileDescriptor = ReturnsNonnegative; + + auto ValidFileDescriptorOrAtFdcwd = [&](ArgNo ArgN) { + return std::make_shared<RangeConstraint>( + ArgN, WithinRange, Range({AT_FDCWDv, AT_FDCWDv}, {0, IntMax}), + "a valid file descriptor or AT_FDCWD"); + }; + + // FILE *fopen(const char *restrict pathname, const char *restrict mode); + addToFunctionSummaryMap( + "fopen", + Signature(ArgTypes{ConstCharPtrRestrictTy, ConstCharPtrRestrictTy}, + RetType{FilePtrTy}), + Summary(NoEvalCall) + .Case({NotNull(Ret)}, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // FILE *fdopen(int fd, const char *mode); + addToFunctionSummaryMap( + "fdopen", + Signature(ArgTypes{IntTy, ConstCharPtrTy}, RetType{FilePtrTy}), + Summary(NoEvalCall) + .Case({NotNull(Ret)}, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // FILE *tmpfile(void); + addToFunctionSummaryMap( + "tmpfile", Signature(ArgTypes{}, RetType{FilePtrTy}), + Summary(NoEvalCall) + .Case({NotNull(Ret)}, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant, GenericFailureMsg)); + + // FILE *freopen(const char *restrict pathname, const char *restrict mode, + // FILE *restrict stream); + addToFunctionSummaryMap( + "freopen", + Signature(ArgTypes{ConstCharPtrRestrictTy, ConstCharPtrRestrictTy, + FilePtrRestrictTy}, + RetType{FilePtrTy}), + Summary(NoEvalCall) + .Case({ReturnValueCondition(BO_EQ, ArgNo(2))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(NotNull(ArgNo(2)))); + + // int fclose(FILE *stream); + addToFunctionSummaryMap( + "fclose", Signature(ArgTypes{FilePtrTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsEOF, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int ungetc(int c, FILE *stream); + addToFunctionSummaryMap( + "ungetc", Signature(ArgTypes{IntTy, FilePtrTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case({ReturnValueCondition(BO_EQ, ArgNo(0)), + ArgumentCondition(0, WithinRange, {{0, UCharRangeMax}})}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({ReturnValueCondition(WithinRange, SingleValue(EOFv)), + ArgumentCondition(0, WithinRange, SingleValue(EOFv))}, + ErrnoNEZeroIrrelevant, + "Assuming that 'ungetc' fails because EOF was passed as " + "character") + .Case({ReturnValueCondition(WithinRange, SingleValue(EOFv)), + ArgumentCondition(0, WithinRange, {{0, UCharRangeMax}})}, + ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(ArgumentCondition( + 0, WithinRange, {{EOFv, EOFv}, {0, UCharRangeMax}})) + .ArgConstraint(NotNull(ArgNo(1)))); + + std::optional<QualType> Off_tTy = lookupTy("off_t"); + std::optional<RangeInt> Off_tMax = getMaxValue(Off_tTy); + + // int fseek(FILE *stream, long offset, int whence); + // FIXME: It can be possible to get the 'SEEK_' values (like EOFv) and use + // these for condition of arg 2. + // Now the range [0,2] is used (the `SEEK_*` constants are usually 0,1,2). + addToFunctionSummaryMap( + "fseek", Signature(ArgTypes{FilePtrTy, LongTy, IntTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(ArgumentCondition(2, WithinRange, {{0, 2}}))); + + // int fseeko(FILE *stream, off_t offset, int whence); + addToFunctionSummaryMap( + "fseeko", + Signature(ArgTypes{FilePtrTy, Off_tTy, IntTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(ArgumentCondition(2, WithinRange, {{0, 2}}))); + + // int fgetpos(FILE *restrict stream, fpos_t *restrict pos); + // From 'The Open Group Base Specifications Issue 7, 2018 edition': + // "The fgetpos() function shall not change the setting of errno if + // successful." + addToFunctionSummaryMap( + "fgetpos", + Signature(ArgTypes{FilePtrRestrictTy, FPosTPtrRestrictTy}, + RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoUnchanged, GenericSuccessMsg) + .Case(ReturnsNonZero, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int fsetpos(FILE *stream, const fpos_t *pos); + // From 'The Open Group Base Specifications Issue 7, 2018 edition': + // "The fsetpos() function shall not change the setting of errno if + // successful." + addToFunctionSummaryMap( + "fsetpos", + Signature(ArgTypes{FilePtrTy, ConstFPosTPtrTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoUnchanged, GenericSuccessMsg) + .Case(ReturnsNonZero, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int fflush(FILE *stream); + addToFunctionSummaryMap( + "fflush", Signature(ArgTypes{FilePtrTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsEOF, ErrnoNEZeroIrrelevant, GenericFailureMsg)); + + // long ftell(FILE *stream); + // From 'The Open Group Base Specifications Issue 7, 2018 edition': + // "The ftell() function shall not change the setting of errno if + // successful." + addToFunctionSummaryMap( + "ftell", Signature(ArgTypes{FilePtrTy}, RetType{LongTy}), + Summary(NoEvalCall) + .Case({ReturnValueCondition(WithinRange, Range(0, LongMax))}, + ErrnoUnchanged, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0)))); + + // off_t ftello(FILE *stream); + addToFunctionSummaryMap( + "ftello", Signature(ArgTypes{FilePtrTy}, RetType{Off_tTy}), + Summary(NoEvalCall) + .Case({ReturnValueCondition(WithinRange, Range(0, Off_tMax))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int fileno(FILE *stream); + addToFunctionSummaryMap( + "fileno", Signature(ArgTypes{FilePtrTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsValidFileDescriptor, ErrnoMustNotBeChecked, + GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0)))); + + // void rewind(FILE *stream); + // This function indicates error only by setting of 'errno'. + addToFunctionSummaryMap("rewind", + Signature(ArgTypes{FilePtrTy}, RetType{VoidTy}), + Summary(NoEvalCall) + .Case({}, ErrnoMustBeChecked) + .ArgConstraint(NotNull(ArgNo(0)))); + + // void clearerr(FILE *stream); + addToFunctionSummaryMap( + "clearerr", Signature(ArgTypes{FilePtrTy}, RetType{VoidTy}), + Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0)))); + + // int feof(FILE *stream); + addToFunctionSummaryMap( + "feof", Signature(ArgTypes{FilePtrTy}, RetType{IntTy}), + Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0)))); + + // int ferror(FILE *stream); + addToFunctionSummaryMap( + "ferror", Signature(ArgTypes{FilePtrTy}, RetType{IntTy}), + Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0)))); // long a64l(const char *str64); addToFunctionSummaryMap( @@ -1447,16 +2355,32 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( .ArgConstraint(ArgumentCondition( 0, WithinRange, Range(0, LongMax)))); - const auto ReturnsZeroOrMinusOne = - ConstraintSet{ReturnValueCondition(WithinRange, Range(-1, 0))}; - const auto ReturnsFileDescriptor = - ConstraintSet{ReturnValueCondition(WithinRange, Range(-1, IntMax))}; + // int open(const char *path, int oflag, ...); + addToFunctionSummaryMap( + "open", Signature(ArgTypes{ConstCharPtrTy, IntTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsValidFileDescriptor, ErrnoMustNotBeChecked, + GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int openat(int fd, const char *path, int oflag, ...); + addToFunctionSummaryMap( + "openat", + Signature(ArgTypes{IntTy, ConstCharPtrTy, IntTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsValidFileDescriptor, ErrnoMustNotBeChecked, + GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(ValidFileDescriptorOrAtFdcwd(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); // int access(const char *pathname, int amode); addToFunctionSummaryMap( "access", Signature(ArgTypes{ConstCharPtrTy, IntTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0)))); // int faccessat(int dirfd, const char *pathname, int mode, int flags); @@ -1465,57 +2389,66 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{IntTy, ConstCharPtrTy, IntTy, IntTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(ValidFileDescriptorOrAtFdcwd(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1)))); // int dup(int fildes); - addToFunctionSummaryMap("dup", Signature(ArgTypes{IntTy}, RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsFileDescriptor) - .ArgConstraint(ArgumentCondition( - 0, WithinRange, Range(0, IntMax)))); + addToFunctionSummaryMap( + "dup", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsValidFileDescriptor, ErrnoMustNotBeChecked, + GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); // int dup2(int fildes1, int filedes2); addToFunctionSummaryMap( "dup2", Signature(ArgTypes{IntTy, IntTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsFileDescriptor) + .Case(ReturnsValidFileDescriptor, ErrnoMustNotBeChecked, + GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) .ArgConstraint( ArgumentCondition(1, WithinRange, Range(0, IntMax)))); // int fdatasync(int fildes); - addToFunctionSummaryMap("fdatasync", - Signature(ArgTypes{IntTy}, RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(ArgumentCondition( - 0, WithinRange, Range(0, IntMax)))); + addToFunctionSummaryMap( + "fdatasync", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); // int fnmatch(const char *pattern, const char *string, int flags); addToFunctionSummaryMap( "fnmatch", Signature(ArgTypes{ConstCharPtrTy, ConstCharPtrTy, IntTy}, RetType{IntTy}), - Summary(EvalCallAsPure) + Summary(NoEvalCall) .ArgConstraint(NotNull(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1)))); // int fsync(int fildes); - addToFunctionSummaryMap("fsync", Signature(ArgTypes{IntTy}, RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(ArgumentCondition( - 0, WithinRange, Range(0, IntMax)))); - - Optional<QualType> Off_tTy = lookupTy("off_t"); + addToFunctionSummaryMap( + "fsync", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); // int truncate(const char *path, off_t length); addToFunctionSummaryMap( "truncate", Signature(ArgTypes{ConstCharPtrTy, Off_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0)))); // int symlink(const char *oldpath, const char *newpath); @@ -1523,7 +2456,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "symlink", Signature(ArgTypes{ConstCharPtrTy, ConstCharPtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1)))); @@ -1533,26 +2467,30 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{ConstCharPtrTy, IntTy, ConstCharPtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0))) - .ArgConstraint(ArgumentCondition(1, WithinRange, Range(0, IntMax))) + .ArgConstraint(ValidFileDescriptorOrAtFdcwd(ArgNo(1))) .ArgConstraint(NotNull(ArgNo(2)))); // int lockf(int fd, int cmd, off_t len); addToFunctionSummaryMap( "lockf", Signature(ArgTypes{IntTy, IntTy, Off_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax)))); - Optional<QualType> Mode_tTy = lookupTy("mode_t"); + std::optional<QualType> Mode_tTy = lookupTy("mode_t"); // int creat(const char *pathname, mode_t mode); addToFunctionSummaryMap( "creat", Signature(ArgTypes{ConstCharPtrTy, Mode_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsFileDescriptor) + .Case(ReturnsValidFileDescriptor, ErrnoMustNotBeChecked, + GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0)))); // unsigned int sleep(unsigned int seconds); @@ -1562,15 +2500,17 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, UnsignedIntMax)))); - Optional<QualType> DirTy = lookupTy("DIR"); - Optional<QualType> DirPtrTy = getPointerTy(DirTy); + std::optional<QualType> DirTy = lookupTy("DIR"); + std::optional<QualType> DirPtrTy = getPointerTy(DirTy); // int dirfd(DIR *dirp); - addToFunctionSummaryMap("dirfd", - Signature(ArgTypes{DirPtrTy}, RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsFileDescriptor) - .ArgConstraint(NotNull(ArgNo(0)))); + addToFunctionSummaryMap( + "dirfd", Signature(ArgTypes{DirPtrTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsValidFileDescriptor, ErrnoMustNotBeChecked, + GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0)))); // unsigned int alarm(unsigned int seconds); addToFunctionSummaryMap( @@ -1580,11 +2520,12 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( ArgumentCondition(0, WithinRange, Range(0, UnsignedIntMax)))); // int closedir(DIR *dir); - addToFunctionSummaryMap("closedir", - Signature(ArgTypes{DirPtrTy}, RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(NotNull(ArgNo(0)))); + addToFunctionSummaryMap( + "closedir", Signature(ArgTypes{DirPtrTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0)))); // char *strdup(const char *s); addToFunctionSummaryMap( @@ -1606,21 +2547,39 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0)))); // int mkstemp(char *template); - addToFunctionSummaryMap("mkstemp", - Signature(ArgTypes{CharPtrTy}, RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsFileDescriptor) - .ArgConstraint(NotNull(ArgNo(0)))); + addToFunctionSummaryMap( + "mkstemp", Signature(ArgTypes{CharPtrTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsValidFileDescriptor, ErrnoMustNotBeChecked, + GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0)))); // char *mkdtemp(char *template); addToFunctionSummaryMap( "mkdtemp", Signature(ArgTypes{CharPtrTy}, RetType{CharPtrTy}), - Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0)))); + Summary(NoEvalCall) + .Case({ReturnValueCondition(BO_EQ, ArgNo(0))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0)))); // char *getcwd(char *buf, size_t size); addToFunctionSummaryMap( "getcwd", Signature(ArgTypes{CharPtrTy, SizeTy}, RetType{CharPtrTy}), Summary(NoEvalCall) + .Case({ArgumentCondition(1, WithinRange, Range(1, SizeMax)), + ReturnValueCondition(BO_EQ, ArgNo(0))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({ArgumentCondition(1, WithinRange, SingleValue(0)), + IsNull(Ret)}, + ErrnoNEZeroIrrelevant, "Assuming that argument 'size' is 0") + .Case({ArgumentCondition(1, WithinRange, Range(1, SizeMax)), + IsNull(Ret)}, + ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint( + BufferSize(/*Buffer*/ ArgNo(0), /*BufSize*/ ArgNo(1))) .ArgConstraint( ArgumentCondition(1, WithinRange, Range(0, SizeMax)))); @@ -1628,7 +2587,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( addToFunctionSummaryMap( "mkdir", Signature(ArgTypes{ConstCharPtrTy, Mode_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0)))); // int mkdirat(int dirfd, const char *pathname, mode_t mode); @@ -1636,17 +2596,20 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "mkdirat", Signature(ArgTypes{IntTy, ConstCharPtrTy, Mode_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(ValidFileDescriptorOrAtFdcwd(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1)))); - Optional<QualType> Dev_tTy = lookupTy("dev_t"); + std::optional<QualType> Dev_tTy = lookupTy("dev_t"); // int mknod(const char *pathname, mode_t mode, dev_t dev); addToFunctionSummaryMap( "mknod", Signature(ArgTypes{ConstCharPtrTy, Mode_tTy, Dev_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0)))); // int mknodat(int dirfd, const char *pathname, mode_t mode, dev_t dev); @@ -1655,14 +2618,17 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{IntTy, ConstCharPtrTy, Mode_tTy, Dev_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(ValidFileDescriptorOrAtFdcwd(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1)))); // int chmod(const char *path, mode_t mode); addToFunctionSummaryMap( "chmod", Signature(ArgTypes{ConstCharPtrTy, Mode_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0)))); // int fchmodat(int dirfd, const char *pathname, mode_t mode, int flags); @@ -1671,20 +2637,22 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{IntTy, ConstCharPtrTy, Mode_tTy, IntTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(ValidFileDescriptorOrAtFdcwd(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1)))); // int fchmod(int fildes, mode_t mode); addToFunctionSummaryMap( "fchmod", Signature(ArgTypes{IntTy, Mode_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax)))); - Optional<QualType> Uid_tTy = lookupTy("uid_t"); - Optional<QualType> Gid_tTy = lookupTy("gid_t"); + std::optional<QualType> Uid_tTy = lookupTy("uid_t"); + std::optional<QualType> Gid_tTy = lookupTy("gid_t"); // int fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, // int flags); @@ -1693,8 +2661,9 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{IntTy, ConstCharPtrTy, Uid_tTy, Gid_tTy, IntTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(ValidFileDescriptorOrAtFdcwd(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1)))); // int chown(const char *path, uid_t owner, gid_t group); @@ -1702,7 +2671,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "chown", Signature(ArgTypes{ConstCharPtrTy, Uid_tTy, Gid_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0)))); // int lchown(const char *path, uid_t owner, gid_t group); @@ -1710,37 +2680,42 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "lchown", Signature(ArgTypes{ConstCharPtrTy, Uid_tTy, Gid_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0)))); // int fchown(int fildes, uid_t owner, gid_t group); addToFunctionSummaryMap( "fchown", Signature(ArgTypes{IntTy, Uid_tTy, Gid_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax)))); // int rmdir(const char *pathname); - addToFunctionSummaryMap("rmdir", - Signature(ArgTypes{ConstCharPtrTy}, RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(NotNull(ArgNo(0)))); + addToFunctionSummaryMap( + "rmdir", Signature(ArgTypes{ConstCharPtrTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0)))); // int chdir(const char *path); - addToFunctionSummaryMap("chdir", - Signature(ArgTypes{ConstCharPtrTy}, RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(NotNull(ArgNo(0)))); + addToFunctionSummaryMap( + "chdir", Signature(ArgTypes{ConstCharPtrTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0)))); // int link(const char *oldpath, const char *newpath); addToFunctionSummaryMap( "link", Signature(ArgTypes{ConstCharPtrTy, ConstCharPtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1)))); @@ -1751,37 +2726,42 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{IntTy, ConstCharPtrTy, IntTy, ConstCharPtrTy, IntTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(ValidFileDescriptorOrAtFdcwd(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1))) - .ArgConstraint(ArgumentCondition(2, WithinRange, Range(0, IntMax))) + .ArgConstraint(ValidFileDescriptorOrAtFdcwd(ArgNo(2))) .ArgConstraint(NotNull(ArgNo(3)))); // int unlink(const char *pathname); - addToFunctionSummaryMap("unlink", - Signature(ArgTypes{ConstCharPtrTy}, RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(NotNull(ArgNo(0)))); + addToFunctionSummaryMap( + "unlink", Signature(ArgTypes{ConstCharPtrTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0)))); // int unlinkat(int fd, const char *path, int flag); addToFunctionSummaryMap( "unlinkat", Signature(ArgTypes{IntTy, ConstCharPtrTy, IntTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(ValidFileDescriptorOrAtFdcwd(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1)))); - Optional<QualType> StructStatTy = lookupTy("stat"); - Optional<QualType> StructStatPtrTy = getPointerTy(StructStatTy); - Optional<QualType> StructStatPtrRestrictTy = getRestrictTy(StructStatPtrTy); + std::optional<QualType> StructStatTy = lookupTy("stat"); + std::optional<QualType> StructStatPtrTy = getPointerTy(StructStatTy); + std::optional<QualType> StructStatPtrRestrictTy = + getRestrictTy(StructStatPtrTy); // int fstat(int fd, struct stat *statbuf); addToFunctionSummaryMap( "fstat", Signature(ArgTypes{IntTy, StructStatPtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) .ArgConstraint(NotNull(ArgNo(1)))); @@ -1791,7 +2771,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{ConstCharPtrRestrictTy, StructStatPtrRestrictTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1)))); @@ -1801,7 +2782,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{ConstCharPtrRestrictTy, StructStatPtrRestrictTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1)))); @@ -1813,32 +2795,40 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( StructStatPtrRestrictTy, IntTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(ValidFileDescriptorOrAtFdcwd(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1))) .ArgConstraint(NotNull(ArgNo(2)))); // DIR *opendir(const char *name); addToFunctionSummaryMap( "opendir", Signature(ArgTypes{ConstCharPtrTy}, RetType{DirPtrTy}), - Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0)))); + Summary(NoEvalCall) + .Case({NotNull(Ret)}, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0)))); // DIR *fdopendir(int fd); - addToFunctionSummaryMap("fdopendir", - Signature(ArgTypes{IntTy}, RetType{DirPtrTy}), - Summary(NoEvalCall) - .ArgConstraint(ArgumentCondition( - 0, WithinRange, Range(0, IntMax)))); + addToFunctionSummaryMap( + "fdopendir", Signature(ArgTypes{IntTy}, RetType{DirPtrTy}), + Summary(NoEvalCall) + .Case({NotNull(Ret)}, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({IsNull(Ret)}, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); // int isatty(int fildes); addToFunctionSummaryMap( "isatty", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case({ReturnValueCondition(WithinRange, Range(0, 1))}) + .Case({ReturnValueCondition(WithinRange, Range(0, 1))}, + ErrnoIrrelevant) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax)))); // FILE *popen(const char *command, const char *type); + // FIXME: Improve for errno modeling. addToFunctionSummaryMap( "popen", Signature(ArgTypes{ConstCharPtrTy, ConstCharPtrTy}, RetType{FilePtrTy}), @@ -1847,16 +2837,19 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( .ArgConstraint(NotNull(ArgNo(1)))); // int pclose(FILE *stream); + // FIXME: Improve for errno modeling. addToFunctionSummaryMap( "pclose", Signature(ArgTypes{FilePtrTy}, RetType{IntTy}), Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0)))); // int close(int fildes); - addToFunctionSummaryMap("close", Signature(ArgTypes{IntTy}, RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(ArgumentCondition( - 0, WithinRange, Range(-1, IntMax)))); + addToFunctionSummaryMap( + "close", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(-1, IntMax)))); // long fpathconf(int fildes, int name); addToFunctionSummaryMap("fpathconf", @@ -1870,14 +2863,6 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "pathconf", Signature(ArgTypes{ConstCharPtrTy, IntTy}, RetType{LongTy}), Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0)))); - // FILE *fdopen(int fd, const char *mode); - addToFunctionSummaryMap( - "fdopen", - Signature(ArgTypes{IntTy, ConstCharPtrTy}, RetType{FilePtrTy}), - Summary(NoEvalCall) - .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) - .ArgConstraint(NotNull(ArgNo(1)))); - // void rewinddir(DIR *dir); addToFunctionSummaryMap( "rewinddir", Signature(ArgTypes{DirPtrTy}, RetType{VoidTy}), @@ -1893,28 +2878,9 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "rand_r", Signature(ArgTypes{UnsignedIntPtrTy}, RetType{IntTy}), Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0)))); - // int fileno(FILE *stream); - addToFunctionSummaryMap("fileno", - Signature(ArgTypes{FilePtrTy}, RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsFileDescriptor) - .ArgConstraint(NotNull(ArgNo(0)))); - - // int fseeko(FILE *stream, off_t offset, int whence); - addToFunctionSummaryMap( - "fseeko", - Signature(ArgTypes{FilePtrTy, Off_tTy, IntTy}, RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(NotNull(ArgNo(0)))); - - // off_t ftello(FILE *stream); - addToFunctionSummaryMap( - "ftello", Signature(ArgTypes{FilePtrTy}, RetType{Off_tTy}), - Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0)))); - // void *mmap(void *addr, size_t length, int prot, int flags, int fd, // off_t offset); + // FIXME: Improve for errno modeling. addToFunctionSummaryMap( "mmap", Signature(ArgTypes{VoidPtrTy, SizeTy, IntTy, IntTy, IntTy, Off_tTy}, @@ -1924,9 +2890,10 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( .ArgConstraint( ArgumentCondition(4, WithinRange, Range(-1, IntMax)))); - Optional<QualType> Off64_tTy = lookupTy("off64_t"); + std::optional<QualType> Off64_tTy = lookupTy("off64_t"); // void *mmap64(void *addr, size_t length, int prot, int flags, int fd, // off64_t offset); + // FIXME: Improve for errno modeling. addToFunctionSummaryMap( "mmap64", Signature(ArgTypes{VoidPtrTy, SizeTy, IntTy, IntTy, IntTy, Off64_tTy}, @@ -1937,16 +2904,23 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( ArgumentCondition(4, WithinRange, Range(-1, IntMax)))); // int pipe(int fildes[2]); - addToFunctionSummaryMap("pipe", - Signature(ArgTypes{IntPtrTy}, RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(NotNull(ArgNo(0)))); + addToFunctionSummaryMap( + "pipe", Signature(ArgTypes{IntPtrTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(0)))); // off_t lseek(int fildes, off_t offset, int whence); + // In the first case we can not tell for sure if it failed or not. + // A return value different from of the expected offset (that is unknown + // here) may indicate failure. For this reason we do not enforce the errno + // check (can cause false positive). addToFunctionSummaryMap( "lseek", Signature(ArgTypes{IntTy, Off_tTy, IntTy}, RetType{Off_tTy}), Summary(NoEvalCall) + .Case(ReturnsNonnegative, ErrnoIrrelevant) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax)))); @@ -1957,8 +2931,15 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{ConstCharPtrRestrictTy, CharPtrRestrictTy, SizeTy}, RetType{Ssize_tTy}), Summary(NoEvalCall) - .Case({ReturnValueCondition(LessThanOrEq, ArgNo(2)), - ReturnValueCondition(WithinRange, Range(-1, Ssize_tMax))}) + .Case({ArgumentCondition(2, WithinRange, Range(1, IntMax)), + ReturnValueCondition(LessThanOrEq, ArgNo(2)), + ReturnValueCondition(WithinRange, Range(1, Ssize_tMax))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({ArgumentCondition(2, WithinRange, SingleValue(0)), + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoMustNotBeChecked, + "Assuming that argument 'bufsize' is 0") + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1))) .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(1), @@ -1974,9 +2955,16 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( ArgTypes{IntTy, ConstCharPtrRestrictTy, CharPtrRestrictTy, SizeTy}, RetType{Ssize_tTy}), Summary(NoEvalCall) - .Case({ReturnValueCondition(LessThanOrEq, ArgNo(3)), - ReturnValueCondition(WithinRange, Range(-1, Ssize_tMax))}) - .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .Case({ArgumentCondition(3, WithinRange, Range(1, IntMax)), + ReturnValueCondition(LessThanOrEq, ArgNo(3)), + ReturnValueCondition(WithinRange, Range(1, Ssize_tMax))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({ArgumentCondition(3, WithinRange, SingleValue(0)), + ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoMustNotBeChecked, + "Assuming that argument 'bufsize' is 0") + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(ValidFileDescriptorOrAtFdcwd(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1))) .ArgConstraint(NotNull(ArgNo(2))) .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(2), @@ -1991,12 +2979,16 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{IntTy, ConstCharPtrTy, IntTy, ConstCharPtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(ValidFileDescriptorOrAtFdcwd(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(ValidFileDescriptorOrAtFdcwd(ArgNo(2))) .ArgConstraint(NotNull(ArgNo(3)))); // char *realpath(const char *restrict file_name, // char *restrict resolved_name); + // FIXME: Improve for errno modeling. addToFunctionSummaryMap( "realpath", Signature(ArgTypes{ConstCharPtrRestrictTy, CharPtrRestrictTy}, @@ -2010,7 +3002,7 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "execv", Signature(ArgTypes{ConstCharPtrTy, CharPtrConstPtr}, RetType{IntTy}), Summary(NoEvalCall) - .Case({ReturnValueCondition(WithinRange, SingleValue(-1))}) + .Case(ReturnsMinusOne, ErrnoIrrelevant) .ArgConstraint(NotNull(ArgNo(0)))); // int execvp(const char *file, char *const argv[]); @@ -2018,7 +3010,7 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "execvp", Signature(ArgTypes{ConstCharPtrTy, CharPtrConstPtr}, RetType{IntTy}), Summary(NoEvalCall) - .Case({ReturnValueCondition(WithinRange, SingleValue(-1))}) + .Case(ReturnsMinusOne, ErrnoIrrelevant) .ArgConstraint(NotNull(ArgNo(0)))); // int getopt(int argc, char * const argv[], const char *optstring); @@ -2027,23 +3019,26 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{IntTy, CharPtrConstPtr, ConstCharPtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case({ReturnValueCondition(WithinRange, Range(-1, UCharRangeMax))}) + .Case({ReturnValueCondition(WithinRange, Range(-1, UCharRangeMax))}, + ErrnoIrrelevant) .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) .ArgConstraint(NotNull(ArgNo(1))) .ArgConstraint(NotNull(ArgNo(2)))); - Optional<QualType> StructSockaddrTy = lookupTy("sockaddr"); - Optional<QualType> StructSockaddrPtrTy = getPointerTy(StructSockaddrTy); - Optional<QualType> ConstStructSockaddrPtrTy = + std::optional<QualType> StructSockaddrTy = lookupTy("sockaddr"); + std::optional<QualType> StructSockaddrPtrTy = + getPointerTy(StructSockaddrTy); + std::optional<QualType> ConstStructSockaddrPtrTy = getPointerTy(getConstTy(StructSockaddrTy)); - Optional<QualType> StructSockaddrPtrRestrictTy = + std::optional<QualType> StructSockaddrPtrRestrictTy = getRestrictTy(StructSockaddrPtrTy); - Optional<QualType> ConstStructSockaddrPtrRestrictTy = + std::optional<QualType> ConstStructSockaddrPtrRestrictTy = getRestrictTy(ConstStructSockaddrPtrTy); - Optional<QualType> Socklen_tTy = lookupTy("socklen_t"); - Optional<QualType> Socklen_tPtrTy = getPointerTy(Socklen_tTy); - Optional<QualType> Socklen_tPtrRestrictTy = getRestrictTy(Socklen_tPtrTy); - Optional<RangeInt> Socklen_tMax = getMaxValue(Socklen_tTy); + std::optional<QualType> Socklen_tTy = lookupTy("socklen_t"); + std::optional<QualType> Socklen_tPtrTy = getPointerTy(Socklen_tTy); + std::optional<QualType> Socklen_tPtrRestrictTy = + getRestrictTy(Socklen_tPtrTy); + std::optional<RangeInt> Socklen_tMax = getMaxValue(Socklen_tTy); // In 'socket.h' of some libc implementations with C99, sockaddr parameter // is a transparent union of the underlying sockaddr_ family of pointers @@ -2051,9 +3046,20 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( // standardized signature will not match, thus we try to match with another // signature that has the joker Irrelevant type. We also remove those // constraints which require pointer types for the sockaddr param. + + // int socket(int domain, int type, int protocol); + addToFunctionSummaryMap( + "socket", Signature(ArgTypes{IntTy, IntTy, IntTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsValidFileDescriptor, ErrnoMustNotBeChecked, + GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg)); + auto Accept = Summary(NoEvalCall) - .Case(ReturnsFileDescriptor) + .Case(ReturnsValidFileDescriptor, ErrnoMustNotBeChecked, + GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))); if (!addToFunctionSummaryMap( "accept", @@ -2076,7 +3082,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{IntTy, ConstStructSockaddrPtrTy, Socklen_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax))) .ArgConstraint(NotNull(ArgNo(1))) @@ -2089,7 +3096,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "bind", Signature(ArgTypes{IntTy, Irrelevant, Socklen_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax))) .ArgConstraint( @@ -2103,7 +3111,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Socklen_tPtrRestrictTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax))) .ArgConstraint(NotNull(ArgNo(1))) @@ -2113,7 +3122,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{IntTy, Irrelevant, Socklen_tPtrRestrictTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax)))); @@ -2125,7 +3135,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Socklen_tPtrRestrictTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax))) .ArgConstraint(NotNull(ArgNo(1))) @@ -2135,7 +3146,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{IntTy, Irrelevant, Socklen_tPtrRestrictTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax)))); @@ -2146,7 +3158,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{IntTy, ConstStructSockaddrPtrTy, Socklen_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax))) .ArgConstraint(NotNull(ArgNo(1))))) @@ -2154,14 +3167,20 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "connect", Signature(ArgTypes{IntTy, Irrelevant, Socklen_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax)))); auto Recvfrom = Summary(NoEvalCall) .Case({ReturnValueCondition(LessThanOrEq, ArgNo(2)), - ReturnValueCondition(WithinRange, Range(-1, Ssize_tMax))}) + ReturnValueCondition(WithinRange, Range(1, Ssize_tMax))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({ReturnValueCondition(WithinRange, SingleValue(0)), + ArgumentCondition(2, WithinRange, SingleValue(0))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(1), /*BufSize=*/ArgNo(2))); @@ -2186,7 +3205,12 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( auto Sendto = Summary(NoEvalCall) .Case({ReturnValueCondition(LessThanOrEq, ArgNo(2)), - ReturnValueCondition(WithinRange, Range(-1, Ssize_tMax))}) + ReturnValueCondition(WithinRange, Range(1, Ssize_tMax))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({ReturnValueCondition(WithinRange, SingleValue(0)), + ArgumentCondition(2, WithinRange, SingleValue(0))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(1), /*BufSize=*/ArgNo(2))); @@ -2207,12 +3231,13 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Sendto); // int listen(int sockfd, int backlog); - addToFunctionSummaryMap("listen", - Signature(ArgTypes{IntTy, IntTy}, RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(ArgumentCondition( - 0, WithinRange, Range(0, IntMax)))); + addToFunctionSummaryMap( + "listen", Signature(ArgTypes{IntTy, IntTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); // ssize_t recv(int sockfd, void *buf, size_t len, int flags); addToFunctionSummaryMap( @@ -2221,14 +3246,19 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( RetType{Ssize_tTy}), Summary(NoEvalCall) .Case({ReturnValueCondition(LessThanOrEq, ArgNo(2)), - ReturnValueCondition(WithinRange, Range(-1, Ssize_tMax))}) + ReturnValueCondition(WithinRange, Range(1, Ssize_tMax))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({ReturnValueCondition(WithinRange, SingleValue(0)), + ArgumentCondition(2, WithinRange, SingleValue(0))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(1), /*BufSize=*/ArgNo(2)))); - Optional<QualType> StructMsghdrTy = lookupTy("msghdr"); - Optional<QualType> StructMsghdrPtrTy = getPointerTy(StructMsghdrTy); - Optional<QualType> ConstStructMsghdrPtrTy = + std::optional<QualType> StructMsghdrTy = lookupTy("msghdr"); + std::optional<QualType> StructMsghdrPtrTy = getPointerTy(StructMsghdrTy); + std::optional<QualType> ConstStructMsghdrPtrTy = getPointerTy(getConstTy(StructMsghdrTy)); // ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); @@ -2237,7 +3267,9 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{IntTy, StructMsghdrPtrTy, IntTy}, RetType{Ssize_tTy}), Summary(NoEvalCall) - .Case({ReturnValueCondition(WithinRange, Range(-1, Ssize_tMax))}) + .Case({ReturnValueCondition(WithinRange, Range(1, Ssize_tMax))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax)))); @@ -2247,7 +3279,9 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{IntTy, ConstStructMsghdrPtrTy, IntTy}, RetType{Ssize_tTy}), Summary(NoEvalCall) - .Case({ReturnValueCondition(WithinRange, Range(-1, Ssize_tMax))}) + .Case({ReturnValueCondition(WithinRange, Range(1, Ssize_tMax))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax)))); @@ -2258,7 +3292,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{IntTy, IntTy, IntTy, ConstVoidPtrTy, Socklen_tTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(3))) .ArgConstraint( BufferSize(/*Buffer=*/ArgNo(3), /*BufSize=*/ArgNo(4))) @@ -2274,7 +3309,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Socklen_tPtrRestrictTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(3))) .ArgConstraint(NotNull(ArgNo(4)))); @@ -2285,7 +3321,12 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( RetType{Ssize_tTy}), Summary(NoEvalCall) .Case({ReturnValueCondition(LessThanOrEq, ArgNo(2)), - ReturnValueCondition(WithinRange, Range(-1, Ssize_tMax))}) + ReturnValueCondition(WithinRange, Range(1, Ssize_tMax))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case({ReturnValueCondition(WithinRange, SingleValue(0)), + ArgumentCondition(2, WithinRange, SingleValue(0))}, + ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(1), /*BufSize=*/ArgNo(2)))); @@ -2295,9 +3336,19 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "socketpair", Signature(ArgTypes{IntTy, IntTy, IntTy, IntPtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(3)))); + // int shutdown(int socket, int how); + addToFunctionSummaryMap( + "shutdown", Signature(ArgTypes{IntTy, IntTy}, RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + // int getnameinfo(const struct sockaddr *restrict sa, socklen_t salen, // char *restrict node, socklen_t nodelen, // char *restrict service, @@ -2325,20 +3376,22 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( .ArgConstraint( ArgumentCondition(5, WithinRange, Range(0, Socklen_tMax)))); - Optional<QualType> StructUtimbufTy = lookupTy("utimbuf"); - Optional<QualType> StructUtimbufPtrTy = getPointerTy(StructUtimbufTy); + std::optional<QualType> StructUtimbufTy = lookupTy("utimbuf"); + std::optional<QualType> StructUtimbufPtrTy = getPointerTy(StructUtimbufTy); // int utime(const char *filename, struct utimbuf *buf); addToFunctionSummaryMap( "utime", Signature(ArgTypes{ConstCharPtrTy, StructUtimbufPtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0)))); - Optional<QualType> StructTimespecTy = lookupTy("timespec"); - Optional<QualType> StructTimespecPtrTy = getPointerTy(StructTimespecTy); - Optional<QualType> ConstStructTimespecPtrTy = + std::optional<QualType> StructTimespecTy = lookupTy("timespec"); + std::optional<QualType> StructTimespecPtrTy = + getPointerTy(StructTimespecTy); + std::optional<QualType> ConstStructTimespecPtrTy = getPointerTy(getConstTy(StructTimespecTy)); // int futimens(int fd, const struct timespec times[2]); @@ -2346,22 +3399,25 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "futimens", Signature(ArgTypes{IntTy, ConstStructTimespecPtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax)))); // int utimensat(int dirfd, const char *pathname, // const struct timespec times[2], int flags); - addToFunctionSummaryMap("utimensat", - Signature(ArgTypes{IntTy, ConstCharPtrTy, - ConstStructTimespecPtrTy, IntTy}, - RetType{IntTy}), - Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) - .ArgConstraint(NotNull(ArgNo(1)))); + addToFunctionSummaryMap( + "utimensat", + Signature( + ArgTypes{IntTy, ConstCharPtrTy, ConstStructTimespecPtrTy, IntTy}, + RetType{IntTy}), + Summary(NoEvalCall) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) + .ArgConstraint(NotNull(ArgNo(1)))); - Optional<QualType> StructTimevalTy = lookupTy("timeval"); - Optional<QualType> ConstStructTimevalPtrTy = + std::optional<QualType> StructTimevalTy = lookupTy("timeval"); + std::optional<QualType> ConstStructTimevalPtrTy = getPointerTy(getConstTy(StructTimevalTy)); // int utimes(const char *filename, const struct timeval times[2]); @@ -2370,7 +3426,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{ConstCharPtrTy, ConstStructTimevalPtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0)))); // int nanosleep(const struct timespec *rqtp, struct timespec *rmtp); @@ -2379,20 +3436,23 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( Signature(ArgTypes{ConstStructTimespecPtrTy, StructTimespecPtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(0)))); - Optional<QualType> Time_tTy = lookupTy("time_t"); - Optional<QualType> ConstTime_tPtrTy = getPointerTy(getConstTy(Time_tTy)); - Optional<QualType> ConstTime_tPtrRestrictTy = + std::optional<QualType> Time_tTy = lookupTy("time_t"); + std::optional<QualType> ConstTime_tPtrTy = + getPointerTy(getConstTy(Time_tTy)); + std::optional<QualType> ConstTime_tPtrRestrictTy = getRestrictTy(ConstTime_tPtrTy); - Optional<QualType> StructTmTy = lookupTy("tm"); - Optional<QualType> StructTmPtrTy = getPointerTy(StructTmTy); - Optional<QualType> StructTmPtrRestrictTy = getRestrictTy(StructTmPtrTy); - Optional<QualType> ConstStructTmPtrTy = + std::optional<QualType> StructTmTy = lookupTy("tm"); + std::optional<QualType> StructTmPtrTy = getPointerTy(StructTmTy); + std::optional<QualType> StructTmPtrRestrictTy = + getRestrictTy(StructTmPtrTy); + std::optional<QualType> ConstStructTmPtrTy = getPointerTy(getConstTy(StructTmTy)); - Optional<QualType> ConstStructTmPtrRestrictTy = + std::optional<QualType> ConstStructTmPtrRestrictTy = getRestrictTy(ConstStructTmPtrTy); // struct tm * localtime(const time_t *tp); @@ -2448,46 +3508,54 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "gmtime", Signature(ArgTypes{ConstTime_tPtrTy}, RetType{StructTmPtrTy}), Summary(NoEvalCall).ArgConstraint(NotNull(ArgNo(0)))); - Optional<QualType> Clockid_tTy = lookupTy("clockid_t"); + std::optional<QualType> Clockid_tTy = lookupTy("clockid_t"); // int clock_gettime(clockid_t clock_id, struct timespec *tp); addToFunctionSummaryMap( "clock_gettime", Signature(ArgTypes{Clockid_tTy, StructTimespecPtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(1)))); - Optional<QualType> StructItimervalTy = lookupTy("itimerval"); - Optional<QualType> StructItimervalPtrTy = getPointerTy(StructItimervalTy); + std::optional<QualType> StructItimervalTy = lookupTy("itimerval"); + std::optional<QualType> StructItimervalPtrTy = + getPointerTy(StructItimervalTy); // int getitimer(int which, struct itimerval *curr_value); addToFunctionSummaryMap( "getitimer", Signature(ArgTypes{IntTy, StructItimervalPtrTy}, RetType{IntTy}), Summary(NoEvalCall) - .Case(ReturnsZeroOrMinusOne) + .Case(ReturnsZero, ErrnoMustNotBeChecked, GenericSuccessMsg) + .Case(ReturnsMinusOne, ErrnoNEZeroIrrelevant, GenericFailureMsg) .ArgConstraint(NotNull(ArgNo(1)))); - Optional<QualType> Pthread_cond_tTy = lookupTy("pthread_cond_t"); - Optional<QualType> Pthread_cond_tPtrTy = getPointerTy(Pthread_cond_tTy); - Optional<QualType> Pthread_tTy = lookupTy("pthread_t"); - Optional<QualType> Pthread_tPtrTy = getPointerTy(Pthread_tTy); - Optional<QualType> Pthread_tPtrRestrictTy = getRestrictTy(Pthread_tPtrTy); - Optional<QualType> Pthread_mutex_tTy = lookupTy("pthread_mutex_t"); - Optional<QualType> Pthread_mutex_tPtrTy = getPointerTy(Pthread_mutex_tTy); - Optional<QualType> Pthread_mutex_tPtrRestrictTy = + std::optional<QualType> Pthread_cond_tTy = lookupTy("pthread_cond_t"); + std::optional<QualType> Pthread_cond_tPtrTy = + getPointerTy(Pthread_cond_tTy); + std::optional<QualType> Pthread_tTy = lookupTy("pthread_t"); + std::optional<QualType> Pthread_tPtrTy = getPointerTy(Pthread_tTy); + std::optional<QualType> Pthread_tPtrRestrictTy = + getRestrictTy(Pthread_tPtrTy); + std::optional<QualType> Pthread_mutex_tTy = lookupTy("pthread_mutex_t"); + std::optional<QualType> Pthread_mutex_tPtrTy = + getPointerTy(Pthread_mutex_tTy); + std::optional<QualType> Pthread_mutex_tPtrRestrictTy = getRestrictTy(Pthread_mutex_tPtrTy); - Optional<QualType> Pthread_attr_tTy = lookupTy("pthread_attr_t"); - Optional<QualType> Pthread_attr_tPtrTy = getPointerTy(Pthread_attr_tTy); - Optional<QualType> ConstPthread_attr_tPtrTy = + std::optional<QualType> Pthread_attr_tTy = lookupTy("pthread_attr_t"); + std::optional<QualType> Pthread_attr_tPtrTy = + getPointerTy(Pthread_attr_tTy); + std::optional<QualType> ConstPthread_attr_tPtrTy = getPointerTy(getConstTy(Pthread_attr_tTy)); - Optional<QualType> ConstPthread_attr_tPtrRestrictTy = + std::optional<QualType> ConstPthread_attr_tPtrRestrictTy = getRestrictTy(ConstPthread_attr_tPtrTy); - Optional<QualType> Pthread_mutexattr_tTy = lookupTy("pthread_mutexattr_t"); - Optional<QualType> ConstPthread_mutexattr_tPtrTy = + std::optional<QualType> Pthread_mutexattr_tTy = + lookupTy("pthread_mutexattr_t"); + std::optional<QualType> ConstPthread_mutexattr_tPtrTy = getPointerTy(getConstTy(Pthread_mutexattr_tTy)); - Optional<QualType> ConstPthread_mutexattr_tPtrRestrictTy = + std::optional<QualType> ConstPthread_mutexattr_tPtrRestrictTy = getRestrictTy(ConstPthread_mutexattr_tPtrTy); QualType PthreadStartRoutineTy = getPointerTy( @@ -2564,12 +3632,24 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( } // Functions for testing. - if (ChecksEnabled[CK_StdCLibraryFunctionsTesterChecker]) { + if (AddTestFunctions) { + const RangeInt IntMin = BVF.getMinValue(IntTy).getLimitedValue(); + addToFunctionSummaryMap( "__not_null", Signature(ArgTypes{IntPtrTy}, RetType{IntTy}), Summary(EvalCallAsPure).ArgConstraint(NotNull(ArgNo(0)))); - // Test range values. + addToFunctionSummaryMap( + "__not_null_buffer", + Signature(ArgTypes{VoidPtrTy, IntTy, IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(NotNullBuffer(ArgNo(0), ArgNo(1), ArgNo(2)))); + + // Test inside range constraints. + addToFunctionSummaryMap( + "__single_val_0", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0U, WithinRange, SingleValue(0)))); addToFunctionSummaryMap( "__single_val_1", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) @@ -2578,11 +3658,124 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "__range_1_2", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) .ArgConstraint(ArgumentCondition(0U, WithinRange, Range(1, 2)))); - addToFunctionSummaryMap("__range_1_2__4_5", + addToFunctionSummaryMap( + "__range_m1_1", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0U, WithinRange, Range(-1, 1)))); + addToFunctionSummaryMap( + "__range_m2_m1", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0U, WithinRange, Range(-2, -1)))); + addToFunctionSummaryMap( + "__range_m10_10", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0U, WithinRange, Range(-10, 10)))); + addToFunctionSummaryMap("__range_m1_inf", + Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition( + 0U, WithinRange, Range(-1, IntMax)))); + addToFunctionSummaryMap("__range_0_inf", + Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition( + 0U, WithinRange, Range(0, IntMax)))); + addToFunctionSummaryMap("__range_1_inf", + Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition( + 0U, WithinRange, Range(1, IntMax)))); + addToFunctionSummaryMap("__range_minf_m1", + Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition( + 0U, WithinRange, Range(IntMin, -1)))); + addToFunctionSummaryMap("__range_minf_0", + Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition( + 0U, WithinRange, Range(IntMin, 0)))); + addToFunctionSummaryMap("__range_minf_1", + Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition( + 0U, WithinRange, Range(IntMin, 1)))); + addToFunctionSummaryMap("__range_1_2__4_6", + Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition( + 0U, WithinRange, Range({1, 2}, {4, 6})))); + addToFunctionSummaryMap( + "__range_1_2__4_inf", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0U, WithinRange, + Range({1, 2}, {4, IntMax})))); + + // Test out of range constraints. + addToFunctionSummaryMap( + "__single_val_out_0", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0U, OutOfRange, SingleValue(0)))); + addToFunctionSummaryMap( + "__single_val_out_1", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0U, OutOfRange, SingleValue(1)))); + addToFunctionSummaryMap( + "__range_out_1_2", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0U, OutOfRange, Range(1, 2)))); + addToFunctionSummaryMap( + "__range_out_m1_1", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0U, OutOfRange, Range(-1, 1)))); + addToFunctionSummaryMap( + "__range_out_m2_m1", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0U, OutOfRange, Range(-2, -1)))); + addToFunctionSummaryMap( + "__range_out_m10_10", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0U, OutOfRange, Range(-10, 10)))); + addToFunctionSummaryMap("__range_out_m1_inf", + Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition( + 0U, OutOfRange, Range(-1, IntMax)))); + addToFunctionSummaryMap("__range_out_0_inf", + Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition( + 0U, OutOfRange, Range(0, IntMax)))); + addToFunctionSummaryMap("__range_out_1_inf", + Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition( + 0U, OutOfRange, Range(1, IntMax)))); + addToFunctionSummaryMap("__range_out_minf_m1", + Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition( + 0U, OutOfRange, Range(IntMin, -1)))); + addToFunctionSummaryMap("__range_out_minf_0", + Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition( + 0U, OutOfRange, Range(IntMin, 0)))); + addToFunctionSummaryMap("__range_out_minf_1", + Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(ArgumentCondition( + 0U, OutOfRange, Range(IntMin, 1)))); + addToFunctionSummaryMap("__range_out_1_2__4_6", Signature(ArgTypes{IntTy}, RetType{IntTy}), Summary(EvalCallAsPure) .ArgConstraint(ArgumentCondition( - 0U, WithinRange, Range({1, 2}, {4, 5})))); + 0U, OutOfRange, Range({1, 2}, {4, 6})))); + addToFunctionSummaryMap( + "__range_out_1_2__4_inf", Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint( + ArgumentCondition(0U, OutOfRange, Range({1, 2}, {4, IntMax})))); // Test range kind. addToFunctionSummaryMap( @@ -2638,18 +3831,48 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "__test_restrict_param_2"}, Signature(ArgTypes{VoidPtrRestrictTy}, RetType{VoidTy}), Summary(EvalCallAsPure)); - } - SummariesInitialized = true; + // Test the application of cases. + addToFunctionSummaryMap( + "__test_case_note", Signature(ArgTypes{}, RetType{IntTy}), + Summary(EvalCallAsPure) + .Case({ReturnValueCondition(WithinRange, SingleValue(0))}, + ErrnoIrrelevant, "Function returns 0") + .Case({ReturnValueCondition(WithinRange, SingleValue(1))}, + ErrnoIrrelevant, "Function returns 1")); + addToFunctionSummaryMap( + "__test_case_range_1_2__4_6", + Signature(ArgTypes{IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, + IntRangeVector{{IntMin, 0}, {3, 3}}), + ReturnValueCondition(WithinRange, SingleValue(1))}, + ErrnoIrrelevant) + .Case({ArgumentCondition(0U, WithinRange, + IntRangeVector{{3, 3}, {7, IntMax}}), + ReturnValueCondition(WithinRange, SingleValue(2))}, + ErrnoIrrelevant) + .Case({ArgumentCondition(0U, WithinRange, + IntRangeVector{{IntMin, 0}, {7, IntMax}}), + ReturnValueCondition(WithinRange, SingleValue(3))}, + ErrnoIrrelevant) + .Case({ArgumentCondition( + 0U, WithinRange, + IntRangeVector{{IntMin, 0}, {3, 3}, {7, IntMax}}), + ReturnValueCondition(WithinRange, SingleValue(4))}, + ErrnoIrrelevant)); + } } void ento::registerStdCLibraryFunctionsChecker(CheckerManager &mgr) { auto *Checker = mgr.registerChecker<StdLibraryFunctionsChecker>(); + Checker->CheckName = mgr.getCurrentCheckerName(); + const AnalyzerOptions &Opts = mgr.getAnalyzerOptions(); Checker->DisplayLoadedSummaries = - mgr.getAnalyzerOptions().getCheckerBooleanOption( - Checker, "DisplayLoadedSummaries"); - Checker->ModelPOSIX = - mgr.getAnalyzerOptions().getCheckerBooleanOption(Checker, "ModelPOSIX"); + Opts.getCheckerBooleanOption(Checker, "DisplayLoadedSummaries"); + Checker->ModelPOSIX = Opts.getCheckerBooleanOption(Checker, "ModelPOSIX"); + Checker->ShouldAssumeControlledEnvironment = + Opts.ShouldAssumeControlledEnvironment; } bool ento::shouldRegisterStdCLibraryFunctionsChecker( @@ -2657,16 +3880,12 @@ bool ento::shouldRegisterStdCLibraryFunctionsChecker( 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) +void ento::registerStdCLibraryFunctionsTesterChecker(CheckerManager &mgr) { + auto *Checker = mgr.getChecker<StdLibraryFunctionsChecker>(); + Checker->AddTestFunctions = true; +} + +bool ento::shouldRegisterStdCLibraryFunctionsTesterChecker( + const CheckerManager &mgr) { + return true; +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp new file mode 100644 index 000000000000..f7b7befe28ee --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp @@ -0,0 +1,298 @@ +//===- StdVariantChecker.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 "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/CallDescription.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include <optional> +#include <string_view> + +#include "TaggedUnionModeling.h" + +using namespace clang; +using namespace ento; +using namespace tagged_union_modeling; + +REGISTER_MAP_WITH_PROGRAMSTATE(VariantHeldTypeMap, const MemRegion *, QualType) + +namespace clang::ento::tagged_union_modeling { + +const CXXConstructorDecl * +getConstructorDeclarationForCall(const CallEvent &Call) { + const auto *ConstructorCall = dyn_cast<CXXConstructorCall>(&Call); + if (!ConstructorCall) + return nullptr; + + return ConstructorCall->getDecl(); +} + +bool isCopyConstructorCall(const CallEvent &Call) { + if (const CXXConstructorDecl *ConstructorDecl = + getConstructorDeclarationForCall(Call)) + return ConstructorDecl->isCopyConstructor(); + return false; +} + +bool isCopyAssignmentCall(const CallEvent &Call) { + const Decl *CopyAssignmentDecl = Call.getDecl(); + + if (const auto *AsMethodDecl = + dyn_cast_or_null<CXXMethodDecl>(CopyAssignmentDecl)) + return AsMethodDecl->isCopyAssignmentOperator(); + return false; +} + +bool isMoveConstructorCall(const CallEvent &Call) { + const CXXConstructorDecl *ConstructorDecl = + getConstructorDeclarationForCall(Call); + if (!ConstructorDecl) + return false; + + return ConstructorDecl->isMoveConstructor(); +} + +bool isMoveAssignmentCall(const CallEvent &Call) { + const Decl *CopyAssignmentDecl = Call.getDecl(); + + const auto *AsMethodDecl = + dyn_cast_or_null<CXXMethodDecl>(CopyAssignmentDecl); + if (!AsMethodDecl) + return false; + + return AsMethodDecl->isMoveAssignmentOperator(); +} + +bool isStdType(const Type *Type, llvm::StringRef TypeName) { + auto *Decl = Type->getAsRecordDecl(); + if (!Decl) + return false; + return (Decl->getName() == TypeName) && Decl->isInStdNamespace(); +} + +bool isStdVariant(const Type *Type) { + return isStdType(Type, llvm::StringLiteral("variant")); +} + +} // end of namespace clang::ento::tagged_union_modeling + +static std::optional<ArrayRef<TemplateArgument>> +getTemplateArgsFromVariant(const Type *VariantType) { + const auto *TempSpecType = VariantType->getAs<TemplateSpecializationType>(); + if (!TempSpecType) + return {}; + + return TempSpecType->template_arguments(); +} + +static std::optional<QualType> +getNthTemplateTypeArgFromVariant(const Type *varType, unsigned i) { + std::optional<ArrayRef<TemplateArgument>> VariantTemplates = + getTemplateArgsFromVariant(varType); + if (!VariantTemplates) + return {}; + + return (*VariantTemplates)[i].getAsType(); +} + +static bool isVowel(char a) { + switch (a) { + case 'a': + case 'e': + case 'i': + case 'o': + case 'u': + return true; + default: + return false; + } +} + +static llvm::StringRef indefiniteArticleBasedOnVowel(char a) { + if (isVowel(a)) + return "an"; + return "a"; +} + +class StdVariantChecker : public Checker<eval::Call, check::RegionChanges> { + // Call descriptors to find relevant calls + CallDescription VariantConstructor{{"std", "variant", "variant"}}; + CallDescription VariantAssignmentOperator{{"std", "variant", "operator="}}; + CallDescription StdGet{{"std", "get"}, 1, 1}; + + BugType BadVariantType{this, "BadVariantType", "BadVariantType"}; + +public: + ProgramStateRef checkRegionChanges(ProgramStateRef State, + const InvalidatedSymbols *, + ArrayRef<const MemRegion *>, + ArrayRef<const MemRegion *> Regions, + const LocationContext *, + const CallEvent *Call) const { + if (!Call) + return State; + + return removeInformationStoredForDeadInstances<VariantHeldTypeMap>( + *Call, State, Regions); + } + + bool evalCall(const CallEvent &Call, CheckerContext &C) const { + // Check if the call was not made from a system header. If it was then + // we do an early return because it is part of the implementation. + if (Call.isCalledFromSystemHeader()) + return false; + + if (StdGet.matches(Call)) + return handleStdGetCall(Call, C); + + // First check if a constructor call is happening. If it is a + // constructor call, check if it is an std::variant constructor call. + bool IsVariantConstructor = + isa<CXXConstructorCall>(Call) && VariantConstructor.matches(Call); + bool IsVariantAssignmentOperatorCall = + isa<CXXMemberOperatorCall>(Call) && + VariantAssignmentOperator.matches(Call); + + if (IsVariantConstructor || IsVariantAssignmentOperatorCall) { + if (Call.getNumArgs() == 0 && IsVariantConstructor) { + handleDefaultConstructor(cast<CXXConstructorCall>(&Call), C); + return true; + } + + // FIXME Later this checker should be extended to handle constructors + // with multiple arguments. + if (Call.getNumArgs() != 1) + return false; + + SVal ThisSVal; + if (IsVariantConstructor) { + const auto &AsConstructorCall = cast<CXXConstructorCall>(Call); + ThisSVal = AsConstructorCall.getCXXThisVal(); + } else if (IsVariantAssignmentOperatorCall) { + const auto &AsMemberOpCall = cast<CXXMemberOperatorCall>(Call); + ThisSVal = AsMemberOpCall.getCXXThisVal(); + } else { + return false; + } + + handleConstructorAndAssignment<VariantHeldTypeMap>(Call, C, ThisSVal); + return true; + } + return false; + } + +private: + // The default constructed std::variant must be handled separately + // by default the std::variant is going to hold a default constructed instance + // of the first type of the possible types + void handleDefaultConstructor(const CXXConstructorCall *ConstructorCall, + CheckerContext &C) const { + SVal ThisSVal = ConstructorCall->getCXXThisVal(); + + const auto *const ThisMemRegion = ThisSVal.getAsRegion(); + if (!ThisMemRegion) + return; + + std::optional<QualType> DefaultType = getNthTemplateTypeArgFromVariant( + ThisSVal.getType(C.getASTContext())->getPointeeType().getTypePtr(), 0); + if (!DefaultType) + return; + + ProgramStateRef State = ConstructorCall->getState(); + State = State->set<VariantHeldTypeMap>(ThisMemRegion, *DefaultType); + C.addTransition(State); + } + + bool handleStdGetCall(const CallEvent &Call, CheckerContext &C) const { + ProgramStateRef State = Call.getState(); + + const auto &ArgType = Call.getArgSVal(0) + .getType(C.getASTContext()) + ->getPointeeType() + .getTypePtr(); + // We have to make sure that the argument is an std::variant. + // There is another std::get with std::pair argument + if (!isStdVariant(ArgType)) + return false; + + // Get the mem region of the argument std::variant and look up the type + // information that we know about it. + const MemRegion *ArgMemRegion = Call.getArgSVal(0).getAsRegion(); + const QualType *StoredType = State->get<VariantHeldTypeMap>(ArgMemRegion); + if (!StoredType) + return false; + + const CallExpr *CE = cast<CallExpr>(Call.getOriginExpr()); + const FunctionDecl *FD = CE->getDirectCallee(); + if (FD->getTemplateSpecializationArgs()->size() < 1) + return false; + + const auto &TypeOut = FD->getTemplateSpecializationArgs()->asArray()[0]; + // std::get's first template parameter can be the type we want to get + // out of the std::variant or a natural number which is the position of + // the requested type in the argument type list of the std::variant's + // argument. + QualType RetrievedType; + switch (TypeOut.getKind()) { + case TemplateArgument::ArgKind::Type: + RetrievedType = TypeOut.getAsType(); + break; + case TemplateArgument::ArgKind::Integral: + // In the natural number case we look up which type corresponds to the + // number. + if (std::optional<QualType> NthTemplate = + getNthTemplateTypeArgFromVariant( + ArgType, TypeOut.getAsIntegral().getSExtValue())) { + RetrievedType = *NthTemplate; + break; + } + [[fallthrough]]; + default: + return false; + } + + QualType RetrievedCanonicalType = RetrievedType.getCanonicalType(); + QualType StoredCanonicalType = StoredType->getCanonicalType(); + if (RetrievedCanonicalType == StoredCanonicalType) + return true; + + ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); + if (!ErrNode) + return false; + llvm::SmallString<128> Str; + llvm::raw_svector_ostream OS(Str); + std::string StoredTypeName = StoredType->getAsString(); + std::string RetrievedTypeName = RetrievedType.getAsString(); + OS << "std::variant " << ArgMemRegion->getDescriptiveName() << " held " + << indefiniteArticleBasedOnVowel(StoredTypeName[0]) << " \'" + << StoredTypeName << "\', not " + << indefiniteArticleBasedOnVowel(RetrievedTypeName[0]) << " \'" + << RetrievedTypeName << "\'"; + auto R = std::make_unique<PathSensitiveBugReport>(BadVariantType, OS.str(), + ErrNode); + C.emitReport(std::move(R)); + return true; + } +}; + +bool clang::ento::shouldRegisterStdVariantChecker( + clang::ento::CheckerManager const &mgr) { + return true; +} + +void clang::ento::registerStdVariantChecker(clang::ento::CheckerManager &mgr) { + mgr.registerChecker<StdVariantChecker>(); +}
\ No newline at end of file diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp index dd65f8c035aa..07727b339d96 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -14,12 +14,15 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.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/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" #include <functional> +#include <optional> using namespace clang; using namespace ento; @@ -84,10 +87,10 @@ const StreamErrorState ErrorFError{false, false, true}; /// Full state information about a stream pointer. struct StreamState { /// The last file operation called in the stream. + /// Can be nullptr. const FnDescription *LastOperation; /// 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). @@ -145,7 +148,7 @@ struct StreamState { void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddPointer(LastOperation); ID.AddInteger(State); - ID.AddInteger(ErrorState); + ErrorState.Profile(ID); ID.AddBoolean(FilePositionIndeterminate); } }; @@ -201,7 +204,7 @@ ProgramStateRef bindAndAssumeTrue(ProgramStateRef State, CheckerContext &C, ProgramStateRef bindInt(uint64_t Value, ProgramStateRef State, CheckerContext &C, const CallExpr *CE) { State = State->BindExpr(CE, C.getLocationContext(), - C.getSValBuilder().makeIntVal(Value, false)); + C.getSValBuilder().makeIntVal(Value, CE->getType())); return State; } @@ -235,48 +238,96 @@ public: private: CallDescriptionMap<FnDescription> FnDescriptions = { - {{"fopen"}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, - {{"freopen", 3}, + {{{"fopen"}, 2}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, + {{{"fdopen"}, 2}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, + {{{"freopen"}, 3}, {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}}, - {{"tmpfile"}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, - {{"fclose", 1}, + {{{"tmpfile"}, 0}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, + {{{"fclose"}, 1}, {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}}, - {{"fread", 4}, - {&StreamChecker::preFread, + {{{"fread"}, 4}, + {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true), std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, true), 3}}, - {{"fwrite", 4}, - {&StreamChecker::preFwrite, + {{{"fwrite"}, 4}, + {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false), 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}, + {{{"fgetc"}, 1}, + {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true), + std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, true), 0}}, + {{{"fgets"}, 3}, + {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true), + std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, false), 2}}, + {{{"fputc"}, 2}, + {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false), + std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, true), 1}}, + {{{"fputs"}, 2}, + {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false), + std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, false), 1}}, + {{{"fprintf"}}, + {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false), + std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}}, + {{{"fscanf"}}, + {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true), + std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}}, + {{{"ungetc"}, 2}, + {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false), + std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}}, + {{{"getdelim"}, 4}, + {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true), + std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 3}}, + {{{"getline"}, 3}, + {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true), + std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 2}}, + {{{"fseek"}, 3}, + {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}}, + {{{"fseeko"}, 3}, + {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}}, + {{{"ftell"}, 1}, + {&StreamChecker::preDefault, &StreamChecker::evalFtell, 0}}, + {{{"ftello"}, 1}, + {&StreamChecker::preDefault, &StreamChecker::evalFtell, 0}}, + {{{"fflush"}, 1}, + {&StreamChecker::preFflush, &StreamChecker::evalFflush, 0}}, + {{{"rewind"}, 1}, + {&StreamChecker::preDefault, &StreamChecker::evalRewind, 0}}, + {{{"fgetpos"}, 2}, + {&StreamChecker::preDefault, &StreamChecker::evalFgetpos, 0}}, + {{{"fsetpos"}, 2}, + {&StreamChecker::preDefault, &StreamChecker::evalFsetpos, 0}}, + {{{"clearerr"}, 1}, {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}}, - {{"feof", 1}, + {{{"feof"}, 1}, {&StreamChecker::preDefault, std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFEof), 0}}, - {{"ferror", 1}, + {{{"ferror"}, 1}, {&StreamChecker::preDefault, std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFError), 0}}, - {{"fileno", 1}, {&StreamChecker::preDefault, nullptr, 0}}, + {{{"fileno"}, 1}, {&StreamChecker::preDefault, nullptr, 0}}, }; CallDescriptionMap<FnDescription> FnTestDescriptions = { - {{"StreamTesterChecker_make_feof_stream", 1}, + {{{"StreamTesterChecker_make_feof_stream"}, 1}, {nullptr, std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, ErrorFEof), 0}}, - {{"StreamTesterChecker_make_ferror_stream", 1}, + {{{"StreamTesterChecker_make_ferror_stream"}, 1}, {nullptr, std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, ErrorFError), 0}}, }; + /// Expanded value of EOF, empty before initialization. + mutable std::optional<int> EofVal; + /// Expanded value of SEEK_SET, 0 if not found. + mutable int SeekSetVal = 0; + /// Expanded value of SEEK_CUR, 1 if not found. + mutable int SeekCurVal = 1; + /// Expanded value of SEEK_END, 2 if not found. + mutable int SeekEndVal = 2; + void evalFopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; @@ -288,20 +339,47 @@ private: 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 preReadWrite(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C, bool IsRead) const; void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C, bool IsFread) const; + void evalFgetx(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C, bool SingleChar) const; + + void evalFputx(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C, bool IsSingleChar) const; + + void evalFprintf(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void evalFscanf(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void evalUngetc(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void evalGetdelim(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + void preFseek(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; void evalFseek(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; + void evalFgetpos(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void evalFsetpos(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void evalFtell(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void evalRewind(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + void preDefault(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; @@ -316,6 +394,12 @@ private: CheckerContext &C, const StreamErrorState &ErrorKind) const; + void preFflush(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void evalFflush(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) 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 @@ -367,7 +451,7 @@ private: // (and matching name) as stream functions. if (!Call.isGlobalCFunction()) return nullptr; - for (auto P : Call.parameters()) { + for (auto *P : Call.parameters()) { QualType T = P->getType(); if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) return nullptr; @@ -378,23 +462,14 @@ private: /// Generate a message for BugReporterVisitor if the stored symbol is /// marked as interesting by the actual bug report. - // FIXME: Use lambda instead. - struct NoteFn { - const BugType *BT_ResourceLeak; - SymbolRef StreamSym; - std::string Message; - - std::string operator()(PathSensitiveBugReport &BR) const { - if (BR.isInteresting(StreamSym) && &BR.getBugType() == BT_ResourceLeak) - return Message; - - return ""; - } - }; - const NoteTag *constructNoteTag(CheckerContext &C, SymbolRef StreamSym, const std::string &Message) const { - return C.getNoteTag(NoteFn{&BT_ResourceLeak, StreamSym, Message}); + return C.getNoteTag([this, StreamSym, + Message](PathSensitiveBugReport &BR) -> std::string { + if (BR.isInteresting(StreamSym) && &BR.getBugType() == &BT_ResourceLeak) + return Message; + return ""; + }); } const NoteTag *constructSetEofNoteTag(CheckerContext &C, @@ -410,6 +485,26 @@ private: }); } + void initMacroValues(CheckerContext &C) const { + if (EofVal) + return; + + if (const std::optional<int> OptInt = + tryExpandAsInteger("EOF", C.getPreprocessor())) + EofVal = *OptInt; + else + EofVal = -1; + if (const std::optional<int> OptInt = + tryExpandAsInteger("SEEK_SET", C.getPreprocessor())) + SeekSetVal = *OptInt; + if (const std::optional<int> OptInt = + tryExpandAsInteger("SEEK_END", C.getPreprocessor())) + SeekEndVal = *OptInt; + if (const std::optional<int> OptInt = + tryExpandAsInteger("SEEK_CUR", C.getPreprocessor())) + SeekCurVal = *OptInt; + } + /// Searches for the ExplodedNode where the file descriptor was acquired for /// StreamSym. static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N, @@ -425,8 +520,7 @@ private: 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?"); + assert(SS->isOpened() && "Stream is expected to be opened"); } const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N, @@ -456,6 +550,8 @@ const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N, void StreamChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { + initMacroValues(C); + const FnDescription *Desc = lookupFn(Call); if (!Desc || !Desc->PreFn) return; @@ -525,7 +621,7 @@ void StreamChecker::evalFreopen(const FnDescription *Desc, if (!CE) return; - Optional<DefinedSVal> StreamVal = + std::optional<DefinedSVal> StreamVal = getStreamArg(Desc, Call).getAs<DefinedSVal>(); if (!StreamVal) return; @@ -548,8 +644,9 @@ void StreamChecker::evalFreopen(const FnDescription *Desc, State->BindExpr(CE, C.getLocationContext(), *StreamVal); // Generate state for NULL return value. // Stream switches to OpenFailed state. - ProgramStateRef StateRetNull = State->BindExpr(CE, C.getLocationContext(), - C.getSValBuilder().makeNull()); + ProgramStateRef StateRetNull = + State->BindExpr(CE, C.getLocationContext(), + C.getSValBuilder().makeNullWithType(CE->getType())); StateRetNotNull = StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); @@ -572,6 +669,10 @@ void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call, if (!SS) return; + auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return; + assertStreamStateOpened(SS); // Close the File Descriptor. @@ -579,11 +680,21 @@ void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call, // and can not be used any more. State = State->set<StreamMap>(Sym, StreamState::getClosed(Desc)); - C.addTransition(State); + // Return 0 on success, EOF on failure. + SValBuilder &SVB = C.getSValBuilder(); + ProgramStateRef StateSuccess = State->BindExpr( + CE, C.getLocationContext(), SVB.makeIntVal(0, C.getASTContext().IntTy)); + ProgramStateRef StateFailure = + State->BindExpr(CE, C.getLocationContext(), + SVB.makeIntVal(*EofVal, C.getASTContext().IntTy)); + + C.addTransition(StateSuccess); + C.addTransition(StateFailure); } -void StreamChecker::preFread(const FnDescription *Desc, const CallEvent &Call, - CheckerContext &C) const { +void StreamChecker::preReadWrite(const FnDescription *Desc, + const CallEvent &Call, CheckerContext &C, + bool IsRead) const { ProgramStateRef State = C.getState(); SVal StreamVal = getStreamArg(Desc, Call); State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C, @@ -597,6 +708,11 @@ void StreamChecker::preFread(const FnDescription *Desc, const CallEvent &Call, if (!State) return; + if (!IsRead) { + C.addTransition(State); + return; + } + SymbolRef Sym = StreamVal.getAsSymbol(); if (Sym && State->get<StreamMap>(Sym)) { const StreamState *SS = State->get<StreamMap>(Sym); @@ -607,24 +723,6 @@ void StreamChecker::preFread(const FnDescription *Desc, const CallEvent &Call, } } -void StreamChecker::preFwrite(const FnDescription *Desc, const CallEvent &Call, - CheckerContext &C) const { - ProgramStateRef State = C.getState(); - SVal StreamVal = getStreamArg(Desc, Call); - State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), 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 { @@ -637,10 +735,10 @@ void StreamChecker::evalFreadFwrite(const FnDescription *Desc, if (!CE) return; - Optional<NonLoc> SizeVal = Call.getArgSVal(1).getAs<NonLoc>(); + std::optional<NonLoc> SizeVal = Call.getArgSVal(1).getAs<NonLoc>(); if (!SizeVal) return; - Optional<NonLoc> NMembVal = Call.getArgSVal(2).getAs<NonLoc>(); + std::optional<NonLoc> NMembVal = Call.getArgSVal(2).getAs<NonLoc>(); if (!NMembVal) return; @@ -670,24 +768,19 @@ void StreamChecker::evalFreadFwrite(const FnDescription *Desc, if (!IsFread || (OldSS->ErrorState != ErrorFEof)) { ProgramStateRef StateNotFailed = State->BindExpr(CE, C.getLocationContext(), *NMembVal); - if (StateNotFailed) { - StateNotFailed = StateNotFailed->set<StreamMap>( - StreamSym, StreamState::getOpened(Desc)); - C.addTransition(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."); + NonLoc RetVal = makeRetVal(C, CE).castAs<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>(); + State->BindExpr(CE, C.getLocationContext(), RetVal); + SValBuilder &SVB = C.getSValBuilder(); + auto Cond = + SVB.evalBinOpNN(State, BO_LT, RetVal, *NMembVal, SVB.getConditionType()) + .getAs<DefinedOrUnknownSVal>(); if (!Cond) return; StateFailed = StateFailed->assume(*Cond, true); @@ -710,6 +803,351 @@ void StreamChecker::evalFreadFwrite(const FnDescription *Desc, C.addTransition(StateFailed); } +void StreamChecker::evalFgetx(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C, bool SingleChar) 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 *OldSS = State->get<StreamMap>(StreamSym); + if (!OldSS) + return; + + assertStreamStateOpened(OldSS); + + // `fgetc` returns the read character on success, otherwise returns EOF. + // `fgets` returns the read buffer address on success, otherwise returns NULL. + + if (OldSS->ErrorState != ErrorFEof) { + if (SingleChar) { + // Generate a transition for the success state of `fgetc`. + NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>(); + ProgramStateRef StateNotFailed = + State->BindExpr(CE, C.getLocationContext(), RetVal); + SValBuilder &SVB = C.getSValBuilder(); + ASTContext &ASTC = C.getASTContext(); + // The returned 'unsigned char' of `fgetc` is converted to 'int', + // so we need to check if it is in range [0, 255]. + auto CondLow = + SVB.evalBinOp(State, BO_GE, RetVal, SVB.makeZeroVal(ASTC.IntTy), + SVB.getConditionType()) + .getAs<DefinedOrUnknownSVal>(); + auto CondHigh = + SVB.evalBinOp(State, BO_LE, RetVal, + SVB.makeIntVal(SVB.getBasicValueFactory() + .getMaxValue(ASTC.UnsignedCharTy) + .getLimitedValue(), + ASTC.IntTy), + SVB.getConditionType()) + .getAs<DefinedOrUnknownSVal>(); + if (!CondLow || !CondHigh) + return; + StateNotFailed = StateNotFailed->assume(*CondLow, true); + if (!StateNotFailed) + return; + StateNotFailed = StateNotFailed->assume(*CondHigh, true); + if (!StateNotFailed) + return; + C.addTransition(StateNotFailed); + } else { + // Generate a transition for the success state of `fgets`. + std::optional<DefinedSVal> GetBuf = + Call.getArgSVal(0).getAs<DefinedSVal>(); + if (!GetBuf) + return; + ProgramStateRef StateNotFailed = + State->BindExpr(CE, C.getLocationContext(), *GetBuf); + StateNotFailed = StateNotFailed->set<StreamMap>( + StreamSym, StreamState::getOpened(Desc)); + C.addTransition(StateNotFailed); + } + } + + // Add transition for the failed state. + ProgramStateRef StateFailed; + if (SingleChar) + StateFailed = bindInt(*EofVal, State, C, CE); + else + StateFailed = + State->BindExpr(CE, C.getLocationContext(), + C.getSValBuilder().makeNullWithType(CE->getType())); + + // If a (non-EOF) error occurs, the resulting value of the file position + // indicator for the stream is indeterminate. + StreamErrorState NewES = + OldSS->ErrorState == ErrorFEof ? ErrorFEof : ErrorFEof | ErrorFError; + StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof()); + StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS); + if (OldSS->ErrorState != ErrorFEof) + C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym)); + else + C.addTransition(StateFailed); +} + +void StreamChecker::evalFputx(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C, bool IsSingleChar) 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 *OldSS = State->get<StreamMap>(StreamSym); + if (!OldSS) + return; + + assertStreamStateOpened(OldSS); + + // `fputc` returns the written character on success, otherwise returns EOF. + // `fputs` returns a non negative value on sucecess, otherwise returns EOF. + + if (IsSingleChar) { + // Generate a transition for the success state of `fputc`. + std::optional<NonLoc> PutVal = Call.getArgSVal(0).getAs<NonLoc>(); + if (!PutVal) + return; + ProgramStateRef StateNotFailed = + State->BindExpr(CE, C.getLocationContext(), *PutVal); + StateNotFailed = + StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); + C.addTransition(StateNotFailed); + } else { + // Generate a transition for the success state of `fputs`. + NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>(); + ProgramStateRef StateNotFailed = + State->BindExpr(CE, C.getLocationContext(), RetVal); + SValBuilder &SVB = C.getSValBuilder(); + auto &ASTC = C.getASTContext(); + auto Cond = SVB.evalBinOp(State, BO_GE, RetVal, SVB.makeZeroVal(ASTC.IntTy), + SVB.getConditionType()) + .getAs<DefinedOrUnknownSVal>(); + if (!Cond) + return; + StateNotFailed = StateNotFailed->assume(*Cond, true); + if (!StateNotFailed) + return; + StateNotFailed = + StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); + C.addTransition(StateNotFailed); + } + + // Add transition for the failed state. The resulting value of the file + // position indicator for the stream is indeterminate. + ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE); + StreamState NewSS = StreamState::getOpened(Desc, ErrorFError, true); + StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS); + C.addTransition(StateFailed); +} + +void StreamChecker::evalFprintf(const FnDescription *Desc, + const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + if (Call.getNumArgs() < 2) + return; + SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); + if (!StreamSym) + return; + + const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return; + + const StreamState *OldSS = State->get<StreamMap>(StreamSym); + if (!OldSS) + return; + + assertStreamStateOpened(OldSS); + + NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>(); + State = State->BindExpr(CE, C.getLocationContext(), RetVal); + SValBuilder &SVB = C.getSValBuilder(); + auto &ACtx = C.getASTContext(); + auto Cond = SVB.evalBinOp(State, BO_GE, RetVal, SVB.makeZeroVal(ACtx.IntTy), + SVB.getConditionType()) + .getAs<DefinedOrUnknownSVal>(); + if (!Cond) + return; + ProgramStateRef StateNotFailed, StateFailed; + std::tie(StateNotFailed, StateFailed) = State->assume(*Cond); + + StateNotFailed = + StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); + C.addTransition(StateNotFailed); + + // Add transition for the failed state. The resulting value of the file + // position indicator for the stream is indeterminate. + StateFailed = StateFailed->set<StreamMap>( + StreamSym, StreamState::getOpened(Desc, ErrorFError, true)); + C.addTransition(StateFailed); +} + +void StreamChecker::evalFscanf(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + if (Call.getNumArgs() < 2) + return; + SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); + if (!StreamSym) + return; + + const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return; + + const StreamState *OldSS = State->get<StreamMap>(StreamSym); + if (!OldSS) + return; + + assertStreamStateOpened(OldSS); + + SValBuilder &SVB = C.getSValBuilder(); + ASTContext &ACtx = C.getASTContext(); + + // Add the success state. + // In this context "success" means there is not an EOF or other read error + // before any item is matched in 'fscanf'. But there may be match failure, + // therefore return value can be 0 or greater. + // It is not specified what happens if some items (not all) are matched and + // then EOF or read error happens. Now this case is handled like a "success" + // case, and no error flags are set on the stream. This is probably not + // accurate, and the POSIX documentation does not tell more. + if (OldSS->ErrorState != ErrorFEof) { + NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>(); + ProgramStateRef StateNotFailed = + State->BindExpr(CE, C.getLocationContext(), RetVal); + auto RetGeZero = + SVB.evalBinOp(StateNotFailed, BO_GE, RetVal, + SVB.makeZeroVal(ACtx.IntTy), SVB.getConditionType()) + .getAs<DefinedOrUnknownSVal>(); + if (!RetGeZero) + return; + StateNotFailed = StateNotFailed->assume(*RetGeZero, true); + + C.addTransition(StateNotFailed); + } + + // Add transition for the failed state. + // Error occurs if nothing is matched yet and reading the input fails. + // Error can be EOF, or other error. At "other error" FERROR or 'errno' can + // be set but it is not further specified if all are required to be set. + // Documentation does not mention, but file position will be set to + // indeterminate similarly as at 'fread'. + ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE); + StreamErrorState NewES = (OldSS->ErrorState == ErrorFEof) + ? ErrorFEof + : ErrorNone | ErrorFEof | ErrorFError; + StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof()); + StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS); + if (OldSS->ErrorState != ErrorFEof) + C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym)); + else + C.addTransition(StateFailed); +} + +void StreamChecker::evalUngetc(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; + + const StreamState *OldSS = State->get<StreamMap>(StreamSym); + if (!OldSS) + return; + + assertStreamStateOpened(OldSS); + + // Generate a transition for the success state. + std::optional<NonLoc> PutVal = Call.getArgSVal(0).getAs<NonLoc>(); + if (!PutVal) + return; + ProgramStateRef StateNotFailed = + State->BindExpr(CE, C.getLocationContext(), *PutVal); + StateNotFailed = + StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); + C.addTransition(StateNotFailed); + + // Add transition for the failed state. + // Failure of 'ungetc' does not result in feof or ferror state. + // If the PutVal has value of EofVal the function should "fail", but this is + // the same transition as the success state. + // In this case only one state transition is added by the analyzer (the two + // new states may be similar). + ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE); + StateFailed = + StateFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); + C.addTransition(StateFailed); +} + +void StreamChecker::evalGetdelim(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; + + const StreamState *OldSS = State->get<StreamMap>(StreamSym); + if (!OldSS) + return; + + assertStreamStateOpened(OldSS); + + // Upon successful completion, the getline() and getdelim() functions shall + // return the number of bytes written into the buffer. + // If the end-of-file indicator for the stream is set, the function shall + // return -1. + // If an error occurs, the function shall return -1 and set 'errno'. + + // Add transition for the successful state. + if (OldSS->ErrorState != ErrorFEof) { + NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>(); + ProgramStateRef StateNotFailed = + State->BindExpr(CE, C.getLocationContext(), RetVal); + SValBuilder &SVB = C.getSValBuilder(); + auto Cond = + SVB.evalBinOp(State, BO_GE, RetVal, SVB.makeZeroVal(CE->getType()), + SVB.getConditionType()) + .getAs<DefinedOrUnknownSVal>(); + if (!Cond) + return; + StateNotFailed = StateNotFailed->assume(*Cond, true); + if (!StateNotFailed) + return; + C.addTransition(StateNotFailed); + } + + // Add transition for the failed state. + // If a (non-EOF) error occurs, the resulting value of the file position + // indicator for the stream is indeterminate. + ProgramStateRef StateFailed = bindInt(-1, State, C, CE); + StreamErrorState NewES = + OldSS->ErrorState == ErrorFEof ? ErrorFEof : ErrorFEof | ErrorFError; + StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof()); + StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS); + if (OldSS->ErrorState != ErrorFEof) + C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym)); + else + C.addTransition(StateFailed); +} + void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); @@ -743,6 +1181,11 @@ void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call, if (!State->get<StreamMap>(StreamSym)) return; + const llvm::APSInt *PosV = + C.getSValBuilder().getKnownValue(State, Call.getArgSVal(1)); + const llvm::APSInt *WhenceV = + C.getSValBuilder().getKnownValue(State, Call.getArgSVal(2)); + DefinedSVal RetVal = makeRetVal(C, CE); // Make expression result. @@ -761,14 +1204,145 @@ void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call, // 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. + StreamErrorState NewErrS = ErrorNone | ErrorFError; + // Setting the position to start of file never produces EOF error. + if (!(PosV && *PosV == 0 && WhenceV && *WhenceV == SeekSetVal)) + NewErrS = NewErrS | ErrorFEof; StateFailed = StateFailed->set<StreamMap>( - StreamSym, - StreamState::getOpened(Desc, ErrorNone | ErrorFEof | ErrorFError, true)); + StreamSym, StreamState::getOpened(Desc, NewErrS, true)); C.addTransition(StateNotFailed); C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym)); } +void StreamChecker::evalFgetpos(const FnDescription *Desc, + const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol(); + if (!Sym) + return; + + // Do not evaluate if stream is not found. + if (!State->get<StreamMap>(Sym)) + return; + + auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return; + + DefinedSVal RetVal = makeRetVal(C, CE); + State = State->BindExpr(CE, C.getLocationContext(), RetVal); + ProgramStateRef StateNotFailed, StateFailed; + std::tie(StateFailed, StateNotFailed) = + C.getConstraintManager().assumeDual(State, RetVal); + + // This function does not affect the stream state. + // Still we add success and failure state with the appropriate return value. + // StdLibraryFunctionsChecker can change these states (set the 'errno' state). + C.addTransition(StateNotFailed); + C.addTransition(StateFailed); +} + +void StreamChecker::evalFsetpos(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; + + auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return; + + assertStreamStateOpened(SS); + + DefinedSVal RetVal = makeRetVal(C, CE); + State = State->BindExpr(CE, C.getLocationContext(), RetVal); + ProgramStateRef StateNotFailed, StateFailed; + std::tie(StateFailed, StateNotFailed) = + C.getConstraintManager().assumeDual(State, RetVal); + + StateNotFailed = StateNotFailed->set<StreamMap>( + StreamSym, StreamState::getOpened(Desc, ErrorNone, false)); + + // At failure ferror could be set. + // The standards do not tell what happens with the file position at failure. + // But we can assume that it is dangerous to make a next I/O operation after + // the position was not set correctly (similar to 'fseek'). + StateFailed = StateFailed->set<StreamMap>( + StreamSym, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true)); + + C.addTransition(StateNotFailed); + C.addTransition(StateFailed); +} + +void StreamChecker::evalFtell(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol(); + if (!Sym) + return; + + if (!State->get<StreamMap>(Sym)) + return; + + auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return; + + SValBuilder &SVB = C.getSValBuilder(); + NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>(); + ProgramStateRef StateNotFailed = + State->BindExpr(CE, C.getLocationContext(), RetVal); + auto Cond = + SVB.evalBinOp(State, BO_GE, RetVal, SVB.makeZeroVal(Call.getResultType()), + SVB.getConditionType()) + .getAs<DefinedOrUnknownSVal>(); + if (!Cond) + return; + StateNotFailed = StateNotFailed->assume(*Cond, true); + if (!StateNotFailed) + return; + + ProgramStateRef StateFailed = State->BindExpr( + CE, C.getLocationContext(), SVB.makeIntVal(-1, Call.getResultType())); + + // This function does not affect the stream state. + // Still we add success and failure state with the appropriate return value. + // StdLibraryFunctionsChecker can change these states (set the 'errno' state). + C.addTransition(StateNotFailed); + C.addTransition(StateFailed); +} + +void StreamChecker::evalRewind(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; + + auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return; + + assertStreamStateOpened(SS); + + State = State->set<StreamMap>(StreamSym, + StreamState::getOpened(Desc, ErrorNone, false)); + + C.addTransition(State); +} + void StreamChecker::evalClearerr(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { @@ -858,6 +1432,84 @@ void StreamChecker::evalSetFeofFerror(const FnDescription *Desc, C.addTransition(State); } +void StreamChecker::preFflush(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SVal StreamVal = getStreamArg(Desc, Call); + std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>(); + if (!Stream) + return; + + ProgramStateRef StateNotNull, StateNull; + std::tie(StateNotNull, StateNull) = + C.getConstraintManager().assumeDual(State, *Stream); + if (StateNotNull && !StateNull) + ensureStreamOpened(StreamVal, C, StateNotNull); +} + +void StreamChecker::evalFflush(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SVal StreamVal = getStreamArg(Desc, Call); + std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>(); + if (!Stream) + return; + + // Skip if the stream can be both NULL and non-NULL. + ProgramStateRef StateNotNull, StateNull; + std::tie(StateNotNull, StateNull) = + C.getConstraintManager().assumeDual(State, *Stream); + if (StateNotNull && StateNull) + return; + if (StateNotNull && !StateNull) + State = StateNotNull; + else + State = StateNull; + + const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return; + + // `fflush` returns EOF on failure, otherwise returns 0. + ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE); + ProgramStateRef StateNotFailed = bindInt(0, State, C, CE); + + // Clear error states if `fflush` returns 0, but retain their EOF flags. + auto ClearErrorInNotFailed = [&StateNotFailed, Desc](SymbolRef Sym, + const StreamState *SS) { + if (SS->ErrorState & ErrorFError) { + StreamErrorState NewES = + (SS->ErrorState & ErrorFEof) ? ErrorFEof : ErrorNone; + StreamState NewSS = StreamState::getOpened(Desc, NewES, false); + StateNotFailed = StateNotFailed->set<StreamMap>(Sym, NewSS); + } + }; + + if (StateNotNull && !StateNull) { + // Skip if the input stream's state is unknown, open-failed or closed. + if (SymbolRef StreamSym = StreamVal.getAsSymbol()) { + const StreamState *SS = State->get<StreamMap>(StreamSym); + if (SS) { + assert(SS->isOpened() && "Stream is expected to be opened"); + ClearErrorInNotFailed(StreamSym, SS); + } else + return; + } + } else { + // Clear error states for all streams. + const StreamMapTy &Map = StateNotFailed->get<StreamMap>(); + for (const auto &I : Map) { + SymbolRef Sym = I.first; + const StreamState &SS = I.second; + if (SS.isOpened()) + ClearErrorInNotFailed(Sym, &SS); + } + } + + C.addTransition(StateNotFailed); + C.addTransition(StateFailed); +} + ProgramStateRef StreamChecker::ensureStreamNonNull(SVal StreamVal, const Expr *StreamE, CheckerContext &C, @@ -869,7 +1521,7 @@ StreamChecker::ensureStreamNonNull(SVal StreamVal, const Expr *StreamE, ConstraintManager &CM = C.getConstraintManager(); ProgramStateRef StateNotNull, StateNull; - std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *Stream); + std::tie(StateNotNull, StateNull) = CM.assumeDual(State, *Stream); if (!StateNotNull && StateNull) { if (ExplodedNode *N = C.generateErrorNode(StateNull)) { @@ -925,7 +1577,6 @@ ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal, N)); return nullptr; } - return State; } return State; @@ -979,12 +1630,13 @@ ProgramStateRef StreamChecker::ensureNoFilePositionIndeterminate( ProgramStateRef StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, ProgramStateRef State) const { - Optional<nonloc::ConcreteInt> CI = WhenceVal.getAs<nonloc::ConcreteInt>(); + std::optional<nonloc::ConcreteInt> CI = + WhenceVal.getAs<nonloc::ConcreteInt>(); if (!CI) return State; int64_t X = CI->getValue().getSExtValue(); - if (X >= 0 && X <= 2) + if (X == SeekSetVal || X == SeekCurVal || X == SeekEndVal) return State; if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { @@ -1035,10 +1687,12 @@ StreamChecker::reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms, // FIXME: Add a checker option to turn this uniqueing feature off. const ExplodedNode *StreamOpenNode = getAcquisitionSite(Err, LeakSym, C); assert(StreamOpenNode && "Could not find place of stream opening."); - PathDiagnosticLocation LocUsedForUniqueing = - PathDiagnosticLocation::createBegin( - StreamOpenNode->getStmtForDiagnostics(), C.getSourceManager(), - StreamOpenNode->getLocationContext()); + + PathDiagnosticLocation LocUsedForUniqueing; + if (const Stmt *StreamStmt = StreamOpenNode->getStmtForDiagnostics()) + LocUsedForUniqueing = PathDiagnosticLocation::createBegin( + StreamStmt, C.getSourceManager(), + StreamOpenNode->getLocationContext()); std::unique_ptr<PathSensitiveBugReport> R = std::make_unique<PathSensitiveBugReport>( @@ -1118,4 +1772,4 @@ void ento::registerStreamTesterChecker(CheckerManager &Mgr) { bool ento::shouldRegisterStreamTesterChecker(const CheckerManager &Mgr) { return true; -}
\ No newline at end of file +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StringChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StringChecker.cpp new file mode 100644 index 000000000000..2dc9e29ca906 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StringChecker.cpp @@ -0,0 +1,105 @@ +//=== StringChecker.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 implements the modeling of the std::basic_string type. +// This involves checking preconditions of the operations and applying the +// effects of the operations, e.g. their post-conditions. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +namespace { +class StringChecker : public Checker<check::PreCall> { + BugType BT_Null{this, "Dereference of null pointer", categories::LogicError}; + mutable const FunctionDecl *StringConstCharPtrCtor = nullptr; + mutable CanQualType SizeTypeTy; + const CallDescription TwoParamStdStringCtor = { + {"std", "basic_string", "basic_string"}, 2, 2}; + + bool isCharToStringCtor(const CallEvent &Call, const ASTContext &ACtx) const; + +public: + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; +}; + +bool StringChecker::isCharToStringCtor(const CallEvent &Call, + const ASTContext &ACtx) const { + if (!TwoParamStdStringCtor.matches(Call)) + return false; + const auto *FD = dyn_cast<FunctionDecl>(Call.getDecl()); + assert(FD); + + // See if we already cached it. + if (StringConstCharPtrCtor && StringConstCharPtrCtor == FD) + return true; + + // Verify that the parameters have the expected types: + // - arg 1: `const CharT *` + // - arg 2: some allocator - which is definately not `size_t`. + const QualType Arg1Ty = Call.getArgExpr(0)->getType().getCanonicalType(); + const QualType Arg2Ty = Call.getArgExpr(1)->getType().getCanonicalType(); + + if (!Arg1Ty->isPointerType()) + return false; + + // It makes sure that we don't select the `string(const char* p, size_t len)` + // overload accidentally. + if (Arg2Ty.getCanonicalType() == ACtx.getSizeType()) + return false; + + StringConstCharPtrCtor = FD; // Cache the decl of the right overload. + return true; +} + +void StringChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + if (!isCharToStringCtor(Call, C.getASTContext())) + return; + const auto Param = Call.getArgSVal(0).getAs<Loc>(); + if (!Param) + return; + + // We managed to constrain the parameter to non-null. + ProgramStateRef NotNull, Null; + std::tie(NotNull, Null) = C.getState()->assume(*Param); + + if (NotNull) { + const auto Callback = [Param](PathSensitiveBugReport &BR) -> std::string { + return BR.isInteresting(*Param) ? "Assuming the pointer is not null." + : ""; + }; + + // Emit note only if this operation constrained the pointer to be null. + C.addTransition(NotNull, Null ? C.getNoteTag(Callback) : nullptr); + return; + } + + // We found a path on which the parameter is NULL. + if (ExplodedNode *N = C.generateErrorNode(C.getState())) { + auto R = std::make_unique<PathSensitiveBugReport>( + BT_Null, "The parameter must not be null", N); + bugreporter::trackExpressionValue(N, Call.getArgExpr(0), *R); + C.emitReport(std::move(R)); + } +} + +} // end anonymous namespace + +void ento::registerStringChecker(CheckerManager &Mgr) { + Mgr.registerChecker<StringChecker>(); +} + +bool ento::shouldRegisterStringChecker(const CheckerManager &) { return true; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h new file mode 100644 index 000000000000..6de33da107a3 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TaggedUnionModeling.h @@ -0,0 +1,99 @@ +//===- TaggedUnionModeling.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_LIB_STATICANALYZER_CHECKERS_TAGGEDUNIONMODELING_H +#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_TAGGEDUNIONMODELING_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/CallDescription.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/ADT/FoldingSet.h" +#include <numeric> + +namespace clang::ento::tagged_union_modeling { + +// The implementation of all these functions can be found in the file +// StdVariantChecker.cpp under the same directory as this file. + +bool isCopyConstructorCall(const CallEvent &Call); +bool isCopyAssignmentCall(const CallEvent &Call); +bool isMoveAssignmentCall(const CallEvent &Call); +bool isMoveConstructorCall(const CallEvent &Call); +bool isStdType(const Type *Type, const std::string &TypeName); +bool isStdVariant(const Type *Type); + +// When invalidating regions, we also have to follow that by invalidating the +// corresponding custom data in the program state. +template <class TypeMap> +ProgramStateRef +removeInformationStoredForDeadInstances(const CallEvent &Call, + ProgramStateRef State, + ArrayRef<const MemRegion *> Regions) { + // If we do not know anything about the call we shall not continue. + // If the call is happens within a system header it is implementation detail. + // We should not take it into consideration. + if (Call.isInSystemHeader()) + return State; + + for (const MemRegion *Region : Regions) + State = State->remove<TypeMap>(Region); + + return State; +} + +template <class TypeMap> +void handleConstructorAndAssignment(const CallEvent &Call, CheckerContext &C, + SVal ThisSVal) { + ProgramStateRef State = Call.getState(); + + if (!State) + return; + + auto ArgSVal = Call.getArgSVal(0); + const auto *ThisRegion = ThisSVal.getAsRegion(); + const auto *ArgMemRegion = ArgSVal.getAsRegion(); + + // Make changes to the state according to type of constructor/assignment + bool IsCopy = isCopyConstructorCall(Call) || isCopyAssignmentCall(Call); + bool IsMove = isMoveConstructorCall(Call) || isMoveAssignmentCall(Call); + // First we handle copy and move operations + if (IsCopy || IsMove) { + const QualType *OtherQType = State->get<TypeMap>(ArgMemRegion); + + // If the argument of a copy constructor or assignment is unknown then + // we will not know the argument of the copied to object. + if (!OtherQType) { + State = State->remove<TypeMap>(ThisRegion); + } else { + // When move semantics is used we can only know that the moved from + // object must be in a destructible state. Other usage of the object + // than destruction is undefined. + if (IsMove) + State = State->remove<TypeMap>(ArgMemRegion); + + State = State->set<TypeMap>(ThisRegion, *OtherQType); + } + } else { + // Value constructor + auto ArgQType = ArgSVal.getType(C.getASTContext()); + const Type *ArgTypePtr = ArgQType.getTypePtr(); + + QualType WoPointer = ArgTypePtr->getPointeeType(); + State = State->set<TypeMap>(ThisRegion, WoPointer); + } + + C.addTransition(State); +} + +} // namespace clang::ento::tagged_union_modeling + +#endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_TAGGEDUNIONMODELING_H
\ No newline at end of file diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Taint.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Taint.cpp index 71b2ab834a07..4edb671753bf 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Taint.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Taint.cpp @@ -10,9 +10,10 @@ // //===----------------------------------------------------------------------===// -#include "Taint.h" +#include "clang/StaticAnalyzer/Checkers/Taint.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include <optional> using namespace clang; using namespace ento; @@ -37,7 +38,9 @@ void taint::printTaint(ProgramStateRef State, raw_ostream &Out, const char *NL, Out << I.first << " : " << I.second << NL; } -void dumpTaint(ProgramStateRef State) { printTaint(State, llvm::errs()); } +void taint::dumpTaint(ProgramStateRef State) { + printTaint(State, llvm::errs()); +} ProgramStateRef taint::addTaint(ProgramStateRef State, const Stmt *S, const LocationContext *LCtx, @@ -61,7 +64,7 @@ ProgramStateRef taint::addTaint(ProgramStateRef State, SVal V, // their parent region, which is a conjured symbol default-bound to the base // region of the parent region. if (auto LCV = V.getAs<nonloc::LazyCompoundVal>()) { - if (Optional<SVal> binding = + if (std::optional<SVal> binding = State->getStateManager().getStoreManager().getDefaultBinding( *LCV)) { if (SymbolRef Sym = binding->getAsSymbol()) @@ -143,62 +146,142 @@ ProgramStateRef taint::addPartialTaint(ProgramStateRef State, bool taint::isTainted(ProgramStateRef State, const Stmt *S, const LocationContext *LCtx, TaintTagType Kind) { - SVal val = State->getSVal(S, LCtx); - return isTainted(State, val, Kind); + return !getTaintedSymbolsImpl(State, S, LCtx, Kind, /*ReturnFirstOnly=*/true) + .empty(); } bool taint::isTainted(ProgramStateRef State, SVal V, TaintTagType Kind) { - if (SymbolRef Sym = V.getAsSymbol()) - return isTainted(State, Sym, Kind); - if (const MemRegion *Reg = V.getAsRegion()) - return isTainted(State, Reg, Kind); - return false; + return !getTaintedSymbolsImpl(State, V, Kind, /*ReturnFirstOnly=*/true) + .empty(); } bool taint::isTainted(ProgramStateRef State, const MemRegion *Reg, TaintTagType K) { - if (!Reg) - return false; + return !getTaintedSymbolsImpl(State, Reg, K, /*ReturnFirstOnly=*/true) + .empty(); +} + +bool taint::isTainted(ProgramStateRef State, SymbolRef Sym, TaintTagType Kind) { + return !getTaintedSymbolsImpl(State, Sym, Kind, /*ReturnFirstOnly=*/true) + .empty(); +} + +std::vector<SymbolRef> taint::getTaintedSymbols(ProgramStateRef State, + const Stmt *S, + const LocationContext *LCtx, + TaintTagType Kind) { + return getTaintedSymbolsImpl(State, S, LCtx, Kind, /*ReturnFirstOnly=*/false); +} + +std::vector<SymbolRef> taint::getTaintedSymbols(ProgramStateRef State, SVal V, + TaintTagType Kind) { + return getTaintedSymbolsImpl(State, V, Kind, /*ReturnFirstOnly=*/false); +} + +std::vector<SymbolRef> taint::getTaintedSymbols(ProgramStateRef State, + SymbolRef Sym, + TaintTagType Kind) { + return getTaintedSymbolsImpl(State, Sym, Kind, /*ReturnFirstOnly=*/false); +} + +std::vector<SymbolRef> taint::getTaintedSymbols(ProgramStateRef State, + const MemRegion *Reg, + TaintTagType Kind) { + return getTaintedSymbolsImpl(State, Reg, Kind, /*ReturnFirstOnly=*/false); +} + +std::vector<SymbolRef> taint::getTaintedSymbolsImpl(ProgramStateRef State, + const Stmt *S, + const LocationContext *LCtx, + TaintTagType Kind, + bool returnFirstOnly) { + SVal val = State->getSVal(S, LCtx); + return getTaintedSymbolsImpl(State, val, Kind, returnFirstOnly); +} + +std::vector<SymbolRef> taint::getTaintedSymbolsImpl(ProgramStateRef State, + SVal V, TaintTagType Kind, + bool returnFirstOnly) { + if (SymbolRef Sym = V.getAsSymbol()) + return getTaintedSymbolsImpl(State, Sym, Kind, returnFirstOnly); + if (const MemRegion *Reg = V.getAsRegion()) + return getTaintedSymbolsImpl(State, Reg, Kind, returnFirstOnly); + return {}; +} +std::vector<SymbolRef> taint::getTaintedSymbolsImpl(ProgramStateRef State, + const MemRegion *Reg, + TaintTagType K, + bool returnFirstOnly) { + std::vector<SymbolRef> TaintedSymbols; + if (!Reg) + return TaintedSymbols; // Element region (array element) is tainted if either the base or the offset // are tainted. - if (const ElementRegion *ER = dyn_cast<ElementRegion>(Reg)) - return isTainted(State, ER->getSuperRegion(), K) || - isTainted(State, ER->getIndex(), K); + if (const ElementRegion *ER = dyn_cast<ElementRegion>(Reg)) { + std::vector<SymbolRef> TaintedIndex = + getTaintedSymbolsImpl(State, ER->getIndex(), K, returnFirstOnly); + llvm::append_range(TaintedSymbols, TaintedIndex); + if (returnFirstOnly && !TaintedSymbols.empty()) + return TaintedSymbols; // return early if needed + std::vector<SymbolRef> TaintedSuperRegion = + getTaintedSymbolsImpl(State, ER->getSuperRegion(), K, returnFirstOnly); + llvm::append_range(TaintedSymbols, TaintedSuperRegion); + if (returnFirstOnly && !TaintedSymbols.empty()) + return TaintedSymbols; // return early if needed + } - if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(Reg)) - return isTainted(State, SR->getSymbol(), K); + if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(Reg)) { + std::vector<SymbolRef> TaintedRegions = + getTaintedSymbolsImpl(State, SR->getSymbol(), K, returnFirstOnly); + llvm::append_range(TaintedSymbols, TaintedRegions); + if (returnFirstOnly && !TaintedSymbols.empty()) + return TaintedSymbols; // return early if needed + } - if (const SubRegion *ER = dyn_cast<SubRegion>(Reg)) - return isTainted(State, ER->getSuperRegion(), K); + if (const SubRegion *ER = dyn_cast<SubRegion>(Reg)) { + std::vector<SymbolRef> TaintedSubRegions = + getTaintedSymbolsImpl(State, ER->getSuperRegion(), K, returnFirstOnly); + llvm::append_range(TaintedSymbols, TaintedSubRegions); + if (returnFirstOnly && !TaintedSymbols.empty()) + return TaintedSymbols; // return early if needed + } - return false; + return TaintedSymbols; } -bool taint::isTainted(ProgramStateRef State, SymbolRef Sym, TaintTagType Kind) { +std::vector<SymbolRef> taint::getTaintedSymbolsImpl(ProgramStateRef State, + SymbolRef Sym, + TaintTagType Kind, + bool returnFirstOnly) { + std::vector<SymbolRef> TaintedSymbols; if (!Sym) - return false; + return TaintedSymbols; // Traverse all the symbols this symbol depends on to see if any are tainted. - for (SymExpr::symbol_iterator SI = Sym->symbol_begin(), - SE = Sym->symbol_end(); - SI != SE; ++SI) { - if (!isa<SymbolData>(*SI)) + for (SymbolRef SubSym : Sym->symbols()) { + if (!isa<SymbolData>(SubSym)) continue; - if (const TaintTagType *Tag = State->get<TaintMap>(*SI)) { - if (*Tag == Kind) - return true; + if (const TaintTagType *Tag = State->get<TaintMap>(SubSym)) { + if (*Tag == Kind) { + TaintedSymbols.push_back(SubSym); + if (returnFirstOnly) + return TaintedSymbols; // return early if needed + } } - if (const auto *SD = dyn_cast<SymbolDerived>(*SI)) { + if (const auto *SD = dyn_cast<SymbolDerived>(SubSym)) { // If this is a SymbolDerived with a tainted parent, it's also tainted. - if (isTainted(State, SD->getParentSymbol(), Kind)) - return true; + std::vector<SymbolRef> TaintedParents = getTaintedSymbolsImpl( + State, SD->getParentSymbol(), Kind, returnFirstOnly); + llvm::append_range(TaintedSymbols, TaintedParents); + if (returnFirstOnly && !TaintedSymbols.empty()) + return TaintedSymbols; // return early if needed // If this is a SymbolDerived with the same parent symbol as another - // tainted SymbolDerived and a region that's a sub-region of that tainted - // symbol, it's also tainted. + // tainted SymbolDerived and a region that's a sub-region of that + // tainted symbol, it's also tainted. if (const TaintedSubRegions *Regs = State->get<DerivedSymTaint>(SD->getParentSymbol())) { const TypedValueRegion *R = SD->getRegion(); @@ -207,46 +290,32 @@ bool taint::isTainted(ProgramStateRef State, SymbolRef Sym, TaintTagType Kind) { // complete. For example, this would not currently identify // overlapping fields in a union as tainted. To identify this we can // check for overlapping/nested byte offsets. - if (Kind == I.second && R->isSubRegionOf(I.first)) - return true; + if (Kind == I.second && R->isSubRegionOf(I.first)) { + TaintedSymbols.push_back(SD->getParentSymbol()); + if (returnFirstOnly && !TaintedSymbols.empty()) + return TaintedSymbols; // return early if needed + } } } } // If memory region is tainted, data is also tainted. - if (const auto *SRV = dyn_cast<SymbolRegionValue>(*SI)) { - if (isTainted(State, SRV->getRegion(), Kind)) - return true; + if (const auto *SRV = dyn_cast<SymbolRegionValue>(SubSym)) { + std::vector<SymbolRef> TaintedRegions = + getTaintedSymbolsImpl(State, SRV->getRegion(), Kind, returnFirstOnly); + llvm::append_range(TaintedSymbols, TaintedRegions); + if (returnFirstOnly && !TaintedSymbols.empty()) + return TaintedSymbols; // return early if needed } // If this is a SymbolCast from a tainted value, it's also tainted. - if (const auto *SC = dyn_cast<SymbolCast>(*SI)) { - if (isTainted(State, SC->getOperand(), Kind)) - return true; + if (const auto *SC = dyn_cast<SymbolCast>(SubSym)) { + std::vector<SymbolRef> TaintedCasts = + getTaintedSymbolsImpl(State, SC->getOperand(), Kind, returnFirstOnly); + llvm::append_range(TaintedSymbols, TaintedCasts); + if (returnFirstOnly && !TaintedSymbols.empty()) + return TaintedSymbols; // return early if needed } } - - return false; -} - -PathDiagnosticPieceRef TaintBugVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - PathSensitiveBugReport &BR) { - - // Find the ExplodedNode where the taint was first introduced - if (!isTainted(N->getState(), V) || - isTainted(N->getFirstPred()->getState(), V)) - return nullptr; - - const Stmt *S = N->getStmtForDiagnostics(); - if (!S) - return nullptr; - - const LocationContext *NCtx = N->getLocationContext(); - PathDiagnosticLocation L = - PathDiagnosticLocation::createBegin(S, BRC.getSourceManager(), NCtx); - if (!L.isValid() || !L.asLocation().isValid()) - return nullptr; - - return std::make_shared<PathDiagnosticEventPiece>(L, "Taint originated here"); -} + return TaintedSymbols; +}
\ No newline at end of file diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Taint.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Taint.h deleted file mode 100644 index 659a3c898d56..000000000000 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Taint.h +++ /dev/null @@ -1,106 +0,0 @@ -//=== Taint.h - Taint tracking and basic propagation rules. --------*- 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 basic, non-domain-specific mechanisms for tracking tainted values. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_TAINT_H -#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_TAINT_H - -#include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" - -namespace clang { -namespace ento { -namespace taint { - -/// The type of taint, which helps to differentiate between different types of -/// taint. -using TaintTagType = unsigned; - -static constexpr TaintTagType TaintTagGeneric = 0; - -/// Create a new state in which the value of the statement is marked as tainted. -LLVM_NODISCARD ProgramStateRef addTaint(ProgramStateRef State, const Stmt *S, - const LocationContext *LCtx, - TaintTagType Kind = TaintTagGeneric); - -/// Create a new state in which the value is marked as tainted. -LLVM_NODISCARD ProgramStateRef addTaint(ProgramStateRef State, SVal V, - TaintTagType Kind = TaintTagGeneric); - -/// Create a new state in which the symbol is marked as tainted. -LLVM_NODISCARD ProgramStateRef addTaint(ProgramStateRef State, SymbolRef Sym, - TaintTagType Kind = TaintTagGeneric); - -/// Create a new state in which the pointer represented by the region -/// is marked as tainted. -LLVM_NODISCARD ProgramStateRef addTaint(ProgramStateRef State, - const MemRegion *R, - TaintTagType Kind = TaintTagGeneric); - -LLVM_NODISCARD ProgramStateRef removeTaint(ProgramStateRef State, SVal V); - -LLVM_NODISCARD ProgramStateRef removeTaint(ProgramStateRef State, - const MemRegion *R); - -LLVM_NODISCARD ProgramStateRef removeTaint(ProgramStateRef State, - SymbolRef Sym); - -/// Create a new state in a which a sub-region of a given symbol is tainted. -/// This might be necessary when referring to regions that can not have an -/// individual symbol, e.g. if they are represented by the default binding of -/// a LazyCompoundVal. -LLVM_NODISCARD ProgramStateRef addPartialTaint( - ProgramStateRef State, SymbolRef ParentSym, const SubRegion *SubRegion, - TaintTagType Kind = TaintTagGeneric); - -/// Check if the statement has a tainted value in the given state. -bool isTainted(ProgramStateRef State, const Stmt *S, - const LocationContext *LCtx, - TaintTagType Kind = TaintTagGeneric); - -/// Check if the value is tainted in the given state. -bool isTainted(ProgramStateRef State, SVal V, - TaintTagType Kind = TaintTagGeneric); - -/// Check if the symbol is tainted in the given state. -bool isTainted(ProgramStateRef State, SymbolRef Sym, - TaintTagType Kind = TaintTagGeneric); - -/// Check if the pointer represented by the region is tainted in the given -/// state. -bool isTainted(ProgramStateRef State, const MemRegion *Reg, - TaintTagType Kind = TaintTagGeneric); - -void printTaint(ProgramStateRef State, raw_ostream &Out, const char *nl = "\n", - const char *sep = ""); - -LLVM_DUMP_METHOD void dumpTaint(ProgramStateRef State); - -/// The bug visitor prints a diagnostic message at the location where a given -/// variable was tainted. -class TaintBugVisitor final : public BugReporterVisitor { -private: - const SVal V; - -public: - TaintBugVisitor(const SVal V) : V(V) {} - void Profile(llvm::FoldingSetNodeID &ID) const override { ID.Add(V); } - - PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - PathSensitiveBugReport &BR) override; -}; - -} // namespace taint -} // namespace ento -} // namespace clang - -#endif diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp index 916977c10c0c..acf4e833095b 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp @@ -10,8 +10,8 @@ // //===----------------------------------------------------------------------===// -#include "Taint.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Checkers/Taint.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -22,27 +22,14 @@ using namespace ento; using namespace taint; namespace { -class TaintTesterChecker : public Checker< check::PostStmt<Expr> > { - - mutable std::unique_ptr<BugType> BT; - void initBugType() const; - - /// Given a pointer argument, get the symbol of the value it contains - /// (points to). - SymbolRef getPointedToSymbol(CheckerContext &C, - const Expr* Arg, - bool IssueWarning = true) const; +class TaintTesterChecker : public Checker<check::PostStmt<Expr>> { + const BugType BT{this, "Tainted data", "General"}; public: void checkPostStmt(const Expr *E, CheckerContext &C) const; }; } -inline void TaintTesterChecker::initBugType() const { - if (!BT) - BT.reset(new BugType(this, "Tainted data", "General")); -} - void TaintTesterChecker::checkPostStmt(const Expr *E, CheckerContext &C) const { ProgramStateRef State = C.getState(); @@ -51,8 +38,7 @@ void TaintTesterChecker::checkPostStmt(const Expr *E, if (isTainted(State, E, C.getLocationContext())) { if (ExplodedNode *N = C.generateNonFatalErrorNode()) { - initBugType(); - auto report = std::make_unique<PathSensitiveBugReport>(*BT, "tainted", N); + auto report = std::make_unique<PathSensitiveBugReport>(BT, "tainted", N); report->addRange(E->getSourceRange()); C.emitReport(std::move(report)); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp index eeec807ccee4..667b19f8120e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp @@ -17,6 +17,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "llvm/ADT/FoldingSet.h" +#include <optional> using namespace clang; using namespace ento; @@ -77,7 +78,7 @@ public: class TestAfterDivZeroChecker : public Checker<check::PreStmt<BinaryOperator>, check::BranchCondition, check::EndFunction> { - mutable std::unique_ptr<BuiltinBug> DivZeroBug; + const BugType DivZeroBug{this, "Division by zero"}; void reportBug(SVal Val, CheckerContext &C) const; public: @@ -100,7 +101,7 @@ DivisionBRVisitor::VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, const Expr *E = nullptr; - if (Optional<PostStmt> P = Succ->getLocationAs<PostStmt>()) + if (std::optional<PostStmt> P = Succ->getLocationAs<PostStmt>()) if (const BinaryOperator *BO = P->getStmtAs<BinaryOperator>()) { BinaryOperator::Opcode Op = BO->getOpcode(); if (Op == BO_Div || Op == BO_Rem || Op == BO_DivAssign || @@ -132,7 +133,7 @@ DivisionBRVisitor::VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, } bool TestAfterDivZeroChecker::isZero(SVal S, CheckerContext &C) const { - Optional<DefinedSVal> DSV = S.getAs<DefinedSVal>(); + std::optional<DefinedSVal> DSV = S.getAs<DefinedSVal>(); if (!DSV) return false; @@ -164,12 +165,10 @@ bool TestAfterDivZeroChecker::hasDivZeroMap(SVal Var, void TestAfterDivZeroChecker::reportBug(SVal Val, CheckerContext &C) const { if (ExplodedNode *N = C.generateErrorNode(C.getState())) { - if (!DivZeroBug) - DivZeroBug.reset(new BuiltinBug(this, "Division by zero")); - auto R = std::make_unique<PathSensitiveBugReport>( - *DivZeroBug, "Value being compared against zero has already been used " - "for division", + DivZeroBug, + "Value being compared against zero has already been used " + "for division", N); R->addVisitor(std::make_unique<DivisionBRVisitor>(Val.getAsSymbol(), @@ -187,10 +186,7 @@ void TestAfterDivZeroChecker::checkEndFunction(const ReturnStmt *, return; DivZeroMapTy::Factory &F = State->get_context<DivZeroMap>(); - for (llvm::ImmutableSet<ZeroState>::iterator I = DivZeroes.begin(), - E = DivZeroes.end(); - I != E; ++I) { - ZeroState ZS = *I; + for (const ZeroState &ZS : DivZeroes) { if (ZS.getStackFrameContext() == C.getStackFrame()) DivZeroes = F.remove(DivZeroes, ZS); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp index 5cc713172527..e2f8bd541c96 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp @@ -69,8 +69,7 @@ public: if (!CondS || CondS->computeComplexity() > ComplexityThreshold) return State; - for (auto B=CondS->symbol_begin(), E=CondS->symbol_end(); B != E; ++B) { - const SymbolRef Antecedent = *B; + for (SymbolRef Antecedent : CondS->symbols()) { State = addImplication(Antecedent, State, true); State = addImplication(Antecedent, State, false); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TrustReturnsNonnullChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TrustReturnsNonnullChecker.cpp new file mode 100644 index 000000000000..d80559c6a915 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/TrustReturnsNonnullChecker.cpp @@ -0,0 +1,60 @@ +//== TrustReturnsNonnullChecker.cpp -- API nullability modeling -*- 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 checker adds nullability-related assumptions to methods annotated with +// returns_nonnull attribute. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/Attr.h" +#include "clang/AST/Decl.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +namespace { + +class TrustReturnsNonnullChecker : public Checker<check::PostCall> { + +public: + TrustReturnsNonnullChecker(ASTContext &Ctx) {} + + void checkPostCall(const CallEvent &Call, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + if (isNonNullPtr(Call)) + if (auto L = Call.getReturnValue().getAs<Loc>()) + State = State->assume(*L, /*assumption=*/true); + + C.addTransition(State); + } + +private: + /// \returns Whether the method declaration has the attribute returns_nonnull. + bool isNonNullPtr(const CallEvent &Call) const { + QualType ExprRetType = Call.getResultType(); + const Decl *CallDeclaration = Call.getDecl(); + if (!ExprRetType->isAnyPointerType() || !CallDeclaration) + return false; + + return CallDeclaration->hasAttr<ReturnsNonNullAttr>(); + } +}; + +} // namespace + +void ento::registerTrustReturnsNonnullChecker(CheckerManager &Mgr) { + Mgr.registerChecker<TrustReturnsNonnullChecker>(Mgr.getASTContext()); +} + +bool ento::shouldRegisterTrustReturnsNonnullChecker(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 ebe5ad53cc30..aa478b69aade 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp @@ -18,6 +18,7 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include <optional> #include <utility> using namespace clang; @@ -26,7 +27,7 @@ using namespace ento; namespace { class UndefBranchChecker : public Checker<check::BranchCondition> { - mutable std::unique_ptr<BuiltinBug> BT; + const BugType BT{this, "Branch condition evaluates to a garbage value"}; struct FindUndefExpr { ProgramStateRef St; @@ -63,52 +64,47 @@ void UndefBranchChecker::checkBranchCondition(const Stmt *Condition, // ObjCForCollection is a loop, but has no actual condition. if (isa<ObjCForCollectionStmt>(Condition)) return; - SVal X = Ctx.getSVal(Condition); - if (X.isUndef()) { - // Generate a sink node, which implicitly marks both outgoing branches as - // infeasible. - ExplodedNode *N = Ctx.generateErrorNode(); - if (N) { - if (!BT) - BT.reset(new BuiltinBug( - this, "Branch condition evaluates to a garbage value")); - - // What's going on here: we want to highlight the subexpression of the - // condition that is the most likely source of the "uninitialized - // branch condition." We do a recursive walk of the condition's - // subexpressions and roughly look for the most nested subexpression - // that binds to Undefined. We then highlight that expression's range. - - // Get the predecessor node and check if is a PostStmt with the Stmt - // being the terminator condition. We want to inspect the state - // of that node instead because it will contain main information about - // the subexpressions. - - // Note: any predecessor will do. They should have identical state, - // since all the BlockEdge did was act as an error sink since the value - // had to already be undefined. - assert (!N->pred_empty()); - const Expr *Ex = cast<Expr>(Condition); - ExplodedNode *PrevN = *N->pred_begin(); - ProgramPoint P = PrevN->getLocation(); - ProgramStateRef St = N->getState(); - - if (Optional<PostStmt> PS = P.getAs<PostStmt>()) - if (PS->getStmt() == Ex) - St = PrevN->getState(); - - FindUndefExpr FindIt(St, Ctx.getLocationContext()); - Ex = FindIt.FindExpr(Ex); - - // Emit the bug report. - auto R = std::make_unique<PathSensitiveBugReport>( - *BT, BT->getDescription(), N); - bugreporter::trackExpressionValue(N, Ex, *R); - R->addRange(Ex->getSourceRange()); - - Ctx.emitReport(std::move(R)); - } - } + if (!Ctx.getSVal(Condition).isUndef()) + return; + + // Generate a sink node, which implicitly marks both outgoing branches as + // infeasible. + ExplodedNode *N = Ctx.generateErrorNode(); + if (!N) + return; + // What's going on here: we want to highlight the subexpression of the + // condition that is the most likely source of the "uninitialized + // branch condition." We do a recursive walk of the condition's + // subexpressions and roughly look for the most nested subexpression + // that binds to Undefined. We then highlight that expression's range. + + // Get the predecessor node and check if is a PostStmt with the Stmt + // being the terminator condition. We want to inspect the state + // of that node instead because it will contain main information about + // the subexpressions. + + // Note: any predecessor will do. They should have identical state, + // since all the BlockEdge did was act as an error sink since the value + // had to already be undefined. + assert(!N->pred_empty()); + const Expr *Ex = cast<Expr>(Condition); + ExplodedNode *PrevN = *N->pred_begin(); + ProgramPoint P = PrevN->getLocation(); + ProgramStateRef St = N->getState(); + + if (std::optional<PostStmt> PS = P.getAs<PostStmt>()) + if (PS->getStmt() == Ex) + St = PrevN->getState(); + + FindUndefExpr FindIt(St, Ctx.getLocationContext()); + Ex = FindIt.FindExpr(Ex); + + // Emit the bug report. + auto R = std::make_unique<PathSensitiveBugReport>(BT, BT.getDescription(), N); + bugreporter::trackExpressionValue(N, Ex, *R); + R->addRange(Ex->getSourceRange()); + + Ctx.emitReport(std::move(R)); } void ento::registerUndefBranchChecker(CheckerManager &mgr) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp index 816a547cadc3..2839ef0b6d2e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp @@ -19,6 +19,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/raw_ostream.h" +#include <optional> using namespace clang; using namespace ento; @@ -26,7 +27,7 @@ using namespace ento; namespace { class UndefCapturedBlockVarChecker : public Checker< check::PostStmt<BlockExpr> > { - mutable std::unique_ptr<BugType> BT; + const BugType BT{this, "uninitialized variable captured by block"}; public: void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const; @@ -56,26 +57,19 @@ UndefCapturedBlockVarChecker::checkPostStmt(const BlockExpr *BE, ProgramStateRef state = C.getState(); auto *R = cast<BlockDataRegion>(C.getSVal(BE).getAsRegion()); - BlockDataRegion::referenced_vars_iterator I = R->referenced_vars_begin(), - E = R->referenced_vars_end(); - - for (; I != E; ++I) { + for (auto Var : R->referenced_vars()) { // This VarRegion is the region associated with the block; we need // the one associated with the encompassing context. - const VarRegion *VR = I.getCapturedRegion(); + const VarRegion *VR = Var.getCapturedRegion(); const VarDecl *VD = VR->getDecl(); if (VD->hasAttr<BlocksAttr>() || !VD->hasLocalStorage()) continue; // Get the VarRegion associated with VD in the local stack frame. - if (Optional<UndefinedVal> V = - state->getSVal(I.getOriginalRegion()).getAs<UndefinedVal>()) { + if (std::optional<UndefinedVal> V = + state->getSVal(Var.getOriginalRegion()).getAs<UndefinedVal>()) { if (ExplodedNode *N = C.generateErrorNode()) { - if (!BT) - BT.reset( - new BuiltinBug(this, "uninitialized variable captured by block")); - // Generate a bug report. SmallString<128> buf; llvm::raw_svector_ostream os(buf); @@ -83,7 +77,7 @@ UndefCapturedBlockVarChecker::checkPostStmt(const BlockExpr *BE, os << "Variable '" << VD->getName() << "' is uninitialized when captured by block"; - auto R = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N); + auto R = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N); if (const Expr *Ex = FindBlockDeclRefExpr(BE->getBody(), VD)) R->addRange(Ex->getSourceRange()); bugreporter::trackStoredValue(*V, VR, *R, diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp index 477d910bc653..4b845bb3ded2 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp @@ -28,7 +28,7 @@ namespace { class UndefResultChecker : public Checker< check::PostStmt<BinaryOperator> > { - mutable std::unique_ptr<BugType> BT; + const BugType BT{this, "Result of operation is garbage or undefined"}; public: void checkPostStmt(const BinaryOperator *B, CheckerContext &C) const; @@ -53,33 +53,17 @@ static bool isArrayIndexOutOfBounds(CheckerContext &C, const Expr *Ex) { DefinedOrUnknownSVal Idx = ER->getIndex().castAs<DefinedOrUnknownSVal>(); DefinedOrUnknownSVal ElementCount = getDynamicElementCount( state, ER->getSuperRegion(), C.getSValBuilder(), ER->getValueType()); - ProgramStateRef StInBound = state->assumeInBound(Idx, ElementCount, true); - ProgramStateRef StOutBound = state->assumeInBound(Idx, ElementCount, false); + ProgramStateRef StInBound, StOutBound; + std::tie(StInBound, StOutBound) = state->assumeInBoundDual(Idx, ElementCount); return StOutBound && !StInBound; } -static bool isShiftOverflow(const BinaryOperator *B, CheckerContext &C) { - return C.isGreaterOrEqual( - B->getRHS(), C.getASTContext().getIntWidth(B->getLHS()->getType())); -} - -static bool isLeftShiftResultUnrepresentable(const BinaryOperator *B, - CheckerContext &C) { - SValBuilder &SB = C.getSValBuilder(); - ProgramStateRef State = C.getState(); - const llvm::APSInt *LHS = SB.getKnownValue(State, C.getSVal(B->getLHS())); - const llvm::APSInt *RHS = SB.getKnownValue(State, C.getSVal(B->getRHS())); - assert(LHS && RHS && "Values unknown, inconsistent state"); - return (unsigned)RHS->getZExtValue() > LHS->countLeadingZeros(); -} - void UndefResultChecker::checkPostStmt(const BinaryOperator *B, CheckerContext &C) const { if (C.getSVal(B).isUndef()) { // Do not report assignments of uninitialized values inside swap functions. // This should allow to swap partially uninitialized structs - // (radar://14129997) if (const FunctionDecl *EnclosingFunctionDecl = dyn_cast<FunctionDecl>(C.getStackFrame()->getDecl())) if (C.getCalleeName(EnclosingFunctionDecl) == "swap") @@ -90,10 +74,6 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B, if (!N) return; - if (!BT) - BT.reset( - new BuiltinBug(this, "Result of operation is garbage or undefined")); - SmallString<256> sbuf; llvm::raw_svector_ostream OS(sbuf); const Expr *Ex = nullptr; @@ -116,62 +96,11 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B, OS << " due to array index out of bounds"; } else { // Neither operand was undefined, but the result is undefined. - if ((B->getOpcode() == BinaryOperatorKind::BO_Shl || - B->getOpcode() == BinaryOperatorKind::BO_Shr) && - C.isNegative(B->getRHS())) { - OS << "The result of the " - << ((B->getOpcode() == BinaryOperatorKind::BO_Shl) ? "left" - : "right") - << " shift is undefined because the right operand is negative"; - Ex = B->getRHS(); - } else if ((B->getOpcode() == BinaryOperatorKind::BO_Shl || - B->getOpcode() == BinaryOperatorKind::BO_Shr) && - isShiftOverflow(B, C)) { - - OS << "The result of the " - << ((B->getOpcode() == BinaryOperatorKind::BO_Shl) ? "left" - : "right") - << " shift is undefined due to shifting by "; - Ex = B->getRHS(); - - SValBuilder &SB = C.getSValBuilder(); - const llvm::APSInt *I = - SB.getKnownValue(C.getState(), C.getSVal(B->getRHS())); - if (!I) - OS << "a value that is"; - else if (I->isUnsigned()) - OS << '\'' << I->getZExtValue() << "\', which is"; - else - OS << '\'' << I->getSExtValue() << "\', which is"; - - OS << " greater or equal to the width of type '" - << B->getLHS()->getType().getAsString() << "'."; - } else if (B->getOpcode() == BinaryOperatorKind::BO_Shl && - C.isNegative(B->getLHS())) { - OS << "The result of the left shift is undefined because the left " - "operand is negative"; - Ex = B->getLHS(); - } else if (B->getOpcode() == BinaryOperatorKind::BO_Shl && - isLeftShiftResultUnrepresentable(B, C)) { - ProgramStateRef State = C.getState(); - SValBuilder &SB = C.getSValBuilder(); - const llvm::APSInt *LHS = - SB.getKnownValue(State, C.getSVal(B->getLHS())); - const llvm::APSInt *RHS = - SB.getKnownValue(State, C.getSVal(B->getRHS())); - OS << "The result of the left shift is undefined due to shifting \'" - << LHS->getSExtValue() << "\' by \'" << RHS->getZExtValue() - << "\', which is unrepresentable in the unsigned version of " - << "the return type \'" << B->getLHS()->getType().getAsString() - << "\'"; - Ex = B->getLHS(); - } else { - OS << "The result of the '" - << BinaryOperator::getOpcodeStr(B->getOpcode()) - << "' expression is undefined"; - } + OS << "The result of the '" + << BinaryOperator::getOpcodeStr(B->getOpcode()) + << "' expression is undefined"; } - auto report = std::make_unique<PathSensitiveBugReport>(*BT, OS.str(), N); + auto report = std::make_unique<PathSensitiveBugReport>(BT, OS.str(), N); if (Ex) { report->addRange(Ex->getSourceRange()); bugreporter::trackExpressionValue(N, Ex, *report); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp index fdefe75e8201..baa07fa66764 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp @@ -24,7 +24,7 @@ using namespace ento; namespace { class UndefinedArraySubscriptChecker : public Checker< check::PreStmt<ArraySubscriptExpr> > { - mutable std::unique_ptr<BugType> BT; + const BugType BT{this, "Array subscript is undefined"}; public: void checkPreStmt(const ArraySubscriptExpr *A, CheckerContext &C) const; @@ -48,11 +48,8 @@ UndefinedArraySubscriptChecker::checkPreStmt(const ArraySubscriptExpr *A, ExplodedNode *N = C.generateErrorNode(); if (!N) return; - if (!BT) - BT.reset(new BuiltinBug(this, "Array subscript is undefined")); - // Generate a report for this bug. - auto R = std::make_unique<PathSensitiveBugReport>(*BT, BT->getDescription(), N); + auto R = std::make_unique<PathSensitiveBugReport>(BT, BT.getDescription(), N); R->addRange(A->getIdx()->getSourceRange()); bugreporter::trackExpressionValue(N, A->getIdx(), *R); C.emitReport(std::move(R)); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp index 05f8f6084c0b..ddc6cc9e8202 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp @@ -23,7 +23,7 @@ using namespace ento; namespace { class UndefinedAssignmentChecker : public Checker<check::Bind> { - mutable std::unique_ptr<BugType> BT; + const BugType BT{this, "Assigned value is garbage or undefined"}; public: void checkBind(SVal location, SVal val, const Stmt *S, @@ -39,7 +39,6 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, // Do not report assignments of uninitialized values inside swap functions. // This should allow to swap partially uninitialized structs - // (radar://14129997) if (const FunctionDecl *EnclosingFunctionDecl = dyn_cast<FunctionDecl>(C.getStackFrame()->getDecl())) if (C.getCalleeName(EnclosingFunctionDecl) == "swap") @@ -50,11 +49,6 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, if (!N) return; - static const char *const DefaultMsg = - "Assigned value is garbage or undefined"; - if (!BT) - BT.reset(new BuiltinBug(this, DefaultMsg)); - // Generate a report for this bug. llvm::SmallString<128> Str; llvm::raw_svector_ostream OS(Str); @@ -92,7 +86,7 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, if (const auto *CD = dyn_cast<CXXConstructorDecl>(C.getStackFrame()->getDecl())) { if (CD->isImplicit()) { - for (auto I : CD->inits()) { + for (auto *I : CD->inits()) { if (I->getInit()->IgnoreImpCasts() == StoreE) { OS << "Value assigned to field '" << I->getMember()->getName() << "' in implicit constructor is garbage or undefined"; @@ -106,9 +100,9 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, } if (OS.str().empty()) - OS << DefaultMsg; + OS << BT.getDescription(); - auto R = std::make_unique<PathSensitiveBugReport>(*BT, OS.str(), N); + auto R = std::make_unique<PathSensitiveBugReport>(BT, OS.str(), N); if (ex) { R->addRange(ex->getSourceRange()); bugreporter::trackExpressionValue(N, ex, *R); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedNewArraySizeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedNewArraySizeChecker.cpp new file mode 100644 index 000000000000..f053ee887a1a --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UndefinedNewArraySizeChecker.cpp @@ -0,0 +1,80 @@ +//===--- UndefinedNewArraySizeChecker.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 defines UndefinedNewArraySizeChecker, a builtin check in ExprEngine +// that checks if the size of the array in a new[] expression is undefined. +// +//===----------------------------------------------------------------------===// + +#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" + +using namespace clang; +using namespace ento; + +namespace { +class UndefinedNewArraySizeChecker : public Checker<check::PreCall> { + +private: + BugType BT{this, "Undefined array element count in new[]", + categories::LogicError}; + +public: + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void HandleUndefinedArrayElementCount(CheckerContext &C, SVal ArgVal, + const Expr *Init, + SourceRange Range) const; +}; +} // namespace + +void UndefinedNewArraySizeChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + if (const auto *AC = dyn_cast<CXXAllocatorCall>(&Call)) { + if (!AC->isArray()) + return; + + auto *SizeEx = *AC->getArraySizeExpr(); + auto SizeVal = AC->getArraySizeVal(); + + if (SizeVal.isUndef()) + HandleUndefinedArrayElementCount(C, SizeVal, SizeEx, + SizeEx->getSourceRange()); + } +} + +void UndefinedNewArraySizeChecker::HandleUndefinedArrayElementCount( + CheckerContext &C, SVal ArgVal, const Expr *Init, SourceRange Range) const { + + if (ExplodedNode *N = C.generateErrorNode()) { + + SmallString<100> buf; + llvm::raw_svector_ostream os(buf); + + os << "Element count in new[] is a garbage value"; + + auto R = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N); + R->markInteresting(ArgVal); + R->addRange(Range); + bugreporter::trackExpressionValue(N, Init, *R); + + C.emitReport(std::move(R)); + } +} + +void ento::registerUndefinedNewArraySizeChecker(CheckerManager &mgr) { + mgr.registerChecker<UndefinedNewArraySizeChecker>(); +} + +bool ento::shouldRegisterUndefinedNewArraySizeChecker( + const CheckerManager &mgr) { + return mgr.getLangOpts().CPlusPlus; +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObject.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObject.h index 2fcdd6086309..e35778e6480c 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObject.h +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObject.h @@ -299,7 +299,7 @@ private: bool isDereferencableUninit(const FieldRegion *FR, FieldChainInfo LocalChain); /// Returns true if the value of a primitive object is uninitialized. - bool isPrimitiveUninit(const SVal &V); + bool isPrimitiveUninit(SVal V); // Note that we don't have a method for arrays -- the elements of an array are // often left uninitialized intentionally even when it is of a C++ record 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 4182b51c02b0..6e1222fedad3 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp @@ -38,15 +38,12 @@ namespace { class UninitializedObjectChecker : public Checker<check::EndFunction, check::DeadSymbols> { - std::unique_ptr<BuiltinBug> BT_uninitField; + const BugType BT_uninitField{this, "Uninitialized fields"}; public: // The fields of this struct will be initialized when registering the checker. UninitObjCheckerOptions Opts; - UninitializedObjectChecker() - : BT_uninitField(new BuiltinBug(this, "Uninitialized fields")) {} - void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; }; @@ -57,19 +54,17 @@ class RegularField final : public FieldNode { public: RegularField(const FieldRegion *FR) : FieldNode(FR) {} - virtual void printNoteMsg(llvm::raw_ostream &Out) const override { + void printNoteMsg(llvm::raw_ostream &Out) const override { Out << "uninitialized field "; } - virtual void printPrefix(llvm::raw_ostream &Out) const override {} + void printPrefix(llvm::raw_ostream &Out) const override {} - virtual void printNode(llvm::raw_ostream &Out) const override { + void printNode(llvm::raw_ostream &Out) const override { Out << getVariableName(getDecl()); } - virtual void printSeparator(llvm::raw_ostream &Out) const override { - Out << '.'; - } + void printSeparator(llvm::raw_ostream &Out) const override { Out << '.'; } }; /// Represents that the FieldNode that comes after this is declared in a base @@ -85,20 +80,20 @@ public: assert(T->getAsCXXRecordDecl()); } - virtual void printNoteMsg(llvm::raw_ostream &Out) const override { + void printNoteMsg(llvm::raw_ostream &Out) const override { llvm_unreachable("This node can never be the final node in the " "fieldchain!"); } - virtual void printPrefix(llvm::raw_ostream &Out) const override {} + void printPrefix(llvm::raw_ostream &Out) const override {} - virtual void printNode(llvm::raw_ostream &Out) const override { + void printNode(llvm::raw_ostream &Out) const override { Out << BaseClassT->getAsCXXRecordDecl()->getName() << "::"; } - virtual void printSeparator(llvm::raw_ostream &Out) const override {} + void printSeparator(llvm::raw_ostream &Out) const override {} - virtual bool isBase() const override { return true; } + bool isBase() const override { return true; } }; } // end of anonymous namespace @@ -188,7 +183,7 @@ void UninitializedObjectChecker::checkEndFunction( for (const auto &Pair : UninitFields) { auto Report = std::make_unique<PathSensitiveBugReport>( - *BT_uninitField, Pair.second, Node, LocUsedForUniqueing, + BT_uninitField, Pair.second, Node, LocUsedForUniqueing, Node->getLocationContext()->getDecl()); Context.emitReport(std::move(Report)); } @@ -202,7 +197,7 @@ void UninitializedObjectChecker::checkEndFunction( << " at the end of the constructor call"; auto Report = std::make_unique<PathSensitiveBugReport>( - *BT_uninitField, WarningOS.str(), Node, LocUsedForUniqueing, + BT_uninitField, WarningOS.str(), Node, LocUsedForUniqueing, Node->getLocationContext()->getDecl()); for (const auto &Pair : UninitFields) { @@ -330,7 +325,7 @@ bool FindUninitializedFields::isNonUnionUninit(const TypedValueRegion *R, SVal V = State->getSVal(FieldVal); - if (isDereferencableType(T) || V.getAs<nonloc::LocAsInteger>()) { + if (isDereferencableType(T) || isa<nonloc::LocAsInteger>(V)) { if (isDereferencableUninit(FR, LocalChain)) ContainsUninitField = true; continue; @@ -381,7 +376,7 @@ bool FindUninitializedFields::isUnionUninit(const TypedValueRegion *R) { return false; } -bool FindUninitializedFields::isPrimitiveUninit(const SVal &V) { +bool FindUninitializedFields::isPrimitiveUninit(SVal V) { if (V.isUndef()) return true; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp index f0dd0bf813af..54e1e0e11909 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp @@ -19,6 +19,7 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" +#include <optional> using namespace clang; using namespace clang::ento; @@ -34,20 +35,20 @@ public: LocField(const FieldRegion *FR, const bool IsDereferenced = true) : FieldNode(FR), IsDereferenced(IsDereferenced) {} - virtual void printNoteMsg(llvm::raw_ostream &Out) const override { + void printNoteMsg(llvm::raw_ostream &Out) const override { if (IsDereferenced) Out << "uninitialized pointee "; else Out << "uninitialized pointer "; } - virtual void printPrefix(llvm::raw_ostream &Out) const override {} + void printPrefix(llvm::raw_ostream &Out) const override {} - virtual void printNode(llvm::raw_ostream &Out) const override { + void printNode(llvm::raw_ostream &Out) const override { Out << getVariableName(getDecl()); } - virtual void printSeparator(llvm::raw_ostream &Out) const override { + void printSeparator(llvm::raw_ostream &Out) const override { if (getDecl()->getType()->isPointerType()) Out << "->"; else @@ -64,11 +65,11 @@ public: NeedsCastLocField(const FieldRegion *FR, const QualType &T) : FieldNode(FR), CastBackType(T) {} - virtual void printNoteMsg(llvm::raw_ostream &Out) const override { + void printNoteMsg(llvm::raw_ostream &Out) const override { Out << "uninitialized pointee "; } - virtual void printPrefix(llvm::raw_ostream &Out) const override { + void printPrefix(llvm::raw_ostream &Out) const override { // If this object is a nonloc::LocAsInteger. if (getDecl()->getType()->isIntegerType()) Out << "reinterpret_cast"; @@ -78,13 +79,11 @@ public: Out << '<' << CastBackType.getAsString() << ">("; } - virtual void printNode(llvm::raw_ostream &Out) const override { + void printNode(llvm::raw_ostream &Out) const override { Out << getVariableName(getDecl()) << ')'; } - virtual void printSeparator(llvm::raw_ostream &Out) const override { - Out << "->"; - } + void printSeparator(llvm::raw_ostream &Out) const override { Out << "->"; } }; /// Represents a Loc field that points to itself. @@ -93,17 +92,17 @@ class CyclicLocField final : public FieldNode { public: CyclicLocField(const FieldRegion *FR) : FieldNode(FR) {} - virtual void printNoteMsg(llvm::raw_ostream &Out) const override { + void printNoteMsg(llvm::raw_ostream &Out) const override { Out << "object references itself "; } - virtual void printPrefix(llvm::raw_ostream &Out) const override {} + void printPrefix(llvm::raw_ostream &Out) const override {} - virtual void printNode(llvm::raw_ostream &Out) const override { + void printNode(llvm::raw_ostream &Out) const override { Out << getVariableName(getDecl()); } - virtual void printSeparator(llvm::raw_ostream &Out) const override { + void printSeparator(llvm::raw_ostream &Out) const override { llvm_unreachable("CyclicLocField objects must be the last node of the " "fieldchain!"); } @@ -123,9 +122,9 @@ struct DereferenceInfo { /// Dereferences \p FR and returns with the pointee's region, and whether it /// needs to be casted back to it's location type. If for whatever reason -/// dereferencing fails, returns with None. -static llvm::Optional<DereferenceInfo> dereference(ProgramStateRef State, - const FieldRegion *FR); +/// dereferencing fails, returns std::nullopt. +static std::optional<DereferenceInfo> dereference(ProgramStateRef State, + const FieldRegion *FR); /// Returns whether \p T can be (transitively) dereferenced to a void pointer /// type (void*, void**, ...). @@ -141,10 +140,10 @@ bool FindUninitializedFields::isDereferencableUninit( SVal V = State->getSVal(FR); assert((isDereferencableType(FR->getDecl()->getType()) || - V.getAs<nonloc::LocAsInteger>()) && + isa<nonloc::LocAsInteger>(V)) && "This method only checks dereferenceable objects!"); - if (V.isUnknown() || V.getAs<loc::ConcreteInt>()) { + if (V.isUnknown() || isa<loc::ConcreteInt>(V)) { IsAnyFieldInitialized = true; return false; } @@ -161,7 +160,7 @@ bool FindUninitializedFields::isDereferencableUninit( // At this point the pointer itself is initialized and points to a valid // location, we'll now check the pointee. - llvm::Optional<DereferenceInfo> DerefInfo = dereference(State, FR); + std::optional<DereferenceInfo> DerefInfo = dereference(State, FR); if (!DerefInfo) { IsAnyFieldInitialized = true; return false; @@ -219,8 +218,8 @@ bool FindUninitializedFields::isDereferencableUninit( // Utility functions. //===----------------------------------------------------------------------===// -static llvm::Optional<DereferenceInfo> dereference(ProgramStateRef State, - const FieldRegion *FR) { +static std::optional<DereferenceInfo> dereference(ProgramStateRef State, + const FieldRegion *FR) { llvm::SmallSet<const TypedValueRegion *, 5> VisitedRegions; @@ -230,13 +229,13 @@ static llvm::Optional<DereferenceInfo> dereference(ProgramStateRef State, // If the static type of the field is a void pointer, or it is a // nonloc::LocAsInteger, we need to cast it back to the dynamic type before // dereferencing. - bool NeedsCastBack = isVoidPointer(FR->getDecl()->getType()) || - V.getAs<nonloc::LocAsInteger>(); + bool NeedsCastBack = + isVoidPointer(FR->getDecl()->getType()) || isa<nonloc::LocAsInteger>(V); // The region we'd like to acquire. const auto *R = V.getAsRegion()->getAs<TypedValueRegion>(); if (!R) - return None; + return std::nullopt; VisitedRegions.insert(R); @@ -247,7 +246,7 @@ static llvm::Optional<DereferenceInfo> dereference(ProgramStateRef State, R = Tmp->getAs<TypedValueRegion>(); if (!R) - return None; + return std::nullopt; // We found a cyclic pointer, like int *ptr = (int *)&ptr. if (!VisitedRegions.insert(R).second) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp index 381334de068e..b05ce610067c 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp @@ -11,17 +11,18 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/Basic/TargetInfo.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/CheckerContext.h" -#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" +#include <optional> using namespace clang; using namespace ento; @@ -39,12 +40,12 @@ enum class OpenVariant { namespace { class UnixAPIMisuseChecker : public Checker< check::PreStmt<CallExpr> > { - mutable std::unique_ptr<BugType> BT_open, BT_pthreadOnce; - mutable Optional<uint64_t> Val_O_CREAT; + const BugType BT_open{this, "Improper use of 'open'", categories::UnixAPI}; + const BugType BT_pthreadOnce{this, "Improper use of 'pthread_once'", + categories::UnixAPI}; + mutable std::optional<uint64_t> Val_O_CREAT; public: - DefaultBool CheckMisuse, CheckPortability; - void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; void CheckOpen(CheckerContext &C, const CallExpr *CE) const; @@ -66,7 +67,9 @@ public: void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; private: - mutable std::unique_ptr<BugType> BT_mallocZero; + const BugType BT_mallocZero{ + this, "Undefined allocation of 0 bytes (CERT MEM04-C; CWE-131)", + categories::UnixAPI}; void CheckCallocZero(CheckerContext &C, const CallExpr *CE) const; void CheckMallocZero(CheckerContext &C, const CallExpr *CE) const; @@ -89,14 +92,6 @@ private: } //end anonymous namespace -static void LazyInitialize(const CheckerBase *Checker, - std::unique_ptr<BugType> &BT, - const char *name) { - if (BT) - return; - BT.reset(new BugType(Checker, name, categories::UnixAPI)); -} - //===----------------------------------------------------------------------===// // "open" (man 2 open) //===----------------------------------------------------------------------===/ @@ -110,7 +105,7 @@ void UnixAPIMisuseChecker::checkPreStmt(const CallExpr *CE, // Don't treat functions in namespaces with the same name a Unix function // as a call to the Unix function. const DeclContext *NamespaceCtx = FD->getEnclosingNamespaceContext(); - if (NamespaceCtx && isa<NamespaceDecl>(NamespaceCtx)) + if (isa_and_nonnull<NamespaceDecl>(NamespaceCtx)) return; StringRef FName = C.getCalleeName(FD); @@ -134,9 +129,7 @@ void UnixAPIMisuseChecker::ReportOpenBug(CheckerContext &C, if (!N) return; - LazyInitialize(this, BT_open, "Improper use of 'open'"); - - auto Report = std::make_unique<PathSensitiveBugReport>(*BT_open, Msg, N); + auto Report = std::make_unique<PathSensitiveBugReport>(BT_open, Msg, N); Report->addRange(SR); C.emitReport(std::move(Report)); } @@ -182,8 +175,7 @@ void UnixAPIMisuseChecker::CheckOpenVariant(CheckerContext &C, ProgramStateRef state = C.getState(); if (CE->getNumArgs() < MinArgCount) { - // The frontend should issue a warning for this case, so this is a sanity - // check. + // The frontend should issue a warning for this case. Just return. return; } else if (CE->getNumArgs() == MaxArgCount) { const Expr *Arg = CE->getArg(CreateModeArgIndex); @@ -214,7 +206,7 @@ void UnixAPIMisuseChecker::CheckOpenVariant(CheckerContext &C, // The definition of O_CREAT is platform specific. We need a better way // of querying this information from the checking environment. - if (!Val_O_CREAT.hasValue()) { + if (!Val_O_CREAT) { if (C.getASTContext().getTargetInfo().getTriple().getVendor() == llvm::Triple::Apple) Val_O_CREAT = 0x0200; @@ -230,14 +222,15 @@ void UnixAPIMisuseChecker::CheckOpenVariant(CheckerContext &C, // Now check if oflags has O_CREAT set. const Expr *oflagsEx = CE->getArg(FlagsArgIndex); const SVal V = C.getSVal(oflagsEx); - if (!V.getAs<NonLoc>()) { + if (!isa<NonLoc>(V)) { // The case where 'V' can be a location can only be due to a bad header, // so in this case bail out. return; } NonLoc oflags = V.castAs<NonLoc>(); NonLoc ocreateFlag = C.getSValBuilder() - .makeIntVal(Val_O_CREAT.getValue(), oflagsEx->getType()).castAs<NonLoc>(); + .makeIntVal(*Val_O_CREAT, oflagsEx->getType()) + .castAs<NonLoc>(); SVal maskedFlagsUC = C.getSValBuilder().evalBinOpNN(state, BO_And, oflags, ocreateFlag, oflagsEx->getType()); @@ -303,10 +296,8 @@ void UnixAPIMisuseChecker::CheckPthreadOnce(CheckerContext &C, if (isa<VarRegion>(R) && isa<StackLocalsSpaceRegion>(R->getMemorySpace())) os << " Perhaps you intended to declare the variable as 'static'?"; - LazyInitialize(this, BT_pthreadOnce, "Improper use of 'pthread_once'"); - auto report = - std::make_unique<PathSensitiveBugReport>(*BT_pthreadOnce, os.str(), N); + std::make_unique<PathSensitiveBugReport>(BT_pthreadOnce, os.str(), N); report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(report)); } @@ -343,14 +334,11 @@ bool UnixAPIPortabilityChecker::ReportZeroByteAllocation( if (!N) return false; - LazyInitialize(this, BT_mallocZero, - "Undefined allocation of 0 bytes (CERT MEM04-C; CWE-131)"); - SmallString<256> S; llvm::raw_svector_ostream os(S); os << "Call to '" << fn_name << "' has an allocation size of 0 bytes"; auto report = - std::make_unique<PathSensitiveBugReport>(*BT_mallocZero, os.str(), N); + std::make_unique<PathSensitiveBugReport>(BT_mallocZero, os.str(), N); report->addRange(arg->getSourceRange()); bugreporter::trackExpressionValue(N, arg, *report); @@ -366,7 +354,7 @@ void UnixAPIPortabilityChecker::BasicAllocationCheck(CheckerContext &C, const unsigned numArgs, const unsigned sizeArg, const char *fn) const { - // Sanity check for the correct number of arguments + // Check for the correct number of arguments. if (CE->getNumArgs() != numArgs) return; @@ -466,7 +454,7 @@ void UnixAPIPortabilityChecker::checkPreStmt(const CallExpr *CE, // Don't treat functions in namespaces with the same name a Unix function // as a call to the Unix function. const DeclContext *NamespaceCtx = FD->getEnclosingNamespaceContext(); - if (NamespaceCtx && isa<NamespaceDecl>(NamespaceCtx)) + if (isa_and_nonnull<NamespaceDecl>(NamespaceCtx)) return; StringRef FName = C.getCalleeName(FD); @@ -504,7 +492,7 @@ void UnixAPIPortabilityChecker::checkPreStmt(const CallExpr *CE, mgr.registerChecker<CHECKERNAME>(); \ } \ \ - bool ento::shouldRegister##CHECKERNAME(const CheckerManager &mgr) { \ + 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 d231be64c2e1..d24a124f5ffe 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp @@ -24,6 +24,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "llvm/ADT/SmallSet.h" +#include <optional> using namespace clang; using namespace ento; @@ -58,9 +59,8 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, const ParentMap *PM = nullptr; const LocationContext *LC = nullptr; // Iterate over ExplodedGraph - for (ExplodedGraph::node_iterator I = G.nodes_begin(), E = G.nodes_end(); - I != E; ++I) { - const ProgramPoint &P = I->getLocation(); + for (const ExplodedNode &N : G.nodes()) { + const ProgramPoint &P = N.getLocation(); LC = P.getLocationContext(); if (!LC->inTopFrame()) continue; @@ -74,7 +74,7 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, if (!PM) PM = &LC->getParentMap(); - if (Optional<BlockEntrance> BE = P.getAs<BlockEntrance>()) { + if (std::optional<BlockEntrance> BE = P.getAs<BlockEntrance>()) { const CFGBlock *CB = BE->getBlock(); reachable.insert(CB->getBlockID()); } @@ -92,8 +92,7 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, return; // Find CFGBlocks that were not covered by any node - for (CFG::const_iterator I = C->begin(), E = C->end(); I != E; ++I) { - const CFGBlock *CB = *I; + for (const CFGBlock *CB : *C) { // Check if the block is unreachable if (reachable.count(CB->getBlockID())) continue; @@ -129,7 +128,7 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, bool foundUnreachable = false; for (CFGBlock::const_iterator ci = CB->begin(), ce = CB->end(); ci != ce; ++ci) { - if (Optional<CFGStmt> S = (*ci).getAs<CFGStmt>()) + if (std::optional<CFGStmt> S = (*ci).getAs<CFGStmt>()) if (const CallExpr *CE = dyn_cast<CallExpr>(S->getStmt())) { if (CE->getBuiltinCallee() == Builtin::BI__builtin_unreachable || CE->isBuiltinAssumeFalse(Eng.getContext())) { @@ -180,34 +179,30 @@ void UnreachableCodeChecker::FindUnreachableEntryPoints(const CFGBlock *CB, CFGBlocksSet &visited) { visited.insert(CB->getBlockID()); - for (CFGBlock::const_pred_iterator I = CB->pred_begin(), E = CB->pred_end(); - I != E; ++I) { - if (!*I) + for (const CFGBlock *PredBlock : CB->preds()) { + if (!PredBlock) continue; - if (!reachable.count((*I)->getBlockID())) { + if (!reachable.count(PredBlock->getBlockID())) { // If we find an unreachable predecessor, mark this block as reachable so // we don't report this block reachable.insert(CB->getBlockID()); - if (!visited.count((*I)->getBlockID())) + if (!visited.count(PredBlock->getBlockID())) // If we haven't previously visited the unreachable predecessor, recurse - FindUnreachableEntryPoints(*I, reachable, visited); + FindUnreachableEntryPoints(PredBlock, reachable, visited); } } } // Find the Stmt* in a CFGBlock for reporting a warning const Stmt *UnreachableCodeChecker::getUnreachableStmt(const CFGBlock *CB) { - for (CFGBlock::const_iterator I = CB->begin(), E = CB->end(); I != E; ++I) { - if (Optional<CFGStmt> S = I->getAs<CFGStmt>()) { + for (const CFGElement &Elem : *CB) { + if (std::optional<CFGStmt> S = Elem.getAs<CFGStmt>()) { if (!isa<DeclStmt>(S->getStmt())) return S->getStmt(); } } - if (const Stmt *S = CB->getTerminatorStmt()) - return S; - else - return nullptr; + return CB->getTerminatorStmt(); } // Determines if the path to this CFGBlock contained an element that infers this diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp index 96501215c689..d76fe4991869 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp @@ -13,9 +13,9 @@ // //===----------------------------------------------------------------------===// -#include "Taint.h" #include "clang/AST/CharUnits.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Checkers/Taint.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -24,6 +24,7 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/raw_ostream.h" +#include <optional> using namespace clang; using namespace ento; @@ -33,14 +34,11 @@ namespace { 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, - VLA_Overflow - }; + const BugType BT{this, "Dangerous variable-length array (VLA) declaration"}; + const BugType TaintBT{this, + "Dangerous variable-length array (VLA) declaration", + categories::TaintedData}; + enum VLASize_Kind { VLA_Garbage, VLA_Zero, VLA_Negative, VLA_Overflow }; /// Check a VLA for validity. /// Every dimension of the array and the total size is checked for validity. @@ -54,8 +52,10 @@ class VLASizeChecker const Expr *SizeE) const; void reportBug(VLASize_Kind Kind, const Expr *SizeE, ProgramStateRef State, - CheckerContext &C, - std::unique_ptr<BugReporterVisitor> Visitor = nullptr) const; + CheckerContext &C) const; + + void reportTaintBug(const Expr *SizeE, ProgramStateRef State, + CheckerContext &C, SVal TaintedSVal) const; public: void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const; @@ -166,8 +166,7 @@ ProgramStateRef VLASizeChecker::checkVLAIndexSize(CheckerContext &C, // Check if the size is tainted. if (isTainted(State, SizeV)) { - reportBug(VLA_Tainted, SizeE, nullptr, C, - std::make_unique<TaintBugVisitor>(SizeV)); + reportTaintBug(SizeE, State, C, SizeV); return nullptr; } @@ -191,8 +190,9 @@ ProgramStateRef VLASizeChecker::checkVLAIndexSize(CheckerContext &C, QualType SizeTy = SizeE->getType(); DefinedOrUnknownSVal Zero = SVB.makeZeroVal(SizeTy); - SVal LessThanZeroVal = SVB.evalBinOp(State, BO_LT, SizeD, Zero, SizeTy); - if (Optional<DefinedSVal> LessThanZeroDVal = + SVal LessThanZeroVal = + SVB.evalBinOp(State, BO_LT, SizeD, Zero, SVB.getConditionType()); + if (std::optional<DefinedSVal> LessThanZeroDVal = LessThanZeroVal.getAs<DefinedSVal>()) { ConstraintManager &CM = C.getConstraintManager(); ProgramStateRef StatePos, StateNeg; @@ -208,17 +208,34 @@ ProgramStateRef VLASizeChecker::checkVLAIndexSize(CheckerContext &C, return State; } -void VLASizeChecker::reportBug( - VLASize_Kind Kind, const Expr *SizeE, ProgramStateRef State, - CheckerContext &C, std::unique_ptr<BugReporterVisitor> Visitor) const { +void VLASizeChecker::reportTaintBug(const Expr *SizeE, ProgramStateRef State, + CheckerContext &C, SVal TaintedSVal) const { // Generate an error node. ExplodedNode *N = C.generateErrorNode(State); if (!N) return; - if (!BT) - BT.reset(new BuiltinBug( - this, "Dangerous variable-length array (VLA) declaration")); + SmallString<256> buf; + llvm::raw_svector_ostream os(buf); + os << "Declared variable-length array (VLA) "; + os << "has tainted size"; + + auto report = std::make_unique<PathSensitiveBugReport>(TaintBT, os.str(), N); + report->addRange(SizeE->getSourceRange()); + bugreporter::trackExpressionValue(N, SizeE, *report); + // The vla size may be a complex expression where multiple memory locations + // are tainted. + for (auto Sym : getTaintedSymbols(State, TaintedSVal)) + report->markInteresting(Sym); + C.emitReport(std::move(report)); +} + +void VLASizeChecker::reportBug(VLASize_Kind Kind, const Expr *SizeE, + ProgramStateRef State, CheckerContext &C) const { + // Generate an error node. + ExplodedNode *N = C.generateErrorNode(State); + if (!N) + return; SmallString<256> buf; llvm::raw_svector_ostream os(buf); @@ -230,9 +247,6 @@ void VLASizeChecker::reportBug( case VLA_Zero: os << "has zero size"; break; - case VLA_Tainted: - os << "has tainted size"; - break; case VLA_Negative: os << "has negative size"; break; @@ -241,8 +255,7 @@ void VLASizeChecker::reportBug( break; } - auto report = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N); - report->addVisitor(std::move(Visitor)); + auto report = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N); report->addRange(SizeE->getSourceRange()); bugreporter::trackExpressionValue(N, SizeE, *report); C.emitReport(std::move(report)); @@ -278,8 +291,7 @@ void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { if (!State) return; - auto ArraySizeNL = ArraySize.getAs<NonLoc>(); - if (!ArraySizeNL) { + if (!isa<NonLoc>(ArraySize)) { // Array size could not be determined but state may contain new assumptions. C.addTransition(State); return; @@ -289,7 +301,7 @@ void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { if (VD) { State = setDynamicExtent(State, State->getRegion(VD, C.getLocationContext()), - ArraySize.castAs<DefinedOrUnknownSVal>(), SVB); + ArraySize.castAs<NonLoc>(), SVB); } // Remember our assumptions! diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp index dde5912b6d6e..2d1b873abf73 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp @@ -15,6 +15,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" @@ -45,7 +46,7 @@ public: CK_NumCheckKinds }; - DefaultBool ChecksEnabled[CK_NumCheckKinds]; + bool ChecksEnabled[CK_NumCheckKinds] = {false}; CheckerNameRef CheckNames[CK_NumCheckKinds]; void checkPreStmt(const VAArgExpr *VAA, CheckerContext &C) const; @@ -99,42 +100,41 @@ private: }; const SmallVector<ValistChecker::VAListAccepter, 15> - ValistChecker::VAListAccepters = { - {{"vfprintf", 3}, 2}, - {{"vfscanf", 3}, 2}, - {{"vprintf", 2}, 1}, - {{"vscanf", 2}, 1}, - {{"vsnprintf", 4}, 3}, - {{"vsprintf", 3}, 2}, - {{"vsscanf", 3}, 2}, - {{"vfwprintf", 3}, 2}, - {{"vfwscanf", 3}, 2}, - {{"vwprintf", 2}, 1}, - {{"vwscanf", 2}, 1}, - {{"vswprintf", 4}, 3}, - // vswprintf is the wide version of vsnprintf, - // vsprintf has no wide version - {{"vswscanf", 3}, 2}}; - -const CallDescription - ValistChecker::VaStart("__builtin_va_start", /*Args=*/2, /*Params=*/1), - ValistChecker::VaCopy("__builtin_va_copy", 2), - ValistChecker::VaEnd("__builtin_va_end", 1); + ValistChecker::VAListAccepters = {{{{"vfprintf"}, 3}, 2}, + {{{"vfscanf"}, 3}, 2}, + {{{"vprintf"}, 2}, 1}, + {{{"vscanf"}, 2}, 1}, + {{{"vsnprintf"}, 4}, 3}, + {{{"vsprintf"}, 3}, 2}, + {{{"vsscanf"}, 3}, 2}, + {{{"vfwprintf"}, 3}, 2}, + {{{"vfwscanf"}, 3}, 2}, + {{{"vwprintf"}, 2}, 1}, + {{{"vwscanf"}, 2}, 1}, + {{{"vswprintf"}, 4}, 3}, + // vswprintf is the wide version of + // vsnprintf, vsprintf has no wide version + {{{"vswscanf"}, 3}, 2}}; + +const CallDescription ValistChecker::VaStart({"__builtin_va_start"}, /*Args=*/2, + /*Params=*/1), + ValistChecker::VaCopy({"__builtin_va_copy"}, 2), + ValistChecker::VaEnd({"__builtin_va_end"}, 1); } // end anonymous namespace void ValistChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { if (!Call.isGlobalCFunction()) return; - if (Call.isCalled(VaStart)) + if (VaStart.matches(Call)) checkVAListStartCall(Call, C, false); - else if (Call.isCalled(VaCopy)) + else if (VaCopy.matches(Call)) checkVAListStartCall(Call, C, true); - else if (Call.isCalled(VaEnd)) + else if (VaEnd.matches(Call)) checkVAListEndCall(Call, C); else { for (auto FuncInfo : VAListAccepters) { - if (!Call.isCalled(FuncInfo.Func)) + if (!FuncInfo.Func.matches(Call)) continue; bool Symbolic; const MemRegion *VAList = @@ -177,7 +177,7 @@ const MemRegion *ValistChecker::getVAListAsRegion(SVal SV, const Expr *E, if (isa<ParmVarDecl>(DeclReg->getDecl())) Reg = C.getState()->getSVal(SV.castAs<Loc>()).getAsRegion(); } - IsSymbolic = Reg && Reg->getAs<SymbolicRegion>(); + IsSymbolic = Reg && Reg->getBaseRegion()->getAs<SymbolicRegion>(); // Some VarRegion based VA lists reach here as ElementRegions. const auto *EReg = dyn_cast_or_null<ElementRegion>(Reg); return (EReg && VaListModelledAsArray) ? EReg->getSuperRegion() : Reg; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp index 8f147026ae19..cb73ac68edd1 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp @@ -9,7 +9,7 @@ // This file defines vfork checker which checks for dangerous uses of vfork. // Vforked process shares memory (including stack) with parent so it's // range of actions is significantly limited: can't write variables, -// can't call functions not in whitelist, etc. For more details, see +// can't call functions not in the allowed list, etc. For more details, see // http://man7.org/linux/man-pages/man2/vfork.2.html // // This checker checks for prohibited constructs in vforked process. @@ -35,6 +35,7 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/AST/ParentMap.h" +#include <optional> using namespace clang; using namespace ento; @@ -43,20 +44,21 @@ namespace { class VforkChecker : public Checker<check::PreCall, check::PostCall, check::Bind, check::PreStmt<ReturnStmt>> { - mutable std::unique_ptr<BuiltinBug> BT; - mutable llvm::SmallSet<const IdentifierInfo *, 10> VforkWhitelist; - mutable const IdentifierInfo *II_vfork; + const BugType BT{this, "Dangerous construct in a vforked process"}; + mutable llvm::SmallSet<const IdentifierInfo *, 10> VforkAllowlist; + mutable const IdentifierInfo *II_vfork = nullptr; static bool isChildProcess(const ProgramStateRef State); bool isVforkCall(const Decl *D, CheckerContext &C) const; - bool isCallWhitelisted(const IdentifierInfo *II, CheckerContext &C) const; + bool isCallExplicitelyAllowed(const IdentifierInfo *II, + CheckerContext &C) const; void reportBug(const char *What, CheckerContext &C, const char *Details = nullptr) const; public: - VforkChecker() : II_vfork(nullptr) {} + VforkChecker() = default; void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; @@ -93,9 +95,9 @@ bool VforkChecker::isVforkCall(const Decl *D, CheckerContext &C) const { } // Returns true iff ok to call function after successful vfork. -bool VforkChecker::isCallWhitelisted(const IdentifierInfo *II, - CheckerContext &C) const { - if (VforkWhitelist.empty()) { +bool VforkChecker::isCallExplicitelyAllowed(const IdentifierInfo *II, + CheckerContext &C) const { + if (VforkAllowlist.empty()) { // According to manpage. const char *ids[] = { "_Exit", @@ -112,19 +114,15 @@ bool VforkChecker::isCallWhitelisted(const IdentifierInfo *II, ASTContext &AC = C.getASTContext(); for (const char **id = ids; *id; ++id) - VforkWhitelist.insert(&AC.Idents.get(*id)); + VforkAllowlist.insert(&AC.Idents.get(*id)); } - return VforkWhitelist.count(II); + return VforkAllowlist.count(II); } void VforkChecker::reportBug(const char *What, CheckerContext &C, const char *Details) const { if (ExplodedNode *N = C.generateErrorNode(C.getState())) { - if (!BT) - BT.reset(new BuiltinBug(this, - "Dangerous construct in a vforked process")); - SmallString<256> buf; llvm::raw_svector_ostream os(buf); @@ -133,7 +131,7 @@ void VforkChecker::reportBug(const char *What, CheckerContext &C, if (Details) os << "; " << Details; - auto Report = std::make_unique<PathSensitiveBugReport>(*BT, os.str(), N); + auto Report = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N); // TODO: mark vfork call in BugReportVisitor C.emitReport(std::move(Report)); } @@ -153,8 +151,8 @@ void VforkChecker::checkPostCall(const CallEvent &Call, // Get return value of vfork. SVal VforkRetVal = Call.getReturnValue(); - Optional<DefinedOrUnknownSVal> DVal = - VforkRetVal.getAs<DefinedOrUnknownSVal>(); + std::optional<DefinedOrUnknownSVal> DVal = + VforkRetVal.getAs<DefinedOrUnknownSVal>(); if (!DVal) return; @@ -179,12 +177,13 @@ void VforkChecker::checkPostCall(const CallEvent &Call, C.addTransition(ChildState); } -// Prohibit calls to non-whitelist functions in child process. +// Prohibit calls to functions in child process which are not explicitly +// allowed. void VforkChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); - if (isChildProcess(State) - && !isCallWhitelisted(Call.getCalleeIdentifier(), C)) + if (isChildProcess(State) && + !isCallExplicitelyAllowed(Call.getCalleeIdentifier(), C)) reportBug("This function call", C); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp index 1c589e3468c2..33a9a07f9d32 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -116,7 +116,7 @@ void VirtualCallChecker::checkPreCall(const CallEvent &Call, if (!ObState) return; - bool IsPure = MD->isPure(); + bool IsPure = MD->isPureVirtual(); // At this point we're sure that we're calling a virtual method // during construction or destruction, so we'll emit a report. diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp index 9c7a59971763..64028b277021 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp @@ -12,8 +12,8 @@ #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/ExprCXX.h" +#include <optional> -using llvm::Optional; namespace clang { std::pair<const Expr *, bool> @@ -34,8 +34,7 @@ tryToFindPtrOrigin(const Expr *E, bool StopAtFirstRefCountedObj) { } if (auto *call = dyn_cast<CallExpr>(E)) { if (auto *memberCall = dyn_cast<CXXMemberCallExpr>(call)) { - Optional<bool> IsGetterOfRefCt = - isGetterOfRefCounted(memberCall->getMethodDecl()); + std::optional<bool> IsGetterOfRefCt = isGetterOfRefCounted(memberCall->getMethodDecl()); if (IsGetterOfRefCt && *IsGetterOfRefCt) { E = memberCall->getImplicitObjectArgument(); if (StopAtFirstRefCountedObj) { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h index ed4577755457..e35ea4ef05dd 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h @@ -17,10 +17,6 @@ #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 @@ -29,7 +25,7 @@ class Expr; /// values). /// /// For more context see Static Analyzer checkers documentation - specifically -/// webkit.UncountedCallArgsChecker checker. Whitelist of transformations: +/// webkit.UncountedCallArgsChecker checker. Allowed list of transformations: /// - constructors of ref-counted types (including factory methods) /// - getters of ref-counted types /// - member overloaded operators diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/NoUncountedMembersChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/NoUncountedMembersChecker.cpp index 97f75135bf92..c753ed84a700 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/NoUncountedMembersChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/NoUncountedMembersChecker.cpp @@ -17,8 +17,8 @@ #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" +#include <optional> using namespace clang; using namespace ento; @@ -69,7 +69,7 @@ public: if (shouldSkipDecl(RD)) return; - for (auto Member : RD->fields()) { + for (auto *Member : RD->fields()) { const Type *MemberType = Member->getType().getTypePtrOrNull(); if (!MemberType) continue; @@ -77,9 +77,9 @@ public: if (auto *MemberCXXRD = MemberType->getPointeeCXXRecordDecl()) { // If we don't see the definition we just don't know. if (MemberCXXRD->hasDefinition()) { - llvm::Optional<bool> isRCAble = isRefCountable(MemberCXXRD); - if (isRCAble && *isRCAble) - reportBug(Member, MemberType, MemberCXXRD, RD); + std::optional<bool> isRCAble = isRefCountable(MemberCXXRD); + if (isRCAble && *isRCAble) + reportBug(Member, MemberType, MemberCXXRD, RD); } } } @@ -103,7 +103,7 @@ public: const auto Kind = RD->getTagKind(); // FIMXE: Should we check union members too? - if (Kind != TTK_Struct && Kind != TTK_Class) + if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class) return true; // Ignore CXXRecords that come from system headers. diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index a198943c9433..d2b663410580 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -12,31 +12,21 @@ #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/ExprCXX.h" -#include "llvm/ADT/Optional.h" +#include <optional> -using llvm::Optional; using namespace clang; namespace { -bool hasPublicRefAndDeref(const CXXRecordDecl *R) { +bool hasPublicMethodInBaseClass(const CXXRecordDecl *R, + const char *NameToMatch) { assert(R); assert(R->hasDefinition()); - 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; - } + if (MethodName == NameToMatch && MD->getAccess() == AS_public) + return true; } return false; } @@ -45,54 +35,70 @@ bool hasPublicRefAndDeref(const CXXRecordDecl *R) { namespace clang { -llvm::Optional<const clang::CXXRecordDecl *> -isRefCountable(const CXXBaseSpecifier *Base) { +std::optional<const clang::CXXRecordDecl *> +hasPublicMethodInBase(const CXXBaseSpecifier *Base, const char *NameToMatch) { assert(Base); const Type *T = Base->getType().getTypePtrOrNull(); if (!T) - return llvm::None; + return std::nullopt; const CXXRecordDecl *R = T->getAsCXXRecordDecl(); if (!R) - return llvm::None; + return std::nullopt; if (!R->hasDefinition()) - return llvm::None; + return std::nullopt; - return hasPublicRefAndDeref(R) ? R : nullptr; + return hasPublicMethodInBaseClass(R, NameToMatch) ? R : nullptr; } -llvm::Optional<bool> isRefCountable(const CXXRecordDecl *R) { +std::optional<bool> isRefCountable(const CXXRecordDecl* R) +{ assert(R); R = R->getDefinition(); if (!R) - return llvm::None; + return std::nullopt; - if (hasPublicRefAndDeref(R)) + bool hasRef = hasPublicMethodInBaseClass(R, "ref"); + bool hasDeref = hasPublicMethodInBaseClass(R, "deref"); + if (hasRef && hasDeref) return true; CXXBasePaths Paths; Paths.setOrigin(const_cast<CXXRecordDecl *>(R)); bool AnyInconclusiveBase = false; - const auto isRefCountableBase = + const auto hasPublicRefInBase = [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) { - Optional<const clang::CXXRecordDecl *> IsRefCountable = - clang::isRefCountable(Base); - if (!IsRefCountable) { + auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref"); + if (!hasRefInBase) { AnyInconclusiveBase = true; return false; } - return (*IsRefCountable) != nullptr; + return (*hasRefInBase) != nullptr; }; - bool BasesResult = R->lookupInBases(isRefCountableBase, Paths, + hasRef = hasRef || R->lookupInBases(hasPublicRefInBase, Paths, /*LookupInDependent =*/true); if (AnyInconclusiveBase) - return llvm::None; + return std::nullopt; - return BasesResult; + const auto hasPublicDerefInBase = + [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, CXXBasePath &) { + auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref"); + if (!hasDerefInBase) { + AnyInconclusiveBase = true; + return false; + } + return (*hasDerefInBase) != nullptr; + }; + hasDeref = hasDeref || R->lookupInBases(hasPublicDerefInBase, Paths, + /*LookupInDependent =*/true); + if (AnyInconclusiveBase) + return std::nullopt; + + return hasRef && hasDeref; } bool isCtorOfRefCounted(const clang::FunctionDecl *F) { @@ -112,19 +118,21 @@ bool isCtorOfRefCounted(const clang::FunctionDecl *F) { || FunctionName == "Identifier"; } -llvm::Optional<bool> isUncounted(const CXXRecordDecl *Class) { +std::optional<bool> isUncounted(const CXXRecordDecl* Class) +{ // Keep isRefCounted first as it's cheaper. if (isRefCounted(Class)) return false; - llvm::Optional<bool> IsRefCountable = isRefCountable(Class); + std::optional<bool> IsRefCountable = isRefCountable(Class); if (!IsRefCountable) - return llvm::None; + return std::nullopt; return (*IsRefCountable); } -llvm::Optional<bool> isUncountedPtr(const Type *T) { +std::optional<bool> isUncountedPtr(const Type* T) +{ assert(T); if (T->isPointerType() || T->isReferenceType()) { @@ -135,7 +143,8 @@ llvm::Optional<bool> isUncountedPtr(const Type *T) { return false; } -Optional<bool> isGetterOfRefCounted(const CXXMethodDecl *M) { +std::optional<bool> isGetterOfRefCounted(const CXXMethodDecl* M) +{ assert(M); if (isa<CXXMethodDecl>(M)) { @@ -183,8 +192,7 @@ bool isPtrConversion(const FunctionDecl *F) { // FIXME: check # of params == 1 const auto FunctionName = safeGetName(F); if (FunctionName == "getPtr" || FunctionName == "WeakPtr" || - FunctionName == "makeWeakPtr" - + FunctionName == "dynamicDowncast" || FunctionName == "downcast" || FunctionName == "bitwise_cast") return true; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h index 730a59977175..45b21cc09184 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h @@ -10,12 +10,12 @@ #define LLVM_CLANG_ANALYZER_WEBKIT_PTRTYPESEMANTICS_H #include "llvm/ADT/APInt.h" +#include <optional> namespace clang { class CXXBaseSpecifier; class CXXMethodDecl; class CXXRecordDecl; -class Expr; class FunctionDecl; class Type; @@ -26,32 +26,32 @@ class Type; // 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, None if inconclusive. -llvm::Optional<const clang::CXXRecordDecl *> -isRefCountable(const clang::CXXBaseSpecifier *Base); +/// \returns CXXRecordDecl of the base if the type has ref as a public method, +/// nullptr if not, std::nullopt if inconclusive. +std::optional<const clang::CXXRecordDecl *> +hasPublicMethodInBase(const CXXBaseSpecifier *Base, const char *NameToMatch); -/// \returns true if \p Class is ref-countable, false if not, None if +/// \returns true if \p Class is ref-countable, false if not, std::nullopt if /// inconclusive. -llvm::Optional<bool> isRefCountable(const clang::CXXRecordDecl *Class); +std::optional<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, None if inconclusive. -llvm::Optional<bool> isUncounted(const clang::CXXRecordDecl *Class); +/// not, std::nullopt if inconclusive. +std::optional<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, None if inconclusive. -llvm::Optional<bool> isUncountedPtr(const clang::Type *T); +/// class, false if not, std::nullopt if inconclusive. +std::optional<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. -llvm::Optional<bool> isGetterOfRefCounted(const clang::CXXMethodDecl *Method); +std::optional<bool> isGetterOfRefCounted(const clang::CXXMethodDecl* Method); /// \returns true if \p F is a conversion between ref-countable or ref-counted /// pointer types. diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp index fa9ece217cc0..d879c110b75d 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp @@ -14,6 +14,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" +#include <optional> using namespace clang; using namespace ento; @@ -76,15 +77,53 @@ public: (AccSpec == AS_none && RD->isClass())) return false; - llvm::Optional<const CXXRecordDecl *> RefCntblBaseRD = - isRefCountable(Base); - if (!RefCntblBaseRD || !(*RefCntblBaseRD)) + auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref"); + auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref"); + + bool hasRef = hasRefInBase && *hasRefInBase != nullptr; + bool hasDeref = hasDerefInBase && *hasDerefInBase != nullptr; + + QualType T = Base->getType(); + if (T.isNull()) + return false; + + const CXXRecordDecl *C = T->getAsCXXRecordDecl(); + if (!C) + return false; + bool AnyInconclusiveBase = false; + const auto hasPublicRefInBase = + [&AnyInconclusiveBase](const CXXBaseSpecifier *Base, + CXXBasePath &) { + auto hasRefInBase = clang::hasPublicMethodInBase(Base, "ref"); + if (!hasRefInBase) { + AnyInconclusiveBase = true; + return false; + } + return (*hasRefInBase) != nullptr; + }; + const auto hasPublicDerefInBase = [&AnyInconclusiveBase]( + const CXXBaseSpecifier *Base, + CXXBasePath &) { + auto hasDerefInBase = clang::hasPublicMethodInBase(Base, "deref"); + if (!hasDerefInBase) { + AnyInconclusiveBase = true; + return false; + } + return (*hasDerefInBase) != nullptr; + }; + CXXBasePaths Paths; + Paths.setOrigin(C); + hasRef = hasRef || C->lookupInBases(hasPublicRefInBase, Paths, + /*LookupInDependent =*/true); + hasDeref = hasDeref || C->lookupInBases(hasPublicDerefInBase, Paths, + /*LookupInDependent =*/true); + if (AnyInconclusiveBase || !hasRef || !hasDeref) return false; - const auto *Dtor = (*RefCntblBaseRD)->getDestructor(); + const auto *Dtor = C->getDestructor(); if (!Dtor || !Dtor->isVirtual()) { ProblematicBaseSpecifier = Base; - ProblematicBaseClass = *RefCntblBaseRD; + ProblematicBaseClass = C; return true; } @@ -114,7 +153,7 @@ public: return true; const auto Kind = RD->getTagKind(); - if (Kind != TTK_Struct && Kind != TTK_Class) + if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class) return true; // Ignore CXXRecords that come from system headers. diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp index d70bd9489d2c..31ccae8b097b 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp @@ -18,7 +18,7 @@ #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 <optional> using namespace clang; using namespace ento; @@ -68,8 +68,7 @@ public: 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); + unsigned ArgIdx = isa<CXXOperatorCallExpr>(CE) && isa_and_nonnull<CXXMethodDecl>(F); for (auto P = F->param_begin(); // FIXME: Also check variadic function parameters. @@ -86,7 +85,7 @@ public: continue; // FIXME? Should we bail? // FIXME: more complex types (arrays, references to raw pointers, etc) - Optional<bool> IsUncounted = isUncountedPtr(ArgType); + std::optional<bool> IsUncounted = isUncountedPtr(ArgType); if (!IsUncounted || !(*IsUncounted)) continue; @@ -149,7 +148,7 @@ public: auto name = safeGetName(Callee); if (name == "adoptRef" || name == "getPtr" || name == "WeakPtr" || - name == "makeWeakPtr" || name == "downcast" || name == "bitwise_cast" || + name == "dynamicDowncast" || name == "downcast" || name == "bitwise_cast" || name == "is" || name == "equal" || name == "hash" || name == "isType" // FIXME: Most/all of these should be implemented via attributes. diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp index deebbd603b2c..a226a01ec0a5 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp @@ -14,6 +14,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" +#include <optional> using namespace clang; using namespace ento; @@ -24,7 +25,7 @@ class UncountedLambdaCapturesChecker private: BugType Bug{this, "Lambda capture of uncounted variable", "WebKit coding guidelines"}; - mutable BugReporter *BR; + mutable BugReporter *BR = nullptr; public: void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, @@ -57,18 +58,18 @@ public: void visitLambdaExpr(LambdaExpr *L) const { for (const LambdaCapture &C : L->captures()) { if (C.capturesVariable()) { - VarDecl *CapturedVar = C.getCapturedVar(); + ValueDecl *CapturedVar = C.getCapturedVar(); if (auto *CapturedVarType = CapturedVar->getType().getTypePtrOrNull()) { - Optional<bool> IsUncountedPtr = isUncountedPtr(CapturedVarType); - if (IsUncountedPtr && *IsUncountedPtr) { - reportBug(C, CapturedVar, CapturedVarType); - } + std::optional<bool> IsUncountedPtr = isUncountedPtr(CapturedVarType); + if (IsUncountedPtr && *IsUncountedPtr) { + reportBug(C, CapturedVar, CapturedVarType); + } } } } } - void reportBug(const LambdaCapture &Capture, VarDecl *CapturedVar, + void reportBug(const LambdaCapture &Capture, ValueDecl *CapturedVar, const Type *T) const { assert(CapturedVar); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLocalVarsChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLocalVarsChecker.cpp index 7e86f28cb70f..5a72f53b12ed 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLocalVarsChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLocalVarsChecker.cpp @@ -19,7 +19,7 @@ #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 <optional> using namespace clang; using namespace ento; @@ -169,7 +169,7 @@ public: if (!ArgType) return; - Optional<bool> IsUncountedPtr = isUncountedPtr(ArgType); + std::optional<bool> IsUncountedPtr = isUncountedPtr(ArgType); if (IsUncountedPtr && *IsUncountedPtr) { const Expr *const InitExpr = V->getInit(); if (!InitExpr) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Yaml.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Yaml.h index ec612dde3b8b..b2d17420686e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Yaml.h +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/Yaml.h @@ -17,6 +17,7 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "llvm/Support/VirtualFileSystem.h" #include "llvm/Support/YAMLTraits.h" +#include <optional> namespace clang { namespace ento { @@ -25,20 +26,20 @@ namespace ento { /// template parameter must have a yaml MappingTraits. /// Emit diagnostic error in case of any failure. template <class T, class Checker> -llvm::Optional<T> getConfiguration(CheckerManager &Mgr, Checker *Chk, - StringRef Option, StringRef ConfigFile) { +std::optional<T> getConfiguration(CheckerManager &Mgr, Checker *Chk, + StringRef Option, StringRef ConfigFile) { if (ConfigFile.trim().empty()) - return None; + return std::nullopt; llvm::vfs::FileSystem *FS = llvm::vfs::getRealFileSystem().get(); llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Buffer = FS->getBufferForFile(ConfigFile.str()); - if (std::error_code ec = Buffer.getError()) { + if (Buffer.getError()) { Mgr.reportInvalidCheckerOptionValue(Chk, Option, "a valid filename instead of '" + std::string(ConfigFile) + "'"); - return None; + return std::nullopt; } llvm::yaml::Input Input(Buffer.get()->getBuffer()); @@ -48,7 +49,7 @@ llvm::Optional<T> getConfiguration(CheckerManager &Mgr, Checker *Chk, if (std::error_code ec = Input.error()) { Mgr.reportInvalidCheckerOptionValue(Chk, Option, "a valid yaml file: " + ec.message()); - return None; + return std::nullopt; } return Config; @@ -57,4 +58,4 @@ llvm::Optional<T> getConfiguration(CheckerManager &Mgr, Checker *Chk, } // namespace ento } // namespace clang -#endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MOVE_H +#endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKER_YAML_H diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/cert/InvalidPtrChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/cert/InvalidPtrChecker.cpp new file mode 100644 index 000000000000..e5dd907c660d --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/cert/InvalidPtrChecker.cpp @@ -0,0 +1,353 @@ +//== InvalidPtrChecker.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 InvalidPtrChecker which finds usages of possibly +// invalidated pointer. +// CERT SEI Rules ENV31-C and ENV34-C +// For more information see: +// https://wiki.sei.cmu.edu/confluence/x/8tYxBQ +// https://wiki.sei.cmu.edu/confluence/x/5NUxBQ +//===----------------------------------------------------------------------===// + +#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/CallDescription.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +namespace { + +class InvalidPtrChecker + : public Checker<check::Location, check::BeginFunction, check::PostCall> { +private: + // For accurate emission of NoteTags, the BugType of this checker should have + // a unique address. + BugType InvalidPtrBugType{this, "Use of invalidated pointer", + categories::MemoryError}; + + void EnvpInvalidatingCall(const CallEvent &Call, CheckerContext &C) const; + + using HandlerFn = void (InvalidPtrChecker::*)(const CallEvent &Call, + CheckerContext &C) const; + + // SEI CERT ENV31-C + + // If set to true, consider getenv calls as invalidating operations on the + // environment variable buffer. This is implied in the standard, but in + // practice does not cause problems (in the commonly used environments). + bool InvalidatingGetEnv = false; + + // GetEnv can be treated invalidating and non-invalidating as well. + const CallDescription GetEnvCall{{"getenv"}, 1}; + + const CallDescriptionMap<HandlerFn> EnvpInvalidatingFunctions = { + {{{"setenv"}, 3}, &InvalidPtrChecker::EnvpInvalidatingCall}, + {{{"unsetenv"}, 1}, &InvalidPtrChecker::EnvpInvalidatingCall}, + {{{"putenv"}, 1}, &InvalidPtrChecker::EnvpInvalidatingCall}, + {{{"_putenv_s"}, 2}, &InvalidPtrChecker::EnvpInvalidatingCall}, + {{{"_wputenv_s"}, 2}, &InvalidPtrChecker::EnvpInvalidatingCall}, + }; + + void postPreviousReturnInvalidatingCall(const CallEvent &Call, + CheckerContext &C) const; + + // SEI CERT ENV34-C + const CallDescriptionMap<HandlerFn> PreviousCallInvalidatingFunctions = { + {{{"setlocale"}, 2}, + &InvalidPtrChecker::postPreviousReturnInvalidatingCall}, + {{{"strerror"}, 1}, + &InvalidPtrChecker::postPreviousReturnInvalidatingCall}, + {{{"localeconv"}, 0}, + &InvalidPtrChecker::postPreviousReturnInvalidatingCall}, + {{{"asctime"}, 1}, + &InvalidPtrChecker::postPreviousReturnInvalidatingCall}, + }; + + // The private members of this checker corresponding to commandline options + // are set in this function. + friend void ento::registerInvalidPtrChecker(CheckerManager &); + +public: + // Obtain the environment pointer from 'main()' (if present). + void checkBeginFunction(CheckerContext &C) const; + + // Handle functions in EnvpInvalidatingFunctions, that invalidate environment + // pointer from 'main()' + // Handle functions in PreviousCallInvalidatingFunctions. + // Also, check if invalidated region is passed to a + // conservatively evaluated function call as an argument. + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + + // Check if invalidated region is being dereferenced. + void checkLocation(SVal l, bool isLoad, const Stmt *S, + CheckerContext &C) const; + +private: + const NoteTag *createEnvInvalidationNote(CheckerContext &C, + ProgramStateRef State, + StringRef FunctionName) const; +}; + +} // namespace + +// Set of memory regions that were invalidated +REGISTER_SET_WITH_PROGRAMSTATE(InvalidMemoryRegions, const MemRegion *) + +// Stores the region of the environment pointer of 'main' (if present). +REGISTER_TRAIT_WITH_PROGRAMSTATE(MainEnvPtrRegion, const MemRegion *) + +// Stores the regions of environments returned by getenv calls. +REGISTER_SET_WITH_PROGRAMSTATE(GetenvEnvPtrRegions, const MemRegion *) + +// Stores key-value pairs, where key is function declaration and value is +// pointer to memory region returned by previous call of this function +REGISTER_MAP_WITH_PROGRAMSTATE(PreviousCallResultMap, const FunctionDecl *, + const MemRegion *) + +const NoteTag *InvalidPtrChecker::createEnvInvalidationNote( + CheckerContext &C, ProgramStateRef State, StringRef FunctionName) const { + + const MemRegion *MainRegion = State->get<MainEnvPtrRegion>(); + const auto GetenvRegions = State->get<GetenvEnvPtrRegions>(); + + return C.getNoteTag([this, MainRegion, GetenvRegions, + FunctionName = std::string{FunctionName}]( + PathSensitiveBugReport &BR, llvm::raw_ostream &Out) { + // Only handle the BugType of this checker. + if (&BR.getBugType() != &InvalidPtrBugType) + return; + + // Mark all regions that were interesting before as NOT interesting now + // to avoid extra notes coming from invalidation points higher up the + // bugpath. This ensures that only the last invalidation point is marked + // with a note tag. + llvm::SmallVector<std::string, 2> InvalidLocationNames; + if (BR.isInteresting(MainRegion)) { + BR.markNotInteresting(MainRegion); + InvalidLocationNames.push_back("the environment parameter of 'main'"); + } + bool InterestingGetenvFound = false; + for (const MemRegion *MR : GetenvRegions) { + if (BR.isInteresting(MR)) { + BR.markNotInteresting(MR); + if (!InterestingGetenvFound) { + InterestingGetenvFound = true; + InvalidLocationNames.push_back( + "the environment returned by 'getenv'"); + } + } + } + + // Emit note tag message. + if (InvalidLocationNames.size() >= 1) + Out << '\'' << FunctionName << "' call may invalidate " + << InvalidLocationNames[0]; + if (InvalidLocationNames.size() == 2) + Out << ", and " << InvalidLocationNames[1]; + }); +} + +void InvalidPtrChecker::EnvpInvalidatingCall(const CallEvent &Call, + CheckerContext &C) const { + // This callevent invalidates all previously generated pointers to the + // environment. + ProgramStateRef State = C.getState(); + if (const MemRegion *MainEnvPtr = State->get<MainEnvPtrRegion>()) + State = State->add<InvalidMemoryRegions>(MainEnvPtr); + for (const MemRegion *EnvPtr : State->get<GetenvEnvPtrRegions>()) + State = State->add<InvalidMemoryRegions>(EnvPtr); + + StringRef FunctionName = Call.getCalleeIdentifier()->getName(); + const NoteTag *InvalidationNote = + createEnvInvalidationNote(C, State, FunctionName); + + C.addTransition(State, InvalidationNote); +} + +void InvalidPtrChecker::postPreviousReturnInvalidatingCall( + const CallEvent &Call, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + const NoteTag *Note = nullptr; + const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + // Invalidate the region of the previously returned pointer - if there was + // one. + if (const MemRegion *const *Reg = State->get<PreviousCallResultMap>(FD)) { + const MemRegion *PrevReg = *Reg; + State = State->add<InvalidMemoryRegions>(PrevReg); + Note = C.getNoteTag([this, PrevReg, FD](PathSensitiveBugReport &BR, + llvm::raw_ostream &Out) { + if (!BR.isInteresting(PrevReg) || &BR.getBugType() != &InvalidPtrBugType) + return; + Out << '\''; + FD->getNameForDiagnostic(Out, FD->getASTContext().getLangOpts(), true); + Out << "' call may invalidate the result of the previous " << '\''; + FD->getNameForDiagnostic(Out, FD->getASTContext().getLangOpts(), true); + Out << '\''; + }); + } + + const LocationContext *LCtx = C.getLocationContext(); + const auto *CE = cast<CallExpr>(Call.getOriginExpr()); + + // Function call will return a pointer to the new symbolic region. + DefinedOrUnknownSVal RetVal = C.getSValBuilder().conjureSymbolVal( + CE, LCtx, CE->getType(), C.blockCount()); + State = State->BindExpr(CE, LCtx, RetVal); + + // Remember to this region. + const auto *SymRegOfRetVal = cast<SymbolicRegion>(RetVal.getAsRegion()); + const MemRegion *MR = SymRegOfRetVal->getBaseRegion(); + State = State->set<PreviousCallResultMap>(FD, MR); + + ExplodedNode *Node = C.addTransition(State, Note); + const NoteTag *PreviousCallNote = C.getNoteTag( + [this, MR](PathSensitiveBugReport &BR, llvm::raw_ostream &Out) { + if (!BR.isInteresting(MR) || &BR.getBugType() != &InvalidPtrBugType) + return; + Out << "previous function call was here"; + }); + + C.addTransition(State, Node, PreviousCallNote); +} + +// TODO: This seems really ugly. Simplify this. +static const MemRegion *findInvalidatedSymbolicBase(ProgramStateRef State, + const MemRegion *Reg) { + while (Reg) { + if (State->contains<InvalidMemoryRegions>(Reg)) + return Reg; + const auto *SymBase = Reg->getSymbolicBase(); + if (!SymBase) + break; + const auto *SRV = dyn_cast<SymbolRegionValue>(SymBase->getSymbol()); + if (!SRV) + break; + Reg = SRV->getRegion(); + if (const auto *VarReg = dyn_cast<VarRegion>(SRV->getRegion())) + Reg = VarReg; + } + return nullptr; +} + +// Handle functions in EnvpInvalidatingFunctions, that invalidate environment +// pointer from 'main()' Also, check if invalidated region is passed to a +// function call as an argument. +void InvalidPtrChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + + ProgramStateRef State = C.getState(); + + // Model 'getenv' calls + if (GetEnvCall.matches(Call)) { + const MemRegion *Region = Call.getReturnValue().getAsRegion(); + if (Region) { + State = State->add<GetenvEnvPtrRegions>(Region); + C.addTransition(State); + } + } + + // Check if function invalidates 'envp' argument of 'main' + if (const auto *Handler = EnvpInvalidatingFunctions.lookup(Call)) + (this->**Handler)(Call, C); + + // Check if function invalidates the result of previous call + if (const auto *Handler = PreviousCallInvalidatingFunctions.lookup(Call)) + (this->**Handler)(Call, C); + + // If pedantic mode is on, regard 'getenv' calls invalidating as well + if (InvalidatingGetEnv && GetEnvCall.matches(Call)) + postPreviousReturnInvalidatingCall(Call, C); + + // Check if one of the arguments of the function call is invalidated + + // If call was inlined, don't report invalidated argument + if (C.wasInlined) + return; + + for (unsigned I = 0, NumArgs = Call.getNumArgs(); I < NumArgs; ++I) { + + if (const auto *SR = dyn_cast_or_null<SymbolicRegion>( + Call.getArgSVal(I).getAsRegion())) { + if (const MemRegion *InvalidatedSymbolicBase = + findInvalidatedSymbolicBase(State, SR)) { + ExplodedNode *ErrorNode = C.generateNonFatalErrorNode(); + if (!ErrorNode) + return; + + SmallString<256> Msg; + llvm::raw_svector_ostream Out(Msg); + Out << "use of invalidated pointer '"; + Call.getArgExpr(I)->printPretty(Out, /*Helper=*/nullptr, + C.getASTContext().getPrintingPolicy()); + Out << "' in a function call"; + + auto Report = std::make_unique<PathSensitiveBugReport>( + InvalidPtrBugType, Out.str(), ErrorNode); + Report->markInteresting(InvalidatedSymbolicBase); + Report->addRange(Call.getArgSourceRange(I)); + C.emitReport(std::move(Report)); + } + } + } +} + +// Obtain the environment pointer from 'main()', if present. +void InvalidPtrChecker::checkBeginFunction(CheckerContext &C) const { + if (!C.inTopFrame()) + return; + + const auto *FD = dyn_cast<FunctionDecl>(C.getLocationContext()->getDecl()); + if (!FD || FD->param_size() != 3 || !FD->isMain()) + return; + + ProgramStateRef State = C.getState(); + const MemRegion *EnvpReg = + State->getRegion(FD->parameters()[2], C.getLocationContext()); + + // Save the memory region pointed by the environment pointer parameter of + // 'main'. + C.addTransition(State->set<MainEnvPtrRegion>(EnvpReg)); +} + +// Check if invalidated region is being dereferenced. +void InvalidPtrChecker::checkLocation(SVal Loc, bool isLoad, const Stmt *S, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + // Ignore memory operations involving 'non-invalidated' locations. + const MemRegion *InvalidatedSymbolicBase = + findInvalidatedSymbolicBase(State, Loc.getAsRegion()); + if (!InvalidatedSymbolicBase) + return; + + ExplodedNode *ErrorNode = C.generateNonFatalErrorNode(); + if (!ErrorNode) + return; + + auto Report = std::make_unique<PathSensitiveBugReport>( + InvalidPtrBugType, "dereferencing an invalid pointer", ErrorNode); + Report->markInteresting(InvalidatedSymbolicBase); + C.emitReport(std::move(Report)); +} + +void ento::registerInvalidPtrChecker(CheckerManager &Mgr) { + auto *Checker = Mgr.registerChecker<InvalidPtrChecker>(); + Checker->InvalidatingGetEnv = + Mgr.getAnalyzerOptions().getCheckerBooleanOption(Checker, + "InvalidatingGetEnv"); +} + +bool ento::shouldRegisterInvalidPtrChecker(const CheckerManager &) { + return true; +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/cert/PutenvWithAutoChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/cert/PutenvWithAutoChecker.cpp index 1c67bbd77ec8..eae162cda693 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/cert/PutenvWithAutoChecker.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/cert/PutenvWithAutoChecker.cpp @@ -17,6 +17,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" @@ -29,7 +30,7 @@ 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}; + const CallDescription Putenv{{"putenv"}, 1}; public: void checkPostCall(const CallEvent &Call, CheckerContext &C) const; @@ -38,7 +39,7 @@ public: void PutenvWithAutoChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { - if (!Call.isCalled(Putenv)) + if (!Putenv.matches(Call)) return; SVal ArgV = Call.getArgSVal(0); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/APSIntType.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/APSIntType.cpp index a1de10c89ed9..1185cdaa044a 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/APSIntType.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/APSIntType.cpp @@ -23,7 +23,7 @@ APSIntType::testInRange(const llvm::APSInt &Value, unsigned MinBits; if (AllowSignConversions) { if (Value.isSigned() && !IsUnsigned) - MinBits = Value.getMinSignedBits(); + MinBits = Value.getSignificantBits(); else MinBits = Value.getActiveBits(); @@ -33,7 +33,7 @@ APSIntType::testInRange(const llvm::APSInt &Value, // Unsigned integers can be converted to unsigned integers of the same width // or signed integers with one more bit. if (Value.isSigned()) - MinBits = Value.getMinSignedBits() - IsUnsigned; + MinBits = Value.getSignificantBits() - IsUnsigned; else MinBits = Value.getActiveBits() + !IsUnsigned; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp index ecfc7106560e..f9750db7b501 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp @@ -50,17 +50,14 @@ AnalysisManager::AnalysisManager(ASTContext &ASTCtx, Preprocessor &PP, AnalysisManager::~AnalysisManager() { FlushDiagnostics(); - for (PathDiagnosticConsumers::iterator I = PathConsumers.begin(), - E = PathConsumers.end(); I != E; ++I) { - delete *I; + for (PathDiagnosticConsumer *Consumer : PathConsumers) { + delete Consumer; } } void AnalysisManager::FlushDiagnostics() { PathDiagnosticConsumer::FilesMade filesMade; - for (PathDiagnosticConsumers::iterator I = PathConsumers.begin(), - E = PathConsumers.end(); - I != E; ++I) { - (*I)->FlushDiagnostics(&filesMade); + for (PathDiagnosticConsumer *Consumer : PathConsumers) { + Consumer->FlushDiagnostics(&filesMade); } } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp index 8cd7f75e4e38..86ef4a568665 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -23,6 +23,7 @@ #include "llvm/Support/raw_ostream.h" #include <cassert> #include <cstddef> +#include <optional> #include <utility> #include <vector> @@ -64,34 +65,44 @@ void AnalyzerOptions::printFormattedEntry( ExplorationStrategyKind AnalyzerOptions::getExplorationStrategy() const { auto K = - llvm::StringSwitch<llvm::Optional<ExplorationStrategyKind>>( - ExplorationStrategy) + llvm::StringSwitch<std::optional<ExplorationStrategyKind>>( + ExplorationStrategy) .Case("dfs", ExplorationStrategyKind::DFS) .Case("bfs", ExplorationStrategyKind::BFS) - .Case("unexplored_first", - ExplorationStrategyKind::UnexploredFirst) + .Case("unexplored_first", ExplorationStrategyKind::UnexploredFirst) .Case("unexplored_first_queue", ExplorationStrategyKind::UnexploredFirstQueue) .Case("unexplored_first_location_queue", ExplorationStrategyKind::UnexploredFirstLocationQueue) .Case("bfs_block_dfs_contents", ExplorationStrategyKind::BFSBlockDFSContents) - .Default(None); - assert(K.hasValue() && "User mode is invalid."); - return K.getValue(); + .Default(std::nullopt); + assert(K && "User mode is invalid."); + return *K; +} + +CTUPhase1InliningKind AnalyzerOptions::getCTUPhase1Inlining() const { + auto K = llvm::StringSwitch<std::optional<CTUPhase1InliningKind>>( + CTUPhase1InliningMode) + .Case("none", CTUPhase1InliningKind::None) + .Case("small", CTUPhase1InliningKind::Small) + .Case("all", CTUPhase1InliningKind::All) + .Default(std::nullopt); + assert(K && "CTU inlining mode is invalid."); + return *K; } IPAKind AnalyzerOptions::getIPAMode() const { - auto K = llvm::StringSwitch<llvm::Optional<IPAKind>>(IPAMode) - .Case("none", IPAK_None) - .Case("basic-inlining", IPAK_BasicInlining) - .Case("inlining", IPAK_Inlining) - .Case("dynamic", IPAK_DynamicDispatch) - .Case("dynamic-bifurcate", IPAK_DynamicDispatchBifurcate) - .Default(None); - assert(K.hasValue() && "IPA Mode is invalid."); - - return K.getValue(); + auto K = llvm::StringSwitch<std::optional<IPAKind>>(IPAMode) + .Case("none", IPAK_None) + .Case("basic-inlining", IPAK_BasicInlining) + .Case("inlining", IPAK_Inlining) + .Case("dynamic", IPAK_DynamicDispatch) + .Case("dynamic-bifurcate", IPAK_DynamicDispatchBifurcate) + .Default(std::nullopt); + assert(K && "IPA Mode is invalid."); + + return *K; } bool @@ -100,16 +111,15 @@ AnalyzerOptions::mayInlineCXXMemberFunction( if (getIPAMode() < IPAK_Inlining) return false; - auto K = - llvm::StringSwitch<llvm::Optional<CXXInlineableMemberKind>>( - CXXMemberInliningMode) - .Case("constructors", CIMK_Constructors) - .Case("destructors", CIMK_Destructors) - .Case("methods", CIMK_MemberFunctions) - .Case("none", CIMK_None) - .Default(None); + auto K = llvm::StringSwitch<std::optional<CXXInlineableMemberKind>>( + CXXMemberInliningMode) + .Case("constructors", CIMK_Constructors) + .Case("destructors", CIMK_Destructors) + .Case("methods", CIMK_MemberFunctions) + .Case("none", CIMK_None) + .Default(std::nullopt); - assert(K.hasValue() && "Invalid c++ member function inlining mode."); + assert(K && "Invalid c++ member function inlining mode."); return *K >= Param; } @@ -151,12 +161,12 @@ StringRef AnalyzerOptions::getCheckerStringOption(const ento::CheckerBase *C, bool AnalyzerOptions::getCheckerBooleanOption(StringRef CheckerName, StringRef OptionName, bool SearchInParents) const { - auto Ret = llvm::StringSwitch<llvm::Optional<bool>>( - getCheckerStringOption(CheckerName, OptionName, - SearchInParents)) - .Case("true", true) - .Case("false", false) - .Default(None); + auto Ret = + llvm::StringSwitch<std::optional<bool>>( + getCheckerStringOption(CheckerName, OptionName, SearchInParents)) + .Case("true", true) + .Case("false", false) + .Default(std::nullopt); assert(Ret && "This option should be either 'true' or 'false', and should've been " diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp index 40cdaef1bfa7..5c10e757244d 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BasicValueFactory.cpp @@ -97,8 +97,7 @@ const llvm::APSInt& BasicValueFactory::getValue(const llvm::APSInt& X) { FoldNodeTy* P = APSIntSet.FindNodeOrInsertPos(ID, InsertPos); if (!P) { - P = (FoldNodeTy*) BPAlloc.Allocate<FoldNodeTy>(); - new (P) FoldNodeTy(X); + P = new (BPAlloc) FoldNodeTy(X); APSIntSet.InsertNode(P, InsertPos); } @@ -132,8 +131,7 @@ BasicValueFactory::getCompoundValData(QualType T, CompoundValData* D = CompoundValDataSet.FindNodeOrInsertPos(ID, InsertPos); if (!D) { - D = (CompoundValData*) BPAlloc.Allocate<CompoundValData>(); - new (D) CompoundValData(T, Vals); + D = new (BPAlloc) CompoundValData(T, Vals); CompoundValDataSet.InsertNode(D, InsertPos); } @@ -151,8 +149,7 @@ BasicValueFactory::getLazyCompoundValData(const StoreRef &store, LazyCompoundValDataSet.FindNodeOrInsertPos(ID, InsertPos); if (!D) { - D = (LazyCompoundValData*) BPAlloc.Allocate<LazyCompoundValData>(); - new (D) LazyCompoundValData(store, region); + D = new (BPAlloc) LazyCompoundValData(store, region); LazyCompoundValDataSet.InsertNode(D, InsertPos); } @@ -169,8 +166,7 @@ const PointerToMemberData *BasicValueFactory::getPointerToMemberData( PointerToMemberDataSet.FindNodeOrInsertPos(ID, InsertPos); if (!D) { - D = (PointerToMemberData *)BPAlloc.Allocate<PointerToMemberData>(); - new (D) PointerToMemberData(ND, L); + D = new (BPAlloc) PointerToMemberData(ND, L); PointerToMemberDataSet.InsertNode(D, InsertPos); } @@ -276,7 +272,7 @@ BasicValueFactory::evalAPSInt(BinaryOperator::Opcode Op, // FIXME: This logic should probably go higher up, where we can // test these conditions symbolically. - if (V2.isSigned() && V2.isNegative()) + if (V2.isNegative() || V2.getBitWidth() > 64) return nullptr; uint64_t Amt = V2.getZExtValue(); @@ -284,14 +280,6 @@ BasicValueFactory::evalAPSInt(BinaryOperator::Opcode Op, if (Amt >= V1.getBitWidth()) return nullptr; - if (!Ctx.getLangOpts().CPlusPlus20) { - if (V1.isSigned() && V1.isNegative()) - return nullptr; - - if (V1.isSigned() && Amt > V1.countLeadingZeros()) - return nullptr; - } - return &getValue( V1.operator<<( (unsigned) Amt )); } @@ -299,7 +287,7 @@ BasicValueFactory::evalAPSInt(BinaryOperator::Opcode Op, // FIXME: This logic should probably go higher up, where we can // test these conditions symbolically. - if (V2.isSigned() && V2.isNegative()) + if (V2.isNegative() || V2.getBitWidth() > 64) return nullptr; uint64_t Amt = V2.getZExtValue(); @@ -358,8 +346,7 @@ BasicValueFactory::getPersistentSValWithData(const SVal& V, uintptr_t Data) { FoldNodeTy* P = Map.FindNodeOrInsertPos(ID, InsertPos); if (!P) { - P = (FoldNodeTy*) BPAlloc.Allocate<FoldNodeTy>(); - new (P) FoldNodeTy(std::make_pair(V, Data)); + P = new (BPAlloc) FoldNodeTy(std::make_pair(V, Data)); Map.InsertNode(P, InsertPos); } @@ -383,8 +370,7 @@ BasicValueFactory::getPersistentSValPair(const SVal& V1, const SVal& V2) { FoldNodeTy* P = Map.FindNodeOrInsertPos(ID, InsertPos); if (!P) { - P = (FoldNodeTy*) BPAlloc.Allocate<FoldNodeTy>(); - new (P) FoldNodeTy(std::make_pair(V1, V2)); + P = new (BPAlloc) FoldNodeTy(std::make_pair(V1, V2)); Map.InsertNode(P, InsertPos); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index d6f69ae03afe..f3e0a5f9f314 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -12,12 +12,15 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/AST/ASTTypeTraits.h" +#include "clang/AST/Attr.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/ParentMap.h" +#include "clang/AST/ParentMapContext.h" #include "clang/AST/Stmt.h" #include "clang/AST/StmtCXX.h" #include "clang/AST/StmtObjC.h" @@ -46,8 +49,6 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/FoldingSet.h" -#include "llvm/ADT/None.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" @@ -66,6 +67,7 @@ #include <cstddef> #include <iterator> #include <memory> +#include <optional> #include <queue> #include <string> #include <tuple> @@ -188,6 +190,9 @@ public: PathPieces &getMutablePieces() { return PD->getMutablePieces(); } bool shouldAddPathEdges() const { return Consumer->shouldAddPathEdges(); } + bool shouldAddControlNotes() const { + return Consumer->shouldAddControlNotes(); + } bool shouldGenerateDiagnostics() const { return Consumer->shouldGenerateDiagnostics(); } @@ -218,8 +223,8 @@ class PathDiagnosticBuilder : public BugReporterContext { public: /// Find a non-invalidated report for a given equivalence class, and returns /// a PathDiagnosticBuilder able to construct bug reports for different - /// consumers. Returns None if no valid report is found. - static Optional<PathDiagnosticBuilder> + /// consumers. Returns std::nullopt if no valid report is found. + static std::optional<PathDiagnosticBuilder> findValidReport(ArrayRef<PathSensitiveBugReport *> &bugReports, PathSensitiveBugReporter &Reporter); @@ -294,26 +299,24 @@ std::string StackHintGeneratorForSymbol::getMessage(const ExplodedNode *N){ return {}; // Check if one of the parameters are set to the interesting symbol. - unsigned ArgIndex = 0; - for (CallExpr::const_arg_iterator I = CE->arg_begin(), - E = CE->arg_end(); I != E; ++I, ++ArgIndex){ - SVal SV = N->getSVal(*I); + for (auto [Idx, ArgExpr] : llvm::enumerate(CE->arguments())) { + SVal SV = N->getSVal(ArgExpr); // Check if the variable corresponding to the symbol is passed by value. SymbolRef AS = SV.getAsLocSymbol(); if (AS == Sym) { - return getMessageForArg(*I, ArgIndex); + return getMessageForArg(ArgExpr, Idx); } // Check if the parameter is a pointer to the symbol. - if (Optional<loc::MemRegionVal> Reg = SV.getAs<loc::MemRegionVal>()) { + if (std::optional<loc::MemRegionVal> Reg = SV.getAs<loc::MemRegionVal>()) { // Do not attempt to dereference void*. - if ((*I)->getType()->isVoidPointerType()) + if (ArgExpr->getType()->isVoidPointerType()) continue; SVal PSV = N->getState()->getSVal(Reg->getRegion()); SymbolRef AS = PSV.getAsLocSymbol(); if (AS == Sym) { - return getMessageForArg(*I, ArgIndex); + return getMessageForArg(ArgExpr, Idx); } } } @@ -534,10 +537,10 @@ static void removeEdgesToDefaultInitializers(PathPieces &Pieces) { if (auto *CF = dyn_cast<PathDiagnosticControlFlowPiece>(I->get())) { const Stmt *Start = CF->getStartLocation().asStmt(); const Stmt *End = CF->getEndLocation().asStmt(); - if (Start && isa<CXXDefaultInitExpr>(Start)) { + if (isa_and_nonnull<CXXDefaultInitExpr>(Start)) { I = Pieces.erase(I); continue; - } else if (End && isa<CXXDefaultInitExpr>(End)) { + } else if (isa_and_nonnull<CXXDefaultInitExpr>(End)) { PathPieces::iterator Next = std::next(I); if (Next != E) { if (auto *NextCF = @@ -764,7 +767,7 @@ PathDiagnosticPieceRef PathDiagnosticBuilder::generateDiagForSwitchOP( case Stmt::CaseStmtClass: { os << "Control jumps to 'case "; const auto *Case = cast<CaseStmt>(S); - const Expr *LHS = Case->getLHS()->IgnoreParenCasts(); + const Expr *LHS = Case->getLHS()->IgnoreParenImpCasts(); // Determine if it is an enum. bool GetRawInt = true; @@ -1030,7 +1033,7 @@ static bool isContainedByStmt(const ParentMap &PM, const Stmt *S, static const Stmt *getStmtBeforeCond(const ParentMap &PM, const Stmt *Term, const ExplodedNode *N) { while (N) { - Optional<StmtPoint> SP = N->getLocation().getAs<StmtPoint>(); + std::optional<StmtPoint> SP = N->getLocation().getAs<StmtPoint>(); if (SP) { const Stmt *S = SP->getStmt(); if (!isContainedByStmt(PM, Term, S)) @@ -1191,7 +1194,7 @@ void PathDiagnosticBuilder::generatePathDiagnosticsForNode( "location context associated with the active path!"); // Have we encountered an exit from a function call? - if (Optional<CallExitEnd> CE = P.getAs<CallExitEnd>()) { + if (std::optional<CallExitEnd> CE = P.getAs<CallExitEnd>()) { // We are descending into a call (backwards). Construct // a new call piece to contain the path pieces for that call. @@ -1232,8 +1235,11 @@ void PathDiagnosticBuilder::generatePathDiagnosticsForNode( } else if (auto BE = P.getAs<BlockEdge>()) { - if (!C.shouldAddPathEdges()) { + if (C.shouldAddControlNotes()) { generateMinimalDiagForBlockEdge(C, *BE); + } + + if (!C.shouldAddPathEdges()) { return; } @@ -1254,12 +1260,14 @@ void PathDiagnosticBuilder::generatePathDiagnosticsForNode( // do-while statements are explicitly excluded here auto p = std::make_shared<PathDiagnosticEventPiece>( - L, "Looping back to the head " - "of the loop"); + L, "Looping back to the head of the loop"); p->setPrunable(true); addEdgeToPath(C.getActivePath(), PrevLoc, p->getLocation()); - C.getActivePath().push_front(std::move(p)); + // We might've added a very similar control node already + if (!C.shouldAddControlNotes()) { + C.getActivePath().push_front(std::move(p)); + } if (const auto *CS = dyn_cast_or_null<CompoundStmt>(Body)) { addEdgeToPath(C.getActivePath(), PrevLoc, @@ -1300,10 +1308,13 @@ void PathDiagnosticBuilder::generatePathDiagnosticsForNode( auto PE = std::make_shared<PathDiagnosticEventPiece>(L, str); PE->setPrunable(true); addEdgeToPath(C.getActivePath(), PrevLoc, PE->getLocation()); - C.getActivePath().push_front(std::move(PE)); + + // We might've added a very similar control node already + if (!C.shouldAddControlNotes()) { + C.getActivePath().push_front(std::move(PE)); + } } - } else if (isa<BreakStmt>(Term) || isa<ContinueStmt>(Term) || - isa<GotoStmt>(Term)) { + } else if (isa<BreakStmt, ContinueStmt, GotoStmt>(Term)) { PathDiagnosticLocation L(Term, SM, C.getCurrLocationContext()); addEdgeToPath(C.getActivePath(), PrevLoc, L); } @@ -1342,9 +1353,7 @@ static const Stmt *getStmtParent(const Stmt *S, const ParentMap &PM) { if (!S) break; - if (isa<FullExpr>(S) || - isa<CXXBindTemporaryExpr>(S) || - isa<SubstNonTypeTemplateParmExpr>(S)) + if (isa<FullExpr, CXXBindTemporaryExpr, SubstNonTypeTemplateParmExpr>(S)) continue; break; @@ -1446,7 +1455,7 @@ static void addContextEdges(PathPieces &pieces, const LocationContext *LC) { break; // If the source is in the same context, we're already good. - if (llvm::find(SrcContexts, DstContext) != SrcContexts.end()) + if (llvm::is_contained(SrcContexts, DstContext)) break; // Update the subexpression node to point to the context edge. @@ -1540,9 +1549,8 @@ static void simplifySimpleBranches(PathPieces &pieces) { // We only perform this transformation for specific branch kinds. // We don't want to do this for do..while, for example. - if (!(isa<ForStmt>(s1Start) || isa<WhileStmt>(s1Start) || - isa<IfStmt>(s1Start) || isa<ObjCForCollectionStmt>(s1Start) || - isa<CXXForRangeStmt>(s1Start))) + if (!isa<ForStmt, WhileStmt, IfStmt, ObjCForCollectionStmt, + CXXForRangeStmt>(s1Start)) continue; // Is s1End the branch condition? @@ -1558,21 +1566,22 @@ static void simplifySimpleBranches(PathPieces &pieces) { /// Returns the number of bytes in the given (character-based) SourceRange. /// -/// If the locations in the range are not on the same line, returns None. +/// If the locations in the range are not on the same line, returns +/// std::nullopt. /// /// Note that this does not do a precise user-visible character or column count. -static Optional<size_t> getLengthOnSingleLine(const SourceManager &SM, - SourceRange Range) { +static std::optional<size_t> getLengthOnSingleLine(const SourceManager &SM, + SourceRange Range) { SourceRange ExpansionRange(SM.getExpansionLoc(Range.getBegin()), SM.getExpansionRange(Range.getEnd()).getEnd()); FileID FID = SM.getFileID(ExpansionRange.getBegin()); if (FID != SM.getFileID(ExpansionRange.getEnd())) - return None; + return std::nullopt; - Optional<MemoryBufferRef> Buffer = SM.getBufferOrNone(FID); + std::optional<MemoryBufferRef> Buffer = SM.getBufferOrNone(FID); if (!Buffer) - return None; + return std::nullopt; unsigned BeginOffset = SM.getFileOffset(ExpansionRange.getBegin()); unsigned EndOffset = SM.getFileOffset(ExpansionRange.getEnd()); @@ -1583,15 +1592,15 @@ static Optional<size_t> getLengthOnSingleLine(const SourceManager &SM, // SourceRange is covering a large or small amount of space in the user's // editor. if (Snippet.find_first_of("\r\n") != StringRef::npos) - return None; + return std::nullopt; // This isn't Unicode-aware, but it doesn't need to be. return Snippet.size(); } /// \sa getLengthOnSingleLine(SourceManager, SourceRange) -static Optional<size_t> getLengthOnSingleLine(const SourceManager &SM, - const Stmt *S) { +static std::optional<size_t> getLengthOnSingleLine(const SourceManager &SM, + const Stmt *S) { return getLengthOnSingleLine(SM, S->getSourceRange()); } @@ -1650,9 +1659,9 @@ static void removeContextCycles(PathPieces &Path, const SourceManager &SM) { if (s1Start && s2Start && s1Start == s2End && s2Start == s1End) { const size_t MAX_SHORT_LINE_LENGTH = 80; - Optional<size_t> s1Length = getLengthOnSingleLine(SM, s1Start); + std::optional<size_t> s1Length = getLengthOnSingleLine(SM, s1Start); if (s1Length && *s1Length <= MAX_SHORT_LINE_LENGTH) { - Optional<size_t> s2Length = getLengthOnSingleLine(SM, s2Start); + std::optional<size_t> s2Length = getLengthOnSingleLine(SM, s2Start); if (s2Length && *s2Length <= MAX_SHORT_LINE_LENGTH) { Path.erase(I); I = Path.erase(NextI); @@ -1711,7 +1720,7 @@ static void removePunyEdges(PathPieces &path, const SourceManager &SM, std::swap(SecondLoc, FirstLoc); SourceRange EdgeRange(FirstLoc, SecondLoc); - Optional<size_t> ByteWidth = getLengthOnSingleLine(SM, EdgeRange); + std::optional<size_t> ByteWidth = getLengthOnSingleLine(SM, EdgeRange); // If the statements are on different lines, continue. if (!ByteWidth) @@ -1875,7 +1884,7 @@ static bool optimizeEdges(const PathDiagnosticConstruct &C, PathPieces &path, lexicalContains(PM, s1Start, s1End)) { SourceRange EdgeRange(PieceI->getEndLocation().asLocation(), PieceI->getStartLocation().asLocation()); - if (!getLengthOnSingleLine(SM, EdgeRange).hasValue()) + if (!getLengthOnSingleLine(SM, EdgeRange)) removeEdge = true; } } @@ -2093,8 +2102,6 @@ PathDiagnosticBuilder::generate(const PathDiagnosticConsumer *PDC) const { void BugType::anchor() {} -void BuiltinBug::anchor() {} - //===----------------------------------------------------------------------===// // Methods for BugReport and subclasses. //===----------------------------------------------------------------------===// @@ -2135,15 +2142,14 @@ PathSensitiveBugReport::PathSensitiveBugReport( "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!"); + assert((bt.getCheckerName().starts_with("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( @@ -2302,7 +2308,7 @@ void PathSensitiveBugReport::markInteresting(const LocationContext *LC) { InterestingLocationContexts.insert(LC); } -Optional<bugreporter::TrackingKind> +std::optional<bugreporter::TrackingKind> PathSensitiveBugReport::getInterestingnessKind(SVal V) const { auto RKind = getInterestingnessKind(V.getAsRegion()); auto SKind = getInterestingnessKind(V.getAsSymbol()); @@ -2324,25 +2330,25 @@ PathSensitiveBugReport::getInterestingnessKind(SVal V) const { "BugReport::getInterestingnessKind currently can only handle 2 different " "tracking kinds! Please define what tracking kind should we return here " "when the kind of getAsRegion() and getAsSymbol() is different!"); - return None; + return std::nullopt; } -Optional<bugreporter::TrackingKind> +std::optional<bugreporter::TrackingKind> PathSensitiveBugReport::getInterestingnessKind(SymbolRef sym) const { if (!sym) - return None; + return std::nullopt; // We don't currently consider metadata symbols to be interesting // even if we know their region is interesting. Is that correct behavior? auto It = InterestingSymbols.find(sym); if (It == InterestingSymbols.end()) - return None; + return std::nullopt; return It->getSecond(); } -Optional<bugreporter::TrackingKind> +std::optional<bugreporter::TrackingKind> PathSensitiveBugReport::getInterestingnessKind(const MemRegion *R) const { if (!R) - return None; + return std::nullopt; R = R->getBaseRegion(); auto It = InterestingRegions.find(R); @@ -2351,19 +2357,19 @@ PathSensitiveBugReport::getInterestingnessKind(const MemRegion *R) const { if (const auto *SR = dyn_cast<SymbolicRegion>(R)) return getInterestingnessKind(SR->getSymbol()); - return None; + return std::nullopt; } bool PathSensitiveBugReport::isInteresting(SVal V) const { - return getInterestingnessKind(V).hasValue(); + return getInterestingnessKind(V).has_value(); } bool PathSensitiveBugReport::isInteresting(SymbolRef sym) const { - return getInterestingnessKind(sym).hasValue(); + return getInterestingnessKind(sym).has_value(); } bool PathSensitiveBugReport::isInteresting(const MemRegion *R) const { - return getInterestingnessKind(R).hasValue(); + return getInterestingnessKind(R).has_value(); } bool PathSensitiveBugReport::isInteresting(const LocationContext *LC) const { @@ -2379,7 +2385,7 @@ const Stmt *PathSensitiveBugReport::getStmt() const { ProgramPoint ProgP = ErrorNode->getLocation(); const Stmt *S = nullptr; - if (Optional<BlockEntrance> BE = ProgP.getAs<BlockEntrance>()) { + if (std::optional<BlockEntrance> BE = ProgP.getAs<BlockEntrance>()) { CFGBlock &Exit = ProgP.getLocationContext()->getCFG()->getExit(); if (BE->getBlock() == &Exit) S = ErrorNode->getPreviousStmtForDiagnostics(); @@ -2411,7 +2417,7 @@ PathSensitiveBugReport::getLocation() const { if (!S) { // If this is an implicit call, return the implicit call point location. - if (Optional<PreImplicitCall> PIE = P.getAs<PreImplicitCall>()) + if (std::optional<PreImplicitCall> PIE = P.getAs<PreImplicitCall>()) return PathDiagnosticLocation(PIE->getLocation(), SM); if (auto FE = P.getAs<FunctionExitPoint>()) { if (const ReturnStmt *RS = FE->getStmt()) @@ -2421,6 +2427,12 @@ PathSensitiveBugReport::getLocation() const { } if (S) { + // Attributed statements usually have corrupted begin locations, + // it's OK to ignore attributes for our purposes and deal with + // the actual annotated statement. + if (const auto *AS = dyn_cast<AttributedStmt>(S)) + S = AS->getSubStmt(); + // For member expressions, return the location of the '.' or '->'. if (const auto *ME = dyn_cast<MemberExpr>(S)) return PathDiagnosticLocation::createMemberLoc(ME, SM); @@ -2619,8 +2631,7 @@ BugPathInfo *BugPathGetter::getNextBugPath() { const ExplodedNode *OrigN; std::tie(CurrentBugPath.Report, OrigN) = ReportNodes.pop_back_val(); - assert(PriorityMap.find(OrigN) != PriorityMap.end() && - "error node not accessible from root"); + assert(PriorityMap.contains(OrigN) && "error node not accessible from root"); // Create a new graph with a single path. This is the graph that will be // returned to the caller. @@ -2813,7 +2824,7 @@ generateVisitorsDiagnostics(PathSensitiveBugReport *R, return Notes; } -Optional<PathDiagnosticBuilder> PathDiagnosticBuilder::findValidReport( +std::optional<PathDiagnosticBuilder> PathDiagnosticBuilder::findValidReport( ArrayRef<PathSensitiveBugReport *> &bugReports, PathSensitiveBugReporter &Reporter) { @@ -2872,7 +2883,7 @@ PathSensitiveBugReporter::generatePathDiagnostics( auto Out = std::make_unique<DiagnosticForConsumerMapTy>(); - Optional<PathDiagnosticBuilder> PDB = + std::optional<PathDiagnosticBuilder> PDB = PathDiagnosticBuilder::findValidReport(bugReports, *this); if (PDB) { @@ -2894,6 +2905,10 @@ void BugReporter::emitReport(std::unique_ptr<BugReport> R) { if (!ValidSourceLoc) return; + // If the user asked to suppress this report, we should skip it. + if (UserSuppressions.isSuppressed(*R)) + return; + // Compute the bug report's hash to determine its equivalence class. llvm::FoldingSetNodeID ID; R->Profile(ID); @@ -3061,8 +3076,7 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) { // See whether we need to silence the checker/package. for (const std::string &CheckerOrPackage : getAnalyzerOptions().SilencedCheckersAndPackages) { - if (report->getBugType().getCheckerName().startswith( - CheckerOrPackage)) + if (report->getBugType().getCheckerName().starts_with(CheckerOrPackage)) return; } @@ -3089,9 +3103,8 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) { if (getAnalyzerOptions().ShouldDisplayNotesAsEvents) { // For path diagnostic consumers that don't support extra notes, // we may optionally convert those to path notes. - for (auto I = report->getNotes().rbegin(), - E = report->getNotes().rend(); I != E; ++I) { - PathDiagnosticNotePiece *Piece = I->get(); + for (const auto &I : llvm::reverse(report->getNotes())) { + PathDiagnosticNotePiece *Piece = I.get(); auto ConvertedPiece = std::make_shared<PathDiagnosticEventPiece>( Piece->getLocation(), Piece->getString()); for (const auto &R: Piece->getRanges()) @@ -3100,9 +3113,8 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) { Pieces.push_front(std::move(ConvertedPiece)); } } else { - for (auto I = report->getNotes().rbegin(), - E = report->getNotes().rend(); I != E; ++I) - Pieces.push_front(*I); + for (const auto &I : llvm::reverse(report->getNotes())) + Pieces.push_front(I); } for (const auto &I : report->getFixits()) @@ -3181,7 +3193,7 @@ findExecutedLines(const SourceManager &SM, const ExplodedNode *N) { P = N->getParentMap().getParent(RS); } - if (P && (isa<SwitchCase>(P) || isa<LabelStmt>(P))) + if (isa_and_nonnull<SwitchCase, LabelStmt>(P)) populateExecutedLinesWithStmt(P, SM, *ExecutedLines); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index d06a2d493303..2f9965036b9e 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -46,8 +46,6 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "llvm/ADT/ArrayRef.h" -#include "llvm/ADT/None.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" @@ -60,6 +58,7 @@ #include <cassert> #include <deque> #include <memory> +#include <optional> #include <string> #include <utility> @@ -82,6 +81,10 @@ static const Expr *peelOffPointerArithmetic(const BinaryOperator *B) { return nullptr; } +/// \return A subexpression of @c Ex which represents the +/// expression-of-interest. +static const Expr *peelOffOuterExpr(const Expr *Ex, const ExplodedNode *N); + /// Given that expression S represents a pointer that would be dereferenced, /// try to find a sub-expression from which the pointer came from. /// This is used for tracking down origins of a null or undefined value: @@ -129,6 +132,16 @@ const Expr *bugreporter::getDerefExpr(const Stmt *S) { } // Pattern match for a few useful cases: a[0], p->f, *p etc. else if (const auto *ME = dyn_cast<MemberExpr>(E)) { + // This handles the case when the dereferencing of a member reference + // happens. This is needed, because the AST for dereferencing a + // member reference looks like the following: + // |-MemberExpr + // `-DeclRefExpr + // Without this special case the notes would refer to the whole object + // (struct, class or union variable) instead of just the relevant member. + + if (ME->getMemberDecl()->getType()->isReferenceType()) + break; E = ME->getBase(); } else if (const auto *IvarRef = dyn_cast<ObjCIvarRefExpr>(E)) { E = IvarRef->getBase(); @@ -154,26 +167,42 @@ const Expr *bugreporter::getDerefExpr(const Stmt *S) { return E; } +static const VarDecl *getVarDeclForExpression(const Expr *E) { + if (const auto *DR = dyn_cast<DeclRefExpr>(E)) + return dyn_cast<VarDecl>(DR->getDecl()); + return nullptr; +} + static const MemRegion * getLocationRegionIfReference(const Expr *E, const ExplodedNode *N, bool LookingForReference = true) { - if (const auto *DR = dyn_cast<DeclRefExpr>(E)) { - if (const auto *VD = dyn_cast<VarDecl>(DR->getDecl())) { - if (LookingForReference && !VD->getType()->isReferenceType()) - return nullptr; - return N->getState() - ->getLValue(VD, N->getLocationContext()) - .getAsRegion(); + if (const auto *ME = dyn_cast<MemberExpr>(E)) { + // This handles null references from FieldRegions, for example: + // struct Wrapper { int &ref; }; + // Wrapper w = { *(int *)0 }; + // w.ref = 1; + const Expr *Base = ME->getBase(); + const VarDecl *VD = getVarDeclForExpression(Base); + if (!VD) + return nullptr; + + const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl()); + if (!FD) + return nullptr; + + if (FD->getType()->isReferenceType()) { + SVal StructSVal = N->getState()->getLValue(VD, N->getLocationContext()); + return N->getState()->getLValue(FD, StructSVal).getAsRegion(); } + return nullptr; } - // FIXME: This does not handle other kinds of null references, - // for example, references from FieldRegions: - // struct Wrapper { int &ref; }; - // Wrapper w = { *(int *)0 }; - // w.ref = 1; - - return nullptr; + const VarDecl *VD = getVarDeclForExpression(E); + if (!VD) + return nullptr; + if (LookingForReference && !VD->getType()->isReferenceType()) + return nullptr; + return N->getState()->getLValue(VD, N->getLocationContext()).getAsRegion(); } /// Comparing internal representations of symbolic values (via @@ -202,8 +231,8 @@ static bool hasVisibleUpdate(const ExplodedNode *LeftNode, SVal LeftVal, RLCV->getStore() == RightNode->getState()->getStore(); } -static Optional<SVal> getSValForVar(const Expr *CondVarExpr, - const ExplodedNode *N) { +static std::optional<SVal> getSValForVar(const Expr *CondVarExpr, + const ExplodedNode *N) { ProgramStateRef State = N->getState(); const LocationContext *LCtx = N->getLocationContext(); @@ -223,16 +252,16 @@ static Optional<SVal> getSValForVar(const Expr *CondVarExpr, if (auto FieldL = State->getSVal(ME, LCtx).getAs<Loc>()) return State->getRawSVal(*FieldL, FD->getType()); - return None; + return std::nullopt; } -static Optional<const llvm::APSInt *> +static std::optional<const llvm::APSInt *> getConcreteIntegerValue(const Expr *CondVarExpr, const ExplodedNode *N) { - if (Optional<SVal> V = getSValForVar(CondVarExpr, N)) + if (std::optional<SVal> V = getSValForVar(CondVarExpr, N)) if (auto CI = V->getAs<nonloc::ConcreteInt>()) return &CI->getValue(); - return None; + return std::nullopt; } static bool isVarAnInterestingCondition(const Expr *CondVarExpr, @@ -244,8 +273,9 @@ static bool isVarAnInterestingCondition(const Expr *CondVarExpr, if (!B->getErrorNode()->getStackFrame()->isParentOf(N->getStackFrame())) return false; - if (Optional<SVal> V = getSValForVar(CondVarExpr, N)) - if (Optional<bugreporter::TrackingKind> K = B->getInterestingnessKind(*V)) + if (std::optional<SVal> V = getSValForVar(CondVarExpr, N)) + if (std::optional<bugreporter::TrackingKind> K = + B->getInterestingnessKind(*V)) return *K == bugreporter::TrackingKind::Condition; return false; @@ -253,8 +283,8 @@ static bool isVarAnInterestingCondition(const Expr *CondVarExpr, static bool isInterestingExpr(const Expr *E, const ExplodedNode *N, const PathSensitiveBugReport *B) { - if (Optional<SVal> V = getSValForVar(E, N)) - return B->getInterestingnessKind(*V).hasValue(); + if (std::optional<SVal> V = getSValForVar(E, N)) + return B->getInterestingnessKind(*V).has_value(); return false; } @@ -344,45 +374,178 @@ BugReporterVisitor::getDefaultEndPath(const BugReporterContext &BRC, } //===----------------------------------------------------------------------===// +// Implementation of NoStateChangeFuncVisitor. +//===----------------------------------------------------------------------===// + +bool NoStateChangeFuncVisitor::isModifiedInFrame(const ExplodedNode *N) { + const LocationContext *Ctx = N->getLocationContext(); + const StackFrameContext *SCtx = Ctx->getStackFrame(); + if (!FramesModifyingCalculated.count(SCtx)) + findModifyingFrames(N); + return FramesModifying.count(SCtx); +} + +void NoStateChangeFuncVisitor::markFrameAsModifying( + const StackFrameContext *SCtx) { + while (!SCtx->inTopFrame()) { + auto p = FramesModifying.insert(SCtx); + if (!p.second) + break; // Frame and all its parents already inserted. + + SCtx = SCtx->getParent()->getStackFrame(); + } +} + +static const ExplodedNode *getMatchingCallExitEnd(const ExplodedNode *N) { + assert(N->getLocationAs<CallEnter>()); + // The stackframe of the callee is only found in the nodes succeeding + // the CallEnter node. CallEnter's stack frame refers to the caller. + const StackFrameContext *OrigSCtx = N->getFirstSucc()->getStackFrame(); + + // Similarly, the nodes preceding CallExitEnd refer to the callee's stack + // frame. + auto IsMatchingCallExitEnd = [OrigSCtx](const ExplodedNode *N) { + return N->getLocationAs<CallExitEnd>() && + OrigSCtx == N->getFirstPred()->getStackFrame(); + }; + while (N && !IsMatchingCallExitEnd(N)) { + assert(N->succ_size() <= 1 && + "This function is to be used on the trimmed ExplodedGraph!"); + N = N->getFirstSucc(); + } + return N; +} + +void NoStateChangeFuncVisitor::findModifyingFrames( + const ExplodedNode *const CallExitBeginN) { + + assert(CallExitBeginN->getLocationAs<CallExitBegin>()); + + const StackFrameContext *const OriginalSCtx = + CallExitBeginN->getLocationContext()->getStackFrame(); + + const ExplodedNode *CurrCallExitBeginN = CallExitBeginN; + const StackFrameContext *CurrentSCtx = OriginalSCtx; + + for (const ExplodedNode *CurrN = CallExitBeginN; CurrN; + CurrN = CurrN->getFirstPred()) { + // Found a new inlined call. + if (CurrN->getLocationAs<CallExitBegin>()) { + CurrCallExitBeginN = CurrN; + CurrentSCtx = CurrN->getStackFrame(); + FramesModifyingCalculated.insert(CurrentSCtx); + // We won't see a change in between two identical exploded nodes: skip. + continue; + } + + if (auto CE = CurrN->getLocationAs<CallEnter>()) { + if (const ExplodedNode *CallExitEndN = getMatchingCallExitEnd(CurrN)) + if (wasModifiedInFunction(CurrN, CallExitEndN)) + markFrameAsModifying(CurrentSCtx); + + // We exited this inlined call, lets actualize the stack frame. + CurrentSCtx = CurrN->getStackFrame(); + + // Stop calculating at the current function, but always regard it as + // modifying, so we can avoid notes like this: + // void f(Foo &F) { + // F.field = 0; // note: 0 assigned to 'F.field' + // // note: returning without writing to 'F.field' + // } + if (CE->getCalleeContext() == OriginalSCtx) { + markFrameAsModifying(CurrentSCtx); + break; + } + } + + if (wasModifiedBeforeCallExit(CurrN, CurrCallExitBeginN)) + markFrameAsModifying(CurrentSCtx); + } +} + +PathDiagnosticPieceRef NoStateChangeFuncVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BR, PathSensitiveBugReport &R) { + + const LocationContext *Ctx = N->getLocationContext(); + const StackFrameContext *SCtx = Ctx->getStackFrame(); + ProgramStateRef State = N->getState(); + auto CallExitLoc = N->getLocationAs<CallExitBegin>(); + + // No diagnostic if region was modified inside the frame. + if (!CallExitLoc || isModifiedInFrame(N)) + return nullptr; + + CallEventRef<> Call = + BR.getStateManager().getCallEventManager().getCaller(SCtx, State); + + // Optimistically suppress uninitialized value bugs that result + // from system headers having a chance to initialize the value + // but failing to do so. It's too unlikely a system header's fault. + // It's much more likely a situation in which the function has a failure + // mode that the user decided not to check. If we want to hunt such + // omitted checks, we should provide an explicit function-specific note + // describing the precondition under which the function isn't supposed to + // initialize its out-parameter, and additionally check that such + // precondition can actually be fulfilled on the current path. + if (Call->isInSystemHeader()) { + // We make an exception for system header functions that have no branches. + // Such functions unconditionally fail to initialize the variable. + // If they call other functions that have more paths within them, + // this suppression would still apply when we visit these inner functions. + // One common example of a standard function that doesn't ever initialize + // its out parameter is operator placement new; it's up to the follow-up + // constructor (if any) to initialize the memory. + if (!N->getStackFrame()->getCFG()->isLinear()) { + static int i = 0; + R.markInvalid(&i, nullptr); + } + return nullptr; + } + + if (const auto *MC = dyn_cast<ObjCMethodCall>(Call)) { + // If we failed to construct a piece for self, we still want to check + // whether the entity of interest is in a parameter. + if (PathDiagnosticPieceRef Piece = maybeEmitNoteForObjCSelf(R, *MC, N)) + return Piece; + } + + if (const auto *CCall = dyn_cast<CXXConstructorCall>(Call)) { + // Do not generate diagnostics for not modified parameters in + // constructors. + return maybeEmitNoteForCXXThis(R, *CCall, N); + } + + return maybeEmitNoteForParameters(R, *Call, N); +} + +//===----------------------------------------------------------------------===// // Implementation of NoStoreFuncVisitor. //===----------------------------------------------------------------------===// namespace { - /// Put a diagnostic on return statement of all inlined functions /// for which the region of interest \p RegionOfInterest was passed into, /// but not written inside, and it has caused an undefined read or a null /// pointer dereference outside. -class NoStoreFuncVisitor final : public BugReporterVisitor { +class NoStoreFuncVisitor final : public NoStateChangeFuncVisitor { const SubRegion *RegionOfInterest; MemRegionManager &MmrMgr; const SourceManager &SM; const PrintingPolicy &PP; - bugreporter::TrackingKind TKind; /// Recursion limit for dereferencing fields when looking for the /// region of interest. /// The limit of two indicates that we will dereference fields only once. static const unsigned DEREFERENCE_LIMIT = 2; - /// Frames writing into \c RegionOfInterest. - /// This visitor generates a note only if a function does not write into - /// a region of interest. This information is not immediately available - /// by looking at the node associated with the exit from the function - /// (usually the return statement). To avoid recomputing the same information - /// many times (going up the path for each node and checking whether the - /// region was written into) we instead lazily compute the - /// stack frames along the path which write into the region of interest. - llvm::SmallPtrSet<const StackFrameContext *, 32> FramesModifyingRegion; - llvm::SmallPtrSet<const StackFrameContext *, 32> FramesModifyingCalculated; - using RegionVector = SmallVector<const MemRegion *, 5>; public: NoStoreFuncVisitor(const SubRegion *R, bugreporter::TrackingKind TKind) - : RegionOfInterest(R), MmrMgr(R->getMemRegionManager()), + : NoStateChangeFuncVisitor(TKind), RegionOfInterest(R), + MmrMgr(R->getMemRegionManager()), SM(MmrMgr.getContext().getSourceManager()), - PP(MmrMgr.getContext().getPrintingPolicy()), TKind(TKind) {} + PP(MmrMgr.getContext().getPrintingPolicy()) {} void Profile(llvm::FoldingSetNodeID &ID) const override { static int Tag = 0; @@ -390,41 +553,36 @@ public: ID.AddPointer(RegionOfInterest); } - void *getTag() const { - static int Tag = 0; - return static_cast<void *>(&Tag); - } - - PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, - BugReporterContext &BR, - PathSensitiveBugReport &R) override; - private: + /// \return Whether \c RegionOfInterest was modified at \p CurrN compared to + /// the value it holds in \p CallExitBeginN. + bool wasModifiedBeforeCallExit(const ExplodedNode *CurrN, + const ExplodedNode *CallExitBeginN) override; + /// Attempts to find the region of interest in a given record decl, /// by either following the base classes or fields. /// Dereferences fields up to a given recursion limit. /// Note that \p Vec is passed by value, leading to quadratic copying cost, /// but it's OK in practice since its length is limited to DEREFERENCE_LIMIT. - /// \return A chain fields leading to the region of interest or None. - const Optional<RegionVector> + /// \return A chain fields leading to the region of interest or std::nullopt. + const std::optional<RegionVector> findRegionOfInterestInRecord(const RecordDecl *RD, ProgramStateRef State, const MemRegion *R, const RegionVector &Vec = {}, int depth = 0); - /// Check and lazily calculate whether the region of interest is - /// modified in the stack frame to which \p N belongs. - /// The calculation is cached in FramesModifyingRegion. - bool isRegionOfInterestModifiedInFrame(const ExplodedNode *N) { - const LocationContext *Ctx = N->getLocationContext(); - const StackFrameContext *SCtx = Ctx->getStackFrame(); - if (!FramesModifyingCalculated.count(SCtx)) - findModifyingFrames(N); - return FramesModifyingRegion.count(SCtx); - } + // Region of interest corresponds to an IVar, exiting a method + // which could have written into that IVar, but did not. + PathDiagnosticPieceRef maybeEmitNoteForObjCSelf(PathSensitiveBugReport &R, + const ObjCMethodCall &Call, + const ExplodedNode *N) final; + + PathDiagnosticPieceRef maybeEmitNoteForCXXThis(PathSensitiveBugReport &R, + const CXXConstructorCall &Call, + const ExplodedNode *N) final; - /// Write to \c FramesModifyingRegion all stack frames along - /// the path in the current stack frame which modify \c RegionOfInterest. - void findModifyingFrames(const ExplodedNode *N); + PathDiagnosticPieceRef + maybeEmitNoteForParameters(PathSensitiveBugReport &R, const CallEvent &Call, + const ExplodedNode *N) final; /// Consume the information on the no-store stack frame in order to /// either emit a note or suppress the report enirely. @@ -436,22 +594,18 @@ private: const MemRegion *MatchedRegion, StringRef FirstElement, bool FirstIsReferenceType, unsigned IndirectionLevel); - /// Pretty-print region \p MatchedRegion to \p os. - /// \return Whether printing succeeded. - bool prettyPrintRegionName(StringRef FirstElement, bool FirstIsReferenceType, + bool prettyPrintRegionName(const RegionVector &FieldChain, const MemRegion *MatchedRegion, - const RegionVector &FieldChain, - int IndirectionLevel, + StringRef FirstElement, bool FirstIsReferenceType, + unsigned IndirectionLevel, llvm::raw_svector_ostream &os); - /// Print first item in the chain, return new separator. - static StringRef prettyPrintFirstElement(StringRef FirstElement, - bool MoreItemsExpected, - int IndirectionLevel, - llvm::raw_svector_ostream &os); + StringRef prettyPrintFirstElement(StringRef FirstElement, + bool MoreItemsExpected, + int IndirectionLevel, + llvm::raw_svector_ostream &os); }; - -} // end of anonymous namespace +} // namespace /// \return Whether the method declaration \p Parent /// syntactically has a binary operation writing into the ivar \p Ivar. @@ -478,7 +632,7 @@ static bool potentiallyWritesIntoIvar(const Decl *Parent, if (const auto *DRE = dyn_cast<DeclRefExpr>(Base)) if (const auto *ID = dyn_cast<ImplicitParamDecl>(DRE->getDecl())) - if (ID->getParameterKind() == ImplicitParamDecl::ObjCSelf) + if (ID->getParameterKind() == ImplicitParamKind::ObjCSelf) return true; return false; @@ -486,50 +640,31 @@ static bool potentiallyWritesIntoIvar(const Decl *Parent, return false; } -/// Get parameters associated with runtime definition in order -/// to get the correct parameter name. -static ArrayRef<ParmVarDecl *> getCallParameters(CallEventRef<> Call) { - // Use runtime definition, if available. - RuntimeDefinition RD = Call->getRuntimeDefinition(); - if (const auto *FD = dyn_cast_or_null<FunctionDecl>(RD.getDecl())) - return FD->parameters(); - if (const auto *MD = dyn_cast_or_null<ObjCMethodDecl>(RD.getDecl())) - return MD->parameters(); - - return Call->parameters(); -} - -/// \return whether \p Ty points to a const type, or is a const reference. -static bool isPointerToConst(QualType Ty) { - return !Ty->getPointeeType().isNull() && - Ty->getPointeeType().getCanonicalType().isConstQualified(); -} - /// Attempts to find the region of interest in a given CXX decl, /// by either following the base classes or fields. /// Dereferences fields up to a given recursion limit. /// Note that \p Vec is passed by value, leading to quadratic copying cost, /// but it's OK in practice since its length is limited to DEREFERENCE_LIMIT. -/// \return A chain fields leading to the region of interest or None. -const Optional<NoStoreFuncVisitor::RegionVector> +/// \return A chain fields leading to the region of interest or std::nullopt. +const std::optional<NoStoreFuncVisitor::RegionVector> NoStoreFuncVisitor::findRegionOfInterestInRecord( const RecordDecl *RD, ProgramStateRef State, const MemRegion *R, const NoStoreFuncVisitor::RegionVector &Vec /* = {} */, int depth /* = 0 */) { if (depth == DEREFERENCE_LIMIT) // Limit the recursion depth. - return None; + return std::nullopt; if (const auto *RDX = dyn_cast<CXXRecordDecl>(RD)) if (!RDX->hasDefinition()) - return None; + return std::nullopt; // Recursively examine the base classes. // Note that following base classes does not increase the recursion depth. if (const auto *RDX = dyn_cast<CXXRecordDecl>(RD)) for (const auto &II : RDX->bases()) if (const RecordDecl *RRD = II.getType()->getAsRecordDecl()) - if (Optional<RegionVector> Out = + if (std::optional<RegionVector> Out = findRegionOfInterestInRecord(RRD, State, R, Vec, depth)) return Out; @@ -555,77 +690,75 @@ NoStoreFuncVisitor::findRegionOfInterestInRecord( continue; if (const RecordDecl *RRD = PT->getAsRecordDecl()) - if (Optional<RegionVector> Out = + if (std::optional<RegionVector> Out = findRegionOfInterestInRecord(RRD, State, VR, VecF, depth + 1)) return Out; } - return None; + return std::nullopt; } PathDiagnosticPieceRef -NoStoreFuncVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BR, - PathSensitiveBugReport &R) { - - const LocationContext *Ctx = N->getLocationContext(); - const StackFrameContext *SCtx = Ctx->getStackFrame(); - ProgramStateRef State = N->getState(); - auto CallExitLoc = N->getLocationAs<CallExitBegin>(); - - // No diagnostic if region was modified inside the frame. - if (!CallExitLoc || isRegionOfInterestModifiedInFrame(N)) - return nullptr; - - CallEventRef<> Call = - BR.getStateManager().getCallEventManager().getCaller(SCtx, State); - - // Region of interest corresponds to an IVar, exiting a method - // which could have written into that IVar, but did not. - if (const auto *MC = dyn_cast<ObjCMethodCall>(Call)) { - if (const auto *IvarR = dyn_cast<ObjCIvarRegion>(RegionOfInterest)) { - const MemRegion *SelfRegion = MC->getReceiverSVal().getAsRegion(); - if (RegionOfInterest->isSubRegionOf(SelfRegion) && - potentiallyWritesIntoIvar(Call->getRuntimeDefinition().getDecl(), - IvarR->getDecl())) - return maybeEmitNote(R, *Call, N, {}, SelfRegion, "self", - /*FirstIsReferenceType=*/false, 1); - } +NoStoreFuncVisitor::maybeEmitNoteForObjCSelf(PathSensitiveBugReport &R, + const ObjCMethodCall &Call, + const ExplodedNode *N) { + if (const auto *IvarR = dyn_cast<ObjCIvarRegion>(RegionOfInterest)) { + const MemRegion *SelfRegion = Call.getReceiverSVal().getAsRegion(); + if (RegionOfInterest->isSubRegionOf(SelfRegion) && + potentiallyWritesIntoIvar(Call.getRuntimeDefinition().getDecl(), + IvarR->getDecl())) + return maybeEmitNote(R, Call, N, {}, SelfRegion, "self", + /*FirstIsReferenceType=*/false, 1); } + return nullptr; +} - if (const auto *CCall = dyn_cast<CXXConstructorCall>(Call)) { - const MemRegion *ThisR = CCall->getCXXThisVal().getAsRegion(); - if (RegionOfInterest->isSubRegionOf(ThisR) && - !CCall->getDecl()->isImplicit()) - return maybeEmitNote(R, *Call, N, {}, ThisR, "this", - /*FirstIsReferenceType=*/false, 1); +PathDiagnosticPieceRef +NoStoreFuncVisitor::maybeEmitNoteForCXXThis(PathSensitiveBugReport &R, + const CXXConstructorCall &Call, + const ExplodedNode *N) { + const MemRegion *ThisR = Call.getCXXThisVal().getAsRegion(); + if (RegionOfInterest->isSubRegionOf(ThisR) && !Call.getDecl()->isImplicit()) + return maybeEmitNote(R, Call, N, {}, ThisR, "this", + /*FirstIsReferenceType=*/false, 1); + + // Do not generate diagnostics for not modified parameters in + // constructors. + return nullptr; +} - // Do not generate diagnostics for not modified parameters in - // constructors. - return nullptr; - } +/// \return whether \p Ty points to a const type, or is a const reference. +static bool isPointerToConst(QualType Ty) { + return !Ty->getPointeeType().isNull() && + Ty->getPointeeType().getCanonicalType().isConstQualified(); +} - ArrayRef<ParmVarDecl *> parameters = getCallParameters(Call); - for (unsigned I = 0; I < Call->getNumArgs() && I < parameters.size(); ++I) { - const ParmVarDecl *PVD = parameters[I]; - SVal V = Call->getArgSVal(I); +PathDiagnosticPieceRef NoStoreFuncVisitor::maybeEmitNoteForParameters( + PathSensitiveBugReport &R, const CallEvent &Call, const ExplodedNode *N) { + ArrayRef<ParmVarDecl *> Parameters = Call.parameters(); + for (unsigned I = 0; I < Call.getNumArgs() && I < Parameters.size(); ++I) { + const ParmVarDecl *PVD = Parameters[I]; + SVal V = Call.getArgSVal(I); bool ParamIsReferenceType = PVD->getType()->isReferenceType(); std::string ParamName = PVD->getNameAsString(); - int IndirectionLevel = 1; + unsigned IndirectionLevel = 1; QualType T = PVD->getType(); while (const MemRegion *MR = V.getAsRegion()) { if (RegionOfInterest->isSubRegionOf(MR) && !isPointerToConst(T)) - return maybeEmitNote(R, *Call, N, {}, MR, ParamName, + return maybeEmitNote(R, Call, N, {}, MR, ParamName, ParamIsReferenceType, IndirectionLevel); QualType PT = T->getPointeeType(); if (PT.isNull() || PT->isVoidType()) break; + ProgramStateRef State = N->getState(); + if (const RecordDecl *RD = PT->getAsRecordDecl()) - if (Optional<RegionVector> P = + if (std::optional<RegionVector> P = findRegionOfInterestInRecord(RD, State, MR)) - return maybeEmitNote(R, *Call, N, *P, RegionOfInterest, ParamName, + return maybeEmitNote(R, Call, N, *P, RegionOfInterest, ParamName, ParamIsReferenceType, IndirectionLevel); V = State->getSVal(MR, PT); @@ -637,40 +770,11 @@ NoStoreFuncVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BR, return nullptr; } -void NoStoreFuncVisitor::findModifyingFrames(const ExplodedNode *N) { - assert(N->getLocationAs<CallExitBegin>()); - ProgramStateRef LastReturnState = N->getState(); - SVal ValueAtReturn = LastReturnState->getSVal(RegionOfInterest); - const LocationContext *Ctx = N->getLocationContext(); - const StackFrameContext *OriginalSCtx = Ctx->getStackFrame(); - - do { - ProgramStateRef State = N->getState(); - auto CallExitLoc = N->getLocationAs<CallExitBegin>(); - if (CallExitLoc) { - LastReturnState = State; - ValueAtReturn = LastReturnState->getSVal(RegionOfInterest); - } - - FramesModifyingCalculated.insert(N->getLocationContext()->getStackFrame()); - - if (wasRegionOfInterestModifiedAt(RegionOfInterest, N, ValueAtReturn)) { - const StackFrameContext *SCtx = N->getStackFrame(); - while (!SCtx->inTopFrame()) { - auto p = FramesModifyingRegion.insert(SCtx); - if (!p.second) - break; // Frame and all its parents already inserted. - SCtx = SCtx->getParent()->getStackFrame(); - } - } - - // Stop calculation at the call to the current function. - if (auto CE = N->getLocationAs<CallEnter>()) - if (CE->getCalleeContext() == OriginalSCtx) - break; - - N = N->getFirstPred(); - } while (N); +bool NoStoreFuncVisitor::wasModifiedBeforeCallExit( + const ExplodedNode *CurrN, const ExplodedNode *CallExitBeginN) { + return ::wasRegionOfInterestModifiedAt( + RegionOfInterest, CurrN, + CallExitBeginN->getState()->getSVal(RegionOfInterest)); } static llvm::StringLiteral WillBeUsedForACondition = @@ -681,27 +785,6 @@ PathDiagnosticPieceRef NoStoreFuncVisitor::maybeEmitNote( const RegionVector &FieldChain, const MemRegion *MatchedRegion, StringRef FirstElement, bool FirstIsReferenceType, unsigned IndirectionLevel) { - // Optimistically suppress uninitialized value bugs that result - // from system headers having a chance to initialize the value - // but failing to do so. It's too unlikely a system header's fault. - // It's much more likely a situation in which the function has a failure - // mode that the user decided not to check. If we want to hunt such - // omitted checks, we should provide an explicit function-specific note - // describing the precondition under which the function isn't supposed to - // initialize its out-parameter, and additionally check that such - // precondition can actually be fulfilled on the current path. - if (Call.isInSystemHeader()) { - // We make an exception for system header functions that have no branches. - // Such functions unconditionally fail to initialize the variable. - // If they call other functions that have more paths within them, - // this suppression would still apply when we visit these inner functions. - // One common example of a standard function that doesn't ever initialize - // its out parameter is operator placement new; it's up to the follow-up - // constructor (if any) to initialize the memory. - if (!N->getStackFrame()->getCFG()->isLinear()) - R.markInvalid(getTag(), nullptr); - return nullptr; - } PathDiagnosticLocation L = PathDiagnosticLocation::create(N->getLocation(), SM); @@ -717,8 +800,8 @@ PathDiagnosticPieceRef NoStoreFuncVisitor::maybeEmitNote( os << "Returning without writing to '"; // Do not generate the note if failed to pretty-print. - if (!prettyPrintRegionName(FirstElement, FirstIsReferenceType, MatchedRegion, - FieldChain, IndirectionLevel, os)) + if (!prettyPrintRegionName(FieldChain, MatchedRegion, FirstElement, + FirstIsReferenceType, IndirectionLevel, os)) return nullptr; os << "'"; @@ -727,11 +810,11 @@ PathDiagnosticPieceRef NoStoreFuncVisitor::maybeEmitNote( return std::make_shared<PathDiagnosticEventPiece>(L, os.str()); } -bool NoStoreFuncVisitor::prettyPrintRegionName(StringRef FirstElement, - bool FirstIsReferenceType, +bool NoStoreFuncVisitor::prettyPrintRegionName(const RegionVector &FieldChain, const MemRegion *MatchedRegion, - const RegionVector &FieldChain, - int IndirectionLevel, + StringRef FirstElement, + bool FirstIsReferenceType, + unsigned IndirectionLevel, llvm::raw_svector_ostream &os) { if (FirstIsReferenceType) @@ -754,7 +837,7 @@ bool NoStoreFuncVisitor::prettyPrintRegionName(StringRef FirstElement, // Just keep going up to the base region. // Element regions may appear due to casts. - if (isa<CXXBaseObjectRegion>(R) || isa<CXXTempObjectRegion>(R)) + if (isa<CXXBaseObjectRegion, CXXTempObjectRegion>(R)) continue; if (Sep.empty()) @@ -854,7 +937,7 @@ public: const SVal V) { AnalyzerOptions &Options = N->getState()->getAnalysisManager().options; if (EnableNullFPSuppression && Options.ShouldSuppressNullReturnPaths && - V.getAs<Loc>()) + isa<Loc>(V)) BR.addVisitor<MacroNullReturnSuppressionVisitor>(R->getAs<SubRegion>(), V); } @@ -871,12 +954,12 @@ public: private: /// \return Source location of right hand side of an assignment /// into \c RegionOfInterest, empty optional if none found. - Optional<SourceLocation> matchAssignment(const ExplodedNode *N) { + std::optional<SourceLocation> matchAssignment(const ExplodedNode *N) { const Stmt *S = N->getStmtForDiagnostics(); ProgramStateRef State = N->getState(); auto *LCtx = N->getLocationContext(); if (!S) - return None; + return std::nullopt; if (const auto *DS = dyn_cast<DeclStmt>(S)) { if (const auto *VD = dyn_cast<VarDecl>(DS->getSingleDecl())) @@ -891,7 +974,7 @@ private: return RHS->getBeginLoc(); } } - return None; + return std::nullopt; } }; @@ -944,7 +1027,7 @@ public: if (N->getLocationContext() != CalleeSFC) return nullptr; - Optional<StmtPoint> SP = N->getLocationAs<StmtPoint>(); + std::optional<StmtPoint> SP = N->getLocationAs<StmtPoint>(); if (!SP) return nullptr; @@ -966,18 +1049,17 @@ public: assert(RetE && "Tracking a return value for a void function"); // Handle cases where a reference is returned and then immediately used. - Optional<Loc> LValue; + std::optional<Loc> LValue; if (RetE->isGLValue()) { if ((LValue = V.getAs<Loc>())) { SVal RValue = State->getRawSVal(*LValue, RetE->getType()); - if (RValue.getAs<DefinedSVal>()) + if (isa<DefinedSVal>(RValue)) V = RValue; } } // Ignore aggregate rvalues. - if (V.getAs<nonloc::LazyCompoundVal>() || - V.getAs<nonloc::CompoundVal>()) + if (isa<nonloc::LazyCompoundVal, nonloc::CompoundVal>(V)) return nullptr; RetE = RetE->IgnoreParenCasts(); @@ -992,7 +1074,7 @@ public: bool WouldEventBeMeaningless = false; if (State->isNull(V).isConstrainedTrue()) { - if (V.getAs<Loc>()) { + if (isa<Loc>(V)) { // If we have counter-suppression enabled, make sure we keep visiting // future nodes. We want to emit a path note as well, in case @@ -1022,10 +1104,7 @@ public: if (N->getCFG().size() == 3) WouldEventBeMeaningless = true; - if (V.getAs<Loc>()) - Out << "Returning pointer"; - else - Out << "Returning value"; + Out << (isa<Loc>(V) ? "Returning pointer" : "Returning value"); } } @@ -1069,7 +1148,7 @@ public: assert(Options.ShouldAvoidSuppressingNullArgumentPaths); // Are we at the entry node for this call? - Optional<CallEnter> CE = N->getLocationAs<CallEnter>(); + std::optional<CallEnter> CE = N->getLocationAs<CallEnter>(); if (!CE) return nullptr; @@ -1087,7 +1166,7 @@ public: ProgramStateRef State = N->getState(); CallEventRef<> Call = CallMgr.getCaller(CalleeSFC, State); for (unsigned I = 0, E = Call->getNumArgs(); I != E; ++I) { - Optional<Loc> ArgV = Call->getArgSVal(I).getAs<Loc>(); + std::optional<Loc> ArgV = Call->getArgSVal(I).getAs<Loc>(); if (!ArgV) continue; @@ -1134,8 +1213,6 @@ public: } }; -} // end of anonymous namespace - //===----------------------------------------------------------------------===// // StoreSiteFinder //===----------------------------------------------------------------------===// @@ -1153,7 +1230,7 @@ class StoreSiteFinder final : public TrackingBugReporterVisitor { public: /// \param V We're searching for the store where \c R received this value. /// \param R The region we're tracking. - /// \param TKind May limit the amount of notes added to the bug report. + /// \param Options Tracking behavior options. /// \param OriginSFC Only adds notes when the last store happened in a /// different stackframe to this one. Disregarded if the tracking kind /// is thorough. @@ -1175,6 +1252,7 @@ public: BugReporterContext &BRC, PathSensitiveBugReport &BR) override; }; +} // namespace void StoreSiteFinder::Profile(llvm::FoldingSetNodeID &ID) const { static int tag = 0; @@ -1188,7 +1266,7 @@ void StoreSiteFinder::Profile(llvm::FoldingSetNodeID &ID) const { /// Returns true if \p N represents the DeclStmt declaring and initializing /// \p VR. static bool isInitializationOfVar(const ExplodedNode *N, const VarRegion *VR) { - Optional<PostStmt> P = N->getLocationAs<PostStmt>(); + std::optional<PostStmt> P = N->getLocationAs<PostStmt>(); if (!P) return false; @@ -1248,7 +1326,7 @@ static void showBRDiagnostics(llvm::raw_svector_ostream &OS, StoreInfo SI) { llvm_unreachable("Unexpected store kind"); } - if (SI.Value.getAs<loc::ConcreteInt>()) { + if (isa<loc::ConcreteInt>(SI.Value)) { OS << Action << (isObjCPointer(SI.Dest) ? "nil" : "a null pointer value"); } else if (auto CVal = SI.Value.getAs<nonloc::ConcreteInt>()) { @@ -1287,13 +1365,12 @@ static void showBRDiagnostics(llvm::raw_svector_ostream &OS, StoreInfo SI) { static void showBRParamDiagnostics(llvm::raw_svector_ostream &OS, StoreInfo SI) { const auto *VR = cast<VarRegion>(SI.Dest); - const auto *Param = cast<ParmVarDecl>(VR->getDecl()); + const auto *D = VR->getDecl(); OS << "Passing "; - if (SI.Value.getAs<loc::ConcreteInt>()) { - OS << (isObjCPointer(Param) ? "nil object reference" - : "null pointer value"); + if (isa<loc::ConcreteInt>(SI.Value)) { + OS << (isObjCPointer(D) ? "nil object reference" : "null pointer value"); } else if (SI.Value.isUndef()) { OS << "uninitialized value"; @@ -1308,12 +1385,18 @@ static void showBRParamDiagnostics(llvm::raw_svector_ostream &OS, OS << "value"; } - // Printed parameter indexes are 1-based, not 0-based. - unsigned Idx = Param->getFunctionScopeIndex() + 1; - OS << " via " << Idx << llvm::getOrdinalSuffix(Idx) << " parameter"; - if (VR->canPrintPretty()) { - OS << " "; - VR->printPretty(OS); + if (const auto *Param = dyn_cast<ParmVarDecl>(VR->getDecl())) { + // Printed parameter indexes are 1-based, not 0-based. + unsigned Idx = Param->getFunctionScopeIndex() + 1; + OS << " via " << Idx << llvm::getOrdinalSuffix(Idx) << " parameter"; + if (VR->canPrintPretty()) { + OS << " "; + VR->printPretty(OS); + } + } else if (const auto *ImplParam = dyn_cast<ImplicitParamDecl>(D)) { + if (ImplParam->getParameterKind() == ImplicitParamKind::ObjCSelf) { + OS << " via implicit parameter 'self'"; + } } } @@ -1322,7 +1405,7 @@ static void showBRDefaultDiagnostics(llvm::raw_svector_ostream &OS, StoreInfo SI) { const bool HasSuffix = SI.Dest->canPrintPretty(); - if (SI.Value.getAs<loc::ConcreteInt>()) { + if (isa<loc::ConcreteInt>(SI.Value)) { OS << (isObjCPointer(SI.Dest) ? "nil object reference stored" : (HasSuffix ? "Null pointer value stored" : "Storing null pointer value")); @@ -1357,6 +1440,83 @@ static void showBRDefaultDiagnostics(llvm::raw_svector_ostream &OS, } } +static bool isTrivialCopyOrMoveCtor(const CXXConstructExpr *CE) { + if (!CE) + return false; + + const auto *CtorDecl = CE->getConstructor(); + + return CtorDecl->isCopyOrMoveConstructor() && CtorDecl->isTrivial(); +} + +static const Expr *tryExtractInitializerFromList(const InitListExpr *ILE, + const MemRegion *R) { + + const auto *TVR = dyn_cast_or_null<TypedValueRegion>(R); + + if (!TVR) + return nullptr; + + const auto ITy = ILE->getType().getCanonicalType(); + + // Push each sub-region onto the stack. + std::stack<const TypedValueRegion *> TVRStack; + while (isa<FieldRegion>(TVR) || isa<ElementRegion>(TVR)) { + // We found a region that matches the type of the init list, + // so we assume this is the outer-most region. This can happen + // if the initializer list is inside a class. If our assumption + // is wrong, we return a nullptr in the end. + if (ITy == TVR->getValueType().getCanonicalType()) + break; + + TVRStack.push(TVR); + TVR = cast<TypedValueRegion>(TVR->getSuperRegion()); + } + + // If the type of the outer most region doesn't match the type + // of the ILE, we can't match the ILE and the region. + if (ITy != TVR->getValueType().getCanonicalType()) + return nullptr; + + const Expr *Init = ILE; + while (!TVRStack.empty()) { + TVR = TVRStack.top(); + TVRStack.pop(); + + // We hit something that's not an init list before + // running out of regions, so we most likely failed. + if (!isa<InitListExpr>(Init)) + return nullptr; + + ILE = cast<InitListExpr>(Init); + auto NumInits = ILE->getNumInits(); + + if (const auto *FR = dyn_cast<FieldRegion>(TVR)) { + const auto *FD = FR->getDecl(); + + if (FD->getFieldIndex() >= NumInits) + return nullptr; + + Init = ILE->getInit(FD->getFieldIndex()); + } else if (const auto *ER = dyn_cast<ElementRegion>(TVR)) { + const auto Ind = ER->getIndex(); + + // If index is symbolic, we can't figure out which expression + // belongs to the region. + if (!Ind.isConstant()) + return nullptr; + + const auto IndVal = Ind.getAsInteger()->getLimitedValue(); + if (IndVal >= NumInits) + return nullptr; + + Init = ILE->getInit(IndVal); + } + } + + return Init; +} + PathDiagnosticPieceRef StoreSiteFinder::VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, PathSensitiveBugReport &BR) { @@ -1378,7 +1538,8 @@ PathDiagnosticPieceRef StoreSiteFinder::VisitNode(const ExplodedNode *Succ, // If this is a post initializer expression, initializing the region, we // should track the initializer expression. - if (Optional<PostInitializer> PIP = Pred->getLocationAs<PostInitializer>()) { + if (std::optional<PostInitializer> PIP = + Pred->getLocationAs<PostInitializer>()) { const MemRegion *FieldReg = (const MemRegion *)PIP->getLocationValue(); if (FieldReg == R) { StoreSite = Pred; @@ -1396,25 +1557,101 @@ PathDiagnosticPieceRef StoreSiteFinder::VisitNode(const ExplodedNode *Succ, return nullptr; if (hasVisibleUpdate(Pred, Pred->getState()->getSVal(R), Succ, V)) { - Optional<PostStore> PS = Succ->getLocationAs<PostStore>(); + std::optional<PostStore> PS = Succ->getLocationAs<PostStore>(); if (!PS || PS->getLocationValue() != R) return nullptr; } StoreSite = Succ; - // If this is an assignment expression, we can track the value - // being assigned. - if (Optional<PostStmt> P = Succ->getLocationAs<PostStmt>()) - if (const BinaryOperator *BO = P->getStmtAs<BinaryOperator>()) + if (std::optional<PostStmt> P = Succ->getLocationAs<PostStmt>()) { + // If this is an assignment expression, we can track the value + // being assigned. + if (const BinaryOperator *BO = P->getStmtAs<BinaryOperator>()) { if (BO->isAssignmentOp()) InitE = BO->getRHS(); + } + // If we have a declaration like 'S s{1,2}' that needs special + // handling, we handle it here. + else if (const auto *DS = P->getStmtAs<DeclStmt>()) { + const auto *Decl = DS->getSingleDecl(); + if (isa<VarDecl>(Decl)) { + const auto *VD = cast<VarDecl>(Decl); + + // FIXME: Here we only track the inner most region, so we lose + // information, but it's still better than a crash or no information + // at all. + // + // E.g.: The region we have is 's.s2.s3.s4.y' and we only track 'y', + // and throw away the rest. + if (const auto *ILE = dyn_cast<InitListExpr>(VD->getInit())) + InitE = tryExtractInitializerFromList(ILE, R); + } + } else if (const auto *CE = P->getStmtAs<CXXConstructExpr>()) { + + const auto State = Succ->getState(); + + if (isTrivialCopyOrMoveCtor(CE) && isa<SubRegion>(R)) { + // Migrate the field regions from the current object to + // the parent object. If we track 'a.y.e' and encounter + // 'S a = b' then we need to track 'b.y.e'. + + // Push the regions to a stack, from last to first, so + // considering the example above the stack will look like + // (bottom) 'e' -> 'y' (top). + + std::stack<const SubRegion *> SRStack; + const SubRegion *SR = cast<SubRegion>(R); + while (isa<FieldRegion>(SR) || isa<ElementRegion>(SR)) { + SRStack.push(SR); + SR = cast<SubRegion>(SR->getSuperRegion()); + } + + // Get the region for the object we copied/moved from. + const auto *OriginEx = CE->getArg(0); + const auto OriginVal = + State->getSVal(OriginEx, Succ->getLocationContext()); + + // Pop the stored field regions and apply them to the origin + // object in the same order we had them on the copy. + // OriginField will evolve like 'b' -> 'b.y' -> 'b.y.e'. + SVal OriginField = OriginVal; + while (!SRStack.empty()) { + const auto *TopR = SRStack.top(); + SRStack.pop(); + + if (const auto *FR = dyn_cast<FieldRegion>(TopR)) { + OriginField = State->getLValue(FR->getDecl(), OriginField); + } else if (const auto *ER = dyn_cast<ElementRegion>(TopR)) { + OriginField = State->getLValue(ER->getElementType(), + ER->getIndex(), OriginField); + } else { + // FIXME: handle other region type + } + } + + // Track 'b.y.e'. + getParentTracker().track(V, OriginField.getAsRegion(), Options); + InitE = OriginEx; + } + } + // This branch can occur in cases like `Ctor() : field{ x, y } {}'. + else if (const auto *ILE = P->getStmtAs<InitListExpr>()) { + // FIXME: Here we only track the top level region, so we lose + // information, but it's still better than a crash or no information + // at all. + // + // E.g.: The region we have is 's.s2.s3.s4.y' and we only track 'y', and + // throw away the rest. + InitE = tryExtractInitializerFromList(ILE, R); + } + } // If this is a call entry, the variable should be a parameter. // FIXME: Handle CXXThisRegion as well. (This is not a priority because // 'this' should never be NULL, but this visitor isn't just for NULL and // UndefinedVal.) - if (Optional<CallEnter> CE = Succ->getLocationAs<CallEnter>()) { + if (std::optional<CallEnter> CE = Succ->getLocationAs<CallEnter>()) { if (const auto *VR = dyn_cast<VarRegion>(R)) { if (const auto *Param = dyn_cast<ParmVarDecl>(VR->getDecl())) { @@ -1537,7 +1774,7 @@ PathDiagnosticPieceRef StoreSiteFinder::VisitNode(const ExplodedNode *Succ, R, OldRegion}; - if (Optional<PostStmt> PS = StoreSite->getLocationAs<PostStmt>()) { + if (std::optional<PostStmt> PS = StoreSite->getLocationAs<PostStmt>()) { const Stmt *S = PS->getStmt(); const auto *DS = dyn_cast<DeclStmt>(S); const auto *VR = dyn_cast<VarRegion>(R); @@ -1574,6 +1811,7 @@ PathDiagnosticPieceRef StoreSiteFinder::VisitNode(const ExplodedNode *Succ, void TrackConstraintBRVisitor::Profile(llvm::FoldingSetNodeID &ID) const { static int tag = 0; ID.AddPointer(&tag); + ID.AddString(Message); ID.AddBoolean(Assumption); ID.Add(Constraint); } @@ -1584,8 +1822,12 @@ const char *TrackConstraintBRVisitor::getTag() { return "TrackConstraintBRVisitor"; } +bool TrackConstraintBRVisitor::isZeroCheck() const { + return !Assumption && Constraint.getAs<Loc>(); +} + bool TrackConstraintBRVisitor::isUnderconstrained(const ExplodedNode *N) const { - if (IsZeroCheck) + if (isZeroCheck()) return N->getState()->isNull(Constraint).isUnderconstrained(); return (bool)N->getState()->assume(Constraint, !Assumption); } @@ -1609,32 +1851,27 @@ PathDiagnosticPieceRef TrackConstraintBRVisitor::VisitNode( if (isUnderconstrained(PrevN)) { IsSatisfied = true; - // As a sanity check, make sure that the negation of the constraint - // was infeasible in the current state. If it is feasible, we somehow - // missed the transition point. + // At this point, the negation of the constraint should be infeasible. If it + // is feasible, make sure that the negation of the constrainti was + // infeasible in the current state. If it is feasible, we somehow missed + // the transition point. assert(!isUnderconstrained(N)); - // We found the transition point for the constraint. We now need to - // pretty-print the constraint. (work-in-progress) - SmallString<64> sbuf; - llvm::raw_svector_ostream os(sbuf); - - if (Constraint.getAs<Loc>()) { - os << "Assuming pointer value is "; - os << (Assumption ? "non-null" : "null"); - } + // Construct a new PathDiagnosticPiece. + ProgramPoint P = N->getLocation(); - if (os.str().empty()) + // If this node already have a specialized note, it's probably better + // than our generic note. + // FIXME: This only looks for note tags, not for other ways to add a note. + if (isa_and_nonnull<NoteTag>(P.getTag())) return nullptr; - // Construct a new PathDiagnosticPiece. - ProgramPoint P = N->getLocation(); PathDiagnosticLocation L = PathDiagnosticLocation::create(P, BRC.getSourceManager()); if (!L.isValid()) return nullptr; - auto X = std::make_shared<PathDiagnosticEventPiece>(L, os.str()); + auto X = std::make_shared<PathDiagnosticEventPiece>(L, Message); X->setTag(getTag()); return std::move(X); } @@ -1857,11 +2094,33 @@ TrackControlDependencyCondBRVisitor::VisitNode(const ExplodedNode *N, return nullptr; if (const Expr *Condition = NB->getLastCondition()) { + + // If we can't retrieve a sensible condition, just bail out. + const Expr *InnerExpr = peelOffOuterExpr(Condition, N); + if (!InnerExpr) + return nullptr; + + // If the condition was a function call, we likely won't gain much from + // tracking it either. Evidence suggests that it will mostly trigger in + // scenarios like this: + // + // void f(int *x) { + // x = nullptr; + // if (alwaysTrue()) // We don't need a whole lot of explanation + // // here, the function name is good enough. + // *x = 5; + // } + // + // Its easy to create a counterexample where this heuristic would make us + // lose valuable information, but we've never really seen one in practice. + if (isa<CallExpr>(InnerExpr)) + return nullptr; + // Keeping track of the already tracked conditions on a visitor level // isn't sufficient, because a new visitor is created for each tracked // expression, hence the BugReport level set. if (BR.addTrackedCondition(N)) { - getParentTracker().track(Condition, N, + getParentTracker().track(InnerExpr, N, {bugreporter::TrackingKind::Condition, /*EnableNullFPSuppression=*/false}); return constructDebugPieceForTrackedCondition(Condition, N, BRC); @@ -1876,10 +2135,8 @@ TrackControlDependencyCondBRVisitor::VisitNode(const ExplodedNode *N, // Implementation of trackExpressionValue. //===----------------------------------------------------------------------===// -/// \return A subexpression of @c Ex which represents the -/// expression-of-interest. -static const Expr *peelOffOuterExpr(const Expr *Ex, - const ExplodedNode *N) { +static const Expr *peelOffOuterExpr(const Expr *Ex, const ExplodedNode *N) { + Ex = Ex->IgnoreParenCasts(); if (const auto *FE = dyn_cast<FullExpr>(Ex)) return peelOffOuterExpr(FE->getSubExpr(), N); @@ -1902,7 +2159,7 @@ static const Expr *peelOffOuterExpr(const Expr *Ex, const ExplodedNode *NI = N; do { ProgramPoint ProgPoint = NI->getLocation(); - if (Optional<BlockEdge> BE = ProgPoint.getAs<BlockEdge>()) { + if (std::optional<BlockEdge> BE = ProgPoint.getAs<BlockEdge>()) { const CFGBlock *srcBlk = BE->getSrc(); if (const Stmt *term = srcBlk->getTerminatorStmt()) { if (term == CO) { @@ -1977,6 +2234,7 @@ PathDiagnosticPieceRef StoreHandler::constructNote(StoreInfo SI, return std::make_shared<PathDiagnosticEventPiece>(L, NodeText); } +namespace { class DefaultStoreHandler final : public StoreHandler { public: using StoreHandler::StoreHandler; @@ -2125,8 +2383,9 @@ public: // null. if (V.getAsLocSymbol(/*IncludeBaseRegions=*/true)) if (LVState->isNull(V).isConstrainedTrue()) - Report.addVisitor<TrackConstraintBRVisitor>(V.castAs<DefinedSVal>(), - false); + Report.addVisitor<TrackConstraintBRVisitor>( + V.castAs<DefinedSVal>(), + /*Assumption=*/false, "Assuming pointer value is null"); // Add visitor, which will suppress inline defensive checks. if (auto DV = V.getAs<DefinedSVal>()) @@ -2174,7 +2433,8 @@ class InlinedFunctionCallHandler final : public ExpressionHandler { do { // If that is satisfied we found our statement as an inlined call. - if (Optional<CallExitEnd> CEE = ExprNode->getLocationAs<CallExitEnd>()) + if (std::optional<CallExitEnd> CEE = + ExprNode->getLocationAs<CallExitEnd>()) if (CEE->getCalleeContext()->getCallSite() == E) break; @@ -2189,7 +2449,7 @@ class InlinedFunctionCallHandler final : public ExpressionHandler { // FIXME: This code currently bypasses the call site for the // conservatively evaluated allocator. if (!BypassCXXNewExprEval) - if (Optional<StmtPoint> SP = ExprNode->getLocationAs<StmtPoint>()) + if (std::optional<StmtPoint> SP = ExprNode->getLocationAs<StmtPoint>()) // See if we do not enter into another context. if (SP->getStmt() == E && CurrentSFC == PredSFC) break; @@ -2204,7 +2464,7 @@ class InlinedFunctionCallHandler final : public ExpressionHandler { return {}; // Finally, see if we inlined the call. - Optional<CallExitEnd> CEE = ExprNode->getLocationAs<CallExitEnd>(); + std::optional<CallExitEnd> CEE = ExprNode->getLocationAs<CallExitEnd>(); if (!CEE) return {}; @@ -2218,7 +2478,7 @@ class InlinedFunctionCallHandler final : public ExpressionHandler { // Handle cases where a reference is returned and then immediately used. if (cast<Expr>(E)->isGLValue()) - if (Optional<Loc> LValue = RetVal.getAs<Loc>()) + if (std::optional<Loc> LValue = RetVal.getAs<Loc>()) RetVal = State->getSVal(*LValue); // See if the return value is NULL. If so, suppress the report. @@ -2226,7 +2486,7 @@ class InlinedFunctionCallHandler final : public ExpressionHandler { bool EnableNullFPSuppression = false; if (Opts.EnableNullFPSuppression && Options.ShouldSuppressNullReturnPaths) - if (Optional<Loc> RetLoc = RetVal.getAs<Loc>()) + if (std::optional<Loc> RetLoc = RetVal.getAs<Loc>()) EnableNullFPSuppression = State->isNull(*RetLoc).isConstrainedTrue(); PathSensitiveBugReport &Report = getParentTracker().getReport(); @@ -2261,7 +2521,7 @@ public: // what is written inside the pointer. bool CanDereference = true; if (const auto *SR = L->getRegionAs<SymbolicRegion>()) { - if (SR->getSymbol()->getType()->getPointeeType()->isVoidType()) + if (SR->getPointeeStaticType()->isVoidType()) CanDereference = false; } else if (L->getRegionAs<AllocaRegion>()) CanDereference = false; @@ -2271,7 +2531,7 @@ public: // well. Try to use the correct type when looking up the value. SVal RVal; if (ExplodedGraph::isInterestingLValueExpr(Inner)) - RVal = LVState->getRawSVal(L.getValue(), Inner->getType()); + RVal = LVState->getRawSVal(*L, Inner->getType()); else if (CanDereference) RVal = LVState->getSVal(L->getRegion()); @@ -2289,7 +2549,7 @@ public: Report.markInteresting(RegionRVal, Opts.Kind); Report.addVisitor<TrackConstraintBRVisitor>( loc::MemRegionVal(RegionRVal), - /*assumption=*/false); + /*Assumption=*/false, "Assuming pointer value is null"); Result.FoundSomethingToTrack = true; } } @@ -2314,6 +2574,29 @@ public: if (!RVNode) return {}; + Tracker::Result CombinedResult; + Tracker &Parent = getParentTracker(); + + const auto track = [&CombinedResult, &Parent, ExprNode, + Opts](const Expr *Inner) { + CombinedResult.combineWith(Parent.track(Inner, ExprNode, Opts)); + }; + + // FIXME: Initializer lists can appear in many different contexts + // and most of them needs a special handling. For now let's handle + // what we can. If the initializer list only has 1 element, we track + // that. + // This snippet even handles nesting, e.g.: int *x{{{{{y}}}}}; + if (const auto *ILE = dyn_cast<InitListExpr>(E)) { + if (ILE->getNumInits() == 1) { + track(ILE->getInit(0)); + + return CombinedResult; + } + + return {}; + } + ProgramStateRef RVState = RVNode->getState(); SVal V = RVState->getSValAsScalarOrLoc(E, RVNode->getLocationContext()); const auto *BO = dyn_cast<BinaryOperator>(E); @@ -2325,13 +2608,6 @@ public: SVal LHSV = RVState->getSVal(BO->getLHS(), RVNode->getLocationContext()); // Track both LHS and RHS of a multiplication. - Tracker::Result CombinedResult; - Tracker &Parent = getParentTracker(); - - const auto track = [&CombinedResult, &Parent, ExprNode, Opts](Expr *Inner) { - CombinedResult.combineWith(Parent.track(Inner, ExprNode, Opts)); - }; - if (BO->getOpcode() == BO_Mul) { if (LHSV.isZeroConstant()) track(BO->getLHS()); @@ -2345,6 +2621,7 @@ public: return CombinedResult; } }; +} // namespace Tracker::Tracker(PathSensitiveBugReport &Report) : Report(Report) { // Default expression handlers. @@ -2443,7 +2720,7 @@ const Expr *NilReceiverBRVisitor::getNilReceiver(const Stmt *S, PathDiagnosticPieceRef NilReceiverBRVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &BR) { - Optional<PreStmt> P = N->getLocationAs<PreStmt>(); + std::optional<PreStmt> P = N->getLocationAs<PreStmt>(); if (!P) return nullptr; @@ -2507,7 +2784,7 @@ ConditionBRVisitor::VisitNodeImpl(const ExplodedNode *N, // If an assumption was made on a branch, it should be caught // here by looking at the state transition. - if (Optional<BlockEdge> BE = ProgPoint.getAs<BlockEdge>()) { + if (std::optional<BlockEdge> BE = ProgPoint.getAs<BlockEdge>()) { const CFGBlock *SrcBlock = BE->getSrc(); if (const Stmt *Term = SrcBlock->getTerminatorStmt()) { // If the tag of the previous node is 'Eagerly Assume...' the current @@ -2524,7 +2801,7 @@ ConditionBRVisitor::VisitNodeImpl(const ExplodedNode *N, return nullptr; } - if (Optional<PostStmt> PS = ProgPoint.getAs<PostStmt>()) { + if (std::optional<PostStmt> PS = ProgPoint.getAs<PostStmt>()) { const ProgramPointTag *CurrentNodeTag = PS->getTag(); if (CurrentNodeTag != Tags.first && CurrentNodeTag != Tags.second) return nullptr; @@ -2663,20 +2940,17 @@ ConditionBRVisitor::VisitTrueTest(const Expr *Cond, BugReporterContext &BRC, Loc, TookTrue ? GenericTrueMessage : GenericFalseMessage); } -bool ConditionBRVisitor::patternMatch(const Expr *Ex, - const Expr *ParentEx, - raw_ostream &Out, - BugReporterContext &BRC, +bool ConditionBRVisitor::patternMatch(const Expr *Ex, const Expr *ParentEx, + raw_ostream &Out, BugReporterContext &BRC, PathSensitiveBugReport &report, const ExplodedNode *N, - Optional<bool> &prunable, + std::optional<bool> &prunable, bool IsSameFieldName) { const Expr *OriginalExpr = Ex; Ex = Ex->IgnoreParenCasts(); - if (isa<GNUNullExpr>(Ex) || isa<ObjCBoolLiteralExpr>(Ex) || - isa<CXXBoolLiteralExpr>(Ex) || isa<IntegerLiteral>(Ex) || - isa<FloatingLiteral>(Ex)) { + if (isa<GNUNullExpr, ObjCBoolLiteralExpr, CXXBoolLiteralExpr, IntegerLiteral, + FloatingLiteral>(Ex)) { // Use heuristics to determine if the expression is a macro // expanding to a literal and if so, use the macro's name. SourceLocation BeginLoc = OriginalExpr->getBeginLoc(); @@ -2743,7 +3017,8 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex, Out << '\'' << Lexer::getSourceText( CharSourceRange::getTokenRange(Ex->getSourceRange()), - BRC.getSourceManager(), BRC.getASTContext().getLangOpts(), 0) + BRC.getSourceManager(), BRC.getASTContext().getLangOpts(), + nullptr) << '\''; } @@ -2755,7 +3030,7 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( PathSensitiveBugReport &R, const ExplodedNode *N, bool TookTrue, bool IsAssuming) { bool shouldInvert = false; - Optional<bool> shouldPrune; + std::optional<bool> shouldPrune; // Check if the field name of the MemberExprs is ambiguous. Example: // " 'a.d' is equal to 'h.d' " in 'test/Analysis/null-deref-path-notes.cpp'. @@ -2865,8 +3140,8 @@ PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( PathDiagnosticLocation Loc(Cond, SM, LCtx); auto event = std::make_shared<PathDiagnosticEventPiece>(Loc, Message); - if (shouldPrune.hasValue()) - event->setPrunable(shouldPrune.getValue()); + if (shouldPrune) + event->setPrunable(*shouldPrune); return event; } @@ -2989,20 +3264,20 @@ bool ConditionBRVisitor::printValue(const Expr *CondVarExpr, raw_ostream &Out, if (!Ty->isIntegralOrEnumerationType()) return false; - Optional<const llvm::APSInt *> IntValue; + std::optional<const llvm::APSInt *> IntValue; if (!IsAssuming) IntValue = getConcreteIntegerValue(CondVarExpr, N); - if (IsAssuming || !IntValue.hasValue()) { + if (IsAssuming || !IntValue) { if (Ty->isBooleanType()) Out << (TookTrue ? "true" : "false"); else Out << (TookTrue ? "not equal to 0" : "0"); } else { if (Ty->isBooleanType()) - Out << (IntValue.getValue()->getBoolValue() ? "true" : "false"); + Out << ((*IntValue)->getBoolValue() ? "true" : "false"); else - Out << *IntValue.getValue(); + Out << **IntValue; } return true; @@ -3097,7 +3372,7 @@ void LikelyFalsePositiveSuppressionBRVisitor::finalizeVisitor( FullSourceLoc Loc = BR.getLocation().asLocation(); while (Loc.isMacroID()) { Loc = Loc.getSpellingLoc(); - if (SM.getFilename(Loc).endswith("sys/queue.h")) { + if (SM.getFilename(Loc).ends_with("sys/queue.h")) { BR.markInvalid(getTag(), nullptr); return; } @@ -3115,7 +3390,7 @@ UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, ProgramPoint ProgLoc = N->getLocation(); // We are only interested in visiting CallEnter nodes. - Optional<CallEnter> CEnter = ProgLoc.getAs<CallEnter>(); + std::optional<CallEnter> CEnter = ProgLoc.getAs<CallEnter>(); if (!CEnter) return nullptr; @@ -3194,11 +3469,11 @@ void FalsePositiveRefutationBRVisitor::finalizeVisitor( } // And check for satisfiability - Optional<bool> IsSAT = RefutationSolver->check(); - if (!IsSAT.hasValue()) + std::optional<bool> IsSAT = RefutationSolver->check(); + if (!IsSAT) return; - if (!IsSAT.getValue()) + if (!*IsSAT) BR.markInvalid("Infeasible constraints", EndPathNode->getLocationContext()); } @@ -3253,7 +3528,7 @@ PathDiagnosticPieceRef TagVisitor::VisitNode(const ExplodedNode *N, if (!T) return nullptr; - if (Optional<std::string> Msg = T->generateMessage(BRC, R)) { + if (std::optional<std::string> Msg = T->generateMessage(BRC, R)) { PathDiagnosticLocation Loc = PathDiagnosticLocation::create(PP, BRC.getSourceManager()); auto Piece = std::make_shared<PathDiagnosticEventPiece>(Loc, *Msg); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp new file mode 100644 index 000000000000..b5991e47a538 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp @@ -0,0 +1,169 @@ +//===- BugSuppression.cpp - Suppression interface -------------------------===// +// +// 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/BugReporter/BugSuppression.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" + +using namespace clang; +using namespace ento; + +namespace { + +using Ranges = llvm::SmallVectorImpl<SourceRange>; + +inline bool hasSuppression(const Decl *D) { + // FIXME: Implement diagnostic identifier arguments + // (checker names, "hashtags"). + if (const auto *Suppression = D->getAttr<SuppressAttr>()) + return !Suppression->isGSL() && + (Suppression->diagnosticIdentifiers().empty()); + return false; +} +inline bool hasSuppression(const AttributedStmt *S) { + // FIXME: Implement diagnostic identifier arguments + // (checker names, "hashtags"). + return llvm::any_of(S->getAttrs(), [](const Attr *A) { + const auto *Suppression = dyn_cast<SuppressAttr>(A); + return Suppression && !Suppression->isGSL() && + (Suppression->diagnosticIdentifiers().empty()); + }); +} + +template <class NodeType> inline SourceRange getRange(const NodeType *Node) { + return Node->getSourceRange(); +} +template <> inline SourceRange getRange(const AttributedStmt *S) { + // Begin location for attributed statement node seems to be ALWAYS invalid. + // + // It is unlikely that we ever report any warnings on suppression + // attribute itself, but even if we do, we wouldn't want that warning + // to be suppressed by that same attribute. + // + // Long story short, we can use inner statement and it's not going to break + // anything. + return getRange(S->getSubStmt()); +} + +inline bool isLessOrEqual(SourceLocation LHS, SourceLocation RHS, + const SourceManager &SM) { + // SourceManager::isBeforeInTranslationUnit tests for strict + // inequality, when we need a non-strict comparison (bug + // can be reported directly on the annotated note). + // For this reason, we use the following equivalence: + // + // A <= B <==> !(B < A) + // + return !SM.isBeforeInTranslationUnit(RHS, LHS); +} + +inline bool fullyContains(SourceRange Larger, SourceRange Smaller, + const SourceManager &SM) { + // Essentially this means: + // + // Larger.fullyContains(Smaller) + // + // However, that method has a very trivial implementation and couldn't + // compare regular locations and locations from macro expansions. + // We could've converted everything into regular locations as a solution, + // but the following solution seems to be the most bulletproof. + return isLessOrEqual(Larger.getBegin(), Smaller.getBegin(), SM) && + isLessOrEqual(Smaller.getEnd(), Larger.getEnd(), SM); +} + +class CacheInitializer : public RecursiveASTVisitor<CacheInitializer> { +public: + static void initialize(const Decl *D, Ranges &ToInit) { + CacheInitializer(ToInit).TraverseDecl(const_cast<Decl *>(D)); + } + + bool VisitVarDecl(VarDecl *VD) { + // Bug location could be somewhere in the init value of + // a freshly declared variable. Even though it looks like the + // user applied attribute to a statement, it will apply to a + // variable declaration, and this is where we check for it. + return VisitAttributedNode(VD); + } + + bool VisitAttributedStmt(AttributedStmt *AS) { + // When we apply attributes to statements, it actually creates + // a wrapper statement that only contains attributes and the wrapped + // statement. + return VisitAttributedNode(AS); + } + +private: + template <class NodeType> bool VisitAttributedNode(NodeType *Node) { + if (hasSuppression(Node)) { + // TODO: In the future, when we come up with good stable IDs for checkers + // we can return a list of kinds to ignore, or all if no arguments + // were provided. + addRange(getRange(Node)); + } + // We should keep traversing AST. + return true; + } + + void addRange(SourceRange R) { + if (R.isValid()) { + Result.push_back(R); + } + } + + CacheInitializer(Ranges &R) : Result(R) {} + Ranges &Result; +}; + +} // end anonymous namespace + +// TODO: Introduce stable IDs for checkers and check for those here +// to be more specific. Attribute without arguments should still +// be considered as "suppress all". +// It is already much finer granularity than what we have now +// (i.e. removing the whole function from the analysis). +bool BugSuppression::isSuppressed(const BugReport &R) { + PathDiagnosticLocation Location = R.getLocation(); + PathDiagnosticLocation UniqueingLocation = R.getUniqueingLocation(); + const Decl *DeclWithIssue = R.getDeclWithIssue(); + + return isSuppressed(Location, DeclWithIssue, {}) || + isSuppressed(UniqueingLocation, DeclWithIssue, {}); +} + +bool BugSuppression::isSuppressed(const PathDiagnosticLocation &Location, + const Decl *DeclWithIssue, + DiagnosticIdentifierList Hashtags) { + if (!Location.isValid() || DeclWithIssue == nullptr) + return false; + + // While some warnings are attached to AST nodes (mostly path-sensitive + // checks), others are simply associated with a plain source location + // or range. Figuring out the node based on locations can be tricky, + // so instead, we traverse the whole body of the declaration and gather + // information on ALL suppressions. After that we can simply check if + // any of those suppressions affect the warning in question. + // + // Traversing AST of a function is not a heavy operation, but for + // large functions with a lot of bugs it can make a dent in performance. + // In order to avoid this scenario, we cache traversal results. + auto InsertionResult = CachedSuppressionLocations.insert( + std::make_pair(DeclWithIssue, CachedRanges{})); + Ranges &SuppressionRanges = InsertionResult.first->second; + if (InsertionResult.second) { + // We haven't checked this declaration for suppressions yet! + CacheInitializer::initialize(DeclWithIssue, SuppressionRanges); + } + + SourceRange BugRange = Location.asRange(); + const SourceManager &SM = Location.getManager(); + + return llvm::any_of(SuppressionRanges, + [BugRange, &SM](SourceRange Suppression) { + return fullyContains(Suppression, BugRange, SM); + }); +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CallDescription.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CallDescription.cpp new file mode 100644 index 000000000000..94b2fde0a6f3 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CallDescription.cpp @@ -0,0 +1,169 @@ +//===- CallDescription.cpp - function/method call matching --*- 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 +// +//===----------------------------------------------------------------------===// +// +/// \file This file defines a generic mechanism for matching for function and +/// method calls of C, C++, and Objective-C languages. Instances of these +/// classes are frequently used together with the CallEvent classes. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" +#include "clang/AST/Decl.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/ADT/ArrayRef.h" +#include <iterator> +#include <optional> + +using namespace llvm; +using namespace clang; + +using MaybeCount = std::optional<unsigned>; + +// A constructor helper. +static MaybeCount readRequiredParams(MaybeCount RequiredArgs, + MaybeCount RequiredParams) { + if (RequiredParams) + return RequiredParams; + if (RequiredArgs) + return RequiredArgs; + return std::nullopt; +} + +ento::CallDescription::CallDescription(CallDescriptionFlags Flags, + ArrayRef<StringRef> QualifiedName, + MaybeCount RequiredArgs /*= None*/, + MaybeCount RequiredParams /*= None*/) + : RequiredArgs(RequiredArgs), + RequiredParams(readRequiredParams(RequiredArgs, RequiredParams)), + Flags(Flags) { + assert(!QualifiedName.empty()); + this->QualifiedName.reserve(QualifiedName.size()); + llvm::transform(QualifiedName, std::back_inserter(this->QualifiedName), + [](StringRef From) { return From.str(); }); +} + +/// Construct a CallDescription with default flags. +ento::CallDescription::CallDescription(ArrayRef<StringRef> QualifiedName, + MaybeCount RequiredArgs /*= None*/, + MaybeCount RequiredParams /*= None*/) + : CallDescription(CDF_None, QualifiedName, RequiredArgs, RequiredParams) {} + +bool ento::CallDescription::matches(const CallEvent &Call) const { + // FIXME: Add ObjC Message support. + if (Call.getKind() == CE_ObjCMessage) + return false; + + const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + if (!FD) + return false; + + return matchesImpl(FD, Call.getNumArgs(), Call.parameters().size()); +} + +bool ento::CallDescription::matchesAsWritten(const CallExpr &CE) const { + const auto *FD = dyn_cast_or_null<FunctionDecl>(CE.getCalleeDecl()); + if (!FD) + return false; + + return matchesImpl(FD, CE.getNumArgs(), FD->param_size()); +} + +bool ento::CallDescription::matchesImpl(const FunctionDecl *Callee, + size_t ArgCount, + size_t ParamCount) const { + const auto *FD = Callee; + if (!FD) + return false; + + if (Flags & CDF_MaybeBuiltin) { + return CheckerContext::isCLibraryFunction(FD, getFunctionName()) && + (!RequiredArgs || *RequiredArgs <= ArgCount) && + (!RequiredParams || *RequiredParams <= ParamCount); + } + + if (!II) { + II = &FD->getASTContext().Idents.get(getFunctionName()); + } + + const auto MatchNameOnly = [](const CallDescription &CD, + const NamedDecl *ND) -> bool { + DeclarationName Name = ND->getDeclName(); + if (const auto *II = Name.getAsIdentifierInfo()) + return II == *CD.II; // Fast case. + + // Fallback to the slow stringification and comparison for: + // C++ overloaded operators, constructors, destructors, etc. + // FIXME This comparison is way SLOWER than comparing pointers. + // At some point in the future, we should compare FunctionDecl pointers. + return Name.getAsString() == CD.getFunctionName(); + }; + + const auto ExactMatchArgAndParamCounts = + [](size_t ArgCount, size_t ParamCount, + const CallDescription &CD) -> bool { + const bool ArgsMatch = !CD.RequiredArgs || *CD.RequiredArgs == ArgCount; + const bool ParamsMatch = + !CD.RequiredParams || *CD.RequiredParams == ParamCount; + return ArgsMatch && ParamsMatch; + }; + + const auto MatchQualifiedNameParts = [](const CallDescription &CD, + const Decl *D) -> bool { + const auto FindNextNamespaceOrRecord = + [](const DeclContext *Ctx) -> const DeclContext * { + while (Ctx && !isa<NamespaceDecl, RecordDecl>(Ctx)) + Ctx = Ctx->getParent(); + return Ctx; + }; + + auto QualifierPartsIt = CD.begin_qualified_name_parts(); + const auto QualifierPartsEndIt = CD.end_qualified_name_parts(); + + // Match namespace and record names. Skip unrelated names if they don't + // match. + const DeclContext *Ctx = FindNextNamespaceOrRecord(D->getDeclContext()); + for (; Ctx && QualifierPartsIt != QualifierPartsEndIt; + Ctx = FindNextNamespaceOrRecord(Ctx->getParent())) { + // If not matched just continue and try matching for the next one. + if (cast<NamedDecl>(Ctx)->getName() != *QualifierPartsIt) + continue; + ++QualifierPartsIt; + } + + // We matched if we consumed all expected qualifier segments. + return QualifierPartsIt == QualifierPartsEndIt; + }; + + // Let's start matching... + if (!ExactMatchArgAndParamCounts(ArgCount, ParamCount, *this)) + return false; + + if (!MatchNameOnly(*this, FD)) + return false; + + if (!hasQualifiedNameParts()) + return true; + + return MatchQualifiedNameParts(*this, FD); +} + +ento::CallDescriptionSet::CallDescriptionSet( + std::initializer_list<CallDescription> &&List) { + Impl.LinearMap.reserve(List.size()); + for (const CallDescription &CD : List) + Impl.LinearMap.push_back({CD, /*unused*/ true}); +} + +bool ento::CallDescriptionSet::contains(const CallEvent &Call) const { + return static_cast<bool>(Impl.lookup(Call)); +} + +bool ento::CallDescriptionSet::containsAsWritten(const CallExpr &CE) const { + return static_cast<bool>(Impl.lookupAsWritten(CE)); +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 3785f498414f..bc14aea27f67 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -36,6 +36,7 @@ #include "clang/Basic/SourceManager.h" #include "clang/Basic/Specifiers.h" #include "clang/CrossTU/CrossTranslationUnit.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h" @@ -48,8 +49,6 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/ImmutableList.h" -#include "llvm/ADT/None.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" @@ -61,6 +60,7 @@ #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" #include <cassert> +#include <optional> #include <utility> #define DEBUG_TYPE "static-analyzer-call-event" @@ -73,26 +73,7 @@ QualType CallEvent::getResultType() const { const Expr *E = getOriginExpr(); if (!E) return Ctx.VoidTy; - assert(E); - - QualType ResultTy = E->getType(); - - // A function that returns a reference to 'int' will have a result type - // of simply 'int'. Check the origin expr's value kind to recover the - // proper type. - switch (E->getValueKind()) { - case VK_LValue: - ResultTy = Ctx.getLValueReferenceType(ResultTy); - break; - case VK_XValue: - ResultTy = Ctx.getRValueReferenceType(ResultTy); - break; - case VK_PRValue: - // No adjustment is necessary. - break; - } - - return ResultTy; + return Ctx.getReferenceQualifiedType(E); } static bool isCallback(QualType T) { @@ -306,6 +287,7 @@ ProgramStateRef CallEvent::invalidateRegions(unsigned BlockCount, ProgramPoint CallEvent::getProgramPoint(bool IsPreVisit, const ProgramPointTag *Tag) const { + if (const Expr *E = getOriginExpr()) { if (IsPreVisit) return PreStmt(E, getLocationContext(), Tag); @@ -314,69 +296,13 @@ ProgramPoint CallEvent::getProgramPoint(bool IsPreVisit, const Decl *D = getDecl(); assert(D && "Cannot get a program point without a statement or decl"); + assert(ElemRef.getParent() && + "Cannot get a program point without a CFGElementRef"); SourceLocation Loc = getSourceRange().getBegin(); if (IsPreVisit) - return PreImplicitCall(D, Loc, getLocationContext(), Tag); - return PostImplicitCall(D, Loc, getLocationContext(), Tag); -} - -bool CallEvent::isCalled(const CallDescription &CD) const { - // FIXME: Add ObjC Message support. - if (getKind() == CE_ObjCMessage) - return false; - - const IdentifierInfo *II = getCalleeIdentifier(); - if (!II) - return false; - const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(getDecl()); - if (!FD) - return false; - - if (CD.Flags & CDF_MaybeBuiltin) { - return CheckerContext::isCLibraryFunction(FD, CD.getFunctionName()) && - (!CD.RequiredArgs || CD.RequiredArgs <= getNumArgs()) && - (!CD.RequiredParams || CD.RequiredParams <= parameters().size()); - } - - if (!CD.IsLookupDone) { - CD.IsLookupDone = true; - CD.II = &getState()->getStateManager().getContext().Idents.get( - CD.getFunctionName()); - } - - if (II != CD.II) - return false; - - // If CallDescription provides prefix names, use them to improve matching - // accuracy. - if (CD.QualifiedName.size() > 1 && FD) { - const DeclContext *Ctx = FD->getDeclContext(); - // See if we'll be able to match them all. - size_t NumUnmatched = CD.QualifiedName.size() - 1; - for (; Ctx && isa<NamedDecl>(Ctx); Ctx = Ctx->getParent()) { - if (NumUnmatched == 0) - break; - - if (const auto *ND = dyn_cast<NamespaceDecl>(Ctx)) { - if (ND->getName() == CD.QualifiedName[NumUnmatched - 1]) - --NumUnmatched; - continue; - } - - if (const auto *RD = dyn_cast<RecordDecl>(Ctx)) { - if (RD->getName() == CD.QualifiedName[NumUnmatched - 1]) - --NumUnmatched; - continue; - } - } - - if (NumUnmatched > 0) - return false; - } - - return (!CD.RequiredArgs || CD.RequiredArgs == getNumArgs()) && - (!CD.RequiredParams || CD.RequiredParams == parameters().size()); + return PreImplicitCall(D, Loc, getLocationContext(), ElemRef, Tag); + return PostImplicitCall(D, Loc, getLocationContext(), ElemRef, Tag); } SVal CallEvent::getArgSVal(unsigned Index) const { @@ -406,7 +332,6 @@ void CallEvent::dump(raw_ostream &Out) const { ASTContext &Ctx = getState()->getStateManager().getContext(); if (const Expr *E = getOriginExpr()) { E->printPretty(Out, nullptr, Ctx.getPrintingPolicy()); - Out << "\n"; return; } @@ -420,9 +345,7 @@ void CallEvent::dump(raw_ostream &Out) const { } bool CallEvent::isCallStmt(const Stmt *S) { - return isa<CallExpr>(S) || isa<ObjCMessageExpr>(S) - || isa<CXXConstructExpr>(S) - || isa<CXXNewExpr>(S); + return isa<CallExpr, ObjCMessageExpr, CXXConstructExpr, CXXNewExpr>(S); } QualType CallEvent::getDeclaredResultType(const Decl *D) { @@ -503,6 +426,38 @@ static SVal processArgument(SVal Value, const Expr *ArgumentExpr, return Value; } +/// Cast the argument value to the type of the parameter at the function +/// declaration. +/// Returns the argument value if it didn't need a cast. +/// Or returns the cast argument if it needed a cast. +/// Or returns 'Unknown' if it would need a cast but the callsite and the +/// runtime definition don't match in terms of argument and parameter count. +static SVal castArgToParamTypeIfNeeded(const CallEvent &Call, unsigned ArgIdx, + SVal ArgVal, SValBuilder &SVB) { + const FunctionDecl *RTDecl = + Call.getRuntimeDefinition().getDecl()->getAsFunction(); + const auto *CallExprDecl = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + + if (!RTDecl || !CallExprDecl) + return ArgVal; + + // The function decl of the Call (in the AST) will not have any parameter + // declarations, if it was 'only' declared without a prototype. However, the + // engine will find the appropriate runtime definition - basically a + // redeclaration, which has a function body (and a function prototype). + if (CallExprDecl->hasPrototype() || !RTDecl->hasPrototype()) + return ArgVal; + + // Only do this cast if the number arguments at the callsite matches with + // the parameters at the runtime definition. + if (Call.getNumArgs() != RTDecl->getNumParams()) + return UnknownVal(); + + const Expr *ArgExpr = Call.getArgExpr(ArgIdx); + const ParmVarDecl *Param = RTDecl->getParamDecl(ArgIdx); + return SVB.evalCast(ArgVal, Param->getType(), ArgExpr->getType()); +} + static void addParameterValuesToBindings(const StackFrameContext *CalleeCtx, CallEvent::BindingsTy &Bindings, SValBuilder &SVB, @@ -528,12 +483,18 @@ static void addParameterValuesToBindings(const StackFrameContext *CalleeCtx, // which makes getArgSVal() fail and return UnknownVal. SVal ArgVal = Call.getArgSVal(Idx); const Expr *ArgExpr = Call.getArgExpr(Idx); - if (!ArgVal.isUnknown()) { - Loc ParamLoc = SVB.makeLoc( - MRMgr.getParamVarRegion(Call.getOriginExpr(), Idx, CalleeCtx)); - Bindings.push_back( - std::make_pair(ParamLoc, processArgument(ArgVal, ArgExpr, *I, SVB))); - } + + if (ArgVal.isUnknown()) + continue; + + // Cast the argument value to match the type of the parameter in some + // edge-cases. + ArgVal = castArgToParamTypeIfNeeded(Call, Idx, ArgVal, SVB); + + Loc ParamLoc = SVB.makeLoc( + MRMgr.getParamVarRegion(Call.getOriginExpr(), Idx, CalleeCtx)); + Bindings.push_back( + std::make_pair(ParamLoc, processArgument(ArgVal, ArgExpr, *I, SVB))); } // FIXME: Variadic arguments are not handled at all right now. @@ -556,24 +517,43 @@ const ConstructionContext *CallEvent::getConstructionContext() const { return nullptr; } -Optional<SVal> -CallEvent::getReturnValueUnderConstruction() const { +const CallEventRef<> CallEvent::getCaller() const { + const auto *CallLocationContext = this->getLocationContext(); + if (!CallLocationContext || CallLocationContext->inTopFrame()) + return nullptr; + + const auto *CallStackFrameContext = CallLocationContext->getStackFrame(); + if (!CallStackFrameContext) + return nullptr; + + CallEventManager &CEMgr = State->getStateManager().getCallEventManager(); + return CEMgr.getCaller(CallStackFrameContext, State); +} + +bool CallEvent::isCalledFromSystemHeader() const { + if (const CallEventRef<> Caller = getCaller()) + return Caller->isInSystemHeader(); + + return false; +} + +std::optional<SVal> CallEvent::getReturnValueUnderConstruction() const { const auto *CC = getConstructionContext(); if (!CC) - return None; + return std::nullopt; EvalCallOptions CallOpts; ExprEngine &Engine = getState()->getStateManager().getOwningEngine(); - SVal RetVal = - Engine.computeObjectUnderConstruction(getOriginExpr(), getState(), - getLocationContext(), CC, CallOpts); + SVal RetVal = Engine.computeObjectUnderConstruction( + getOriginExpr(), getState(), &Engine.getBuilderContext(), + getLocationContext(), CC, CallOpts); return RetVal; } ArrayRef<ParmVarDecl*> AnyFunctionCall::parameters() const { const FunctionDecl *D = getDecl(); if (!D) - return None; + return std::nullopt; return D->parameters(); } @@ -594,20 +574,28 @@ RuntimeDefinition AnyFunctionCall::getRuntimeDefinition() const { llvm::dbgs() << "Using autosynthesized body for " << FD->getName() << "\n"; }); - if (Body) { - const Decl* Decl = AD->getDecl(); - return RuntimeDefinition(Decl); - } ExprEngine &Engine = getState()->getStateManager().getOwningEngine(); + cross_tu::CrossTranslationUnitContext &CTUCtx = + *Engine.getCrossTranslationUnitContext(); + AnalyzerOptions &Opts = Engine.getAnalysisManager().options; + if (Body) { + const Decl* Decl = AD->getDecl(); + if (Opts.IsNaiveCTUEnabled && CTUCtx.isImportedAsNew(Decl)) { + // A newly created definition, but we had error(s) during the import. + if (CTUCtx.hasError(Decl)) + return {}; + return RuntimeDefinition(Decl, /*Foreign=*/true); + } + return RuntimeDefinition(Decl, /*Foreign=*/false); + } + // Try to get CTU definition only if CTUDir is provided. if (!Opts.IsNaiveCTUEnabled) return {}; - cross_tu::CrossTranslationUnitContext &CTUCtx = - *Engine.getCrossTranslationUnitContext(); llvm::Expected<const FunctionDecl *> CTUDeclOrError = CTUCtx.getCrossTUDefinition(FD, Opts.CTUDir, Opts.CTUIndexName, Opts.DisplayCTUProgress); @@ -620,7 +608,7 @@ RuntimeDefinition AnyFunctionCall::getRuntimeDefinition() const { return {}; } - return RuntimeDefinition(*CTUDeclOrError); + return RuntimeDefinition(*CTUDeclOrError, /*Foreign=*/true); } void AnyFunctionCall::getInitialStackFrameContents( @@ -671,17 +659,17 @@ bool AnyFunctionCall::argumentsMayEscape() const { // - CoreFoundation functions that end with "NoCopy" can free a passed-in // buffer even if it is const. - if (FName.endswith("NoCopy")) + if (FName.ends_with("NoCopy")) return true; // - NSXXInsertXX, for example NSMapInsertIfAbsent, since they can // be deallocated by NSMapRemove. - if (FName.startswith("NS") && (FName.find("Insert") != StringRef::npos)) + if (FName.starts_with("NS") && FName.contains("Insert")) return true; // - Many CF containers allow objects to escape through custom // allocators/deallocators upon container construction. (PR12101) - if (FName.startswith("CF") || FName.startswith("CG")) { + if (FName.starts_with("CF") || FName.starts_with("CG")) { return StrInStrNoCase(FName, "InsertValue") != StringRef::npos || StrInStrNoCase(FName, "AddValue") != StringRef::npos || StrInStrNoCase(FName, "SetValue") != StringRef::npos || @@ -747,11 +735,15 @@ void CXXInstanceCall::getExtraInvalidatedValues( SVal CXXInstanceCall::getCXXThisVal() const { const Expr *Base = getCXXThisExpr(); // FIXME: This doesn't handle an overloaded ->* operator. - if (!Base) - return UnknownVal(); + SVal ThisVal = Base ? getSVal(Base) : UnknownVal(); - SVal ThisVal = getSVal(Base); - assert(ThisVal.isUnknownOrUndef() || ThisVal.getAs<Loc>()); + if (isa<NonLoc>(ThisVal)) { + SValBuilder &SVB = getState()->getStateManager().getSValBuilder(); + QualType OriginalTy = ThisVal.getType(SVB.getContext()); + return SVB.evalCast(ThisVal, Base->getType(), OriginalTy); + } + + assert(ThisVal.isUnknownOrUndef() || isa<Loc>(ThisVal)); return ThisVal; } @@ -797,8 +789,9 @@ RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const { // the static type. However, because we currently don't update // DynamicTypeInfo when an object is cast, we can't actually be sure the // DynamicTypeInfo is up to date. This assert should be re-enabled once - // this is fixed. <rdar://problem/12287087> - //assert(!MD->getParent()->isDerivedFrom(RD) && "Bad DynamicTypeInfo"); + // this is fixed. + // + // assert(!MD->getParent()->isDerivedFrom(RD) && "Bad DynamicTypeInfo"); return {}; } @@ -841,9 +834,9 @@ void CXXInstanceCall::getInitialStackFrameContents( QualType Ty = Ctx.getPointerType(Ctx.getRecordType(Class)); // FIXME: CallEvent maybe shouldn't be directly accessing StoreManager. - bool Failed; - ThisVal = StateMgr.getStoreManager().attemptDownCast(ThisVal, Ty, Failed); - if (Failed) { + std::optional<SVal> V = + StateMgr.getStoreManager().evalBaseToDerived(ThisVal, Ty); + if (!V) { // We might have suffered some sort of placement new earlier, so // we're constructing in a completely unexpected storage. // Fall back to a generic pointer cast for this-value. @@ -851,7 +844,8 @@ void CXXInstanceCall::getInitialStackFrameContents( const CXXRecordDecl *StaticClass = StaticMD->getParent(); QualType StaticTy = Ctx.getPointerType(Ctx.getRecordType(StaticClass)); ThisVal = SVB.evalCast(ThisVal, Ty, StaticTy); - } + } else + ThisVal = *V; } if (!ThisVal.isUnknown()) @@ -889,7 +883,7 @@ const BlockDataRegion *BlockCall::getBlockRegion() const { ArrayRef<ParmVarDecl*> BlockCall::parameters() const { const BlockDecl *D = getDecl(); if (!D) - return None; + return std::nullopt; return D->parameters(); } @@ -978,7 +972,7 @@ RuntimeDefinition CXXDestructorCall::getRuntimeDefinition() const { ArrayRef<ParmVarDecl*> ObjCMethodCall::parameters() const { const ObjCMethodDecl *D = getDecl(); if (!D) - return None; + return std::nullopt; return D->parameters(); } @@ -1058,12 +1052,12 @@ const PseudoObjectExpr *ObjCMethodCall::getContainingPseudoObjectExpr() const { static const Expr * getSyntacticFromForPseudoObjectExpr(const PseudoObjectExpr *POE) { - const Expr *Syntactic = POE->getSyntacticForm(); + const Expr *Syntactic = POE->getSyntacticForm()->IgnoreParens(); // This handles the funny case of assigning to the result of a getter. // This can happen if the getter returns a non-const reference. if (const auto *BO = dyn_cast<BinaryOperator>(Syntactic)) - Syntactic = BO->getLHS(); + Syntactic = BO->getLHS()->IgnoreParens(); return Syntactic; } @@ -1194,7 +1188,7 @@ static const ObjCMethodDecl *findDefiningRedecl(const ObjCMethodDecl *MD) { // Find the redeclaration that defines the method. if (!MD->hasBody()) { - for (auto I : MD->redecls()) + for (auto *I : MD->redecls()) if (I->hasBody()) MD = cast<ObjCMethodDecl>(I); } @@ -1255,14 +1249,14 @@ lookupRuntimeDefinition(const ObjCInterfaceDecl *Interface, // stays around until clang quits, which also may be bad if we // need to release memory. using PrivateMethodCache = - llvm::DenseMap<PrivateMethodKey, Optional<const ObjCMethodDecl *>>; + llvm::DenseMap<PrivateMethodKey, std::optional<const ObjCMethodDecl *>>; static PrivateMethodCache PMC; - Optional<const ObjCMethodDecl *> &Val = + std::optional<const ObjCMethodDecl *> &Val = PMC[{Interface, LookupSelector, InstanceMethod}]; // Query lookupPrivateMethod() if the cache does not hit. - if (!Val.hasValue()) { + if (!Val) { Val = Interface->lookupPrivateMethod(LookupSelector, InstanceMethod); if (!*Val) { @@ -1271,7 +1265,7 @@ lookupRuntimeDefinition(const ObjCInterfaceDecl *Interface, } } - return Val.getValue(); + return *Val; } RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { @@ -1407,23 +1401,24 @@ void ObjCMethodCall::getInitialStackFrameContents( CallEventRef<> CallEventManager::getSimpleCall(const CallExpr *CE, ProgramStateRef State, - const LocationContext *LCtx) { + const LocationContext *LCtx, + CFGBlock::ConstCFGElementRef ElemRef) { if (const auto *MCE = dyn_cast<CXXMemberCallExpr>(CE)) - return create<CXXMemberCall>(MCE, State, LCtx); + return create<CXXMemberCall>(MCE, State, LCtx, ElemRef); if (const auto *OpCE = dyn_cast<CXXOperatorCallExpr>(CE)) { const FunctionDecl *DirectCallee = OpCE->getDirectCallee(); if (const auto *MD = dyn_cast<CXXMethodDecl>(DirectCallee)) - if (MD->isInstance()) - return create<CXXMemberOperatorCall>(OpCE, State, LCtx); + if (MD->isImplicitObjectMemberFunction()) + return create<CXXMemberOperatorCall>(OpCE, State, LCtx, ElemRef); } else if (CE->getCallee()->getType()->isBlockPointerType()) { - return create<BlockCall>(CE, State, LCtx); + return create<BlockCall>(CE, State, LCtx, ElemRef); } // Otherwise, it's a normal function call, static member function call, or // something we can't reason about. - return create<SimpleFunctionCall>(CE, State, LCtx); + return create<SimpleFunctionCall>(CE, State, LCtx, ElemRef); } CallEventRef<> @@ -1431,12 +1426,14 @@ CallEventManager::getCaller(const StackFrameContext *CalleeCtx, ProgramStateRef State) { const LocationContext *ParentCtx = CalleeCtx->getParent(); const LocationContext *CallerCtx = ParentCtx->getStackFrame(); + CFGBlock::ConstCFGElementRef ElemRef = {CalleeCtx->getCallSiteBlock(), + CalleeCtx->getIndex()}; assert(CallerCtx && "This should not be used for top-level stack frames"); const Stmt *CallSite = CalleeCtx->getCallSite(); if (CallSite) { - if (CallEventRef<> Out = getCall(CallSite, State, CallerCtx)) + if (CallEventRef<> Out = getCall(CallSite, State, CallerCtx, ElemRef)) return Out; SValBuilder &SVB = State->getStateManager().getSValBuilder(); @@ -1445,10 +1442,11 @@ CallEventManager::getCaller(const StackFrameContext *CalleeCtx, SVal ThisVal = State->getSVal(ThisPtr); if (const auto *CE = dyn_cast<CXXConstructExpr>(CallSite)) - return getCXXConstructorCall(CE, ThisVal.getAsRegion(), State, CallerCtx); + return getCXXConstructorCall(CE, ThisVal.getAsRegion(), State, CallerCtx, + ElemRef); else if (const auto *CIE = dyn_cast<CXXInheritedCtorInitExpr>(CallSite)) return getCXXInheritedConstructorCall(CIE, ThisVal.getAsRegion(), State, - CallerCtx); + CallerCtx, ElemRef); else { // All other cases are handled by getCall. llvm_unreachable("This is not an inlineable statement"); @@ -1468,26 +1466,30 @@ CallEventManager::getCaller(const StackFrameContext *CalleeCtx, SVal ThisVal = State->getSVal(ThisPtr); const Stmt *Trigger; - if (Optional<CFGAutomaticObjDtor> AutoDtor = E.getAs<CFGAutomaticObjDtor>()) + if (std::optional<CFGAutomaticObjDtor> AutoDtor = + E.getAs<CFGAutomaticObjDtor>()) Trigger = AutoDtor->getTriggerStmt(); - else if (Optional<CFGDeleteDtor> DeleteDtor = E.getAs<CFGDeleteDtor>()) + else if (std::optional<CFGDeleteDtor> DeleteDtor = E.getAs<CFGDeleteDtor>()) Trigger = DeleteDtor->getDeleteExpr(); else Trigger = Dtor->getBody(); return getCXXDestructorCall(Dtor, Trigger, ThisVal.getAsRegion(), - E.getAs<CFGBaseDtor>().hasValue(), State, - CallerCtx); + E.getAs<CFGBaseDtor>().has_value(), State, + CallerCtx, ElemRef); } CallEventRef<> CallEventManager::getCall(const Stmt *S, ProgramStateRef State, - const LocationContext *LC) { + const LocationContext *LC, + CFGBlock::ConstCFGElementRef ElemRef) { if (const auto *CE = dyn_cast<CallExpr>(S)) { - return getSimpleCall(CE, State, LC); + return getSimpleCall(CE, State, LC, ElemRef); } else if (const auto *NE = dyn_cast<CXXNewExpr>(S)) { - return getCXXAllocatorCall(NE, State, LC); + return getCXXAllocatorCall(NE, State, LC, ElemRef); + } else if (const auto *DE = dyn_cast<CXXDeleteExpr>(S)) { + return getCXXDeallocatorCall(DE, State, LC, ElemRef); } else if (const auto *ME = dyn_cast<ObjCMessageExpr>(S)) { - return getObjCMethodCall(ME, State, LC); + return getObjCMethodCall(ME, State, LC, ElemRef); } else { return nullptr; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerContext.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerContext.cpp index 3d64ce453479..d6d4cec9dd3d 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerContext.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerContext.cpp @@ -14,6 +14,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/Basic/Builtins.h" #include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringExtras.h" using namespace clang; using namespace ento; @@ -38,7 +39,7 @@ StringRef CheckerContext::getCalleeName(const FunctionDecl *FunDecl) const { } StringRef CheckerContext::getDeclDescription(const Decl *D) { - if (isa<ObjCMethodDecl>(D) || isa<CXXMethodDecl>(D)) + if (isa<ObjCMethodDecl, CXXMethodDecl>(D)) return "method"; if (isa<BlockDecl>(D)) return "anonymous block"; @@ -55,8 +56,29 @@ bool CheckerContext::isCLibraryFunction(const FunctionDecl *FD, if (Name.empty()) return true; StringRef BName = FD->getASTContext().BuiltinInfo.getName(BId); - if (BName.find(Name) != StringRef::npos) - return true; + size_t start = BName.find(Name); + if (start != StringRef::npos) { + // Accept exact match. + if (BName.size() == Name.size()) + return true; + + // v-- match starts here + // ...xxxxx... + // _xxxxx_ + // ^ ^ lookbehind and lookahead characters + + const auto MatchPredecessor = [=]() -> bool { + return start <= 0 || !llvm::isAlpha(BName[start - 1]); + }; + const auto MatchSuccessor = [=]() -> bool { + std::size_t LookbehindPlace = start + Name.size(); + return LookbehindPlace >= BName.size() || + !llvm::isAlpha(BName[LookbehindPlace]); + }; + + if (MatchPredecessor() && MatchSuccessor()) + return true; + } } const IdentifierInfo *II = FD->getIdentifier(); @@ -83,11 +105,11 @@ bool CheckerContext::isCLibraryFunction(const FunctionDecl *FD, if (FName.equals(Name)) return true; - if (FName.startswith("__inline") && (FName.find(Name) != StringRef::npos)) + if (FName.starts_with("__inline") && FName.contains(Name)) return true; - if (FName.startswith("__") && FName.endswith("_chk") && - FName.find(Name) != StringRef::npos) + if (FName.starts_with("__") && FName.ends_with("_chk") && + FName.contains(Name)) return true; return false; @@ -107,10 +129,10 @@ static bool evalComparison(SVal LHSVal, BinaryOperatorKind ComparisonOp, if (LHSVal.isUnknownOrUndef()) return false; ProgramStateManager &Mgr = State->getStateManager(); - if (!LHSVal.getAs<NonLoc>()) { + if (!isa<NonLoc>(LHSVal)) { LHSVal = Mgr.getStoreManager().getBinding(State->getStore(), LHSVal.castAs<Loc>()); - if (LHSVal.isUnknownOrUndef() || !LHSVal.getAs<NonLoc>()) + if (LHSVal.isUnknownOrUndef() || !isa<NonLoc>(LHSVal)) return false; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp index 626ae1ae8066..84ad20a54807 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp @@ -14,6 +14,7 @@ #include "clang/AST/Decl.h" #include "clang/AST/Expr.h" #include "clang/Lex/Preprocessor.h" +#include <optional> namespace clang { @@ -110,14 +111,13 @@ Nullability getNullabilityAnnotation(QualType Type) { return Nullability::Unspecified; } -llvm::Optional<int> tryExpandAsInteger(StringRef Macro, - const Preprocessor &PP) { +std::optional<int> tryExpandAsInteger(StringRef Macro, const Preprocessor &PP) { const auto *MacroII = PP.getIdentifierInfo(Macro); if (!MacroII) - return llvm::None; + return std::nullopt; const MacroInfo *MI = PP.getMacroInfo(MacroII); if (!MI) - return llvm::None; + return std::nullopt; // Filter out parens. std::vector<Token> FilteredTokens; @@ -131,12 +131,12 @@ llvm::Optional<int> tryExpandAsInteger(StringRef Macro, // 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; + return std::nullopt; StringRef ValueStr = StringRef(T.getLiteralData(), T.getLength()); llvm::APInt IntValue; constexpr unsigned AutoSenseRadix = 0; if (ValueStr.getAsInteger(AutoSenseRadix, IntValue)) - return llvm::None; + return std::nullopt; // Parse an optional minus sign. size_t Size = FilteredTokens.size(); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp index e09399a83589..6fc16223ea82 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -26,7 +26,9 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Casting.h" #include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/FormatVariadic.h" #include <cassert> +#include <optional> #include <vector> using namespace clang; @@ -34,10 +36,7 @@ using namespace ento; bool CheckerManager::hasPathSensitiveCheckers() const { const auto IfAnyAreNonEmpty = [](const auto &... Callbacks) -> bool { - bool Result = false; - // FIXME: Use fold expressions in C++17. - LLVM_ATTRIBUTE_UNUSED int Unused[]{0, (Result |= !Callbacks.empty())...}; - return Result; + return (!Callbacks.empty() || ...); }; return IfAnyAreNonEmpty( StmtCheckers, PreObjCMessageCheckers, ObjCMessageNilCheckers, @@ -655,7 +654,7 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, ExprEngine &Eng, const EvalCallOptions &CallOpts) { for (auto *const Pred : Src) { - bool anyEvaluated = false; + std::optional<CheckerNameRef> evaluatorChecker; ExplodedNodeSet checkDst; NodeBuilder B(Pred, checkDst, Eng.getBuilderContext()); @@ -674,10 +673,26 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, CheckerContext C(B, Eng, Pred, L); evaluated = EvalCallChecker(Call, C); } - assert(!(evaluated && anyEvaluated) - && "There are more than one checkers evaluating the call"); +#ifndef NDEBUG + if (evaluated && evaluatorChecker) { + const auto toString = [](const CallEvent &Call) -> std::string { + std::string Buf; + llvm::raw_string_ostream OS(Buf); + Call.dump(OS); + OS.flush(); + return Buf; + }; + std::string AssertionMessage = llvm::formatv( + "The '{0}' call has been already evaluated by the {1} checker, " + "while the {2} checker also tried to evaluate the same call. At " + "most one checker supposed to evaluate a call.", + toString(Call), evaluatorChecker->getName(), + EvalCallChecker.Checker->getCheckerName()); + llvm_unreachable(AssertionMessage.c_str()); + } +#endif if (evaluated) { - anyEvaluated = true; + evaluatorChecker = EvalCallChecker.Checker->getCheckerName(); Dst.insert(checkDst); #ifdef NDEBUG break; // on release don't check that no other checker also evals. @@ -686,7 +701,7 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, } // If none of the checkers evaluated the call, ask ExprEngine to handle it. - if (!anyEvaluated) { + if (!evaluatorChecker) { NodeBuilder B(Pred, Dst, Eng.getBuilderContext()); Eng.defaultEvalCall(B, Pred, Call, CallOpts); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerRegistryData.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerRegistryData.cpp index 1b3e8b11549d..b9c6278991f4 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerRegistryData.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CheckerRegistryData.cpp @@ -82,7 +82,7 @@ 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)) + if (!Checker.FullName.starts_with(PackageName)) return false; // Is the package actually just the name of a specific checker? @@ -158,7 +158,7 @@ void CheckerRegistryData::printCheckerWithDescList( continue; } - if (Checker.FullName.startswith("alpha")) { + if (Checker.FullName.starts_with("alpha")) { if (AnOpts.ShowCheckerHelpAlpha) Print(Out, Checker, ("(Enable only for development!) " + Checker.Desc).str()); @@ -228,7 +228,7 @@ void CheckerRegistryData::printCheckerOptionList(const AnalyzerOptions &AnOpts, } if (Option.DevelopmentStatus == "alpha" || - Entry.first.startswith("alpha")) { + Entry.first.starts_with("alpha")) { if (AnOpts.ShowCheckerOptionAlphaList) Print(Out, FullOption, llvm::Twine("(Enable only for development!) " + Desc).str()); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp index d12c35ef156a..66fab523c864 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp @@ -13,6 +13,7 @@ namespace clang { namespace ento { namespace categories { +const char *const AppleAPIMisuse = "API Misuse (Apple)"; const char *const CoreFoundationObjectiveC = "Core Foundation/Objective-C"; const char *const LogicError = "Logic error"; const char *const MemoryRefCount = @@ -23,6 +24,7 @@ const char *const CXXObjectLifecycle = "C++ object lifecycle"; const char *const CXXMoveSemantics = "C++ move semantics"; const char *const SecurityError = "Security error"; const char *const UnusedCode = "Unused code"; +const char *const TaintedData = "Tainted data used"; } // namespace categories } // namespace ento } // namespace clang diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ConstraintManager.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ConstraintManager.cpp index d642c3530268..c0b3f346b654 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ConstraintManager.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ConstraintManager.cpp @@ -16,6 +16,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "llvm/ADT/ScopeExit.h" using namespace clang; using namespace ento; @@ -41,3 +42,82 @@ ConditionTruthVal ConstraintManager::checkNull(ProgramStateRef State, return ConditionTruthVal(true); return {}; } + +template <typename AssumeFunction> +ConstraintManager::ProgramStatePair +ConstraintManager::assumeDualImpl(ProgramStateRef &State, + AssumeFunction &Assume) { + if (LLVM_UNLIKELY(State->isPosteriorlyOverconstrained())) + return {State, State}; + + // Assume functions might recurse (see `reAssume` or `tryRearrange`). During + // the recursion the State might not change anymore, that means we reached a + // fixpoint. + // We avoid infinite recursion of assume calls by checking already visited + // States on the stack of assume function calls. + const ProgramState *RawSt = State.get(); + if (LLVM_UNLIKELY(AssumeStack.contains(RawSt))) + return {State, State}; + AssumeStack.push(RawSt); + auto AssumeStackBuilder = + llvm::make_scope_exit([this]() { AssumeStack.pop(); }); + + ProgramStateRef StTrue = Assume(true); + + if (!StTrue) { + ProgramStateRef StFalse = Assume(false); + if (LLVM_UNLIKELY(!StFalse)) { // both infeasible + ProgramStateRef StInfeasible = State->cloneAsPosteriorlyOverconstrained(); + assert(StInfeasible->isPosteriorlyOverconstrained()); + // Checkers might rely on the API contract that both returned states + // cannot be null. Thus, we return StInfeasible for both branches because + // it might happen that a Checker uncoditionally uses one of them if the + // other is a nullptr. This may also happen with the non-dual and + // adjacent `assume(true)` and `assume(false)` calls. By implementing + // assume in therms of assumeDual, we can keep our API contract there as + // well. + return ProgramStatePair(StInfeasible, StInfeasible); + } + return ProgramStatePair(nullptr, StFalse); + } + + ProgramStateRef StFalse = Assume(false); + if (!StFalse) { + return ProgramStatePair(StTrue, nullptr); + } + + return ProgramStatePair(StTrue, StFalse); +} + +ConstraintManager::ProgramStatePair +ConstraintManager::assumeDual(ProgramStateRef State, DefinedSVal Cond) { + auto AssumeFun = [&, Cond](bool Assumption) { + return assumeInternal(State, Cond, Assumption); + }; + return assumeDualImpl(State, AssumeFun); +} + +ConstraintManager::ProgramStatePair +ConstraintManager::assumeInclusiveRangeDual(ProgramStateRef State, NonLoc Value, + const llvm::APSInt &From, + const llvm::APSInt &To) { + auto AssumeFun = [&](bool Assumption) { + return assumeInclusiveRangeInternal(State, Value, From, To, Assumption); + }; + return assumeDualImpl(State, AssumeFun); +} + +ProgramStateRef ConstraintManager::assume(ProgramStateRef State, + DefinedSVal Cond, bool Assumption) { + ConstraintManager::ProgramStatePair R = assumeDual(State, Cond); + return Assumption ? R.first : R.second; +} + +ProgramStateRef +ConstraintManager::assumeInclusiveRange(ProgramStateRef State, NonLoc Value, + const llvm::APSInt &From, + const llvm::APSInt &To, bool InBound) { + ConstraintManager::ProgramStatePair R = + assumeInclusiveRangeDual(State, Value, From, To); + return InBound ? R.first : R.second; +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp index bc939d252800..d3499e7a917d 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp @@ -26,7 +26,6 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/FunctionSummary.h" #include "clang/StaticAnalyzer/Core/PathSensitive/WorkList.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/Statistic.h" #include "llvm/Support/Casting.h" @@ -34,6 +33,7 @@ #include <algorithm> #include <cassert> #include <memory> +#include <optional> #include <utility> using namespace clang; @@ -43,6 +43,8 @@ using namespace ento; STATISTIC(NumSteps, "The # of steps executed."); +STATISTIC(NumSTUSteps, "The # of STU steps executed."); +STATISTIC(NumCTUSteps, "The # of CTU steps executed."); STATISTIC(NumReachedMaxSteps, "The # of times we reached the max number of steps."); STATISTIC(NumPathsExplored, @@ -73,11 +75,18 @@ static std::unique_ptr<WorkList> generateWorkList(AnalyzerOptions &Opts) { CoreEngine::CoreEngine(ExprEngine &exprengine, FunctionSummariesTy *FS, AnalyzerOptions &Opts) : ExprEng(exprengine), WList(generateWorkList(Opts)), + CTUWList(Opts.IsNaiveCTUEnabled ? generateWorkList(Opts) : nullptr), BCounterFactory(G.getAllocator()), FunctionSummaries(FS) {} +void CoreEngine::setBlockCounter(BlockCounter C) { + WList->setBlockCounter(C); + if (CTUWList) + CTUWList->setBlockCounter(C); +} + /// ExecuteWorkList - Run the worklist algorithm for a maximum number of steps. -bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned Steps, - ProgramStateRef InitState) { +bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned MaxSteps, + ProgramStateRef InitState) { if (G.num_roots() == 0) { // Initialize the analysis by constructing // the root if none exists. @@ -100,7 +109,7 @@ bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned Steps, BlockEdge StartLoc(Entry, Succ, L); // Set the current block counter to being empty. - WList->setBlockCounter(BCounterFactory.GetEmptyCounter()); + setBlockCounter(BCounterFactory.GetEmptyCounter()); if (!InitState) InitState = ExprEng.getInitialState(L); @@ -118,34 +127,54 @@ bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned Steps, } // Check if we have a steps limit - bool UnlimitedSteps = Steps == 0; + bool UnlimitedSteps = MaxSteps == 0; + // Cap our pre-reservation in the event that the user specifies // a very large number of maximum steps. const unsigned PreReservationCap = 4000000; if(!UnlimitedSteps) - G.reserve(std::min(Steps,PreReservationCap)); - - while (WList->hasWork()) { - if (!UnlimitedSteps) { - if (Steps == 0) { - NumReachedMaxSteps++; - break; + G.reserve(std::min(MaxSteps, PreReservationCap)); + + auto ProcessWList = [this, UnlimitedSteps](unsigned MaxSteps) { + unsigned Steps = MaxSteps; + while (WList->hasWork()) { + if (!UnlimitedSteps) { + if (Steps == 0) { + NumReachedMaxSteps++; + break; + } + --Steps; } - --Steps; - } - NumSteps++; + NumSteps++; - const WorkListUnit& WU = WList->dequeue(); + const WorkListUnit &WU = WList->dequeue(); - // Set the current block counter. - WList->setBlockCounter(WU.getBlockCounter()); + // Set the current block counter. + setBlockCounter(WU.getBlockCounter()); - // Retrieve the node. - ExplodedNode *Node = WU.getNode(); + // Retrieve the node. + ExplodedNode *Node = WU.getNode(); - dispatchWorkItem(Node, Node->getLocation(), WU); + dispatchWorkItem(Node, Node->getLocation(), WU); + } + return MaxSteps - Steps; + }; + const unsigned STUSteps = ProcessWList(MaxSteps); + + if (CTUWList) { + NumSTUSteps += STUSteps; + const unsigned MinCTUSteps = + this->ExprEng.getAnalysisManager().options.CTUMaxNodesMin; + const unsigned Pct = + this->ExprEng.getAnalysisManager().options.CTUMaxNodesPercentage; + unsigned MaxCTUSteps = std::max(STUSteps * Pct / 100, MinCTUSteps); + + WList = std::move(CTUWList); + const unsigned CTUSteps = ProcessWList(MaxCTUSteps); + NumCTUSteps += CTUSteps; } + ExprEng.processEndWorklist(); return WList->hasWork(); } @@ -244,10 +273,10 @@ void CoreEngine::HandleBlockEdge(const BlockEdge &L, ExplodedNode *Pred) { const ReturnStmt *RS = nullptr; if (!L.getSrc()->empty()) { CFGElement LastElement = L.getSrc()->back(); - if (Optional<CFGStmt> LastStmt = LastElement.getAs<CFGStmt>()) { + if (std::optional<CFGStmt> LastStmt = LastElement.getAs<CFGStmt>()) { RS = dyn_cast<ReturnStmt>(LastStmt->getStmt()); - } else if (Optional<CFGAutomaticObjDtor> AutoDtor = - LastElement.getAs<CFGAutomaticObjDtor>()) { + } else if (std::optional<CFGAutomaticObjDtor> AutoDtor = + LastElement.getAs<CFGAutomaticObjDtor>()) { RS = dyn_cast<ReturnStmt>(AutoDtor->getTriggerStmt()); } } @@ -282,14 +311,13 @@ void CoreEngine::HandleBlockEntrance(const BlockEntrance &L, BlockCounter Counter = WList->getBlockCounter(); Counter = BCounterFactory.IncrementCount(Counter, LC->getStackFrame(), BlockId); - WList->setBlockCounter(Counter); + setBlockCounter(Counter); // Process the entrance of the block. - if (Optional<CFGElement> E = L.getFirstElement()) { + if (std::optional<CFGElement> E = L.getFirstElement()) { NodeBuilderContext Ctx(*this, L.getBlock(), Pred); ExprEng.processCFGElement(*E, Pred, 0, &Ctx); - } - else + } else HandleBlockExit(L.getBlock(), Pred); } @@ -475,8 +503,8 @@ void CoreEngine::HandleVirtualBaseBranch(const CFGBlock *B, if (const auto *CallerCtor = dyn_cast_or_null<CXXConstructExpr>( LCtx->getStackFrame()->getCallSite())) { switch (CallerCtor->getConstructionKind()) { - case CXXConstructExpr::CK_NonVirtualBase: - case CXXConstructExpr::CK_VirtualBase: { + case CXXConstructionKind::NonVirtualBase: + case CXXConstructionKind::VirtualBase: { BlockEdge Loc(B, *B->succ_begin(), LCtx); HandleBlockEdge(Loc, Pred); return; @@ -587,7 +615,7 @@ void CoreEngine::enqueue(ExplodedNodeSet &Set, } void CoreEngine::enqueueEndOfFunction(ExplodedNodeSet &Set, const ReturnStmt *RS) { - for (auto I : Set) { + for (auto *I : Set) { // If we are in an inlined call, generate CallExitBegin node. if (I->getLocationContext()->getParent()) { I = generateCallExitBeginNode(I, RS); @@ -686,8 +714,8 @@ SwitchNodeBuilder::generateDefaultCaseNode(ProgramStateRef St, assert(Src->succ_rbegin() != Src->succ_rend()); CFGBlock *DefaultBlock = *Src->succ_rbegin(); - // Sanity check for default blocks that are unreachable and not caught - // by earlier stages. + // Basic correctness check for default blocks that are unreachable and not + // caught by earlier stages. if (!DefaultBlock) return nullptr; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/DynamicExtent.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/DynamicExtent.cpp index db9698b4086e..6cf06413b537 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/DynamicExtent.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/DynamicExtent.cpp @@ -30,7 +30,9 @@ DefinedOrUnknownSVal getDynamicExtent(ProgramStateRef State, MR = MR->StripCasts(); if (const DefinedOrUnknownSVal *Size = State->get<DynamicExtentMap>(MR)) - return *Size; + if (auto SSize = + SVB.convertToArrayIndex(*Size).getAs<DefinedOrUnknownSVal>()) + return *SSize; return MR->getMemRegionManager().getStaticSize(MR, SVB); } @@ -40,23 +42,49 @@ DefinedOrUnknownSVal getElementExtent(QualType Ty, SValBuilder &SVB) { SVB.getArrayIndexType()); } +static DefinedOrUnknownSVal getConstantArrayElementCount(SValBuilder &SVB, + const MemRegion *MR) { + MR = MR->StripCasts(); + + const auto *TVR = MR->getAs<TypedValueRegion>(); + if (!TVR) + return UnknownVal(); + + if (const ConstantArrayType *CAT = + SVB.getContext().getAsConstantArrayType(TVR->getValueType())) + return SVB.makeIntVal(CAT->getSize(), /* isUnsigned = */ false); + + return UnknownVal(); +} + +static DefinedOrUnknownSVal +getDynamicElementCount(ProgramStateRef State, SVal Size, + DefinedOrUnknownSVal ElementSize) { + SValBuilder &SVB = State->getStateManager().getSValBuilder(); + + auto ElementCount = + SVB.evalBinOp(State, BO_Div, Size, ElementSize, SVB.getArrayIndexType()) + .getAs<DefinedOrUnknownSVal>(); + return ElementCount.value_or(UnknownVal()); +} + DefinedOrUnknownSVal getDynamicElementCount(ProgramStateRef State, const MemRegion *MR, SValBuilder &SVB, QualType ElementTy) { + assert(MR != nullptr && "Not-null region expected"); MR = MR->StripCasts(); - DefinedOrUnknownSVal Size = getDynamicExtent(State, MR, SVB); - SVal ElementSize = getElementExtent(ElementTy, SVB); + DefinedOrUnknownSVal ElementSize = getElementExtent(ElementTy, SVB); + if (ElementSize.isZeroConstant()) + return getConstantArrayElementCount(SVB, MR); - SVal ElementCount = - SVB.evalBinOp(State, BO_Div, Size, ElementSize, SVB.getArrayIndexType()); - - return ElementCount.castAs<DefinedOrUnknownSVal>(); + return getDynamicElementCount(State, getDynamicExtent(State, MR, SVB), + ElementSize); } SVal getDynamicExtentWithOffset(ProgramStateRef State, SVal BufV) { - SValBuilder &SvalBuilder = State->getStateManager().getSValBuilder(); + SValBuilder &SVB = State->getStateManager().getSValBuilder(); const MemRegion *MRegion = BufV.getAsRegion(); if (!MRegion) return UnknownVal(); @@ -67,15 +95,28 @@ SVal getDynamicExtentWithOffset(ProgramStateRef State, SVal BufV) { if (!BaseRegion) return UnknownVal(); - NonLoc OffsetInBytes = SvalBuilder.makeArrayIndex( - Offset.getOffset() / - MRegion->getMemRegionManager().getContext().getCharWidth()); - DefinedOrUnknownSVal ExtentInBytes = - getDynamicExtent(State, BaseRegion, SvalBuilder); + NonLoc OffsetInChars = + SVB.makeArrayIndex(Offset.getOffset() / SVB.getContext().getCharWidth()); + DefinedOrUnknownSVal ExtentInBytes = getDynamicExtent(State, BaseRegion, SVB); + + return SVB.evalBinOp(State, BinaryOperator::Opcode::BO_Sub, ExtentInBytes, + OffsetInChars, SVB.getArrayIndexType()); +} + +DefinedOrUnknownSVal getDynamicElementCountWithOffset(ProgramStateRef State, + SVal BufV, + QualType ElementTy) { + const MemRegion *MR = BufV.getAsRegion(); + if (!MR) + return UnknownVal(); + + SValBuilder &SVB = State->getStateManager().getSValBuilder(); + DefinedOrUnknownSVal ElementSize = getElementExtent(ElementTy, SVB); + if (ElementSize.isZeroConstant()) + return getConstantArrayElementCount(SVB, MR); - return SvalBuilder.evalBinOp(State, BinaryOperator::Opcode::BO_Sub, - ExtentInBytes, OffsetInBytes, - SvalBuilder.getArrayIndexType()); + return getDynamicElementCount(State, getDynamicExtentWithOffset(State, BufV), + ElementSize); } ProgramStateRef setDynamicExtent(ProgramStateRef State, const MemRegion *MR, diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/DynamicType.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/DynamicType.cpp index 9ed915aafcab..06052cb99fd1 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/DynamicType.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/DynamicType.cpp @@ -209,7 +209,7 @@ static raw_ostream &printJson(const DynamicTypeInfo &DTI, raw_ostream &Out, if (ToPrint->isAnyPointerType()) ToPrint = ToPrint->getPointeeType(); - Out << '\"' << ToPrint.getAsString() << "\", \"sub_classable\": " + Out << '\"' << ToPrint << "\", \"sub_classable\": " << (DTI.canBeASubClass() ? "true" : "false"); } return Out; @@ -217,9 +217,9 @@ static raw_ostream &printJson(const DynamicTypeInfo &DTI, raw_ostream &Out, 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") << "\""; + return Out << "\"from\": \"" << DCI.from() << "\", \"to\": \"" << DCI.to() + << "\", \"kind\": \"" << (DCI.succeeds() ? "success" : "fail") + << "\""; } template <class T, class U> diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Environment.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Environment.cpp index ee7474592528..427f51109853 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Environment.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Environment.cpp @@ -17,9 +17,9 @@ #include "clang/AST/Stmt.h" #include "clang/AST/StmtObjC.h" #include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/Basic/JsonSupport.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/LangOptions.h" -#include "clang/Basic/JsonSupport.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" @@ -40,8 +40,11 @@ static const Expr *ignoreTransparentExprs(const Expr *E) { switch (E->getStmtClass()) { case Stmt::OpaqueValueExprClass: - E = cast<OpaqueValueExpr>(E)->getSourceExpr(); - break; + if (const Expr *SE = cast<OpaqueValueExpr>(E)->getSourceExpr()) { + E = SE; + break; + } + return E; case Stmt::ExprWithCleanupsClass: E = cast<ExprWithCleanups>(E)->getSubExpr(); break; @@ -88,7 +91,7 @@ SVal Environment::getSVal(const EnvironmentEntry &Entry, const Stmt *S = Entry.getStmt(); assert(!isa<ObjCForCollectionStmt>(S) && "Use ExprEngine::hasMoreIteration()!"); - assert((isa<Expr>(S) || isa<ReturnStmt>(S)) && + assert((isa<Expr, ReturnStmt>(S)) && "Environment can only argue about Exprs, since only they express " "a value! Any non-expression statement stored in Environment is a " "result of a hack!"); @@ -98,7 +101,6 @@ SVal Environment::getSVal(const EnvironmentEntry &Entry, case Stmt::CXXBindTemporaryExprClass: case Stmt::ExprWithCleanupsClass: case Stmt::GenericSelectionExprClass: - case Stmt::OpaqueValueExprClass: case Stmt::ConstantExprClass: case Stmt::ParenExprClass: case Stmt::SubstNonTypeTemplateParmExprClass: @@ -118,7 +120,7 @@ SVal Environment::getSVal(const EnvironmentEntry &Entry, case Stmt::SizeOfPackExprClass: case Stmt::PredefinedExprClass: // Known constants; defer to SValBuilder. - return svalBuilder.getConstantVal(cast<Expr>(S)).getValue(); + return *svalBuilder.getConstantVal(cast<Expr>(S)); case Stmt::ReturnStmtClass: { const auto *RS = cast<ReturnStmt>(S); @@ -193,7 +195,7 @@ EnvironmentManager::removeDeadBindings(Environment Env, // Iterate over the block-expr bindings. for (Environment::iterator I = Env.begin(), End = Env.end(); I != End; ++I) { const EnvironmentEntry &BlkExpr = I.getKey(); - const SVal &X = I.getData(); + SVal X = I.getData(); const Expr *E = dyn_cast<Expr>(BlkExpr.getStmt()); if (!E) @@ -274,7 +276,8 @@ void Environment::printJson(raw_ostream &Out, const ASTContext &Ctx, const Stmt *S = I->first.getStmt(); Indent(Out, InnerSpace, IsDot) - << "{ \"stmt_id\": " << S->getID(Ctx) << ", \"pretty\": "; + << "{ \"stmt_id\": " << S->getID(Ctx) << ", \"kind\": \"" + << S->getStmtClassName() << "\", \"pretty\": "; S->printJson(Out, nullptr, PP, /*AddQuotes=*/true); Out << ", \"value\": "; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp index 635495e9bf60..f84da769d182 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp @@ -25,12 +25,12 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/FoldingSet.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/PointerUnion.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Casting.h" #include <cassert> #include <memory> +#include <optional> using namespace clang; using namespace ento; @@ -50,8 +50,7 @@ ExplodedGraph::~ExplodedGraph() = default; bool ExplodedGraph::isInterestingLValueExpr(const Expr *Ex) { if (!Ex->isLValue()) return false; - return isa<DeclRefExpr>(Ex) || isa<MemberExpr>(Ex) || - isa<ObjCIvarRefExpr>(Ex) || isa<ArraySubscriptExpr>(Ex); + return isa<DeclRefExpr, MemberExpr, ObjCIvarRefExpr, ArraySubscriptExpr>(Ex); } bool ExplodedGraph::shouldCollect(const ExplodedNode *node) { @@ -140,7 +139,7 @@ bool ExplodedGraph::shouldCollect(const ExplodedNode *node) { // Condition 10. const ProgramPoint SuccLoc = succ->getLocation(); - if (Optional<StmtPoint> SP = SuccLoc.getAs<StmtPoint>()) + if (std::optional<StmtPoint> SP = SuccLoc.getAs<StmtPoint>()) if (CallEvent::isCallStmt(SP->getStmt())) return false; @@ -234,8 +233,7 @@ void ExplodedNode::NodeGroup::addNode(ExplodedNode *N, ExplodedGraph &G) { ExplodedNode *Old = Storage.get<ExplodedNode *>(); BumpVectorContext &Ctx = G.getNodeAllocator(); - V = G.getAllocator().Allocate<ExplodedNodeVector>(); - new (V) ExplodedNodeVector(Ctx, 4); + V = new (G.getAllocator()) ExplodedNodeVector(Ctx, 4); V->push_back(Old, Ctx); Storage = V; @@ -409,7 +407,7 @@ ExplodedNode *ExplodedGraph::getNode(const ProgramPoint &L, } else { // Allocate a new node. - V = (NodeTy*) getAllocator().Allocate<NodeTy>(); + V = getAllocator().Allocate<NodeTy>(); } ++NumNodes; @@ -433,7 +431,7 @@ ExplodedNode *ExplodedGraph::createUncachedNode(const ProgramPoint &L, ProgramStateRef State, int64_t Id, bool IsSink) { - NodeTy *V = (NodeTy *) getAllocator().Allocate<NodeTy>(); + NodeTy *V = getAllocator().Allocate<NodeTy>(); new (V) NodeTy(L, State, Id, IsSink); return V; } @@ -489,7 +487,7 @@ ExplodedGraph::trim(ArrayRef<const NodeTy *> Sinks, const ExplodedNode *N = WL2.pop_back_val(); // Skip this node if we have already processed it. - if (Pass2.find(N) != Pass2.end()) + if (Pass2.contains(N)) continue; // Create the corresponding node in the new graph and record the mapping @@ -510,9 +508,8 @@ ExplodedGraph::trim(ArrayRef<const NodeTy *> Sinks, // Walk through the predecessors of 'N' and hook up their corresponding // nodes in the new graph (if any) to the freshly created node. - for (ExplodedNode::pred_iterator I = N->Preds.begin(), E = N->Preds.end(); - I != E; ++I) { - Pass2Ty::iterator PI = Pass2.find(*I); + for (const ExplodedNode *Pred : N->Preds) { + Pass2Ty::iterator PI = Pass2.find(Pred); if (PI == Pass2.end()) continue; @@ -523,17 +520,16 @@ ExplodedGraph::trim(ArrayRef<const NodeTy *> Sinks, // been created, we should hook them up as successors. Otherwise, enqueue // the new nodes from the original graph that should have nodes created // in the new graph. - for (ExplodedNode::succ_iterator I = N->Succs.begin(), E = N->Succs.end(); - I != E; ++I) { - Pass2Ty::iterator PI = Pass2.find(*I); + for (const ExplodedNode *Succ : N->Succs) { + Pass2Ty::iterator PI = Pass2.find(Succ); if (PI != Pass2.end()) { const_cast<ExplodedNode *>(PI->second)->addPredecessor(NewN, *G); continue; } // Enqueue nodes to the worklist that were marked during pass 1. - if (Pass1.count(*I)) - WL2.push_back(*I); + if (Pass1.count(Succ)) + WL2.push_back(Succ); } } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 66332d3b848c..24e91a22fd68 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -7,7 +7,7 @@ //===----------------------------------------------------------------------===// // // This file defines a meta-engine for path-sensitive dataflow analysis that -// is built on GREngine, but provides the boilerplate to execute transfer +// is built on CoreEngine, but provides the boilerplate to execute transfer // functions and build the ExplodedGraph at the expression level. // //===----------------------------------------------------------------------===// @@ -48,6 +48,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ConstraintManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" #include "clang/StaticAnalyzer/Core/PathSensitive/LoopUnrolling.h" #include "clang/StaticAnalyzer/Core/PathSensitive/LoopWidening.h" @@ -64,7 +65,7 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/ImmutableMap.h" #include "llvm/ADT/ImmutableSet.h" -#include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Statistic.h" #include "llvm/Support/Casting.h" @@ -77,6 +78,7 @@ #include <cassert> #include <cstdint> #include <memory> +#include <optional> #include <string> #include <tuple> #include <utility> @@ -118,18 +120,10 @@ namespace { /// the construction context was present and contained references to these /// AST nodes. class ConstructedObjectKey { - typedef std::pair<ConstructionContextItem, const LocationContext *> - ConstructedObjectKeyImpl; - + using ConstructedObjectKeyImpl = + std::pair<ConstructionContextItem, const LocationContext *>; const ConstructedObjectKeyImpl Impl; - const void *getAnyASTNodePtr() const { - if (const Stmt *S = getItem().getStmtOrNull()) - return S; - else - return getItem().getCXXCtorInitializer(); - } - public: explicit ConstructedObjectKey(const ConstructionContextItem &Item, const LocationContext *LC) @@ -193,6 +187,31 @@ typedef llvm::ImmutableMap<ConstructedObjectKey, SVal> REGISTER_TRAIT_WITH_PROGRAMSTATE(ObjectsUnderConstruction, ObjectsUnderConstructionMap) +// This trait is responsible for storing the index of the element that is to be +// constructed in the next iteration. As a result a CXXConstructExpr is only +// stored if it is array type. Also the index is the index of the continuous +// memory region, which is important for multi-dimensional arrays. E.g:: int +// arr[2][2]; assume arr[1][1] will be the next element under construction, so +// the index is 3. +typedef llvm::ImmutableMap< + std::pair<const CXXConstructExpr *, const LocationContext *>, unsigned> + IndexOfElementToConstructMap; +REGISTER_TRAIT_WITH_PROGRAMSTATE(IndexOfElementToConstruct, + IndexOfElementToConstructMap) + +// This trait is responsible for holding our pending ArrayInitLoopExprs. +// It pairs the LocationContext and the initializer CXXConstructExpr with +// the size of the array that's being copy initialized. +typedef llvm::ImmutableMap< + std::pair<const CXXConstructExpr *, const LocationContext *>, unsigned> + PendingInitLoopMap; +REGISTER_TRAIT_WITH_PROGRAMSTATE(PendingInitLoop, PendingInitLoopMap) + +typedef llvm::ImmutableMap<const LocationContext *, unsigned> + PendingArrayDestructionMap; +REGISTER_TRAIT_WITH_PROGRAMSTATE(PendingArrayDestruction, + PendingArrayDestructionMap) + //===----------------------------------------------------------------------===// // Engine construction and deletion. //===----------------------------------------------------------------------===// @@ -200,24 +219,17 @@ REGISTER_TRAIT_WITH_PROGRAMSTATE(ObjectsUnderConstruction, static const char* TagProviderName = "ExprEngine"; ExprEngine::ExprEngine(cross_tu::CrossTranslationUnitContext &CTU, - AnalysisManager &mgr, - SetOfConstDecls *VisitedCalleesIn, - FunctionSummariesTy *FS, - InliningModes HowToInlineIn) - : CTU(CTU), AMgr(mgr), - AnalysisDeclContexts(mgr.getAnalysisDeclContextManager()), + AnalysisManager &mgr, SetOfConstDecls *VisitedCalleesIn, + FunctionSummariesTy *FS, InliningModes HowToInlineIn) + : CTU(CTU), IsCTUEnabled(mgr.getAnalyzerOptions().IsNaiveCTUEnabled), + AMgr(mgr), AnalysisDeclContexts(mgr.getAnalysisDeclContextManager()), Engine(*this, FS, mgr.getAnalyzerOptions()), G(Engine.getGraph()), StateMgr(getContext(), mgr.getStoreManagerCreator(), - mgr.getConstraintManagerCreator(), G.getAllocator(), - this), - SymMgr(StateMgr.getSymbolManager()), - MRMgr(StateMgr.getRegionManager()), - svalBuilder(StateMgr.getSValBuilder()), - ObjCNoRet(mgr.getASTContext()), - BR(mgr, *this), - VisitedCallees(VisitedCalleesIn), - HowToInline(HowToInlineIn) - { + mgr.getConstraintManagerCreator(), G.getAllocator(), this), + SymMgr(StateMgr.getSymbolManager()), MRMgr(StateMgr.getRegionManager()), + svalBuilder(StateMgr.getSValBuilder()), ObjCNoRet(mgr.getASTContext()), + BR(mgr, *this), VisitedCallees(VisitedCalleesIn), + HowToInline(HowToInlineIn) { unsigned TrimInterval = mgr.options.GraphTrimInterval; if (TrimInterval != 0) { // Enable eager node reclamation when constructing the ExplodedGraph. @@ -259,7 +271,7 @@ ProgramStateRef ExprEngine::getInitialState(const LocationContext *InitLoc) { svalBuilder.makeZeroVal(T), svalBuilder.getConditionType()); - Optional<DefinedOrUnknownSVal> Constraint = + std::optional<DefinedOrUnknownSVal> Constraint = Constraint_untested.getAs<DefinedOrUnknownSVal>(); if (!Constraint) @@ -279,7 +291,7 @@ ProgramStateRef ExprEngine::getInitialState(const LocationContext *InitLoc) { const MemRegion *R = state->getRegion(SelfD, InitLoc); SVal V = state->getSVal(loc::MemRegionVal(R)); - if (Optional<Loc> LV = V.getAs<Loc>()) { + if (std::optional<Loc> LV = V.getAs<Loc>()) { // Assume that the pointer value in 'self' is non-null. state = state->assume(*LV, true); assert(state && "'self' cannot be null"); @@ -287,7 +299,7 @@ ProgramStateRef ExprEngine::getInitialState(const LocationContext *InitLoc) { } if (const auto *MD = dyn_cast<CXXMethodDecl>(D)) { - if (!MD->isStatic()) { + if (MD->isImplicitObjectMemberFunction()) { // Precondition: 'this' is always non-null upon entry to the // top-level function. This is our starting assumption for // analyzing an "open" program. @@ -295,7 +307,7 @@ ProgramStateRef ExprEngine::getInitialState(const LocationContext *InitLoc) { if (SFC->getParent() == nullptr) { loc::MemRegionVal L = svalBuilder.getCXXThis(MD, SFC); SVal V = state->getSVal(L); - if (Optional<Loc> LV = V.getAs<Loc>()) { + if (std::optional<Loc> LV = V.getAs<Loc>()) { state = state->assume(*LV, true); assert(state && "'this' cannot be null"); } @@ -319,16 +331,16 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded( if (!Result) { // If we don't have an explicit result expression, we're in "if needed" // mode. Only create a region if the current value is a NonLoc. - if (!InitValWithAdjustments.getAs<NonLoc>()) { + if (!isa<NonLoc>(InitValWithAdjustments)) { if (OutRegionWithAdjustments) *OutRegionWithAdjustments = nullptr; return State; } Result = InitWithAdjustments; } else { - // We need to create a region no matter what. For sanity, make sure we don't - // try to stuff a Loc into a non-pointer temporary region. - assert(!InitValWithAdjustments.getAs<Loc>() || + // We need to create a region no matter what. Make sure we don't try to + // stuff a Loc into a non-pointer temporary region. + assert(!isa<Loc>(InitValWithAdjustments) || Loc::isLocType(Result->getType()) || Result->getType()->isMemberPointerType()); } @@ -371,19 +383,23 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded( // into that region. This is not correct, but it is better than nothing. const TypedValueRegion *TR = nullptr; if (const auto *MT = dyn_cast<MaterializeTemporaryExpr>(Result)) { - if (Optional<SVal> V = getObjectUnderConstruction(State, MT, LC)) { + if (std::optional<SVal> V = getObjectUnderConstruction(State, MT, LC)) { State = finishObjectConstruction(State, MT, LC); State = State->BindExpr(Result, LC, *V); return State; - } else { + } else if (const ValueDecl *VD = MT->getExtendingDecl()) { StorageDuration SD = MT->getStorageDuration(); + assert(SD != SD_FullExpression); // If this object is bound to a reference with static storage duration, we // put it in a different region to prevent "address leakage" warnings. if (SD == SD_Static || SD == SD_Thread) { - TR = MRMgr.getCXXStaticTempObjectRegion(Init); + TR = MRMgr.getCXXStaticLifetimeExtendedObjectRegion(Init, VD); } else { - TR = MRMgr.getCXXTempObjectRegion(Init, LC); + TR = MRMgr.getCXXLifetimeExtendedObjectRegion(Init, VD, LC); } + } else { + assert(MT->getStorageDuration() == SD_FullExpression); + TR = MRMgr.getCXXTempObjectRegion(Init, LC); } } else { TR = MRMgr.getCXXTempObjectRegion(Init, LC); @@ -393,8 +409,7 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded( SVal BaseReg = Reg; // Make the necessary adjustments to obtain the sub-object. - for (auto I = Adjustments.rbegin(), E = Adjustments.rend(); I != E; ++I) { - const SubobjectAdjustment &Adj = *I; + for (const SubobjectAdjustment &Adj : llvm::reverse(Adjustments)) { switch (Adj.Kind) { case SubobjectAdjustment::DerivedToBaseAdjustment: Reg = StoreMgr.evalDerivedToBase(Reg, Adj.DerivedToBase.BasePath); @@ -457,25 +472,140 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded( return State; } +ProgramStateRef ExprEngine::setIndexOfElementToConstruct( + ProgramStateRef State, const CXXConstructExpr *E, + const LocationContext *LCtx, unsigned Idx) { + auto Key = std::make_pair(E, LCtx->getStackFrame()); + + assert(!State->contains<IndexOfElementToConstruct>(Key) || Idx > 0); + + return State->set<IndexOfElementToConstruct>(Key, Idx); +} + +std::optional<unsigned> +ExprEngine::getPendingInitLoop(ProgramStateRef State, const CXXConstructExpr *E, + const LocationContext *LCtx) { + const unsigned *V = State->get<PendingInitLoop>({E, LCtx->getStackFrame()}); + return V ? std::make_optional(*V) : std::nullopt; +} + +ProgramStateRef ExprEngine::removePendingInitLoop(ProgramStateRef State, + const CXXConstructExpr *E, + const LocationContext *LCtx) { + auto Key = std::make_pair(E, LCtx->getStackFrame()); + + assert(E && State->contains<PendingInitLoop>(Key)); + return State->remove<PendingInitLoop>(Key); +} + +ProgramStateRef ExprEngine::setPendingInitLoop(ProgramStateRef State, + const CXXConstructExpr *E, + const LocationContext *LCtx, + unsigned Size) { + auto Key = std::make_pair(E, LCtx->getStackFrame()); + + assert(!State->contains<PendingInitLoop>(Key) && Size > 0); + + return State->set<PendingInitLoop>(Key, Size); +} + +std::optional<unsigned> +ExprEngine::getIndexOfElementToConstruct(ProgramStateRef State, + const CXXConstructExpr *E, + const LocationContext *LCtx) { + const unsigned *V = + State->get<IndexOfElementToConstruct>({E, LCtx->getStackFrame()}); + return V ? std::make_optional(*V) : std::nullopt; +} + +ProgramStateRef +ExprEngine::removeIndexOfElementToConstruct(ProgramStateRef State, + const CXXConstructExpr *E, + const LocationContext *LCtx) { + auto Key = std::make_pair(E, LCtx->getStackFrame()); + + assert(E && State->contains<IndexOfElementToConstruct>(Key)); + return State->remove<IndexOfElementToConstruct>(Key); +} + +std::optional<unsigned> +ExprEngine::getPendingArrayDestruction(ProgramStateRef State, + const LocationContext *LCtx) { + assert(LCtx && "LocationContext shouldn't be null!"); + + const unsigned *V = + State->get<PendingArrayDestruction>(LCtx->getStackFrame()); + return V ? std::make_optional(*V) : std::nullopt; +} + +ProgramStateRef ExprEngine::setPendingArrayDestruction( + ProgramStateRef State, const LocationContext *LCtx, unsigned Idx) { + assert(LCtx && "LocationContext shouldn't be null!"); + + auto Key = LCtx->getStackFrame(); + + return State->set<PendingArrayDestruction>(Key, Idx); +} + +ProgramStateRef +ExprEngine::removePendingArrayDestruction(ProgramStateRef State, + const LocationContext *LCtx) { + assert(LCtx && "LocationContext shouldn't be null!"); + + auto Key = LCtx->getStackFrame(); + + assert(LCtx && State->contains<PendingArrayDestruction>(Key)); + return State->remove<PendingArrayDestruction>(Key); +} + ProgramStateRef ExprEngine::addObjectUnderConstruction(ProgramStateRef State, const ConstructionContextItem &Item, const LocationContext *LC, SVal V) { ConstructedObjectKey Key(Item, LC->getStackFrame()); + + const Expr *Init = nullptr; + + if (auto DS = dyn_cast_or_null<DeclStmt>(Item.getStmtOrNull())) { + if (auto VD = dyn_cast_or_null<VarDecl>(DS->getSingleDecl())) + Init = VD->getInit(); + } + + if (auto LE = dyn_cast_or_null<LambdaExpr>(Item.getStmtOrNull())) + Init = *(LE->capture_init_begin() + Item.getIndex()); + + if (!Init && !Item.getStmtOrNull()) + Init = Item.getCXXCtorInitializer()->getInit(); + + // In an ArrayInitLoopExpr the real initializer is returned by + // getSubExpr(). Note that AILEs can be nested in case of + // multidimesnional arrays. + if (const auto *AILE = dyn_cast_or_null<ArrayInitLoopExpr>(Init)) + Init = extractElementInitializerFromNestedAILE(AILE); + // FIXME: Currently the state might already contain the marker due to // incorrect handling of temporaries bound to default parameters. - assert(!State->get<ObjectsUnderConstruction>(Key) || - Key.getItem().getKind() == - ConstructionContextItem::TemporaryDestructorKind); + // The state will already contain the marker if we construct elements + // in an array, as we visit the same statement multiple times before + // the array declaration. The marker is removed when we exit the + // constructor call. + assert((!State->get<ObjectsUnderConstruction>(Key) || + Key.getItem().getKind() == + ConstructionContextItem::TemporaryDestructorKind || + State->contains<IndexOfElementToConstruct>( + {dyn_cast_or_null<CXXConstructExpr>(Init), LC})) && + "The object is already marked as `UnderConstruction`, when it's not " + "supposed to!"); return State->set<ObjectsUnderConstruction>(Key, V); } -Optional<SVal> +std::optional<SVal> ExprEngine::getObjectUnderConstruction(ProgramStateRef State, const ConstructionContextItem &Item, const LocationContext *LC) { ConstructedObjectKey Key(Item, LC->getStackFrame()); - return Optional<SVal>::create(State->get<ObjectsUnderConstruction>(Key)); + const SVal *V = State->get<ObjectsUnderConstruction>(Key); + return V ? std::make_optional(*V) : std::nullopt; } ProgramStateRef @@ -569,7 +699,7 @@ printObjectsUnderConstructionJson(raw_ostream &Out, ProgramStateRef State, continue; if (!HasItem) { - Out << "[" << NL; + Out << '[' << NL; HasItem = true; } @@ -598,29 +728,238 @@ printObjectsUnderConstructionJson(raw_ostream &Out, ProgramStateRef State, } } -void ExprEngine::printJson(raw_ostream &Out, ProgramStateRef State, - const LocationContext *LCtx, const char *NL, - unsigned int Space, bool IsDot) const { - Indent(Out, Space, IsDot) << "\"constructing_objects\": "; +static void printIndicesOfElementsToConstructJson( + raw_ostream &Out, ProgramStateRef State, const char *NL, + const LocationContext *LCtx, unsigned int Space = 0, bool IsDot = false) { + using KeyT = std::pair<const Expr *, const LocationContext *>; + + const auto &Context = LCtx->getAnalysisDeclContext()->getASTContext(); + PrintingPolicy PP = Context.getPrintingPolicy(); + + ++Space; + bool HasItem = false; + + // Store the last key. + KeyT LastKey; + for (const auto &I : State->get<IndexOfElementToConstruct>()) { + const KeyT &Key = I.first; + if (Key.second != LCtx) + continue; + + if (!HasItem) { + Out << '[' << NL; + HasItem = true; + } + + LastKey = Key; + } + + for (const auto &I : State->get<IndexOfElementToConstruct>()) { + const KeyT &Key = I.first; + unsigned Value = I.second; + if (Key.second != LCtx) + continue; + + Indent(Out, Space, IsDot) << "{ "; + + // Expr + const Expr *E = Key.first; + Out << "\"stmt_id\": " << E->getID(Context); + + // Kind + Out << ", \"kind\": null"; + + // Pretty-print + Out << ", \"pretty\": "; + Out << "\"" << E->getStmtClassName() << ' ' + << E->getSourceRange().printToString(Context.getSourceManager()) << " '" + << QualType::getAsString(E->getType().split(), PP); + Out << "'\""; + + Out << ", \"value\": \"Current index: " << Value - 1 << "\" }"; + + if (Key != LastKey) + Out << ','; + Out << NL; + } + + if (HasItem) + Indent(Out, --Space, IsDot) << ']'; // End of "location_context". + else { + Out << "null "; + } +} + +static void printPendingInitLoopJson(raw_ostream &Out, ProgramStateRef State, + const char *NL, + const LocationContext *LCtx, + unsigned int Space = 0, + bool IsDot = false) { + using KeyT = std::pair<const CXXConstructExpr *, const LocationContext *>; + + const auto &Context = LCtx->getAnalysisDeclContext()->getASTContext(); + PrintingPolicy PP = Context.getPrintingPolicy(); + + ++Space; + bool HasItem = false; + + // Store the last key. + KeyT LastKey; + for (const auto &I : State->get<PendingInitLoop>()) { + const KeyT &Key = I.first; + if (Key.second != LCtx) + continue; + + if (!HasItem) { + Out << '[' << NL; + HasItem = true; + } + + LastKey = Key; + } + + for (const auto &I : State->get<PendingInitLoop>()) { + const KeyT &Key = I.first; + unsigned Value = I.second; + if (Key.second != LCtx) + continue; + + Indent(Out, Space, IsDot) << "{ "; + + const CXXConstructExpr *E = Key.first; + Out << "\"stmt_id\": " << E->getID(Context); + + Out << ", \"kind\": null"; + Out << ", \"pretty\": "; + Out << '\"' << E->getStmtClassName() << ' ' + << E->getSourceRange().printToString(Context.getSourceManager()) << " '" + << QualType::getAsString(E->getType().split(), PP); + Out << "'\""; + + Out << ", \"value\": \"Flattened size: " << Value << "\"}"; + + if (Key != LastKey) + Out << ','; + Out << NL; + } + + if (HasItem) + Indent(Out, --Space, IsDot) << ']'; // End of "location_context". + else { + Out << "null "; + } +} + +static void +printPendingArrayDestructionsJson(raw_ostream &Out, ProgramStateRef State, + const char *NL, const LocationContext *LCtx, + unsigned int Space = 0, bool IsDot = false) { + using KeyT = const LocationContext *; + + ++Space; + bool HasItem = false; + + // Store the last key. + KeyT LastKey = nullptr; + for (const auto &I : State->get<PendingArrayDestruction>()) { + const KeyT &Key = I.first; + if (Key != LCtx) + continue; + + if (!HasItem) { + Out << '[' << NL; + HasItem = true; + } + + LastKey = Key; + } + + for (const auto &I : State->get<PendingArrayDestruction>()) { + const KeyT &Key = I.first; + if (Key != LCtx) + continue; + + Indent(Out, Space, IsDot) << "{ "; + + Out << "\"stmt_id\": null"; + Out << ", \"kind\": null"; + Out << ", \"pretty\": \"Current index: \""; + Out << ", \"value\": \"" << I.second << "\" }"; - if (LCtx && !State->get<ObjectsUnderConstruction>().isEmpty()) { + if (Key != LastKey) + Out << ','; + Out << NL; + } + + if (HasItem) + Indent(Out, --Space, IsDot) << ']'; // End of "location_context". + else { + Out << "null "; + } +} + +/// A helper function to generalize program state trait printing. +/// The function invokes Printer as 'Printer(Out, State, NL, LC, Space, IsDot, +/// std::forward<Args>(args)...)'. \n One possible type for Printer is +/// 'void()(raw_ostream &, ProgramStateRef, const char *, const LocationContext +/// *, unsigned int, bool, ...)' \n \param Trait The state trait to be printed. +/// \param Printer A void function that prints Trait. +/// \param Args An additional parameter pack that is passed to Print upon +/// invocation. +template <typename Trait, typename Printer, typename... Args> +static void printStateTraitWithLocationContextJson( + raw_ostream &Out, ProgramStateRef State, const LocationContext *LCtx, + const char *NL, unsigned int Space, bool IsDot, + const char *jsonPropertyName, Printer printer, Args &&...args) { + + using RequiredType = + void (*)(raw_ostream &, ProgramStateRef, const char *, + const LocationContext *, unsigned int, bool, Args &&...); + + // Try to do as much compile time checking as possible. + // FIXME: check for invocable instead of function? + static_assert(std::is_function_v<std::remove_pointer_t<Printer>>, + "Printer is not a function!"); + static_assert(std::is_convertible_v<Printer, RequiredType>, + "Printer doesn't have the required type!"); + + if (LCtx && !State->get<Trait>().isEmpty()) { + Indent(Out, Space, IsDot) << '\"' << jsonPropertyName << "\": "; ++Space; Out << '[' << NL; LCtx->printJson(Out, NL, Space, IsDot, [&](const LocationContext *LC) { - printObjectsUnderConstructionJson(Out, State, NL, LC, Space, IsDot); + printer(Out, State, NL, LC, Space, IsDot, std::forward<Args>(args)...); }); --Space; - Indent(Out, Space, IsDot) << "]," << NL; // End of "constructing_objects". - } else { - Out << "null," << NL; + Indent(Out, Space, IsDot) << "]," << NL; // End of "jsonPropertyName". } +} + +void ExprEngine::printJson(raw_ostream &Out, ProgramStateRef State, + const LocationContext *LCtx, const char *NL, + unsigned int Space, bool IsDot) const { + + printStateTraitWithLocationContextJson<ObjectsUnderConstruction>( + Out, State, LCtx, NL, Space, IsDot, "constructing_objects", + printObjectsUnderConstructionJson); + printStateTraitWithLocationContextJson<IndexOfElementToConstruct>( + Out, State, LCtx, NL, Space, IsDot, "index_of_element", + printIndicesOfElementsToConstructJson); + printStateTraitWithLocationContextJson<PendingInitLoop>( + Out, State, LCtx, NL, Space, IsDot, "pending_init_loops", + printPendingInitLoopJson); + printStateTraitWithLocationContextJson<PendingArrayDestruction>( + Out, State, LCtx, NL, Space, IsDot, "pending_destructors", + printPendingArrayDestructionsJson); getCheckerManager().runCheckersForPrintStateJson(Out, State, NL, Space, IsDot); } void ExprEngine::processEndWorklist() { + // This prints the name of the top-level function if we crash. + PrettyStackTraceLocationContext CrashInfo(getRootLocationContext()); getCheckerManager().runCheckersForEndAnalysis(G, BR, *this); } @@ -654,6 +993,7 @@ void ExprEngine::processCFGElement(const CFGElement E, ExplodedNode *Pred, ProcessLoopExit(E.castAs<CFGLoopExit>().getLoopStmt(), Pred); return; case CFGElement::LifetimeEnds: + case CFGElement::CleanupFunction: case CFGElement::ScopeBegin: case CFGElement::ScopeEnd: return; @@ -865,7 +1205,7 @@ void ExprEngine::ProcessInitializer(const CFGInitializer CFGInit, SVal LValue = State->getSVal(Init, stackFrame); if (!Field->getType()->isReferenceType()) - if (Optional<Loc> LValueLoc = LValue.getAs<Loc>()) + if (std::optional<Loc> LValueLoc = LValue.getAs<Loc>()) InitVal = State->getSVal(*LValueLoc); // If we fail to get the value for some reason, use a symbolic value. @@ -882,6 +1222,14 @@ void ExprEngine::ProcessInitializer(const CFGInitializer CFGInit, PostInitializer PP(BMI, FieldLoc.getAsRegion(), stackFrame); evalBind(Tmp, Init, Pred, FieldLoc, InitVal, /*isInit=*/true, &PP); } + } else if (BMI->isBaseInitializer() && isa<InitListExpr>(Init)) { + // When the base class is initialized with an initialization list and the + // base class does not have a ctor, there will not be a CXXConstructExpr to + // initialize the base region. Hence, we need to make the bind for it. + SVal BaseLoc = getStoreManager().evalDerivedToBase( + thisVal, QualType(BMI->getBaseClass(), 0), BMI->isBaseVirtual()); + SVal InitVal = State->getSVal(Init, stackFrame); + evalBind(Tmp, Init, Pred, BaseLoc, InitVal, /*isInit=*/true); } else { assert(BMI->isBaseInitializer() || BMI->isDelegatingInitializer()); Tmp.insert(Pred); @@ -902,6 +1250,43 @@ void ExprEngine::ProcessInitializer(const CFGInitializer CFGInit, Engine.enqueue(Dst, currBldrCtx->getBlock(), currStmtIdx); } +std::pair<ProgramStateRef, uint64_t> +ExprEngine::prepareStateForArrayDestruction(const ProgramStateRef State, + const MemRegion *Region, + const QualType &ElementTy, + const LocationContext *LCtx, + SVal *ElementCountVal) { + assert(Region != nullptr && "Not-null region expected"); + + QualType Ty = ElementTy.getDesugaredType(getContext()); + while (const auto *NTy = dyn_cast<ArrayType>(Ty)) + Ty = NTy->getElementType().getDesugaredType(getContext()); + + auto ElementCount = getDynamicElementCount(State, Region, svalBuilder, Ty); + + if (ElementCountVal) + *ElementCountVal = ElementCount; + + // Note: the destructors are called in reverse order. + unsigned Idx = 0; + if (auto OptionalIdx = getPendingArrayDestruction(State, LCtx)) { + Idx = *OptionalIdx; + } else { + // The element count is either unknown, or an SVal that's not an integer. + if (!ElementCount.isConstant()) + return {State, 0}; + + Idx = ElementCount.getAsInteger()->getLimitedValue(); + } + + if (Idx == 0) + return {State, 0}; + + --Idx; + + return {setPendingArrayDestruction(State, LCtx, Idx), Idx}; +} + void ExprEngine::ProcessImplicitDtor(const CFGImplicitDtor D, ExplodedNode *Pred) { ExplodedNodeSet Dst; @@ -942,7 +1327,8 @@ void ExprEngine::ProcessNewAllocator(const CXXNewExpr *NE, else { NodeBuilder Bldr(Pred, Dst, *currBldrCtx); const LocationContext *LCtx = Pred->getLocationContext(); - PostImplicitCall PP(NE->getOperatorNew(), NE->getBeginLoc(), LCtx); + PostImplicitCall PP(NE->getOperatorNew(), NE->getBeginLoc(), LCtx, + getCFGElementRef()); Bldr.generateNode(PP, Pred->getState(), Pred); } Engine.enqueue(Dst, currBldrCtx->getBlock(), currStmtIdx); @@ -951,11 +1337,14 @@ void ExprEngine::ProcessNewAllocator(const CXXNewExpr *NE, void ExprEngine::ProcessAutomaticObjDtor(const CFGAutomaticObjDtor Dtor, ExplodedNode *Pred, ExplodedNodeSet &Dst) { + const auto *DtorDecl = Dtor.getDestructorDecl(getContext()); const VarDecl *varDecl = Dtor.getVarDecl(); QualType varType = varDecl->getType(); ProgramStateRef state = Pred->getState(); - SVal dest = state->getLValue(varDecl, Pred->getLocationContext()); + const LocationContext *LCtx = Pred->getLocationContext(); + + SVal dest = state->getLValue(varDecl, LCtx); const MemRegion *Region = dest.castAs<loc::MemRegionVal>().getRegion(); if (varType->isReferenceType()) { @@ -971,12 +1360,47 @@ void ExprEngine::ProcessAutomaticObjDtor(const CFGAutomaticObjDtor Dtor, varType = cast<TypedValueRegion>(Region)->getValueType(); } - // FIXME: We need to run the same destructor on every element of the array. - // This workaround will just run the first destructor (which will still - // invalidate the entire array). + unsigned Idx = 0; + if (isa<ArrayType>(varType)) { + SVal ElementCount; + std::tie(state, Idx) = prepareStateForArrayDestruction( + state, Region, varType, LCtx, &ElementCount); + + if (ElementCount.isConstant()) { + uint64_t ArrayLength = ElementCount.getAsInteger()->getLimitedValue(); + assert(ArrayLength && + "An automatic dtor for a 0 length array shouldn't be triggered!"); + + // Still handle this case if we don't have assertions enabled. + if (!ArrayLength) { + static SimpleProgramPointTag PT( + "ExprEngine", "Skipping automatic 0 length array destruction, " + "which shouldn't be in the CFG."); + PostImplicitCall PP(DtorDecl, varDecl->getLocation(), LCtx, + getCFGElementRef(), &PT); + NodeBuilder Bldr(Pred, Dst, *currBldrCtx); + Bldr.generateSink(PP, Pred->getState(), Pred); + return; + } + } + } + EvalCallOptions CallOpts; - Region = makeZeroElementRegion(state, loc::MemRegionVal(Region), varType, - CallOpts.IsArrayCtorOrDtor).getAsRegion(); + Region = makeElementRegion(state, loc::MemRegionVal(Region), varType, + CallOpts.IsArrayCtorOrDtor, Idx) + .getAsRegion(); + + NodeBuilder Bldr(Pred, Dst, getBuilderContext()); + + static SimpleProgramPointTag PT("ExprEngine", + "Prepare for object destruction"); + PreImplicitCall PP(DtorDecl, varDecl->getLocation(), LCtx, getCFGElementRef(), + &PT); + Pred = Bldr.generateNode(PP, state, Pred); + + if (!Pred) + return; + Bldr.takeNodes(Pred); VisitCXXDestructor(varType, Region, Dtor.getTriggerStmt(), /*IsBase=*/false, Pred, Dst, CallOpts); @@ -999,26 +1423,62 @@ void ExprEngine::ProcessDeleteDtor(const CFGDeleteDtor Dtor, const CXXRecordDecl *RD = BTy->getAsCXXRecordDecl(); const CXXDestructorDecl *Dtor = RD->getDestructor(); - PostImplicitCall PP(Dtor, DE->getBeginLoc(), LCtx); + PostImplicitCall PP(Dtor, DE->getBeginLoc(), LCtx, getCFGElementRef()); NodeBuilder Bldr(Pred, Dst, *currBldrCtx); Bldr.generateNode(PP, Pred->getState(), Pred); return; } + auto getDtorDecl = [](const QualType &DTy) { + const CXXRecordDecl *RD = DTy->getAsCXXRecordDecl(); + return RD->getDestructor(); + }; + + unsigned Idx = 0; EvalCallOptions CallOpts; const MemRegion *ArgR = ArgVal.getAsRegion(); + if (DE->isArrayForm()) { - // FIXME: We need to run the same destructor on every element of the array. - // This workaround will just run the first destructor (which will still - // invalidate the entire array). CallOpts.IsArrayCtorOrDtor = true; // Yes, it may even be a multi-dimensional array. while (const auto *AT = getContext().getAsArrayType(DTy)) DTy = AT->getElementType(); - if (ArgR) - ArgR = getStoreManager().GetElementZeroRegion(cast<SubRegion>(ArgR), DTy); + + if (ArgR) { + SVal ElementCount; + std::tie(State, Idx) = prepareStateForArrayDestruction( + State, ArgR, DTy, LCtx, &ElementCount); + + // If we're about to destruct a 0 length array, don't run any of the + // destructors. + if (ElementCount.isConstant() && + ElementCount.getAsInteger()->getLimitedValue() == 0) { + + static SimpleProgramPointTag PT( + "ExprEngine", "Skipping 0 length array delete destruction"); + PostImplicitCall PP(getDtorDecl(DTy), DE->getBeginLoc(), LCtx, + getCFGElementRef(), &PT); + NodeBuilder Bldr(Pred, Dst, *currBldrCtx); + Bldr.generateNode(PP, Pred->getState(), Pred); + return; + } + + ArgR = State->getLValue(DTy, svalBuilder.makeArrayIndex(Idx), ArgVal) + .getAsRegion(); + } } + NodeBuilder Bldr(Pred, Dst, getBuilderContext()); + static SimpleProgramPointTag PT("ExprEngine", + "Prepare for object destruction"); + PreImplicitCall PP(getDtorDecl(DTy), DE->getBeginLoc(), LCtx, + getCFGElementRef(), &PT); + Pred = Bldr.generateNode(PP, State, Pred); + + if (!Pred) + return; + Bldr.takeNodes(Pred); + VisitCXXDestructor(DTy, ArgR, DE, /*IsBase=*/false, Pred, Dst, CallOpts); } @@ -1044,6 +1504,7 @@ void ExprEngine::ProcessBaseDtor(const CFGBaseDtor D, void ExprEngine::ProcessMemberDtor(const CFGMemberDtor D, ExplodedNode *Pred, ExplodedNodeSet &Dst) { + const auto *DtorDecl = D.getDestructorDecl(getContext()); const FieldDecl *Member = D.getFieldDecl(); QualType T = Member->getType(); ProgramStateRef State = Pred->getState(); @@ -1055,12 +1516,46 @@ void ExprEngine::ProcessMemberDtor(const CFGMemberDtor D, Loc ThisLoc = State->getSVal(ThisStorageLoc).castAs<Loc>(); SVal FieldVal = State->getLValue(Member, ThisLoc); - // FIXME: We need to run the same destructor on every element of the array. - // This workaround will just run the first destructor (which will still - // invalidate the entire array). + unsigned Idx = 0; + if (isa<ArrayType>(T)) { + SVal ElementCount; + std::tie(State, Idx) = prepareStateForArrayDestruction( + State, FieldVal.getAsRegion(), T, LCtx, &ElementCount); + + if (ElementCount.isConstant()) { + uint64_t ArrayLength = ElementCount.getAsInteger()->getLimitedValue(); + assert(ArrayLength && + "A member dtor for a 0 length array shouldn't be triggered!"); + + // Still handle this case if we don't have assertions enabled. + if (!ArrayLength) { + static SimpleProgramPointTag PT( + "ExprEngine", "Skipping member 0 length array destruction, which " + "shouldn't be in the CFG."); + PostImplicitCall PP(DtorDecl, Member->getLocation(), LCtx, + getCFGElementRef(), &PT); + NodeBuilder Bldr(Pred, Dst, *currBldrCtx); + Bldr.generateSink(PP, Pred->getState(), Pred); + return; + } + } + } + EvalCallOptions CallOpts; - FieldVal = makeZeroElementRegion(State, FieldVal, T, - CallOpts.IsArrayCtorOrDtor); + FieldVal = + makeElementRegion(State, FieldVal, T, CallOpts.IsArrayCtorOrDtor, Idx); + + NodeBuilder Bldr(Pred, Dst, getBuilderContext()); + + static SimpleProgramPointTag PT("ExprEngine", + "Prepare for object destruction"); + PreImplicitCall PP(DtorDecl, Member->getLocation(), LCtx, getCFGElementRef(), + &PT); + Pred = Bldr.generateNode(PP, State, Pred); + + if (!Pred) + return; + Bldr.takeNodes(Pred); VisitCXXDestructor(T, FieldVal.getAsRegion(), CurDtor->getBody(), /*IsBase=*/false, Pred, Dst, CallOpts); @@ -1074,9 +1569,8 @@ void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D, const LocationContext *LC = Pred->getLocationContext(); const MemRegion *MR = nullptr; - if (Optional<SVal> V = - getObjectUnderConstruction(State, D.getBindTemporaryExpr(), - Pred->getLocationContext())) { + if (std::optional<SVal> V = getObjectUnderConstruction( + State, D.getBindTemporaryExpr(), Pred->getLocationContext())) { // FIXME: Currently we insert temporary destructors for default parameters, // but we don't insert the constructors, so the entry in // ObjectsUnderConstruction may be missing. @@ -1092,7 +1586,7 @@ void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D, NodeBuilder Bldr(Pred, Dst, *currBldrCtx); PostImplicitCall PP(D.getDestructorDecl(getContext()), D.getBindTemporaryExpr()->getBeginLoc(), - Pred->getLocationContext()); + Pred->getLocationContext(), getCFGElementRef()); Bldr.generateNode(PP, State, Pred); return; } @@ -1111,15 +1605,31 @@ void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D, EvalCallOptions CallOpts; CallOpts.IsTemporaryCtorOrDtor = true; if (!MR) { - // If we have no MR, we still need to unwrap the array to avoid destroying - // the whole array at once. Regardless, we'd eventually need to model array - // destructors properly, element-by-element. + // FIXME: If we have no MR, we still need to unwrap the array to avoid + // destroying the whole array at once. + // + // For this case there is no universal solution as there is no way to + // directly create an array of temporary objects. There are some expressions + // however which can create temporary objects and have an array type. + // + // E.g.: std::initializer_list<S>{S(), S()}; + // + // The expression above has a type of 'const struct S[2]' but it's a single + // 'std::initializer_list<>'. The destructors of the 2 temporary 'S()' + // objects will be called anyway, because they are 2 separate objects in 2 + // separate clusters, i.e.: not an array. + // + // Now the 'std::initializer_list<>' is not an array either even though it + // has the type of an array. The point is, we only want to invoke the + // destructor for the initializer list once not twice or so. while (const ArrayType *AT = getContext().getAsArrayType(T)) { T = AT->getElementType(); - CallOpts.IsArrayCtorOrDtor = true; + + // FIXME: Enable this flag once we handle this case properly. + // CallOpts.IsArrayCtorOrDtor = true; } } else { - // We'd eventually need to makeZeroElementRegion() trick here, + // FIXME: We'd eventually need to makeElementRegion() trick here, // but for now we don't have the respective construction contexts, // so MR would always be null in this case. Do nothing for now. } @@ -1245,6 +1755,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::OMPForSimdDirectiveClass: case Stmt::OMPSectionsDirectiveClass: case Stmt::OMPSectionDirectiveClass: + case Stmt::OMPScopeDirectiveClass: case Stmt::OMPSingleDirectiveClass: case Stmt::OMPMasterDirectiveClass: case Stmt::OMPCriticalDirectiveClass: @@ -1252,10 +1763,12 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::OMPParallelForSimdDirectiveClass: case Stmt::OMPParallelSectionsDirectiveClass: case Stmt::OMPParallelMasterDirectiveClass: + case Stmt::OMPParallelMaskedDirectiveClass: case Stmt::OMPTaskDirectiveClass: case Stmt::OMPTaskyieldDirectiveClass: case Stmt::OMPBarrierDirectiveClass: case Stmt::OMPTaskwaitDirectiveClass: + case Stmt::OMPErrorDirectiveClass: case Stmt::OMPTaskgroupDirectiveClass: case Stmt::OMPFlushDirectiveClass: case Stmt::OMPDepobjDirectiveClass: @@ -1275,9 +1788,13 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::OMPTaskLoopDirectiveClass: case Stmt::OMPTaskLoopSimdDirectiveClass: case Stmt::OMPMasterTaskLoopDirectiveClass: + case Stmt::OMPMaskedTaskLoopDirectiveClass: case Stmt::OMPMasterTaskLoopSimdDirectiveClass: + case Stmt::OMPMaskedTaskLoopSimdDirectiveClass: case Stmt::OMPParallelMasterTaskLoopDirectiveClass: + case Stmt::OMPParallelMaskedTaskLoopDirectiveClass: case Stmt::OMPParallelMasterTaskLoopSimdDirectiveClass: + case Stmt::OMPParallelMaskedTaskLoopSimdDirectiveClass: case Stmt::OMPDistributeDirectiveClass: case Stmt::OMPDistributeParallelForDirectiveClass: case Stmt::OMPDistributeParallelForSimdDirectiveClass: @@ -1297,8 +1814,14 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::OMPInteropDirectiveClass: case Stmt::OMPDispatchDirectiveClass: case Stmt::OMPMaskedDirectiveClass: + case Stmt::OMPGenericLoopDirectiveClass: + case Stmt::OMPTeamsGenericLoopDirectiveClass: + case Stmt::OMPTargetTeamsGenericLoopDirectiveClass: + case Stmt::OMPParallelGenericLoopDirectiveClass: + case Stmt::OMPTargetParallelGenericLoopDirectiveClass: case Stmt::CapturedStmtClass: - case Stmt::OMPUnrollDirectiveClass: { + case Stmt::OMPUnrollDirectiveClass: + case Stmt::OMPMetaDirectiveClass: { const ExplodedNode *node = Bldr.generateSink(S, Pred, Pred->getState()); Engine.addAbortedBlock(node, currBldrCtx->getBlock()); break; @@ -1341,8 +1864,9 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::GNUNullExprClass: { // GNU __null is a pointer-width integer, not an actual pointer. ProgramStateRef state = Pred->getState(); - state = state->BindExpr(S, Pred->getLocationContext(), - svalBuilder.makeIntValWithPtrWidth(0, false)); + state = state->BindExpr( + S, Pred->getLocationContext(), + svalBuilder.makeIntValWithWidth(getContext().VoidPtrTy, 0)); Bldr.generateNode(S, Pred, state); break; } @@ -1369,10 +1893,14 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, break; } + case Stmt::ArrayInitLoopExprClass: + Bldr.takeNodes(Pred); + VisitArrayInitLoopExpr(cast<ArrayInitLoopExpr>(S), Pred, Dst); + Bldr.addNodes(Dst); + break; // Cases not handled yet; but will handle some day. case Stmt::DesignatedInitExprClass: case Stmt::DesignatedInitUpdateExprClass: - case Stmt::ArrayInitLoopExprClass: case Stmt::ArrayInitIndexExprClass: case Stmt::ExtVectorElementExprClass: case Stmt::ImaginaryLiteralClass: @@ -1394,6 +1922,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::ConceptSpecializationExprClass: case Stmt::CXXRewrittenBinaryOperatorClass: case Stmt::RequiresExprClass: + case Expr::CXXParenListInitExprClass: // Fall through. // Cases we intentionally don't evaluate, since they don't need @@ -1453,7 +1982,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, IsTemporary = true; } - Optional<SVal> ConstantVal = svalBuilder.getConstantVal(ArgE); + std::optional<SVal> ConstantVal = svalBuilder.getConstantVal(ArgE); if (!ConstantVal) ConstantVal = UnknownVal(); @@ -1593,7 +2122,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, // valid region. const Decl *Callee = OCE->getCalleeDecl(); if (const auto *MD = dyn_cast_or_null<CXXMethodDecl>(Callee)) { - if (MD->isInstance()) { + if (MD->isImplicitObjectMemberFunction()) { ProgramStateRef State = Pred->getState(); const LocationContext *LCtx = Pred->getLocationContext(); ProgramStateRef NewState = @@ -1607,8 +2136,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, } } } - // FALLTHROUGH - LLVM_FALLTHROUGH; + [[fallthrough]]; } case Stmt::CallExprClass: @@ -1919,7 +2447,7 @@ bool ExprEngine::replayWithoutInlining(ExplodedNode *N, continue; if (L.getAs<CallEnter>()) continue; - if (Optional<StmtPoint> SP = L.getAs<StmtPoint>()) + if (std::optional<StmtPoint> SP = L.getAs<StmtPoint>()) if (SP->getStmt() == CE) continue; break; @@ -1932,8 +2460,9 @@ bool ExprEngine::replayWithoutInlining(ExplodedNode *N, // Build an Epsilon node from which we will restart the analyzes. // Note that CE is permitted to be NULL! - ProgramPoint NewNodeLoc = - EpsilonPoint(BeforeProcessingCall->getLocationContext(), CE); + static SimpleProgramPointTag PT("ExprEngine", "Replay without inlining"); + ProgramPoint NewNodeLoc = EpsilonPoint( + BeforeProcessingCall->getLocationContext(), CE, nullptr, &PT); // Add the special flag to GDM to signal retrying with no inlining. // Note, changing the state ensures that we are not going to cache out. ProgramStateRef NewNodeState = BeforeProcessingCall->getState(); @@ -1988,8 +2517,7 @@ void ExprEngine::processCFGBlockEntrance(const BlockEdge &L, if (BlockCount == AMgr.options.maxBlockVisitOnPath - 1 && AMgr.options.ShouldWidenLoops) { const Stmt *Term = nodeBuilder.getContext().getBlock()->getTerminatorStmt(); - if (!(Term && - (isa<ForStmt>(Term) || isa<WhileStmt>(Term) || isa<DoStmt>(Term)))) + if (!isa_and_nonnull<ForStmt, WhileStmt, DoStmt, CXXForRangeStmt>(Term)) return; // Widen. const LocationContext *LCtx = Pred->getLocationContext(); @@ -2123,10 +2651,8 @@ static const Stmt *ResolveCondition(const Stmt *Condition, // The invariants are still shifting, but it is possible that the // last element in a CFGBlock is not a CFGStmt. Look for the last // CFGStmt as the value of the condition. - CFGBlock::const_reverse_iterator I = B->rbegin(), E = B->rend(); - for (; I != E; ++I) { - CFGElement Elem = *I; - Optional<CFGStmt> CS = Elem.getAs<CFGStmt>(); + for (CFGElement Elem : llvm::reverse(*B)) { + std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>(); if (!CS) continue; const Stmt *LastStmt = CS->getStmt(); @@ -2164,9 +2690,9 @@ bool ExprEngine::hasMoreIteration(ProgramStateRef State, } /// Split the state on whether there are any more iterations left for this loop. -/// Returns a (HasMoreIteration, HasNoMoreIteration) pair, or None when the -/// acquisition of the loop condition value failed. -static Optional<std::pair<ProgramStateRef, ProgramStateRef>> +/// Returns a (HasMoreIteration, HasNoMoreIteration) pair, or std::nullopt when +/// the acquisition of the loop condition value failed. +static std::optional<std::pair<ProgramStateRef, ProgramStateRef>> assumeCondition(const Stmt *Condition, ExplodedNode *N) { ProgramStateRef State = N->getState(); if (const auto *ObjCFor = dyn_cast<ObjCForCollectionStmt>(Condition)) { @@ -2205,7 +2731,7 @@ assumeCondition(const Stmt *Condition, ExplodedNode *N) { // If the condition is still unknown, give up. if (X.isUnknownOrUndef()) - return None; + return std::nullopt; DefinedSVal V = X.castAs<DefinedSVal>(); @@ -2265,7 +2791,7 @@ void ExprEngine::processBranch(const Stmt *Condition, continue; } if (StTrue && StFalse) - assert(!isa<ObjCForCollectionStmt>(Condition));; + assert(!isa<ObjCForCollectionStmt>(Condition)); // Process the true branch. if (builder.isFeasible(true)) { @@ -2330,12 +2856,12 @@ void ExprEngine::processIndirectGoto(IndirectGotoNodeBuilder &builder) { using iterator = IndirectGotoNodeBuilder::iterator; - if (Optional<loc::GotoLabel> LV = V.getAs<loc::GotoLabel>()) { + if (std::optional<loc::GotoLabel> LV = V.getAs<loc::GotoLabel>()) { const LabelDecl *L = LV->getLabel(); - for (iterator I = builder.begin(), E = builder.end(); I != E; ++I) { - if (I.getLabel() == L) { - builder.generateNode(I, state); + for (iterator Succ : builder) { + if (Succ.getLabel() == L) { + builder.generateNode(Succ, state); return; } } @@ -2343,7 +2869,7 @@ void ExprEngine::processIndirectGoto(IndirectGotoNodeBuilder &builder) { llvm_unreachable("No block with label."); } - if (V.getAs<loc::ConcreteInt>() || V.getAs<UndefinedVal>()) { + if (isa<UndefinedVal, loc::ConcreteInt>(V)) { // Dispatch to the first target and mark it as a sink. //ExplodedNode* N = builder.generateNode(builder.begin(), state, true); // FIXME: add checker visit. @@ -2354,8 +2880,8 @@ void ExprEngine::processIndirectGoto(IndirectGotoNodeBuilder &builder) { // This is really a catch-all. We don't support symbolics yet. // FIXME: Implement dispatch for symbolic pointers. - for (iterator I = builder.begin(), E = builder.end(); I != E; ++I) - builder.generateNode(I, state); + for (iterator Succ : builder) + builder.generateNode(Succ, state); } void ExprEngine::processBeginOfFunction(NodeBuilderContext &BC, @@ -2482,7 +3008,7 @@ void ExprEngine::processSwitch(SwitchNodeBuilder& builder) { V2 = V1; ProgramStateRef StateCase; - if (Optional<NonLoc> NL = CondV.getAs<NonLoc>()) + if (std::optional<NonLoc> NL = CondV.getAs<NonLoc>()) std::tie(StateCase, DefaultSt) = DefaultSt->assumeInclusiveRange(*NL, V1, V2); else // UnknownVal @@ -2541,14 +3067,14 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D, const Decl *D = LocCtxt->getDecl(); const auto *MD = dyn_cast_or_null<CXXMethodDecl>(D); const auto *DeclRefEx = dyn_cast<DeclRefExpr>(Ex); - Optional<std::pair<SVal, QualType>> VInfo; + std::optional<std::pair<SVal, QualType>> VInfo; if (AMgr.options.ShouldInlineLambdas && DeclRefEx && DeclRefEx->refersToEnclosingVariableOrCapture() && MD && MD->getParent()->isLambda()) { // Lookup the field of the lambda. const CXXRecordDecl *CXXRec = MD->getParent(); - llvm::DenseMap<const VarDecl *, FieldDecl *> LambdaCaptureFields; + llvm::DenseMap<const ValueDecl *, FieldDecl *> LambdaCaptureFields; FieldDecl *LambdaThisCaptureField; CXXRec->getCaptureFields(LambdaCaptureFields, LambdaThisCaptureField); @@ -2593,20 +3119,175 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D, ProgramPoint::PostLValueKind); return; } - if (isa<FieldDecl>(D) || isa<IndirectFieldDecl>(D)) { + if (isa<FieldDecl, IndirectFieldDecl>(D)) { // Delegate all work related to pointer to members to the surrounding // operator&. return; } - if (isa<BindingDecl>(D)) { - // FIXME: proper support for bound declarations. - // For now, let's just prevent crashing. + if (const auto *BD = dyn_cast<BindingDecl>(D)) { + const auto *DD = cast<DecompositionDecl>(BD->getDecomposedDecl()); + + SVal Base = state->getLValue(DD, LCtx); + if (DD->getType()->isReferenceType()) { + if (const MemRegion *R = Base.getAsRegion()) + Base = state->getSVal(R); + else + Base = UnknownVal(); + } + + SVal V = UnknownVal(); + + // Handle binding to data members + if (const auto *ME = dyn_cast<MemberExpr>(BD->getBinding())) { + const auto *Field = cast<FieldDecl>(ME->getMemberDecl()); + V = state->getLValue(Field, Base); + } + // Handle binding to arrays + else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(BD->getBinding())) { + SVal Idx = state->getSVal(ASE->getIdx(), LCtx); + + // Note: the index of an element in a structured binding is automatically + // created and it is a unique identifier of the specific element. Thus it + // cannot be a value that varies at runtime. + assert(Idx.isConstant() && "BindingDecl array index is not a constant!"); + + V = state->getLValue(BD->getType(), Idx, Base); + } + // Handle binding to tuple-like structures + else if (const auto *HV = BD->getHoldingVar()) { + V = state->getLValue(HV, LCtx); + + if (HV->getType()->isReferenceType()) { + if (const MemRegion *R = V.getAsRegion()) + V = state->getSVal(R); + else + V = UnknownVal(); + } + } else + llvm_unreachable("An unknown case of structured binding encountered!"); + + // In case of tuple-like types the references are already handled, so we + // don't want to handle them again. + if (BD->getType()->isReferenceType() && !BD->getHoldingVar()) { + if (const MemRegion *R = V.getAsRegion()) + V = state->getSVal(R); + else + V = UnknownVal(); + } + + Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, V), nullptr, + ProgramPoint::PostLValueKind); + + return; + } + + if (const auto *TPO = dyn_cast<TemplateParamObjectDecl>(D)) { + // FIXME: We should meaningfully implement this. + (void)TPO; return; } llvm_unreachable("Support for this Decl not implemented."); } +/// VisitArrayInitLoopExpr - Transfer function for array init loop. +void ExprEngine::VisitArrayInitLoopExpr(const ArrayInitLoopExpr *Ex, + ExplodedNode *Pred, + ExplodedNodeSet &Dst) { + ExplodedNodeSet CheckerPreStmt; + getCheckerManager().runCheckersForPreStmt(CheckerPreStmt, Pred, Ex, *this); + + ExplodedNodeSet EvalSet; + StmtNodeBuilder Bldr(CheckerPreStmt, EvalSet, *currBldrCtx); + + const Expr *Arr = Ex->getCommonExpr()->getSourceExpr(); + + for (auto *Node : CheckerPreStmt) { + + // The constructor visitior has already taken care of everything. + if (isa<CXXConstructExpr>(Ex->getSubExpr())) + break; + + const LocationContext *LCtx = Node->getLocationContext(); + ProgramStateRef state = Node->getState(); + + SVal Base = UnknownVal(); + + // As in case of this expression the sub-expressions are not visited by any + // other transfer functions, they are handled by matching their AST. + + // Case of implicit copy or move ctor of object with array member + // + // Note: ExprEngine::VisitMemberExpr is not able to bind the array to the + // environment. + // + // struct S { + // int arr[2]; + // }; + // + // + // S a; + // S b = a; + // + // The AST in case of a *copy constructor* looks like this: + // ArrayInitLoopExpr + // |-OpaqueValueExpr + // | `-MemberExpr <-- match this + // | `-DeclRefExpr + // ` ... + // + // + // S c; + // S d = std::move(d); + // + // In case of a *move constructor* the resulting AST looks like: + // ArrayInitLoopExpr + // |-OpaqueValueExpr + // | `-MemberExpr <-- match this first + // | `-CXXStaticCastExpr <-- match this after + // | `-DeclRefExpr + // ` ... + if (const auto *ME = dyn_cast<MemberExpr>(Arr)) { + Expr *MEBase = ME->getBase(); + + // Move ctor + if (auto CXXSCE = dyn_cast<CXXStaticCastExpr>(MEBase)) { + MEBase = CXXSCE->getSubExpr(); + } + + auto ObjDeclExpr = cast<DeclRefExpr>(MEBase); + SVal Obj = state->getLValue(cast<VarDecl>(ObjDeclExpr->getDecl()), LCtx); + + Base = state->getLValue(cast<FieldDecl>(ME->getMemberDecl()), Obj); + } + + // Case of lambda capture and decomposition declaration + // + // int arr[2]; + // + // [arr]{ int a = arr[0]; }(); + // auto[a, b] = arr; + // + // In both of these cases the AST looks like the following: + // ArrayInitLoopExpr + // |-OpaqueValueExpr + // | `-DeclRefExpr <-- match this + // ` ... + if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Arr)) + Base = state->getLValue(cast<VarDecl>(DRE->getDecl()), LCtx); + + // Create a lazy compound value to the original array + if (const MemRegion *R = Base.getAsRegion()) + Base = state->getSVal(R); + else + Base = UnknownVal(); + + Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, Base)); + } + + getCheckerManager().runCheckersForPostStmt(Dst, EvalSet, Ex, *this); +} + /// VisitArraySubscriptExpr - Transfer function for array accesses void ExprEngine::VisitArraySubscriptExpr(const ArraySubscriptExpr *A, ExplodedNode *Pred, @@ -2670,7 +3351,7 @@ void ExprEngine::VisitMemberExpr(const MemberExpr *M, ExplodedNode *Pred, // Handle static member variables and enum constants accessed via // member syntax. - if (isa<VarDecl>(Member) || isa<EnumConstantDecl>(Member)) { + if (isa<VarDecl, EnumConstantDecl>(Member)) { for (const auto I : CheckedSet) VisitCommonDeclRefExpr(M, Member, I, EvalSet); } else { @@ -2684,7 +3365,7 @@ void ExprEngine::VisitMemberExpr(const MemberExpr *M, ExplodedNode *Pred, // Handle C++ method calls. if (const auto *MD = dyn_cast<CXXMethodDecl>(Member)) { - if (MD->isInstance()) + if (MD->isImplicitObjectMemberFunction()) state = createTemporaryRegionIfNeeded(state, LCtx, BaseExpr); SVal MDVal = svalBuilder.getFunctionPointer(MD); @@ -2702,6 +3383,14 @@ void ExprEngine::VisitMemberExpr(const MemberExpr *M, ExplodedNode *Pred, SVal baseExprVal = MR ? loc::MemRegionVal(MR) : state->getSVal(BaseExpr, LCtx); + // FIXME: Copied from RegionStoreManager::bind() + if (const auto *SR = + dyn_cast_or_null<SymbolicRegion>(baseExprVal.getAsRegion())) { + QualType T = SR->getPointeeStaticType(); + baseExprVal = + loc::MemRegionVal(getStoreManager().GetElementZeroRegion(SR, T)); + } + const auto *field = cast<FieldDecl>(Member); SVal L = state->getLValue(field, baseExprVal); @@ -2791,7 +3480,8 @@ ProgramStateRef ExprEngine::processPointerEscapedOnBind( for (const std::pair<SVal, SVal> &LocAndVal : LocAndVals) { // Cases (1) and (2). const MemRegion *MR = LocAndVal.first.getAsRegion(); - if (!MR || !MR->hasStackStorage()) { + if (!MR || + !isa<StackSpaceRegion, StaticGlobalSpaceRegion>(MR->getMemorySpace())) { Escaped.push_back(LocAndVal.second); continue; } @@ -2894,7 +3584,7 @@ void ExprEngine::evalBind(ExplodedNodeSet &Dst, const Stmt *StoreE, // If the location is not a 'Loc', it will already be handled by // the checkers. There is nothing left to do. - if (!location.getAs<Loc>()) { + if (!isa<Loc>(location)) { const ProgramPoint L = PostStore(StoreE, LC, /*Loc*/nullptr, /*tag*/nullptr); ProgramStateRef state = Pred->getState(); @@ -2915,7 +3605,7 @@ void ExprEngine::evalBind(ExplodedNodeSet &Dst, const Stmt *StoreE, Val, LC, /* notifyChanges = */ !atDeclInit); const MemRegion *LocReg = nullptr; - if (Optional<loc::MemRegionVal> LocRegVal = + if (std::optional<loc::MemRegionVal> LocRegVal = location.getAs<loc::MemRegionVal>()) { LocReg = LocRegVal->getRegion(); } @@ -2964,7 +3654,7 @@ void ExprEngine::evalLoad(ExplodedNodeSet &Dst, SVal location, const ProgramPointTag *tag, QualType LoadTy) { - assert(!location.getAs<NonLoc>() && "location cannot be a NonLoc."); + assert(!isa<NonLoc>(location) && "location cannot be a NonLoc."); assert(NodeEx); assert(BoundEx); // Evaluate the location (checks for bad dereferences). @@ -3056,7 +3746,7 @@ void ExprEngine::evalEagerlyAssumeBinOpBifurcation(ExplodedNodeSet &Dst, ProgramStateRef state = Pred->getState(); SVal V = state->getSVal(Ex, Pred->getLocationContext()); - Optional<nonloc::SymbolVal> SEV = V.getAs<nonloc::SymbolVal>(); + std::optional<nonloc::SymbolVal> SEV = V.getAs<nonloc::SymbolVal>(); if (SEV && SEV->isExpression()) { const std::pair<const ProgramPointTag *, const ProgramPointTag*> &tags = geteagerlyAssumeBinOpBifurcationTags(); @@ -3095,9 +3785,9 @@ void ExprEngine::VisitGCCAsmStmt(const GCCAsmStmt *A, ExplodedNode *Pred, for (const Expr *O : A->outputs()) { SVal X = state->getSVal(O, Pred->getLocationContext()); - assert(!X.getAs<NonLoc>()); // Should be an Lval, or unknown, undef. + assert(!isa<NonLoc>(X)); // Should be an Lval, or unknown, undef. - if (Optional<Loc> LV = X.getAs<Loc>()) + if (std::optional<Loc> LV = X.getAs<Loc>()) state = state->bindLoc(*LV, UnknownVal(), Pred->getLocationContext()); } @@ -3114,7 +3804,6 @@ void ExprEngine::VisitMSAsmStmt(const MSAsmStmt *A, ExplodedNode *Pred, // Visualization. //===----------------------------------------------------------------------===// -#ifndef NDEBUG namespace llvm { template<> @@ -3125,12 +3814,9 @@ struct DOTGraphTraits<ExplodedGraph*> : public DefaultDOTGraphTraits { BugReporter &BR = static_cast<ExprEngine &>( N->getState()->getStateManager().getOwningEngine()).getBugReporter(); - const auto EQClasses = - llvm::make_range(BR.EQClasses_begin(), BR.EQClasses_end()); - - for (const auto &EQ : EQClasses) { - for (const auto &I : EQ.getReports()) { - const auto *PR = dyn_cast<PathSensitiveBugReport>(I.get()); + for (const auto &Class : BR.equivalenceClasses()) { + for (const auto &Report : Class.getReports()) { + const auto *PR = dyn_cast<PathSensitiveBugReport>(Report.get()); if (!PR) continue; const ExplodedNode *EN = PR->getErrorNode(); @@ -3190,7 +3876,7 @@ struct DOTGraphTraits<ExplodedGraph*> : public DefaultDOTGraphTraits { OtherNode->getLocation().printJson(Out, /*NL=*/"\\l"); Out << ", \"tag\": "; if (const ProgramPointTag *Tag = OtherNode->getLocation().getTag()) - Out << '\"' << Tag->getTagDescription() << "\""; + Out << '\"' << Tag->getTagDescription() << '\"'; else Out << "null"; Out << ", \"node_id\": " << OtherNode->getID() << @@ -3212,72 +3898,51 @@ struct DOTGraphTraits<ExplodedGraph*> : public DefaultDOTGraphTraits { }; } // namespace llvm -#endif void ExprEngine::ViewGraph(bool trim) { -#ifndef NDEBUG std::string Filename = DumpGraph(trim); llvm::DisplayGraph(Filename, false, llvm::GraphProgram::DOT); -#else - llvm::errs() << "Warning: viewing graph requires assertions" << "\n"; -#endif } - -void ExprEngine::ViewGraph(ArrayRef<const ExplodedNode*> Nodes) { -#ifndef NDEBUG +void ExprEngine::ViewGraph(ArrayRef<const ExplodedNode *> Nodes) { std::string Filename = DumpGraph(Nodes); llvm::DisplayGraph(Filename, false, llvm::GraphProgram::DOT); -#else - llvm::errs() << "Warning: viewing graph requires assertions" << "\n"; -#endif } std::string ExprEngine::DumpGraph(bool trim, StringRef Filename) { -#ifndef NDEBUG if (trim) { std::vector<const ExplodedNode *> Src; // Iterate through the reports and get their nodes. - for (BugReporter::EQClasses_iterator - EI = BR.EQClasses_begin(), EE = BR.EQClasses_end(); EI != EE; ++EI) { + for (const auto &Class : BR.equivalenceClasses()) { const auto *R = - dyn_cast<PathSensitiveBugReport>(EI->getReports()[0].get()); + dyn_cast<PathSensitiveBugReport>(Class.getReports()[0].get()); if (!R) continue; const auto *N = const_cast<ExplodedNode *>(R->getErrorNode()); Src.push_back(N); } return DumpGraph(Src, Filename); - } else { - return llvm::WriteGraph(&G, "ExprEngine", /*ShortNames=*/false, - /*Title=*/"Exploded Graph", - /*Filename=*/std::string(Filename)); } -#else - llvm::errs() << "Warning: dumping graph requires assertions" << "\n"; - return ""; -#endif + + return llvm::WriteGraph(&G, "ExprEngine", /*ShortNames=*/false, + /*Title=*/"Exploded Graph", + /*Filename=*/std::string(Filename)); } -std::string ExprEngine::DumpGraph(ArrayRef<const ExplodedNode*> Nodes, +std::string ExprEngine::DumpGraph(ArrayRef<const ExplodedNode *> Nodes, StringRef Filename) { -#ifndef NDEBUG std::unique_ptr<ExplodedGraph> TrimmedG(G.trim(Nodes)); if (!TrimmedG.get()) { llvm::errs() << "warning: Trimmed ExplodedGraph is empty.\n"; return ""; - } else { - return llvm::WriteGraph(TrimmedG.get(), "TrimmedExprEngine", - /*ShortNames=*/false, - /*Title=*/"Trimmed Exploded Graph", - /*Filename=*/std::string(Filename)); - } -#else - llvm::errs() << "Warning: dumping graph requires assertions" << "\n"; - return ""; -#endif + } + + return llvm::WriteGraph(TrimmedG.get(), "TrimmedExprEngine", + /*ShortNames=*/false, + /*Title=*/"Trimmed Exploded Graph", + /*Filename=*/std::string(Filename)); } void *ProgramStateTrait<ReplayWithoutInlining>::GDMIndex() { diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp index 7ad3dca831ac..7e431f7e598c 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -10,10 +10,11 @@ // //===----------------------------------------------------------------------===// -#include "clang/AST/ExprCXX.h" #include "clang/AST/DeclCXX.h" +#include "clang/AST/ExprCXX.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" +#include <optional> using namespace clang; using namespace ento; @@ -29,8 +30,7 @@ static SVal conjureOffsetSymbolOnLocation( SVal Symbol, SVal Other, Expr* Expression, SValBuilder &svalBuilder, unsigned Count, const LocationContext *LCtx) { QualType Ty = Expression->getType(); - if (Other.getAs<Loc>() && - Ty->isIntegralOrEnumerationType() && + if (isa<Loc>(Other) && Ty->isIntegralOrEnumerationType() && Symbol.isUnknown()) { return svalBuilder.conjureSymbolVal(Expression, LCtx, Ty, Count); } @@ -133,11 +133,9 @@ void ExprEngine::VisitBinaryOperator(const BinaryOperator* B, SVal location = LeftV; evalLoad(Tmp, B, LHS, *it, state, location); - for (ExplodedNodeSet::iterator I = Tmp.begin(), E = Tmp.end(); I != E; - ++I) { - - state = (*I)->getState(); - const LocationContext *LCtx = (*I)->getLocationContext(); + for (ExplodedNode *N : Tmp) { + state = N->getState(); + const LocationContext *LCtx = N->getLocationContext(); SVal V = state->getSVal(LHS, LCtx); // Get the computation type. @@ -171,8 +169,7 @@ void ExprEngine::VisitBinaryOperator(const BinaryOperator* B, currBldrCtx->blockCount()); // However, we need to convert the symbol to the computation type. Result = svalBuilder.evalCast(LHSVal, CTy, LTy); - } - else { + } else { // The left-hand side may bind to a different value then the // computation type. LHSVal = svalBuilder.evalCast(Result, LTy, CTy); @@ -185,7 +182,7 @@ void ExprEngine::VisitBinaryOperator(const BinaryOperator* B, else state = state->BindExpr(B, LCtx, Result); - evalStore(Tmp2, B, LHS, *I, state, location, LHSVal); + evalStore(Tmp2, B, LHS, N, state, location, LHSVal); } } @@ -211,14 +208,12 @@ void ExprEngine::VisitBlockExpr(const BlockExpr *BE, ExplodedNode *Pred, if (const BlockDataRegion *BDR = dyn_cast_or_null<BlockDataRegion>(V.getAsRegion())) { - BlockDataRegion::referenced_vars_iterator I = BDR->referenced_vars_begin(), - E = BDR->referenced_vars_end(); - + auto ReferencedVars = BDR->referenced_vars(); auto CI = BD->capture_begin(); auto CE = BD->capture_end(); - for (; I != E; ++I) { - const VarRegion *capturedR = I.getCapturedRegion(); - const TypedValueRegion *originalR = I.getOriginalRegion(); + for (auto Var : ReferencedVars) { + const VarRegion *capturedR = Var.getCapturedRegion(); + const TypedValueRegion *originalR = Var.getOriginalRegion(); // If the capture had a copy expression, use the result of evaluating // that expression, otherwise use the original value. @@ -269,10 +264,12 @@ ProgramStateRef ExprEngine::handleLValueBitCast( } // Delegate to SValBuilder to process. SVal OrigV = state->getSVal(Ex, LCtx); - SVal V = svalBuilder.evalCast(OrigV, T, ExTy); + SVal SimplifiedOrigV = svalBuilder.simplifySVal(state, OrigV); + SVal V = svalBuilder.evalCast(SimplifiedOrigV, T, ExTy); // Negate the result if we're treating the boolean as a signed i1 - if (CastE->getCastKind() == CK_BooleanToSignedIntegral) - V = evalMinus(V); + if (CastE->getCastKind() == CK_BooleanToSignedIntegral && V.isValid()) + V = svalBuilder.evalMinus(V.castAs<NonLoc>()); + state = state->BindExpr(CastE, LCtx, V); if (V.isUnknown() && !OrigV.isUnknown()) { state = escapeValues(state, OrigV, PSK_EscapeOther); @@ -290,9 +287,7 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, if (CastE->getCastKind() == CK_LValueToRValue || CastE->getCastKind() == CK_LValueToRValueBitCast) { - for (ExplodedNodeSet::iterator I = dstPreStmt.begin(), E = dstPreStmt.end(); - I!=E; ++I) { - ExplodedNode *subExprNode = *I; + for (ExplodedNode *subExprNode : dstPreStmt) { ProgramStateRef state = subExprNode->getState(); const LocationContext *LCtx = subExprNode->getLocationContext(); evalLoad(Dst, CastE, CastE, subExprNode, state, state->getSVal(Ex, LCtx)); @@ -308,10 +303,7 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, T = ExCast->getTypeAsWritten(); StmtNodeBuilder Bldr(dstPreStmt, Dst, *currBldrCtx); - for (ExplodedNodeSet::iterator I = dstPreStmt.begin(), E = dstPreStmt.end(); - I != E; ++I) { - - Pred = *I; + for (ExplodedNode *Pred : dstPreStmt) { ProgramStateRef state = Pred->getState(); const LocationContext *LCtx = Pred->getLocationContext(); @@ -371,7 +363,7 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, case CK_IntegralToPointer: case CK_PointerToIntegral: { SVal V = state->getSVal(Ex, LCtx); - if (V.getAs<nonloc::PointerToMember>()) { + if (isa<nonloc::PointerToMember>(V)) { state = state->BindExpr(CastE, LCtx, UnknownVal()); Bldr.generateNode(CastE, Pred, state); continue; @@ -416,7 +408,10 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, case CK_IntegralCast: { // Delegate to SValBuilder to process. SVal V = state->getSVal(Ex, LCtx); - V = svalBuilder.evalIntegralCast(state, V, T, ExTy); + if (AMgr.options.ShouldSupportSymbolicIntegerCasts) + V = svalBuilder.evalCast(V, T, ExTy); + else + V = svalBuilder.evalIntegralCast(state, V, T, ExTy); state = state->BindExpr(CastE, LCtx, V); Bldr.generateNode(CastE, Pred, state); continue; @@ -439,14 +434,15 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, if (CastE->isGLValue()) resultType = getContext().getPointerType(resultType); - bool Failed = false; + bool Failed = true; - // Check if the value being cast evaluates to 0. - if (val.isZeroConstant()) - Failed = true; - // Else, evaluate the cast. - else - val = getStoreManager().attemptDownCast(val, T, Failed); + // Check if the value being cast does not evaluates to 0. + if (!val.isZeroConstant()) + if (std::optional<SVal> V = + StateMgr.getStoreManager().evalBaseToDerived(val, T)) { + val = *V; + Failed = false; + } if (Failed) { if (T->isReferenceType()) { @@ -456,7 +452,8 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, continue; } else { // If the cast fails on a pointer, bind to 0. - state = state->BindExpr(CastE, LCtx, svalBuilder.makeNull()); + state = state->BindExpr(CastE, LCtx, + svalBuilder.makeNullWithType(resultType)); } } else { // If we don't know if the cast succeeded, conjure a new symbol. @@ -478,14 +475,13 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, if (CastE->isGLValue()) resultType = getContext().getPointerType(resultType); - bool Failed = false; - if (!val.isConstant()) { - val = getStoreManager().attemptDownCast(val, T, Failed); + std::optional<SVal> V = getStoreManager().evalBaseToDerived(val, T); + val = V ? *V : UnknownVal(); } // Failed to cast or the result is unknown, fall back to conservative. - if (Failed || val.isUnknown()) { + if (val.isUnknown()) { val = svalBuilder.conjureSymbolVal(nullptr, CastE, LCtx, resultType, currBldrCtx->blockCount()); @@ -495,7 +491,7 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, continue; } case CK_NullToPointer: { - SVal V = svalBuilder.makeNull(); + SVal V = svalBuilder.makeNullWithType(CastE->getType()); state = state->BindExpr(CastE, LCtx, V); Bldr.generateNode(CastE, Pred, state); continue; @@ -520,7 +516,7 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, } // Explicitly proceed with default handler for this case cascade. } - LLVM_FALLTHROUGH; + [[fallthrough]]; // Various C++ casts that are not handled yet. case CK_ToUnion: case CK_MatrixCast: @@ -550,7 +546,7 @@ void ExprEngine::VisitCompoundLiteralExpr(const CompoundLiteralExpr *CL, const Expr *Init = CL->getInitializer(); SVal V = State->getSVal(CL->getInitializer(), LCtx); - if (isa<CXXConstructExpr>(Init) || isa<CXXStdInitializerListExpr>(Init)) { + if (isa<CXXConstructExpr, CXXStdInitializerListExpr>(Init)) { // No work needed. Just pass the value up to this expression. } else { assert(isa<InitListExpr>(Init)); @@ -757,9 +753,8 @@ void ExprEngine::VisitInitListExpr(const InitListExpr *IE, return; } - for (InitListExpr::const_reverse_iterator it = IE->rbegin(), - ei = IE->rend(); it != ei; ++it) { - SVal V = state->getSVal(cast<Expr>(*it), LCtx); + for (const Stmt *S : llvm::reverse(*IE)) { + SVal V = state->getSVal(cast<Expr>(S), LCtx); vals = getBasicVals().prependSVal(V, vals); } @@ -820,7 +815,7 @@ void ExprEngine::VisitGuardedExpr(const Expr *Ex, SVal V; for (CFGElement CE : llvm::reverse(*SrcBlock)) { - if (Optional<CFGStmt> CS = CE.getAs<CFGStmt>()) { + if (std::optional<CFGStmt> CS = CE.getAs<CFGStmt>()) { const Expr *ValEx = cast<Expr>(CS->getStmt()); ValEx = ValEx->IgnoreParens(); @@ -879,8 +874,7 @@ VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *Ex, QualType T = Ex->getTypeOfArgument(); - for (ExplodedNodeSet::iterator I = CheckedSet.begin(), E = CheckedSet.end(); - I != E; ++I) { + for (ExplodedNode *N : CheckedSet) { if (Ex->getKind() == UETT_SizeOf) { if (!T->isIncompleteType() && !T->isConstantSizeType()) { assert(T->isVariableArrayType() && "Unknown non-constant-sized type."); @@ -899,18 +893,17 @@ VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *Ex, APSInt Value = Ex->EvaluateKnownConstInt(getContext()); CharUnits amt = CharUnits::fromQuantity(Value.getZExtValue()); - ProgramStateRef state = (*I)->getState(); - state = state->BindExpr(Ex, (*I)->getLocationContext(), - svalBuilder.makeIntVal(amt.getQuantity(), - Ex->getType())); - Bldr.generateNode(Ex, *I, state); + ProgramStateRef state = N->getState(); + state = state->BindExpr( + Ex, N->getLocationContext(), + svalBuilder.makeIntVal(amt.getQuantity(), Ex->getType())); + Bldr.generateNode(Ex, N, state); } getCheckerManager().runCheckersForPostStmt(Dst, EvalSet, Ex, *this); } -void ExprEngine::handleUOExtension(ExplodedNodeSet::iterator I, - const UnaryOperator *U, +void ExprEngine::handleUOExtension(ExplodedNode *N, const UnaryOperator *U, StmtNodeBuilder &Bldr) { // FIXME: We can probably just have some magic in Environment::getSVal() // that propagates values, instead of creating a new node here. @@ -920,10 +913,9 @@ void ExprEngine::handleUOExtension(ExplodedNodeSet::iterator I, // generate an extra node that just propagates the value of the // subexpression. const Expr *Ex = U->getSubExpr()->IgnoreParens(); - ProgramStateRef state = (*I)->getState(); - const LocationContext *LCtx = (*I)->getLocationContext(); - Bldr.generateNode(U, *I, state->BindExpr(U, LCtx, - state->getSVal(Ex, LCtx))); + ProgramStateRef state = N->getState(); + const LocationContext *LCtx = N->getLocationContext(); + Bldr.generateNode(U, N, state->BindExpr(U, LCtx, state->getSVal(Ex, LCtx))); } void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, ExplodedNode *Pred, @@ -935,13 +927,12 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, ExplodedNode *Pred, ExplodedNodeSet EvalSet; StmtNodeBuilder Bldr(CheckedSet, EvalSet, *currBldrCtx); - for (ExplodedNodeSet::iterator I = CheckedSet.begin(), E = CheckedSet.end(); - I != E; ++I) { + for (ExplodedNode *N : CheckedSet) { switch (U->getOpcode()) { default: { - Bldr.takeNodes(*I); + Bldr.takeNodes(N); ExplodedNodeSet Tmp; - VisitIncrementDecrementOperator(U, *I, Tmp); + VisitIncrementDecrementOperator(U, N, Tmp); Bldr.addNodes(Tmp); break; } @@ -956,10 +947,10 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, ExplodedNode *Pred, // For all other types, UO_Real is an identity operation. assert (U->getType() == Ex->getType()); - ProgramStateRef state = (*I)->getState(); - const LocationContext *LCtx = (*I)->getLocationContext(); - Bldr.generateNode(U, *I, state->BindExpr(U, LCtx, - state->getSVal(Ex, LCtx))); + ProgramStateRef state = N->getState(); + const LocationContext *LCtx = N->getLocationContext(); + Bldr.generateNode(U, N, + state->BindExpr(U, LCtx, state->getSVal(Ex, LCtx))); break; } @@ -971,10 +962,10 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, ExplodedNode *Pred, break; } // For all other types, UO_Imag returns 0. - ProgramStateRef state = (*I)->getState(); - const LocationContext *LCtx = (*I)->getLocationContext(); + ProgramStateRef state = N->getState(); + const LocationContext *LCtx = N->getLocationContext(); SVal X = svalBuilder.makeZeroVal(Ex->getType()); - Bldr.generateNode(U, *I, state->BindExpr(U, LCtx, X)); + Bldr.generateNode(U, N, state->BindExpr(U, LCtx, X)); break; } @@ -984,25 +975,24 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, ExplodedNode *Pred, if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Ex)) { const ValueDecl *VD = DRE->getDecl(); - if (isa<CXXMethodDecl>(VD) || isa<FieldDecl>(VD) || - isa<IndirectFieldDecl>(VD)) { - ProgramStateRef State = (*I)->getState(); - const LocationContext *LCtx = (*I)->getLocationContext(); + if (isa<CXXMethodDecl, FieldDecl, IndirectFieldDecl>(VD)) { + ProgramStateRef State = N->getState(); + const LocationContext *LCtx = N->getLocationContext(); SVal SV = svalBuilder.getMemberPointer(cast<NamedDecl>(VD)); - Bldr.generateNode(U, *I, State->BindExpr(U, LCtx, SV)); + Bldr.generateNode(U, N, State->BindExpr(U, LCtx, SV)); break; } } // Explicitly proceed with default handler for this case cascade. - handleUOExtension(I, U, Bldr); + handleUOExtension(N, U, Bldr); break; } case UO_Plus: assert(!U->isGLValue()); - LLVM_FALLTHROUGH; + [[fallthrough]]; case UO_Deref: case UO_Extension: { - handleUOExtension(I, U, Bldr); + handleUOExtension(N, U, Bldr); break; } @@ -1011,14 +1001,14 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, ExplodedNode *Pred, case UO_Not: { assert (!U->isGLValue()); const Expr *Ex = U->getSubExpr()->IgnoreParens(); - ProgramStateRef state = (*I)->getState(); - const LocationContext *LCtx = (*I)->getLocationContext(); + ProgramStateRef state = N->getState(); + const LocationContext *LCtx = N->getLocationContext(); // Get the value of the subexpression. SVal V = state->getSVal(Ex, LCtx); if (V.isUnknownOrUndef()) { - Bldr.generateNode(U, *I, state->BindExpr(U, LCtx, V)); + Bldr.generateNode(U, N, state->BindExpr(U, LCtx, V)); break; } @@ -1027,11 +1017,13 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, ExplodedNode *Pred, llvm_unreachable("Invalid Opcode."); case UO_Not: // FIXME: Do we need to handle promotions? - state = state->BindExpr(U, LCtx, evalComplement(V.castAs<NonLoc>())); + state = state->BindExpr( + U, LCtx, svalBuilder.evalComplement(V.castAs<NonLoc>())); break; case UO_Minus: // FIXME: Do we need to handle promotions? - state = state->BindExpr(U, LCtx, evalMinus(V.castAs<NonLoc>())); + state = state->BindExpr(U, LCtx, + svalBuilder.evalMinus(V.castAs<NonLoc>())); break; case UO_LNot: // C99 6.5.3.3: "The expression !E is equivalent to (0==E)." @@ -1039,22 +1031,21 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, ExplodedNode *Pred, // Note: technically we do "E == 0", but this is the same in the // transfer functions as "0 == E". SVal Result; - if (Optional<Loc> LV = V.getAs<Loc>()) { - Loc X = svalBuilder.makeNullWithType(Ex->getType()); - Result = evalBinOp(state, BO_EQ, *LV, X, U->getType()); + if (std::optional<Loc> LV = V.getAs<Loc>()) { + Loc X = svalBuilder.makeNullWithType(Ex->getType()); + Result = evalBinOp(state, BO_EQ, *LV, X, U->getType()); } else if (Ex->getType()->isFloatingType()) { - // FIXME: handle floating point types. - Result = UnknownVal(); + // FIXME: handle floating point types. + Result = UnknownVal(); } else { - nonloc::ConcreteInt X(getBasicVals().getValue(0, Ex->getType())); - Result = evalBinOp(state, BO_EQ, V.castAs<NonLoc>(), X, - U->getType()); + nonloc::ConcreteInt X(getBasicVals().getValue(0, Ex->getType())); + Result = evalBinOp(state, BO_EQ, V.castAs<NonLoc>(), X, U->getType()); } state = state->BindExpr(U, LCtx, Result); break; } - Bldr.generateNode(U, *I, state); + Bldr.generateNode(U, N, state); break; } } @@ -1080,10 +1071,9 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U, ExplodedNodeSet Dst2; StmtNodeBuilder Bldr(Tmp, Dst2, *currBldrCtx); - for (ExplodedNodeSet::iterator I=Tmp.begin(), E=Tmp.end();I!=E;++I) { - - state = (*I)->getState(); - assert(LCtx == (*I)->getLocationContext()); + for (ExplodedNode *N : Tmp) { + state = N->getState(); + assert(LCtx == N->getLocationContext()); SVal V2_untested = state->getSVal(Ex, LCtx); // Propagate unknown and undefined values. @@ -1091,9 +1081,9 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U, state = state->BindExpr(U, LCtx, V2_untested); // Perform the store, so that the uninitialized value detection happens. - Bldr.takeNodes(*I); + Bldr.takeNodes(N); ExplodedNodeSet Dst3; - evalStore(Dst3, U, Ex, *I, state, loc, V2_untested); + evalStore(Dst3, U, Ex, N, state, loc, V2_untested); Bldr.addNodes(Dst3); continue; @@ -1159,9 +1149,9 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U, state = state->BindExpr(U, LCtx, U->isPostfix() ? V2 : Result); // Perform the store. - Bldr.takeNodes(*I); + Bldr.takeNodes(N); ExplodedNodeSet Dst3; - evalStore(Dst3, U, Ex, *I, state, loc, Result); + evalStore(Dst3, U, Ex, N, state, loc, Result); Bldr.addNodes(Dst3); } Dst.insert(Dst2); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index cab65687444b..504fd7f05e0f 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -10,15 +10,19 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" -#include "clang/Analysis/ConstructionContext.h" #include "clang/AST/DeclCXX.h" -#include "clang/AST/StmtCXX.h" #include "clang/AST/ParentMap.h" +#include "clang/AST/StmtCXX.h" +#include "clang/Analysis/ConstructionContext.h" #include "clang/Basic/PrettyStackTrace.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/Sequence.h" +#include <optional> using namespace clang; using namespace ento; @@ -57,61 +61,68 @@ void ExprEngine::performTrivialCopy(NodeBuilder &Bldr, ExplodedNode *Pred, AlwaysReturnsLValue = true; } - assert(ThisRD); - if (ThisRD->isEmpty()) { - // Do nothing for empty classes. Otherwise it'd retrieve an UnknownVal - // and bind it and RegionStore would think that the actual value - // in this region at this offset is unknown. - return; - } - const LocationContext *LCtx = Pred->getLocationContext(); + const Expr *CallExpr = Call.getOriginExpr(); ExplodedNodeSet Dst; Bldr.takeNodes(Pred); - SVal V = Call.getArgSVal(0); - - // If the value being copied is not unknown, load from its location to get - // an aggregate rvalue. - if (Optional<Loc> L = V.getAs<Loc>()) - V = Pred->getState()->getSVal(*L); - else - assert(V.isUnknownOrUndef()); + assert(ThisRD); + if (!ThisRD->isEmpty()) { + // Load the source value only for non-empty classes. + // Otherwise it'd retrieve an UnknownVal + // and bind it and RegionStore would think that the actual value + // in this region at this offset is unknown. + SVal V = Call.getArgSVal(0); - const Expr *CallExpr = Call.getOriginExpr(); - evalBind(Dst, CallExpr, Pred, ThisVal, V, true); + // If the value being copied is not unknown, load from its location to get + // an aggregate rvalue. + if (std::optional<Loc> L = V.getAs<Loc>()) + V = Pred->getState()->getSVal(*L); + else + assert(V.isUnknownOrUndef()); + evalBind(Dst, CallExpr, Pred, ThisVal, V, true); + } else { + Dst.Add(Pred); + } PostStmt PS(CallExpr, LCtx); - for (ExplodedNodeSet::iterator I = Dst.begin(), E = Dst.end(); - I != E; ++I) { - ProgramStateRef State = (*I)->getState(); + for (ExplodedNode *N : Dst) { + ProgramStateRef State = N->getState(); if (AlwaysReturnsLValue) State = State->BindExpr(CallExpr, LCtx, ThisVal); else State = bindReturnValue(Call, LCtx, State); - Bldr.generateNode(PS, State, *I); + Bldr.generateNode(PS, State, N); } } - -SVal ExprEngine::makeZeroElementRegion(ProgramStateRef State, SVal LValue, - QualType &Ty, bool &IsArray) { +SVal ExprEngine::makeElementRegion(ProgramStateRef State, SVal LValue, + QualType &Ty, bool &IsArray, unsigned Idx) { SValBuilder &SVB = State->getStateManager().getSValBuilder(); ASTContext &Ctx = SVB.getContext(); - while (const ArrayType *AT = Ctx.getAsArrayType(Ty)) { - Ty = AT->getElementType(); - LValue = State->getLValue(Ty, SVB.makeZeroArrayIndex(), LValue); + if (const ArrayType *AT = Ctx.getAsArrayType(Ty)) { + while (AT) { + Ty = AT->getElementType(); + AT = dyn_cast<ArrayType>(AT->getElementType()); + } + LValue = State->getLValue(Ty, SVB.makeArrayIndex(Idx), LValue); IsArray = true; } return LValue; } +// In case when the prvalue is returned from the function (kind is one of +// SimpleReturnedValueKind, CXX17ElidedCopyReturnedValueKind), then +// it's materialization happens in context of the caller. +// We pass BldrCtx explicitly, as currBldrCtx always refers to callee's context. SVal ExprEngine::computeObjectUnderConstruction( - const Expr *E, ProgramStateRef State, const LocationContext *LCtx, - const ConstructionContext *CC, EvalCallOptions &CallOpts) { + const Expr *E, ProgramStateRef State, const NodeBuilderContext *BldrCtx, + const LocationContext *LCtx, const ConstructionContext *CC, + EvalCallOptions &CallOpts, unsigned Idx) { + SValBuilder &SVB = getSValBuilder(); MemRegionManager &MRMgr = SVB.getRegionManager(); ASTContext &ACtx = SVB.getContext(); @@ -125,8 +136,8 @@ SVal ExprEngine::computeObjectUnderConstruction( const auto *DS = DSCC->getDeclStmt(); const auto *Var = cast<VarDecl>(DS->getSingleDecl()); QualType Ty = Var->getType(); - return makeZeroElementRegion(State, State->getLValue(Var, LCtx), Ty, - CallOpts.IsArrayCtorOrDtor); + return makeElementRegion(State, State->getLValue(Var, LCtx), Ty, + CallOpts.IsArrayCtorOrDtor, Idx); } case ConstructionContext::CXX17ElidedCopyConstructorInitializerKind: case ConstructionContext::SimpleConstructorInitializerKind: { @@ -158,8 +169,8 @@ SVal ExprEngine::computeObjectUnderConstruction( } QualType Ty = Field->getType(); - return makeZeroElementRegion(State, FieldVal, Ty, - CallOpts.IsArrayCtorOrDtor); + return makeElementRegion(State, FieldVal, Ty, CallOpts.IsArrayCtorOrDtor, + Idx); } case ConstructionContext::NewAllocatedObjectKind: { if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) { @@ -169,11 +180,16 @@ SVal ExprEngine::computeObjectUnderConstruction( if (const SubRegion *MR = dyn_cast_or_null<SubRegion>(V.getAsRegion())) { if (NE->isArray()) { - // TODO: In fact, we need to call the constructor for every - // allocated element, not just the first one! CallOpts.IsArrayCtorOrDtor = true; - return loc::MemRegionVal(getStoreManager().GetElementZeroRegion( - MR, NE->getType()->getPointeeType())); + + auto Ty = NE->getType()->getPointeeType(); + while (const auto *AT = getContext().getAsArrayType(Ty)) + Ty = AT->getElementType(); + + auto R = MRMgr.getElementRegion(Ty, svalBuilder.makeArrayIndex(Idx), + MR, SVB.getContext()); + + return loc::MemRegionVal(R); } return V; } @@ -203,8 +219,11 @@ SVal ExprEngine::computeObjectUnderConstruction( CallerLCtx = CallerLCtx->getParent(); assert(!isa<BlockInvocationContext>(CallerLCtx)); } + + NodeBuilderContext CallerBldrCtx(getCoreEngine(), + SFC->getCallSiteBlock(), CallerLCtx); return computeObjectUnderConstruction( - cast<Expr>(SFC->getCallSite()), State, CallerLCtx, + cast<Expr>(SFC->getCallSite()), State, &CallerBldrCtx, CallerLCtx, RTC->getConstructionContext(), CallOpts); } else { // We are on the top frame of the analysis. We do not know where is the @@ -244,7 +263,7 @@ SVal ExprEngine::computeObjectUnderConstruction( EvalCallOptions PreElideCallOpts = CallOpts; SVal V = computeObjectUnderConstruction( - TCC->getConstructorAfterElision(), State, LCtx, + TCC->getConstructorAfterElision(), State, BldrCtx, LCtx, TCC->getConstructionContextAfterElision(), CallOpts); // FIXME: This definition of "copy elision has not failed" is unreliable. @@ -257,7 +276,7 @@ SVal ExprEngine::computeObjectUnderConstruction( // a simple temporary. CallOpts = PreElideCallOpts; CallOpts.IsElidableCtorThatHasNotBeenElided = true; - LLVM_FALLTHROUGH; + [[fallthrough]]; } case ConstructionContext::SimpleTemporaryObjectKind: { const auto *TCC = cast<TemporaryObjectConstructionContext>(CC); @@ -266,7 +285,8 @@ SVal ExprEngine::computeObjectUnderConstruction( CallOpts.IsTemporaryCtorOrDtor = true; if (MTE) { if (const ValueDecl *VD = MTE->getExtendingDecl()) { - assert(MTE->getStorageDuration() != SD_FullExpression); + StorageDuration SD = MTE->getStorageDuration(); + assert(SD != SD_FullExpression); if (!VD->getType()->isReferenceType()) { // We're lifetime-extended by a surrounding aggregate. // Automatic destructors aren't quite working in this case @@ -275,15 +295,36 @@ SVal ExprEngine::computeObjectUnderConstruction( // the MaterializeTemporaryExpr? CallOpts.IsTemporaryLifetimeExtendedViaAggregate = true; } - } - if (MTE->getStorageDuration() == SD_Static || - MTE->getStorageDuration() == SD_Thread) - return loc::MemRegionVal(MRMgr.getCXXStaticTempObjectRegion(E)); + if (SD == SD_Static || SD == SD_Thread) + return loc::MemRegionVal( + MRMgr.getCXXStaticLifetimeExtendedObjectRegion(E, VD)); + + return loc::MemRegionVal( + MRMgr.getCXXLifetimeExtendedObjectRegion(E, VD, LCtx)); + } + assert(MTE->getStorageDuration() == SD_FullExpression); } return loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)); } + case ConstructionContext::LambdaCaptureKind: { + CallOpts.IsTemporaryCtorOrDtor = true; + + const auto *LCC = cast<LambdaCaptureConstructionContext>(CC); + + SVal Base = loc::MemRegionVal( + MRMgr.getCXXTempObjectRegion(LCC->getInitializer(), LCtx)); + + const auto *CE = dyn_cast_or_null<CXXConstructExpr>(E); + if (getIndexOfElementToConstruct(State, CE, LCtx)) { + CallOpts.IsArrayCtorOrDtor = true; + Base = State->getLValue(E->getType(), svalBuilder.makeArrayIndex(Idx), + Base); + } + + return Base; + } case ConstructionContext::ArgumentKind: { // Arguments are technically temporaries. CallOpts.IsTemporaryCtorOrDtor = true; @@ -293,13 +334,13 @@ SVal ExprEngine::computeObjectUnderConstruction( unsigned Idx = ACC->getIndex(); CallEventManager &CEMgr = getStateManager().getCallEventManager(); - auto getArgLoc = [&](CallEventRef<> Caller) -> Optional<SVal> { + auto getArgLoc = [&](CallEventRef<> Caller) -> std::optional<SVal> { const LocationContext *FutureSFC = - Caller->getCalleeStackFrame(currBldrCtx->blockCount()); + Caller->getCalleeStackFrame(BldrCtx->blockCount()); // Return early if we are unable to reliably foresee // the future stack frame. if (!FutureSFC) - return None; + return std::nullopt; // This should be equivalent to Caller->getDecl() for now, but // FutureSFC->getDecl() is likely to support better stuff (like @@ -308,37 +349,39 @@ SVal ExprEngine::computeObjectUnderConstruction( // FIXME: Support for variadic arguments is not implemented here yet. if (CallEvent::isVariadic(CalleeD)) - return None; + return std::nullopt; // 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 TypedValueRegion *TVR = Caller->getParameterLocation( - *Caller->getAdjustedParameterIndex(Idx), currBldrCtx->blockCount()); + *Caller->getAdjustedParameterIndex(Idx), BldrCtx->blockCount()); if (!TVR) - return None; + return std::nullopt; return loc::MemRegionVal(TVR); }; if (const auto *CE = dyn_cast<CallExpr>(E)) { - CallEventRef<> Caller = CEMgr.getSimpleCall(CE, State, LCtx); - if (Optional<SVal> V = getArgLoc(Caller)) + CallEventRef<> Caller = + CEMgr.getSimpleCall(CE, State, LCtx, getCFGElementRef()); + if (std::optional<SVal> V = getArgLoc(Caller)) return *V; else break; } 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 (Optional<SVal> V = getArgLoc(Caller)) + CallEventRef<> Caller = CEMgr.getCXXConstructorCall( + CCE, /*Target=*/nullptr, State, LCtx, getCFGElementRef()); + if (std::optional<SVal> V = getArgLoc(Caller)) return *V; else break; } else if (const auto *ME = dyn_cast<ObjCMessageExpr>(E)) { - CallEventRef<> Caller = CEMgr.getObjCMethodCall(ME, State, LCtx); - if (Optional<SVal> V = getArgLoc(Caller)) + CallEventRef<> Caller = + CEMgr.getObjCMethodCall(ME, State, LCtx, getCFGElementRef()); + if (std::optional<SVal> V = getArgLoc(Caller)) return *V; else break; @@ -432,7 +475,7 @@ ProgramStateRef ExprEngine::updateObjectsUnderConstruction( } // If we decided not to elide the constructor, proceed as if // it's a simple temporary. - LLVM_FALLTHROUGH; + [[fallthrough]]; } case ConstructionContext::SimpleTemporaryObjectKind: { const auto *TCC = cast<TemporaryObjectConstructionContext>(CC); @@ -444,6 +487,17 @@ ProgramStateRef ExprEngine::updateObjectsUnderConstruction( return State; } + case ConstructionContext::LambdaCaptureKind: { + const auto *LCC = cast<LambdaCaptureConstructionContext>(CC); + + // If we capture and array, we want to store the super region, not a + // sub-region. + if (const auto *EL = dyn_cast_or_null<ElementRegion>(V.getAsRegion())) + V = loc::MemRegionVal(EL->getSuperRegion()); + + return addObjectUnderConstruction( + State, {LCC->getLambdaExpr(), LCC->getIndex()}, LCtx, V); + } case ConstructionContext::ArgumentKind: { const auto *ACC = cast<ArgumentConstructionContext>(CC); if (const auto *BTE = ACC->getCXXBindTemporaryExpr()) @@ -456,6 +510,75 @@ ProgramStateRef ExprEngine::updateObjectsUnderConstruction( llvm_unreachable("Unhandled construction context!"); } +static ProgramStateRef +bindRequiredArrayElementToEnvironment(ProgramStateRef State, + const ArrayInitLoopExpr *AILE, + const LocationContext *LCtx, SVal Idx) { + // The ctor in this case is guaranteed to be a copy ctor, otherwise we hit a + // compile time error. + // + // -ArrayInitLoopExpr <-- we're here + // |-OpaqueValueExpr + // | `-DeclRefExpr <-- match this + // `-CXXConstructExpr + // `-ImplicitCastExpr + // `-ArraySubscriptExpr + // |-ImplicitCastExpr + // | `-OpaqueValueExpr + // | `-DeclRefExpr + // `-ArrayInitIndexExpr + // + // The resulting expression might look like the one below in an implicit + // copy/move ctor. + // + // ArrayInitLoopExpr <-- we're here + // |-OpaqueValueExpr + // | `-MemberExpr <-- match this + // | (`-CXXStaticCastExpr) <-- move ctor only + // | `-DeclRefExpr + // `-CXXConstructExpr + // `-ArraySubscriptExpr + // |-ImplicitCastExpr + // | `-OpaqueValueExpr + // | `-MemberExpr + // | `-DeclRefExpr + // `-ArrayInitIndexExpr + // + // The resulting expression for a multidimensional array. + // ArrayInitLoopExpr <-- we're here + // |-OpaqueValueExpr + // | `-DeclRefExpr <-- match this + // `-ArrayInitLoopExpr + // |-OpaqueValueExpr + // | `-ArraySubscriptExpr + // | |-ImplicitCastExpr + // | | `-OpaqueValueExpr + // | | `-DeclRefExpr + // | `-ArrayInitIndexExpr + // `-CXXConstructExpr <-- extract this + // ` ... + + const auto *OVESrc = AILE->getCommonExpr()->getSourceExpr(); + + // HACK: There is no way we can put the index of the array element into the + // CFG unless we unroll the loop, so we manually select and bind the required + // parameter to the environment. + const auto *CE = + cast<CXXConstructExpr>(extractElementInitializerFromNestedAILE(AILE)); + + SVal Base = UnknownVal(); + if (const auto *ME = dyn_cast<MemberExpr>(OVESrc)) + Base = State->getSVal(ME, LCtx); + else if (const auto *DRE = dyn_cast<DeclRefExpr>(OVESrc)) + Base = State->getLValue(cast<VarDecl>(DRE->getDecl()), LCtx); + else + llvm_unreachable("ArrayInitLoopExpr contains unexpected source expression"); + + SVal NthElem = State->getLValue(CE->getType(), Idx, Base); + + return State->BindExpr(CE->getArg(0), LCtx, NthElem); +} + void ExprEngine::handleConstructor(const Expr *E, ExplodedNode *Pred, ExplodedNodeSet &destNodes) { @@ -469,57 +592,95 @@ void ExprEngine::handleConstructor(const Expr *E, SVal Target = UnknownVal(); if (CE) { - if (Optional<SVal> ElidedTarget = + if (std::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; + // We've previously modeled an elidable constructor by pretending that + // it in fact constructs into the correct target. This constructor can + // therefore be skipped. + Target = *ElidedTarget; + StmtNodeBuilder Bldr(Pred, destNodes, *currBldrCtx); + State = finishObjectConstruction(State, CE, LCtx); + if (auto L = Target.getAs<Loc>()) + State = State->BindExpr(CE, LCtx, State->getSVal(*L, CE->getType())); + Bldr.generateNode(CE, Pred, State); + return; } } - // FIXME: Handle arrays, which run the same constructor for every element. - // For now, we just run the first constructor (which should still invalidate - // the entire array). - EvalCallOptions CallOpts; auto C = getCurrentCFGElement().getAs<CFGConstructor>(); assert(C || getCurrentCFGElement().getAs<CFGStmt>()); const ConstructionContext *CC = C ? C->getConstructionContext() : nullptr; - const CXXConstructExpr::ConstructionKind CK = + const CXXConstructionKind CK = CE ? CE->getConstructionKind() : CIE->getConstructionKind(); switch (CK) { - case CXXConstructExpr::CK_Complete: { + case CXXConstructionKind::Complete: { // Inherited constructors are always base class constructors. assert(CE && !CIE && "A complete constructor is inherited?!"); + // If the ctor is part of an ArrayInitLoopExpr, we want to handle it + // differently. + auto *AILE = CC ? CC->getArrayInitLoop() : nullptr; + + unsigned Idx = 0; + if (CE->getType()->isArrayType() || AILE) { + + auto isZeroSizeArray = [&] { + uint64_t Size = 1; + + if (const auto *CAT = dyn_cast<ConstantArrayType>(CE->getType())) + Size = getContext().getConstantArrayElementCount(CAT); + else if (AILE) + Size = getContext().getArrayInitLoopExprElementCount(AILE); + + return Size == 0; + }; + + // No element construction will happen in a 0 size array. + if (isZeroSizeArray()) { + StmtNodeBuilder Bldr(Pred, destNodes, *currBldrCtx); + static SimpleProgramPointTag T{"ExprEngine", + "Skipping 0 size array construction"}; + Bldr.generateNode(CE, Pred, State, &T); + return; + } + + Idx = getIndexOfElementToConstruct(State, CE, LCtx).value_or(0u); + State = setIndexOfElementToConstruct(State, CE, LCtx, Idx + 1); + } + + if (AILE) { + // Only set this once even though we loop through it multiple times. + if (!getPendingInitLoop(State, CE, LCtx)) + State = setPendingInitLoop( + State, CE, LCtx, + getContext().getArrayInitLoopExprElementCount(AILE)); + + State = bindRequiredArrayElementToEnvironment( + State, AILE, LCtx, svalBuilder.makeArrayIndex(Idx)); + } + // The target region is found from construction context. - std::tie(State, Target) = - handleConstructionContext(CE, State, LCtx, CC, CallOpts); + std::tie(State, Target) = handleConstructionContext( + CE, State, currBldrCtx, LCtx, CC, CallOpts, Idx); break; } - case CXXConstructExpr::CK_VirtualBase: { + case CXXConstructionKind::VirtualBase: { // Make sure we are not calling virtual base class initializers twice. // Only the most-derived object should initialize virtual base classes. const auto *OuterCtor = dyn_cast_or_null<CXXConstructExpr>( LCtx->getStackFrame()->getCallSite()); assert( (!OuterCtor || - OuterCtor->getConstructionKind() == CXXConstructExpr::CK_Complete || - OuterCtor->getConstructionKind() == CXXConstructExpr::CK_Delegating) && + OuterCtor->getConstructionKind() == CXXConstructionKind::Complete || + OuterCtor->getConstructionKind() == CXXConstructionKind::Delegating) && ("This virtual base should have already been initialized by " "the most derived class!")); (void)OuterCtor; - LLVM_FALLTHROUGH; + [[fallthrough]]; } - case CXXConstructExpr::CK_NonVirtualBase: + case CXXConstructionKind::NonVirtualBase: // In C++17, classes with non-virtual bases may be aggregates, so they would // be initialized as aggregates without a constructor call, so we may have // a base class constructed directly into an initializer list without @@ -531,24 +692,24 @@ void ExprEngine::handleConstructor(const Expr *E, // 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(E))) { + if (isa_and_nonnull<InitListExpr>(LCtx->getParentMap().getParent(E))) { MemRegionManager &MRMgr = getSValBuilder().getRegionManager(); Target = loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)); CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true; break; } - LLVM_FALLTHROUGH; - case CXXConstructExpr::CK_Delegating: { + [[fallthrough]]; + case CXXConstructionKind::Delegating: { const CXXMethodDecl *CurCtor = cast<CXXMethodDecl>(LCtx->getDecl()); Loc ThisPtr = getSValBuilder().getCXXThis(CurCtor, LCtx->getStackFrame()); SVal ThisVal = State->getSVal(ThisPtr); - if (CK == CXXConstructExpr::CK_Delegating) { + if (CK == CXXConstructionKind::Delegating) { Target = ThisVal; } else { // Cast to the base type. - bool IsVirtual = (CK == CXXConstructExpr::CK_VirtualBase); + bool IsVirtual = (CK == CXXConstructionKind::VirtualBase); SVal BaseVal = getStoreManager().evalDerivedToBase(ThisVal, E->getType(), IsVirtual); Target = BaseVal; @@ -573,9 +734,9 @@ void ExprEngine::handleConstructor(const Expr *E, CallEventManager &CEMgr = getStateManager().getCallEventManager(); CallEventRef<> Call = CIE ? (CallEventRef<>)CEMgr.getCXXInheritedConstructorCall( - CIE, TargetRegion, State, LCtx) + CIE, TargetRegion, State, LCtx, getCFGElementRef()) : (CallEventRef<>)CEMgr.getCXXConstructorCall( - CE, TargetRegion, State, LCtx); + CE, TargetRegion, State, LCtx, getCFGElementRef()); ExplodedNodeSet DstPreVisit; getCheckerManager().runCheckersForPreStmt(DstPreVisit, Pred, E, *this); @@ -584,10 +745,8 @@ void ExprEngine::handleConstructor(const Expr *E, 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(); - I != E; ++I) { - ProgramStateRef State = (*I)->getState(); + for (ExplodedNode *N : DstPreVisit) { + ProgramStateRef State = N->getState(); if (CE->requiresZeroInitialization()) { // FIXME: Once we properly handle constructors in new-expressions, we'll // need to invalidate the region before setting a default value, to make @@ -604,7 +763,7 @@ void ExprEngine::handleConstructor(const Expr *E, State = State->bindDefaultZero(Target, LCtx); } - Bldr.generateNode(CE, *I, State, /*tag=*/nullptr, + Bldr.generateNode(CE, N, State, /*tag=*/nullptr, ProgramPoint::PreStmtKind); } } else { @@ -622,14 +781,12 @@ void ExprEngine::handleConstructor(const Expr *E, !CallOpts.IsArrayCtorOrDtor) { StmtNodeBuilder Bldr(DstPreCall, DstEvaluated, *currBldrCtx); // FIXME: Handle other kinds of trivial constructors as well. - for (ExplodedNodeSet::iterator I = DstPreCall.begin(), E = DstPreCall.end(); - I != E; ++I) - performTrivialCopy(Bldr, *I, *Call); + for (ExplodedNode *N : DstPreCall) + performTrivialCopy(Bldr, N, *Call); } else { - for (ExplodedNodeSet::iterator I = DstPreCall.begin(), E = DstPreCall.end(); - I != E; ++I) - getCheckerManager().runCheckersForEvalCall(DstEvaluated, *I, *Call, *this, + for (ExplodedNode *N : DstPreCall) + getCheckerManager().runCheckersForEvalCall(DstEvaluated, N, *Call, *this, CallOpts); } @@ -644,7 +801,8 @@ void ExprEngine::handleConstructor(const Expr *E, StmtNodeBuilder Bldr(DstEvaluated, DstEvaluatedPostProcessed, *currBldrCtx); const AnalysisDeclContext *ADC = LCtx->getAnalysisDeclContext(); if (!ADC->getCFGBuildOptions().AddTemporaryDtors) { - if (llvm::isa_and_nonnull<CXXTempObjectRegion>(TargetRegion) && + if (llvm::isa_and_nonnull<CXXTempObjectRegion, + CXXLifetimeExtendedObjectRegion>(TargetRegion) && cast<CXXConstructorDecl>(Call->getDecl()) ->getParent() ->isAnyDestructorNoReturn()) { @@ -716,7 +874,8 @@ void ExprEngine::VisitCXXDestructor(QualType ObjectType, // it would interrupt the analysis instead. static SimpleProgramPointTag T("ExprEngine", "SkipInvalidDestructor"); // FIXME: PostImplicitCall with a null decl may crash elsewhere anyway. - PostImplicitCall PP(/*Decl=*/nullptr, S->getEndLoc(), LCtx, &T); + PostImplicitCall PP(/*Decl=*/nullptr, S->getEndLoc(), LCtx, + getCFGElementRef(), &T); NodeBuilder Bldr(Pred, Dst, *currBldrCtx); Bldr.generateNode(PP, Pred->getState(), Pred); return; @@ -741,8 +900,8 @@ void ExprEngine::VisitCXXDestructor(QualType ObjectType, } CallEventManager &CEMgr = getStateManager().getCallEventManager(); - CallEventRef<CXXDestructorCall> Call = - CEMgr.getCXXDestructorCall(DtorDecl, S, Dest, IsBaseDtor, State, LCtx); + CallEventRef<CXXDestructorCall> Call = CEMgr.getCXXDestructorCall( + DtorDecl, S, Dest, IsBaseDtor, State, LCtx, getCFGElementRef()); PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(), Call->getSourceRange().getBegin(), @@ -754,9 +913,8 @@ void ExprEngine::VisitCXXDestructor(QualType ObjectType, ExplodedNodeSet DstInvalidated; StmtNodeBuilder Bldr(DstPreCall, DstInvalidated, *currBldrCtx); - for (ExplodedNodeSet::iterator I = DstPreCall.begin(), E = DstPreCall.end(); - I != E; ++I) - defaultEvalCall(Bldr, *I, *Call, CallOpts); + for (ExplodedNode *N : DstPreCall) + defaultEvalCall(Bldr, N, *Call, CallOpts); getCheckerManager().runCheckersForPostCall(Dst, DstInvalidated, *Call, *this); @@ -772,7 +930,7 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE, "Error evaluating New Allocator Call"); CallEventManager &CEMgr = getStateManager().getCallEventManager(); CallEventRef<CXXAllocatorCall> Call = - CEMgr.getCXXAllocatorCall(CNE, State, LCtx); + CEMgr.getCXXAllocatorCall(CNE, State, LCtx, getCFGElementRef()); ExplodedNodeSet DstPreCall; getCheckerManager().runCheckersForPreCall(DstPreCall, Pred, @@ -802,6 +960,11 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE, // skip it for now. ProgramStateRef State = I->getState(); SVal RetVal = State->getSVal(CNE, LCtx); + // [basic.stc.dynamic.allocation] (on the return value of an allocation + // function): + // "The order, contiguity, and initial value of storage allocated by + // successive calls to an allocation function are unspecified." + State = State->bindDefaultInitial(RetVal, UndefinedVal{}, LCtx); // If this allocation function is not declared as non-throwing, failures // /must/ be signalled by exceptions, and thus the return value will never @@ -865,7 +1028,7 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, CallEventManager &CEMgr = getStateManager().getCallEventManager(); CallEventRef<CXXAllocatorCall> Call = - CEMgr.getCXXAllocatorCall(CNE, State, LCtx); + CEMgr.getCXXAllocatorCall(CNE, State, LCtx, getCFGElementRef()); if (!AMgr.getAnalyzerOptions().MayInlineCXXAllocator) { // Invalidate placement args. @@ -883,13 +1046,10 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, // where new can return NULL. If we end up supporting that option, we can // consider adding a check for it here. // C++11 [basic.stc.dynamic.allocation]p3. - if (FD) { - QualType Ty = FD->getType(); - if (const auto *ProtoType = Ty->getAs<FunctionProtoType>()) - if (!ProtoType->isNothrow()) - if (auto dSymVal = symVal.getAs<DefinedOrUnknownSVal>()) - State = State->assume(*dSymVal, true); - } + if (const auto *ProtoType = FD->getType()->getAs<FunctionProtoType>()) + if (!ProtoType->isNothrow()) + if (auto dSymVal = symVal.getAs<DefinedOrUnknownSVal>()) + State = State->assume(*dSymVal, true); } StmtNodeBuilder Bldr(Pred, Dst, *currBldrCtx); @@ -897,14 +1057,39 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, SVal Result = symVal; if (CNE->isArray()) { - // FIXME: allocating an array requires simulating the constructors. - // For now, just return a symbolicated region. + if (const auto *NewReg = cast_or_null<SubRegion>(symVal.getAsRegion())) { - QualType ObjTy = CNE->getType()->getPointeeType(); + // If each element is initialized by their default constructor, the field + // values are properly placed inside the required region, however if an + // initializer list is used, this doesn't happen automatically. + auto *Init = CNE->getInitializer(); + bool isInitList = isa_and_nonnull<InitListExpr>(Init); + + QualType ObjTy = + isInitList ? Init->getType() : CNE->getType()->getPointeeType(); const ElementRegion *EleReg = - getStoreManager().GetElementZeroRegion(NewReg, ObjTy); + MRMgr.getElementRegion(ObjTy, svalBuilder.makeArrayIndex(0), NewReg, + svalBuilder.getContext()); Result = loc::MemRegionVal(EleReg); + + // If the array is list initialized, we bind the initializer list to the + // memory region here, otherwise we would lose it. + if (isInitList) { + Bldr.takeNodes(Pred); + Pred = Bldr.generateNode(CNE, Pred, State); + + SVal V = State->getSVal(Init, LCtx); + ExplodedNodeSet evaluated; + evalBind(evaluated, CNE, Pred, Result, V, true); + + Bldr.takeNodes(Pred); + Bldr.addNodes(evaluated); + + Pred = *evaluated.begin(); + State = Pred->getState(); + } } + State = State->BindExpr(CNE, Pred->getLocationContext(), Result); Bldr.generateNode(CNE, Pred, State); return; @@ -914,7 +1099,7 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, // CXXNewExpr, we need to make sure that the constructed object is not // immediately invalidated here. (The placement call should happen before // the constructor call anyway.) - if (FD && FD->isReservedGlobalPlacementOperator()) { + if (FD->isReservedGlobalPlacementOperator()) { // Non-array placement new should always return the placement location. SVal PlacementLoc = State->getSVal(CNE->getPlacementArg(0), LCtx); Result = svalBuilder.evalCast(PlacementLoc, CNE->getType(), @@ -944,12 +1129,21 @@ void ExprEngine::VisitCXXDeleteExpr(const CXXDeleteExpr *CDE, CallEventManager &CEMgr = getStateManager().getCallEventManager(); CallEventRef<CXXDeallocatorCall> Call = CEMgr.getCXXDeallocatorCall( - CDE, Pred->getState(), Pred->getLocationContext()); + CDE, Pred->getState(), Pred->getLocationContext(), getCFGElementRef()); ExplodedNodeSet DstPreCall; getCheckerManager().runCheckersForPreCall(DstPreCall, Pred, *Call, *this); + ExplodedNodeSet DstPostCall; - getCheckerManager().runCheckersForPostCall(Dst, DstPreCall, *Call, *this); + if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) { + StmtNodeBuilder Bldr(DstPreCall, DstPostCall, *currBldrCtx); + for (ExplodedNode *I : DstPreCall) { + defaultEvalCall(Bldr, I, *Call); + } + } else { + DstPostCall = DstPreCall; + } + getCheckerManager().runCheckersForPostCall(Dst, DstPostCall, *Call, *this); } void ExprEngine::VisitCXXCatchStmt(const CXXCatchStmt *CS, ExplodedNode *Pred, @@ -999,19 +1193,41 @@ void ExprEngine::VisitLambdaExpr(const LambdaExpr *LE, ExplodedNode *Pred, // If we created a new MemRegion for the lambda, we should explicitly bind // the captures. - CXXRecordDecl::field_iterator CurField = LE->getLambdaClass()->field_begin(); - for (LambdaExpr::const_capture_init_iterator i = LE->capture_init_begin(), - e = LE->capture_init_end(); - i != e; ++i, ++CurField) { - FieldDecl *FieldForCapture = *CurField; + for (auto const [Idx, FieldForCapture, InitExpr] : + llvm::zip(llvm::seq<unsigned>(0, -1), LE->getLambdaClass()->fields(), + LE->capture_inits())) { SVal FieldLoc = State->getLValue(FieldForCapture, V); SVal InitVal; if (!FieldForCapture->hasCapturedVLAType()) { - Expr *InitExpr = *i; assert(InitExpr && "Capture missing initialization expression"); - InitVal = State->getSVal(InitExpr, LocCtxt); + + // Capturing a 0 length array is a no-op, so we ignore it to get a more + // accurate analysis. If it's not ignored, it would set the default + // binding of the lambda to 'Unknown', which can lead to falsely detecting + // 'Uninitialized' values as 'Unknown' and not reporting a warning. + const auto FTy = FieldForCapture->getType(); + if (FTy->isConstantArrayType() && + getContext().getConstantArrayElementCount( + getContext().getAsConstantArrayType(FTy)) == 0) + continue; + + // With C++17 copy elision the InitExpr can be anything, so instead of + // pattern matching all cases, we simple check if the current field is + // under construction or not, regardless what it's InitExpr is. + if (const auto OUC = + getObjectUnderConstruction(State, {LE, Idx}, LocCtxt)) { + InitVal = State->getSVal(OUC->getAsRegion()); + + State = finishObjectConstruction(State, {LE, Idx}, LocCtxt); + } else + InitVal = State->getSVal(InitExpr, LocCtxt); + } else { + + assert(!getObjectUnderConstruction(State, {LE, Idx}, LocCtxt) && + "VLA capture by value is a compile time error!"); + // The field stores the length of a captured variable-length array. // These captures don't have initialization expressions; instead we // get the length from the VLAType size expression. diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index e6918e071a4f..4755b6bfa6dc 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -25,6 +25,7 @@ #include "llvm/Support/Casting.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/SaveAndRestore.h" +#include <optional> using namespace clang; using namespace ento; @@ -85,10 +86,10 @@ static std::pair<const Stmt*, const ProgramPoint &PP = Node->getLocation(); if (PP.getStackFrame() == SF) { - if (Optional<StmtPoint> SP = PP.getAs<StmtPoint>()) { + if (std::optional<StmtPoint> SP = PP.getAs<StmtPoint>()) { S = SP->getStmt(); break; - } else if (Optional<CallExitEnd> CEE = PP.getAs<CallExitEnd>()) { + } else if (std::optional<CallExitEnd> CEE = PP.getAs<CallExitEnd>()) { S = CEE->getCalleeContext()->getCallSite(); if (S) break; @@ -96,17 +97,17 @@ static std::pair<const Stmt*, // If there is no statement, this is an implicitly-generated call. // We'll walk backwards over it and then continue the loop to find // an actual statement. - Optional<CallEnter> CE; + std::optional<CallEnter> CE; do { Node = Node->getFirstPred(); CE = Node->getLocationAs<CallEnter>(); } while (!CE || CE->getCalleeContext() != CEE->getCalleeContext()); // Continue searching the graph. - } else if (Optional<BlockEdge> BE = PP.getAs<BlockEdge>()) { + } else if (std::optional<BlockEdge> BE = PP.getAs<BlockEdge>()) { Blk = BE->getSrc(); } - } else if (Optional<CallEnter> CE = PP.getAs<CallEnter>()) { + } else if (std::optional<CallEnter> CE = PP.getAs<CallEnter>()) { // If we reached the CallEnter for this function, it has no statements. if (CE->getCalleeContext() == SF) break; @@ -129,7 +130,7 @@ static std::pair<const Stmt*, static SVal adjustReturnValue(SVal V, QualType ExpectedTy, QualType ActualTy, StoreManager &StoreMgr) { // For now, the only adjustments we handle apply only to locations. - if (!V.getAs<Loc>()) + if (!isa<Loc>(V)) return V; // If the types already match, don't do any unnecessary work. @@ -195,6 +196,53 @@ static bool wasDifferentDeclUsedForInlining(CallEventRef<> Call, return RuntimeCallee->getCanonicalDecl() != StaticDecl->getCanonicalDecl(); } +// Returns the number of elements in the array currently being destructed. +// If the element count is not found 0 will be returned. +static unsigned getElementCountOfArrayBeingDestructed( + const CallEvent &Call, const ProgramStateRef State, SValBuilder &SVB) { + assert(isa<CXXDestructorCall>(Call) && + "The call event is not a destructor call!"); + + const auto &DtorCall = cast<CXXDestructorCall>(Call); + + auto ThisVal = DtorCall.getCXXThisVal(); + + if (auto ThisElementRegion = dyn_cast<ElementRegion>(ThisVal.getAsRegion())) { + auto ArrayRegion = ThisElementRegion->getAsArrayOffset().getRegion(); + auto ElementType = ThisElementRegion->getElementType(); + + auto ElementCount = + getDynamicElementCount(State, ArrayRegion, SVB, ElementType); + + if (!ElementCount.isConstant()) + return 0; + + return ElementCount.getAsInteger()->getLimitedValue(); + } + + return 0; +} + +ProgramStateRef ExprEngine::removeStateTraitsUsedForArrayEvaluation( + ProgramStateRef State, const CXXConstructExpr *E, + const LocationContext *LCtx) { + + assert(LCtx && "Location context must be provided!"); + + if (E) { + if (getPendingInitLoop(State, E, LCtx)) + State = removePendingInitLoop(State, E, LCtx); + + if (getIndexOfElementToConstruct(State, E, LCtx)) + State = removeIndexOfElementToConstruct(State, E, LCtx); + } + + if (getPendingArrayDestruction(State, LCtx)) + State = removePendingArrayDestruction(State, LCtx); + + return State; +} + /// The call exit is simulated with a sequence of nodes, which occur between /// CallExitBegin and CallExitEnd. The following operations occur between the /// two program points: @@ -227,6 +275,23 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { // Step 2: generate node with bound return value: CEBNode -> BindedRetNode. + // If this variable is set to 'true' the analyzer will evaluate the call + // statement we are about to exit again, instead of continuing the execution + // from the statement after the call. This is useful for non-POD type array + // construction where the CXXConstructExpr is referenced only once in the CFG, + // but we want to evaluate it as many times as many elements the array has. + bool ShouldRepeatCall = false; + + if (const auto *DtorDecl = + dyn_cast_or_null<CXXDestructorDecl>(Call->getDecl())) { + if (auto Idx = getPendingArrayDestruction(state, callerCtx)) { + ShouldRepeatCall = *Idx > 0; + + auto ThisVal = svalBuilder.getCXXThis(DtorDecl->getParent(), calleeCtx); + state = state->killBinding(ThisVal); + } + } + // If the callee returns an expression, bind its value to CallExpr. if (CE) { if (const ReturnStmt *RS = dyn_cast_or_null<ReturnStmt>(LastSt)) { @@ -255,6 +320,8 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { SVal ThisV = state->getSVal(This); ThisV = state->getSVal(ThisV.castAs<Loc>()); state = state->BindExpr(CCE, callerCtx, ThisV); + + ShouldRepeatCall = shouldRepeatCtorCall(state, CCE, callerCtx); } if (const auto *CNE = dyn_cast<CXXNewExpr>(CE)) { @@ -273,6 +340,11 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { } } + if (!ShouldRepeatCall) { + state = removeStateTraitsUsedForArrayEvaluation( + state, dyn_cast_or_null<CXXConstructExpr>(CE), callerCtx); + } + // Step 3: BindedRetNode -> CleanedNodes // If we can find a statement and a block in the inlined function, run remove // dead bindings before returning from the call. This is important to ensure @@ -302,17 +374,15 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { CleanedNodes.Add(CEBNode); } - for (ExplodedNodeSet::iterator I = CleanedNodes.begin(), - E = CleanedNodes.end(); I != E; ++I) { - + for (ExplodedNode *N : CleanedNodes) { // Step 4: Generate the CallExit and leave the callee's context. // CleanedNodes -> CEENode CallExitEnd Loc(calleeCtx, callerCtx); bool isNew; - ProgramStateRef CEEState = (*I == CEBNode) ? state : (*I)->getState(); + ProgramStateRef CEEState = (N == CEBNode) ? state : N->getState(); ExplodedNode *CEENode = G.getNode(Loc, CEEState, false, &isNew); - CEENode->addPredecessor(*I, G); + CEENode->addPredecessor(N, G); if (!isNew) return; @@ -320,9 +390,8 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { // result onto the work list. // CEENode -> Dst -> WorkList NodeBuilderContext Ctx(Engine, calleeCtx->getCallSiteBlock(), CEENode); - SaveAndRestore<const NodeBuilderContext*> NBCSave(currBldrCtx, - &Ctx); - SaveAndRestore<unsigned> CBISave(currStmtIdx, calleeCtx->getIndex()); + SaveAndRestore<const NodeBuilderContext *> NBCSave(currBldrCtx, &Ctx); + SaveAndRestore CBISave(currStmtIdx, calleeCtx->getIndex()); CallEventRef<> UpdatedCall = Call.cloneWithState(CEEState); @@ -358,9 +427,10 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { // Enqueue the next element in the block. for (ExplodedNodeSet::iterator PSI = Dst.begin(), PSE = Dst.end(); - PSI != PSE; ++PSI) { - Engine.getWorkList()->enqueue(*PSI, calleeCtx->getCallSiteBlock(), - calleeCtx->getIndex()+1); + PSI != PSE; ++PSI) { + unsigned Idx = calleeCtx->getIndex() + (ShouldRepeatCall ? 0 : 1); + + Engine.getWorkList()->enqueue(*PSI, calleeCtx->getCallSiteBlock(), Idx); } } } @@ -427,10 +497,39 @@ namespace { REGISTER_MAP_WITH_PROGRAMSTATE(DynamicDispatchBifurcationMap, const MemRegion *, unsigned) +REGISTER_TRAIT_WITH_PROGRAMSTATE(CTUDispatchBifurcation, bool) + +void ExprEngine::ctuBifurcate(const CallEvent &Call, const Decl *D, + NodeBuilder &Bldr, ExplodedNode *Pred, + ProgramStateRef State) { + ProgramStateRef ConservativeEvalState = nullptr; + if (Call.isForeign() && !isSecondPhaseCTU()) { + const auto IK = AMgr.options.getCTUPhase1Inlining(); + const bool DoInline = IK == CTUPhase1InliningKind::All || + (IK == CTUPhase1InliningKind::Small && + isSmall(AMgr.getAnalysisDeclContext(D))); + if (DoInline) { + inlineCall(Engine.getWorkList(), Call, D, Bldr, Pred, State); + return; + } + const bool BState = State->get<CTUDispatchBifurcation>(); + if (!BState) { // This is the first time we see this foreign function. + // Enqueue it to be analyzed in the second (ctu) phase. + inlineCall(Engine.getCTUWorkList(), Call, D, Bldr, Pred, State); + // Conservatively evaluate in the first phase. + ConservativeEvalState = State->set<CTUDispatchBifurcation>(true); + conservativeEvalCall(Call, Bldr, Pred, ConservativeEvalState); + } else { + conservativeEvalCall(Call, Bldr, Pred, State); + } + return; + } + inlineCall(Engine.getWorkList(), Call, D, Bldr, Pred, State); +} -bool ExprEngine::inlineCall(const CallEvent &Call, const Decl *D, - NodeBuilder &Bldr, ExplodedNode *Pred, - ProgramStateRef State) { +void ExprEngine::inlineCall(WorkList *WList, const CallEvent &Call, + const Decl *D, NodeBuilder &Bldr, + ExplodedNode *Pred, ProgramStateRef State) { assert(D); const LocationContext *CurLC = Pred->getLocationContext(); @@ -465,7 +564,7 @@ bool ExprEngine::inlineCall(const CallEvent &Call, const Decl *D, if (ExplodedNode *N = G.getNode(Loc, State, false, &isNew)) { N->addPredecessor(Pred, G); if (isNew) - Engine.getWorkList()->enqueue(N); + WList->enqueue(N); } // If we decided to inline the call, the successor has been manually @@ -475,11 +574,17 @@ bool ExprEngine::inlineCall(const CallEvent &Call, const Decl *D, NumInlinedCalls++; Engine.FunctionSummaries->bumpNumTimesInlined(D); - // Mark the decl as visited. - if (VisitedCallees) - VisitedCallees->insert(D); - - return true; + // Do not mark as visited in the 2nd run (CTUWList), so the function will + // be visited as top-level, this way we won't loose reports in non-ctu + // mode. Considering the case when a function in a foreign TU calls back + // into the main TU. + // Note, during the 1st run, it doesn't matter if we mark the foreign + // functions as visited (or not) because they can never appear as a top level + // function in the main TU. + if (!isSecondPhaseCTU()) + // Mark the decl as visited. + if (VisitedCallees) + VisitedCallees->insert(D); } static ProgramStateRef getInlineFailedState(ProgramStateRef State, @@ -503,15 +608,14 @@ void ExprEngine::VisitCallExpr(const CallExpr *CE, ExplodedNode *Pred, // Get the call in its initial state. We use this as a template to perform // all the checks. CallEventManager &CEMgr = getStateManager().getCallEventManager(); - CallEventRef<> CallTemplate - = CEMgr.getSimpleCall(CE, Pred->getState(), Pred->getLocationContext()); + CallEventRef<> CallTemplate = CEMgr.getSimpleCall( + CE, Pred->getState(), Pred->getLocationContext(), getCFGElementRef()); // Evaluate the function call. We try each of the checkers // to see if the can evaluate the function call. ExplodedNodeSet dstCallEvaluated; - for (ExplodedNodeSet::iterator I = dstPreVisit.begin(), E = dstPreVisit.end(); - I != E; ++I) { - evalCall(dstCallEvaluated, *I, *CallTemplate); + for (ExplodedNode *N : dstPreVisit) { + evalCall(dstCallEvaluated, N, *CallTemplate); } // Finally, perform the post-condition check of the CallExpr and store @@ -533,8 +637,7 @@ ProgramStateRef ExprEngine::finishArgumentConstruction(ProgramStateRef State, const LocationContext *LC = Call.getLocationContext(); for (unsigned CallI = 0, CallN = Call.getNumArgs(); CallI != CallN; ++CallI) { unsigned I = Call.getASTArgumentIndex(CallI); - if (Optional<SVal> V = - getObjectUnderConstruction(State, {E, I}, LC)) { + if (std::optional<SVal> V = getObjectUnderConstruction(State, {E, I}, LC)) { SVal VV = *V; (void)VV; assert(cast<VarRegion>(VV.castAs<loc::MemRegionVal>().getRegion()) @@ -667,9 +770,9 @@ ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call, SVal Target; assert(RTC->getStmt() == Call.getOriginExpr()); EvalCallOptions CallOpts; // FIXME: We won't really need those. - std::tie(State, Target) = - handleConstructionContext(Call.getOriginExpr(), State, LCtx, - RTC->getConstructionContext(), CallOpts); + std::tie(State, Target) = handleConstructionContext( + Call.getOriginExpr(), State, currBldrCtx, LCtx, + RTC->getConstructionContext(), CallOpts); const MemRegion *TargetR = Target.getAsRegion(); assert(TargetR); // Invalidate the region so that it didn't look uninitialized. If this is @@ -697,7 +800,7 @@ ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call, // Store the extent of the allocated object(s). SVal ElementCount; - if (const Expr *SizeExpr = CNE->getArraySize().getValueOr(nullptr)) { + if (const Expr *SizeExpr = CNE->getArraySize().value_or(nullptr)) { ElementCount = State->getSVal(SizeExpr, LCtx); } else { ElementCount = svalBuilder.makeIntVal(1, /*IsUnsigned=*/true); @@ -709,6 +812,11 @@ ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call, svalBuilder.evalBinOp(State, BO_Mul, ElementCount, ElementSize, svalBuilder.getArrayIndexType()); + // FIXME: This line is to prevent a crash. For more details please check + // issue #56264. + if (Size.isUndef()) + Size = UnknownVal(); + State = setDynamicExtent(State, MR, Size.castAs<DefinedOrUnknownSVal>(), svalBuilder); } else { @@ -726,7 +834,8 @@ void ExprEngine::conservativeEvalCall(const CallEvent &Call, NodeBuilder &Bldr, State = bindReturnValue(Call, Pred->getLocationContext(), State); // And make the result node. - Bldr.generateNode(Call.getProgramPoint(), State, Pred); + static SimpleProgramPointTag PT("ExprEngine", "Conservative eval call"); + Bldr.generateNode(Call.getProgramPoint(false, &PT), State, Pred); } ExprEngine::CallInlinePolicy @@ -760,13 +869,10 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, !Opts.MayInlineCXXAllocator) return CIP_DisallowedOnce; - // FIXME: We don't handle constructors or destructors for arrays properly. - // Even once we do, we still need to be careful about implicitly-generated - // initializers for array fields in default move/copy constructors. - // We still allow construction into ElementRegion targets when they don't - // represent array elements. - if (CallOpts.IsArrayCtorOrDtor) - return CIP_DisallowedOnce; + if (CallOpts.IsArrayCtorOrDtor) { + if (!shouldInlineArrayConstruction(Pred->getState(), CtorExpr, CurLC)) + return CIP_DisallowedOnce; + } // Inlining constructors requires including initializers in the CFG. const AnalysisDeclContext *ADC = CallerSFC->getAnalysisDeclContext(); @@ -782,7 +888,7 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, if (!Opts.mayInlineCXXMemberFunction(CIMK_Destructors)) return CIP_DisallowedAlways; - if (CtorExpr->getConstructionKind() == CXXConstructExpr::CK_Complete) { + if (CtorExpr->getConstructionKind() == CXXConstructionKind::Complete) { // If we don't handle temporary destructors, we shouldn't inline // their constructors. if (CallOpts.IsTemporaryCtorOrDtor && @@ -817,9 +923,12 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, assert(ADC->getCFGBuildOptions().AddImplicitDtors && "No CFG destructors"); (void)ADC; - // FIXME: We don't handle constructors or destructors for arrays properly. - if (CallOpts.IsArrayCtorOrDtor) - return CIP_DisallowedOnce; + if (CallOpts.IsArrayCtorOrDtor) { + if (!shouldInlineArrayDestruction(getElementCountOfArrayBeingDestructed( + Call, Pred->getState(), svalBuilder))) { + return CIP_DisallowedOnce; + } + } // Allow disabling temporary destructor inlining with a separate option. if (CallOpts.IsTemporaryCtorOrDtor && @@ -834,7 +943,7 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred, break; } case CE_CXXDeallocator: - LLVM_FALLTHROUGH; + [[fallthrough]]; case CE_CXXAllocator: if (Opts.MayInlineCXXAllocator) break; @@ -979,9 +1088,9 @@ bool ExprEngine::shouldInlineCall(const CallEvent &Call, const Decl *D, return false; // Check if this function has been marked as non-inlinable. - Optional<bool> MayInline = Engine.FunctionSummaries->mayInline(D); - if (MayInline.hasValue()) { - if (!MayInline.getValue()) + std::optional<bool> MayInline = Engine.FunctionSummaries->mayInline(D); + if (MayInline) { + if (!*MayInline) return false; } else { @@ -1002,7 +1111,7 @@ bool ExprEngine::shouldInlineCall(const CallEvent &Call, const Decl *D, CallInlinePolicy CIP = mayInlineCallKind(Call, Pred, Opts, CallOpts); if (CIP != CIP_Allowed) { if (CIP == CIP_DisallowedAlways) { - assert(!MayInline.hasValue() || MayInline.getValue()); + assert(!MayInline || *MayInline); Engine.FunctionSummaries->markShouldNotInline(D); } return false; @@ -1030,6 +1139,63 @@ bool ExprEngine::shouldInlineCall(const CallEvent &Call, const Decl *D, return true; } +bool ExprEngine::shouldInlineArrayConstruction(const ProgramStateRef State, + const CXXConstructExpr *CE, + const LocationContext *LCtx) { + if (!CE) + return false; + + // FIXME: Handle other arrays types. + if (const auto *CAT = dyn_cast<ConstantArrayType>(CE->getType())) { + unsigned ArrSize = getContext().getConstantArrayElementCount(CAT); + + // This might seem conter-intuitive at first glance, but the functions are + // closely related. Reasoning about destructors depends only on the type + // of the expression that initialized the memory region, which is the + // CXXConstructExpr. So to avoid code repetition, the work is delegated + // to the function that reasons about destructor inlining. Also note that + // if the constructors of the array elements are inlined, the destructors + // can also be inlined and if the destructors can be inline, it's safe to + // inline the constructors. + return shouldInlineArrayDestruction(ArrSize); + } + + // Check if we're inside an ArrayInitLoopExpr, and it's sufficiently small. + if (auto Size = getPendingInitLoop(State, CE, LCtx)) + return shouldInlineArrayDestruction(*Size); + + return false; +} + +bool ExprEngine::shouldInlineArrayDestruction(uint64_t Size) { + + uint64_t maxAllowedSize = AMgr.options.maxBlockVisitOnPath; + + // Declaring a 0 element array is also possible. + return Size <= maxAllowedSize && Size > 0; +} + +bool ExprEngine::shouldRepeatCtorCall(ProgramStateRef State, + const CXXConstructExpr *E, + const LocationContext *LCtx) { + + if (!E) + return false; + + auto Ty = E->getType(); + + // FIXME: Handle non constant array types + if (const auto *CAT = dyn_cast<ConstantArrayType>(Ty)) { + unsigned Size = getContext().getConstantArrayElementCount(CAT); + return Size > getIndexOfElementToConstruct(State, E, LCtx); + } + + if (auto Size = getPendingInitLoop(State, E, LCtx)) + return Size > getIndexOfElementToConstruct(State, E, LCtx); + + return false; +} + static bool isTrivialObjectAssignment(const CallEvent &Call) { const CXXInstanceCall *ICall = dyn_cast<CXXInstanceCall>(&Call); if (!ICall) @@ -1068,6 +1234,7 @@ void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred, State = InlinedFailedState; } else { RuntimeDefinition RD = Call->getRuntimeDefinition(); + Call->setForeign(RD.isForeign()); const Decl *D = RD.getDecl(); if (shouldInlineCall(*Call, D, Pred, CallOpts)) { if (RD.mayHaveOtherDefinitions()) { @@ -1085,14 +1252,17 @@ void ExprEngine::defaultEvalCall(NodeBuilder &Bldr, ExplodedNode *Pred, return; } } - - // We are not bifurcating and we do have a Decl, so just inline. - if (inlineCall(*Call, D, Bldr, Pred, State)) - return; + ctuBifurcate(*Call, D, Bldr, Pred, State); + return; } } - // If we can't inline it, handle the return value and invalidate the regions. + // If we can't inline it, clean up the state traits used only if the function + // is inlined. + State = removeStateTraitsUsedForArrayEvaluation( + State, dyn_cast_or_null<CXXConstructExpr>(E), Call->getLocationContext()); + + // Also handle the return value and invalidate the regions. conservativeEvalCall(*Call, Bldr, Pred, State); } @@ -1110,8 +1280,7 @@ void ExprEngine::BifurcateCall(const MemRegion *BifurReg, if (BState) { // If we are on "inline path", keep inlining if possible. if (*BState == DynamicDispatchModeInlined) - if (inlineCall(Call, D, Bldr, Pred, State)) - return; + ctuBifurcate(Call, D, Bldr, Pred, State); // If inline failed, or we are on the path where we assume we // don't have enough info about the receiver to inline, conjure the // return value and invalidate the regions. @@ -1124,7 +1293,7 @@ void ExprEngine::BifurcateCall(const MemRegion *BifurReg, ProgramStateRef IState = State->set<DynamicDispatchBifurcationMap>(BifurReg, DynamicDispatchModeInlined); - inlineCall(Call, D, Bldr, Pred, IState); + ctuBifurcate(Call, D, Bldr, Pred, IState); ProgramStateRef NoIState = State->set<DynamicDispatchBifurcationMap>(BifurReg, diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp index 5a55e81497b0..f075df3ab5e4 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp @@ -148,8 +148,8 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME, ExplodedNode *Pred, ExplodedNodeSet &Dst) { CallEventManager &CEMgr = getStateManager().getCallEventManager(); - CallEventRef<ObjCMethodCall> Msg = - CEMgr.getObjCMethodCall(ME, Pred->getState(), Pred->getLocationContext()); + CallEventRef<ObjCMethodCall> Msg = CEMgr.getObjCMethodCall( + ME, Pred->getState(), Pred->getLocationContext(), getCFGElementRef()); // There are three cases for the receiver: // (1) it is definitely nil, @@ -167,19 +167,32 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME, // intentionally drops coverage in order to prevent false alarms // in the following scenario: // - // id result = [o someMethod] - // if (result) { - // if (!o) { - // // <-- This program point should be unreachable because if o is nil - // // it must the case that result is nil as well. + // id result = [o someMethod] + // if (result) { + // if (!o) { + // // <-- This program point should be unreachable because if o is nil + // // it must the case that result is nil as well. + // } // } - // } // - // We could avoid dropping coverage by performing an explicit case split - // on each method call -- but this would get very expensive. An alternative - // would be to introduce lazy constraints. - // FIXME: This ignores many potential bugs (<rdar://problem/11733396>). - // Revisit once we have lazier constraints. + // However, it also loses coverage of the nil path prematurely, + // leading to missed reports. + // + // It's possible to handle this by performing a state split on every call: + // explore the state where the receiver is non-nil, and independently + // explore the state where it's nil. But this is not only slow, but + // completely unwarranted. The mere presence of the message syntax in the code + // isn't sufficient evidence that nil is a realistic possibility. + // + // An ideal solution would be to add the following constraint that captures + // both possibilities without splitting the state: + // + // ($x == 0) => ($y == 0) (1) + // + // where in our case '$x' is the receiver symbol, '$y' is the returned symbol, + // and '=>' is logical implication. But RangeConstraintManager can't handle + // such constraints yet, so for now we go with a simpler, more restrictive + // constraint: $x != 0, from which (1) follows as a vacuous truth. if (Msg->isInstanceMessage()) { SVal recVal = Msg->getReceiverSVal(); if (!recVal.isUndef()) { @@ -206,7 +219,7 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME, ExplodedNodeSet dstPostCheckers; getCheckerManager().runCheckersForObjCMessageNil(dstPostCheckers, Pred, *Msg, *this); - for (auto I : dstPostCheckers) + for (auto *I : dstPostCheckers) finishArgumentConstruction(Dst, I, *Msg); return; } @@ -270,7 +283,7 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME, // If there were constructors called for object-type arguments, clean them up. ExplodedNodeSet dstArgCleanup; - for (auto I : dstEval) + for (auto *I : dstEval) finishArgumentConstruction(dstArgCleanup, I, *Msg); ExplodedNodeSet dstPostvisit; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp index 64fc32ea7554..86947b7929e9 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -27,6 +27,8 @@ #include "clang/Rewrite/Core/Rewriter.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/Sequence.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/iterator_range.h" @@ -57,6 +59,8 @@ using namespace ento; namespace { +class ArrowMap; + class HTMLDiagnostics : public PathDiagnosticConsumer { PathDiagnosticConsumerOptions DiagOpts; std::string Directory; @@ -64,6 +68,7 @@ class HTMLDiagnostics : public PathDiagnosticConsumer { bool noDir = false; const Preprocessor &PP; const bool SupportsCrossFileDiagnostics; + llvm::StringSet<> EmittedHashes; public: HTMLDiagnostics(PathDiagnosticConsumerOptions DiagOpts, @@ -77,60 +82,93 @@ public: void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, FilesMade *filesMade) override; - StringRef getName() const override { - return "HTMLDiagnostics"; - } + StringRef getName() const override { return "HTMLDiagnostics"; } bool supportsCrossFileDiagnostics() const override { return SupportsCrossFileDiagnostics; } - unsigned ProcessMacroPiece(raw_ostream &os, - const PathDiagnosticMacroPiece& P, + unsigned ProcessMacroPiece(raw_ostream &os, const PathDiagnosticMacroPiece &P, unsigned num); + unsigned ProcessControlFlowPiece(Rewriter &R, FileID BugFileID, + const PathDiagnosticControlFlowPiece &P, + unsigned Number); + void HandlePiece(Rewriter &R, FileID BugFileID, const PathDiagnosticPiece &P, const std::vector<SourceRange> &PopUpRanges, unsigned num, unsigned max); - void HighlightRange(Rewriter& R, FileID BugFileID, SourceRange Range, + void HighlightRange(Rewriter &R, FileID BugFileID, SourceRange Range, const char *HighlightStart = "<span class=\"mrange\">", const char *HighlightEnd = "</span>"); - void ReportDiag(const PathDiagnostic& D, - FilesMade *filesMade); + void ReportDiag(const PathDiagnostic &D, FilesMade *filesMade); // Generate the full HTML report - std::string GenerateHTML(const PathDiagnostic& D, Rewriter &R, - const SourceManager& SMgr, const PathPieces& path, + std::string GenerateHTML(const PathDiagnostic &D, Rewriter &R, + const SourceManager &SMgr, const PathPieces &path, const char *declName); // Add HTML header/footers to file specified by FID - void FinalizeHTML(const PathDiagnostic& D, Rewriter &R, - const SourceManager& SMgr, const PathPieces& path, - FileID FID, const FileEntry *Entry, const char *declName); + void FinalizeHTML(const PathDiagnostic &D, Rewriter &R, + const SourceManager &SMgr, const PathPieces &path, + FileID FID, FileEntryRef Entry, const char *declName); // Rewrite the file specified by FID with HTML formatting. - void RewriteFile(Rewriter &R, const PathPieces& path, FileID FID); + void RewriteFile(Rewriter &R, const PathPieces &path, FileID FID); + PathGenerationScheme getGenerationScheme() const override { + return Everything; + } private: + void addArrowSVGs(Rewriter &R, FileID BugFileID, + const ArrowMap &ArrowIndices); + /// \return Javascript for displaying shortcuts help; StringRef showHelpJavascript(); /// \return Javascript for navigating the HTML report using j/k keys. StringRef generateKeyboardNavigationJavascript(); + /// \return Javascript for drawing control-flow arrows. + StringRef generateArrowDrawingJavascript(); + /// \return JavaScript for an option to only show relevant lines. - std::string showRelevantLinesJavascript( - const PathDiagnostic &D, const PathPieces &path); + std::string showRelevantLinesJavascript(const PathDiagnostic &D, + const PathPieces &path); /// Write executed lines from \p D in JSON format into \p os. - void dumpCoverageData(const PathDiagnostic &D, - const PathPieces &path, + void dumpCoverageData(const PathDiagnostic &D, const PathPieces &path, llvm::raw_string_ostream &os); }; +bool isArrowPiece(const PathDiagnosticPiece &P) { + return isa<PathDiagnosticControlFlowPiece>(P) && P.getString().empty(); +} + +unsigned getPathSizeWithoutArrows(const PathPieces &Path) { + unsigned TotalPieces = Path.size(); + unsigned TotalArrowPieces = llvm::count_if( + Path, [](const PathDiagnosticPieceRef &P) { return isArrowPiece(*P); }); + return TotalPieces - TotalArrowPieces; +} + +class ArrowMap : public std::vector<unsigned> { + using Base = std::vector<unsigned>; + +public: + ArrowMap(unsigned Size) : Base(Size, 0) {} + unsigned getTotalNumberOfArrows() const { return at(0); } +}; + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const ArrowMap &Indices) { + OS << "[ "; + llvm::interleave(Indices, OS, ","); + return OS << " ]"; +} + } // namespace void ento::createHTMLDiagnosticConsumer( @@ -208,6 +246,18 @@ void HTMLDiagnostics::FlushDiagnosticsImpl( ReportDiag(*Diag, filesMade); } +static llvm::SmallString<32> getIssueHash(const PathDiagnostic &D, + const Preprocessor &PP) { + SourceManager &SMgr = PP.getSourceManager(); + PathDiagnosticLocation UPDLoc = D.getUniqueingLoc(); + FullSourceLoc L(SMgr.getExpansionLoc(UPDLoc.isValid() + ? UPDLoc.asLocation() + : D.getLocation().asLocation()), + SMgr); + return getIssueHash(L, D.getCheckerName(), D.getBugType(), + D.getDeclWithIssue(), PP.getLangOpts()); +} + void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, FilesMade *filesMade) { // Create the HTML directory if it is missing. @@ -234,11 +284,6 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, // Create a new rewriter to generate HTML. Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts()); - // The file for the first path element is considered the main report file, it - // will usually be equivalent to SMgr.getMainFileID(); however, it might be a - // header when -analyzer-opt-analyze-headers is used. - FileID ReportFile = path.front()->getLocation().asLocation().getExpansionLoc().getFileID(); - // Get the function/method name SmallString<128> declName("unknown"); int offsetDecl = 0; @@ -257,6 +302,17 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, } } + SmallString<32> IssueHash = getIssueHash(D, PP); + auto [It, IsNew] = EmittedHashes.insert(IssueHash); + if (!IsNew) { + // We've already emitted a duplicate issue. It'll get overwritten anyway. + return; + } + + // FIXME: This causes each file to be re-parsed and syntax-highlighted + // and macro-expanded separately for each report. We could cache such rewrites + // across all reports and only re-do the part that's actually different: + // the warning/note bubbles. std::string report = GenerateHTML(D, R, SMgr, path, declName.c_str()); if (report.empty()) { llvm::errs() << "warning: no diagnostics generated for main file.\n"; @@ -265,46 +321,52 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, // Create a path for the target HTML file. int FD; - SmallString<128> Model, ResultPath; - - if (!DiagOpts.ShouldWriteStableReportFilename) { - llvm::sys::path::append(Model, Directory, "report-%%%%%%.html"); - if (std::error_code EC = - llvm::sys::fs::make_absolute(Model)) { - llvm::errs() << "warning: could not make '" << Model - << "' absolute: " << EC.message() << '\n'; - return; - } - if (std::error_code EC = llvm::sys::fs::createUniqueFile( - Model, FD, ResultPath, llvm::sys::fs::OF_Text)) { - llvm::errs() << "warning: could not create file in '" << Directory - << "': " << EC.message() << '\n'; - return; - } - } else { - int i = 1; - std::error_code EC; - do { - // Find a filename which is not already used - const FileEntry* Entry = SMgr.getFileEntryForID(ReportFile); - std::stringstream filename; - Model = ""; - filename << "report-" - << llvm::sys::path::filename(Entry->getName()).str() - << "-" << declName.c_str() - << "-" << offsetDecl - << "-" << i << ".html"; - llvm::sys::path::append(Model, Directory, - filename.str()); - EC = llvm::sys::fs::openFileForReadWrite( - Model, FD, llvm::sys::fs::CD_CreateNew, llvm::sys::fs::OF_None); - if (EC && EC != llvm::errc::file_exists) { - llvm::errs() << "warning: could not create file '" << Model - << "': " << EC.message() << '\n'; - return; - } - i++; - } while (EC); + + SmallString<128> FileNameStr; + llvm::raw_svector_ostream FileName(FileNameStr); + FileName << "report-"; + + // Historically, neither the stable report filename nor the unstable report + // filename were actually stable. That said, the stable report filename + // was more stable because it was mostly composed of information + // about the bug report instead of being completely random. + // Now both stable and unstable report filenames are in fact stable + // but the stable report filename is still more verbose. + if (DiagOpts.ShouldWriteVerboseReportFilename) { + // FIXME: This code relies on knowing what constitutes the issue hash. + // Otherwise deduplication won't work correctly. + FileID ReportFile = + path.back()->getLocation().asLocation().getExpansionLoc().getFileID(); + + OptionalFileEntryRef Entry = SMgr.getFileEntryRefForID(ReportFile); + + FileName << llvm::sys::path::filename(Entry->getName()).str() << "-" + << declName.c_str() << "-" << offsetDecl << "-"; + } + + FileName << StringRef(IssueHash).substr(0, 6).str() << ".html"; + + SmallString<128> ResultPath; + llvm::sys::path::append(ResultPath, Directory, FileName.str()); + if (std::error_code EC = llvm::sys::fs::make_absolute(ResultPath)) { + llvm::errs() << "warning: could not make '" << ResultPath + << "' absolute: " << EC.message() << '\n'; + return; + } + + if (std::error_code EC = llvm::sys::fs::openFileForReadWrite( + ResultPath, FD, llvm::sys::fs::CD_CreateNew, + llvm::sys::fs::OF_Text)) { + // Existence of the file corresponds to the situation where a different + // Clang instance has emitted a bug report with the same issue hash. + // This is an entirely normal situation that does not deserve a warning, + // as apart from hash collisions this can happen because the reports + // are in fact similar enough to be considered duplicates of each other. + if (EC != llvm::errc::file_exists) { + llvm::errs() << "warning: could not create file in '" << Directory + << "': " << EC.message() << '\n'; + } + return; } llvm::raw_fd_ostream os(FD, true); @@ -346,7 +408,7 @@ std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R, os << "<div class=FileNav><a href=\"#File" << (I - 1)->getHashValue() << "\">←</a></div>"; - os << "<h4 class=FileName>" << SMgr.getFileEntryForID(*I)->getName() + os << "<h4 class=FileName>" << SMgr.getFileEntryRefForID(*I)->getName() << "</h4>\n"; // Right nav arrow @@ -360,7 +422,7 @@ std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R, } // Append files to the main report file in the order they appear in the path - for (auto I : llvm::make_range(FileIDs.begin() + 1, FileIDs.end())) { + for (auto I : llvm::drop_begin(FileIDs)) { std::string s; llvm::raw_string_ostream os(s); @@ -379,15 +441,15 @@ std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R, // Add CSS, header, and footer. FileID FID = path.back()->getLocation().asLocation().getExpansionLoc().getFileID(); - const FileEntry* Entry = SMgr.getFileEntryForID(FID); - FinalizeHTML(D, R, SMgr, path, FileIDs[0], Entry, declName); + OptionalFileEntryRef Entry = SMgr.getFileEntryRefForID(FID); + FinalizeHTML(D, R, SMgr, path, FileIDs[0], *Entry, declName); std::string file; llvm::raw_string_ostream os(file); for (auto BI : *Buf) os << BI; - return os.str(); + return file; } void HTMLDiagnostics::dumpCoverageData( @@ -452,10 +514,11 @@ window.addEventListener("keydown", function (event) { if (event.defaultPrevented) { return; } - if (event.key == "S") { + // SHIFT + S + if (event.shiftKey && event.keyCode == 83) { var checked = document.getElementsByName("showCounterexample")[0].checked; filterCounterexample(!checked); - document.getElementsByName("showCounterexample")[0].checked = !checked; + document.getElementsByName("showCounterexample")[0].click(); } else { return; } @@ -475,22 +538,28 @@ document.addEventListener("DOMContentLoaded", function() { <label for="showCounterexample"> Show only relevant lines </label> + <input type="checkbox" name="showArrows" + id="showArrows" style="margin-left: 10px" /> + <label for="showArrows"> + Show control flow arrows + </label> </form> )<<<"; - return os.str(); + return s; } -void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, - const SourceManager& SMgr, const PathPieces& path, FileID FID, - const FileEntry *Entry, const char *declName) { +void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic &D, Rewriter &R, + const SourceManager &SMgr, + const PathPieces &path, FileID FID, + FileEntryRef Entry, const char *declName) { // This is a cludge; basically we want to append either the full // working directory if we have no directory information. This is // a work in progress. llvm::SmallString<0> DirName; - if (llvm::sys::path::is_relative(Entry->getName())) { + if (llvm::sys::path::is_relative(Entry.getName())) { llvm::sys::fs::current_path(DirName); DirName += '/'; } @@ -503,6 +572,9 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), generateKeyboardNavigationJavascript()); + R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), + generateArrowDrawingJavascript()); + // Checkbox and javascript for filtering the output to the counterexample. R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), showRelevantLinesJavascript(D, path)); @@ -516,7 +588,7 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, << "<h3>Bug Summary</h3>\n<table class=\"simpletable\">\n" "<tr><td class=\"rowname\">File:</td><td>" << html::EscapeText(DirName) - << html::EscapeText(Entry->getName()) + << html::EscapeText(Entry.getName()) << "</td></tr>\n<tr><td class=\"rowname\">Warning:</td><td>" "<a href=\"#EndPath\">line " << LineNumber @@ -533,19 +605,19 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, P->getLocation().asLocation().getExpansionLineNumber(); int ColumnNumber = P->getLocation().asLocation().getExpansionColumnNumber(); + ++NumExtraPieces; os << "<tr><td class=\"rowname\">Note:</td><td>" << "<a href=\"#Note" << NumExtraPieces << "\">line " << LineNumber << ", column " << ColumnNumber << "</a><br />" << P->getString() << "</td></tr>"; - ++NumExtraPieces; } } // Output any other meta data. - for (PathDiagnostic::meta_iterator I = D.meta_begin(), E = D.meta_end(); - I != E; ++I) { - os << "<tr><td></td><td>" << html::EscapeText(*I) << "</td></tr>\n"; + for (const std::string &Metadata : + llvm::make_range(D.meta_begin(), D.meta_end())) { + os << "<tr><td></td><td>" << html::EscapeText(Metadata) << "</td></tr>\n"; } os << R"<<<( @@ -570,6 +642,7 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, <a href="#" onclick="toggleHelp(); return false;">Close</a> </div> )<<<"; + R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str()); } @@ -591,21 +664,18 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, ? UPDLoc.asLocation() : D.getLocation().asLocation()), SMgr); - const Decl *DeclWithIssue = D.getDeclWithIssue(); StringRef BugCategory = D.getCategory(); if (!BugCategory.empty()) os << "\n<!-- BUGCATEGORY " << BugCategory << " -->\n"; - os << "\n<!-- BUGFILE " << DirName << Entry->getName() << " -->\n"; + os << "\n<!-- BUGFILE " << DirName << Entry.getName() << " -->\n"; - os << "\n<!-- FILENAME " << llvm::sys::path::filename(Entry->getName()) << " -->\n"; + os << "\n<!-- FILENAME " << llvm::sys::path::filename(Entry.getName()) << " -->\n"; os << "\n<!-- FUNCTIONNAME " << declName << " -->\n"; - os << "\n<!-- ISSUEHASHCONTENTOFLINEINCONTEXT " - << getIssueHash(L, D.getCheckerName(), D.getBugType(), DeclWithIssue, - PP.getLangOpts()) + os << "\n<!-- ISSUEHASHCONTENTOFLINEINCONTEXT " << getIssueHash(D, PP) << " -->\n"; os << "\n<!-- BUGLINE " @@ -616,7 +686,7 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, << ColumnNumber << " -->\n"; - os << "\n<!-- BUGPATHLENGTH " << path.size() << " -->\n"; + os << "\n<!-- BUGPATHLENGTH " << getPathSizeWithoutArrows(path) << " -->\n"; // Mark the end of the tags. os << "\n<!-- BUGMETAEND -->\n"; @@ -625,7 +695,7 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, R.InsertTextBefore(SMgr.getLocForStartOfFile(FID), os.str()); } - html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry->getName()); + html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry.getName()); } StringRef HTMLDiagnostics::showHelpJavascript() { @@ -695,8 +765,7 @@ static void HandlePopUpPieceEndTag(Rewriter &R, Out << "</div></td><td>" << Piece.getString() << "</td></tr>"; // If no report made at this range mark the variable and add the end tags. - if (std::find(PopUpRanges.begin(), PopUpRanges.end(), Range) == - PopUpRanges.end()) { + if (!llvm::is_contained(PopUpRanges, Range)) { // Store that we create a report at this range. PopUpRanges.push_back(Range); @@ -711,30 +780,33 @@ static void HandlePopUpPieceEndTag(Rewriter &R, } } -void HTMLDiagnostics::RewriteFile(Rewriter &R, - const PathPieces& path, FileID FID) { +void HTMLDiagnostics::RewriteFile(Rewriter &R, const PathPieces &path, + FileID FID) { + // Process the path. // Maintain the counts of extra note pieces separately. - unsigned TotalPieces = path.size(); - unsigned TotalNotePieces = std::count_if( - path.begin(), path.end(), [](const PathDiagnosticPieceRef &p) { + unsigned TotalPieces = getPathSizeWithoutArrows(path); + unsigned TotalNotePieces = + llvm::count_if(path, [](const PathDiagnosticPieceRef &p) { return isa<PathDiagnosticNotePiece>(*p); }); - unsigned PopUpPieceCount = std::count_if( - path.begin(), path.end(), [](const PathDiagnosticPieceRef &p) { + unsigned PopUpPieceCount = + llvm::count_if(path, [](const PathDiagnosticPieceRef &p) { return isa<PathDiagnosticPopUpPiece>(*p); }); unsigned TotalRegularPieces = TotalPieces - TotalNotePieces - PopUpPieceCount; unsigned NumRegularPieces = TotalRegularPieces; unsigned NumNotePieces = TotalNotePieces; + unsigned NumberOfArrows = 0; // Stores the count of the regular piece indices. std::map<int, int> IndexMap; + ArrowMap ArrowIndices(TotalRegularPieces + 1); // Stores the different ranges where we have reported something. std::vector<SourceRange> PopUpRanges; - for (auto I = path.rbegin(), E = path.rend(); I != E; ++I) { - const auto &Piece = *I->get(); + for (const PathDiagnosticPieceRef &I : llvm::reverse(path)) { + const auto &Piece = *I.get(); if (isa<PathDiagnosticPopUpPiece>(Piece)) { ++IndexMap[NumRegularPieces]; @@ -744,18 +816,40 @@ void HTMLDiagnostics::RewriteFile(Rewriter &R, // as a separate pass through the piece list. HandlePiece(R, FID, Piece, PopUpRanges, NumNotePieces, TotalNotePieces); --NumNotePieces; + + } else if (isArrowPiece(Piece)) { + NumberOfArrows = ProcessControlFlowPiece( + R, FID, cast<PathDiagnosticControlFlowPiece>(Piece), NumberOfArrows); + ArrowIndices[NumRegularPieces] = NumberOfArrows; + } else { HandlePiece(R, FID, Piece, PopUpRanges, NumRegularPieces, TotalRegularPieces); --NumRegularPieces; + ArrowIndices[NumRegularPieces] = ArrowIndices[NumRegularPieces + 1]; } } + ArrowIndices[0] = NumberOfArrows; + + // At this point ArrowIndices represent the following data structure: + // [a_0, a_1, ..., a_N] + // where N is the number of events in the path. + // + // Then for every event with index i \in [0, N - 1], we can say that + // arrows with indices \in [a_(i+1), a_i) correspond to that event. + // We can say that because arrows with these indices appeared in the + // path in between the i-th and the (i+1)-th events. + assert(ArrowIndices.back() == 0 && + "No arrows should be after the last event"); + // This assertion also guarantees that all indices in are <= NumberOfArrows. + assert(llvm::is_sorted(ArrowIndices, std::greater<unsigned>()) && + "Incorrect arrow indices map"); // Secondary indexing if we are having multiple pop-ups between two notes. // (e.g. [(13) 'a' is 'true']; [(13.1) 'b' is 'false']; [(13.2) 'c' is...) NumRegularPieces = TotalRegularPieces; - for (auto I = path.rbegin(), E = path.rend(); I != E; ++I) { - const auto &Piece = *I->get(); + for (const PathDiagnosticPieceRef &I : llvm::reverse(path)) { + const auto &Piece = *I.get(); if (const auto *PopUpP = dyn_cast<PathDiagnosticPopUpPiece>(&Piece)) { int PopUpPieceIndex = IndexMap[NumRegularPieces]; @@ -771,7 +865,7 @@ void HTMLDiagnostics::RewriteFile(Rewriter &R, if (PopUpPieceIndex > 0) --IndexMap[NumRegularPieces]; - } else if (!isa<PathDiagnosticNotePiece>(Piece)) { + } else if (!isa<PathDiagnosticNotePiece>(Piece) && !isArrowPiece(Piece)) { --NumRegularPieces; } } @@ -783,6 +877,8 @@ void HTMLDiagnostics::RewriteFile(Rewriter &R, html::EscapeText(R, FID); html::AddLineNumbers(R, FID); + addArrowSVGs(R, FID, ArrowIndices); + // If we have a preprocessor, relex the file and syntax highlight. // We might not have a preprocessor if we come from a deserialized AST file, // for example. @@ -1007,8 +1103,7 @@ void HTMLDiagnostics::HandlePiece(Rewriter &R, FileID BugFileID, ArrayRef<SourceRange> Ranges = P.getRanges(); for (const auto &Range : Ranges) { // If we have already highlighted the range as a pop-up there is no work. - if (std::find(PopUpRanges.begin(), PopUpRanges.end(), Range) != - PopUpRanges.end()) + if (llvm::is_contained(PopUpRanges, Range)) continue; HighlightRange(R, LPosInfo.first, Range); @@ -1049,6 +1144,104 @@ unsigned HTMLDiagnostics::ProcessMacroPiece(raw_ostream &os, return num; } +void HTMLDiagnostics::addArrowSVGs(Rewriter &R, FileID BugFileID, + const ArrowMap &ArrowIndices) { + std::string S; + llvm::raw_string_ostream OS(S); + + OS << R"<<<( +<style type="text/css"> + svg { + position:absolute; + top:0; + left:0; + height:100%; + width:100%; + pointer-events: none; + overflow: visible + } + .arrow { + stroke-opacity: 0.2; + stroke-width: 1; + marker-end: url(#arrowhead); + } + + .arrow.selected { + stroke-opacity: 0.6; + stroke-width: 2; + marker-end: url(#arrowheadSelected); + } + + .arrowhead { + orient: auto; + stroke: none; + opacity: 0.6; + fill: blue; + } +</style> +<svg xmlns="http://www.w3.org/2000/svg"> + <defs> + <marker id="arrowheadSelected" class="arrowhead" opacity="0.6" + viewBox="0 0 10 10" refX="3" refY="5" + markerWidth="4" markerHeight="4"> + <path d="M 0 0 L 10 5 L 0 10 z" /> + </marker> + <marker id="arrowhead" class="arrowhead" opacity="0.2" + viewBox="0 0 10 10" refX="3" refY="5" + markerWidth="4" markerHeight="4"> + <path d="M 0 0 L 10 5 L 0 10 z" /> + </marker> + </defs> + <g id="arrows" fill="none" stroke="blue" visibility="hidden"> +)<<<"; + + for (unsigned Index : llvm::seq(0u, ArrowIndices.getTotalNumberOfArrows())) { + OS << " <path class=\"arrow\" id=\"arrow" << Index << "\"/>\n"; + } + + OS << R"<<<( + </g> +</svg> +<script type='text/javascript'> +const arrowIndices = )<<<"; + + OS << ArrowIndices << "\n</script>\n"; + + R.InsertTextBefore(R.getSourceMgr().getLocForStartOfFile(BugFileID), + OS.str()); +} + +std::string getSpanBeginForControl(const char *ClassName, unsigned Index) { + std::string Result; + llvm::raw_string_ostream OS(Result); + OS << "<span id=\"" << ClassName << Index << "\">"; + return Result; +} + +std::string getSpanBeginForControlStart(unsigned Index) { + return getSpanBeginForControl("start", Index); +} + +std::string getSpanBeginForControlEnd(unsigned Index) { + return getSpanBeginForControl("end", Index); +} + +unsigned HTMLDiagnostics::ProcessControlFlowPiece( + Rewriter &R, FileID BugFileID, const PathDiagnosticControlFlowPiece &P, + unsigned Number) { + for (const PathDiagnosticLocationPair &LPair : P) { + std::string Start = getSpanBeginForControlStart(Number), + End = getSpanBeginForControlEnd(Number++); + + HighlightRange(R, BugFileID, LPair.getStart().asRange().getBegin(), + Start.c_str()); + HighlightRange(R, BugFileID, LPair.getEnd().asRange().getBegin(), + End.c_str()); + } + + return Number; +} + void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID, SourceRange Range, const char *HighlightStart, @@ -1109,7 +1302,7 @@ document.addEventListener("DOMContentLoaded", function() { }); var findNum = function() { - var s = document.querySelector(".selected"); + var s = document.querySelector(".msg.selected"); if (!s || s.id == "EndPath") { return 0; } @@ -1117,14 +1310,32 @@ var findNum = function() { return out; }; +var classListAdd = function(el, theClass) { + if(!el.className.baseVal) + el.className += " " + theClass; + else + el.className.baseVal += " " + theClass; +}; + +var classListRemove = function(el, theClass) { + var className = (!el.className.baseVal) ? + el.className : el.className.baseVal; + className = className.replace(" " + theClass, ""); + if(!el.className.baseVal) + el.className = className; + else + el.className.baseVal = className; +}; + var scrollTo = function(el) { querySelectorAllArray(".selected").forEach(function(s) { - s.classList.remove("selected"); + classListRemove(s, "selected"); }); - el.classList.add("selected"); + classListAdd(el, "selected"); window.scrollBy(0, el.getBoundingClientRect().top - (window.innerHeight / 2)); -} + highlightArrowsForSelectedEvent(); +}; var move = function(num, up, numItems) { if (num == 1 && up || num == numItems - 1 && !up) { @@ -1159,9 +1370,11 @@ window.addEventListener("keydown", function (event) { if (event.defaultPrevented) { return; } - if (event.key == "j") { + // key 'j' + if (event.keyCode == 74) { navigateTo(/*up=*/false); - } else if (event.key == "k") { + // key 'k' + } else if (event.keyCode == 75) { navigateTo(/*up=*/true); } else { return; @@ -1171,3 +1384,258 @@ window.addEventListener("keydown", function (event) { </script> )<<<"; } + +StringRef HTMLDiagnostics::generateArrowDrawingJavascript() { + return R"<<<( +<script type='text/javascript'> +// Return range of numbers from a range [lower, upper). +function range(lower, upper) { + var array = []; + for (var i = lower; i <= upper; ++i) { + array.push(i); + } + return array; +} + +var getRelatedArrowIndices = function(pathId) { + // HTML numeration of events is a bit different than it is in the path. + // Everything is rotated one step to the right, so the last element + // (error diagnostic) has index 0. + if (pathId == 0) { + // arrowIndices has at least 2 elements + pathId = arrowIndices.length - 1; + } + + return range(arrowIndices[pathId], arrowIndices[pathId - 1]); +} + +var highlightArrowsForSelectedEvent = function() { + const selectedNum = findNum(); + const arrowIndicesToHighlight = getRelatedArrowIndices(selectedNum); + arrowIndicesToHighlight.forEach((index) => { + var arrow = document.querySelector("#arrow" + index); + if(arrow) { + classListAdd(arrow, "selected") + } + }); +} + +var getAbsoluteBoundingRect = function(element) { + const relative = element.getBoundingClientRect(); + return { + left: relative.left + window.pageXOffset, + right: relative.right + window.pageXOffset, + top: relative.top + window.pageYOffset, + bottom: relative.bottom + window.pageYOffset, + height: relative.height, + width: relative.width + }; +} + +var drawArrow = function(index) { + // This function is based on the great answer from SO: + // https://stackoverflow.com/a/39575674/11582326 + var start = document.querySelector("#start" + index); + var end = document.querySelector("#end" + index); + var arrow = document.querySelector("#arrow" + index); + + var startRect = getAbsoluteBoundingRect(start); + var endRect = getAbsoluteBoundingRect(end); + + // It is an arrow from a token to itself, no need to visualize it. + if (startRect.top == endRect.top && + startRect.left == endRect.left) + return; + + // Each arrow is a very simple Bézier curve, with two nodes and + // two handles. So, we need to calculate four points in the window: + // * start node + var posStart = { x: 0, y: 0 }; + // * end node + var posEnd = { x: 0, y: 0 }; + // * handle for the start node + var startHandle = { x: 0, y: 0 }; + // * handle for the end node + var endHandle = { x: 0, y: 0 }; + // One can visualize it as follows: + // + // start handle + // / + // X"""_.-""""X + // .' \ + // / start node + // | + // | + // | end node + // \ / + // `->X + // X-' + // \ + // end handle + // + // NOTE: (0, 0) is the top left corner of the window. + + // We have 3 similar, but still different scenarios to cover: + // + // 1. Two tokens on different lines. + // -xxx + // / + // \ + // -> xxx + // In this situation, we draw arrow on the left curving to the left. + // 2. Two tokens on the same line, and the destination is on the right. + // ____ + // / \ + // / V + // xxx xxx + // In this situation, we draw arrow above curving upwards. + // 3. Two tokens on the same line, and the destination is on the left. + // xxx xxx + // ^ / + // \____/ + // In this situation, we draw arrow below curving downwards. + const onDifferentLines = startRect.top <= endRect.top - 5 || + startRect.top >= endRect.top + 5; + const leftToRight = startRect.left < endRect.left; + + // NOTE: various magic constants are chosen empirically for + // better positioning and look + if (onDifferentLines) { + // Case #1 + const topToBottom = startRect.top < endRect.top; + posStart.x = startRect.left - 1; + // We don't want to start it at the top left corner of the token, + // it doesn't feel like this is where the arrow comes from. + // For this reason, we start it in the middle of the left side + // of the token. + posStart.y = startRect.top + startRect.height / 2; + + // End node has arrow head and we give it a bit more space. + posEnd.x = endRect.left - 4; + posEnd.y = endRect.top; + + // Utility object with x and y offsets for handles. + var curvature = { + // We want bottom-to-top arrow to curve a bit more, so it doesn't + // overlap much with top-to-bottom curves (much more frequent). + x: topToBottom ? 15 : 25, + y: Math.min((posEnd.y - posStart.y) / 3, 10) + } + + // When destination is on the different line, we can make a + // curvier arrow because we have space for it. + // So, instead of using + // + // startHandle.x = posStart.x - curvature.x + // endHandle.x = posEnd.x - curvature.x + // + // We use the leftmost of these two values for both handles. + startHandle.x = Math.min(posStart.x, posEnd.x) - curvature.x; + endHandle.x = startHandle.x; + + // Curving downwards from the start node... + startHandle.y = posStart.y + curvature.y; + // ... and upwards from the end node. + endHandle.y = posEnd.y - curvature.y; + + } else if (leftToRight) { + // Case #2 + // Starting from the top right corner... + posStart.x = startRect.right - 1; + posStart.y = startRect.top; + + // ...and ending at the top left corner of the end token. + posEnd.x = endRect.left + 1; + posEnd.y = endRect.top - 1; + + // Utility object with x and y offsets for handles. + var curvature = { + x: Math.min((posEnd.x - posStart.x) / 3, 15), + y: 5 + } + + // Curving to the right... + startHandle.x = posStart.x + curvature.x; + // ... and upwards from the start node. + startHandle.y = posStart.y - curvature.y; + + // And to the left... + endHandle.x = posEnd.x - curvature.x; + // ... and upwards from the end node. + endHandle.y = posEnd.y - curvature.y; + + } else { + // Case #3 + // Starting from the bottom right corner... + posStart.x = startRect.right; + posStart.y = startRect.bottom; + + // ...and ending also at the bottom right corner, but of the end token. + posEnd.x = endRect.right - 1; + posEnd.y = endRect.bottom + 1; + + // Utility object with x and y offsets for handles. + var curvature = { + x: Math.min((posStart.x - posEnd.x) / 3, 15), + y: 5 + } + + // Curving to the left... + startHandle.x = posStart.x - curvature.x; + // ... and downwards from the start node. + startHandle.y = posStart.y + curvature.y; + + // And to the right... + endHandle.x = posEnd.x + curvature.x; + // ... and downwards from the end node. + endHandle.y = posEnd.y + curvature.y; + } + + // Put it all together into a path. + // More information on the format: + // https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths + var pathStr = "M" + posStart.x + "," + posStart.y + " " + + "C" + startHandle.x + "," + startHandle.y + " " + + endHandle.x + "," + endHandle.y + " " + + posEnd.x + "," + posEnd.y; + + arrow.setAttribute("d", pathStr); +}; + +var drawArrows = function() { + const numOfArrows = document.querySelectorAll("path[id^=arrow]").length; + for (var i = 0; i < numOfArrows; ++i) { + drawArrow(i); + } +} + +var toggleArrows = function(event) { + const arrows = document.querySelector("#arrows"); + if (event.target.checked) { + arrows.setAttribute("visibility", "visible"); + } else { + arrows.setAttribute("visibility", "hidden"); + } +} + +window.addEventListener("resize", drawArrows); +document.addEventListener("DOMContentLoaded", function() { + // Whenever we show invocation, locations change, i.e. we + // need to redraw arrows. + document + .querySelector('input[id="showinvocation"]') + .addEventListener("click", drawArrows); + // Hiding irrelevant lines also should cause arrow rerender. + document + .querySelector('input[name="showCounterexample"]') + .addEventListener("change", drawArrows); + document + .querySelector('input[name="showArrows"]') + .addEventListener("change", toggleArrows); + drawArrows(); + // Default highlighting for the last event. + highlightArrowsForSelectedEvent(); +}); +</script> + )<<<"; +} diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp index e5f4e9ea30c9..a80352816be6 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp @@ -17,6 +17,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/LoopUnrolling.h" +#include <optional> using namespace clang; using namespace ento; @@ -24,6 +25,7 @@ using namespace clang::ast_matchers; static const int MAXIMUM_STEP_UNROLLED = 128; +namespace { struct LoopState { private: enum Kind { Normal, Unrolled } K; @@ -56,6 +58,7 @@ public: ID.AddInteger(maxStep); } }; +} // namespace // The tracked stack of loops. The stack indicates that which loops the // simulated element contained by. The loops are marked depending if we decided @@ -69,7 +72,7 @@ namespace clang { namespace ento { static bool isLoopStmt(const Stmt *S) { - return S && (isa<ForStmt>(S) || isa<WhileStmt>(S) || isa<DoStmt>(S)); + return isa_and_nonnull<ForStmt, WhileStmt, DoStmt>(S); } ProgramStateRef processLoopEnd(const Stmt *LoopStmt, ProgramStateRef State) { @@ -175,7 +178,7 @@ static bool isCapturedByReference(ExplodedNode *N, const DeclRefExpr *DR) { const CXXRecordDecl *LambdaCXXRec = MD->getParent(); // Lookup the fields of the lambda - llvm::DenseMap<const VarDecl *, FieldDecl *> LambdaCaptureFields; + llvm::DenseMap<const ValueDecl *, FieldDecl *> LambdaCaptureFields; FieldDecl *LambdaThisCaptureField; LambdaCXXRec->getCaptureFields(LambdaCaptureFields, LambdaThisCaptureField); @@ -264,8 +267,8 @@ bool shouldCompletelyUnroll(const Stmt *LoopStmt, ASTContext &ASTCtx, Matches[0].getNodeAs<IntegerLiteral>("initNum")->getValue(); auto CondOp = Matches[0].getNodeAs<BinaryOperator>("conditionOperator"); if (InitNum.getBitWidth() != BoundNum.getBitWidth()) { - InitNum = InitNum.zextOrSelf(BoundNum.getBitWidth()); - BoundNum = BoundNum.zextOrSelf(InitNum.getBitWidth()); + InitNum = InitNum.zext(BoundNum.getBitWidth()); + BoundNum = BoundNum.zext(InitNum.getBitWidth()); } if (CondOp->getOpcode() == BO_GE || CondOp->getOpcode() == BO_LE) @@ -284,7 +287,7 @@ bool madeNewBranch(ExplodedNode *N, const Stmt *LoopStmt) { return true; ProgramPoint P = N->getLocation(); - if (Optional<BlockEntrance> BE = P.getAs<BlockEntrance>()) + if (std::optional<BlockEntrance> BE = P.getAs<BlockEntrance>()) S = BE->getBlock()->getTerminatorStmt(); if (S == LoopStmt) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp index 47e34dd84b9a..9e4280176062 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp @@ -35,6 +35,8 @@ static const Expr *getLoopCondition(const Stmt *LoopStmt) { return cast<WhileStmt>(LoopStmt)->getCond(); case Stmt::DoStmtClass: return cast<DoStmt>(LoopStmt)->getCond(); + case Stmt::CXXForRangeStmtClass: + return cast<CXXForRangeStmt>(LoopStmt)->getCond(); } } @@ -45,8 +47,7 @@ ProgramStateRef getWidenedLoopState(ProgramStateRef PrevState, const LocationContext *LCtx, unsigned BlockCount, const Stmt *LoopStmt) { - assert(isa<ForStmt>(LoopStmt) || isa<WhileStmt>(LoopStmt) || - isa<DoStmt>(LoopStmt)); + assert((isa<ForStmt, WhileStmt, DoStmt, CXXForRangeStmt>(LoopStmt))); // Invalidate values in the current state. // TODO Make this more conservative by only invalidating values that might @@ -85,7 +86,7 @@ ProgramStateRef getWidenedLoopState(ProgramStateRef PrevState, // pointer should remain unchanged. Ignore static methods, since they do not // have 'this' pointers. const CXXMethodDecl *CXXMD = dyn_cast<CXXMethodDecl>(STC->getDecl()); - if (CXXMD && !CXXMD->isStatic()) { + if (CXXMD && CXXMD->isImplicitObjectMemberFunction()) { const CXXThisRegion *ThisR = MRMgr.getCXXThisRegion(CXXMD->getThisType(), STC); ITraits.setTrait(ThisR, diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/MemRegion.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/MemRegion.cpp index bd725ee9eaa3..16db6b249dc9 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/MemRegion.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -28,17 +28,18 @@ #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceManager.h" +#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" #include "llvm/ADT/APInt.h" #include "llvm/ADT/FoldingSet.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/PointerUnion.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" +#include "llvm/ADT/iterator_range.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/Casting.h" #include "llvm/Support/CheckedArithmetic.h" @@ -50,6 +51,7 @@ #include <cstdint> #include <functional> #include <iterator> +#include <optional> #include <string> #include <tuple> #include <utility> @@ -72,8 +74,7 @@ RegionTy* MemRegionManager::getSubRegion(const Arg1Ty arg1, auto *R = cast_or_null<RegionTy>(Regions.FindNodeOrInsertPos(ID, InsertPos)); if (!R) { - R = A.Allocate<RegionTy>(); - new (R) RegionTy(arg1, superRegion); + R = new (A) RegionTy(arg1, superRegion); Regions.InsertNode(R, InsertPos); } @@ -89,8 +90,7 @@ RegionTy* MemRegionManager::getSubRegion(const Arg1Ty arg1, const Arg2Ty arg2, auto *R = cast_or_null<RegionTy>(Regions.FindNodeOrInsertPos(ID, InsertPos)); if (!R) { - R = A.Allocate<RegionTy>(); - new (R) RegionTy(arg1, arg2, superRegion); + R = new (A) RegionTy(arg1, arg2, superRegion); Regions.InsertNode(R, InsertPos); } @@ -108,8 +108,7 @@ RegionTy* MemRegionManager::getSubRegion(const Arg1Ty arg1, const Arg2Ty arg2, auto *R = cast_or_null<RegionTy>(Regions.FindNodeOrInsertPos(ID, InsertPos)); if (!R) { - R = A.Allocate<RegionTy>(); - new (R) RegionTy(arg1, arg2, arg3, superRegion); + R = new (A) RegionTy(arg1, arg2, arg3, superRegion); Regions.InsertNode(R, InsertPos); } @@ -160,8 +159,22 @@ const StackFrameContext *VarRegion::getStackFrame() const { return SSR ? SSR->getStackFrame() : nullptr; } +const StackFrameContext * +CXXLifetimeExtendedObjectRegion::getStackFrame() const { + const auto *SSR = dyn_cast<StackSpaceRegion>(getMemorySpace()); + return SSR ? SSR->getStackFrame() : nullptr; +} + +const StackFrameContext *CXXTempObjectRegion::getStackFrame() const { + assert(isa<StackSpaceRegion>(getMemorySpace()) && + "A temporary object can only be allocated on the stack"); + return cast<StackSpaceRegion>(getMemorySpace())->getStackFrame(); +} + ObjCIvarRegion::ObjCIvarRegion(const ObjCIvarDecl *ivd, const SubRegion *sReg) - : DeclRegion(sReg, ObjCIvarRegionKind), IVD(ivd) {} + : DeclRegion(sReg, ObjCIvarRegionKind), IVD(ivd) { + assert(IVD); +} const ObjCIvarDecl *ObjCIvarRegion::getDecl() const { return IVD; } @@ -389,6 +402,20 @@ void CXXTempObjectRegion::Profile(llvm::FoldingSetNodeID &ID) const { ProfileRegion(ID, Ex, getSuperRegion()); } +void CXXLifetimeExtendedObjectRegion::ProfileRegion(llvm::FoldingSetNodeID &ID, + const Expr *E, + const ValueDecl *D, + const MemRegion *sReg) { + ID.AddPointer(E); + ID.AddPointer(D); + ID.AddPointer(sReg); +} + +void CXXLifetimeExtendedObjectRegion::Profile( + llvm::FoldingSetNodeID &ID) const { + ProfileRegion(ID, Ex, ExD, getSuperRegion()); +} + void CXXBaseObjectRegion::ProfileRegion(llvm::FoldingSetNodeID &ID, const CXXRecordDecl *RD, bool IsVirtual, @@ -443,7 +470,7 @@ std::string MemRegion::getString() const { std::string s; llvm::raw_string_ostream os(s); dumpToStream(os); - return os.str(); + return s; } void MemRegion::dumpToStream(raw_ostream &os) const { @@ -465,11 +492,9 @@ void BlockCodeRegion::dumpToStream(raw_ostream &os) const { void BlockDataRegion::dumpToStream(raw_ostream &os) const { os << "block_data{" << BC; os << "; "; - for (BlockDataRegion::referenced_vars_iterator - I = referenced_vars_begin(), - E = referenced_vars_end(); I != E; ++I) - os << "(" << I.getCapturedRegion() << "<-" << - I.getOriginalRegion() << ") "; + for (auto Var : referenced_vars()) + os << "(" << Var.getCapturedRegion() << "<-" << Var.getOriginalRegion() + << ") "; os << '}'; } @@ -479,7 +504,17 @@ void CompoundLiteralRegion::dumpToStream(raw_ostream &os) const { } void CXXTempObjectRegion::dumpToStream(raw_ostream &os) const { - os << "temp_object{" << getValueType().getAsString() << ", " + os << "temp_object{" << getValueType() << ", " + << "S" << Ex->getID(getContext()) << '}'; +} + +void CXXLifetimeExtendedObjectRegion::dumpToStream(raw_ostream &os) const { + os << "lifetime_extended_object{" << getValueType() << ", "; + if (const IdentifierInfo *ID = ExD->getIdentifier()) + os << ID->getName(); + else + os << "D" << ExD->getID(); + os << ", " << "S" << Ex->getID(getContext()) << '}'; } @@ -496,8 +531,8 @@ void CXXThisRegion::dumpToStream(raw_ostream &os) const { } void ElementRegion::dumpToStream(raw_ostream &os) const { - os << "Element{" << superRegion << ',' - << Index << ',' << getElementType().getAsString() << '}'; + os << "Element{" << superRegion << ',' << Index << ',' << getElementType() + << '}'; } void FieldRegion::dumpToStream(raw_ostream &os) const { @@ -709,21 +744,17 @@ std::string MemRegion::getDescriptiveName(bool UseQuotes) const { } SourceRange MemRegion::sourceRange() const { - const auto *const VR = dyn_cast<VarRegion>(this->getBaseRegion()); - const auto *const FR = dyn_cast<FieldRegion>(this); - // Check for more specific regions first. - // FieldRegion - if (FR) { + if (auto *FR = dyn_cast<FieldRegion>(this)) { return FR->getDecl()->getSourceRange(); } - // VarRegion - else if (VR) { + + if (auto *VR = dyn_cast<VarRegion>(this->getBaseRegion())) { return VR->getDecl()->getSourceRange(); } + // Return invalid source range (can be checked by client). - else - return {}; + return {}; } //===----------------------------------------------------------------------===// @@ -747,6 +778,7 @@ DefinedOrUnknownSVal MemRegionManager::getStaticSize(const MemRegion *MR, case MemRegion::CXXBaseObjectRegionKind: case MemRegion::CXXDerivedObjectRegionKind: case MemRegion::CXXTempObjectRegionKind: + case MemRegion::CXXLifetimeExtendedObjectRegionKind: case MemRegion::CXXThisRegionKind: case MemRegion::ObjCIvarRegionKind: case MemRegion::NonParamVarRegionKind: @@ -768,14 +800,52 @@ DefinedOrUnknownSVal MemRegionManager::getStaticSize(const MemRegion *MR, return UnknownVal(); QualType Ty = cast<TypedValueRegion>(SR)->getDesugaredValueType(Ctx); - DefinedOrUnknownSVal Size = getElementExtent(Ty, 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(); - } + const DefinedOrUnknownSVal Size = getElementExtent(Ty, SVB); + + // We currently don't model flexible array members (FAMs), which are: + // - int array[]; of IncompleteArrayType + // - int array[0]; of ConstantArrayType with size 0 + // - int array[1]; of ConstantArrayType with size 1 + // https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html + const auto isFlexibleArrayMemberCandidate = + [this](const ArrayType *AT) -> bool { + if (!AT) + return false; + + auto IsIncompleteArray = [](const ArrayType *AT) { + return isa<IncompleteArrayType>(AT); + }; + auto IsArrayOfZero = [](const ArrayType *AT) { + const auto *CAT = dyn_cast<ConstantArrayType>(AT); + return CAT && CAT->getSize() == 0; + }; + auto IsArrayOfOne = [](const ArrayType *AT) { + const auto *CAT = dyn_cast<ConstantArrayType>(AT); + return CAT && CAT->getSize() == 1; + }; + + using FAMKind = LangOptions::StrictFlexArraysLevelKind; + const FAMKind StrictFlexArraysLevel = + Ctx.getLangOpts().getStrictFlexArraysLevel(); + + // "Default": Any trailing array member is a FAM. + // Since we cannot tell at this point if this array is a trailing member + // or not, let's just do the same as for "OneZeroOrIncomplete". + if (StrictFlexArraysLevel == FAMKind::Default) + return IsArrayOfOne(AT) || IsArrayOfZero(AT) || IsIncompleteArray(AT); + + if (StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete) + return IsArrayOfOne(AT) || IsArrayOfZero(AT) || IsIncompleteArray(AT); + + if (StrictFlexArraysLevel == FAMKind::ZeroOrIncomplete) + return IsArrayOfZero(AT) || IsIncompleteArray(AT); + + assert(StrictFlexArraysLevel == FAMKind::IncompleteOnly); + return IsIncompleteArray(AT); + }; + + if (isFlexibleArrayMemberCandidate(Ctx.getAsArrayType(Ty))) + return UnknownVal(); return Size; } @@ -794,8 +864,7 @@ DefinedOrUnknownSVal MemRegionManager::getStaticSize(const MemRegion *MR, template <typename REG> const REG *MemRegionManager::LazyAllocate(REG*& region) { if (!region) { - region = A.Allocate<REG>(); - new (region) REG(*this); + region = new (A) REG(*this); } return region; @@ -804,8 +873,7 @@ const REG *MemRegionManager::LazyAllocate(REG*& region) { template <typename REG, typename ARG> const REG *MemRegionManager::LazyAllocate(REG*& region, ARG a) { if (!region) { - region = A.Allocate<REG>(); - new (region) REG(this, a); + region = new (A) REG(this, a); } return region; @@ -819,8 +887,7 @@ MemRegionManager::getStackLocalsRegion(const StackFrameContext *STC) { if (R) return R; - R = A.Allocate<StackLocalsSpaceRegion>(); - new (R) StackLocalsSpaceRegion(*this, STC); + R = new (A) StackLocalsSpaceRegion(*this, STC); return R; } @@ -832,8 +899,7 @@ MemRegionManager::getStackArgumentsRegion(const StackFrameContext *STC) { if (R) return R; - R = A.Allocate<StackArgumentsSpaceRegion>(); - new (R) StackArgumentsSpaceRegion(*this, STC); + R = new (A) StackArgumentsSpaceRegion(*this, STC); return R; } @@ -854,8 +920,7 @@ const GlobalsSpaceRegion if (R) return R; - R = A.Allocate<StaticGlobalSpaceRegion>(); - new (R) StaticGlobalSpaceRegion(*this, CR); + R = new (A) StaticGlobalSpaceRegion(*this, CR); return R; } @@ -901,13 +966,11 @@ getStackOrCaptureRegionForDeclContext(const LocationContext *LC, if (const auto *BC = dyn_cast<BlockInvocationContext>(LC)) { 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 TypedValueRegion *OrigR = I.getOriginalRegion(); + for (auto Var : BR->referenced_vars()) { + const TypedValueRegion *OrigR = Var.getOriginalRegion(); if (const auto *VR = dyn_cast<VarRegion>(OrigR)) { if (VR->getDecl() == VD) - return cast<VarRegion>(I.getCapturedRegion()); + return cast<VarRegion>(Var.getCapturedRegion()); } } } @@ -945,26 +1008,14 @@ const VarRegion *MemRegionManager::getVarRegion(const VarDecl *D, const MemRegion *sReg = nullptr; if (D->hasGlobalStorage() && !D->isStaticLocal()) { - - // First handle the globals defined in system headers. - 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) - sReg = getGlobalsRegion(MemRegion::GlobalSystemSpaceRegionKind); - else - sReg = getGlobalsRegion(MemRegion::GlobalImmutableSpaceRegionKind); - - // Treat other globals as GlobalInternal unless they are constants. + QualType Ty = D->getType(); + assert(!Ty.isNull()); + if (Ty.isConstQualified()) { + sReg = getGlobalsRegion(MemRegion::GlobalImmutableSpaceRegionKind); + } else if (Ctx.getSourceManager().isInSystemHeader(D->getLocation())) { + sReg = getGlobalsRegion(MemRegion::GlobalSystemSpaceRegionKind); } else { - QualType GQT = D->getType(); - const Type *GT = GQT.getTypePtrOrNull(); - // TODO: We could walk the complex types here and see if everything is - // constified. - if (GT && GQT.isConstQualified() && GT->isArithmeticType()) - sReg = getGlobalsRegion(MemRegion::GlobalImmutableSpaceRegionKind); - else - sReg = getGlobalsRegion(); + sReg = getGlobalsRegion(MemRegion::GlobalInternalSpaceRegionKind); } // Finally handle static locals. @@ -986,14 +1037,15 @@ const VarRegion *MemRegionManager::getVarRegion(const VarDecl *D, sReg = getUnknownRegion(); } else { if (D->hasLocalStorage()) { - sReg = isa<ParmVarDecl>(D) || isa<ImplicitParamDecl>(D) - ? static_cast<const MemRegion*>(getStackArgumentsRegion(STC)) - : static_cast<const MemRegion*>(getStackLocalsRegion(STC)); + sReg = + isa<ParmVarDecl, ImplicitParamDecl>(D) + ? static_cast<const MemRegion *>(getStackArgumentsRegion(STC)) + : static_cast<const MemRegion *>(getStackLocalsRegion(STC)); } else { assert(D->isStaticLocal()); const Decl *STCD = STC->getDecl(); - if (isa<FunctionDecl>(STCD) || isa<ObjCMethodDecl>(STCD)) + if (isa<FunctionDecl, ObjCMethodDecl>(STCD)) sReg = getGlobalsRegion(MemRegion::StaticGlobalSpaceRegionKind, getFunctionCodeRegion(cast<NamedDecl>(STCD))); else if (const auto *BD = dyn_cast<BlockDecl>(STCD)) { @@ -1006,8 +1058,10 @@ const VarRegion *MemRegionManager::getVarRegion(const VarDecl *D, T = TSI->getType(); if (T.isNull()) T = getContext().VoidTy; - if (!T->getAs<FunctionType>()) - T = getContext().getFunctionNoProtoType(T); + if (!T->getAs<FunctionType>()) { + FunctionProtoType::ExtProtoInfo Ext; + T = getContext().getFunctionType(T, std::nullopt, Ext); + } T = getContext().getBlockPointerType(T); const BlockCodeRegion *BTR = @@ -1023,13 +1077,16 @@ const VarRegion *MemRegionManager::getVarRegion(const VarDecl *D, } } - return getSubRegion<NonParamVarRegion>(D, sReg); + return getNonParamVarRegion(D, sReg); } const NonParamVarRegion * MemRegionManager::getNonParamVarRegion(const VarDecl *D, const MemRegion *superR) { + // Prefer the definition over the canonical decl as the canonical form. D = D->getCanonicalDecl(); + if (const VarDecl *Def = D->getDefinition()) + D = Def; return getSubRegion<NonParamVarRegion>(D, superR); } @@ -1053,14 +1110,18 @@ MemRegionManager::getBlockDataRegion(const BlockCodeRegion *BC, sReg = getGlobalsRegion(MemRegion::GlobalImmutableSpaceRegionKind); } else { - if (LC) { + bool IsArcManagedBlock = Ctx.getLangOpts().ObjCAutoRefCount; + + // ARC managed blocks can be initialized on stack or directly in heap + // depending on the implementations. So we initialize them with + // UnknownRegion. + if (!IsArcManagedBlock && LC) { // FIXME: Once we implement scope handling, we want the parent region // to be the scope. const StackFrameContext *STC = LC->getStackFrame(); assert(STC); sReg = getStackLocalsRegion(STC); - } - else { + } else { // We allow 'LC' to be NULL for cases where want BlockDataRegions // without context-sensitivity. sReg = getUnknownRegion(); @@ -1070,12 +1131,6 @@ MemRegionManager::getBlockDataRegion(const BlockCodeRegion *BC, return getSubRegion<BlockDataRegion>(BC, LC, blockCount, sReg); } -const CXXTempObjectRegion * -MemRegionManager::getCXXStaticTempObjectRegion(const Expr *Ex) { - return getSubRegion<CXXTempObjectRegion>( - Ex, getGlobalsRegion(MemRegion::GlobalInternalSpaceRegionKind, nullptr)); -} - const CompoundLiteralRegion* MemRegionManager::getCompoundLiteralRegion(const CompoundLiteralExpr *CL, const LocationContext *LC) { @@ -1106,8 +1161,7 @@ MemRegionManager::getElementRegion(QualType elementType, NonLoc Idx, auto *R = cast_or_null<ElementRegion>(data); if (!R) { - R = A.Allocate<ElementRegion>(); - new (R) ElementRegion(T, Idx, superRegion); + R = new (A) ElementRegion(T, Idx, superRegion); Regions.InsertNode(R, InsertPos); } @@ -1126,9 +1180,12 @@ MemRegionManager::getBlockCodeRegion(const BlockDecl *BD, CanQualType locTy, return getSubRegion<BlockCodeRegion>(BD, locTy, AC, getCodeRegion()); } -/// getSymbolicRegion - Retrieve or create a "symbolic" memory region. -const SymbolicRegion *MemRegionManager::getSymbolicRegion(SymbolRef sym) { - return getSubRegion<SymbolicRegion>(sym, getUnknownRegion()); +const SymbolicRegion * +MemRegionManager::getSymbolicRegion(SymbolRef sym, + const MemSpaceRegion *MemSpace) { + if (MemSpace == nullptr) + MemSpace = getUnknownRegion(); + return getSubRegion<SymbolicRegion>(sym, MemSpace); } const SymbolicRegion *MemRegionManager::getSymbolicHeapRegion(SymbolRef Sym) { @@ -1155,6 +1212,23 @@ MemRegionManager::getCXXTempObjectRegion(Expr const *E, return getSubRegion<CXXTempObjectRegion>(E, getStackLocalsRegion(SFC)); } +const CXXLifetimeExtendedObjectRegion * +MemRegionManager::getCXXLifetimeExtendedObjectRegion( + const Expr *Ex, const ValueDecl *VD, const LocationContext *LC) { + const StackFrameContext *SFC = LC->getStackFrame(); + assert(SFC); + return getSubRegion<CXXLifetimeExtendedObjectRegion>( + Ex, VD, getStackLocalsRegion(SFC)); +} + +const CXXLifetimeExtendedObjectRegion * +MemRegionManager::getCXXStaticLifetimeExtendedObjectRegion( + const Expr *Ex, const ValueDecl *VD) { + return getSubRegion<CXXLifetimeExtendedObjectRegion>( + Ex, VD, + getGlobalsRegion(MemRegion::GlobalInternalSpaceRegionKind, nullptr)); +} + /// Checks whether \p BaseClass is a valid virtual or direct non-virtual base /// class of the type of \p Super. static bool isValidBaseClass(const CXXRecordDecl *BaseClass, @@ -1241,7 +1315,7 @@ const MemSpaceRegion *MemRegion::getMemorySpace() const { SR = dyn_cast<SubRegion>(R); } - return dyn_cast<MemSpaceRegion>(R); + return cast<MemSpaceRegion>(R); } bool MemRegion::hasStackStorage() const { @@ -1256,14 +1330,8 @@ bool MemRegion::hasStackParametersStorage() const { return isa<StackArgumentsSpaceRegion>(getMemorySpace()); } -bool MemRegion::hasGlobalsOrParametersStorage() const { - const MemSpaceRegion *MS = getMemorySpace(); - return isa<StackArgumentsSpaceRegion>(MS) || - isa<GlobalsSpaceRegion>(MS); -} - -// getBaseRegion strips away all elements and fields, and get the base region -// of them. +// Strips away all elements and fields. +// Returns the base region of them. const MemRegion *MemRegion::getBaseRegion() const { const MemRegion *R = this; while (true) { @@ -1283,8 +1351,7 @@ const MemRegion *MemRegion::getBaseRegion() const { return R; } -// getgetMostDerivedObjectRegion gets the region of the root class of a C++ -// class hierarchy. +// Returns the region of the root class of a C++ class hierarchy. const MemRegion *MemRegion::getMostDerivedObjectRegion() const { const MemRegion *R = this; while (const auto *BR = dyn_cast<CXXBaseObjectRegion>(R)) @@ -1435,6 +1502,7 @@ static RegionOffset calculateOffset(const MemRegion *R) { case MemRegion::NonParamVarRegionKind: case MemRegion::ParamVarRegionKind: case MemRegion::CXXTempObjectRegionKind: + case MemRegion::CXXLifetimeExtendedObjectRegionKind: // Usual base regions. goto Finish; @@ -1458,7 +1526,7 @@ static RegionOffset calculateOffset(const MemRegion *R) { // If our base region is symbolic, we don't know what type it really is. // Pretend the type of the symbol is the true dynamic type. // (This will at least be self-consistent for the life of the symbol.) - Ty = SR->getSymbol()->getType()->getPointeeType(); + Ty = SR->getPointeeStaticType(); RootIsSymbolic = true; } @@ -1515,7 +1583,7 @@ static RegionOffset calculateOffset(const MemRegion *R) { } SVal Index = ER->getIndex(); - if (Optional<nonloc::ConcreteInt> CI = + if (std::optional<nonloc::ConcreteInt> CI = Index.getAs<nonloc::ConcreteInt>()) { // Don't bother calculating precise offsets if we already have a // symbolic offset somewhere in the chain. @@ -1625,10 +1693,8 @@ void BlockDataRegion::LazyInitializeReferencedVars() { using VarVec = BumpVector<const MemRegion *>; - auto *BV = A.Allocate<VarVec>(); - new (BV) VarVec(BC, NumBlockVars); - auto *BVOriginal = A.Allocate<VarVec>(); - new (BVOriginal) VarVec(BC, NumBlockVars); + auto *BV = new (A) VarVec(BC, NumBlockVars); + auto *BVOriginal = new (A) VarVec(BC, NumBlockVars); for (const auto *VD : ReferencedBlockVars) { const VarRegion *VR = nullptr; @@ -1676,10 +1742,13 @@ BlockDataRegion::referenced_vars_end() const { VecOriginal->end()); } +llvm::iterator_range<BlockDataRegion::referenced_vars_iterator> +BlockDataRegion::referenced_vars() const { + return llvm::make_range(referenced_vars_begin(), referenced_vars_end()); +} + const VarRegion *BlockDataRegion::getOriginalRegion(const VarRegion *R) const { - for (referenced_vars_iterator I = referenced_vars_begin(), - E = referenced_vars_end(); - I != E; ++I) { + for (const auto &I : referenced_vars()) { if (I.getCapturedRegion() == R) return I.getOriginalRegion(); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp index 92104d628711..be19a1c118ea 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -28,6 +28,7 @@ #include "llvm/ADT/Statistic.h" #include "llvm/Support/Casting.h" #include <memory> +#include <optional> using namespace clang; using namespace ento; @@ -165,7 +166,7 @@ static void printCoverage(const PathDiagnostic *D, FIDMap &FM, llvm::raw_fd_ostream &o); -static Optional<StringRef> getExpandedMacro( +static std::optional<StringRef> getExpandedMacro( SourceLocation MacroLoc, const cross_tu::CrossTranslationUnitContext &CTU, const MacroExpansionContext &MacroExpansions, const SourceManager &SM); @@ -366,10 +367,8 @@ void PlistPrinter::ReportMacroSubPieces(raw_ostream &o, unsigned indent, unsigned depth) { MacroPieces.push_back(&P); - for (PathPieces::const_iterator I = P.subPieces.begin(), - E = P.subPieces.end(); - I != E; ++I) { - ReportPiece(o, **I, indent, depth, /*includeControlFlow*/ false); + for (const auto &SubPiece : P.subPieces) { + ReportPiece(o, *SubPiece, indent, depth, /*includeControlFlow*/ false); } assert(P.getFixits().size() == 0 && @@ -384,12 +383,12 @@ void PlistPrinter::ReportMacroExpansions(raw_ostream &o, unsigned indent) { SourceLocation MacroExpansionLoc = P->getLocation().asLocation().getExpansionLoc(); - const Optional<StringRef> MacroName = + const std::optional<StringRef> MacroName = MacroExpansions.getOriginalText(MacroExpansionLoc); - const Optional<StringRef> ExpansionText = + const std::optional<StringRef> ExpansionText = getExpandedMacro(MacroExpansionLoc, CTU, MacroExpansions, SM); - if (!MacroName.hasValue() || !ExpansionText.hasValue()) + if (!MacroName || !ExpansionText) continue; Indent(o, indent) << "<dict>\n"; @@ -407,11 +406,11 @@ void PlistPrinter::ReportMacroExpansions(raw_ostream &o, unsigned indent) { // Output the macro name. Indent(o, indent) << "<key>name</key>"; - EmitString(o, MacroName.getValue()) << '\n'; + EmitString(o, *MacroName) << '\n'; // Output what it expands into. Indent(o, indent) << "<key>expansion</key>"; - EmitString(o, ExpansionText.getValue()) << '\n'; + EmitString(o, *ExpansionText) << '\n'; // Finish up. --indent; @@ -499,12 +498,12 @@ static void printCoverage(const PathDiagnostic *D, // Mapping from file IDs to executed lines. const FilesToLineNumsMap &ExecutedLines = D->getExecutedLines(); - for (auto I = ExecutedLines.begin(), E = ExecutedLines.end(); I != E; ++I) { - unsigned FileKey = AddFID(FM, Fids, I->first); + for (const auto &[FID, Lines] : ExecutedLines) { + unsigned FileKey = AddFID(FM, Fids, FID); Indent(o, IndentLevel) << "<key>" << FileKey << "</key>\n"; Indent(o, IndentLevel) << "<array>\n"; IndentLevel++; - for (unsigned LineNo : I->second) { + for (unsigned LineNo : Lines) { Indent(o, IndentLevel); EmitInteger(o, LineNo) << "\n"; } @@ -596,8 +595,8 @@ void PlistDiagnostics::printBugPath(llvm::raw_ostream &o, const FIDMap &FM, o << " <array>\n"; - for (PathPieces::const_iterator E = Path.end(); I != E; ++I) - Printer.ReportDiag(o, **I); + for (const auto &Piece : llvm::make_range(I, Path.end())) + Printer.ReportDiag(o, *Piece); o << " </array>\n"; @@ -805,7 +804,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl( o << " <key>files</key>\n" " <array>\n"; for (FileID FID : Fids) - EmitString(o << " ", SM.getFileEntryForID(FID)->getName()) << '\n'; + EmitString(o << " ", SM.getFileEntryRefForID(FID)->getName()) << '\n'; o << " </array>\n"; if (llvm::AreStatisticsEnabled() && DiagOpts.ShouldSerializeStats) { @@ -825,7 +824,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl( // Definitions of helper functions and methods for expanding macros. //===----------------------------------------------------------------------===// -static Optional<StringRef> +static std::optional<StringRef> getExpandedMacro(SourceLocation MacroExpansionLoc, const cross_tu::CrossTranslationUnitContext &CTU, const MacroExpansionContext &MacroExpansions, diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ProgramState.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ProgramState.cpp index 1ccb0de92fba..f12f1a5ac970 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ProgramState.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ProgramState.cpp @@ -19,6 +19,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "llvm/Support/raw_ostream.h" +#include <optional> using namespace clang; using namespace ento; @@ -54,12 +55,8 @@ ProgramState::ProgramState(ProgramStateManager *mgr, const Environment& env, } ProgramState::ProgramState(const ProgramState &RHS) - : llvm::FoldingSetNode(), - stateMgr(RHS.stateMgr), - Env(RHS.Env), - store(RHS.store), - GDM(RHS.GDM), - refCount(0) { + : stateMgr(RHS.stateMgr), Env(RHS.Env), store(RHS.store), GDM(RHS.GDM), + PosteriorlyOverconstrained(RHS.PosteriorlyOverconstrained), refCount(0) { stateMgr->getStoreManager().incrementReferenceCount(store); } @@ -159,9 +156,8 @@ ProgramState::invalidateRegions(RegionList Regions, const CallEvent *Call, RegionAndSymbolInvalidationTraits *ITraits) const { SmallVector<SVal, 8> Values; - for (RegionList::const_iterator I = Regions.begin(), - End = Regions.end(); I != End; ++I) - Values.push_back(loc::MemRegionVal(*I)); + for (const MemRegion *Reg : Regions) + Values.push_back(loc::MemRegionVal(Reg)); return invalidateRegionsImpl(Values, E, Count, LCtx, CausedByPointerEscape, IS, ITraits, Call); @@ -220,8 +216,6 @@ ProgramState::invalidateRegionsImpl(ValueList Values, } ProgramStateRef ProgramState::killBinding(Loc LV) const { - assert(!LV.getAs<loc::MemRegionVal>() && "Use invalidateRegion instead."); - Store OldStore = getStore(); const StoreRef &newStore = getStateManager().StoreMgr->killBinding(OldStore, LV); @@ -318,12 +312,12 @@ ProgramStateRef ProgramState::BindExpr(const Stmt *S, return getStateManager().getPersistentState(NewSt); } -ProgramStateRef ProgramState::assumeInBound(DefinedOrUnknownSVal Idx, - DefinedOrUnknownSVal UpperBound, - bool Assumption, - QualType indexTy) const { +[[nodiscard]] std::pair<ProgramStateRef, ProgramStateRef> +ProgramState::assumeInBoundDual(DefinedOrUnknownSVal Idx, + DefinedOrUnknownSVal UpperBound, + QualType indexTy) const { if (Idx.isUnknown() || UpperBound.isUnknown()) - return this; + return {this, this}; // Build an expression for 0 <= Idx < UpperBound. // This is the same as Idx + MIN < UpperBound + MIN, if overflow is allowed. @@ -342,7 +336,7 @@ ProgramStateRef ProgramState::assumeInBound(DefinedOrUnknownSVal Idx, SVal newIdx = svalBuilder.evalBinOpNN(this, BO_Add, Idx.castAs<NonLoc>(), Min, indexTy); if (newIdx.isUnknownOrUndef()) - return this; + return {this, this}; // Adjust the upper bound. SVal newBound = @@ -350,17 +344,26 @@ ProgramStateRef ProgramState::assumeInBound(DefinedOrUnknownSVal Idx, Min, indexTy); if (newBound.isUnknownOrUndef()) - return this; + return {this, this}; // Build the actual comparison. SVal inBound = svalBuilder.evalBinOpNN(this, BO_LT, newIdx.castAs<NonLoc>(), newBound.castAs<NonLoc>(), Ctx.IntTy); if (inBound.isUnknownOrUndef()) - return this; + return {this, this}; // Finally, let the constraint manager take care of it. ConstraintManager &CM = SM.getConstraintManager(); - return CM.assume(this, inBound.castAs<DefinedSVal>(), Assumption); + return CM.assumeDual(this, inBound.castAs<DefinedSVal>()); +} + +ProgramStateRef ProgramState::assumeInBound(DefinedOrUnknownSVal Idx, + DefinedOrUnknownSVal UpperBound, + bool Assumption, + QualType indexTy) const { + std::pair<ProgramStateRef, ProgramStateRef> R = + assumeInBoundDual(Idx, UpperBound, indexTy); + return Assumption ? R.first : R.second; } ConditionTruthVal ProgramState::isNonNull(SVal V) const { @@ -420,7 +423,7 @@ ProgramStateRef ProgramStateManager::getPersistentState(ProgramState &State) { freeStates.pop_back(); } else { - newState = (ProgramState*) Alloc.Allocate<ProgramState>(); + newState = Alloc.Allocate<ProgramState>(); } new (newState) ProgramState(State); StateSet.InsertNode(newState, InsertPos); @@ -433,6 +436,12 @@ ProgramStateRef ProgramState::makeWithStore(const StoreRef &store) const { return getStateManager().getPersistentState(NewSt); } +ProgramStateRef ProgramState::cloneAsPosteriorlyOverconstrained() const { + ProgramState NewSt(*this); + NewSt.PosteriorlyOverconstrained = true; + return getStateManager().getPersistentState(NewSt); +} + void ProgramState::setStore(const StoreRef &newStore) { Store newStoreStore = newStore.getStore(); if (newStoreStore) @@ -546,22 +555,20 @@ bool ScanReachableSymbols::scan(nonloc::LazyCompoundVal val) { } bool ScanReachableSymbols::scan(nonloc::CompoundVal val) { - for (nonloc::CompoundVal::iterator I=val.begin(), E=val.end(); I!=E; ++I) - if (!scan(*I)) + for (SVal V : val) + if (!scan(V)) return false; return true; } bool ScanReachableSymbols::scan(const SymExpr *sym) { - for (SymExpr::symbol_iterator SI = sym->symbol_begin(), - SE = sym->symbol_end(); - SI != SE; ++SI) { - bool wasVisited = !visited.insert(*SI).second; + for (SymbolRef SubSym : sym->symbols()) { + bool wasVisited = !visited.insert(SubSym).second; if (wasVisited) continue; - if (!visitor.VisitSymbol(*SI)) + if (!visitor.VisitSymbol(SubSym)) return false; } @@ -569,20 +576,20 @@ bool ScanReachableSymbols::scan(const SymExpr *sym) { } bool ScanReachableSymbols::scan(SVal val) { - if (Optional<loc::MemRegionVal> X = val.getAs<loc::MemRegionVal>()) + if (std::optional<loc::MemRegionVal> X = val.getAs<loc::MemRegionVal>()) return scan(X->getRegion()); - if (Optional<nonloc::LazyCompoundVal> X = + if (std::optional<nonloc::LazyCompoundVal> X = val.getAs<nonloc::LazyCompoundVal>()) return scan(*X); - if (Optional<nonloc::LocAsInteger> X = val.getAs<nonloc::LocAsInteger>()) + if (std::optional<nonloc::LocAsInteger> X = val.getAs<nonloc::LocAsInteger>()) return scan(X->getLoc()); if (SymbolRef Sym = val.getAsSymbol()) return scan(Sym); - if (Optional<nonloc::CompoundVal> X = val.getAs<nonloc::CompoundVal>()) + if (std::optional<nonloc::CompoundVal> X = val.getAs<nonloc::CompoundVal>()) return scan(*X); return true; @@ -620,10 +627,8 @@ bool ScanReachableSymbols::scan(const MemRegion *R) { // Regions captured by a block are also implicitly reachable. if (const BlockDataRegion *BDR = dyn_cast<BlockDataRegion>(R)) { - BlockDataRegion::referenced_vars_iterator I = BDR->referenced_vars_begin(), - E = BDR->referenced_vars_end(); - for ( ; I != E; ++I) { - if (!scan(I.getCapturedRegion())) + for (auto Var : BDR->referenced_vars()) { + if (!scan(Var.getCapturedRegion())) return false; } } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp index 69554576bdb2..25d066c4652f 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp @@ -20,12 +20,13 @@ #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/ImmutableSet.h" #include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/StringExtras.h" #include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/raw_ostream.h" #include <algorithm> #include <iterator> +#include <optional> using namespace clang; using namespace ento; @@ -110,6 +111,14 @@ public: RangeSet::ContainerType RangeSet::Factory::EmptySet{}; +RangeSet RangeSet::Factory::add(RangeSet LHS, RangeSet RHS) { + ContainerType Result; + Result.reserve(LHS.size() + RHS.size()); + std::merge(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(), + std::back_inserter(Result)); + return makePersistent(std::move(Result)); +} + RangeSet RangeSet::Factory::add(RangeSet Original, Range Element) { ContainerType Result; Result.reserve(Original.size() + 1); @@ -126,6 +135,186 @@ RangeSet RangeSet::Factory::add(RangeSet Original, const llvm::APSInt &Point) { return add(Original, Range(Point)); } +RangeSet RangeSet::Factory::unite(RangeSet LHS, RangeSet RHS) { + ContainerType Result = unite(*LHS.Impl, *RHS.Impl); + return makePersistent(std::move(Result)); +} + +RangeSet RangeSet::Factory::unite(RangeSet Original, Range R) { + ContainerType Result; + Result.push_back(R); + Result = unite(*Original.Impl, Result); + return makePersistent(std::move(Result)); +} + +RangeSet RangeSet::Factory::unite(RangeSet Original, llvm::APSInt Point) { + return unite(Original, Range(ValueFactory.getValue(Point))); +} + +RangeSet RangeSet::Factory::unite(RangeSet Original, llvm::APSInt From, + llvm::APSInt To) { + return unite(Original, + Range(ValueFactory.getValue(From), ValueFactory.getValue(To))); +} + +template <typename T> +void swapIterators(T &First, T &FirstEnd, T &Second, T &SecondEnd) { + std::swap(First, Second); + std::swap(FirstEnd, SecondEnd); +} + +RangeSet::ContainerType RangeSet::Factory::unite(const ContainerType &LHS, + const ContainerType &RHS) { + if (LHS.empty()) + return RHS; + if (RHS.empty()) + return LHS; + + using llvm::APSInt; + using iterator = ContainerType::const_iterator; + + iterator First = LHS.begin(); + iterator FirstEnd = LHS.end(); + iterator Second = RHS.begin(); + iterator SecondEnd = RHS.end(); + APSIntType Ty = APSIntType(First->From()); + const APSInt Min = Ty.getMinValue(); + + // Handle a corner case first when both range sets start from MIN. + // This helps to avoid complicated conditions below. Specifically, this + // particular check for `MIN` is not needed in the loop below every time + // when we do `Second->From() - One` operation. + if (Min == First->From() && Min == Second->From()) { + if (First->To() > Second->To()) { + // [ First ]---> + // [ Second ]-----> + // MIN^ + // The Second range is entirely inside the First one. + + // Check if Second is the last in its RangeSet. + if (++Second == SecondEnd) + // [ First ]--[ First + 1 ]---> + // [ Second ]---------------------> + // MIN^ + // The Union is equal to First's RangeSet. + return LHS; + } else { + // case 1: [ First ]-----> + // case 2: [ First ]---> + // [ Second ]---> + // MIN^ + // The First range is entirely inside or equal to the Second one. + + // Check if First is the last in its RangeSet. + if (++First == FirstEnd) + // [ First ]-----------------------> + // [ Second ]--[ Second + 1 ]----> + // MIN^ + // The Union is equal to Second's RangeSet. + return RHS; + } + } + + const APSInt One = Ty.getValue(1); + ContainerType Result; + + // This is called when there are no ranges left in one of the ranges. + // Append the rest of the ranges from another range set to the Result + // and return with that. + const auto AppendTheRest = [&Result](iterator I, iterator E) { + Result.append(I, E); + return Result; + }; + + while (true) { + // We want to keep the following invariant at all times: + // ---[ First ------> + // -----[ Second ---> + if (First->From() > Second->From()) + swapIterators(First, FirstEnd, Second, SecondEnd); + + // The Union definitely starts with First->From(). + // ----------[ First ------> + // ------------[ Second ---> + // ----------[ Union ------> + // UnionStart^ + const llvm::APSInt &UnionStart = First->From(); + + // Loop where the invariant holds. + while (true) { + // Skip all enclosed ranges. + // ---[ First ]---> + // -----[ Second ]--[ Second + 1 ]--[ Second + N ]-----> + while (First->To() >= Second->To()) { + // Check if Second is the last in its RangeSet. + if (++Second == SecondEnd) { + // Append the Union. + // ---[ Union ]---> + // -----[ Second ]-----> + // --------[ First ]---> + // UnionEnd^ + Result.emplace_back(UnionStart, First->To()); + // ---[ Union ]-----------------> + // --------------[ First + 1]---> + // Append all remaining ranges from the First's RangeSet. + return AppendTheRest(++First, FirstEnd); + } + } + + // Check if First and Second are disjoint. It means that we find + // the end of the Union. Exit the loop and append the Union. + // ---[ First ]=-------------> + // ------------=[ Second ]---> + // ----MinusOne^ + if (First->To() < Second->From() - One) + break; + + // First is entirely inside the Union. Go next. + // ---[ Union -----------> + // ---- [ First ]--------> + // -------[ Second ]-----> + // Check if First is the last in its RangeSet. + if (++First == FirstEnd) { + // Append the Union. + // ---[ Union ]---> + // -----[ First ]-------> + // --------[ Second ]---> + // UnionEnd^ + Result.emplace_back(UnionStart, Second->To()); + // ---[ Union ]------------------> + // --------------[ Second + 1]---> + // Append all remaining ranges from the Second's RangeSet. + return AppendTheRest(++Second, SecondEnd); + } + + // We know that we are at one of the two cases: + // case 1: --[ First ]---------> + // case 2: ----[ First ]-------> + // --------[ Second ]----------> + // In both cases First starts after Second->From(). + // Make sure that the loop invariant holds. + swapIterators(First, FirstEnd, Second, SecondEnd); + } + + // Here First and Second are disjoint. + // Append the Union. + // ---[ Union ]---------------> + // -----------------[ Second ]---> + // ------[ First ]---------------> + // UnionEnd^ + Result.emplace_back(UnionStart, First->To()); + + // Check if First is the last in its RangeSet. + if (++First == FirstEnd) + // ---[ Union ]---------------> + // --------------[ Second ]---> + // Append all remaining ranges from the Second's RangeSet. + return AppendTheRest(Second, SecondEnd); + } + + llvm_unreachable("Normally, we should not reach here"); +} + RangeSet RangeSet::Factory::getRangeSet(Range From) { ContainerType Result; Result.push_back(From); @@ -155,13 +344,6 @@ RangeSet::ContainerType *RangeSet::Factory::construct(ContainerType &&From) { return new (Buffer) ContainerType(std::move(From)); } -RangeSet RangeSet::Factory::add(RangeSet LHS, RangeSet RHS) { - ContainerType Result; - std::merge(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(), - std::back_inserter(Result)); - return makePersistent(std::move(Result)); -} - const llvm::APSInt &RangeSet::getMinValue() const { assert(!isEmpty()); return begin()->From(); @@ -172,6 +354,21 @@ const llvm::APSInt &RangeSet::getMaxValue() const { return std::prev(end())->To(); } +bool clang::ento::RangeSet::isUnsigned() const { + assert(!isEmpty()); + return begin()->From().isUnsigned(); +} + +uint32_t clang::ento::RangeSet::getBitWidth() const { + assert(!isEmpty()); + return begin()->From().getBitWidth(); +} + +APSIntType clang::ento::RangeSet::getAPSIntType() const { + assert(!isEmpty()); + return APSIntType(begin()->From()); +} + bool RangeSet::containsImpl(llvm::APSInt &Point) const { if (isEmpty() || !pin(Point)) return false; @@ -325,11 +522,6 @@ RangeSet RangeSet::Factory::intersect(const RangeSet::ContainerType &LHS, const_iterator First = LHS.begin(), Second = RHS.begin(), FirstEnd = LHS.end(), SecondEnd = RHS.end(); - const auto SwapIterators = [&First, &FirstEnd, &Second, &SecondEnd]() { - std::swap(First, Second); - std::swap(FirstEnd, SecondEnd); - }; - // If we ran out of ranges in one set, but not in the other, // it means that those elements are definitely not in the // intersection. @@ -339,7 +531,7 @@ RangeSet RangeSet::Factory::intersect(const RangeSet::ContainerType &LHS, // ----[ First ----------------------> // --------[ Second -----------------> if (Second->From() < First->From()) - SwapIterators(); + swapIterators(First, FirstEnd, Second, SecondEnd); // Loop where the invariant holds: do { @@ -373,7 +565,7 @@ RangeSet RangeSet::Factory::intersect(const RangeSet::ContainerType &LHS, if (Second->To() > First->To()) { // Here we make a decision to keep First as the "longer" // range. - SwapIterators(); + swapIterators(First, FirstEnd, Second, SecondEnd); } // At this point, we have the following situation: @@ -479,6 +671,181 @@ RangeSet RangeSet::Factory::negate(RangeSet What) { return makePersistent(std::move(Result)); } +// Convert range set to the given integral type using truncation and promotion. +// This works similar to APSIntType::apply function but for the range set. +RangeSet RangeSet::Factory::castTo(RangeSet What, APSIntType Ty) { + // Set is empty or NOOP (aka cast to the same type). + if (What.isEmpty() || What.getAPSIntType() == Ty) + return What; + + const bool IsConversion = What.isUnsigned() != Ty.isUnsigned(); + const bool IsTruncation = What.getBitWidth() > Ty.getBitWidth(); + const bool IsPromotion = What.getBitWidth() < Ty.getBitWidth(); + + if (IsTruncation) + return makePersistent(truncateTo(What, Ty)); + + // Here we handle 2 cases: + // - IsConversion && !IsPromotion. + // In this case we handle changing a sign with same bitwidth: char -> uchar, + // uint -> int. Here we convert negatives to positives and positives which + // is out of range to negatives. We use convertTo function for that. + // - IsConversion && IsPromotion && !What.isUnsigned(). + // In this case we handle changing a sign from signeds to unsigneds with + // higher bitwidth: char -> uint, int-> uint64. The point is that we also + // need convert negatives to positives and use convertTo function as well. + // For example, we don't need such a convertion when converting unsigned to + // signed with higher bitwidth, because all the values of unsigned is valid + // for the such signed. + if (IsConversion && (!IsPromotion || !What.isUnsigned())) + return makePersistent(convertTo(What, Ty)); + + assert(IsPromotion && "Only promotion operation from unsigneds left."); + return makePersistent(promoteTo(What, Ty)); +} + +RangeSet RangeSet::Factory::castTo(RangeSet What, QualType T) { + assert(T->isIntegralOrEnumerationType() && "T shall be an integral type."); + return castTo(What, ValueFactory.getAPSIntType(T)); +} + +RangeSet::ContainerType RangeSet::Factory::truncateTo(RangeSet What, + APSIntType Ty) { + using llvm::APInt; + using llvm::APSInt; + ContainerType Result; + ContainerType Dummy; + // CastRangeSize is an amount of all possible values of cast type. + // Example: `char` has 256 values; `short` has 65536 values. + // But in fact we use `amount of values` - 1, because + // we can't keep `amount of values of UINT64` inside uint64_t. + // E.g. 256 is an amount of all possible values of `char` and we can't keep + // it inside `char`. + // And it's OK, it's enough to do correct calculations. + uint64_t CastRangeSize = APInt::getMaxValue(Ty.getBitWidth()).getZExtValue(); + for (const Range &R : What) { + // Get bounds of the given range. + APSInt FromInt = R.From(); + APSInt ToInt = R.To(); + // CurrentRangeSize is an amount of all possible values of the current + // range minus one. + uint64_t CurrentRangeSize = (ToInt - FromInt).getZExtValue(); + // This is an optimization for a specific case when this Range covers + // the whole range of the target type. + Dummy.clear(); + if (CurrentRangeSize >= CastRangeSize) { + Dummy.emplace_back(ValueFactory.getMinValue(Ty), + ValueFactory.getMaxValue(Ty)); + Result = std::move(Dummy); + break; + } + // Cast the bounds. + Ty.apply(FromInt); + Ty.apply(ToInt); + const APSInt &PersistentFrom = ValueFactory.getValue(FromInt); + const APSInt &PersistentTo = ValueFactory.getValue(ToInt); + if (FromInt > ToInt) { + Dummy.emplace_back(ValueFactory.getMinValue(Ty), PersistentTo); + Dummy.emplace_back(PersistentFrom, ValueFactory.getMaxValue(Ty)); + } else + Dummy.emplace_back(PersistentFrom, PersistentTo); + // Every range retrieved after truncation potentialy has garbage values. + // So, we have to unite every next range with the previouses. + Result = unite(Result, Dummy); + } + + return Result; +} + +// Divide the convertion into two phases (presented as loops here). +// First phase(loop) works when casted values go in ascending order. +// E.g. char{1,3,5,127} -> uint{1,3,5,127} +// Interrupt the first phase and go to second one when casted values start +// go in descending order. That means that we crossed over the middle of +// the type value set (aka 0 for signeds and MAX/2+1 for unsigneds). +// For instance: +// 1: uchar{1,3,5,128,255} -> char{1,3,5,-128,-1} +// Here we put {1,3,5} to one array and {-128, -1} to another +// 2: char{-128,-127,-1,0,1,2} -> uchar{128,129,255,0,1,3} +// Here we put {128,129,255} to one array and {0,1,3} to another. +// After that we unite both arrays. +// NOTE: We don't just concatenate the arrays, because they may have +// adjacent ranges, e.g.: +// 1: char(-128, 127) -> uchar -> arr1(128, 255), arr2(0, 127) -> +// unite -> uchar(0, 255) +// 2: uchar(0, 1)U(254, 255) -> char -> arr1(0, 1), arr2(-2, -1) -> +// unite -> uchar(-2, 1) +RangeSet::ContainerType RangeSet::Factory::convertTo(RangeSet What, + APSIntType Ty) { + using llvm::APInt; + using llvm::APSInt; + using Bounds = std::pair<const APSInt &, const APSInt &>; + ContainerType AscendArray; + ContainerType DescendArray; + auto CastRange = [Ty, &VF = ValueFactory](const Range &R) -> Bounds { + // Get bounds of the given range. + APSInt FromInt = R.From(); + APSInt ToInt = R.To(); + // Cast the bounds. + Ty.apply(FromInt); + Ty.apply(ToInt); + return {VF.getValue(FromInt), VF.getValue(ToInt)}; + }; + // Phase 1. Fill the first array. + APSInt LastConvertedInt = Ty.getMinValue(); + const auto *It = What.begin(); + const auto *E = What.end(); + while (It != E) { + Bounds NewBounds = CastRange(*(It++)); + // If values stop going acsending order, go to the second phase(loop). + if (NewBounds.first < LastConvertedInt) { + DescendArray.emplace_back(NewBounds.first, NewBounds.second); + break; + } + // If the range contains a midpoint, then split the range. + // E.g. char(-5, 5) -> uchar(251, 5) + // Here we shall add a range (251, 255) to the first array and (0, 5) to the + // second one. + if (NewBounds.first > NewBounds.second) { + DescendArray.emplace_back(ValueFactory.getMinValue(Ty), NewBounds.second); + AscendArray.emplace_back(NewBounds.first, ValueFactory.getMaxValue(Ty)); + } else + // Values are going acsending order. + AscendArray.emplace_back(NewBounds.first, NewBounds.second); + LastConvertedInt = NewBounds.first; + } + // Phase 2. Fill the second array. + while (It != E) { + Bounds NewBounds = CastRange(*(It++)); + DescendArray.emplace_back(NewBounds.first, NewBounds.second); + } + // Unite both arrays. + return unite(AscendArray, DescendArray); +} + +/// Promotion from unsigneds to signeds/unsigneds left. +RangeSet::ContainerType RangeSet::Factory::promoteTo(RangeSet What, + APSIntType Ty) { + ContainerType Result; + // We definitely know the size of the result set. + Result.reserve(What.size()); + + // Each unsigned value fits every larger type without any changes, + // whether the larger type is signed or unsigned. So just promote and push + // back each range one by one. + for (const Range &R : What) { + // Get bounds of the given range. + llvm::APSInt FromInt = R.From(); + llvm::APSInt ToInt = R.To(); + // Cast the bounds. + Ty.apply(FromInt); + Ty.apply(ToInt); + Result.emplace_back(ValueFactory.getValue(FromInt), + ValueFactory.getValue(ToInt)); + } + return Result; +} + RangeSet RangeSet::Factory::deletePoint(RangeSet From, const llvm::APSInt &Point) { if (!From.contains(Point)) @@ -494,15 +861,17 @@ RangeSet RangeSet::Factory::deletePoint(RangeSet From, return intersect(From, Upper, Lower); } -void Range::dump(raw_ostream &OS) const { +LLVM_DUMP_METHOD void Range::dump(raw_ostream &OS) const { OS << '[' << toString(From(), 10) << ", " << toString(To(), 10) << ']'; } +LLVM_DUMP_METHOD void Range::dump() const { dump(llvm::errs()); } -void RangeSet::dump(raw_ostream &OS) const { +LLVM_DUMP_METHOD void RangeSet::dump(raw_ostream &OS) const { OS << "{ "; llvm::interleaveComma(*this, OS, [&OS](const Range &R) { R.dump(OS); }); OS << " }"; } +LLVM_DUMP_METHOD void RangeSet::dump() const { dump(llvm::errs()); } REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(SymbolSet, SymbolRef) @@ -545,20 +914,20 @@ namespace { class EquivalenceClass : public llvm::FoldingSetNode { public: /// Find equivalence class for the given symbol in the given state. - LLVM_NODISCARD static inline EquivalenceClass find(ProgramStateRef State, - SymbolRef Sym); + [[nodiscard]] static inline EquivalenceClass find(ProgramStateRef State, + SymbolRef Sym); /// Merge classes for the given symbols and return a new state. - LLVM_NODISCARD static inline ProgramStateRef merge(RangeSet::Factory &F, - ProgramStateRef State, - SymbolRef First, - SymbolRef Second); + [[nodiscard]] static inline ProgramStateRef merge(RangeSet::Factory &F, + ProgramStateRef State, + SymbolRef First, + SymbolRef Second); // Merge this class with the given class and return a new state. - LLVM_NODISCARD inline ProgramStateRef + [[nodiscard]] inline ProgramStateRef merge(RangeSet::Factory &F, ProgramStateRef State, EquivalenceClass Other); /// Return a set of class members for the given state. - LLVM_NODISCARD inline SymbolSet getClassMembers(ProgramStateRef State) const; + [[nodiscard]] inline SymbolSet getClassMembers(ProgramStateRef State) const; /// Return true if the current class is trivial in the given state. /// A class is trivial if and only if there is not any member relations stored @@ -571,39 +940,42 @@ public: /// members and then during the removal of dead symbols we remove one of its /// members. In this case, the class is still non-trivial (it still has the /// mappings in ClassMembers), even though it has only one member. - LLVM_NODISCARD inline bool isTrivial(ProgramStateRef State) const; + [[nodiscard]] inline bool isTrivial(ProgramStateRef State) const; /// Return true if the current class is trivial and its only member is dead. - LLVM_NODISCARD inline bool isTriviallyDead(ProgramStateRef State, - SymbolReaper &Reaper) const; + [[nodiscard]] inline bool isTriviallyDead(ProgramStateRef State, + SymbolReaper &Reaper) const; - LLVM_NODISCARD static inline ProgramStateRef + [[nodiscard]] static inline ProgramStateRef markDisequal(RangeSet::Factory &F, ProgramStateRef State, SymbolRef First, SymbolRef Second); - LLVM_NODISCARD static inline ProgramStateRef + [[nodiscard]] static inline ProgramStateRef markDisequal(RangeSet::Factory &F, ProgramStateRef State, EquivalenceClass First, EquivalenceClass Second); - LLVM_NODISCARD inline ProgramStateRef + [[nodiscard]] inline ProgramStateRef markDisequal(RangeSet::Factory &F, ProgramStateRef State, EquivalenceClass Other) const; - LLVM_NODISCARD static inline ClassSet - getDisequalClasses(ProgramStateRef State, SymbolRef Sym); - LLVM_NODISCARD inline ClassSet - getDisequalClasses(ProgramStateRef State) const; - LLVM_NODISCARD inline ClassSet + [[nodiscard]] static inline ClassSet getDisequalClasses(ProgramStateRef State, + SymbolRef Sym); + [[nodiscard]] inline ClassSet getDisequalClasses(ProgramStateRef State) const; + [[nodiscard]] inline ClassSet getDisequalClasses(DisequalityMapTy Map, ClassSet::Factory &Factory) const; - LLVM_NODISCARD static inline Optional<bool> areEqual(ProgramStateRef State, - EquivalenceClass First, - EquivalenceClass Second); - LLVM_NODISCARD static inline Optional<bool> + [[nodiscard]] static inline std::optional<bool> + areEqual(ProgramStateRef State, EquivalenceClass First, + EquivalenceClass Second); + [[nodiscard]] static inline std::optional<bool> areEqual(ProgramStateRef State, SymbolRef First, SymbolRef Second); + /// Remove one member from the class. + [[nodiscard]] ProgramStateRef removeMember(ProgramStateRef State, + const SymbolRef Old); + /// Iterate over all symbols and try to simplify them. - LLVM_NODISCARD static inline ProgramStateRef simplify(SValBuilder &SVB, - RangeSet::Factory &F, - ProgramStateRef State, - EquivalenceClass Class); + [[nodiscard]] static inline ProgramStateRef simplify(SValBuilder &SVB, + RangeSet::Factory &F, + ProgramStateRef State, + EquivalenceClass Class); void dumpToStream(ProgramStateRef State, raw_ostream &os) const; LLVM_DUMP_METHOD void dump(ProgramStateRef State) const { @@ -611,10 +983,10 @@ public: } /// Check equivalence data for consistency. - LLVM_NODISCARD LLVM_ATTRIBUTE_UNUSED static bool + [[nodiscard]] LLVM_ATTRIBUTE_UNUSED static bool isClassDataConsistent(ProgramStateRef State); - LLVM_NODISCARD QualType getType() const { + [[nodiscard]] QualType getType() const { return getRepresentativeSymbol()->getType(); } @@ -655,6 +1027,7 @@ private: inline ProgramStateRef mergeImpl(RangeSet::Factory &F, ProgramStateRef State, SymbolSet Members, EquivalenceClass Other, SymbolSet OtherMembers); + static inline bool addToDisequalityInfo(DisequalityMapTy &Info, ConstraintRangeTy &Constraints, RangeSet::Factory &F, ProgramStateRef State, @@ -668,7 +1041,7 @@ private: // Constraint functions //===----------------------------------------------------------------------===// -LLVM_NODISCARD LLVM_ATTRIBUTE_UNUSED bool +[[nodiscard]] LLVM_ATTRIBUTE_UNUSED bool areFeasible(ConstraintRangeTy Constraints) { return llvm::none_of( Constraints, @@ -677,24 +1050,24 @@ areFeasible(ConstraintRangeTy Constraints) { }); } -LLVM_NODISCARD inline const RangeSet *getConstraint(ProgramStateRef State, - EquivalenceClass Class) { +[[nodiscard]] inline const RangeSet *getConstraint(ProgramStateRef State, + EquivalenceClass Class) { return State->get<ConstraintRange>(Class); } -LLVM_NODISCARD inline const RangeSet *getConstraint(ProgramStateRef State, - SymbolRef Sym) { +[[nodiscard]] inline const RangeSet *getConstraint(ProgramStateRef State, + SymbolRef Sym) { return getConstraint(State, EquivalenceClass::find(State, Sym)); } -LLVM_NODISCARD ProgramStateRef setConstraint(ProgramStateRef State, - EquivalenceClass Class, - RangeSet Constraint) { +[[nodiscard]] ProgramStateRef setConstraint(ProgramStateRef State, + EquivalenceClass Class, + RangeSet Constraint) { return State->set<ConstraintRange>(Class, Constraint); } -LLVM_NODISCARD ProgramStateRef setConstraints(ProgramStateRef State, - ConstraintRangeTy Constraints) { +[[nodiscard]] ProgramStateRef setConstraints(ProgramStateRef State, + ConstraintRangeTy Constraints) { return State->set<ConstraintRange>(Constraints); } @@ -710,8 +1083,8 @@ LLVM_NODISCARD ProgramStateRef setConstraints(ProgramStateRef State, /// /// \returns true if assuming this Sym to be true means equality of operands /// false if it means disequality of operands -/// None otherwise -Optional<bool> meansEquality(const SymSymExpr *Sym) { +/// std::nullopt otherwise +std::optional<bool> meansEquality(const SymSymExpr *Sym) { switch (Sym->getOpcode()) { case BO_Sub: // This case is: A - B != 0 -> disequality check. @@ -723,7 +1096,7 @@ Optional<bool> meansEquality(const SymSymExpr *Sym) { // This case is: A != B != 0 -> diseqiality check. return false; default: - return llvm::None; + return std::nullopt; } } @@ -732,8 +1105,8 @@ Optional<bool> meansEquality(const SymSymExpr *Sym) { //===----------------------------------------------------------------------===// template <class SecondTy, class... RestTy> -LLVM_NODISCARD inline RangeSet intersect(RangeSet::Factory &F, RangeSet Head, - SecondTy Second, RestTy... Tail); +[[nodiscard]] inline RangeSet intersect(RangeSet::Factory &F, RangeSet Head, + SecondTy Second, RestTy... Tail); template <class... RangeTy> struct IntersectionTraits; @@ -745,7 +1118,7 @@ template <class... TailTy> struct IntersectionTraits<RangeSet, TailTy...> { template <> struct IntersectionTraits<> { // We ran out of types, and we didn't find any RangeSet, so the result should // be optional. - using Type = Optional<RangeSet>; + using Type = std::optional<RangeSet>; }; template <class OptionalOrPointer, class... TailTy> @@ -755,32 +1128,33 @@ struct IntersectionTraits<OptionalOrPointer, TailTy...> { }; template <class EndTy> -LLVM_NODISCARD inline EndTy intersect(RangeSet::Factory &F, EndTy End) { - // If the list contains only RangeSet or Optional<RangeSet>, simply return - // that range set. +[[nodiscard]] inline EndTy intersect(RangeSet::Factory &F, EndTy End) { + // If the list contains only RangeSet or std::optional<RangeSet>, simply + // return that range set. return End; } -LLVM_NODISCARD LLVM_ATTRIBUTE_UNUSED inline Optional<RangeSet> +[[nodiscard]] LLVM_ATTRIBUTE_UNUSED inline std::optional<RangeSet> intersect(RangeSet::Factory &F, const RangeSet *End) { - // This is an extraneous conversion from a raw pointer into Optional<RangeSet> + // This is an extraneous conversion from a raw pointer into + // std::optional<RangeSet> if (End) { return *End; } - return llvm::None; + return std::nullopt; } template <class... RestTy> -LLVM_NODISCARD inline RangeSet intersect(RangeSet::Factory &F, RangeSet Head, - RangeSet Second, RestTy... Tail) { +[[nodiscard]] inline RangeSet intersect(RangeSet::Factory &F, RangeSet Head, + RangeSet Second, RestTy... Tail) { // Here we call either the <RangeSet,RangeSet,...> or <RangeSet,...> version // of the function and can be sure that the result is RangeSet. return intersect(F, F.intersect(Head, Second), Tail...); } template <class SecondTy, class... RestTy> -LLVM_NODISCARD inline RangeSet intersect(RangeSet::Factory &F, RangeSet Head, - SecondTy Second, RestTy... Tail) { +[[nodiscard]] inline RangeSet intersect(RangeSet::Factory &F, RangeSet Head, + SecondTy Second, RestTy... Tail) { if (Second) { // Here we call the <RangeSet,RangeSet,...> version of the function... return intersect(F, Head, *Second, Tail...); @@ -792,11 +1166,12 @@ LLVM_NODISCARD inline RangeSet intersect(RangeSet::Factory &F, RangeSet Head, /// Main generic intersect function. /// It intersects all of the given range sets. If some of the given arguments -/// don't hold a range set (nullptr or llvm::None), the function will skip them. +/// don't hold a range set (nullptr or std::nullopt), the function will skip +/// them. /// /// Available representations for the arguments are: /// * RangeSet -/// * Optional<RangeSet> +/// * std::optional<RangeSet> /// * RangeSet * /// Pointer to a RangeSet is automatically assumed to be nullable and will get /// checked as well as the optional version. If this behaviour is undesired, @@ -804,13 +1179,14 @@ LLVM_NODISCARD inline RangeSet intersect(RangeSet::Factory &F, RangeSet Head, /// /// Return type depends on the arguments' types. If we can be sure in compile /// time that there will be a range set as a result, the returning type is -/// simply RangeSet, in other cases we have to back off to Optional<RangeSet>. +/// simply RangeSet, in other cases we have to back off to +/// std::optional<RangeSet>. /// /// Please, prefer optional range sets to raw pointers. If the last argument is -/// a raw pointer and all previous arguments are None, it will cost one -/// additional check to convert RangeSet * into Optional<RangeSet>. +/// a raw pointer and all previous arguments are std::nullopt, it will cost one +/// additional check to convert RangeSet * into std::optional<RangeSet>. template <class HeadTy, class SecondTy, class... RestTy> -LLVM_NODISCARD inline +[[nodiscard]] inline typename IntersectionTraits<HeadTy, SecondTy, RestTy...>::Type intersect(RangeSet::Factory &F, HeadTy Head, SecondTy Second, RestTy... Tail) { @@ -840,13 +1216,21 @@ public: } RangeSet VisitSymExpr(SymbolRef Sym) { - // If we got to this function, the actual type of the symbolic + if (std::optional<RangeSet> RS = getRangeForNegatedSym(Sym)) + return *RS; + // If we've reached this line, 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 VisitUnarySymExpr(const UnarySymExpr *USE) { + if (std::optional<RangeSet> RS = getRangeForNegatedUnarySym(USE)) + return *RS; + return infer(USE->getType()); + } + RangeSet VisitSymIntExpr(const SymIntExpr *Sym) { return VisitBinaryOperator(Sym); } @@ -855,14 +1239,25 @@ public: return VisitBinaryOperator(Sym); } - RangeSet VisitSymSymExpr(const SymSymExpr *Sym) { + RangeSet VisitSymSymExpr(const SymSymExpr *SSE) { return intersect( RangeFactory, + // If Sym is a difference of symbols A - B, then maybe we have range + // set stored for B - A. + // + // If we have range set stored for both A - B and B - A then + // calculate the effective range set by intersecting the range set + // for A - B and the negated range set of B - A. + getRangeForNegatedSymSym(SSE), + // If Sym is a comparison expression (except <=>), + // find any other comparisons with the same operands. + // See function description. + getRangeForComparisonSymbol(SSE), // If Sym is (dis)equality, we might have some information // on that in our equality classes data structure. - getRangeForEqualities(Sym), + getRangeForEqualities(SSE), // And we should always check what we can get from the operands. - VisitBinaryOperator(Sym)); + VisitBinaryOperator(SSE)); } private: @@ -891,25 +1286,13 @@ private: } RangeSet infer(SymbolRef Sym) { - return intersect( - RangeFactory, - // Of course, we should take the constraint directly associated with - // this symbol into consideration. - getConstraint(State, Sym), - // If Sym is a difference of symbols A - B, then maybe we have range - // set stored for B - A. - // - // If we have range set stored for both A - B and B - A then - // calculate the effective range set by intersecting the range set - // for A - B and the negated range set of B - A. - getRangeForNegatedSub(Sym), - // If Sym is a comparison expression (except <=>), - // find any other comparisons with the same operands. - // See function description. - getRangeForComparisonSymbol(Sym), - // Apart from the Sym itself, we can infer quite a lot if we look - // into subexpressions of Sym. - Visit(Sym)); + return intersect(RangeFactory, + // Of course, we should take the constraint directly + // associated with this symbol into consideration. + getConstraint(State, Sym), + // Apart from the Sym itself, we can infer quite a lot if + // we look into subexpressions of Sym. + Visit(Sym)); } RangeSet infer(EquivalenceClass Class) { @@ -953,18 +1336,7 @@ private: } 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); - } - } + RangeSet RHS, QualType T); //===----------------------------------------------------------------------===// // Ranges and operators @@ -982,11 +1354,11 @@ private: /// 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) { + /// It will return std::nullopt only when the trivial conversion is possible. + std::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 std::nullopt; } return Range(ValueFactory.Convert(To, Origin.From()), ValueFactory.Convert(To, Origin.To())); @@ -994,11 +1366,7 @@ private: 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(); - } + assert(!LHS.isEmpty() && !RHS.isEmpty()); Range CoarseLHS = fillGaps(LHS); Range CoarseRHS = fillGaps(RHS); @@ -1070,31 +1438,51 @@ private: return RangeFactory.deletePoint(Domain, 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. - Optional<RangeSet> getRangeForNegatedSub(SymbolRef Sym) { - if (const SymSymExpr *SSE = dyn_cast<SymSymExpr>(Sym)) { - if (SSE->getOpcode() == BO_Sub) { - QualType T = Sym->getType(); + template <typename ProduceNegatedSymFunc> + std::optional<RangeSet> getRangeForNegatedExpr(ProduceNegatedSymFunc F, + QualType T) { + // Do not negate if the type cannot be meaningfully negated. + if (!T->isUnsignedIntegerOrEnumerationType() && + !T->isSignedIntegerOrEnumerationType()) + return std::nullopt; + + if (SymbolRef NegatedSym = F()) + if (const RangeSet *NegatedRange = getConstraint(State, NegatedSym)) + return RangeFactory.negate(*NegatedRange); - // Do not negate unsigned ranges - if (!T->isUnsignedIntegerOrEnumerationType() && - !T->isSignedIntegerOrEnumerationType()) - return llvm::None; + return std::nullopt; + } - SymbolManager &SymMgr = State->getSymbolManager(); - SymbolRef NegatedSym = - SymMgr.getSymSymExpr(SSE->getRHS(), BO_Sub, SSE->getLHS(), T); + std::optional<RangeSet> getRangeForNegatedUnarySym(const UnarySymExpr *USE) { + // Just get the operand when we negate a symbol that is already negated. + // -(-a) == a + return getRangeForNegatedExpr( + [USE]() -> SymbolRef { + if (USE->getOpcode() == UO_Minus) + return USE->getOperand(); + return nullptr; + }, + USE->getType()); + } - if (const RangeSet *NegatedRange = getConstraint(State, NegatedSym)) { - return RangeFactory.negate(*NegatedRange); - } - } - } - return llvm::None; + std::optional<RangeSet> getRangeForNegatedSymSym(const SymSymExpr *SSE) { + return getRangeForNegatedExpr( + [SSE, State = this->State]() -> SymbolRef { + if (SSE->getOpcode() == BO_Sub) + return State->getSymbolManager().getSymSymExpr( + SSE->getRHS(), BO_Sub, SSE->getLHS(), SSE->getType()); + return nullptr; + }, + SSE->getType()); + } + + std::optional<RangeSet> getRangeForNegatedSym(SymbolRef Sym) { + return getRangeForNegatedExpr( + [Sym, State = this->State]() { + return State->getSymbolManager().getUnarySymExpr(Sym, UO_Minus, + Sym->getType()); + }, + Sym->getType()); } // Returns ranges only for binary comparison operators (except <=>) @@ -1107,16 +1495,12 @@ private: // It covers all possible combinations (see CmpOpTable description). // Note that `x` and `y` can also stand for subexpressions, // not only for actual symbols. - Optional<RangeSet> getRangeForComparisonSymbol(SymbolRef Sym) { - const auto *SSE = dyn_cast<SymSymExpr>(Sym); - if (!SSE) - return llvm::None; - - BinaryOperatorKind CurrentOP = SSE->getOpcode(); + std::optional<RangeSet> getRangeForComparisonSymbol(const SymSymExpr *SSE) { + const BinaryOperatorKind CurrentOP = SSE->getOpcode(); // We currently do not support <=> (C++20). if (!BinaryOperator::isComparisonOp(CurrentOP) || (CurrentOP == BO_Cmp)) - return llvm::None; + return std::nullopt; static const OperatorRelationsTable CmpOpTable{}; @@ -1126,7 +1510,12 @@ private: SymbolManager &SymMgr = State->getSymbolManager(); - int UnknownStates = 0; + // We use this variable to store the last queried operator (`QueriedOP`) + // for which the `getCmpOpState` returned with `Unknown`. If there are two + // different OPs that returned `Unknown` then we have to query the special + // `UnknownX2` column. We assume that `getCmpOpState(CurrentOP, CurrentOP)` + // never returns `Unknown`, so `CurrentOP` is a good initial value. + BinaryOperatorKind LastQueriedOpToUnknown = CurrentOP; // Loop goes through all of the columns exept the last one ('UnknownX2'). // We treat `UnknownX2` column separately at the end of the loop body. @@ -1163,31 +1552,34 @@ private: CmpOpTable.getCmpOpState(CurrentOP, QueriedOP); if (BranchState == OperatorRelationsTable::Unknown) { - if (++UnknownStates == 2) - // If we met both Unknown states. + if (LastQueriedOpToUnknown != CurrentOP && + LastQueriedOpToUnknown != QueriedOP) { + // If we got the Unknown state for both different operators. // 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 + } else { + LastQueriedOpToUnknown = QueriedOP; continue; + } } return (BranchState == OperatorRelationsTable::True) ? getTrueRange(T) : getFalseRange(T); } - return llvm::None; + return std::nullopt; } - Optional<RangeSet> getRangeForEqualities(const SymSymExpr *Sym) { - Optional<bool> Equality = meansEquality(Sym); + std::optional<RangeSet> getRangeForEqualities(const SymSymExpr *Sym) { + std::optional<bool> Equality = meansEquality(Sym); if (!Equality) - return llvm::None; + return std::nullopt; - if (Optional<bool> AreEqual = + if (std::optional<bool> AreEqual = EquivalenceClass::areEqual(State, Sym->getLHS(), Sym->getRHS())) { // Here we cover two cases at once: // * if Sym is equality and its operands are known to be equal -> true @@ -1199,7 +1591,7 @@ private: return getFalseRange(Sym->getType()); } - return llvm::None; + return std::nullopt; } RangeSet getTrueRange(QualType T) { @@ -1222,6 +1614,57 @@ private: //===----------------------------------------------------------------------===// template <> +RangeSet SymbolicRangeInferrer::VisitBinaryOperator<BO_NE>(RangeSet LHS, + RangeSet RHS, + QualType T) { + assert(!LHS.isEmpty() && !RHS.isEmpty()); + + if (LHS.getAPSIntType() == RHS.getAPSIntType()) { + if (intersect(RangeFactory, LHS, RHS).isEmpty()) + return getTrueRange(T); + + } else { + // We can only lose information if we are casting smaller signed type to + // bigger unsigned type. For e.g., + // LHS (unsigned short): [2, USHRT_MAX] + // RHS (signed short): [SHRT_MIN, 0] + // + // Casting RHS to LHS type will leave us with overlapping values + // CastedRHS : [0, 0] U [SHRT_MAX + 1, USHRT_MAX] + // + // We can avoid this by checking if signed type's maximum value is lesser + // than unsigned type's minimum value. + + // If both have different signs then only we can get more information. + if (LHS.isUnsigned() != RHS.isUnsigned()) { + if (LHS.isUnsigned() && (LHS.getBitWidth() >= RHS.getBitWidth())) { + if (RHS.getMaxValue().isNegative() || + LHS.getAPSIntType().convert(RHS.getMaxValue()) < LHS.getMinValue()) + return getTrueRange(T); + + } else if (RHS.isUnsigned() && (LHS.getBitWidth() <= RHS.getBitWidth())) { + if (LHS.getMaxValue().isNegative() || + RHS.getAPSIntType().convert(LHS.getMaxValue()) < RHS.getMinValue()) + return getTrueRange(T); + } + } + + // Both RangeSets should be casted to bigger unsigned type. + APSIntType CastingType(std::max(LHS.getBitWidth(), RHS.getBitWidth()), + LHS.isUnsigned() || RHS.isUnsigned()); + + RangeSet CastedLHS = RangeFactory.castTo(LHS, CastingType); + RangeSet CastedRHS = RangeFactory.castTo(RHS, CastingType); + + if (intersect(RangeFactory, CastedLHS, CastedRHS).isEmpty()) + return getTrueRange(T); + } + + // In all other cases, the resulting range cannot be deduced. + return infer(T); +} + +template <> RangeSet SymbolicRangeInferrer::VisitBinaryOperator<BO_Or>(Range LHS, Range RHS, QualType T) { APSIntType ResultType = ValueFactory.getAPSIntType(T); @@ -1381,6 +1824,144 @@ RangeSet SymbolicRangeInferrer::VisitBinaryOperator<BO_Rem>(Range LHS, return {RangeFactory, ValueFactory.getValue(Min), ValueFactory.getValue(Max)}; } +RangeSet SymbolicRangeInferrer::VisitBinaryOperator(RangeSet LHS, + BinaryOperator::Opcode Op, + 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(); + } + + switch (Op) { + case BO_NE: + return VisitBinaryOperator<BO_NE>(LHS, RHS, T); + 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); + } +} + +//===----------------------------------------------------------------------===// +// Constraint manager implementation details +//===----------------------------------------------------------------------===// + +class RangeConstraintManager : public RangedConstraintManager { +public: + RangeConstraintManager(ExprEngine *EE, SValBuilder &SVB) + : RangedConstraintManager(EE, SVB), F(getBasicVals()) {} + + //===------------------------------------------------------------------===// + // Implementation for interface from ConstraintManager. + //===------------------------------------------------------------------===// + + bool haveEqualConstraints(ProgramStateRef S1, + ProgramStateRef S2) const override { + // NOTE: ClassMembers are as simple as back pointers for ClassMap, + // so comparing constraint ranges and class maps should be + // sufficient. + return S1->get<ConstraintRange>() == S2->get<ConstraintRange>() && + S1->get<ClassMap>() == S2->get<ClassMap>(); + } + + bool canReasonAbout(SVal X) const override; + + ConditionTruthVal checkNull(ProgramStateRef State, SymbolRef Sym) override; + + const llvm::APSInt *getSymVal(ProgramStateRef State, + SymbolRef Sym) const override; + + const llvm::APSInt *getSymMinVal(ProgramStateRef State, + SymbolRef Sym) const override; + + const llvm::APSInt *getSymMaxVal(ProgramStateRef State, + SymbolRef Sym) const override; + + ProgramStateRef removeDeadBindings(ProgramStateRef State, + SymbolReaper &SymReaper) override; + + void printJson(raw_ostream &Out, ProgramStateRef State, const char *NL = "\n", + unsigned int Space = 0, bool IsDot = false) const override; + void printValue(raw_ostream &Out, ProgramStateRef State, + SymbolRef Sym) override; + void printConstraints(raw_ostream &Out, ProgramStateRef State, + const char *NL = "\n", unsigned int Space = 0, + bool IsDot = false) const; + void printEquivalenceClasses(raw_ostream &Out, ProgramStateRef State, + const char *NL = "\n", unsigned int Space = 0, + bool IsDot = false) const; + void printDisequalities(raw_ostream &Out, ProgramStateRef State, + const char *NL = "\n", unsigned int Space = 0, + bool IsDot = false) const; + + //===------------------------------------------------------------------===// + // Implementation for interface from RangedConstraintManager. + //===------------------------------------------------------------------===// + + ProgramStateRef assumeSymNE(ProgramStateRef State, SymbolRef Sym, + const llvm::APSInt &V, + const llvm::APSInt &Adjustment) override; + + ProgramStateRef assumeSymEQ(ProgramStateRef State, SymbolRef Sym, + const llvm::APSInt &V, + const llvm::APSInt &Adjustment) override; + + ProgramStateRef assumeSymLT(ProgramStateRef State, SymbolRef Sym, + const llvm::APSInt &V, + const llvm::APSInt &Adjustment) override; + + ProgramStateRef assumeSymGT(ProgramStateRef State, SymbolRef Sym, + const llvm::APSInt &V, + const llvm::APSInt &Adjustment) override; + + ProgramStateRef assumeSymLE(ProgramStateRef State, SymbolRef Sym, + const llvm::APSInt &V, + const llvm::APSInt &Adjustment) override; + + ProgramStateRef assumeSymGE(ProgramStateRef State, SymbolRef Sym, + const llvm::APSInt &V, + const llvm::APSInt &Adjustment) override; + + ProgramStateRef assumeSymWithinInclusiveRange( + ProgramStateRef State, SymbolRef Sym, const llvm::APSInt &From, + const llvm::APSInt &To, const llvm::APSInt &Adjustment) override; + + ProgramStateRef assumeSymOutsideInclusiveRange( + ProgramStateRef State, SymbolRef Sym, const llvm::APSInt &From, + const llvm::APSInt &To, const llvm::APSInt &Adjustment) override; + +private: + RangeSet::Factory F; + + RangeSet getRange(ProgramStateRef State, SymbolRef Sym); + RangeSet getRange(ProgramStateRef State, EquivalenceClass Class); + ProgramStateRef setRange(ProgramStateRef State, SymbolRef Sym, + RangeSet Range); + ProgramStateRef setRange(ProgramStateRef State, EquivalenceClass Class, + RangeSet Range); + + RangeSet getSymLTRange(ProgramStateRef St, SymbolRef Sym, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment); + RangeSet getSymGTRange(ProgramStateRef St, SymbolRef Sym, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment); + RangeSet getSymLERange(ProgramStateRef St, SymbolRef Sym, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment); + RangeSet getSymLERange(llvm::function_ref<RangeSet()> RS, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment); + RangeSet getSymGERange(ProgramStateRef St, SymbolRef Sym, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment); +}; + //===----------------------------------------------------------------------===// // Constraint assignment logic //===----------------------------------------------------------------------===// @@ -1482,7 +2063,7 @@ public: class ConstraintAssignor : public ConstraintAssignorBase<ConstraintAssignor> { public: template <class ClassOrSymbol> - LLVM_NODISCARD static ProgramStateRef + [[nodiscard]] static ProgramStateRef assign(ProgramStateRef State, SValBuilder &Builder, RangeSet::Factory &F, ClassOrSymbol CoS, RangeSet NewConstraint) { if (!State || NewConstraint.isEmpty()) @@ -1492,7 +2073,28 @@ public: return Assignor.assign(CoS, NewConstraint); } + /// Handle expressions like: a % b != 0. + template <typename SymT> + bool handleRemainderOp(const SymT *Sym, RangeSet Constraint) { + if (Sym->getOpcode() != BO_Rem) + return true; + // a % b != 0 implies that a != 0. + if (!Constraint.containsZero()) { + SVal SymSVal = Builder.makeSymbolVal(Sym->getLHS()); + if (auto NonLocSymSVal = SymSVal.getAs<nonloc::SymbolVal>()) { + State = State->assume(*NonLocSymSVal, true); + if (!State) + return false; + } + } + return true; + } + inline bool assignSymExprToConst(const SymExpr *Sym, Const Constraint); + inline bool assignSymIntExprToRangeSet(const SymIntExpr *Sym, + RangeSet Constraint) { + return handleRemainderOp(Sym, Constraint); + } inline bool assignSymSymExprToRangeSet(const SymSymExpr *Sym, RangeSet Constraint); @@ -1503,7 +2105,7 @@ private: using Base = ConstraintAssignorBase<ConstraintAssignor>; /// Base method for handling new constraints for symbols. - LLVM_NODISCARD ProgramStateRef assign(SymbolRef Sym, RangeSet NewConstraint) { + [[nodiscard]] ProgramStateRef assign(SymbolRef Sym, RangeSet NewConstraint) { // All constraints are actually associated with equivalence classes, and // that's what we are going to do first. State = assign(EquivalenceClass::find(State, Sym), NewConstraint); @@ -1517,8 +2119,8 @@ private: } /// Base method for handling new constraints for classes. - LLVM_NODISCARD ProgramStateRef assign(EquivalenceClass Class, - RangeSet NewConstraint) { + [[nodiscard]] ProgramStateRef assign(EquivalenceClass Class, + RangeSet NewConstraint) { // There is a chance that we might need to update constraints for the // classes that are known to be disequal to Class. // @@ -1564,18 +2166,16 @@ private: return EquivalenceClass::merge(RangeFactory, State, LHS, RHS); } - LLVM_NODISCARD Optional<bool> interpreteAsBool(RangeSet Constraint) { + [[nodiscard]] std::optional<bool> interpreteAsBool(RangeSet Constraint) { assert(!Constraint.isEmpty() && "Empty ranges shouldn't get here"); if (Constraint.getConcreteValue()) - return !Constraint.getConcreteValue()->isNullValue(); + return !Constraint.getConcreteValue()->isZero(); - APSIntType T{Constraint.getMinValue()}; - Const Zero = T.getZeroValue(); - if (!Constraint.contains(Zero)) + if (!Constraint.containsZero()) return true; - return llvm::None; + return std::nullopt; } ProgramStateRef State; @@ -1583,113 +2183,6 @@ private: RangeSet::Factory &RangeFactory; }; -//===----------------------------------------------------------------------===// -// Constraint manager implementation details -//===----------------------------------------------------------------------===// - -class RangeConstraintManager : public RangedConstraintManager { -public: - RangeConstraintManager(ExprEngine *EE, SValBuilder &SVB) - : RangedConstraintManager(EE, SVB), F(getBasicVals()) {} - - //===------------------------------------------------------------------===// - // Implementation for interface from ConstraintManager. - //===------------------------------------------------------------------===// - - bool haveEqualConstraints(ProgramStateRef S1, - ProgramStateRef S2) const override { - // NOTE: ClassMembers are as simple as back pointers for ClassMap, - // so comparing constraint ranges and class maps should be - // sufficient. - return S1->get<ConstraintRange>() == S2->get<ConstraintRange>() && - S1->get<ClassMap>() == S2->get<ClassMap>(); - } - - bool canReasonAbout(SVal X) const override; - - ConditionTruthVal checkNull(ProgramStateRef State, SymbolRef Sym) override; - - const llvm::APSInt *getSymVal(ProgramStateRef State, - SymbolRef Sym) const override; - - ProgramStateRef removeDeadBindings(ProgramStateRef State, - SymbolReaper &SymReaper) override; - - void printJson(raw_ostream &Out, ProgramStateRef State, const char *NL = "\n", - unsigned int Space = 0, bool IsDot = false) const override; - void printConstraints(raw_ostream &Out, ProgramStateRef State, - const char *NL = "\n", unsigned int Space = 0, - bool IsDot = false) const; - void printEquivalenceClasses(raw_ostream &Out, ProgramStateRef State, - const char *NL = "\n", unsigned int Space = 0, - bool IsDot = false) const; - void printDisequalities(raw_ostream &Out, ProgramStateRef State, - const char *NL = "\n", unsigned int Space = 0, - bool IsDot = false) const; - - //===------------------------------------------------------------------===// - // Implementation for interface from RangedConstraintManager. - //===------------------------------------------------------------------===// - - ProgramStateRef assumeSymNE(ProgramStateRef State, SymbolRef Sym, - const llvm::APSInt &V, - const llvm::APSInt &Adjustment) override; - - ProgramStateRef assumeSymEQ(ProgramStateRef State, SymbolRef Sym, - const llvm::APSInt &V, - const llvm::APSInt &Adjustment) override; - - ProgramStateRef assumeSymLT(ProgramStateRef State, SymbolRef Sym, - const llvm::APSInt &V, - const llvm::APSInt &Adjustment) override; - - ProgramStateRef assumeSymGT(ProgramStateRef State, SymbolRef Sym, - const llvm::APSInt &V, - const llvm::APSInt &Adjustment) override; - - ProgramStateRef assumeSymLE(ProgramStateRef State, SymbolRef Sym, - const llvm::APSInt &V, - const llvm::APSInt &Adjustment) override; - - ProgramStateRef assumeSymGE(ProgramStateRef State, SymbolRef Sym, - const llvm::APSInt &V, - const llvm::APSInt &Adjustment) override; - - ProgramStateRef assumeSymWithinInclusiveRange( - ProgramStateRef State, SymbolRef Sym, const llvm::APSInt &From, - const llvm::APSInt &To, const llvm::APSInt &Adjustment) override; - - ProgramStateRef assumeSymOutsideInclusiveRange( - ProgramStateRef State, SymbolRef Sym, const llvm::APSInt &From, - const llvm::APSInt &To, const llvm::APSInt &Adjustment) override; - -private: - RangeSet::Factory F; - - RangeSet getRange(ProgramStateRef State, SymbolRef Sym); - RangeSet getRange(ProgramStateRef State, EquivalenceClass Class); - ProgramStateRef setRange(ProgramStateRef State, SymbolRef Sym, - RangeSet Range); - ProgramStateRef setRange(ProgramStateRef State, EquivalenceClass Class, - RangeSet Range); - - RangeSet getSymLTRange(ProgramStateRef St, SymbolRef Sym, - const llvm::APSInt &Int, - const llvm::APSInt &Adjustment); - RangeSet getSymGTRange(ProgramStateRef St, SymbolRef Sym, - const llvm::APSInt &Int, - const llvm::APSInt &Adjustment); - RangeSet getSymLERange(ProgramStateRef St, SymbolRef Sym, - const llvm::APSInt &Int, - const llvm::APSInt &Adjustment); - RangeSet getSymLERange(llvm::function_ref<RangeSet()> RS, - const llvm::APSInt &Int, - const llvm::APSInt &Adjustment); - RangeSet getSymGERange(ProgramStateRef St, SymbolRef Sym, - const llvm::APSInt &Int, - const llvm::APSInt &Adjustment); -}; - bool ConstraintAssignor::assignSymExprToConst(const SymExpr *Sym, const llvm::APSInt &Constraint) { llvm::SmallSet<EquivalenceClass, 4> SimplifiedClasses; @@ -1716,17 +2209,32 @@ bool ConstraintAssignor::assignSymExprToConst(const SymExpr *Sym, return false; } + // We may have trivial equivalence classes in the disequality info as + // well, and we need to simplify them. + DisequalityMapTy DisequalityInfo = State->get<DisequalityMap>(); + for (std::pair<EquivalenceClass, ClassSet> DisequalityEntry : + DisequalityInfo) { + EquivalenceClass Class = DisequalityEntry.first; + ClassSet DisequalClasses = DisequalityEntry.second; + State = EquivalenceClass::simplify(Builder, RangeFactory, State, Class); + if (!State) + return false; + } + return true; } bool ConstraintAssignor::assignSymSymExprToRangeSet(const SymSymExpr *Sym, RangeSet Constraint) { - Optional<bool> ConstraintAsBool = interpreteAsBool(Constraint); + if (!handleRemainderOp(Sym, Constraint)) + return false; + + std::optional<bool> ConstraintAsBool = interpreteAsBool(Constraint); if (!ConstraintAsBool) return true; - if (Optional<bool> Equality = meansEquality(Sym)) { + if (std::optional<bool> Equality = meansEquality(Sym)) { // Here we cover two cases: // * if Sym is equality and the new constraint is true -> Sym's operands // should be marked as equal @@ -1864,7 +2372,7 @@ EquivalenceClass::mergeImpl(RangeSet::Factory &RangeFactory, // // Intersection here makes perfect sense because both of these constraints // must hold for the whole new class. - if (Optional<RangeSet> NewClassConstraint = + if (std::optional<RangeSet> NewClassConstraint = intersect(RangeFactory, getConstraint(State, *this), getConstraint(State, Other))) { // NOTE: Essentially, NewClassConstraint should NEVER be infeasible because @@ -2062,16 +2570,16 @@ inline bool EquivalenceClass::addToDisequalityInfo( return true; } -inline Optional<bool> EquivalenceClass::areEqual(ProgramStateRef State, - SymbolRef FirstSym, - SymbolRef SecondSym) { +inline std::optional<bool> EquivalenceClass::areEqual(ProgramStateRef State, + SymbolRef FirstSym, + SymbolRef SecondSym) { return EquivalenceClass::areEqual(State, find(State, FirstSym), find(State, SecondSym)); } -inline Optional<bool> EquivalenceClass::areEqual(ProgramStateRef State, - EquivalenceClass First, - EquivalenceClass Second) { +inline std::optional<bool> EquivalenceClass::areEqual(ProgramStateRef State, + EquivalenceClass First, + EquivalenceClass Second) { // The same equivalence class => symbols are equal. if (First == Second) return true; @@ -2083,7 +2591,61 @@ inline Optional<bool> EquivalenceClass::areEqual(ProgramStateRef State, return false; // It is not clear. - return llvm::None; + return std::nullopt; +} + +[[nodiscard]] ProgramStateRef +EquivalenceClass::removeMember(ProgramStateRef State, const SymbolRef Old) { + + SymbolSet ClsMembers = getClassMembers(State); + assert(ClsMembers.contains(Old)); + + // Remove `Old`'s Class->Sym relation. + SymbolSet::Factory &F = getMembersFactory(State); + ClassMembersTy::Factory &EMFactory = State->get_context<ClassMembers>(); + ClsMembers = F.remove(ClsMembers, Old); + // Ensure another precondition of the removeMember function (we can check + // this only with isEmpty, thus we have to do the remove first). + assert(!ClsMembers.isEmpty() && + "Class should have had at least two members before member removal"); + // Overwrite the existing members assigned to this class. + ClassMembersTy ClassMembersMap = State->get<ClassMembers>(); + ClassMembersMap = EMFactory.add(ClassMembersMap, *this, ClsMembers); + State = State->set<ClassMembers>(ClassMembersMap); + + // Remove `Old`'s Sym->Class relation. + ClassMapTy Classes = State->get<ClassMap>(); + ClassMapTy::Factory &CMF = State->get_context<ClassMap>(); + Classes = CMF.remove(Classes, Old); + State = State->set<ClassMap>(Classes); + + return State; +} + +// Re-evaluate an SVal with top-level `State->assume` logic. +[[nodiscard]] ProgramStateRef +reAssume(ProgramStateRef State, const RangeSet *Constraint, SVal TheValue) { + if (!Constraint) + return State; + + const auto DefinedVal = TheValue.castAs<DefinedSVal>(); + + // If the SVal is 0, we can simply interpret that as `false`. + if (Constraint->encodesFalseRange()) + return State->assume(DefinedVal, false); + + // If the constraint does not encode 0 then we can interpret that as `true` + // AND as a Range(Set). + if (Constraint->encodesTrueRange()) { + State = State->assume(DefinedVal, true); + if (!State) + return nullptr; + // Fall through, re-assume based on the range values as well. + } + // Overestimate the individual Ranges with the RangeSet' lowest and + // highest values. + return State->assumeInclusiveRange(DefinedVal, Constraint->getMinValue(), + Constraint->getMaxValue(), true); } // Iterate over all symbols and try to simplify them. Once a symbol is @@ -2091,19 +2653,78 @@ inline Optional<bool> EquivalenceClass::areEqual(ProgramStateRef State, // class to this class. This way, we simplify not just the symbols but the // classes as well: we strive to keep the number of the classes to be the // absolute minimum. -LLVM_NODISCARD ProgramStateRef +[[nodiscard]] ProgramStateRef EquivalenceClass::simplify(SValBuilder &SVB, RangeSet::Factory &F, ProgramStateRef State, EquivalenceClass Class) { SymbolSet ClassMembers = Class.getClassMembers(State); for (const SymbolRef &MemberSym : ClassMembers) { - SymbolRef SimplifiedMemberSym = ento::simplify(State, MemberSym); + + const SVal SimplifiedMemberVal = simplifyToSVal(State, MemberSym); + const SymbolRef SimplifiedMemberSym = SimplifiedMemberVal.getAsSymbol(); + + // The symbol is collapsed to a constant, check if the current State is + // still feasible. + if (const auto CI = SimplifiedMemberVal.getAs<nonloc::ConcreteInt>()) { + const llvm::APSInt &SV = CI->getValue(); + const RangeSet *ClassConstraint = getConstraint(State, Class); + // We have found a contradiction. + if (ClassConstraint && !ClassConstraint->contains(SV)) + return nullptr; + } + if (SimplifiedMemberSym && MemberSym != SimplifiedMemberSym) { // The simplified symbol should be the member of the original Class, // however, it might be in another existing class at the moment. We // have to merge these classes. + ProgramStateRef OldState = State; State = merge(F, State, MemberSym, SimplifiedMemberSym); if (!State) return nullptr; + // No state change, no merge happened actually. + if (OldState == State) + continue; + + // Be aware that `SimplifiedMemberSym` might refer to an already dead + // symbol. In that case, the eqclass of that might not be the same as the + // eqclass of `MemberSym`. This is because the dead symbols are not + // preserved in the `ClassMap`, hence + // `find(State, SimplifiedMemberSym)` will result in a trivial eqclass + // compared to the eqclass of `MemberSym`. + // These eqclasses should be the same if `SimplifiedMemberSym` is alive. + // --> assert(find(State, MemberSym) == find(State, SimplifiedMemberSym)) + // + // Note that `MemberSym` must be alive here since that is from the + // `ClassMembers` where all the symbols are alive. + + // Remove the old and more complex symbol. + State = find(State, MemberSym).removeMember(State, MemberSym); + + // Query the class constraint again b/c that may have changed during the + // merge above. + const RangeSet *ClassConstraint = getConstraint(State, Class); + + // Re-evaluate an SVal with top-level `State->assume`, this ignites + // a RECURSIVE algorithm that will reach a FIXPOINT. + // + // About performance and complexity: Let us assume that in a State we + // have N non-trivial equivalence classes and that all constraints and + // disequality info is related to non-trivial classes. In the worst case, + // we can simplify only one symbol of one class in each iteration. The + // number of symbols in one class cannot grow b/c we replace the old + // symbol with the simplified one. Also, the number of the equivalence + // classes can decrease only, b/c the algorithm does a merge operation + // optionally. We need N iterations in this case to reach the fixpoint. + // Thus, the steps needed to be done in the worst case is proportional to + // N*N. + // + // This worst case scenario can be extended to that case when we have + // trivial classes in the constraints and in the disequality map. This + // case can be reduced to the case with a State where there are only + // non-trivial classes. This is because a merge operation on two trivial + // classes results in one non-trivial class. + State = reAssume(State, ClassConstraint, SimplifiedMemberVal); + if (!State) + return nullptr; } } return State; @@ -2173,7 +2794,7 @@ bool EquivalenceClass::isClassDataConsistent(ProgramStateRef State) { //===----------------------------------------------------------------------===// bool RangeConstraintManager::canReasonAbout(SVal X) const { - Optional<nonloc::SymbolVal> SymVal = X.getAs<nonloc::SymbolVal>(); + std::optional<nonloc::SymbolVal> SymVal = X.getAs<nonloc::SymbolVal>(); if (SymVal && SymVal->isExpression()) { const SymExpr *SE = SymVal->getSymbol(); @@ -2248,6 +2869,22 @@ const llvm::APSInt *RangeConstraintManager::getSymVal(ProgramStateRef St, return T ? T->getConcreteValue() : nullptr; } +const llvm::APSInt *RangeConstraintManager::getSymMinVal(ProgramStateRef St, + SymbolRef Sym) const { + const RangeSet *T = getConstraint(St, Sym); + if (!T || T->isEmpty()) + return nullptr; + return &T->getMinValue(); +} + +const llvm::APSInt *RangeConstraintManager::getSymMaxVal(ProgramStateRef St, + SymbolRef Sym) const { + const RangeSet *T = getConstraint(St, Sym); + if (!T || T->isEmpty()) + return nullptr; + return &T->getMaxValue(); +} + //===----------------------------------------------------------------------===// // Remove dead symbols from existing constraints //===----------------------------------------------------------------------===// @@ -2630,6 +3267,13 @@ void RangeConstraintManager::printJson(raw_ostream &Out, ProgramStateRef State, printDisequalities(Out, State, NL, Space, IsDot); } +void RangeConstraintManager::printValue(raw_ostream &Out, ProgramStateRef State, + SymbolRef Sym) { + const RangeSet RS = getRange(State, Sym); + Out << RS.getBitWidth() << (RS.isUnsigned() ? "u:" : "s:"); + RS.dump(Out); +} + static std::string toString(const SymbolRef &Sym) { std::string S; llvm::raw_string_ostream O(S); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp index d227c025fb20..4bbe933be212 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp @@ -41,49 +41,55 @@ ProgramStateRef RangedConstraintManager::assumeSym(ProgramStateRef State, return assumeSymRel(State, SIE->getLHS(), op, SIE->getRHS()); } - } else if (const SymSymExpr *SSE = dyn_cast<SymSymExpr>(Sym)) { + // Handle adjustment with non-comparison ops. + const llvm::APSInt &Zero = getBasicVals().getValue(0, SIE->getType()); + return assumeSymRel(State, SIE, (Assumption ? BO_NE : BO_EQ), Zero); + } + + if (const auto *SSE = dyn_cast<SymSymExpr>(Sym)) { BinaryOperator::Opcode Op = SSE->getOpcode(); - assert(BinaryOperator::isComparisonOp(Op)); - - // We convert equality operations for pointers only. - if (Loc::isLocType(SSE->getLHS()->getType()) && - Loc::isLocType(SSE->getRHS()->getType())) { - // Translate "a != b" to "(b - a) != 0". - // We invert the order of the operands as a heuristic for how loop - // conditions are usually written ("begin != end") as compared to length - // calculations ("end - begin"). The more correct thing to do would be to - // canonicalize "a - b" and "b - a", which would allow us to treat - // "a != b" and "b != a" the same. - - SymbolManager &SymMgr = getSymbolManager(); - QualType DiffTy = SymMgr.getContext().getPointerDiffType(); - SymbolRef Subtraction = - SymMgr.getSymSymExpr(SSE->getRHS(), BO_Sub, SSE->getLHS(), DiffTy); - - const llvm::APSInt &Zero = getBasicVals().getValue(0, DiffTy); - Op = BinaryOperator::reverseComparisonOp(Op); - if (!Assumption) - Op = BinaryOperator::negateComparisonOp(Op); - return assumeSymRel(State, Subtraction, Op, Zero); - } + if (BinaryOperator::isComparisonOp(Op)) { + + // We convert equality operations for pointers only. + if (Loc::isLocType(SSE->getLHS()->getType()) && + Loc::isLocType(SSE->getRHS()->getType())) { + // Translate "a != b" to "(b - a) != 0". + // We invert the order of the operands as a heuristic for how loop + // conditions are usually written ("begin != end") as compared to length + // calculations ("end - begin"). The more correct thing to do would be + // to canonicalize "a - b" and "b - a", which would allow us to treat + // "a != b" and "b != a" the same. + + SymbolManager &SymMgr = getSymbolManager(); + QualType DiffTy = SymMgr.getContext().getPointerDiffType(); + SymbolRef Subtraction = + SymMgr.getSymSymExpr(SSE->getRHS(), BO_Sub, SSE->getLHS(), DiffTy); + + const llvm::APSInt &Zero = getBasicVals().getValue(0, DiffTy); + Op = BinaryOperator::reverseComparisonOp(Op); + if (!Assumption) + Op = BinaryOperator::negateComparisonOp(Op); + return assumeSymRel(State, Subtraction, Op, Zero); + } - if (BinaryOperator::isEqualityOp(Op)) { - SymbolManager &SymMgr = getSymbolManager(); + if (BinaryOperator::isEqualityOp(Op)) { + SymbolManager &SymMgr = getSymbolManager(); - QualType ExprType = SSE->getType(); - SymbolRef CanonicalEquality = - SymMgr.getSymSymExpr(SSE->getLHS(), BO_EQ, SSE->getRHS(), ExprType); + QualType ExprType = SSE->getType(); + SymbolRef CanonicalEquality = + SymMgr.getSymSymExpr(SSE->getLHS(), BO_EQ, SSE->getRHS(), ExprType); - bool WasEqual = SSE->getOpcode() == BO_EQ; - bool IsExpectedEqual = WasEqual == Assumption; + bool WasEqual = SSE->getOpcode() == BO_EQ; + bool IsExpectedEqual = WasEqual == Assumption; - const llvm::APSInt &Zero = getBasicVals().getValue(0, ExprType); + const llvm::APSInt &Zero = getBasicVals().getValue(0, ExprType); - if (IsExpectedEqual) { - return assumeSymNE(State, CanonicalEquality, Zero, Zero); - } + if (IsExpectedEqual) { + return assumeSymNE(State, CanonicalEquality, Zero, Zero); + } - return assumeSymEQ(State, CanonicalEquality, Zero, Zero); + return assumeSymEQ(State, CanonicalEquality, Zero, Zero); + } } } @@ -226,9 +232,13 @@ void RangedConstraintManager::computeAdjustment(SymbolRef &Sym, } } -SymbolRef simplify(ProgramStateRef State, SymbolRef Sym) { +SVal simplifyToSVal(ProgramStateRef State, SymbolRef Sym) { SValBuilder &SVB = State->getStateManager().getSValBuilder(); - SVal SimplifiedVal = SVB.simplifySVal(State, SVB.makeSymbolVal(Sym)); + return SVB.simplifySVal(State, SVB.makeSymbolVal(Sym)); +} + +SymbolRef simplify(ProgramStateRef State, SymbolRef Sym) { + SVal SimplifiedVal = simplifyToSVal(State, Sym); if (SymbolRef SimplifiedSym = SimplifiedVal.getAsSymbol()) return SimplifiedSym; return Sym; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index 4ffa1aacb41f..da9a1a1a4d1f 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -28,8 +28,9 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "llvm/ADT/ImmutableMap.h" -#include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/Support/raw_ostream.h" +#include <optional> #include <utility> using namespace clang; @@ -62,8 +63,8 @@ private: : P(r, k), Data(offset) { assert(r && "Must have known regions."); assert(getOffset() == offset && "Failed to store offset"); - assert((r == r->getBaseRegion() || isa<ObjCIvarRegion>(r) || - isa <CXXDerivedObjectRegion>(r)) && + assert((r == r->getBaseRegion() || + isa<ObjCIvarRegion, CXXDerivedObjectRegion>(r)) && "Not a base"); } public: @@ -212,11 +213,11 @@ public: removeBinding(R, BindingKey::Default); } - Optional<SVal> getDirectBinding(const MemRegion *R) const; + std::optional<SVal> getDirectBinding(const MemRegion *R) const; /// getDefaultBinding - Returns an SVal* representing an optional default /// binding associated with a region and its subregions. - Optional<SVal> getDefaultBinding(const MemRegion *R) const; + std::optional<SVal> getDefaultBinding(const MemRegion *R) const; /// Return the internal tree as a Store. Store asStore() const { @@ -231,7 +232,7 @@ public: void printJson(raw_ostream &Out, const char *NL = "\n", unsigned int Space = 0, bool IsDot = false) const { - for (iterator I = begin(); I != end(); ++I) { + for (iterator I = begin(), E = end(); I != E; ++I) { // TODO: We might need a .printJson for I.getKey() as well. Indent(Out, Space, IsDot) << "{ \"cluster\": \"" << I.getKey() << "\", \"pointer\": \"" @@ -239,18 +240,19 @@ public: ++Space; const ClusterBindings &CB = I.getData(); - for (ClusterBindings::iterator CI = CB.begin(); CI != CB.end(); ++CI) { + for (ClusterBindings::iterator CI = CB.begin(), CE = CB.end(); CI != CE; + ++CI) { Indent(Out, Space, IsDot) << "{ " << CI.getKey() << ", \"value\": "; CI.getData().printJson(Out, /*AddQuotes=*/true); Out << " }"; - if (std::next(CI) != CB.end()) + if (std::next(CI) != CE) Out << ','; Out << NL; } --Space; Indent(Out, Space, IsDot) << "]}"; - if (std::next(I) != end()) + if (std::next(I) != E) Out << ','; Out << NL; } @@ -262,12 +264,16 @@ public: typedef const RegionBindingsRef& RegionBindingsConstRef; -Optional<SVal> RegionBindingsRef::getDirectBinding(const MemRegion *R) const { - return Optional<SVal>::create(lookup(R, BindingKey::Direct)); +std::optional<SVal> +RegionBindingsRef::getDirectBinding(const MemRegion *R) const { + const SVal *V = lookup(R, BindingKey::Direct); + return V ? std::optional<SVal>(*V) : std::nullopt; } -Optional<SVal> RegionBindingsRef::getDefaultBinding(const MemRegion *R) const { - return Optional<SVal>::create(lookup(R, BindingKey::Default)); +std::optional<SVal> +RegionBindingsRef::getDefaultBinding(const MemRegion *R) const { + const SVal *V = lookup(R, BindingKey::Default); + return V ? std::optional<SVal>(*V) : std::nullopt; } RegionBindingsRef RegionBindingsRef::addBinding(BindingKey K, SVal V) const { @@ -318,29 +324,6 @@ RegionBindingsRef RegionBindingsRef::removeBinding(const MemRegion *R, } //===----------------------------------------------------------------------===// -// Fine-grained control of RegionStoreManager. -//===----------------------------------------------------------------------===// - -namespace { -struct minimal_features_tag {}; -struct maximal_features_tag {}; - -class RegionStoreFeatures { - bool SupportsFields; -public: - RegionStoreFeatures(minimal_features_tag) : - SupportsFields(false) {} - - RegionStoreFeatures(maximal_features_tag) : - SupportsFields(true) {} - - void enableFields(bool t) { SupportsFields = t; } - - bool supportsFields() const { return SupportsFields; } -}; -} - -//===----------------------------------------------------------------------===// // Main RegionStore logic. //===----------------------------------------------------------------------===// @@ -349,8 +332,6 @@ class InvalidateRegionsWorker; class RegionStoreManager : public StoreManager { public: - const RegionStoreFeatures Features; - RegionBindings::Factory RBFactory; mutable ClusterBindings::Factory CBFactory; @@ -370,6 +351,16 @@ private: /// To disable all small-struct-dependent behavior, set the option to "0". unsigned SmallStructLimit; + /// The largest number of element an array can have and still be + /// considered "small". + /// + /// This is currently used to decide whether or not it is worth "forcing" a + /// LazyCompoundVal on bind. + /// + /// This is controlled by 'region-store-small-struct-limit' option. + /// To disable all small-struct-dependent behavior, set the option to "0". + unsigned SmallArrayLimit; + /// A helper used to populate the work list with the given set of /// regions. void populateWorkList(InvalidateRegionsWorker &W, @@ -377,16 +368,15 @@ private: InvalidatedRegions *TopLevelRegions); public: - RegionStoreManager(ProgramStateManager& mgr, const RegionStoreFeatures &f) - : StoreManager(mgr), Features(f), - RBFactory(mgr.getAllocator()), CBFactory(mgr.getAllocator()), - SmallStructLimit(0) { + RegionStoreManager(ProgramStateManager &mgr) + : StoreManager(mgr), RBFactory(mgr.getAllocator()), + CBFactory(mgr.getAllocator()), SmallStructLimit(0), SmallArrayLimit(0) { ExprEngine &Eng = StateMgr.getOwningEngine(); AnalyzerOptions &Options = Eng.getAnalysisManager().options; SmallStructLimit = Options.RegionStoreSmallStructLimit; + SmallArrayLimit = Options.RegionStoreSmallArrayLimit; } - /// setImplicitDefaultValue - Set the default binding for the provided /// MemRegion to the value implicitly defined for compound literals when /// the value is not specified. @@ -437,6 +427,15 @@ public: RegionBindingsRef removeSubRegionBindings(RegionBindingsConstRef B, const SubRegion *R); + std::optional<SVal> + getConstantValFromConstArrayInitializer(RegionBindingsConstRef B, + const ElementRegion *R); + std::optional<SVal> + getSValFromInitListExpr(const InitListExpr *ILE, + const SmallVector<uint64_t, 2> &ConcreteOffsets, + QualType ElemT); + SVal getSValFromStringLiteral(const StringLiteral *SL, uint64_t Offset, + QualType ElemT); public: // Part of public interface to class. @@ -490,12 +489,11 @@ public: // Part of public interface to class. /// than using a Default binding at the base of the entire region. This is a /// heuristic attempting to avoid building long chains of LazyCompoundVals. /// - /// \returns The updated store bindings, or \c None if binding non-lazily - /// would be too expensive. - Optional<RegionBindingsRef> tryBindSmallStruct(RegionBindingsConstRef B, - const TypedValueRegion *R, - const RecordDecl *RD, - nonloc::LazyCompoundVal LCV); + /// \returns The updated store bindings, or \c std::nullopt if binding + /// non-lazily would be too expensive. + std::optional<RegionBindingsRef> + tryBindSmallStruct(RegionBindingsConstRef B, const TypedValueRegion *R, + const RecordDecl *RD, nonloc::LazyCompoundVal LCV); /// BindStruct - Bind a compound value to a structure. RegionBindingsRef bindStruct(RegionBindingsConstRef B, @@ -505,6 +503,10 @@ public: // Part of public interface to class. RegionBindingsRef bindVector(RegionBindingsConstRef B, const TypedValueRegion* R, SVal V); + std::optional<RegionBindingsRef> + tryBindSmallArray(RegionBindingsConstRef B, const TypedValueRegion *R, + const ArrayType *AT, nonloc::LazyCompoundVal LCV); + RegionBindingsRef bindArray(RegionBindingsConstRef B, const TypedValueRegion* R, SVal V); @@ -550,7 +552,7 @@ public: // Part of public interface to class. return getBinding(getRegionBindings(S), L, T); } - Optional<SVal> getDefaultBinding(Store S, const MemRegion *R) override { + std::optional<SVal> getDefaultBinding(Store S, const MemRegion *R) override { RegionBindingsRef B = getRegionBindings(S); // Default bindings are always applied over a base region so look up the // base region's default binding, otherwise the lookup will fail when R @@ -591,10 +593,10 @@ public: // Part of public interface to class. /// /// Note that callers may need to specially handle LazyCompoundVals, which /// are returned as is in case the caller needs to treat them differently. - Optional<SVal> getBindingForDerivedDefaultValue(RegionBindingsConstRef B, - const MemRegion *superR, - const TypedValueRegion *R, - QualType Ty); + std::optional<SVal> + getBindingForDerivedDefaultValue(RegionBindingsConstRef B, + const MemRegion *superR, + const TypedValueRegion *R, QualType Ty); /// Get the state and region whose binding this region \p R corresponds to. /// @@ -610,6 +612,10 @@ public: // Part of public interface to class. /// The precise value of "interesting" is determined for the purposes of /// RegionStore's internal analysis. It must always contain all regions and /// symbols, but may omit constants and other kinds of SVal. + /// + /// In contrast to compound values, LazyCompoundVals are also added + /// to the 'interesting values' list in addition to the child interesting + /// values. const SValListTy &getInterestingValues(nonloc::LazyCompoundVal LCV); //===------------------------------------------------------------------===// @@ -640,16 +646,13 @@ public: // Part of public interface to class. void iterBindings(Store store, BindingsHandler& f) override { RegionBindingsRef B = getRegionBindings(store); - for (RegionBindingsRef::iterator I = B.begin(), E = B.end(); I != E; ++I) { - const ClusterBindings &Cluster = I.getData(); - for (ClusterBindings::iterator CI = Cluster.begin(), CE = Cluster.end(); - CI != CE; ++CI) { - const BindingKey &K = CI.getKey(); - if (!K.isDirect()) + for (const auto &[Region, Cluster] : B) { + for (const auto &[Key, Value] : Cluster) { + if (!Key.isDirect()) continue; - if (const SubRegion *R = dyn_cast<SubRegion>(K.getRegion())) { + if (const SubRegion *R = dyn_cast<SubRegion>(Key.getRegion())) { // FIXME: Possibly incorporate the offset? - if (!f.HandleBinding(*this, store, R, CI.getData())) + if (!f.HandleBinding(*this, store, R, Value)) return; } } @@ -665,18 +668,9 @@ public: // Part of public interface to class. std::unique_ptr<StoreManager> ento::CreateRegionStoreManager(ProgramStateManager &StMgr) { - RegionStoreFeatures F = maximal_features_tag(); - return std::make_unique<RegionStoreManager>(StMgr, F); -} - -std::unique_ptr<StoreManager> -ento::CreateFieldsOnlyRegionStoreManager(ProgramStateManager &StMgr) { - RegionStoreFeatures F = minimal_features_tag(); - F.enableFields(true); - return std::make_unique<RegionStoreManager>(StMgr, F); + return std::make_unique<RegionStoreManager>(StMgr); } - //===----------------------------------------------------------------------===// // Region Cluster analysis. //===----------------------------------------------------------------------===// @@ -868,7 +862,7 @@ collectSubRegionBindings(SmallVectorImpl<BindingPair> &Bindings, // Find the length (in bits) of the region being invalidated. uint64_t Length = UINT64_MAX; SVal Extent = Top->getMemRegionManager().getStaticSize(Top, SVB); - if (Optional<nonloc::ConcreteInt> ExtentCI = + if (std::optional<nonloc::ConcreteInt> ExtentCI = Extent.getAs<nonloc::ConcreteInt>()) { const llvm::APSInt &ExtentInt = ExtentCI->getValue(); assert(ExtentInt.isNonNegative() || ExtentInt.isUnsigned()); @@ -879,9 +873,8 @@ collectSubRegionBindings(SmallVectorImpl<BindingPair> &Bindings, Length = FR->getDecl()->getBitWidthValue(SVB.getContext()); } - for (ClusterBindings::iterator I = Cluster.begin(), E = Cluster.end(); - I != E; ++I) { - BindingKey NextKey = I.getKey(); + for (const auto &StoreEntry : Cluster) { + BindingKey NextKey = StoreEntry.first; if (NextKey.getRegion() == TopKey.getRegion()) { // FIXME: This doesn't catch the case where we're really invalidating a // region with a symbolic offset. Example: @@ -892,7 +885,7 @@ collectSubRegionBindings(SmallVectorImpl<BindingPair> &Bindings, NextKey.getOffset() - TopKey.getOffset() < Length) { // Case 1: The next binding is inside the region we're invalidating. // Include it. - Bindings.push_back(*I); + Bindings.push_back(StoreEntry); } else if (NextKey.getOffset() == TopKey.getOffset()) { // Case 2: The next binding is at the same offset as the region we're @@ -902,7 +895,7 @@ collectSubRegionBindings(SmallVectorImpl<BindingPair> &Bindings, // FIXME: This is probably incorrect; consider invalidating an outer // struct whose first field is bound to a LazyCompoundVal. if (IncludeAllDefaultBindings || NextKey.isDirect()) - Bindings.push_back(*I); + Bindings.push_back(StoreEntry); } } else if (NextKey.hasSymbolicOffset()) { @@ -913,13 +906,13 @@ collectSubRegionBindings(SmallVectorImpl<BindingPair> &Bindings, // we'll be conservative and include it. if (IncludeAllDefaultBindings || NextKey.isDirect()) if (isCompatibleWithFields(NextKey, FieldsInSymbolicSubregions)) - Bindings.push_back(*I); + Bindings.push_back(StoreEntry); } else if (const SubRegion *BaseSR = dyn_cast<SubRegion>(Base)) { // Case 4: The next key is symbolic, but we changed a known // super-region. In this case the binding is certainly included. if (BaseSR->isSubRegionOf(Top)) if (isCompatibleWithFields(NextKey, FieldsInSymbolicSubregions)) - Bindings.push_back(*I); + Bindings.push_back(StoreEntry); } } } @@ -961,10 +954,8 @@ RegionStoreManager::removeSubRegionBindings(RegionBindingsConstRef B, /*IncludeAllDefaultBindings=*/false); ClusterBindingsRef Result(*Cluster, CBFactory); - for (SmallVectorImpl<BindingPair>::const_iterator I = Bindings.begin(), - E = Bindings.end(); - I != E; ++I) - Result = Result.remove(I->first); + for (BindingKey Key : llvm::make_first_range(Bindings)) + Result = Result.remove(Key); // If we're invalidating a region with a symbolic offset, we need to make sure // we don't treat the base region as uninitialized anymore. @@ -1040,15 +1031,14 @@ void InvalidateRegionsWorker::VisitBinding(SVal V) { } // Is it a LazyCompoundVal? All references get invalidated as well. - if (Optional<nonloc::LazyCompoundVal> LCS = + if (std::optional<nonloc::LazyCompoundVal> LCS = V.getAs<nonloc::LazyCompoundVal>()) { - const RegionStoreManager::SValListTy &Vals = RM.getInterestingValues(*LCS); - - for (RegionStoreManager::SValListTy::const_iterator I = Vals.begin(), - E = Vals.end(); - I != E; ++I) - VisitBinding(*I); + // `getInterestingValues()` returns SVals contained within LazyCompoundVals, + // so there is no need to visit them. + for (SVal V : RM.getInterestingValues(*LCS)) + if (!isa<nonloc::LazyCompoundVal>(V)) + VisitBinding(V); return; } @@ -1062,8 +1052,8 @@ void InvalidateRegionsWorker::VisitCluster(const MemRegion *baseR, RegionAndSymbolInvalidationTraits::TK_PreserveContents); if (C) { - for (ClusterBindings::iterator I = C->begin(), E = C->end(); I != E; ++I) - VisitBinding(I.getData()); + for (SVal Val : llvm::make_second_range(*C)) + VisitBinding(Val); // Invalidate regions contents. if (!PreserveRegionsContents) @@ -1099,10 +1089,8 @@ void InvalidateRegionsWorker::VisitCluster(const MemRegion *baseR, // BlockDataRegion? If so, invalidate captured variables that are passed // by reference. if (const BlockDataRegion *BR = dyn_cast<BlockDataRegion>(baseR)) { - for (BlockDataRegion::referenced_vars_iterator - BI = BR->referenced_vars_begin(), BE = BR->referenced_vars_end() ; - BI != BE; ++BI) { - const VarRegion *VR = BI.getCapturedRegion(); + for (auto Var : BR->referenced_vars()) { + const VarRegion *VR = Var.getCapturedRegion(); const VarDecl *VD = VR->getDecl(); if (VD->hasAttr<BlocksAttr>() || !VD->hasLocalStorage()) { AddToWorkList(VR); @@ -1114,7 +1102,7 @@ void InvalidateRegionsWorker::VisitCluster(const MemRegion *baseR, // a pointer value, but the thing pointed by that pointer may // get invalidated. SVal V = RM.getBinding(B, loc::MemRegionVal(VR)); - if (Optional<Loc> L = V.getAs<Loc>()) { + if (std::optional<Loc> L = V.getAs<Loc>()) { if (const MemRegion *LR = L->getAsRegion()) AddToWorkList(LR); } @@ -1135,7 +1123,7 @@ void InvalidateRegionsWorker::VisitCluster(const MemRegion *baseR, if (Regions) Regions->push_back(baseR); - if (isa<AllocaRegion>(baseR) || isa<SymbolicRegion>(baseR)) { + if (isa<AllocaRegion, SymbolicRegion>(baseR)) { // Invalidate the region by setting its default value to // conjured symbol. The type of the symbol is irrelevant. DefinedOrUnknownSVal V = @@ -1174,7 +1162,7 @@ void InvalidateRegionsWorker::VisitCluster(const MemRegion *baseR, if (doNotInvalidateSuperRegion) { // We are not doing blank invalidation of the whole array region so we // have to manually invalidate each elements. - Optional<uint64_t> NumElements; + std::optional<uint64_t> NumElements; // Compute lower and upper offsets for region within array. if (const ConstantArrayType *CAT = dyn_cast<ConstantArrayType>(AT)) @@ -1206,11 +1194,9 @@ void InvalidateRegionsWorker::VisitCluster(const MemRegion *baseR, if (!C) goto conjure_default; - for (ClusterBindings::iterator I = C->begin(), E = C->end(); I != E; - ++I) { - const BindingKey &BK = I.getKey(); - Optional<uint64_t> ROffset = - BK.hasSymbolicOffset() ? Optional<uint64_t>() : BK.getOffset(); + for (const auto &[BK, V] : *C) { + std::optional<uint64_t> ROffset = + BK.hasSymbolicOffset() ? std::optional<uint64_t>() : BK.getOffset(); // Check offset is not symbolic and within array's boundaries. // Handles arrays of 0 elements and of 0-sized elements as well. @@ -1219,12 +1205,11 @@ void InvalidateRegionsWorker::VisitCluster(const MemRegion *baseR, (UpperOverflow && (*ROffset >= LowerOffset || *ROffset < UpperOffset)) || (LowerOffset == UpperOffset && *ROffset == LowerOffset))) { - B = B.removeBinding(I.getKey()); + B = B.removeBinding(BK); // Bound symbolic regions need to be invalidated for dead symbol // detection. - SVal V = I.getData(); const MemRegion *R = V.getAsRegion(); - if (R && isa<SymbolicRegion>(R)) + if (isa_and_nonnull<SymbolicRegion>(R)) VisitBinding(V); } } @@ -1295,21 +1280,12 @@ RegionStoreManager::invalidateGlobalRegion(MemRegion::Kind K, void RegionStoreManager::populateWorkList(InvalidateRegionsWorker &W, ArrayRef<SVal> Values, InvalidatedRegions *TopLevelRegions) { - for (ArrayRef<SVal>::iterator I = Values.begin(), - E = Values.end(); I != E; ++I) { - SVal V = *I; - if (Optional<nonloc::LazyCompoundVal> LCS = - V.getAs<nonloc::LazyCompoundVal>()) { - - const SValListTy &Vals = getInterestingValues(*LCS); - - for (SValListTy::const_iterator I = Vals.begin(), - E = Vals.end(); I != E; ++I) { - // Note: the last argument is false here because these are - // non-top-level regions. - if (const MemRegion *R = (*I).getAsRegion()) + for (SVal V : Values) { + if (auto LCS = V.getAs<nonloc::LazyCompoundVal>()) { + for (SVal S : getInterestingValues(*LCS)) + if (const MemRegion *R = S.getAsRegion()) W.AddToWorkList(R); - } + continue; } @@ -1365,11 +1341,11 @@ RegionStoreManager::invalidateRegions(Store store, case GFK_All: B = invalidateGlobalRegion(MemRegion::GlobalInternalSpaceRegionKind, Ex, Count, LCtx, B, Invalidated); - LLVM_FALLTHROUGH; + [[fallthrough]]; case GFK_SystemOnly: B = invalidateGlobalRegion(MemRegion::GlobalSystemSpaceRegionKind, Ex, Count, LCtx, B, Invalidated); - LLVM_FALLTHROUGH; + [[fallthrough]]; case GFK_None: break; } @@ -1388,10 +1364,10 @@ RegionStoreManager::invalidateRegions(Store store, /// the array). This is called by ExprEngine when evaluating casts /// from arrays to pointers. SVal RegionStoreManager::ArrayToPointer(Loc Array, QualType T) { - if (Array.getAs<loc::ConcreteInt>()) + if (isa<loc::ConcreteInt>(Array)) return Array; - if (!Array.getAs<loc::MemRegionVal>()) + if (!isa<loc::MemRegionVal>(Array)) return UnknownVal(); const SubRegion *R = @@ -1405,8 +1381,8 @@ SVal RegionStoreManager::ArrayToPointer(Loc Array, QualType T) { //===----------------------------------------------------------------------===// SVal RegionStoreManager::getBinding(RegionBindingsConstRef B, Loc L, QualType T) { - assert(!L.getAs<UnknownVal>() && "location unknown"); - assert(!L.getAs<UndefinedVal>() && "location undefined"); + assert(!isa<UnknownVal>(L) && "location unknown"); + assert(!isa<UndefinedVal>(L) && "location undefined"); // For access to concrete addresses, return UnknownVal. Checks // for null dereferences (and similar errors) are done by checkers, not @@ -1427,19 +1403,20 @@ SVal RegionStoreManager::getBinding(RegionBindingsConstRef B, Loc L, QualType T) return UnknownVal(); } - if (!isa<TypedValueRegion>(MR)) { - if (T.isNull()) { - if (const TypedRegion *TR = dyn_cast<TypedRegion>(MR)) - T = TR->getLocationType()->getPointeeType(); - else if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(MR)) - T = SR->getSymbol()->getType()->getPointeeType(); - } - assert(!T.isNull() && "Unable to auto-detect binding type!"); - assert(!T->isVoidType() && "Attempting to dereference a void pointer!"); - MR = GetElementZeroRegion(cast<SubRegion>(MR), T); - } else { - T = cast<TypedValueRegion>(MR)->getValueType(); + // Auto-detect the binding type. + if (T.isNull()) { + if (const auto *TVR = dyn_cast<TypedValueRegion>(MR)) + T = TVR->getValueType(); + else if (const auto *TR = dyn_cast<TypedRegion>(MR)) + T = TR->getLocationType()->getPointeeType(); + else if (const auto *SR = dyn_cast<SymbolicRegion>(MR)) + T = SR->getPointeeStaticType(); } + assert(!T.isNull() && "Unable to auto-detect binding type!"); + assert(!T->isVoidType() && "Attempting to dereference a void pointer!"); + + if (!isa<TypedValueRegion>(MR)) + MR = GetElementZeroRegion(cast<SubRegion>(MR), T); // FIXME: Perhaps this method should just take a 'const MemRegion*' argument // instead of 'Loc', and have the other Loc cases handled at a higher level. @@ -1548,16 +1525,17 @@ static QualType getUnderlyingType(const SubRegion *R) { /// /// Note that unlike RegionStoreManager::findLazyBinding, this will not search /// for lazy bindings for super-regions of \p R. -static Optional<nonloc::LazyCompoundVal> +static std::optional<nonloc::LazyCompoundVal> getExistingLazyBinding(SValBuilder &SVB, RegionBindingsConstRef B, const SubRegion *R, bool AllowSubregionBindings) { - Optional<SVal> V = B.getDefaultBinding(R); + std::optional<SVal> V = B.getDefaultBinding(R); if (!V) - return None; + return std::nullopt; - Optional<nonloc::LazyCompoundVal> LCV = V->getAs<nonloc::LazyCompoundVal>(); + std::optional<nonloc::LazyCompoundVal> LCV = + V->getAs<nonloc::LazyCompoundVal>(); if (!LCV) - return None; + return std::nullopt; // If the LCV is for a subregion, the types might not match, and we shouldn't // reuse the binding. @@ -1566,7 +1544,7 @@ getExistingLazyBinding(SValBuilder &SVB, RegionBindingsConstRef B, !RegionTy->isVoidPointerType()) { QualType SourceRegionTy = LCV->getRegion()->getValueType(); if (!SVB.getContext().hasSameUnqualifiedType(RegionTy, SourceRegionTy)) - return None; + return std::nullopt; } if (!AllowSubregionBindings) { @@ -1576,20 +1554,19 @@ getExistingLazyBinding(SValBuilder &SVB, RegionBindingsConstRef B, collectSubRegionBindings(Bindings, SVB, *B.lookup(R->getBaseRegion()), R, /*IncludeAllDefaultBindings=*/true); if (Bindings.size() > 1) - return None; + return std::nullopt; } return *LCV; } - std::pair<Store, const SubRegion *> RegionStoreManager::findLazyBinding(RegionBindingsConstRef B, const SubRegion *R, const SubRegion *originalRegion) { if (originalRegion != R) { - if (Optional<nonloc::LazyCompoundVal> V = - getExistingLazyBinding(svalBuilder, B, R, true)) + if (std::optional<nonloc::LazyCompoundVal> V = + getExistingLazyBinding(svalBuilder, B, R, true)) return std::make_pair(V->getStore(), V->getRegion()); } @@ -1625,10 +1602,313 @@ RegionStoreManager::findLazyBinding(RegionBindingsConstRef B, return Result; } +/// This is a helper function for `getConstantValFromConstArrayInitializer`. +/// +/// Return an array of extents of the declared array type. +/// +/// E.g. for `int x[1][2][3];` returns { 1, 2, 3 }. +static SmallVector<uint64_t, 2> +getConstantArrayExtents(const ConstantArrayType *CAT) { + assert(CAT && "ConstantArrayType should not be null"); + CAT = cast<ConstantArrayType>(CAT->getCanonicalTypeInternal()); + SmallVector<uint64_t, 2> Extents; + do { + Extents.push_back(CAT->getSize().getZExtValue()); + } while ((CAT = dyn_cast<ConstantArrayType>(CAT->getElementType()))); + return Extents; +} + +/// This is a helper function for `getConstantValFromConstArrayInitializer`. +/// +/// Return an array of offsets from nested ElementRegions and a root base +/// region. The array is never empty and a base region is never null. +/// +/// E.g. for `Element{Element{Element{VarRegion},1},2},3}` returns { 3, 2, 1 }. +/// This represents an access through indirection: `arr[1][2][3];` +/// +/// \param ER The given (possibly nested) ElementRegion. +/// +/// \note The result array is in the reverse order of indirection expression: +/// arr[1][2][3] -> { 3, 2, 1 }. This helps to provide complexity O(n), where n +/// is a number of indirections. It may not affect performance in real-life +/// code, though. +static std::pair<SmallVector<SVal, 2>, const MemRegion *> +getElementRegionOffsetsWithBase(const ElementRegion *ER) { + assert(ER && "ConstantArrayType should not be null"); + const MemRegion *Base; + SmallVector<SVal, 2> SValOffsets; + do { + SValOffsets.push_back(ER->getIndex()); + Base = ER->getSuperRegion(); + ER = dyn_cast<ElementRegion>(Base); + } while (ER); + return {SValOffsets, Base}; +} + +/// This is a helper function for `getConstantValFromConstArrayInitializer`. +/// +/// Convert array of offsets from `SVal` to `uint64_t` in consideration of +/// respective array extents. +/// \param SrcOffsets [in] The array of offsets of type `SVal` in reversed +/// order (expectedly received from `getElementRegionOffsetsWithBase`). +/// \param ArrayExtents [in] The array of extents. +/// \param DstOffsets [out] The array of offsets of type `uint64_t`. +/// \returns: +/// - `std::nullopt` for successful convertion. +/// - `UndefinedVal` or `UnknownVal` otherwise. It's expected that this SVal +/// will be returned as a suitable value of the access operation. +/// which should be returned as a correct +/// +/// \example: +/// const int arr[10][20][30] = {}; // ArrayExtents { 10, 20, 30 } +/// int x1 = arr[4][5][6]; // SrcOffsets { NonLoc(6), NonLoc(5), NonLoc(4) } +/// // DstOffsets { 4, 5, 6 } +/// // returns std::nullopt +/// int x2 = arr[42][5][-6]; // returns UndefinedVal +/// int x3 = arr[4][5][x2]; // returns UnknownVal +static std::optional<SVal> +convertOffsetsFromSvalToUnsigneds(const SmallVector<SVal, 2> &SrcOffsets, + const SmallVector<uint64_t, 2> ArrayExtents, + SmallVector<uint64_t, 2> &DstOffsets) { + // Check offsets for being out of bounds. + // C++20 [expr.add] 7.6.6.4 (excerpt): + // If P points to an array element i of an array object x with n + // elements, where i < 0 or i > n, the behavior is undefined. + // Dereferencing is not allowed on the "one past the last + // element", when i == n. + // Example: + // const int arr[3][2] = {{1, 2}, {3, 4}}; + // arr[0][0]; // 1 + // arr[0][1]; // 2 + // arr[0][2]; // UB + // arr[1][0]; // 3 + // arr[1][1]; // 4 + // arr[1][-1]; // UB + // arr[2][0]; // 0 + // arr[2][1]; // 0 + // arr[-2][0]; // UB + DstOffsets.resize(SrcOffsets.size()); + auto ExtentIt = ArrayExtents.begin(); + auto OffsetIt = DstOffsets.begin(); + // Reverse `SValOffsets` to make it consistent with `ArrayExtents`. + for (SVal V : llvm::reverse(SrcOffsets)) { + if (auto CI = V.getAs<nonloc::ConcreteInt>()) { + // When offset is out of array's bounds, result is UB. + const llvm::APSInt &Offset = CI->getValue(); + if (Offset.isNegative() || Offset.uge(*(ExtentIt++))) + return UndefinedVal(); + // Store index in a reversive order. + *(OffsetIt++) = Offset.getZExtValue(); + continue; + } + // Symbolic index presented. Return Unknown value. + // FIXME: We also need to take ElementRegions with symbolic indexes into + // account. + return UnknownVal(); + } + return std::nullopt; +} + +std::optional<SVal> RegionStoreManager::getConstantValFromConstArrayInitializer( + RegionBindingsConstRef B, const ElementRegion *R) { + assert(R && "ElementRegion should not be null"); + + // Treat an n-dimensional array. + SmallVector<SVal, 2> SValOffsets; + const MemRegion *Base; + std::tie(SValOffsets, Base) = getElementRegionOffsetsWithBase(R); + const VarRegion *VR = dyn_cast<VarRegion>(Base); + if (!VR) + return std::nullopt; + + assert(!SValOffsets.empty() && "getElementRegionOffsets guarantees the " + "offsets vector is not empty."); + + // Check if the containing array has an initialized value that we can trust. + // We can trust a const value or a value of a global initializer in main(). + const VarDecl *VD = VR->getDecl(); + if (!VD->getType().isConstQualified() && + !R->getElementType().isConstQualified() && + (!B.isMainAnalysis() || !VD->hasGlobalStorage())) + return std::nullopt; + + // Array's declaration should have `ConstantArrayType` type, because only this + // type contains an array extent. It may happen that array type can be of + // `IncompleteArrayType` type. To get the declaration of `ConstantArrayType` + // type, we should find the declaration in the redeclarations chain that has + // the initialization expression. + // NOTE: `getAnyInitializer` has an out-parameter, which returns a new `VD` + // from which an initializer is obtained. We replace current `VD` with the new + // `VD`. If the return value of the function is null than `VD` won't be + // replaced. + const Expr *Init = VD->getAnyInitializer(VD); + // NOTE: If `Init` is non-null, then a new `VD` is non-null for sure. So check + // `Init` for null only and don't worry about the replaced `VD`. + if (!Init) + return std::nullopt; + + // Array's declaration should have ConstantArrayType type, because only this + // type contains an array extent. + const ConstantArrayType *CAT = Ctx.getAsConstantArrayType(VD->getType()); + if (!CAT) + return std::nullopt; + + // Get array extents. + SmallVector<uint64_t, 2> Extents = getConstantArrayExtents(CAT); + + // The number of offsets should equal to the numbers of extents, + // otherwise wrong type punning occurred. For instance: + // int arr[1][2][3]; + // auto ptr = (int(*)[42])arr; + // auto x = ptr[4][2]; // UB + // FIXME: Should return UndefinedVal. + if (SValOffsets.size() != Extents.size()) + return std::nullopt; + + SmallVector<uint64_t, 2> ConcreteOffsets; + if (std::optional<SVal> V = convertOffsetsFromSvalToUnsigneds( + SValOffsets, Extents, ConcreteOffsets)) + return *V; + + // Handle InitListExpr. + // Example: + // const char arr[4][2] = { { 1, 2 }, { 3 }, 4, 5 }; + if (const auto *ILE = dyn_cast<InitListExpr>(Init)) + return getSValFromInitListExpr(ILE, ConcreteOffsets, R->getElementType()); + + // Handle StringLiteral. + // Example: + // const char arr[] = "abc"; + if (const auto *SL = dyn_cast<StringLiteral>(Init)) + return getSValFromStringLiteral(SL, ConcreteOffsets.front(), + R->getElementType()); + + // FIXME: Handle CompoundLiteralExpr. + + return std::nullopt; +} + +/// Returns an SVal, if possible, for the specified position of an +/// initialization list. +/// +/// \param ILE The given initialization list. +/// \param Offsets The array of unsigned offsets. E.g. for the expression +/// `int x = arr[1][2][3];` an array should be { 1, 2, 3 }. +/// \param ElemT The type of the result SVal expression. +/// \return Optional SVal for the particular position in the initialization +/// list. E.g. for the list `{{1, 2},[3, 4],{5, 6}, {}}` offsets: +/// - {1, 1} returns SVal{4}, because it's the second position in the second +/// sublist; +/// - {3, 0} returns SVal{0}, because there's no explicit value at this +/// position in the sublist. +/// +/// NOTE: Inorder to get a valid SVal, a caller shall guarantee valid offsets +/// for the given initialization list. Otherwise SVal can be an equivalent to 0 +/// or lead to assertion. +std::optional<SVal> RegionStoreManager::getSValFromInitListExpr( + const InitListExpr *ILE, const SmallVector<uint64_t, 2> &Offsets, + QualType ElemT) { + assert(ILE && "InitListExpr should not be null"); + + for (uint64_t Offset : Offsets) { + // C++20 [dcl.init.string] 9.4.2.1: + // An array of ordinary character type [...] can be initialized by [...] + // an appropriately-typed string-literal enclosed in braces. + // Example: + // const char arr[] = { "abc" }; + if (ILE->isStringLiteralInit()) + if (const auto *SL = dyn_cast<StringLiteral>(ILE->getInit(0))) + return getSValFromStringLiteral(SL, Offset, ElemT); + + // C++20 [expr.add] 9.4.17.5 (excerpt): + // i-th array element is value-initialized for each k < i ≤ n, + // where k is an expression-list size and n is an array extent. + if (Offset >= ILE->getNumInits()) + return svalBuilder.makeZeroVal(ElemT); + + const Expr *E = ILE->getInit(Offset); + const auto *IL = dyn_cast<InitListExpr>(E); + if (!IL) + // Return a constant value, if it is presented. + // FIXME: Support other SVals. + return svalBuilder.getConstantVal(E); + + // Go to the nested initializer list. + ILE = IL; + } + + assert(ILE); + + // FIXME: Unhandeled InitListExpr sub-expression, possibly constructing an + // enum? + return std::nullopt; +} + +/// Returns an SVal, if possible, for the specified position in a string +/// literal. +/// +/// \param SL The given string literal. +/// \param Offset The unsigned offset. E.g. for the expression +/// `char x = str[42];` an offset should be 42. +/// E.g. for the string "abc" offset: +/// - 1 returns SVal{b}, because it's the second position in the string. +/// - 42 returns SVal{0}, because there's no explicit value at this +/// position in the string. +/// \param ElemT The type of the result SVal expression. +/// +/// NOTE: We return `0` for every offset >= the literal length for array +/// declarations, like: +/// const char str[42] = "123"; // Literal length is 4. +/// char c = str[41]; // Offset is 41. +/// FIXME: Nevertheless, we can't do the same for pointer declaraions, like: +/// const char * const str = "123"; // Literal length is 4. +/// char c = str[41]; // Offset is 41. Returns `0`, but Undef +/// // expected. +/// It should be properly handled before reaching this point. +/// The main problem is that we can't distinguish between these declarations, +/// because in case of array we can get the Decl from VarRegion, but in case +/// of pointer the region is a StringRegion, which doesn't contain a Decl. +/// Possible solution could be passing an array extent along with the offset. +SVal RegionStoreManager::getSValFromStringLiteral(const StringLiteral *SL, + uint64_t Offset, + QualType ElemT) { + assert(SL && "StringLiteral should not be null"); + // C++20 [dcl.init.string] 9.4.2.3: + // If there are fewer initializers than there are array elements, each + // element not explicitly initialized shall be zero-initialized [dcl.init]. + uint32_t Code = (Offset >= SL->getLength()) ? 0 : SL->getCodeUnit(Offset); + return svalBuilder.makeIntVal(Code, ElemT); +} + +static std::optional<SVal> getDerivedSymbolForBinding( + RegionBindingsConstRef B, const TypedValueRegion *BaseRegion, + const TypedValueRegion *SubReg, const ASTContext &Ctx, SValBuilder &SVB) { + assert(BaseRegion); + QualType BaseTy = BaseRegion->getValueType(); + QualType Ty = SubReg->getValueType(); + if (BaseTy->isScalarType() && Ty->isScalarType()) { + if (Ctx.getTypeSizeInChars(BaseTy) >= Ctx.getTypeSizeInChars(Ty)) { + if (const std::optional<SVal> &ParentValue = + B.getDirectBinding(BaseRegion)) { + if (SymbolRef ParentValueAsSym = ParentValue->getAsSymbol()) + return SVB.getDerivedRegionValueSymbolVal(ParentValueAsSym, SubReg); + + if (ParentValue->isUndef()) + return UndefinedVal(); + + // Other cases: give up. We are indexing into a larger object + // that has some value, but we don't know how to handle that yet. + return UnknownVal(); + } + } + } + return std::nullopt; +} + SVal RegionStoreManager::getBindingForElement(RegionBindingsConstRef B, const ElementRegion* R) { // Check if the region has a binding. - if (const Optional<SVal> &V = B.getDirectBinding(R)) + if (const std::optional<SVal> &V = B.getDirectBinding(R)) return *V; const MemRegion* superR = R->getSuperRegion(); @@ -1636,59 +1916,21 @@ SVal RegionStoreManager::getBindingForElement(RegionBindingsConstRef B, // Check if the region is an element region of a string literal. if (const StringRegion *StrR = dyn_cast<StringRegion>(superR)) { // FIXME: Handle loads from strings where the literal is treated as - // an integer, e.g., *((unsigned int*)"hello") + // an integer, e.g., *((unsigned int*)"hello"). Such loads are UB according + // to C++20 7.2.1.11 [basic.lval]. QualType T = Ctx.getAsArrayType(StrR->getValueType())->getElementType(); if (!Ctx.hasSameUnqualifiedType(T, R->getElementType())) return UnknownVal(); - - const StringLiteral *Str = StrR->getStringLiteral(); - SVal Idx = R->getIndex(); - if (Optional<nonloc::ConcreteInt> CI = Idx.getAs<nonloc::ConcreteInt>()) { - int64_t i = CI->getValue().getSExtValue(); - // Abort on string underrun. This can be possible by arbitrary - // clients of getBindingForElement(). - if (i < 0) + if (const auto CI = R->getIndex().getAs<nonloc::ConcreteInt>()) { + const llvm::APSInt &Idx = CI->getValue(); + if (Idx < 0) return UndefinedVal(); - int64_t length = Str->getLength(); - // Technically, only i == length is guaranteed to be null. - // However, such overflows should be caught before reaching this point; - // the only time such an access would be made is if a string literal was - // used to initialize a larger array. - char c = (i >= length) ? '\0' : Str->getCodeUnit(i); - return svalBuilder.makeIntVal(c, T); - } - } else if (const VarRegion *VR = dyn_cast<VarRegion>(superR)) { - // Check if the containing array has an initialized value that we can trust. - // We can trust a const value or a value of a global initializer in main(). - const VarDecl *VD = VR->getDecl(); - if (VD->getType().isConstQualified() || - R->getElementType().isConstQualified() || - (B.isMainAnalysis() && VD->hasGlobalStorage())) { - if (const Expr *Init = VD->getAnyInitializer()) { - if (const auto *InitList = dyn_cast<InitListExpr>(Init)) { - // The array index has to be known. - if (auto CI = R->getIndex().getAs<nonloc::ConcreteInt>()) { - int64_t i = CI->getValue().getSExtValue(); - // If it is known that the index is out of bounds, we can return - // an undefined value. - if (i < 0) - return UndefinedVal(); - - if (auto CAT = Ctx.getAsConstantArrayType(VD->getType())) - if (CAT->getSize().sle(i)) - return UndefinedVal(); - - // If there is a list, but no init, it must be zero. - if (i >= InitList->getNumInits()) - return svalBuilder.makeZeroVal(R->getElementType()); - - if (const Expr *ElemInit = InitList->getInit(i)) - if (Optional<SVal> V = svalBuilder.getConstantVal(ElemInit)) - return *V; - } - } - } + const StringLiteral *SL = StrR->getStringLiteral(); + return getSValFromStringLiteral(SL, Idx.getZExtValue(), T); } + } else if (isa<ElementRegion, VarRegion>(superR)) { + if (std::optional<SVal> V = getConstantValFromConstArrayInitializer(B, R)) + return *V; } // Check for loads from a code text region. For such loads, just give up. @@ -1707,27 +1949,10 @@ SVal RegionStoreManager::getBindingForElement(RegionBindingsConstRef B, if (!O.getRegion()) return UnknownVal(); - if (const TypedValueRegion *baseR = - dyn_cast_or_null<TypedValueRegion>(O.getRegion())) { - QualType baseT = baseR->getValueType(); - if (baseT->isScalarType()) { - QualType elemT = R->getElementType(); - if (elemT->isScalarType()) { - if (Ctx.getTypeSizeInChars(baseT) >= Ctx.getTypeSizeInChars(elemT)) { - if (const Optional<SVal> &V = B.getDirectBinding(superR)) { - if (SymbolRef parentSym = V->getAsSymbol()) - return svalBuilder.getDerivedRegionValueSymbolVal(parentSym, R); - - if (V->isUnknownOrUndef()) - return *V; - // Other cases: give up. We are indexing into a larger object - // that has some value, but we don't know how to handle that yet. - return UnknownVal(); - } - } - } - } - } + if (const TypedValueRegion *baseR = dyn_cast<TypedValueRegion>(O.getRegion())) + if (auto V = getDerivedSymbolForBinding(B, baseR, R, Ctx, svalBuilder)) + return *V; + return getBindingForFieldOrElementCommon(B, R, R->getElementType()); } @@ -1735,18 +1960,12 @@ SVal RegionStoreManager::getBindingForField(RegionBindingsConstRef B, const FieldRegion* R) { // Check if the region has a binding. - if (const Optional<SVal> &V = B.getDirectBinding(R)) + if (const std::optional<SVal> &V = B.getDirectBinding(R)) return *V; - // Is the field declared constant and has an in-class initializer? + // If the containing record was initialized, try to get its constant value. const FieldDecl *FD = R->getDecl(); QualType Ty = FD->getType(); - if (Ty.isConstQualified()) - if (const Expr *Init = FD->getInClassInitializer()) - if (Optional<SVal> V = svalBuilder.getConstantVal(Init)) - return *V; - - // If the containing record was initialized, try to get its constant value. const MemRegion* superR = R->getSuperRegion(); if (const auto *VR = dyn_cast<VarRegion>(superR)) { const VarDecl *VD = VR->getDecl(); @@ -1761,7 +1980,7 @@ SVal RegionStoreManager::getBindingForField(RegionBindingsConstRef B, if (const auto *InitList = dyn_cast<InitListExpr>(Init)) { if (Index < InitList->getNumInits()) { if (const Expr *FieldInit = InitList->getInit(Index)) - if (Optional<SVal> V = svalBuilder.getConstantVal(FieldInit)) + if (std::optional<SVal> V = svalBuilder.getConstantVal(FieldInit)) return *V; } else { return svalBuilder.makeZeroVal(Ty); @@ -1769,17 +1988,35 @@ SVal RegionStoreManager::getBindingForField(RegionBindingsConstRef B, } } + // Handle the case where we are accessing into a larger scalar object. + // For example, this handles: + // struct header { + // unsigned a : 1; + // unsigned b : 1; + // }; + // struct parse_t { + // unsigned bits0 : 1; + // unsigned bits2 : 2; // <-- header + // unsigned bits4 : 4; + // }; + // int parse(parse_t *p) { + // unsigned copy = p->bits2; + // header *bits = (header *)© + // return bits->b; <-- here + // } + if (const auto *Base = dyn_cast<TypedValueRegion>(R->getBaseRegion())) + if (auto V = getDerivedSymbolForBinding(B, Base, R, Ctx, svalBuilder)) + return *V; + return getBindingForFieldOrElementCommon(B, R, Ty); } -Optional<SVal> -RegionStoreManager::getBindingForDerivedDefaultValue(RegionBindingsConstRef B, - const MemRegion *superR, - const TypedValueRegion *R, - QualType Ty) { +std::optional<SVal> RegionStoreManager::getBindingForDerivedDefaultValue( + RegionBindingsConstRef B, const MemRegion *superR, + const TypedValueRegion *R, QualType Ty) { - if (const Optional<SVal> &D = B.getDefaultBinding(superR)) { - const SVal &val = D.getValue(); + if (const std::optional<SVal> &D = B.getDefaultBinding(superR)) { + SVal val = *D; if (SymbolRef parentSym = val.getAsSymbol()) return svalBuilder.getDerivedRegionValueSymbolVal(parentSym, R); @@ -1791,14 +2028,13 @@ RegionStoreManager::getBindingForDerivedDefaultValue(RegionBindingsConstRef B, // Lazy bindings are usually handled through getExistingLazyBinding(). // We should unify these two code paths at some point. - if (val.getAs<nonloc::LazyCompoundVal>() || - val.getAs<nonloc::CompoundVal>()) + if (isa<nonloc::LazyCompoundVal, nonloc::CompoundVal>(val)) return val; llvm_unreachable("Unknown default value"); } - return None; + return std::nullopt; } SVal RegionStoreManager::getLazyBinding(const SubRegion *LazyBindingRegion, @@ -1869,7 +2105,8 @@ RegionStoreManager::getBindingForFieldOrElementCommon(RegionBindingsConstRef B, const SubRegion *SR = R; while (SR) { const MemRegion *Base = SR->getSuperRegion(); - if (Optional<SVal> D = getBindingForDerivedDefaultValue(B, Base, R, Ty)) { + if (std::optional<SVal> D = + getBindingForDerivedDefaultValue(B, Base, R, Ty)) { if (D->getAs<nonloc::LazyCompoundVal>()) { hasPartialLazyBinding = true; break; @@ -1908,8 +2145,13 @@ RegionStoreManager::getBindingForFieldOrElementCommon(RegionBindingsConstRef B, return UnknownVal(); // Additionally allow introspection of a block's internal layout. - if (!hasPartialLazyBinding && !isa<BlockDataRegion>(R->getBaseRegion())) + // Try to get direct binding if all other attempts failed thus far. + // Else, return UndefinedVal() + if (!hasPartialLazyBinding && !isa<BlockDataRegion>(R->getBaseRegion())) { + if (const std::optional<SVal> &V = B.getDefaultBinding(R)) + return *V; return UndefinedVal(); + } } // All other values are symbolic. @@ -1919,13 +2161,13 @@ RegionStoreManager::getBindingForFieldOrElementCommon(RegionBindingsConstRef B, SVal RegionStoreManager::getBindingForObjCIvar(RegionBindingsConstRef B, const ObjCIvarRegion* R) { // Check if the region has a binding. - if (const Optional<SVal> &V = B.getDirectBinding(R)) + if (const std::optional<SVal> &V = B.getDirectBinding(R)) return *V; const MemRegion *superR = R->getSuperRegion(); // Check if the super region has a default binding. - if (const Optional<SVal> &V = B.getDefaultBinding(superR)) { + if (const std::optional<SVal> &V = B.getDefaultBinding(superR)) { if (SymbolRef parentSym = V->getAsSymbol()) return svalBuilder.getDerivedRegionValueSymbolVal(parentSym, R); @@ -1940,10 +2182,10 @@ SVal RegionStoreManager::getBindingForVar(RegionBindingsConstRef B, const VarRegion *R) { // Check if the region has a binding. - if (Optional<SVal> V = B.getDirectBinding(R)) + if (std::optional<SVal> V = B.getDirectBinding(R)) return *V; - if (Optional<SVal> V = B.getDefaultBinding(R)) + if (std::optional<SVal> V = B.getDefaultBinding(R)) return *V; // Lazily derive a value for the VarRegion. @@ -1957,7 +2199,7 @@ SVal RegionStoreManager::getBindingForVar(RegionBindingsConstRef B, // Is 'VD' declared constant? If so, retrieve the constant value. if (VD->getType().isConstQualified()) { if (const Expr *Init = VD->getAnyInitializer()) { - if (Optional<SVal> V = svalBuilder.getConstantVal(Init)) + if (std::optional<SVal> V = svalBuilder.getConstantVal(Init)) return *V; // If the variable is const qualified and has an initializer but @@ -1978,7 +2220,7 @@ SVal RegionStoreManager::getBindingForVar(RegionBindingsConstRef B, // If we're in main(), then global initializers have not become stale yet. if (B.isMainAnalysis()) if (const Expr *Init = VD->getAnyInitializer()) - if (Optional<SVal> V = svalBuilder.getConstantVal(Init)) + if (std::optional<SVal> V = svalBuilder.getConstantVal(Init)) return *V; // Function-scoped static variables are default-initialized to 0; if they @@ -1988,9 +2230,9 @@ SVal RegionStoreManager::getBindingForVar(RegionBindingsConstRef B, if (isa<StaticGlobalSpaceRegion>(MS)) return svalBuilder.makeZeroVal(T); - if (Optional<SVal> V = getBindingForDerivedDefaultValue(B, MS, R, T)) { + if (std::optional<SVal> V = getBindingForDerivedDefaultValue(B, MS, R, T)) { assert(!V->getAs<nonloc::LazyCompoundVal>()); - return V.getValue(); + return *V; } return svalBuilder.getRegionValueSymbolVal(R); @@ -2026,18 +2268,13 @@ RegionStoreManager::getInterestingValues(nonloc::LazyCompoundVal LCV) { SmallVector<BindingPair, 32> Bindings; collectSubRegionBindings(Bindings, svalBuilder, *Cluster, LazyR, /*IncludeAllDefaultBindings=*/true); - for (SmallVectorImpl<BindingPair>::const_iterator I = Bindings.begin(), - E = Bindings.end(); - I != E; ++I) { - SVal V = I->second; + for (SVal V : llvm::make_second_range(Bindings)) { if (V.isUnknownOrUndef() || V.isConstant()) continue; - if (Optional<nonloc::LazyCompoundVal> InnerLCV = - V.getAs<nonloc::LazyCompoundVal>()) { + if (auto InnerLCV = V.getAs<nonloc::LazyCompoundVal>()) { const SValListTy &InnerList = getInterestingValues(*InnerLCV); List.insert(List.end(), InnerList.begin(), InnerList.end()); - continue; } List.push_back(V); @@ -2048,8 +2285,8 @@ RegionStoreManager::getInterestingValues(nonloc::LazyCompoundVal LCV) { NonLoc RegionStoreManager::createLazyBinding(RegionBindingsConstRef B, const TypedValueRegion *R) { - if (Optional<nonloc::LazyCompoundVal> V = - getExistingLazyBinding(svalBuilder, B, R, false)) + if (std::optional<nonloc::LazyCompoundVal> V = + getExistingLazyBinding(svalBuilder, B, R, false)) return *V; return svalBuilder.makeLazyCompoundVal(StoreRef(B.asStore(), *this), R); @@ -2094,7 +2331,7 @@ bool RegionStoreManager::includedInBindings(Store store, const ClusterBindings &Cluster = RI.getData(); for (ClusterBindings::iterator CI = Cluster.begin(), CE = Cluster.end(); CI != CE; ++CI) { - const SVal &D = CI.getData(); + SVal D = CI.getData(); if (const MemRegion *R = D.getAsRegion()) if (R->getBaseRegion() == region) return true; @@ -2109,7 +2346,7 @@ bool RegionStoreManager::includedInBindings(Store store, //===----------------------------------------------------------------------===// StoreRef RegionStoreManager::killBinding(Store ST, Loc L) { - if (Optional<loc::MemRegionVal> LV = L.getAs<loc::MemRegionVal>()) + if (std::optional<loc::MemRegionVal> LV = L.getAs<loc::MemRegionVal>()) if (const MemRegion* R = LV->getRegion()) return StoreRef(getRegionBindings(ST).removeBinding(R) .asImmutableMap() @@ -2140,22 +2377,21 @@ RegionStoreManager::bind(RegionBindingsConstRef B, Loc L, SVal V) { return bindAggregate(B, TR, V); } - if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(R)) { - // Binding directly to a symbolic region should be treated as binding - // to element 0. - QualType T = SR->getSymbol()->getType(); - if (T->isAnyPointerType() || T->isReferenceType()) - T = T->getPointeeType(); - - R = GetElementZeroRegion(SR, T); - } + // Binding directly to a symbolic region should be treated as binding + // to element 0. + if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(R)) + R = GetElementZeroRegion(SR, SR->getPointeeStaticType()); assert((!isa<CXXThisRegion>(R) || !B.lookup(R)) && "'this' pointer is not an l-value and is not assignable"); // Clear out bindings that may overlap with this binding. RegionBindingsRef NewB = removeSubRegionBindings(B, cast<SubRegion>(R)); - return NewB.addBinding(BindingKey::Make(R, BindingKey::Direct), V); + + // LazyCompoundVals should be always bound as 'default' bindings. + auto KeyKind = isa<nonloc::LazyCompoundVal>(V) ? BindingKey::Default + : BindingKey::Direct; + return NewB.addBinding(BindingKey::Make(R, KeyKind), V); } RegionBindingsRef @@ -2165,7 +2401,7 @@ RegionStoreManager::setImplicitDefaultValue(RegionBindingsConstRef B, SVal V; if (Loc::isLocType(T)) - V = svalBuilder.makeNull(); + V = svalBuilder.makeNullWithType(T); else if (T->isIntegralOrEnumerationType()) V = svalBuilder.makeZeroVal(T); else if (T->isStructureOrClassType() || T->isArrayType()) { @@ -2185,6 +2421,40 @@ RegionStoreManager::setImplicitDefaultValue(RegionBindingsConstRef B, return B.addBinding(R, BindingKey::Default, V); } +std::optional<RegionBindingsRef> RegionStoreManager::tryBindSmallArray( + RegionBindingsConstRef B, const TypedValueRegion *R, const ArrayType *AT, + nonloc::LazyCompoundVal LCV) { + + auto CAT = dyn_cast<ConstantArrayType>(AT); + + // If we don't know the size, create a lazyCompoundVal instead. + if (!CAT) + return std::nullopt; + + QualType Ty = CAT->getElementType(); + if (!(Ty->isScalarType() || Ty->isReferenceType())) + return std::nullopt; + + // If the array is too big, create a LCV instead. + uint64_t ArrSize = CAT->getSize().getLimitedValue(); + if (ArrSize > SmallArrayLimit) + return std::nullopt; + + RegionBindingsRef NewB = B; + + for (uint64_t i = 0; i < ArrSize; ++i) { + auto Idx = svalBuilder.makeArrayIndex(i); + const ElementRegion *SrcER = + MRMgr.getElementRegion(Ty, Idx, LCV.getRegion(), Ctx); + SVal V = getBindingForElement(getRegionBindings(LCV.getStore()), SrcER); + + const ElementRegion *DstER = MRMgr.getElementRegion(Ty, Idx, R, Ctx); + NewB = bind(NewB, loc::MemRegionVal(DstER), V); + } + + return NewB; +} + RegionBindingsRef RegionStoreManager::bindArray(RegionBindingsConstRef B, const TypedValueRegion* R, @@ -2192,7 +2462,7 @@ RegionStoreManager::bindArray(RegionBindingsConstRef B, const ArrayType *AT =cast<ArrayType>(Ctx.getCanonicalType(R->getValueType())); QualType ElementTy = AT->getElementType(); - Optional<uint64_t> Size; + std::optional<uint64_t> Size; if (const ConstantArrayType* CAT = dyn_cast<ConstantArrayType>(AT)) Size = CAT->getSize().getZExtValue(); @@ -2200,14 +2470,20 @@ RegionStoreManager::bindArray(RegionBindingsConstRef B, // Check if the init expr is a literal. If so, bind the rvalue instead. // FIXME: It's not responsibility of the Store to transform this lvalue // to rvalue. ExprEngine or maybe even CFG should do this before binding. - if (Optional<loc::MemRegionVal> MRV = Init.getAs<loc::MemRegionVal>()) { + if (std::optional<loc::MemRegionVal> MRV = Init.getAs<loc::MemRegionVal>()) { SVal V = getBinding(B.asStore(), *MRV, R->getValueType()); return bindAggregate(B, R, V); } // Handle lazy compound values. - if (Init.getAs<nonloc::LazyCompoundVal>()) + if (std::optional<nonloc::LazyCompoundVal> LCV = + Init.getAs<nonloc::LazyCompoundVal>()) { + if (std::optional<RegionBindingsRef> NewB = + tryBindSmallArray(B, R, AT, *LCV)) + return *NewB; + return bindAggregate(B, R, Init); + } if (Init.isUnknown()) return bindAggregate(B, R, UnknownVal()); @@ -2219,12 +2495,12 @@ RegionStoreManager::bindArray(RegionBindingsConstRef B, RegionBindingsRef NewB(B); - for (; Size.hasValue() ? i < Size.getValue() : true ; ++i, ++VI) { + for (; Size ? i < *Size : true; ++i, ++VI) { // The init list might be shorter than the array length. if (VI == VE) break; - const NonLoc &Idx = svalBuilder.makeArrayIndex(i); + NonLoc Idx = svalBuilder.makeArrayIndex(i); const ElementRegion *ER = MRMgr.getElementRegion(ElementTy, Idx, R, Ctx); if (ElementTy->isStructureOrClassType()) @@ -2238,7 +2514,7 @@ RegionStoreManager::bindArray(RegionBindingsConstRef B, // If the init list is shorter than the array length (or the array has // variable length), set the array default value. Values that are already set // are not overwritten. - if (!Size.hasValue() || i < Size.getValue()) + if (!Size || i < *Size) NewB = setImplicitDefaultValue(NewB, R, ElementTy); return NewB; @@ -2251,13 +2527,13 @@ RegionBindingsRef RegionStoreManager::bindVector(RegionBindingsConstRef B, const VectorType *VT = T->castAs<VectorType>(); // Use castAs for typedefs. // Handle lazy compound values and symbolic values. - if (V.getAs<nonloc::LazyCompoundVal>() || V.getAs<nonloc::SymbolVal>()) + if (isa<nonloc::LazyCompoundVal, nonloc::SymbolVal>(V)) return bindAggregate(B, R, V); // We may get non-CompoundVal accidentally due to imprecise cast logic or // that we are binding symbolic struct value. Kill the field values, and if // the value is symbolic go and bind it as a "default" binding. - if (!V.getAs<nonloc::CompoundVal>()) { + if (!isa<nonloc::CompoundVal>(V)) { return bindAggregate(B, R, UnknownVal()); } @@ -2284,16 +2560,14 @@ RegionBindingsRef RegionStoreManager::bindVector(RegionBindingsConstRef B, return NewB; } -Optional<RegionBindingsRef> -RegionStoreManager::tryBindSmallStruct(RegionBindingsConstRef B, - const TypedValueRegion *R, - const RecordDecl *RD, - nonloc::LazyCompoundVal LCV) { +std::optional<RegionBindingsRef> RegionStoreManager::tryBindSmallStruct( + RegionBindingsConstRef B, const TypedValueRegion *R, const RecordDecl *RD, + nonloc::LazyCompoundVal LCV) { FieldVector Fields; if (const CXXRecordDecl *Class = dyn_cast<CXXRecordDecl>(RD)) if (Class->getNumBases() != 0 || Class->getNumVBases() != 0) - return None; + return std::nullopt; for (const auto *FD : RD->fields()) { if (FD->isUnnamedBitfield()) @@ -2302,22 +2576,28 @@ RegionStoreManager::tryBindSmallStruct(RegionBindingsConstRef B, // If there are too many fields, or if any of the fields are aggregates, // just use the LCV as a default binding. if (Fields.size() == SmallStructLimit) - return None; + return std::nullopt; QualType Ty = FD->getType(); + + // Zero length arrays are basically no-ops, so we also ignore them here. + if (Ty->isConstantArrayType() && + Ctx.getConstantArrayElementCount(Ctx.getAsConstantArrayType(Ty)) == 0) + continue; + if (!(Ty->isScalarType() || Ty->isReferenceType())) - return None; + return std::nullopt; Fields.push_back(FD); } RegionBindingsRef NewB = B; - for (FieldVector::iterator I = Fields.begin(), E = Fields.end(); I != E; ++I){ - const FieldRegion *SourceFR = MRMgr.getFieldRegion(*I, LCV.getRegion()); + for (const FieldDecl *Field : Fields) { + const FieldRegion *SourceFR = MRMgr.getFieldRegion(Field, LCV.getRegion()); SVal V = getBindingForField(getRegionBindings(LCV.getStore()), SourceFR); - const FieldRegion *DestFR = MRMgr.getFieldRegion(*I, R); + const FieldRegion *DestFR = MRMgr.getFieldRegion(Field, R); NewB = bind(NewB, loc::MemRegionVal(DestFR), V); } @@ -2325,11 +2605,8 @@ RegionStoreManager::tryBindSmallStruct(RegionBindingsConstRef B, } RegionBindingsRef RegionStoreManager::bindStruct(RegionBindingsConstRef B, - const TypedValueRegion* R, + const TypedValueRegion *R, SVal V) { - if (!Features.supportsFields()) - return B; - QualType T = R->getValueType(); assert(T->isStructureOrClassType()); @@ -2340,19 +2617,20 @@ RegionBindingsRef RegionStoreManager::bindStruct(RegionBindingsConstRef B, return B; // Handle lazy compound values and symbolic values. - if (Optional<nonloc::LazyCompoundVal> LCV = - V.getAs<nonloc::LazyCompoundVal>()) { - if (Optional<RegionBindingsRef> NewB = tryBindSmallStruct(B, R, RD, *LCV)) + if (std::optional<nonloc::LazyCompoundVal> LCV = + V.getAs<nonloc::LazyCompoundVal>()) { + if (std::optional<RegionBindingsRef> NewB = + tryBindSmallStruct(B, R, RD, *LCV)) return *NewB; return bindAggregate(B, R, V); } - if (V.getAs<nonloc::SymbolVal>()) + if (isa<nonloc::SymbolVal>(V)) return bindAggregate(B, R, V); // We may get non-CompoundVal accidentally due to imprecise cast logic or // that we are binding symbolic struct value. Kill the field values, and if // the value is symbolic go and bind it as a "default" binding. - if (V.isUnknown() || !V.getAs<nonloc::CompoundVal>()) + if (V.isUnknown() || !isa<nonloc::CompoundVal>(V)) return bindAggregate(B, R, UnknownVal()); // The raw CompoundVal is essentially a symbolic InitListExpr: an (immutable) @@ -2535,25 +2813,26 @@ void RemoveDeadBindingsWorker::VisitCluster(const MemRegion *baseR, if (const SymbolicRegion *SymR = dyn_cast<SymbolicRegion>(baseR)) SymReaper.markLive(SymR->getSymbol()); - for (ClusterBindings::iterator I = C->begin(), E = C->end(); I != E; ++I) { + for (const auto &[Key, Val] : *C) { // Element index of a binding key is live. - SymReaper.markElementIndicesLive(I.getKey().getRegion()); + SymReaper.markElementIndicesLive(Key.getRegion()); - VisitBinding(I.getData()); + VisitBinding(Val); } } void RemoveDeadBindingsWorker::VisitBinding(SVal V) { - // Is it a LazyCompoundVal? All referenced regions are live as well. - if (Optional<nonloc::LazyCompoundVal> LCS = - V.getAs<nonloc::LazyCompoundVal>()) { - - const RegionStoreManager::SValListTy &Vals = RM.getInterestingValues(*LCS); - - for (RegionStoreManager::SValListTy::const_iterator I = Vals.begin(), - E = Vals.end(); - I != E; ++I) - VisitBinding(*I); + // Is it a LazyCompoundVal? All referenced regions are live as well. + // The LazyCompoundVal itself is not live but should be readable. + if (auto LCS = V.getAs<nonloc::LazyCompoundVal>()) { + SymReaper.markLazilyCopied(LCS->getRegion()); + + for (SVal V : RM.getInterestingValues(*LCS)) { + if (auto DepLCS = V.getAs<nonloc::LazyCompoundVal>()) + SymReaper.markLazilyCopied(DepLCS->getRegion()); + else + VisitBinding(V); + } return; } @@ -2565,17 +2844,15 @@ void RemoveDeadBindingsWorker::VisitBinding(SVal V) { // All regions captured by a block are also live. if (const BlockDataRegion *BR = dyn_cast<BlockDataRegion>(R)) { - BlockDataRegion::referenced_vars_iterator I = BR->referenced_vars_begin(), - E = BR->referenced_vars_end(); - for ( ; I != E; ++I) - AddToWorkList(I.getCapturedRegion()); + for (auto Var : BR->referenced_vars()) + AddToWorkList(Var.getCapturedRegion()); } } // Update the set of live symbols. - for (auto SI = V.symbol_begin(), SE = V.symbol_end(); SI!=SE; ++SI) - SymReaper.markLive(*SI); + for (SymbolRef Sym : V.symbols()) + SymReaper.markLive(Sym); } bool RemoveDeadBindingsWorker::UpdatePostponed() { @@ -2583,12 +2860,10 @@ bool RemoveDeadBindingsWorker::UpdatePostponed() { // having done a scan. bool Changed = false; - for (auto I = Postponed.begin(), E = Postponed.end(); I != E; ++I) { - if (const SymbolicRegion *SR = *I) { - if (SymReaper.isLive(SR->getSymbol())) { - Changed |= AddToWorkList(SR); - *I = nullptr; - } + for (const SymbolicRegion *SR : Postponed) { + if (SymReaper.isLive(SR->getSymbol())) { + Changed |= AddToWorkList(SR); + SR = nullptr; } } @@ -2603,9 +2878,8 @@ StoreRef RegionStoreManager::removeDeadBindings(Store store, W.GenerateClusters(); // Enqueue the region roots onto the worklist. - for (SymbolReaper::region_iterator I = SymReaper.region_begin(), - E = SymReaper.region_end(); I != E; ++I) { - W.AddToWorkList(*I); + for (const MemRegion *Reg : SymReaper.regions()) { + W.AddToWorkList(Reg); } do W.RunWorkList(); while (W.UpdatePostponed()); @@ -2613,9 +2887,7 @@ StoreRef RegionStoreManager::removeDeadBindings(Store store, // We have now scanned the store, marking reachable regions and symbols // as live. We now remove all the regions that are dead from the store // as well as update DSymbols with the set symbols that are now dead. - for (RegionBindingsRef::iterator I = B.begin(), E = B.end(); I != E; ++I) { - const MemRegion *Base = I.getKey(); - + for (const MemRegion *Base : llvm::make_first_range(B)) { // If the cluster has been visited, we know the region has been marked. // Otherwise, remove the dead entry. if (!W.isVisited(Base)) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp index 7395622a659c..04165a443fff 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp @@ -1,9 +1,8 @@ //== SMTConstraintManager.cpp -----------------------------------*- C++ -*--==// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// 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 // //===----------------------------------------------------------------------===// diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index b459b5adb511..eb9cde5c8918 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -19,25 +19,25 @@ #include "clang/AST/ExprObjC.h" #include "clang/AST/Stmt.h" #include "clang/AST/Type.h" -#include "clang/Basic/LLVM.h" #include "clang/Analysis/AnalysisDeclContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "clang/Basic/LLVM.h" #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.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/SValVisitor.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/Store.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" #include "llvm/ADT/APSInt.h" -#include "llvm/ADT/None.h" -#include "llvm/ADT/Optional.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Compiler.h" #include <cassert> +#include <optional> #include <tuple> using namespace clang; @@ -49,9 +49,19 @@ using namespace ento; void SValBuilder::anchor() {} +SValBuilder::SValBuilder(llvm::BumpPtrAllocator &alloc, ASTContext &context, + ProgramStateManager &stateMgr) + : Context(context), BasicVals(context, alloc), + SymMgr(context, BasicVals, alloc), MemMgr(context, alloc), + StateMgr(stateMgr), + AnOpts( + stateMgr.getOwningEngine().getAnalysisManager().getAnalyzerOptions()), + ArrayIndexTy(context.LongLongTy), + ArrayIndexWidth(context.getTypeSize(ArrayIndexTy)) {} + DefinedOrUnknownSVal SValBuilder::makeZeroVal(QualType type) { if (Loc::isLocType(type)) - return makeNull(); + return makeNullWithType(type); if (type->isIntegralOrEnumerationType()) return makeIntVal(0, type); @@ -64,8 +74,10 @@ DefinedOrUnknownSVal SValBuilder::makeZeroVal(QualType type) { return UnknownVal(); } -NonLoc SValBuilder::makeNonLoc(const SymExpr *lhs, BinaryOperator::Opcode op, - const llvm::APSInt& rhs, QualType type) { +nonloc::SymbolVal SValBuilder::makeNonLoc(const SymExpr *lhs, + BinaryOperator::Opcode op, + const llvm::APSInt &rhs, + QualType type) { // The Environment ensures we always get a persistent APSInt in // BasicValueFactory, so we don't need to get the APSInt from // BasicValueFactory again. @@ -74,25 +86,35 @@ NonLoc SValBuilder::makeNonLoc(const SymExpr *lhs, BinaryOperator::Opcode op, return nonloc::SymbolVal(SymMgr.getSymIntExpr(lhs, op, rhs, type)); } -NonLoc SValBuilder::makeNonLoc(const llvm::APSInt& lhs, - BinaryOperator::Opcode op, const SymExpr *rhs, - QualType type) { +nonloc::SymbolVal SValBuilder::makeNonLoc(const llvm::APSInt &lhs, + BinaryOperator::Opcode op, + const SymExpr *rhs, QualType type) { assert(rhs); assert(!Loc::isLocType(type)); return nonloc::SymbolVal(SymMgr.getIntSymExpr(lhs, op, rhs, type)); } -NonLoc SValBuilder::makeNonLoc(const SymExpr *lhs, BinaryOperator::Opcode op, - const SymExpr *rhs, QualType type) { +nonloc::SymbolVal SValBuilder::makeNonLoc(const SymExpr *lhs, + BinaryOperator::Opcode op, + const SymExpr *rhs, QualType type) { assert(lhs && rhs); assert(!Loc::isLocType(type)); return nonloc::SymbolVal(SymMgr.getSymSymExpr(lhs, op, rhs, type)); } -NonLoc SValBuilder::makeNonLoc(const SymExpr *operand, - QualType fromTy, QualType toTy) { +NonLoc SValBuilder::makeNonLoc(const SymExpr *operand, UnaryOperator::Opcode op, + QualType type) { + assert(operand); + assert(!Loc::isLocType(type)); + return nonloc::SymbolVal(SymMgr.getUnarySymExpr(operand, op, type)); +} + +nonloc::SymbolVal SValBuilder::makeNonLoc(const SymExpr *operand, + QualType fromTy, QualType toTy) { assert(operand); assert(!Loc::isLocType(toTy)); + if (fromTy == toTy) + return nonloc::SymbolVal(operand); return nonloc::SymbolVal(SymMgr.getCastSymbol(operand, fromTy, toTy)); } @@ -101,7 +123,8 @@ SVal SValBuilder::convertToArrayIndex(SVal val) { return val; // Common case: we have an appropriately sized integer. - if (Optional<nonloc::ConcreteInt> CI = val.getAs<nonloc::ConcreteInt>()) { + if (std::optional<nonloc::ConcreteInt> CI = + val.getAs<nonloc::ConcreteInt>()) { const llvm::APSInt& I = CI->getValue(); if (I.getBitWidth() == ArrayIndexWidth && I.isSigned()) return val; @@ -208,6 +231,14 @@ SValBuilder::getConjuredHeapSymbolVal(const Expr *E, return loc::MemRegionVal(MemMgr.getSymbolicHeapRegion(sym)); } +loc::MemRegionVal SValBuilder::getAllocaRegionVal(const Expr *E, + const LocationContext *LCtx, + unsigned VisitCount) { + const AllocaRegion *R = + getRegionManager().getAllocaRegion(E, VisitCount, LCtx); + return loc::MemRegionVal(R); +} + DefinedSVal SValBuilder::getMetadataSymbolVal(const void *symbolTag, const MemRegion *region, const Expr *expr, QualType type, @@ -244,8 +275,7 @@ SValBuilder::getDerivedRegionValueSymbolVal(SymbolRef parentSymbol, } DefinedSVal SValBuilder::getMemberPointer(const NamedDecl *ND) { - assert(!ND || isa<CXXMethodDecl>(ND) || isa<FieldDecl>(ND) || - isa<IndirectFieldDecl>(ND)); + assert(!ND || (isa<CXXMethodDecl, FieldDecl, IndirectFieldDecl>(ND))); if (const auto *MD = dyn_cast_or_null<CXXMethodDecl>(ND)) { // Sema treats pointers to static member functions as have function pointer @@ -253,7 +283,7 @@ DefinedSVal SValBuilder::getMemberPointer(const NamedDecl *ND) { // We don't need to play a similar trick for static member fields // because these are represented as plain VarDecls and not FieldDecls // in the AST. - if (MD->isStatic()) + if (!MD->isImplicitObjectMemberFunction()) return getFunctionPointer(MD); } @@ -275,11 +305,11 @@ DefinedSVal SValBuilder::getBlockPointer(const BlockDecl *block, return loc::MemRegionVal(BD); } -Optional<loc::MemRegionVal> +std::optional<loc::MemRegionVal> SValBuilder::getCastedMemRegionVal(const MemRegion *R, QualType Ty) { if (auto OptR = StateMgr.getStoreManager().castRegion(R, Ty)) return loc::MemRegionVal(*OptR); - return None; + return std::nullopt; } /// Return a memory region for the 'this' object reference. @@ -297,7 +327,7 @@ loc::MemRegionVal SValBuilder::getCXXThis(const CXXRecordDecl *D, return loc::MemRegionVal(getRegionManager().getCXXThisRegion(PT, SFC)); } -Optional<SVal> SValBuilder::getConstantVal(const Expr *E) { +std::optional<SVal> SValBuilder::getConstantVal(const Expr *E) { E = E->IgnoreParens(); switch (E->getStmtClass()) { @@ -350,7 +380,7 @@ Optional<SVal> SValBuilder::getConstantVal(const Expr *E) { return makeBoolVal(cast<ObjCBoolLiteralExpr>(E)); case Stmt::CXXNullPtrLiteralExprClass: - return makeNull(); + return makeNullWithType(E->getType()); case Stmt::CStyleCastExprClass: case Stmt::CXXFunctionalCastExprClass: @@ -367,21 +397,20 @@ Optional<SVal> SValBuilder::getConstantVal(const Expr *E) { case CK_NoOp: case CK_BitCast: { const Expr *SE = CE->getSubExpr(); - Optional<SVal> Val = getConstantVal(SE); + std::optional<SVal> Val = getConstantVal(SE); if (!Val) - return None; + return std::nullopt; return evalCast(*Val, CE->getType(), SE->getType()); } } - // FALLTHROUGH - LLVM_FALLTHROUGH; + [[fallthrough]]; } // If we don't have a special case, fall back to the AST's constant evaluator. default: { // Don't try to come up with a value for materialized temporaries. if (E->isGLValue()) - return None; + return std::nullopt; ASTContext &Ctx = getContext(); Expr::EvalResult Result; @@ -390,9 +419,9 @@ Optional<SVal> SValBuilder::getConstantVal(const Expr *E) { if (Loc::isLocType(E->getType())) if (E->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNotNull)) - return makeNull(); + return makeNullWithType(E->getType()); - return None; + return std::nullopt; } } } @@ -405,25 +434,62 @@ SVal SValBuilder::makeSymExprValNN(BinaryOperator::Opcode Op, // TODO: When the Max Complexity is reached, we should conjure a symbol // instead of generating an Unknown value and propagate the taint info to it. - const unsigned MaxComp = StateMgr.getOwningEngine() - .getAnalysisManager() - .options.MaxSymbolComplexity; + const unsigned MaxComp = AnOpts.MaxSymbolComplexity; if (symLHS && symRHS && (symLHS->computeComplexity() + symRHS->computeComplexity()) < MaxComp) return makeNonLoc(symLHS, Op, symRHS, ResultTy); if (symLHS && symLHS->computeComplexity() < MaxComp) - if (Optional<nonloc::ConcreteInt> rInt = RHS.getAs<nonloc::ConcreteInt>()) + if (std::optional<nonloc::ConcreteInt> rInt = + RHS.getAs<nonloc::ConcreteInt>()) return makeNonLoc(symLHS, Op, rInt->getValue(), ResultTy); if (symRHS && symRHS->computeComplexity() < MaxComp) - if (Optional<nonloc::ConcreteInt> lInt = LHS.getAs<nonloc::ConcreteInt>()) + if (std::optional<nonloc::ConcreteInt> lInt = + LHS.getAs<nonloc::ConcreteInt>()) return makeNonLoc(lInt->getValue(), Op, symRHS, ResultTy); return UnknownVal(); } +SVal SValBuilder::evalMinus(NonLoc X) { + switch (X.getKind()) { + case nonloc::ConcreteIntKind: + return makeIntVal(-X.castAs<nonloc::ConcreteInt>().getValue()); + case nonloc::SymbolValKind: + return makeNonLoc(X.castAs<nonloc::SymbolVal>().getSymbol(), UO_Minus, + X.getType(Context)); + default: + return UnknownVal(); + } +} + +SVal SValBuilder::evalComplement(NonLoc X) { + switch (X.getKind()) { + case nonloc::ConcreteIntKind: + return makeIntVal(~X.castAs<nonloc::ConcreteInt>().getValue()); + case nonloc::SymbolValKind: + return makeNonLoc(X.castAs<nonloc::SymbolVal>().getSymbol(), UO_Not, + X.getType(Context)); + default: + return UnknownVal(); + } +} + +SVal SValBuilder::evalUnaryOp(ProgramStateRef state, UnaryOperator::Opcode opc, + SVal operand, QualType type) { + auto OpN = operand.getAs<NonLoc>(); + if (!OpN) + return UnknownVal(); + + if (opc == UO_Minus) + return evalMinus(*OpN); + if (opc == UO_Not) + return evalComplement(*OpN); + llvm_unreachable("Unexpected unary operator"); +} + SVal SValBuilder::evalBinOp(ProgramStateRef state, BinaryOperator::Opcode op, SVal lhs, SVal rhs, QualType type) { if (lhs.isUndef() || rhs.isUndef()) @@ -432,8 +498,7 @@ SVal SValBuilder::evalBinOp(ProgramStateRef state, BinaryOperator::Opcode op, if (lhs.isUnknown() || rhs.isUnknown()) return UnknownVal(); - if (lhs.getAs<nonloc::LazyCompoundVal>() || - rhs.getAs<nonloc::LazyCompoundVal>()) { + if (isa<nonloc::LazyCompoundVal>(lhs) || isa<nonloc::LazyCompoundVal>(rhs)) { return UnknownVal(); } @@ -445,20 +510,30 @@ SVal SValBuilder::evalBinOp(ProgramStateRef state, BinaryOperator::Opcode op, return UnknownVal(); } - if (Optional<Loc> LV = lhs.getAs<Loc>()) { - if (Optional<Loc> RV = rhs.getAs<Loc>()) + if (std::optional<Loc> LV = lhs.getAs<Loc>()) { + if (std::optional<Loc> RV = rhs.getAs<Loc>()) return evalBinOpLL(state, op, *LV, *RV, type); return evalBinOpLN(state, op, *LV, rhs.castAs<NonLoc>(), type); } - if (Optional<Loc> RV = rhs.getAs<Loc>()) { - // Support pointer arithmetic where the addend is on the left - // and the pointer on the right. - assert(op == BO_Add); + if (const std::optional<Loc> RV = rhs.getAs<Loc>()) { + const auto IsCommutative = [](BinaryOperatorKind Op) { + return Op == BO_Mul || Op == BO_Add || Op == BO_And || Op == BO_Xor || + Op == BO_Or; + }; + + if (IsCommutative(op)) { + // Swap operands. + return evalBinOpLN(state, op, *RV, lhs.castAs<NonLoc>(), type); + } - // Commute the operands. - return evalBinOpLN(state, op, *RV, lhs.castAs<NonLoc>(), type); + // If the right operand is a concrete int location then we have nothing + // better but to treat it as a simple nonloc. + if (auto RV = rhs.getAs<loc::ConcreteInt>()) { + const nonloc::ConcreteInt RhsAsLoc = makeIntVal(RV->getValue()); + return evalBinOpNN(state, op, lhs.castAs<NonLoc>(), RhsAsLoc, type); + } } return evalBinOpNN(state, op, lhs.castAs<NonLoc>(), rhs.castAs<NonLoc>(), @@ -531,11 +606,9 @@ SVal SValBuilder::evalIntegralCast(ProgramStateRef state, SVal val, APSIntType ToType(getContext().getTypeSize(castTy), castTy->isUnsignedIntegerType()); llvm::APSInt ToTypeMax = ToType.getMaxValue(); - NonLoc ToTypeMaxVal = - makeIntVal(ToTypeMax.isUnsigned() ? ToTypeMax.getZExtValue() - : ToTypeMax.getSExtValue(), - castTy) - .castAs<NonLoc>(); + + NonLoc ToTypeMaxVal = makeIntVal(ToTypeMax); + // Check the range of the symbol being casted against the maximum value of the // target type. NonLoc FromVal = val.castAs<NonLoc>(); @@ -546,448 +619,489 @@ SVal SValBuilder::evalIntegralCast(ProgramStateRef state, SVal val, std::tie(IsNotTruncated, IsTruncated) = state->assume(CompVal); if (!IsNotTruncated && IsTruncated) { // Symbol is truncated so we evaluate it as a cast. - NonLoc CastVal = makeNonLoc(se, originalTy, castTy); - return CastVal; + return makeNonLoc(se, originalTy, castTy); } return evalCast(val, castTy, originalTy); } //===----------------------------------------------------------------------===// -// Cast methods. -// `evalCast` is the main method -// `evalCastKind` and `evalCastSubKind` are helpers +// Cast method. +// `evalCast` and its helper `EvalCastVisitor` //===----------------------------------------------------------------------===// -/// Cast a given SVal to another SVal using given QualType's. -/// \param V -- SVal that should be casted. -/// \param CastTy -- QualType that V should be casted according to. -/// \param OriginalTy -- QualType which is associated to V. It provides -/// additional information about what type the cast performs from. -/// \returns the most appropriate casted SVal. -/// Note: Many cases don't use an exact OriginalTy. It can be extracted -/// from SVal or the cast can performs unconditionaly. Always pass OriginalTy! -/// It can be crucial in certain cases and generates different results. -/// FIXME: If `OriginalTy.isNull()` is true, then cast performs based on CastTy -/// only. This behavior is uncertain and should be improved. -SVal SValBuilder::evalCast(SVal V, QualType CastTy, QualType OriginalTy) { - if (CastTy.isNull()) - return V; - - CastTy = Context.getCanonicalType(CastTy); +namespace { +class EvalCastVisitor : public SValVisitor<EvalCastVisitor, SVal> { +private: + SValBuilder &VB; + ASTContext &Context; + QualType CastTy, OriginalTy; - const bool IsUnknownOriginalType = OriginalTy.isNull(); - if (!IsUnknownOriginalType) { - OriginalTy = Context.getCanonicalType(OriginalTy); +public: + EvalCastVisitor(SValBuilder &VB, QualType CastTy, QualType OriginalTy) + : VB(VB), Context(VB.getContext()), CastTy(CastTy), + OriginalTy(OriginalTy) {} - if (CastTy == OriginalTy) + SVal Visit(SVal V) { + if (CastTy.isNull()) return V; - // FIXME: Move this check to the most appropriate - // evalCastKind/evalCastSubKind function. For const casts, casts to void, - // just propagate the value. - if (!CastTy->isVariableArrayType() && !OriginalTy->isVariableArrayType()) - if (shouldBeModeledWithNoOp(Context, Context.getPointerType(CastTy), - Context.getPointerType(OriginalTy))) - return V; - } - - // Cast SVal according to kinds. - switch (V.getBaseKind()) { - case SVal::UndefinedValKind: - return evalCastKind(V.castAs<UndefinedVal>(), CastTy, OriginalTy); - case SVal::UnknownValKind: - return evalCastKind(V.castAs<UnknownVal>(), CastTy, OriginalTy); - case SVal::LocKind: - return evalCastKind(V.castAs<Loc>(), CastTy, OriginalTy); - case SVal::NonLocKind: - return evalCastKind(V.castAs<NonLoc>(), CastTy, OriginalTy); - } - - llvm_unreachable("Unknown SVal kind"); -} + CastTy = Context.getCanonicalType(CastTy); -SVal SValBuilder::evalCastKind(UndefinedVal V, QualType CastTy, - QualType OriginalTy) { - return V; -} + const bool IsUnknownOriginalType = OriginalTy.isNull(); + if (!IsUnknownOriginalType) { + OriginalTy = Context.getCanonicalType(OriginalTy); -SVal SValBuilder::evalCastKind(UnknownVal V, QualType CastTy, - QualType OriginalTy) { - return V; -} + if (CastTy == OriginalTy) + return V; -SVal SValBuilder::evalCastKind(Loc V, QualType CastTy, QualType OriginalTy) { - switch (V.getSubKind()) { - case loc::ConcreteIntKind: - return evalCastSubKind(V.castAs<loc::ConcreteInt>(), CastTy, OriginalTy); - case loc::GotoLabelKind: - return evalCastSubKind(V.castAs<loc::GotoLabel>(), CastTy, OriginalTy); - case loc::MemRegionValKind: - return evalCastSubKind(V.castAs<loc::MemRegionVal>(), CastTy, OriginalTy); + // FIXME: Move this check to the most appropriate + // evalCastKind/evalCastSubKind function. For const casts, casts to void, + // just propagate the value. + if (!CastTy->isVariableArrayType() && !OriginalTy->isVariableArrayType()) + if (shouldBeModeledWithNoOp(Context, Context.getPointerType(CastTy), + Context.getPointerType(OriginalTy))) + return V; + } + return SValVisitor::Visit(V); } + SVal VisitUndefinedVal(UndefinedVal V) { return V; } + SVal VisitUnknownVal(UnknownVal V) { return V; } + SVal VisitConcreteInt(loc::ConcreteInt V) { + // Pointer to bool. + if (CastTy->isBooleanType()) + return VB.makeTruthVal(V.getValue().getBoolValue(), CastTy); + + // Pointer to integer. + if (CastTy->isIntegralOrEnumerationType()) { + llvm::APSInt Value = V.getValue(); + VB.getBasicValueFactory().getAPSIntType(CastTy).apply(Value); + return VB.makeIntVal(Value); + } - llvm_unreachable("Unknown SVal kind"); -} + // Pointer to any pointer. + if (Loc::isLocType(CastTy)) { + llvm::APSInt Value = V.getValue(); + VB.getBasicValueFactory().getAPSIntType(CastTy).apply(Value); + return loc::ConcreteInt(VB.getBasicValueFactory().getValue(Value)); + } -SVal SValBuilder::evalCastKind(NonLoc V, QualType CastTy, QualType OriginalTy) { - switch (V.getSubKind()) { - case nonloc::CompoundValKind: - return evalCastSubKind(V.castAs<nonloc::CompoundVal>(), CastTy, OriginalTy); - case nonloc::ConcreteIntKind: - return evalCastSubKind(V.castAs<nonloc::ConcreteInt>(), CastTy, OriginalTy); - case nonloc::LazyCompoundValKind: - return evalCastSubKind(V.castAs<nonloc::LazyCompoundVal>(), CastTy, - OriginalTy); - case nonloc::LocAsIntegerKind: - return evalCastSubKind(V.castAs<nonloc::LocAsInteger>(), CastTy, - OriginalTy); - case nonloc::SymbolValKind: - return evalCastSubKind(V.castAs<nonloc::SymbolVal>(), CastTy, OriginalTy); - case nonloc::PointerToMemberKind: - return evalCastSubKind(V.castAs<nonloc::PointerToMember>(), CastTy, - OriginalTy); + // Pointer to whatever else. + return UnknownVal(); } + SVal VisitGotoLabel(loc::GotoLabel V) { + // Pointer to bool. + if (CastTy->isBooleanType()) + // Labels are always true. + return VB.makeTruthVal(true, CastTy); + + // Pointer to integer. + if (CastTy->isIntegralOrEnumerationType()) { + const unsigned BitWidth = Context.getIntWidth(CastTy); + return VB.makeLocAsInteger(V, BitWidth); + } - llvm_unreachable("Unknown SVal kind"); -} + const bool IsUnknownOriginalType = OriginalTy.isNull(); + if (!IsUnknownOriginalType) { + // Array to pointer. + if (isa<ArrayType>(OriginalTy)) + if (CastTy->isPointerType() || CastTy->isReferenceType()) + return UnknownVal(); + } -SVal SValBuilder::evalCastSubKind(loc::ConcreteInt V, QualType CastTy, - QualType OriginalTy) { - // Pointer to bool. - if (CastTy->isBooleanType()) - return makeTruthVal(V.getValue().getBoolValue(), CastTy); + // Pointer to any pointer. + if (Loc::isLocType(CastTy)) + return V; - // Pointer to integer. - if (CastTy->isIntegralOrEnumerationType()) { - llvm::APSInt Value = V.getValue(); - BasicVals.getAPSIntType(CastTy).apply(Value); - return makeIntVal(Value); + // Pointer to whatever else. + return UnknownVal(); } + SVal VisitMemRegionVal(loc::MemRegionVal V) { + // Pointer to bool. + if (CastTy->isBooleanType()) { + const MemRegion *R = V.getRegion(); + if (const FunctionCodeRegion *FTR = dyn_cast<FunctionCodeRegion>(R)) + if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(FTR->getDecl())) + if (FD->isWeak()) + // FIXME: Currently we are using an extent symbol here, + // because there are no generic region address metadata + // symbols to use, only content metadata. + return nonloc::SymbolVal( + VB.getSymbolManager().getExtentSymbol(FTR)); + + if (const SymbolicRegion *SymR = R->getSymbolicBase()) { + SymbolRef Sym = SymR->getSymbol(); + QualType Ty = Sym->getType(); + // This change is needed for architectures with varying + // pointer widths. See the amdgcn opencl reproducer with + // this change as an example: solver-sym-simplification-ptr-bool.cl + if (!Ty->isReferenceType()) + return VB.makeNonLoc( + Sym, BO_NE, VB.getBasicValueFactory().getZeroWithTypeSize(Ty), + CastTy); + } + // Non-symbolic memory regions are always true. + return VB.makeTruthVal(true, CastTy); + } - // Pointer to any pointer. - if (Loc::isLocType(CastTy)) - return V; + const bool IsUnknownOriginalType = OriginalTy.isNull(); + // Try to cast to array + const auto *ArrayTy = + IsUnknownOriginalType + ? nullptr + : dyn_cast<ArrayType>(OriginalTy.getCanonicalType()); + + // Pointer to integer. + if (CastTy->isIntegralOrEnumerationType()) { + SVal Val = V; + // Array to integer. + if (ArrayTy) { + // We will always decay to a pointer. + QualType ElemTy = ArrayTy->getElementType(); + Val = VB.getStateManager().ArrayToPointer(V, ElemTy); + // FIXME: Keep these here for now in case we decide soon that we + // need the original decayed type. + // QualType elemTy = cast<ArrayType>(originalTy)->getElementType(); + // QualType pointerTy = C.getPointerType(elemTy); + } + const unsigned BitWidth = Context.getIntWidth(CastTy); + return VB.makeLocAsInteger(Val.castAs<Loc>(), BitWidth); + } - // Pointer to whatever else. - return UnknownVal(); -} + // Pointer to pointer. + if (Loc::isLocType(CastTy)) { -SVal SValBuilder::evalCastSubKind(loc::GotoLabel V, QualType CastTy, - QualType OriginalTy) { - // Pointer to bool. - if (CastTy->isBooleanType()) - // Labels are always true. - return makeTruthVal(true, CastTy); + if (IsUnknownOriginalType) { + // When retrieving symbolic pointer and expecting a non-void pointer, + // wrap them into element regions of the expected type if necessary. + // It is necessary to make sure that the retrieved value makes sense, + // because there's no other cast in the AST that would tell us to cast + // it to the correct pointer type. We might need to do that for non-void + // pointers as well. + // FIXME: We really need a single good function to perform casts for us + // correctly every time we need it. + const MemRegion *R = V.getRegion(); + if (CastTy->isPointerType() && !CastTy->isVoidPointerType()) { + if (const auto *SR = dyn_cast<SymbolicRegion>(R)) { + QualType SRTy = SR->getSymbol()->getType(); + + auto HasSameUnqualifiedPointeeType = [](QualType ty1, + QualType ty2) { + return ty1->getPointeeType().getCanonicalType().getTypePtr() == + ty2->getPointeeType().getCanonicalType().getTypePtr(); + }; + if (!HasSameUnqualifiedPointeeType(SRTy, CastTy)) { + if (auto OptMemRegV = VB.getCastedMemRegionVal(SR, CastTy)) + return *OptMemRegV; + } + } + } + // Next fixes pointer dereference using type different from its initial + // one. See PR37503 and PR49007 for details. + if (const auto *ER = dyn_cast<ElementRegion>(R)) { + if (auto OptMemRegV = VB.getCastedMemRegionVal(ER, CastTy)) + return *OptMemRegV; + } - // Pointer to integer. - if (CastTy->isIntegralOrEnumerationType()) { - const unsigned BitWidth = Context.getIntWidth(CastTy); - return makeLocAsInteger(V, BitWidth); - } + return V; + } - const bool IsUnknownOriginalType = OriginalTy.isNull(); - if (!IsUnknownOriginalType) { - // Array to pointer. - if (isa<ArrayType>(OriginalTy)) - if (CastTy->isPointerType() || CastTy->isReferenceType()) - return UnknownVal(); - } + if (OriginalTy->isIntegralOrEnumerationType() || + OriginalTy->isBlockPointerType() || + OriginalTy->isFunctionPointerType()) + return V; - // Pointer to any pointer. - if (Loc::isLocType(CastTy)) - return V; + // Array to pointer. + if (ArrayTy) { + // Are we casting from an array to a pointer? If so just pass on + // the decayed value. + if (CastTy->isPointerType() || CastTy->isReferenceType()) { + // We will always decay to a pointer. + QualType ElemTy = ArrayTy->getElementType(); + return VB.getStateManager().ArrayToPointer(V, ElemTy); + } + // Are we casting from an array to an integer? If so, cast the decayed + // pointer value to an integer. + assert(CastTy->isIntegralOrEnumerationType()); + } - // Pointer to whatever else. - return UnknownVal(); -} + // Other pointer to pointer. + assert(Loc::isLocType(OriginalTy) || OriginalTy->isFunctionType() || + CastTy->isReferenceType()); -static bool hasSameUnqualifiedPointeeType(QualType ty1, QualType ty2) { - return ty1->getPointeeType().getCanonicalType().getTypePtr() == - ty2->getPointeeType().getCanonicalType().getTypePtr(); -} - -SVal SValBuilder::evalCastSubKind(loc::MemRegionVal V, QualType CastTy, - QualType OriginalTy) { - // Pointer to bool. - if (CastTy->isBooleanType()) { - const MemRegion *R = V.getRegion(); - if (const FunctionCodeRegion *FTR = dyn_cast<FunctionCodeRegion>(R)) - if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(FTR->getDecl())) - if (FD->isWeak()) - // FIXME: Currently we are using an extent symbol here, - // because there are no generic region address metadata - // symbols to use, only content metadata. - return nonloc::SymbolVal(SymMgr.getExtentSymbol(FTR)); - - if (const SymbolicRegion *SymR = R->getSymbolicBase()) { - SymbolRef Sym = SymR->getSymbol(); - QualType Ty = Sym->getType(); - // This change is needed for architectures with varying - // pointer widths. See the amdgcn opencl reproducer with - // this change as an example: solver-sym-simplification-ptr-bool.cl - // FIXME: We could encounter a reference here, - // try returning a concrete 'true' since it might - // be easier on the solver. - // FIXME: Cleanup remainder of `getZeroWithPtrWidth ()` - // and `getIntWithPtrWidth()` functions to prevent future - // confusion - const llvm::APSInt &Zero = Ty->isReferenceType() - ? BasicVals.getZeroWithPtrWidth() - : BasicVals.getZeroWithTypeSize(Ty); - return makeNonLoc(Sym, BO_NE, Zero, CastTy); + // We get a symbolic function pointer for a dereference of a function + // pointer, but it is of function type. Example: + + // struct FPRec { + // void (*my_func)(int * x); + // }; + // + // int bar(int x); + // + // int f1_a(struct FPRec* foo) { + // int x; + // (*foo->my_func)(&x); + // return bar(x)+1; // no-warning + // } + + // Get the result of casting a region to a different type. + const MemRegion *R = V.getRegion(); + if (auto OptMemRegV = VB.getCastedMemRegionVal(R, CastTy)) + return *OptMemRegV; } - // Non-symbolic memory regions are always true. - return makeTruthVal(true, CastTy); + + // Pointer to whatever else. + // FIXME: There can be gross cases where one casts the result of a + // function (that returns a pointer) to some other value that happens to + // fit within that pointer value. We currently have no good way to model + // such operations. When this happens, the underlying operation is that + // the caller is reasoning about bits. Conceptually we are layering a + // "view" of a location on top of those bits. Perhaps we need to be more + // lazy about mutual possible views, even on an SVal? This may be + // necessary for bit-level reasoning as well. + return UnknownVal(); } + SVal VisitCompoundVal(nonloc::CompoundVal V) { + // Compound to whatever. + return UnknownVal(); + } + SVal VisitConcreteInt(nonloc::ConcreteInt V) { + auto CastedValue = [V, this]() { + llvm::APSInt Value = V.getValue(); + VB.getBasicValueFactory().getAPSIntType(CastTy).apply(Value); + return Value; + }; + + // Integer to bool. + if (CastTy->isBooleanType()) + return VB.makeTruthVal(V.getValue().getBoolValue(), CastTy); + + // Integer to pointer. + if (CastTy->isIntegralOrEnumerationType()) + return VB.makeIntVal(CastedValue()); - const bool IsUnknownOriginalType = OriginalTy.isNull(); - // Try to cast to array - const auto *ArrayTy = - IsUnknownOriginalType - ? nullptr - : dyn_cast<ArrayType>(OriginalTy.getCanonicalType()); - - // Pointer to integer. - if (CastTy->isIntegralOrEnumerationType()) { - SVal Val = V; - // Array to integer. - if (ArrayTy) { - // We will always decay to a pointer. - QualType ElemTy = ArrayTy->getElementType(); - Val = StateMgr.ArrayToPointer(V, ElemTy); - // FIXME: Keep these here for now in case we decide soon that we - // need the original decayed type. - // QualType elemTy = cast<ArrayType>(originalTy)->getElementType(); - // QualType pointerTy = C.getPointerType(elemTy); - } - const unsigned BitWidth = Context.getIntWidth(CastTy); - return makeLocAsInteger(Val.castAs<Loc>(), BitWidth); + // Integer to pointer. + if (Loc::isLocType(CastTy)) + return VB.makeIntLocVal(CastedValue()); + + // Pointer to whatever else. + return UnknownVal(); } + SVal VisitLazyCompoundVal(nonloc::LazyCompoundVal V) { + // LazyCompound to whatever. + return UnknownVal(); + } + SVal VisitLocAsInteger(nonloc::LocAsInteger V) { + Loc L = V.getLoc(); + + // Pointer as integer to bool. + if (CastTy->isBooleanType()) + // Pass to Loc function. + return Visit(L); + + const bool IsUnknownOriginalType = OriginalTy.isNull(); + // Pointer as integer to pointer. + if (!IsUnknownOriginalType && Loc::isLocType(CastTy) && + OriginalTy->isIntegralOrEnumerationType()) { + if (const MemRegion *R = L.getAsRegion()) + if (auto OptMemRegV = VB.getCastedMemRegionVal(R, CastTy)) + return *OptMemRegV; + return L; + } - // Pointer to pointer. - if (Loc::isLocType(CastTy)) { - - if (IsUnknownOriginalType) { - // When retrieving symbolic pointer and expecting a non-void pointer, - // wrap them into element regions of the expected type if necessary. - // It is necessary to make sure that the retrieved value makes sense, - // because there's no other cast in the AST that would tell us to cast - // it to the correct pointer type. We might need to do that for non-void - // pointers as well. - // FIXME: We really need a single good function to perform casts for us - // correctly every time we need it. - const MemRegion *R = V.getRegion(); - if (CastTy->isPointerType() && !CastTy->isVoidPointerType()) { - if (const auto *SR = dyn_cast<SymbolicRegion>(R)) { - QualType SRTy = SR->getSymbol()->getType(); - if (!hasSameUnqualifiedPointeeType(SRTy, CastTy)) { - if (auto OptMemRegV = getCastedMemRegionVal(SR, CastTy)) - return *OptMemRegV; - } - } - } - // Next fixes pointer dereference using type different from its initial - // one. See PR37503 and PR49007 for details. - if (const auto *ER = dyn_cast<ElementRegion>(R)) { - if (auto OptMemRegV = getCastedMemRegionVal(ER, CastTy)) + // Pointer as integer with region to integer/pointer. + const MemRegion *R = L.getAsRegion(); + if (!IsUnknownOriginalType && R) { + if (CastTy->isIntegralOrEnumerationType()) + return VisitMemRegionVal(loc::MemRegionVal(R)); + + if (Loc::isLocType(CastTy)) { + assert(Loc::isLocType(OriginalTy) || OriginalTy->isFunctionType() || + CastTy->isReferenceType()); + // Delegate to store manager to get the result of casting a region to a + // different type. If the MemRegion* returned is NULL, this expression + // Evaluates to UnknownVal. + if (auto OptMemRegV = VB.getCastedMemRegionVal(R, CastTy)) return *OptMemRegV; } + } else { + if (Loc::isLocType(CastTy)) { + if (IsUnknownOriginalType) + return VisitMemRegionVal(loc::MemRegionVal(R)); + return L; + } - return V; - } + SymbolRef SE = nullptr; + if (R) { + if (const SymbolicRegion *SR = + dyn_cast<SymbolicRegion>(R->StripCasts())) { + SE = SR->getSymbol(); + } + } - if (OriginalTy->isIntegralOrEnumerationType() || - OriginalTy->isBlockPointerType() || OriginalTy->isFunctionPointerType()) - return V; + if (!CastTy->isFloatingType() || !SE || SE->getType()->isFloatingType()) { + // FIXME: Correctly support promotions/truncations. + const unsigned CastSize = Context.getIntWidth(CastTy); + if (CastSize == V.getNumBits()) + return V; - // Array to pointer. - if (ArrayTy) { - // Are we casting from an array to a pointer? If so just pass on - // the decayed value. - if (CastTy->isPointerType() || CastTy->isReferenceType()) { - // We will always decay to a pointer. - QualType ElemTy = ArrayTy->getElementType(); - return StateMgr.ArrayToPointer(V, ElemTy); + return VB.makeLocAsInteger(L, CastSize); } - // Are we casting from an array to an integer? If so, cast the decayed - // pointer value to an integer. - assert(CastTy->isIntegralOrEnumerationType()); } - // Other pointer to pointer. - assert(Loc::isLocType(OriginalTy) || OriginalTy->isFunctionType() || - CastTy->isReferenceType()); - - // We get a symbolic function pointer for a dereference of a function - // pointer, but it is of function type. Example: - - // struct FPRec { - // void (*my_func)(int * x); - // }; - // - // int bar(int x); - // - // int f1_a(struct FPRec* foo) { - // int x; - // (*foo->my_func)(&x); - // return bar(x)+1; // no-warning - // } - - // Get the result of casting a region to a different type. - const MemRegion *R = V.getRegion(); - if (auto OptMemRegV = getCastedMemRegionVal(R, CastTy)) - return *OptMemRegV; + // Pointer as integer to whatever else. + return UnknownVal(); } + SVal VisitSymbolVal(nonloc::SymbolVal V) { + SymbolRef SE = V.getSymbol(); + + const bool IsUnknownOriginalType = OriginalTy.isNull(); + // Symbol to bool. + if (!IsUnknownOriginalType && CastTy->isBooleanType()) { + // Non-float to bool. + if (Loc::isLocType(OriginalTy) || + OriginalTy->isIntegralOrEnumerationType() || + OriginalTy->isMemberPointerType()) { + BasicValueFactory &BVF = VB.getBasicValueFactory(); + return VB.makeNonLoc(SE, BO_NE, BVF.getValue(0, SE->getType()), CastTy); + } + } else { + // Symbol to integer, float. + QualType T = Context.getCanonicalType(SE->getType()); + + // Produce SymbolCast if CastTy and T are different integers. + // NOTE: In the end the type of SymbolCast shall be equal to CastTy. + if (T->isIntegralOrUnscopedEnumerationType() && + CastTy->isIntegralOrUnscopedEnumerationType()) { + AnalyzerOptions &Opts = VB.getStateManager() + .getOwningEngine() + .getAnalysisManager() + .getAnalyzerOptions(); + // If appropriate option is disabled, ignore the cast. + // NOTE: ShouldSupportSymbolicIntegerCasts is `false` by default. + if (!Opts.ShouldSupportSymbolicIntegerCasts) + return V; + return simplifySymbolCast(V, CastTy); + } + if (!Loc::isLocType(CastTy)) + if (!IsUnknownOriginalType || !CastTy->isFloatingType() || + T->isFloatingType()) + return VB.makeNonLoc(SE, T, CastTy); + } - // Pointer to whatever else. - // FIXME: There can be gross cases where one casts the result of a - // function (that returns a pointer) to some other value that happens to - // fit within that pointer value. We currently have no good way to model - // such operations. When this happens, the underlying operation is that - // the caller is reasoning about bits. Conceptually we are layering a - // "view" of a location on top of those bits. Perhaps we need to be more - // lazy about mutual possible views, even on an SVal? This may be - // necessary for bit-level reasoning as well. - return UnknownVal(); -} - -SVal SValBuilder::evalCastSubKind(nonloc::CompoundVal V, QualType CastTy, - QualType OriginalTy) { - // Compound to whatever. - return UnknownVal(); -} - -SVal SValBuilder::evalCastSubKind(nonloc::ConcreteInt V, QualType CastTy, - QualType OriginalTy) { - auto CastedValue = [V, CastTy, this]() { - llvm::APSInt Value = V.getValue(); - BasicVals.getAPSIntType(CastTy).apply(Value); - return Value; - }; - - // Integer to bool. - if (CastTy->isBooleanType()) - return makeTruthVal(V.getValue().getBoolValue(), CastTy); - - // Integer to pointer. - if (CastTy->isIntegralOrEnumerationType()) - return makeIntVal(CastedValue()); - - // Integer to pointer. - if (Loc::isLocType(CastTy)) - return makeIntLocVal(CastedValue()); + // FIXME: We should be able to cast NonLoc -> Loc + // (when Loc::isLocType(CastTy) is true) + // But it's hard to do as SymbolicRegions can't refer to SymbolCasts holding + // generic SymExprs. Check the commit message for the details. - // Pointer to whatever else. - return UnknownVal(); -} + // Symbol to pointer and whatever else. + return UnknownVal(); + } + SVal VisitPointerToMember(nonloc::PointerToMember V) { + // Member pointer to whatever. + return V; + } -SVal SValBuilder::evalCastSubKind(nonloc::LazyCompoundVal V, QualType CastTy, - QualType OriginalTy) { - // Compound to whatever. - return UnknownVal(); -} + /// Reduce cast expression by removing redundant intermediate casts. + /// E.g. + /// - (char)(short)(int x) -> (char)(int x) + /// - (int)(int x) -> int x + /// + /// \param V -- SymbolVal, which pressumably contains SymbolCast or any symbol + /// that is applicable for cast operation. + /// \param CastTy -- QualType, which `V` shall be cast to. + /// \return SVal with simplified cast expression. + /// \note: Currently only support integral casts. + nonloc::SymbolVal simplifySymbolCast(nonloc::SymbolVal V, QualType CastTy) { + // We use seven conditions to recognize a simplification case. + // For the clarity let `CastTy` be `C`, SE->getType() - `T`, root type - + // `R`, prefix `u` for unsigned, `s` for signed, no prefix - any sign: E.g. + // (char)(short)(uint x) + // ( sC )( sT )( uR x) + // + // C === R (the same type) + // (char)(char x) -> (char x) + // (long)(long x) -> (long x) + // Note: Comparisons operators below are for bit width. + // C == T + // (short)(short)(int x) -> (short)(int x) + // (int)(long)(char x) -> (int)(char x) (sizeof(long) == sizeof(int)) + // (long)(ullong)(char x) -> (long)(char x) (sizeof(long) == + // sizeof(ullong)) + // C < T + // (short)(int)(char x) -> (short)(char x) + // (char)(int)(short x) -> (char)(short x) + // (short)(int)(short x) -> (short x) + // C > T > uR + // (int)(short)(uchar x) -> (int)(uchar x) + // (uint)(short)(uchar x) -> (uint)(uchar x) + // (int)(ushort)(uchar x) -> (int)(uchar x) + // C > sT > sR + // (int)(short)(char x) -> (int)(char x) + // (uint)(short)(char x) -> (uint)(char x) + // C > sT == sR + // (int)(char)(char x) -> (int)(char x) + // (uint)(short)(short x) -> (uint)(short x) + // C > uT == uR + // (int)(uchar)(uchar x) -> (int)(uchar x) + // (uint)(ushort)(ushort x) -> (uint)(ushort x) + // (llong)(ulong)(uint x) -> (llong)(uint x) (sizeof(ulong) == + // sizeof(uint)) + + SymbolRef SE = V.getSymbol(); + QualType T = Context.getCanonicalType(SE->getType()); -SVal SValBuilder::evalCastSubKind(nonloc::LocAsInteger V, QualType CastTy, - QualType OriginalTy) { - Loc L = V.getLoc(); + if (T == CastTy) + return V; - // Pointer as integer to bool. - if (CastTy->isBooleanType()) - // Pass to Loc function. - return evalCastKind(L, CastTy, OriginalTy); + if (!isa<SymbolCast>(SE)) + return VB.makeNonLoc(SE, T, CastTy); - const bool IsUnknownOriginalType = OriginalTy.isNull(); - // Pointer as integer to pointer. - if (!IsUnknownOriginalType && Loc::isLocType(CastTy) && - OriginalTy->isIntegralOrEnumerationType()) { - if (const MemRegion *R = L.getAsRegion()) - if (auto OptMemRegV = getCastedMemRegionVal(R, CastTy)) - return *OptMemRegV; - return L; - } + SymbolRef RootSym = cast<SymbolCast>(SE)->getOperand(); + QualType RT = RootSym->getType().getCanonicalType(); - // Pointer as integer with region to integer/pointer. - const MemRegion *R = L.getAsRegion(); - if (!IsUnknownOriginalType && R) { - if (CastTy->isIntegralOrEnumerationType()) - return evalCastSubKind(loc::MemRegionVal(R), CastTy, OriginalTy); + // FIXME support simplification from non-integers. + if (!RT->isIntegralOrEnumerationType()) + return VB.makeNonLoc(SE, T, CastTy); - if (Loc::isLocType(CastTy)) { - assert(Loc::isLocType(OriginalTy) || OriginalTy->isFunctionType() || - CastTy->isReferenceType()); - // Delegate to store manager to get the result of casting a region to a - // different type. If the MemRegion* returned is NULL, this expression - // Evaluates to UnknownVal. - if (auto OptMemRegV = getCastedMemRegionVal(R, CastTy)) - return *OptMemRegV; - } - } else { - if (Loc::isLocType(CastTy)) { - if (IsUnknownOriginalType) - return evalCastSubKind(loc::MemRegionVal(R), CastTy, OriginalTy); - return L; - } - - SymbolRef SE = nullptr; - if (R) { - if (const SymbolicRegion *SR = - dyn_cast<SymbolicRegion>(R->StripCasts())) { - SE = SR->getSymbol(); - } - } + BasicValueFactory &BVF = VB.getBasicValueFactory(); + APSIntType CTy = BVF.getAPSIntType(CastTy); + APSIntType TTy = BVF.getAPSIntType(T); - if (!CastTy->isFloatingType() || !SE || SE->getType()->isFloatingType()) { - // FIXME: Correctly support promotions/truncations. - const unsigned CastSize = Context.getIntWidth(CastTy); - if (CastSize == V.getNumBits()) - return V; + const auto WC = CTy.getBitWidth(); + const auto WT = TTy.getBitWidth(); - return makeLocAsInteger(L, CastSize); + if (WC <= WT) { + const bool isSameType = (RT == CastTy); + if (isSameType) + return nonloc::SymbolVal(RootSym); + return VB.makeNonLoc(RootSym, RT, CastTy); } - } - // Pointer as integer to whatever else. - return UnknownVal(); -} + APSIntType RTy = BVF.getAPSIntType(RT); + const auto WR = RTy.getBitWidth(); + const bool UT = TTy.isUnsigned(); + const bool UR = RTy.isUnsigned(); -SVal SValBuilder::evalCastSubKind(nonloc::SymbolVal V, QualType CastTy, - QualType OriginalTy) { - SymbolRef SE = V.getSymbol(); + if (((WT > WR) && (UR || !UT)) || ((WT == WR) && (UT == UR))) + return VB.makeNonLoc(RootSym, RT, CastTy); - const bool IsUnknownOriginalType = OriginalTy.isNull(); - // Symbol to bool. - if (!IsUnknownOriginalType && CastTy->isBooleanType()) { - // Non-float to bool. - if (Loc::isLocType(OriginalTy) || - OriginalTy->isIntegralOrEnumerationType() || - OriginalTy->isMemberPointerType()) { - BasicValueFactory &BVF = getBasicValueFactory(); - return makeNonLoc(SE, BO_NE, BVF.getValue(0, SE->getType()), CastTy); - } - } else { - // Symbol to integer, float. - QualType T = Context.getCanonicalType(SE->getType()); - // If types are the same or both are integers, ignore the cast. - // FIXME: Remove this hack when we support symbolic truncation/extension. - // HACK: If both castTy and T are integers, ignore the cast. This is - // not a permanent solution. Eventually we want to precisely handle - // extension/truncation of symbolic integers. This prevents us from losing - // precision when we assign 'x = y' and 'y' is symbolic and x and y are - // different integer types. - if (haveSameType(T, CastTy)) - return V; - if (!Loc::isLocType(CastTy)) - if (!IsUnknownOriginalType || !CastTy->isFloatingType() || - T->isFloatingType()) - return makeNonLoc(SE, T, CastTy); + return VB.makeNonLoc(SE, T, CastTy); } +}; +} // end anonymous namespace - // Symbol to pointer and whatever else. - return UnknownVal(); -} - -SVal SValBuilder::evalCastSubKind(nonloc::PointerToMember V, QualType CastTy, - QualType OriginalTy) { - // Member pointer to whatever. - return V; +/// Cast a given SVal to another SVal using given QualType's. +/// \param V -- SVal that should be casted. +/// \param CastTy -- QualType that V should be casted according to. +/// \param OriginalTy -- QualType which is associated to V. It provides +/// additional information about what type the cast performs from. +/// \returns the most appropriate casted SVal. +/// Note: Many cases don't use an exact OriginalTy. It can be extracted +/// from SVal or the cast can performs unconditionaly. Always pass OriginalTy! +/// It can be crucial in certain cases and generates different results. +/// FIXME: If `OriginalTy.isNull()` is true, then cast performs based on CastTy +/// only. This behavior is uncertain and should be improved. +SVal SValBuilder::evalCast(SVal V, QualType CastTy, QualType OriginalTy) { + EvalCastVisitor TRV{*this, CastTy, OriginalTy}; + return TRV.Visit(V); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SVals.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SVals.cpp index 117546e43b1a..0e1351215bb4 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SVals.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SVals.cpp @@ -25,12 +25,12 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/SValVisitor.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" -#include "llvm/ADT/Optional.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" #include <cassert> +#include <optional> using namespace clang; using namespace ento; @@ -43,27 +43,8 @@ using namespace ento; // Utility methods. //===----------------------------------------------------------------------===// -bool SVal::hasConjuredSymbol() const { - if (Optional<nonloc::SymbolVal> SV = getAs<nonloc::SymbolVal>()) { - SymbolRef sym = SV->getSymbol(); - if (isa<SymbolConjured>(sym)) - return true; - } - - if (Optional<loc::MemRegionVal> RV = getAs<loc::MemRegionVal>()) { - const MemRegion *R = RV->getRegion(); - if (const auto *SR = dyn_cast<SymbolicRegion>(R)) { - SymbolRef sym = SR->getSymbol(); - if (isa<SymbolConjured>(sym)) - return true; - } - } - - return false; -} - const FunctionDecl *SVal::getAsFunctionDecl() const { - if (Optional<loc::MemRegionVal> X = getAs<loc::MemRegionVal>()) { + if (std::optional<loc::MemRegionVal> X = getAs<loc::MemRegionVal>()) { const MemRegion* R = X->getRegion(); if (const FunctionCodeRegion *CTR = R->getAs<FunctionCodeRegion>()) if (const auto *FD = dyn_cast<FunctionDecl>(CTR->getDecl())) @@ -97,7 +78,7 @@ SymbolRef SVal::getAsLocSymbol(bool IncludeBaseRegions) const { /// Get the symbol in the SVal or its base region. SymbolRef SVal::getLocSymbolInBase() const { - Optional<loc::MemRegionVal> X = getAs<loc::MemRegionVal>(); + std::optional<loc::MemRegionVal> X = getAs<loc::MemRegionVal>(); if (!X) return nullptr; @@ -122,17 +103,25 @@ SymbolRef SVal::getLocSymbolInBase() const { /// should continue to the base regions if the region is not symbolic. SymbolRef SVal::getAsSymbol(bool IncludeBaseRegions) const { // FIXME: should we consider SymbolRef wrapped in CodeTextRegion? - if (Optional<nonloc::SymbolVal> X = getAs<nonloc::SymbolVal>()) + if (std::optional<nonloc::SymbolVal> X = getAs<nonloc::SymbolVal>()) return X->getSymbol(); return getAsLocSymbol(IncludeBaseRegions); } +const llvm::APSInt *SVal::getAsInteger() const { + if (auto CI = getAs<nonloc::ConcreteInt>()) + return &CI->getValue(); + if (auto CI = getAs<loc::ConcreteInt>()) + return &CI->getValue(); + return nullptr; +} + const MemRegion *SVal::getAsRegion() const { - if (Optional<loc::MemRegionVal> X = getAs<loc::MemRegionVal>()) + if (std::optional<loc::MemRegionVal> X = getAs<loc::MemRegionVal>()) return X->getRegion(); - if (Optional<nonloc::LocAsInteger> X = getAs<nonloc::LocAsInteger>()) + if (std::optional<nonloc::LocAsInteger> X = getAs<nonloc::LocAsInteger>()) return X->getLoc().getAsRegion(); return nullptr; @@ -147,23 +136,19 @@ private: public: TypeRetrievingVisitor(const ASTContext &Context) : Context(Context) {} - QualType VisitLocMemRegionVal(loc::MemRegionVal MRV) { + QualType VisitMemRegionVal(loc::MemRegionVal MRV) { return Visit(MRV.getRegion()); } - QualType VisitLocGotoLabel(loc::GotoLabel GL) { + QualType VisitGotoLabel(loc::GotoLabel GL) { return QualType{Context.VoidPtrTy}; } template <class ConcreteInt> QualType VisitConcreteInt(ConcreteInt CI) { const llvm::APSInt &Value = CI.getValue(); + if (1 == Value.getBitWidth()) + return Context.BoolTy; return Context.getIntTypeForBitwidth(Value.getBitWidth(), Value.isSigned()); } - QualType VisitLocConcreteInt(loc::ConcreteInt CI) { - return VisitConcreteInt(CI); - } - QualType VisitNonLocConcreteInt(nonloc::ConcreteInt CI) { - return VisitConcreteInt(CI); - } - QualType VisitNonLocLocAsInteger(nonloc::LocAsInteger LI) { + QualType VisitLocAsInteger(nonloc::LocAsInteger LI) { QualType NestedType = Visit(LI.getLoc()); if (NestedType.isNull()) return NestedType; @@ -171,18 +156,21 @@ public: return Context.getIntTypeForBitwidth(LI.getNumBits(), NestedType->isSignedIntegerType()); } - QualType VisitNonLocCompoundVal(nonloc::CompoundVal CV) { + QualType VisitCompoundVal(nonloc::CompoundVal CV) { return CV.getValue()->getType(); } - QualType VisitNonLocLazyCompoundVal(nonloc::LazyCompoundVal LCV) { + QualType VisitLazyCompoundVal(nonloc::LazyCompoundVal LCV) { return LCV.getRegion()->getValueType(); } - QualType VisitNonLocSymbolVal(nonloc::SymbolVal SV) { + QualType VisitSymbolVal(nonloc::SymbolVal SV) { return Visit(SV.getSymbol()); } QualType VisitSymbolicRegion(const SymbolicRegion *SR) { return Visit(SR->getSymbol()); } + QualType VisitAllocaRegion(const AllocaRegion *) { + return QualType{Context.VoidPtrTy}; + } QualType VisitTypedRegion(const TypedRegion *TR) { return TR->getLocationType(); } @@ -196,8 +184,7 @@ QualType SVal::getType(const ASTContext &Context) const { } const MemRegion *loc::MemRegionVal::stripCasts(bool StripBaseCasts) const { - const MemRegion *R = getRegion(); - return R ? R->StripCasts(StripBaseCasts) : nullptr; + return getRegion()->StripCasts(StripBaseCasts); } const void *nonloc::LazyCompoundVal::getStore() const { @@ -261,9 +248,9 @@ bool SVal::isConstant() const { } bool SVal::isConstant(int I) const { - if (Optional<loc::ConcreteInt> LV = getAs<loc::ConcreteInt>()) + if (std::optional<loc::ConcreteInt> LV = getAs<loc::ConcreteInt>()) return LV->getValue() == I; - if (Optional<nonloc::ConcreteInt> NV = getAs<nonloc::ConcreteInt>()) + if (std::optional<nonloc::ConcreteInt> NV = getAs<nonloc::ConcreteInt>()) return NV->getValue() == I; return false; } @@ -273,49 +260,6 @@ bool SVal::isZeroConstant() const { } //===----------------------------------------------------------------------===// -// Transfer function dispatch for Non-Locs. -//===----------------------------------------------------------------------===// - -SVal nonloc::ConcreteInt::evalBinOp(SValBuilder &svalBuilder, - BinaryOperator::Opcode Op, - const nonloc::ConcreteInt& R) const { - const llvm::APSInt* X = - svalBuilder.getBasicValueFactory().evalAPSInt(Op, getValue(), R.getValue()); - - if (X) - return nonloc::ConcreteInt(*X); - else - return UndefinedVal(); -} - -nonloc::ConcreteInt -nonloc::ConcreteInt::evalComplement(SValBuilder &svalBuilder) const { - return svalBuilder.makeIntVal(~getValue()); -} - -nonloc::ConcreteInt -nonloc::ConcreteInt::evalMinus(SValBuilder &svalBuilder) const { - return svalBuilder.makeIntVal(-getValue()); -} - -//===----------------------------------------------------------------------===// -// Transfer function dispatch for Locs. -//===----------------------------------------------------------------------===// - -SVal loc::ConcreteInt::evalBinOp(BasicValueFactory& BasicVals, - BinaryOperator::Opcode Op, - const loc::ConcreteInt& R) const { - assert(BinaryOperator::isComparisonOp(Op) || Op == BO_Sub); - - const llvm::APSInt *X = BasicVals.evalAPSInt(Op, getValue(), R.getValue()); - - if (X) - return nonloc::ConcreteInt(*X); - else - return UndefinedVal(); -} - -//===----------------------------------------------------------------------===// // Pretty-Printing. //===----------------------------------------------------------------------===// @@ -331,30 +275,33 @@ void SVal::printJson(raw_ostream &Out, bool AddQuotes) const { } void SVal::dumpToStream(raw_ostream &os) const { - switch (getBaseKind()) { - case UnknownValKind: - os << "Unknown"; - break; - case NonLocKind: - castAs<NonLoc>().dumpToStream(os); - break; - case LocKind: - castAs<Loc>().dumpToStream(os); - break; - case UndefinedValKind: - os << "Undefined"; - break; + if (isUndef()) { + os << "Undefined"; + return; } + if (isUnknown()) { + os << "Unknown"; + return; + } + if (NonLoc::classof(*this)) { + castAs<NonLoc>().dumpToStream(os); + return; + } + if (Loc::classof(*this)) { + castAs<Loc>().dumpToStream(os); + return; + } + llvm_unreachable("Unhandled SVal kind!"); } void NonLoc::dumpToStream(raw_ostream &os) const { - switch (getSubKind()) { - case nonloc::ConcreteIntKind: { - const auto &Value = castAs<nonloc::ConcreteInt>().getValue(); - os << Value << ' ' << (Value.isSigned() ? 'S' : 'U') - << Value.getBitWidth() << 'b'; - break; - } + switch (getKind()) { + case nonloc::ConcreteIntKind: { + const auto &Value = castAs<nonloc::ConcreteInt>().getValue(); + os << Value << ' ' << (Value.isSigned() ? 'S' : 'U') << Value.getBitWidth() + << 'b'; + break; + } case nonloc::SymbolValKind: os << castAs<nonloc::SymbolVal>().getSymbol(); break; @@ -401,7 +348,7 @@ void NonLoc::dumpToStream(raw_ostream &os) const { else os << ", "; - os << (*I).getType().getAsString(); + os << I->getType(); } os << '}'; @@ -410,21 +357,21 @@ void NonLoc::dumpToStream(raw_ostream &os) const { default: assert(false && "Pretty-printed not implemented for this NonLoc."); break; - } + } } void Loc::dumpToStream(raw_ostream &os) const { - switch (getSubKind()) { - case loc::ConcreteIntKind: - os << castAs<loc::ConcreteInt>().getValue().getZExtValue() << " (Loc)"; - break; - case loc::GotoLabelKind: - os << "&&" << castAs<loc::GotoLabel>().getLabel()->getName(); - break; - case loc::MemRegionValKind: - os << '&' << castAs<loc::MemRegionVal>().getRegion()->getString(); - break; - default: - llvm_unreachable("Pretty-printing not implemented for this Loc."); + switch (getKind()) { + case loc::ConcreteIntKind: + os << castAs<loc::ConcreteInt>().getValue().getZExtValue() << " (Loc)"; + break; + case loc::GotoLabelKind: + os << "&&" << castAs<loc::GotoLabel>().getLabel()->getName(); + break; + case loc::MemRegionValKind: + os << '&' << castAs<loc::MemRegionVal>().getRegion()->getString(); + break; + default: + llvm_unreachable("Pretty-printing not implemented for this Loc."); } } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp index e1319a4c2e41..fab520098f13 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp @@ -13,6 +13,8 @@ #include "clang/Analysis/MacroExpansionContext.h" #include "clang/Analysis/PathDiagnostic.h" #include "clang/Basic/FileManager.h" +#include "clang/Basic/Sarif.h" +#include "clang/Basic/SourceManager.h" #include "clang/Basic/Version.h" #include "clang/Lex/Preprocessor.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" @@ -30,10 +32,12 @@ namespace { class SarifDiagnostics : public PathDiagnosticConsumer { std::string OutputFile; const LangOptions &LO; + SarifDocumentWriter SarifWriter; public: - SarifDiagnostics(const std::string &Output, const LangOptions &LO) - : OutputFile(Output), LO(LO) {} + SarifDiagnostics(const std::string &Output, const LangOptions &LO, + const SourceManager &SM) + : OutputFile(Output), LO(LO), SarifWriter(SM) {} ~SarifDiagnostics() override = default; void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, @@ -56,250 +60,12 @@ void ento::createSarifDiagnosticConsumer( if (Output.empty()) return; - C.push_back(new SarifDiagnostics(Output, PP.getLangOpts())); + C.push_back( + new SarifDiagnostics(Output, PP.getLangOpts(), PP.getSourceManager())); createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, Output, PP, CTU, MacroExpansions); } -static StringRef getFileName(const FileEntry &FE) { - StringRef Filename = FE.tryGetRealPathName(); - if (Filename.empty()) - Filename = FE.getName(); - return Filename; -} - -static std::string percentEncodeURICharacter(char C) { - // RFC 3986 claims alpha, numeric, and this handful of - // characters are not reserved for the path component and - // should be written out directly. Otherwise, percent - // encode the character and write that out instead of the - // reserved character. - if (llvm::isAlnum(C) || - StringRef::npos != StringRef("-._~:@!$&'()*+,;=").find(C)) - return std::string(&C, 1); - return "%" + llvm::toHex(StringRef(&C, 1)); -} - -static std::string fileNameToURI(StringRef Filename) { - llvm::SmallString<32> Ret = StringRef("file://"); - - // Get the root name to see if it has a URI authority. - StringRef Root = sys::path::root_name(Filename); - if (Root.startswith("//")) { - // There is an authority, so add it to the URI. - Ret += Root.drop_front(2).str(); - } else if (!Root.empty()) { - // There is no authority, so end the component and add the root to the URI. - Ret += Twine("/" + Root).str(); - } - - auto Iter = sys::path::begin(Filename), End = sys::path::end(Filename); - assert(Iter != End && "Expected there to be a non-root path component."); - // Add the rest of the path components, encoding any reserved characters; - // we skip past the first path component, as it was handled it above. - std::for_each(++Iter, End, [&Ret](StringRef Component) { - // For reasons unknown to me, we may get a backslash with Windows native - // paths for the initial backslash following the drive component, which - // we need to ignore as a URI path part. - if (Component == "\\") - return; - - // Add the separator between the previous path part and the one being - // currently processed. - Ret += "/"; - - // URI encode the part. - for (char C : Component) { - Ret += percentEncodeURICharacter(C); - } - }); - - return std::string(Ret); -} - -static json::Object createArtifactLocation(const FileEntry &FE) { - return json::Object{{"uri", fileNameToURI(getFileName(FE))}}; -} - -static json::Object createArtifact(const FileEntry &FE) { - return json::Object{{"location", createArtifactLocation(FE)}, - {"roles", json::Array{"resultFile"}}, - {"length", FE.getSize()}, - {"mimeType", "text/plain"}}; -} - -static json::Object createArtifactLocation(const FileEntry &FE, - json::Array &Artifacts) { - std::string FileURI = fileNameToURI(getFileName(FE)); - - // See if the Artifacts array contains this URI already. If it does not, - // create a new artifact object to add to the array. - auto I = llvm::find_if(Artifacts, [&](const json::Value &File) { - if (const json::Object *Obj = File.getAsObject()) { - if (const json::Object *FileLoc = Obj->getObject("location")) { - Optional<StringRef> URI = FileLoc->getString("uri"); - return URI && URI->equals(FileURI); - } - } - return false; - }); - - // Calculate the index within the artifact array so it can be stored in - // the JSON object. - auto Index = static_cast<unsigned>(std::distance(Artifacts.begin(), I)); - if (I == Artifacts.end()) - Artifacts.push_back(createArtifact(FE)); - - return json::Object{{"uri", FileURI}, {"index", Index}}; -} - -static unsigned int adjustColumnPos(const SourceManager &SM, SourceLocation Loc, - unsigned int TokenLen = 0) { - assert(!Loc.isInvalid() && "invalid Loc when adjusting column position"); - - std::pair<FileID, unsigned> LocInfo = SM.getDecomposedExpansionLoc(Loc); - assert(LocInfo.second > SM.getExpansionColumnNumber(Loc) && - "position in file is before column number?"); - - Optional<MemoryBufferRef> Buf = SM.getBufferOrNone(LocInfo.first); - assert(Buf && "got an invalid buffer for the location's file"); - assert(Buf->getBufferSize() >= (LocInfo.second + TokenLen) && - "token extends past end of buffer?"); - - // Adjust the offset to be the start of the line, since we'll be counting - // Unicode characters from there until our column offset. - unsigned int Off = LocInfo.second - (SM.getExpansionColumnNumber(Loc) - 1); - unsigned int Ret = 1; - while (Off < (LocInfo.second + TokenLen)) { - Off += getNumBytesForUTF8(Buf->getBuffer()[Off]); - Ret++; - } - - return Ret; -} - -static json::Object createTextRegion(const LangOptions &LO, SourceRange R, - const SourceManager &SM) { - json::Object Region{ - {"startLine", SM.getExpansionLineNumber(R.getBegin())}, - {"startColumn", adjustColumnPos(SM, R.getBegin())}, - }; - if (R.getBegin() == R.getEnd()) { - Region["endColumn"] = adjustColumnPos(SM, R.getBegin()); - } else { - Region["endLine"] = SM.getExpansionLineNumber(R.getEnd()); - Region["endColumn"] = adjustColumnPos( - SM, R.getEnd(), - Lexer::MeasureTokenLength(R.getEnd(), SM, LO)); - } - return Region; -} - -static json::Object createPhysicalLocation(const LangOptions &LO, - SourceRange R, const FileEntry &FE, - const SourceManager &SMgr, - json::Array &Artifacts) { - return json::Object{ - {{"artifactLocation", createArtifactLocation(FE, Artifacts)}, - {"region", createTextRegion(LO, R, SMgr)}}}; -} - -enum class Importance { Important, Essential, Unimportant }; - -static StringRef importanceToStr(Importance I) { - switch (I) { - case Importance::Important: - return "important"; - case Importance::Essential: - return "essential"; - case Importance::Unimportant: - return "unimportant"; - } - llvm_unreachable("Fully covered switch is not so fully covered"); -} - -static json::Object createThreadFlowLocation(json::Object &&Location, - Importance I) { - return json::Object{{"location", std::move(Location)}, - {"importance", importanceToStr(I)}}; -} - -static json::Object createMessage(StringRef Text) { - return json::Object{{"text", Text.str()}}; -} - -static json::Object createLocation(json::Object &&PhysicalLocation, - StringRef Message = "") { - json::Object Ret{{"physicalLocation", std::move(PhysicalLocation)}}; - if (!Message.empty()) - Ret.insert({"message", createMessage(Message)}); - return Ret; -} - -static Importance calculateImportance(const PathDiagnosticPiece &Piece) { - switch (Piece.getKind()) { - case PathDiagnosticPiece::Call: - case PathDiagnosticPiece::Macro: - case PathDiagnosticPiece::Note: - case PathDiagnosticPiece::PopUp: - // FIXME: What should be reported here? - break; - case PathDiagnosticPiece::Event: - return Piece.getTagStr() == "ConditionBRVisitor" ? Importance::Important - : Importance::Essential; - case PathDiagnosticPiece::ControlFlow: - return Importance::Unimportant; - } - return Importance::Unimportant; -} - -static json::Object createThreadFlow(const LangOptions &LO, - const PathPieces &Pieces, - json::Array &Artifacts) { - const SourceManager &SMgr = Pieces.front()->getLocation().getManager(); - json::Array Locations; - for (const auto &Piece : Pieces) { - const PathDiagnosticLocation &P = Piece->getLocation(); - Locations.push_back(createThreadFlowLocation( - createLocation(createPhysicalLocation( - LO, P.asRange(), - *P.asLocation().getExpansionLoc().getFileEntry(), - SMgr, Artifacts), - Piece->getString()), - calculateImportance(*Piece))); - } - return json::Object{{"locations", std::move(Locations)}}; -} - -static json::Object createCodeFlow(const LangOptions &LO, - const PathPieces &Pieces, - json::Array &Artifacts) { - return json::Object{ - {"threadFlows", json::Array{createThreadFlow(LO, Pieces, Artifacts)}}}; -} - -static json::Object createResult(const LangOptions &LO, - const PathDiagnostic &Diag, - json::Array &Artifacts, - const StringMap<unsigned> &RuleMapping) { - const PathPieces &Path = Diag.path.flatten(false); - const SourceManager &SMgr = Path.front()->getLocation().getManager(); - - auto Iter = RuleMapping.find(Diag.getCheckerName()); - assert(Iter != RuleMapping.end() && "Rule ID is not in the array index map?"); - - return json::Object{ - {"message", createMessage(Diag.getVerboseDescription())}, - {"codeFlows", json::Array{createCodeFlow(LO, Path, Artifacts)}}, - {"locations", - json::Array{createLocation(createPhysicalLocation( - LO, Diag.getLocation().asRange(), - *Diag.getLocation().asLocation().getExpansionLoc().getFileEntry(), - SMgr, Artifacts))}}, - {"ruleIndex", Iter->getValue()}, - {"ruleId", Diag.getCheckerName()}}; -} - static StringRef getRuleDescription(StringRef CheckName) { return llvm::StringSwitch<StringRef>(CheckName) #define GET_CHECKERS @@ -322,61 +88,99 @@ static StringRef getRuleHelpURIStr(StringRef CheckName) { ; } -static json::Object createRule(const PathDiagnostic &Diag) { - StringRef CheckName = Diag.getCheckerName(); - json::Object Ret{ - {"fullDescription", createMessage(getRuleDescription(CheckName))}, - {"name", CheckName}, - {"id", CheckName}}; - - std::string RuleURI = std::string(getRuleHelpURIStr(CheckName)); - if (!RuleURI.empty()) - Ret["helpUri"] = RuleURI; - - return Ret; +static ThreadFlowImportance +calculateImportance(const PathDiagnosticPiece &Piece) { + switch (Piece.getKind()) { + case PathDiagnosticPiece::Call: + case PathDiagnosticPiece::Macro: + case PathDiagnosticPiece::Note: + case PathDiagnosticPiece::PopUp: + // FIXME: What should be reported here? + break; + case PathDiagnosticPiece::Event: + return Piece.getTagStr() == "ConditionBRVisitor" + ? ThreadFlowImportance::Important + : ThreadFlowImportance::Essential; + case PathDiagnosticPiece::ControlFlow: + return ThreadFlowImportance::Unimportant; + } + return ThreadFlowImportance::Unimportant; +} + +/// Accepts a SourceRange corresponding to a pair of the first and last tokens +/// and converts to a Character granular CharSourceRange. +static CharSourceRange convertTokenRangeToCharRange(const SourceRange &R, + const SourceManager &SM, + const LangOptions &LO) { + // Caret diagnostics have the first and last locations pointed at the same + // location, return these as-is. + if (R.getBegin() == R.getEnd()) + return CharSourceRange::getCharRange(R); + + SourceLocation BeginCharLoc = R.getBegin(); + // For token ranges, the raw end SLoc points at the first character of the + // last token in the range. This must be moved to one past the end of the + // last character using the lexer. + SourceLocation EndCharLoc = + Lexer::getLocForEndOfToken(R.getEnd(), /* Offset = */ 0, SM, LO); + return CharSourceRange::getCharRange(BeginCharLoc, EndCharLoc); +} + +static SmallVector<ThreadFlow, 8> createThreadFlows(const PathDiagnostic *Diag, + const LangOptions &LO) { + SmallVector<ThreadFlow, 8> Flows; + const PathPieces &Pieces = Diag->path.flatten(false); + for (const auto &Piece : Pieces) { + auto Range = convertTokenRangeToCharRange( + Piece->getLocation().asRange(), Piece->getLocation().getManager(), LO); + auto Flow = ThreadFlow::create() + .setImportance(calculateImportance(*Piece)) + .setRange(Range) + .setMessage(Piece->getString()); + Flows.push_back(Flow); + } + return Flows; } -static json::Array createRules(std::vector<const PathDiagnostic *> &Diags, - StringMap<unsigned> &RuleMapping) { - json::Array Rules; +static StringMap<uint32_t> +createRuleMapping(const std::vector<const PathDiagnostic *> &Diags, + SarifDocumentWriter &SarifWriter) { + StringMap<uint32_t> RuleMapping; llvm::StringSet<> Seen; - llvm::for_each(Diags, [&](const PathDiagnostic *D) { - StringRef RuleID = D->getCheckerName(); - std::pair<llvm::StringSet<>::iterator, bool> P = Seen.insert(RuleID); + for (const PathDiagnostic *D : Diags) { + StringRef CheckName = D->getCheckerName(); + std::pair<llvm::StringSet<>::iterator, bool> P = Seen.insert(CheckName); if (P.second) { - RuleMapping[RuleID] = Rules.size(); // Maps RuleID to an Array Index. - Rules.push_back(createRule(*D)); + auto Rule = SarifRule::create() + .setName(CheckName) + .setRuleId(CheckName) + .setDescription(getRuleDescription(CheckName)) + .setHelpURI(getRuleHelpURIStr(CheckName)); + size_t RuleIdx = SarifWriter.createRule(Rule); + RuleMapping[CheckName] = RuleIdx; } - }); - - return Rules; + } + return RuleMapping; } -static json::Object createTool(std::vector<const PathDiagnostic *> &Diags, - StringMap<unsigned> &RuleMapping) { - return json::Object{ - {"driver", json::Object{{"name", "clang"}, - {"fullName", "clang static analyzer"}, - {"language", "en-US"}, - {"version", getClangFullVersion()}, - {"rules", createRules(Diags, RuleMapping)}}}}; -} +static SarifResult createResult(const PathDiagnostic *Diag, + const StringMap<uint32_t> &RuleMapping, + const LangOptions &LO) { -static json::Object createRun(const LangOptions &LO, - std::vector<const PathDiagnostic *> &Diags) { - json::Array Results, Artifacts; - StringMap<unsigned> RuleMapping; - json::Object Tool = createTool(Diags, RuleMapping); - - llvm::for_each(Diags, [&](const PathDiagnostic *D) { - Results.push_back(createResult(LO, *D, Artifacts, RuleMapping)); - }); + StringRef CheckName = Diag->getCheckerName(); + uint32_t RuleIdx = RuleMapping.lookup(CheckName); + auto Range = convertTokenRangeToCharRange( + Diag->getLocation().asRange(), Diag->getLocation().getManager(), LO); - return json::Object{{"tool", std::move(Tool)}, - {"results", std::move(Results)}, - {"artifacts", std::move(Artifacts)}, - {"columnKind", "unicodeCodePoints"}}; + SmallVector<ThreadFlow, 8> Flows = createThreadFlows(Diag, LO); + auto Result = SarifResult::create(RuleIdx) + .setRuleId(CheckName) + .setDiagnosticMessage(Diag->getVerboseDescription()) + .setDiagnosticLevel(SarifResultLevel::Warning) + .setLocations({Range}) + .setThreadFlows(Flows); + return Result; } void SarifDiagnostics::FlushDiagnosticsImpl( @@ -392,10 +196,14 @@ void SarifDiagnostics::FlushDiagnosticsImpl( llvm::errs() << "warning: could not create file: " << EC.message() << '\n'; return; } - json::Object Sarif{ - {"$schema", - "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"}, - {"version", "2.1.0"}, - {"runs", json::Array{createRun(LO, Diags)}}}; - OS << llvm::formatv("{0:2}\n", json::Value(std::move(Sarif))); + + std::string ToolVersion = getClangFullVersion(); + SarifWriter.createRun("clang", "clang static analyzer", ToolVersion); + StringMap<uint32_t> RuleMapping = createRuleMapping(Diags, SarifWriter); + for (const PathDiagnostic *D : Diags) { + SarifResult Result = createResult(D, RuleMapping, LO); + SarifWriter.appendResult(Result); + } + auto Document = SarifWriter.createDocument(); + OS << llvm::formatv("{0:2}\n", json::Value(std::move(Document))); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp index f96974f97dcc..8ca2cdb9d3ab 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp @@ -15,6 +15,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include <optional> namespace clang { @@ -22,11 +23,11 @@ namespace ento { SimpleConstraintManager::~SimpleConstraintManager() {} -ProgramStateRef SimpleConstraintManager::assume(ProgramStateRef State, - DefinedSVal Cond, - bool Assumption) { +ProgramStateRef SimpleConstraintManager::assumeInternal(ProgramStateRef State, + DefinedSVal Cond, + bool Assumption) { // If we have a Loc value, cast it to a bool NonLoc first. - if (Optional<Loc> LV = Cond.getAs<Loc>()) { + if (std::optional<Loc> LV = Cond.getAs<Loc>()) { SValBuilder &SVB = State->getStateManager().getSValBuilder(); QualType T; const MemRegion *MR = LV->getAsRegion(); @@ -44,7 +45,7 @@ ProgramStateRef SimpleConstraintManager::assume(ProgramStateRef State, ProgramStateRef SimpleConstraintManager::assume(ProgramStateRef State, NonLoc Cond, bool Assumption) { State = assumeAux(State, Cond, Assumption); - if (NotifyAssumeClients && EE) + if (EE) return EE->processAssume(State, Cond, Assumption); return State; } @@ -62,7 +63,7 @@ ProgramStateRef SimpleConstraintManager::assumeAux(ProgramStateRef State, return assumeSymUnsupported(State, Sym, Assumption); } - switch (Cond.getSubKind()) { + switch (Cond.getKind()) { default: llvm_unreachable("'Assume' not implemented for this NonLoc"); @@ -86,12 +87,12 @@ ProgramStateRef SimpleConstraintManager::assumeAux(ProgramStateRef State, } case nonloc::LocAsIntegerKind: - return assume(State, Cond.castAs<nonloc::LocAsInteger>().getLoc(), - Assumption); + return assumeInternal(State, Cond.castAs<nonloc::LocAsInteger>().getLoc(), + Assumption); } // end switch } -ProgramStateRef SimpleConstraintManager::assumeInclusiveRange( +ProgramStateRef SimpleConstraintManager::assumeInclusiveRangeInternal( ProgramStateRef State, NonLoc Value, const llvm::APSInt &From, const llvm::APSInt &To, bool InRange) { @@ -106,7 +107,7 @@ ProgramStateRef SimpleConstraintManager::assumeInclusiveRange( return assumeSymInclusiveRange(State, Sym, From, To, InRange); } - switch (Value.getSubKind()) { + switch (Value.getKind()) { default: llvm_unreachable("'assumeInclusiveRange' is not implemented" "for this NonLoc"); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp index e57d92fbcebb..45e48d435aca 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp @@ -11,25 +11,63 @@ //===----------------------------------------------------------------------===// #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/SValVisitor.h" +#include <optional> using namespace clang; using namespace ento; namespace { class SimpleSValBuilder : public SValBuilder { + + // Query the constraint manager whether the SVal has only one possible + // (integer) value. If that is the case, the value is returned. Otherwise, + // returns NULL. + // This is an implementation detail. Checkers should use `getKnownValue()` + // instead. + static const llvm::APSInt *getConstValue(ProgramStateRef state, SVal V); + + // Helper function that returns the value stored in a nonloc::ConcreteInt or + // loc::ConcreteInt. + static const llvm::APSInt *getConcreteValue(SVal V); + + // With one `simplifySValOnce` call, a compound symbols might collapse to + // simpler symbol tree that is still possible to further simplify. Thus, we + // do the simplification on a new symbol tree until we reach the simplest + // form, i.e. the fixpoint. + // Consider the following symbol `(b * b) * b * b` which has this tree: + // * + // / \ + // * b + // / \ + // / b + // (b * b) + // Now, if the `b * b == 1` new constraint is added then during the first + // iteration we have the following transformations: + // * * + // / \ / \ + // * b --> b b + // / \ + // / b + // 1 + // We need another iteration to reach the final result `1`. + SVal simplifyUntilFixpoint(ProgramStateRef State, SVal Val); + + // Recursively descends into symbolic expressions and replaces symbols + // with their known values (in the sense of the getConstValue() method). + // We traverse the symbol tree and query the constraint values for the + // sub-trees and if a value is a constant we do the constant folding. + SVal simplifySValOnce(ProgramStateRef State, SVal V); + public: SimpleSValBuilder(llvm::BumpPtrAllocator &alloc, ASTContext &context, ProgramStateManager &stateMgr) - : SValBuilder(alloc, context, stateMgr) {} + : SValBuilder(alloc, context, stateMgr) {} ~SimpleSValBuilder() override {} - SVal evalMinus(NonLoc val) override; - SVal evalComplement(NonLoc val) override; SVal evalBinOpNN(ProgramStateRef state, BinaryOperator::Opcode op, NonLoc lhs, NonLoc rhs, QualType resultTy) override; SVal evalBinOpLL(ProgramStateRef state, BinaryOperator::Opcode op, @@ -37,12 +75,21 @@ public: SVal evalBinOpLN(ProgramStateRef state, BinaryOperator::Opcode op, Loc lhs, NonLoc rhs, QualType resultTy) override; - /// getKnownValue - evaluates a given SVal. If the SVal has only one possible - /// (integer) value, that value is returned. Otherwise, returns NULL. + /// Evaluates a given SVal by recursively evaluating and + /// simplifying the children SVals. If the SVal has only one possible + /// (integer) value, that value is returned. Otherwise, returns NULL. const llvm::APSInt *getKnownValue(ProgramStateRef state, SVal V) override; - /// Recursively descends into symbolic expressions and replaces symbols - /// with their known values (in the sense of the getKnownValue() method). + /// Evaluates a given SVal by recursively evaluating and simplifying the + /// children SVals, then returns its minimal possible (integer) value. If the + /// constraint manager cannot provide a meaningful answer, this returns NULL. + const llvm::APSInt *getMinValue(ProgramStateRef state, SVal V) override; + + /// Evaluates a given SVal by recursively evaluating and simplifying the + /// children SVals, then returns its maximal possible (integer) value. If the + /// constraint manager cannot provide a meaningful answer, this returns NULL. + const llvm::APSInt *getMaxValue(ProgramStateRef state, SVal V) override; + SVal simplifySVal(ProgramStateRef State, SVal V) override; SVal MakeSymIntVal(const SymExpr *LHS, BinaryOperator::Opcode op, @@ -56,26 +103,21 @@ SValBuilder *ento::createSimpleSValBuilder(llvm::BumpPtrAllocator &alloc, return new SimpleSValBuilder(alloc, context, stateMgr); } -//===----------------------------------------------------------------------===// -// Transfer function for unary operators. -//===----------------------------------------------------------------------===// - -SVal SimpleSValBuilder::evalMinus(NonLoc val) { - switch (val.getSubKind()) { - case nonloc::ConcreteIntKind: - return val.castAs<nonloc::ConcreteInt>().evalMinus(*this); - default: - return UnknownVal(); +// Checks if the negation the value and flipping sign preserve +// the semantics on the operation in the resultType +static bool isNegationValuePreserving(const llvm::APSInt &Value, + APSIntType ResultType) { + const unsigned ValueBits = Value.getSignificantBits(); + if (ValueBits == ResultType.getBitWidth()) { + // The value is the lowest negative value that is representable + // in signed integer with bitWith of result type. The + // negation is representable if resultType is unsigned. + return ResultType.isUnsigned(); } -} -SVal SimpleSValBuilder::evalComplement(NonLoc X) { - switch (X.getSubKind()) { - case nonloc::ConcreteIntKind: - return X.castAs<nonloc::ConcreteInt>().evalComplement(*this); - default: - return UnknownVal(); - } + // If resultType bitWith is higher that number of bits required + // to represent RHS, the sign flip produce same value. + return ValueBits < ResultType.getBitWidth(); } //===----------------------------------------------------------------------===// @@ -129,14 +171,14 @@ SVal SimpleSValBuilder::MakeSymIntVal(const SymExpr *LHS, // a&0 and a&(~0) if (RHS == 0) return makeIntVal(0, resultTy); - else if (RHS.isAllOnesValue()) + else if (RHS.isAllOnes()) isIdempotent = true; break; case BO_Or: // a|0 and a|(~0) if (RHS == 0) isIdempotent = true; - else if (RHS.isAllOnesValue()) { + else if (RHS.isAllOnes()) { const llvm::APSInt &Result = BasicVals.Convert(resultTy, RHS); return nonloc::ConcreteInt(Result); } @@ -171,6 +213,17 @@ SVal SimpleSValBuilder::MakeSymIntVal(const SymExpr *LHS, if (RHS.isSigned() && !SymbolType->isSignedIntegerOrEnumerationType()) ConvertedRHS = &BasicVals.Convert(SymbolType, RHS); } + } else if (BinaryOperator::isAdditiveOp(op) && RHS.isNegative()) { + // Change a+(-N) into a-N, and a-(-N) into a+N + // Adjust addition/subtraction of negative value, to + // subtraction/addition of the negated value. + APSIntType resultIntTy = BasicVals.getAPSIntType(resultTy); + if (isNegationValuePreserving(RHS, resultIntTy)) { + ConvertedRHS = &BasicVals.getValue(-resultIntTy.convert(RHS)); + op = (op == BO_Add) ? BO_Sub : BO_Add; + } else { + ConvertedRHS = &BasicVals.Convert(resultTy, RHS); + } } else ConvertedRHS = &BasicVals.Convert(resultTy, RHS); @@ -260,7 +313,6 @@ static NonLoc doRearrangeUnchecked(ProgramStateRef State, else llvm_unreachable("Operation not suitable for unchecked rearrangement!"); - // FIXME: Can we use assume() without getting into an infinite recursion? if (LSym == RSym) return SVB.evalBinOpNN(State, Op, nonloc::ConcreteInt(LInt), nonloc::ConcreteInt(RInt), ResultTy) @@ -311,51 +363,48 @@ static bool shouldRearrange(ProgramStateRef State, BinaryOperator::Opcode Op, isWithinConstantOverflowBounds(Int))); } -static Optional<NonLoc> tryRearrange(ProgramStateRef State, - BinaryOperator::Opcode Op, NonLoc Lhs, - NonLoc Rhs, QualType ResultTy) { +static std::optional<NonLoc> tryRearrange(ProgramStateRef State, + BinaryOperator::Opcode Op, NonLoc Lhs, + NonLoc Rhs, QualType ResultTy) { ProgramStateManager &StateMgr = State->getStateManager(); SValBuilder &SVB = StateMgr.getSValBuilder(); // We expect everything to be of the same type - this type. QualType SingleTy; - auto &Opts = - StateMgr.getOwningEngine().getAnalysisManager().getAnalyzerOptions(); - // FIXME: After putting complexity threshold to the symbols we can always // rearrange additive operations but rearrange comparisons only if // option is set. - if(!Opts.ShouldAggressivelySimplifyBinaryOperation) - return None; + if (!SVB.getAnalyzerOptions().ShouldAggressivelySimplifyBinaryOperation) + return std::nullopt; SymbolRef LSym = Lhs.getAsSymbol(); if (!LSym) - return None; + return std::nullopt; if (BinaryOperator::isComparisonOp(Op)) { SingleTy = LSym->getType(); if (ResultTy != SVB.getConditionType()) - return None; + return std::nullopt; // Initialize SingleTy later with a symbol's type. } else if (BinaryOperator::isAdditiveOp(Op)) { SingleTy = ResultTy; if (LSym->getType() != SingleTy) - return None; + return std::nullopt; } else { // Don't rearrange other operations. - return None; + return std::nullopt; } assert(!SingleTy.isNull() && "We should have figured out the type by now!"); // Rearrange signed symbolic expressions only if (!SingleTy->isSignedIntegerOrEnumerationType()) - return None; + return std::nullopt; SymbolRef RSym = Rhs.getAsSymbol(); if (!RSym || RSym->getType() != SingleTy) - return None; + return std::nullopt; BasicValueFactory &BV = State->getBasicVals(); llvm::APSInt LInt, RInt; @@ -363,7 +412,7 @@ static Optional<NonLoc> tryRearrange(ProgramStateRef State, std::tie(RSym, RInt) = decomposeSymbol(RSym, BV); if (!shouldRearrange(State, Op, LSym, LInt, SingleTy) || !shouldRearrange(State, Op, RSym, RInt, SingleTy)) - return None; + return std::nullopt; // We know that no overflows can occur anymore. return doRearrangeUnchecked(State, Op, LSym, LInt, RSym, RInt); @@ -376,6 +425,15 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, NonLoc InputLHS = lhs; NonLoc InputRHS = rhs; + // Constraints may have changed since the creation of a bound SVal. Check if + // the values can be simplified based on those new constraints. + SVal simplifiedLhs = simplifySVal(state, lhs); + SVal simplifiedRhs = simplifySVal(state, rhs); + if (auto simplifiedLhsAsNonLoc = simplifiedLhs.getAs<NonLoc>()) + lhs = *simplifiedLhsAsNonLoc; + if (auto simplifiedRhsAsNonLoc = simplifiedRhs.getAs<NonLoc>()) + rhs = *simplifiedRhsAsNonLoc; + // Handle trivial case where left-side and right-side are the same. if (lhs == rhs) switch (op) { @@ -400,12 +458,12 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, return evalCast(lhs, resultTy, QualType{}); } - while (1) { - switch (lhs.getSubKind()) { + while (true) { + switch (lhs.getKind()) { default: return makeSymExprValNN(op, lhs, rhs, resultTy); case nonloc::PointerToMemberKind: { - assert(rhs.getSubKind() == nonloc::PointerToMemberKind && + assert(rhs.getKind() == nonloc::PointerToMemberKind && "Both SVals should have pointer-to-member-type"); auto LPTM = lhs.castAs<nonloc::PointerToMember>(), RPTM = rhs.castAs<nonloc::PointerToMember>(); @@ -421,36 +479,36 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, } case nonloc::LocAsIntegerKind: { Loc lhsL = lhs.castAs<nonloc::LocAsInteger>().getLoc(); - switch (rhs.getSubKind()) { - case nonloc::LocAsIntegerKind: - // FIXME: at the moment the implementation - // of modeling "pointers as integers" is not complete. - if (!BinaryOperator::isComparisonOp(op)) - return UnknownVal(); - return evalBinOpLL(state, op, lhsL, - rhs.castAs<nonloc::LocAsInteger>().getLoc(), - resultTy); - case nonloc::ConcreteIntKind: { - // FIXME: at the moment the implementation - // of modeling "pointers as integers" is not complete. - if (!BinaryOperator::isComparisonOp(op)) - return UnknownVal(); - // Transform the integer into a location and compare. - // FIXME: This only makes sense for comparisons. If we want to, say, - // add 1 to a LocAsInteger, we'd better unpack the Loc and add to it, - // then pack it back into a LocAsInteger. - llvm::APSInt i = rhs.castAs<nonloc::ConcreteInt>().getValue(); - // If the region has a symbolic base, pay attention to the type; it - // might be coming from a non-default address space. For non-symbolic - // regions it doesn't matter that much because such comparisons would - // most likely evaluate to concrete false anyway. FIXME: We might - // still need to handle the non-comparison case. - if (SymbolRef lSym = lhs.getAsLocSymbol(true)) - BasicVals.getAPSIntType(lSym->getType()).apply(i); - else - BasicVals.getAPSIntType(Context.VoidPtrTy).apply(i); - return evalBinOpLL(state, op, lhsL, makeLoc(i), resultTy); - } + switch (rhs.getKind()) { + case nonloc::LocAsIntegerKind: + // FIXME: at the moment the implementation + // of modeling "pointers as integers" is not complete. + if (!BinaryOperator::isComparisonOp(op)) + return UnknownVal(); + return evalBinOpLL(state, op, lhsL, + rhs.castAs<nonloc::LocAsInteger>().getLoc(), + resultTy); + case nonloc::ConcreteIntKind: { + // FIXME: at the moment the implementation + // of modeling "pointers as integers" is not complete. + if (!BinaryOperator::isComparisonOp(op)) + return UnknownVal(); + // Transform the integer into a location and compare. + // FIXME: This only makes sense for comparisons. If we want to, say, + // add 1 to a LocAsInteger, we'd better unpack the Loc and add to it, + // then pack it back into a LocAsInteger. + llvm::APSInt i = rhs.castAs<nonloc::ConcreteInt>().getValue(); + // If the region has a symbolic base, pay attention to the type; it + // might be coming from a non-default address space. For non-symbolic + // regions it doesn't matter that much because such comparisons would + // most likely evaluate to concrete false anyway. FIXME: We might + // still need to handle the non-comparison case. + if (SymbolRef lSym = lhs.getAsLocSymbol(true)) + BasicVals.getAPSIntType(lSym->getType()).apply(i); + else + BasicVals.getAPSIntType(Context.VoidPtrTy).apply(i); + return evalBinOpLL(state, op, lhsL, makeLoc(i), resultTy); + } default: switch (op) { case BO_EQ: @@ -461,13 +519,13 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, // This case also handles pointer arithmetic. return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); } - } + } } case nonloc::ConcreteIntKind: { llvm::APSInt LHSValue = lhs.castAs<nonloc::ConcreteInt>().getValue(); // If we're dealing with two known constants, just perform the operation. - if (const llvm::APSInt *KnownRHSValue = getKnownValue(state, rhs)) { + if (const llvm::APSInt *KnownRHSValue = getConstValue(state, rhs)) { llvm::APSInt RHSValue = *KnownRHSValue; if (BinaryOperator::isComparisonOp(op)) { // We're looking for a type big enough to compare the two values. @@ -485,8 +543,21 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, const llvm::APSInt *Result = BasicVals.evalAPSInt(op, LHSValue, RHSValue); - if (!Result) + if (!Result) { + if (op == BO_Shl || op == BO_Shr) { + // FIXME: At this point the constant folding claims that the result + // of a bitwise shift is undefined. However, constant folding + // relies on the inaccurate type information that is stored in the + // bit size of APSInt objects, and if we reached this point, then + // the checker core.BitwiseShift already determined that the shift + // is valid (in a PreStmt callback, by querying the real type from + // the AST node). + // To avoid embarrassing false positives, let's just say that we + // don't know anything about the result of the shift. + return UnknownVal(); + } return UndefinedVal(); + } return nonloc::ConcreteInt(*Result); } @@ -501,7 +572,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, case BO_LE: case BO_GE: op = BinaryOperator::reverseComparisonOp(op); - LLVM_FALLTHROUGH; + [[fallthrough]]; case BO_EQ: case BO_NE: case BO_Add: @@ -513,9 +584,9 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, continue; case BO_Shr: // (~0)>>a - if (LHSValue.isAllOnesValue() && LHSValue.isSigned()) + if (LHSValue.isAllOnes() && LHSValue.isSigned()) return evalCast(lhs, resultTy, QualType{}); - LLVM_FALLTHROUGH; + [[fallthrough]]; case BO_Shl: // 0<<a and 0>>a if (LHSValue == 0) @@ -527,7 +598,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, // 0 % x == 0 if (LHSValue == 0) return makeZeroVal(resultTy); - LLVM_FALLTHROUGH; + [[fallthrough]]; default: return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); } @@ -587,7 +658,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, } // For now, only handle expressions whose RHS is a constant. - if (const llvm::APSInt *RHSValue = getKnownValue(state, rhs)) { + if (const llvm::APSInt *RHSValue = getConstValue(state, rhs)) { // If both the LHS and the current expression are additive, // fold their constants and try again. if (BinaryOperator::isAdditiveOp(op)) { @@ -604,16 +675,26 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, const llvm::APSInt &first = IntType.convert(symIntExpr->getRHS()); const llvm::APSInt &second = IntType.convert(*RHSValue); + // If the op and lop agrees, then we just need to + // sum the constants. Otherwise, we change to operation + // type if substraction would produce negative value + // (and cause overflow for unsigned integers), + // as consequence x+1U-10 produces x-9U, instead + // of x+4294967287U, that would be produced without this + // additional check. const llvm::APSInt *newRHS; - if (lop == op) + if (lop == op) { newRHS = BasicVals.evalAPSInt(BO_Add, first, second); - else + } else if (first >= second) { newRHS = BasicVals.evalAPSInt(BO_Sub, first, second); + op = lop; + } else { + newRHS = BasicVals.evalAPSInt(BO_Sub, second, first); + } assert(newRHS && "Invalid operation despite common type!"); rhs = nonloc::ConcreteInt(*newRHS); lhs = nonloc::SymbolVal(symIntExpr->getLHS()); - op = lop; continue; } } @@ -623,21 +704,11 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, } } - // Does the symbolic expression simplify to a constant? - // If so, "fold" the constant by setting 'lhs' to a ConcreteInt - // and try again. - SVal simplifiedLhs = simplifySVal(state, lhs); - if (simplifiedLhs != lhs) - if (auto simplifiedLhsAsNonLoc = simplifiedLhs.getAs<NonLoc>()) { - lhs = *simplifiedLhsAsNonLoc; - continue; - } - // Is the RHS a constant? - if (const llvm::APSInt *RHSValue = getKnownValue(state, rhs)) + if (const llvm::APSInt *RHSValue = getConstValue(state, rhs)) return MakeSymIntVal(Sym, op, *RHSValue, resultTy); - if (Optional<NonLoc> V = tryRearrange(state, op, lhs, rhs, resultTy)) + if (std::optional<NonLoc> V = tryRearrange(state, op, lhs, rhs, resultTy)) return *V; // Give up -- this is not a symbolic expression we can handle. @@ -693,11 +764,40 @@ static SVal evalBinOpFieldRegionFieldRegion(const FieldRegion *LeftFR, llvm_unreachable("Fields not found in parent record's definition"); } +// This is used in debug builds only for now because some downstream users +// may hit this assert in their subsequent merges. +// There are still places in the analyzer where equal bitwidth Locs +// are compared, and need to be found and corrected. Recent previous fixes have +// addressed the known problems of making NULLs with specific bitwidths +// for Loc comparisons along with deprecation of APIs for the same purpose. +// +static void assertEqualBitWidths(ProgramStateRef State, Loc RhsLoc, + Loc LhsLoc) { + // Implements a "best effort" check for RhsLoc and LhsLoc bit widths + ASTContext &Ctx = State->getStateManager().getContext(); + uint64_t RhsBitwidth = + RhsLoc.getType(Ctx).isNull() ? 0 : Ctx.getTypeSize(RhsLoc.getType(Ctx)); + uint64_t LhsBitwidth = + LhsLoc.getType(Ctx).isNull() ? 0 : Ctx.getTypeSize(LhsLoc.getType(Ctx)); + if (RhsBitwidth && LhsBitwidth && (LhsLoc.getKind() == RhsLoc.getKind())) { + assert(RhsBitwidth == LhsBitwidth && + "RhsLoc and LhsLoc bitwidth must be same!"); + } +} + // FIXME: all this logic will change if/when we have MemRegion::getLocation(). SVal SimpleSValBuilder::evalBinOpLL(ProgramStateRef state, BinaryOperator::Opcode op, Loc lhs, Loc rhs, QualType resultTy) { + + // Assert that bitwidth of lhs and rhs are the same. + // This can happen if two different address spaces are used, + // and the bitwidths of the address spaces are different. + // See LIT case clang/test/Analysis/cstring-checker-addressspace.c + // FIXME: See comment above in the function assertEqualBitWidths + assertEqualBitWidths(state, rhs, lhs); + // Only comparisons and subtractions are valid operations on two pointers. // See [C99 6.5.5 through 6.5.14] or [C++0x 5.6 through 5.15]. // However, if a pointer is casted to an integer, evalBinOpNN may end up @@ -725,7 +825,7 @@ SVal SimpleSValBuilder::evalBinOpLL(ProgramStateRef state, } } - switch (lhs.getSubKind()) { + switch (lhs.getKind()) { default: llvm_unreachable("Ordering not implemented for this Loc."); @@ -755,6 +855,8 @@ SVal SimpleSValBuilder::evalBinOpLL(ProgramStateRef state, return UnknownVal(); case loc::ConcreteIntKind: { + auto L = lhs.castAs<loc::ConcreteInt>(); + // If one of the operands is a symbol and the other is a constant, // build an expression for use by the constraint manager. if (SymbolRef rSym = rhs.getAsLocSymbol()) { @@ -763,19 +865,17 @@ SVal SimpleSValBuilder::evalBinOpLL(ProgramStateRef state, if (!BinaryOperator::isComparisonOp(op) || op == BO_Cmp) return UnknownVal(); - const llvm::APSInt &lVal = lhs.castAs<loc::ConcreteInt>().getValue(); op = BinaryOperator::reverseComparisonOp(op); - return makeNonLoc(rSym, op, lVal, resultTy); + return makeNonLoc(rSym, op, L.getValue(), resultTy); } // If both operands are constants, just perform the operation. - if (Optional<loc::ConcreteInt> rInt = rhs.getAs<loc::ConcreteInt>()) { - SVal ResultVal = - lhs.castAs<loc::ConcreteInt>().evalBinOp(BasicVals, op, *rInt); - if (Optional<NonLoc> Result = ResultVal.getAs<NonLoc>()) - return evalCast(*Result, resultTy, QualType{}); + if (std::optional<loc::ConcreteInt> rInt = rhs.getAs<loc::ConcreteInt>()) { + assert(BinaryOperator::isComparisonOp(op) || op == BO_Sub); - assert(!ResultVal.getAs<Loc>() && "Loc-Loc ops should not produce Locs"); + if (const auto *ResultInt = + BasicVals.evalAPSInt(op, L.getValue(), rInt->getValue())) + return evalCast(nonloc::ConcreteInt(*ResultInt), resultTy, QualType{}); return UnknownVal(); } @@ -783,7 +883,7 @@ SVal SimpleSValBuilder::evalBinOpLL(ProgramStateRef state, // This must come after the test if the RHS is a symbol, which is used to // build constraints. The address of any non-symbolic region is guaranteed // to be non-NULL, as is any label. - assert(rhs.getAs<loc::MemRegionVal>() || rhs.getAs<loc::GotoLabel>()); + assert((isa<loc::MemRegionVal, loc::GotoLabel>(rhs))); if (lhs.isZeroConstant()) { switch (op) { default: @@ -804,7 +904,7 @@ SVal SimpleSValBuilder::evalBinOpLL(ProgramStateRef state, return UnknownVal(); } case loc::MemRegionValKind: { - if (Optional<loc::ConcreteInt> rInt = rhs.getAs<loc::ConcreteInt>()) { + if (std::optional<loc::ConcreteInt> rInt = rhs.getAs<loc::ConcreteInt>()) { // If one of the operands is a symbol and the other is a constant, // build an expression for use by the constraint manager. if (SymbolRef lSym = lhs.getAsLocSymbol(true)) { @@ -901,7 +1001,7 @@ SVal SimpleSValBuilder::evalBinOpLL(ProgramStateRef state, // Get the left index and cast it to the correct type. // If the index is unknown or undefined, bail out here. SVal LeftIndexVal = LeftER->getIndex(); - Optional<NonLoc> LeftIndex = LeftIndexVal.getAs<NonLoc>(); + std::optional<NonLoc> LeftIndex = LeftIndexVal.getAs<NonLoc>(); if (!LeftIndex) return UnknownVal(); LeftIndexVal = evalCast(*LeftIndex, ArrayIndexTy, QualType{}); @@ -911,7 +1011,7 @@ SVal SimpleSValBuilder::evalBinOpLL(ProgramStateRef state, // Do the same for the right index. SVal RightIndexVal = RightER->getIndex(); - Optional<NonLoc> RightIndex = RightIndexVal.getAs<NonLoc>(); + std::optional<NonLoc> RightIndex = RightIndexVal.getAs<NonLoc>(); if (!RightIndex) return UnknownVal(); RightIndexVal = evalCast(*RightIndex, ArrayIndexTy, QualType{}); @@ -1019,8 +1119,10 @@ SVal SimpleSValBuilder::evalBinOpLN(ProgramStateRef state, // We are dealing with pointer arithmetic. // Handle pointer arithmetic on constant values. - if (Optional<nonloc::ConcreteInt> rhsInt = rhs.getAs<nonloc::ConcreteInt>()) { - if (Optional<loc::ConcreteInt> lhsInt = lhs.getAs<loc::ConcreteInt>()) { + if (std::optional<nonloc::ConcreteInt> rhsInt = + rhs.getAs<nonloc::ConcreteInt>()) { + if (std::optional<loc::ConcreteInt> lhsInt = + lhs.getAs<loc::ConcreteInt>()) { const llvm::APSInt &leftI = lhsInt->getValue(); assert(leftI.isUnsigned()); llvm::APSInt rightI(rhsInt->getValue(), /* isUnsigned */ true); @@ -1084,7 +1186,7 @@ SVal SimpleSValBuilder::evalBinOpLN(ProgramStateRef state, if (elementType->isVoidType()) elementType = getContext().CharTy; - if (Optional<NonLoc> indexV = index.getAs<NonLoc>()) { + if (std::optional<NonLoc> indexV = index.getAs<NonLoc>()) { return loc::MemRegionVal(MemMgr.getElementRegion(elementType, *indexV, superR, getContext())); } @@ -1092,26 +1194,72 @@ SVal SimpleSValBuilder::evalBinOpLN(ProgramStateRef state, return UnknownVal(); } +const llvm::APSInt *SimpleSValBuilder::getConstValue(ProgramStateRef state, + SVal V) { + if (const llvm::APSInt *Res = getConcreteValue(V)) + return Res; + + if (SymbolRef Sym = V.getAsSymbol()) + return state->getConstraintManager().getSymVal(state, Sym); + + return nullptr; +} + +const llvm::APSInt *SimpleSValBuilder::getConcreteValue(SVal V) { + if (std::optional<loc::ConcreteInt> X = V.getAs<loc::ConcreteInt>()) + return &X->getValue(); + + if (std::optional<nonloc::ConcreteInt> X = V.getAs<nonloc::ConcreteInt>()) + return &X->getValue(); + + return nullptr; +} + const llvm::APSInt *SimpleSValBuilder::getKnownValue(ProgramStateRef state, + SVal V) { + return getConstValue(state, simplifySVal(state, V)); +} + +const llvm::APSInt *SimpleSValBuilder::getMinValue(ProgramStateRef state, SVal V) { V = simplifySVal(state, V); - if (V.isUnknownOrUndef()) - return nullptr; - if (Optional<loc::ConcreteInt> X = V.getAs<loc::ConcreteInt>()) - return &X->getValue(); + if (const llvm::APSInt *Res = getConcreteValue(V)) + return Res; - if (Optional<nonloc::ConcreteInt> X = V.getAs<nonloc::ConcreteInt>()) - return &X->getValue(); + if (SymbolRef Sym = V.getAsSymbol()) + return state->getConstraintManager().getSymMinVal(state, Sym); + + return nullptr; +} + +const llvm::APSInt *SimpleSValBuilder::getMaxValue(ProgramStateRef state, + SVal V) { + V = simplifySVal(state, V); + + if (const llvm::APSInt *Res = getConcreteValue(V)) + return Res; if (SymbolRef Sym = V.getAsSymbol()) - return state->getConstraintManager().getSymVal(state, Sym); + return state->getConstraintManager().getSymMaxVal(state, Sym); - // FIXME: Add support for SymExprs. return nullptr; } +SVal SimpleSValBuilder::simplifyUntilFixpoint(ProgramStateRef State, SVal Val) { + SVal SimplifiedVal = simplifySValOnce(State, Val); + while (SimplifiedVal != Val) { + Val = SimplifiedVal; + SimplifiedVal = simplifySValOnce(State, Val); + } + return SimplifiedVal; +} + SVal SimpleSValBuilder::simplifySVal(ProgramStateRef State, SVal V) { + return simplifyUntilFixpoint(State, V); +} + +SVal SimpleSValBuilder::simplifySValOnce(ProgramStateRef State, SVal V) { // For now, this function tries to constant-fold symbols inside a // nonloc::SymbolVal, and does nothing else. More simplifications should // be possible, such as constant-folding an index in an ElementRegion. @@ -1139,6 +1287,24 @@ SVal SimpleSValBuilder::simplifySVal(ProgramStateRef State, SVal V) { return cache(Sym, SVB.makeSymbolVal(Sym)); } + // Return the known const value for the Sym if available, or return Undef + // otherwise. + SVal getConst(SymbolRef Sym) { + const llvm::APSInt *Const = + State->getConstraintManager().getSymVal(State, Sym); + if (Const) + return Loc::isLocType(Sym->getType()) ? (SVal)SVB.makeIntLocVal(*Const) + : (SVal)SVB.makeIntVal(*Const); + return UndefinedVal(); + } + + SVal getConstOrVisit(SymbolRef Sym) { + const SVal Ret = getConst(Sym); + if (Ret.isUndef()) + return Visit(Sym); + return Ret; + } + public: Simplifier(ProgramStateRef State) : State(State), SVB(State->getStateManager().getSValBuilder()) {} @@ -1146,21 +1312,18 @@ SVal SimpleSValBuilder::simplifySVal(ProgramStateRef State, SVal V) { SVal VisitSymbolData(const SymbolData *S) { // No cache here. if (const llvm::APSInt *I = - SVB.getKnownValue(State, SVB.makeSymbolVal(S))) + State->getConstraintManager().getSymVal(State, S)) return Loc::isLocType(S->getType()) ? (SVal)SVB.makeIntLocVal(*I) : (SVal)SVB.makeIntVal(*I); return SVB.makeSymbolVal(S); } - // TODO: Support SymbolCast. Support IntSymExpr when/if we actually - // start producing them. - SVal VisitSymIntExpr(const SymIntExpr *S) { auto I = Cached.find(S); if (I != Cached.end()) return I->second; - SVal LHS = Visit(S->getLHS()); + SVal LHS = getConstOrVisit(S->getLHS()); if (isUnchanged(S->getLHS(), LHS)) return skip(S); @@ -1187,6 +1350,20 @@ SVal SimpleSValBuilder::simplifySVal(ProgramStateRef State, SVal V) { S, SVB.evalBinOp(State, S->getOpcode(), LHS, RHS, S->getType())); } + SVal VisitIntSymExpr(const IntSymExpr *S) { + auto I = Cached.find(S); + if (I != Cached.end()) + return I->second; + + SVal RHS = getConstOrVisit(S->getRHS()); + if (isUnchanged(S->getRHS(), RHS)) + return skip(S); + + SVal LHS = SVB.makeIntVal(S->getLHS()); + return cache( + S, SVB.evalBinOp(State, S->getOpcode(), LHS, RHS, S->getType())); + } + SVal VisitSymSymExpr(const SymSymExpr *S) { auto I = Cached.find(S); if (I != Cached.end()) @@ -1200,8 +1377,9 @@ SVal SimpleSValBuilder::simplifySVal(ProgramStateRef State, SVal V) { Loc::isLocType(S->getRHS()->getType())) return skip(S); - SVal LHS = Visit(S->getLHS()); - SVal RHS = Visit(S->getRHS()); + SVal LHS = getConstOrVisit(S->getLHS()); + SVal RHS = getConstOrVisit(S->getRHS()); + if (isUnchanged(S->getLHS(), LHS) && isUnchanged(S->getRHS(), RHS)) return skip(S); @@ -1209,11 +1387,35 @@ SVal SimpleSValBuilder::simplifySVal(ProgramStateRef State, SVal V) { S, SVB.evalBinOp(State, S->getOpcode(), LHS, RHS, S->getType())); } + SVal VisitSymbolCast(const SymbolCast *S) { + auto I = Cached.find(S); + if (I != Cached.end()) + return I->second; + const SymExpr *OpSym = S->getOperand(); + SVal OpVal = getConstOrVisit(OpSym); + if (isUnchanged(OpSym, OpVal)) + return skip(S); + + return cache(S, SVB.evalCast(OpVal, S->getType(), OpSym->getType())); + } + + SVal VisitUnarySymExpr(const UnarySymExpr *S) { + auto I = Cached.find(S); + if (I != Cached.end()) + return I->second; + SVal Op = getConstOrVisit(S->getOperand()); + if (isUnchanged(S->getOperand(), Op)) + return skip(S); + + return cache( + S, SVB.evalUnaryOp(State, S->getOpcode(), Op, S->getType())); + } + SVal VisitSymExpr(SymbolRef S) { return nonloc::SymbolVal(S); } SVal VisitMemRegion(const MemRegion *R) { return loc::MemRegionVal(R); } - SVal VisitNonLocSymbolVal(nonloc::SymbolVal V) { + SVal VisitSymbolVal(nonloc::SymbolVal V) { // Simplification is much more costly than computing complexity. // For high complexity, it may be not worth it. return Visit(V.getSymbol()); @@ -1222,14 +1424,6 @@ SVal SimpleSValBuilder::simplifySVal(ProgramStateRef State, SVal V) { SVal VisitSVal(SVal V) { return V; } }; - // A crude way of preventing this function from calling itself from evalBinOp. - static bool isReentering = false; - if (isReentering) - return V; - - isReentering = true; SVal SimplifiedV = Simplifier(State).Visit(V); - isReentering = false; - return SimplifiedV; } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Store.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Store.cpp index b867b0746f90..67ca61bb56ba 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Store.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/Store.cpp @@ -29,12 +29,12 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/StoreRef.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" #include "llvm/ADT/APSInt.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Casting.h" #include "llvm/Support/ErrorHandling.h" #include <cassert> #include <cstdint> +#include <optional> using namespace clang; using namespace ento; @@ -71,8 +71,8 @@ const ElementRegion *StoreManager::GetElementZeroRegion(const SubRegion *R, return MRMgr.getElementRegion(T, idx, R, Ctx); } -Optional<const MemRegion *> StoreManager::castRegion(const MemRegion *R, - QualType CastToTy) { +std::optional<const MemRegion *> StoreManager::castRegion(const MemRegion *R, + QualType CastToTy) { ASTContext &Ctx = StateMgr.getContext(); // Handle casts to Objective-C objects. @@ -84,30 +84,36 @@ Optional<const MemRegion *> StoreManager::castRegion(const MemRegion *R, // involved. Blocks can be casted to/from 'id', as they can be treated // as Objective-C objects. This could possibly be handled by enhancing // our reasoning of downcasts of symbolic objects. - if (isa<CodeTextRegion>(R) || isa<SymbolicRegion>(R)) + if (isa<CodeTextRegion, SymbolicRegion>(R)) return R; // We don't know what to make of it. Return a NULL region, which // will be interpreted as UnknownVal. - return None; + return std::nullopt; } // Now assume we are casting from pointer to pointer. Other cases should // already be handled. QualType PointeeTy = CastToTy->getPointeeType(); QualType CanonPointeeTy = Ctx.getCanonicalType(PointeeTy); + CanonPointeeTy = CanonPointeeTy.getLocalUnqualifiedType(); // Handle casts to void*. We just pass the region through. - if (CanonPointeeTy.getLocalUnqualifiedType() == Ctx.VoidTy) + if (CanonPointeeTy == Ctx.VoidTy) return R; - // Handle casts from compatible types. - if (R->isBoundable()) + const auto IsSameRegionType = [&Ctx](const MemRegion *R, QualType OtherTy) { if (const auto *TR = dyn_cast<TypedValueRegion>(R)) { QualType ObjTy = Ctx.getCanonicalType(TR->getValueType()); - if (CanonPointeeTy == ObjTy) - return R; + if (OtherTy == ObjTy.getLocalUnqualifiedType()) + return true; } + return false; + }; + + // Handle casts from compatible types. + if (R->isBoundable() && IsSameRegionType(R, CanonPointeeTy)) + return R; // Process region cast according to the kind of the region being cast. switch (R->getKind()) { @@ -138,6 +144,7 @@ Optional<const MemRegion *> StoreManager::castRegion(const MemRegion *R, case MemRegion::NonParamVarRegionKind: case MemRegion::ParamVarRegionKind: case MemRegion::CXXTempObjectRegionKind: + case MemRegion::CXXLifetimeExtendedObjectRegionKind: case MemRegion::CXXBaseObjectRegionKind: case MemRegion::CXXDerivedObjectRegionKind: return MakeElementRegion(cast<SubRegion>(R), PointeeTy); @@ -169,21 +176,16 @@ Optional<const MemRegion *> StoreManager::castRegion(const MemRegion *R, // If we cannot compute a raw offset, throw up our hands and return // a NULL MemRegion*. if (!baseR) - return None; + return std::nullopt; CharUnits off = rawOff.getOffset(); if (off.isZero()) { - // Edge case: we are at 0 bytes off the beginning of baseR. We - // check to see if type we are casting to is the same as the base - // region. If so, just return the base region. - if (const auto *TR = dyn_cast<TypedValueRegion>(baseR)) { - QualType ObjTy = Ctx.getCanonicalType(TR->getValueType()); - QualType CanonPointeeTy = Ctx.getCanonicalType(PointeeTy); - if (CanonPointeeTy == ObjTy) - return baseR; - } - + // Edge case: we are at 0 bytes off the beginning of baseR. We check to + // see if the type we are casting to is the same as the type of the base + // region. If so, just return the base region. + if (IsSameRegionType(baseR, CanonPointeeTy)) + return baseR; // Otherwise, create a new ElementRegion at offset 0. return MakeElementRegion(cast<SubRegion>(baseR), PointeeTy); } @@ -248,17 +250,15 @@ static bool regionMatchesCXXRecordType(SVal V, QualType Ty) { } SVal StoreManager::evalDerivedToBase(SVal Derived, const CastExpr *Cast) { - // Sanity check to avoid doing the wrong thing in the face of + // Early return to avoid doing the wrong thing in the face of // reinterpret_cast. if (!regionMatchesCXXRecordType(Derived, Cast->getSubExpr()->getType())) return UnknownVal(); // Walk through the cast path to create nested CXXBaseRegions. SVal Result = Derived; - for (CastExpr::path_const_iterator I = Cast->path_begin(), - E = Cast->path_end(); - I != E; ++I) { - Result = evalDerivedToBase(Result, (*I)->getType(), (*I)->isVirtual()); + for (const CXXBaseSpecifier *Base : Cast->path()) { + Result = evalDerivedToBase(Result, Base->getType(), Base->isVirtual()); } return Result; } @@ -313,10 +313,8 @@ static const CXXRecordDecl *getCXXRecordType(const MemRegion *MR) { return nullptr; } -SVal StoreManager::attemptDownCast(SVal Base, QualType TargetType, - bool &Failed) { - Failed = false; - +std::optional<SVal> StoreManager::evalBaseToDerived(SVal Base, + QualType TargetType) { const MemRegion *MR = Base.getAsRegion(); if (!MR) return UnknownVal(); @@ -391,7 +389,9 @@ SVal StoreManager::attemptDownCast(SVal Base, QualType TargetType, } // We failed if the region we ended up with has perfect type info. - Failed = isa<TypedValueRegion>(MR); + if (isa<TypedValueRegion>(MR)) + return std::nullopt; + return UnknownVal(); } @@ -402,7 +402,7 @@ SVal StoreManager::getLValueFieldOrIvar(const Decl *D, SVal Base) { Loc BaseL = Base.castAs<Loc>(); const SubRegion* BaseR = nullptr; - switch (BaseL.getSubKind()) { + switch (BaseL.getKind()) { case loc::MemRegionValKind: BaseR = cast<SubRegion>(BaseL.castAs<loc::MemRegionVal>().getRegion()); break; @@ -442,14 +442,27 @@ SVal StoreManager::getLValueIvar(const ObjCIvarDecl *decl, SVal base) { SVal StoreManager::getLValueElement(QualType elementType, NonLoc Offset, SVal Base) { + + // Special case, if index is 0, return the same type as if + // this was not an array dereference. + if (Offset.isZeroConstant()) { + QualType BT = Base.getType(this->Ctx); + if (!BT.isNull() && !elementType.isNull()) { + QualType PointeeTy = BT->getPointeeType(); + if (!PointeeTy.isNull() && + PointeeTy.getCanonicalType() == elementType.getCanonicalType()) + return Base; + } + } + // If the base is an unknown or undefined value, just return it back. // FIXME: For absolute pointer addresses, we just return that value back as // well, although in reality we should return the offset added to that // value. See also the similar FIXME in getLValueFieldOrIvar(). - if (Base.isUnknownOrUndef() || Base.getAs<loc::ConcreteInt>()) + if (Base.isUnknownOrUndef() || isa<loc::ConcreteInt>(Base)) return Base; - if (Base.getAs<loc::GotoLabel>()) + if (isa<loc::GotoLabel>(Base)) return UnknownVal(); const SubRegion *BaseRegion = @@ -475,7 +488,7 @@ SVal StoreManager::getLValueElement(QualType elementType, NonLoc Offset, SVal BaseIdx = ElemR->getIndex(); - if (!BaseIdx.getAs<nonloc::ConcreteInt>()) + if (!isa<nonloc::ConcreteInt>(BaseIdx)) return UnknownVal(); const llvm::APSInt &BaseIdxI = @@ -484,7 +497,7 @@ SVal StoreManager::getLValueElement(QualType elementType, NonLoc Offset, // Only allow non-integer offsets if the base region has no offset itself. // FIXME: This is a somewhat arbitrary restriction. We should be using // SValBuilder here to add the two offsets without checking their types. - if (!Offset.getAs<nonloc::ConcreteInt>()) { + if (!isa<nonloc::ConcreteInt>(Offset)) { if (isa<ElementRegion>(BaseRegion->StripCasts())) return UnknownVal(); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp index 79a8eef30576..9025e11a3f51 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp @@ -65,14 +65,23 @@ void BinarySymExpr::dumpToStreamImpl(raw_ostream &OS, } void SymbolCast::dumpToStream(raw_ostream &os) const { - os << '(' << ToTy.getAsString() << ") ("; + os << '(' << ToTy << ") ("; Operand->dumpToStream(os); os << ')'; } +void UnarySymExpr::dumpToStream(raw_ostream &os) const { + os << UnaryOperator::getOpcodeStr(Op); + bool Binary = isa<BinarySymExpr>(Operand); + if (Binary) + os << '('; + Operand->dumpToStream(os); + if (Binary) + os << ')'; +} + void SymbolConjured::dumpToStream(raw_ostream &os) const { - os << getKindStr() << getSymbolID() << '{' << T.getAsString() << ", LC" - << LCtx->getID(); + os << getKindStr() << getSymbolID() << '{' << T << ", LC" << LCtx->getID(); if (S) os << ", S" << S->getID(LCtx->getDecl()->getASTContext()); else @@ -90,15 +99,13 @@ void SymbolExtent::dumpToStream(raw_ostream &os) const { } void SymbolMetadata::dumpToStream(raw_ostream &os) const { - os << getKindStr() << getSymbolID() << '{' << getRegion() << ',' - << T.getAsString() << '}'; + os << getKindStr() << getSymbolID() << '{' << getRegion() << ',' << T << '}'; } void SymbolData::anchor() {} void SymbolRegionValue::dumpToStream(raw_ostream &os) const { - os << getKindStr() << getSymbolID() << '<' << getType().getAsString() << ' ' - << R << '>'; + os << getKindStr() << getSymbolID() << '<' << getType() << ' ' << R << '>'; } bool SymExpr::symbol_iterator::operator==(const symbol_iterator &X) const { @@ -137,6 +144,9 @@ void SymExpr::symbol_iterator::expand() { case SymExpr::SymbolCastKind: itr.push_back(cast<SymbolCast>(SE)->getOperand()); return; + case SymExpr::UnarySymExprKind: + itr.push_back(cast<UnarySymExpr>(SE)->getOperand()); + return; case SymExpr::SymIntExprKind: itr.push_back(cast<SymIntExpr>(SE)->getLHS()); return; @@ -160,8 +170,7 @@ SymbolManager::getRegionValueSymbol(const TypedValueRegion* R) { void *InsertPos; SymExpr *SD = DataSet.FindNodeOrInsertPos(profile, InsertPos); if (!SD) { - SD = (SymExpr*) BPAlloc.Allocate<SymbolRegionValue>(); - new (SD) SymbolRegionValue(SymbolCounter, R); + SD = new (BPAlloc) SymbolRegionValue(SymbolCounter, R); DataSet.InsertNode(SD, InsertPos); ++SymbolCounter; } @@ -179,8 +188,7 @@ const SymbolConjured* SymbolManager::conjureSymbol(const Stmt *E, void *InsertPos; SymExpr *SD = DataSet.FindNodeOrInsertPos(profile, InsertPos); if (!SD) { - SD = (SymExpr*) BPAlloc.Allocate<SymbolConjured>(); - new (SD) SymbolConjured(SymbolCounter, E, LCtx, T, Count, SymbolTag); + SD = new (BPAlloc) SymbolConjured(SymbolCounter, E, LCtx, T, Count, SymbolTag); DataSet.InsertNode(SD, InsertPos); ++SymbolCounter; } @@ -196,8 +204,7 @@ SymbolManager::getDerivedSymbol(SymbolRef parentSymbol, void *InsertPos; SymExpr *SD = DataSet.FindNodeOrInsertPos(profile, InsertPos); if (!SD) { - SD = (SymExpr*) BPAlloc.Allocate<SymbolDerived>(); - new (SD) SymbolDerived(SymbolCounter, parentSymbol, R); + SD = new (BPAlloc) SymbolDerived(SymbolCounter, parentSymbol, R); DataSet.InsertNode(SD, InsertPos); ++SymbolCounter; } @@ -212,8 +219,7 @@ SymbolManager::getExtentSymbol(const SubRegion *R) { void *InsertPos; SymExpr *SD = DataSet.FindNodeOrInsertPos(profile, InsertPos); if (!SD) { - SD = (SymExpr*) BPAlloc.Allocate<SymbolExtent>(); - new (SD) SymbolExtent(SymbolCounter, R); + SD = new (BPAlloc) SymbolExtent(SymbolCounter, R); DataSet.InsertNode(SD, InsertPos); ++SymbolCounter; } @@ -230,8 +236,7 @@ SymbolManager::getMetadataSymbol(const MemRegion* R, const Stmt *S, QualType T, void *InsertPos; SymExpr *SD = DataSet.FindNodeOrInsertPos(profile, InsertPos); if (!SD) { - SD = (SymExpr*) BPAlloc.Allocate<SymbolMetadata>(); - new (SD) SymbolMetadata(SymbolCounter, R, S, T, LCtx, Count, SymbolTag); + SD = new (BPAlloc) SymbolMetadata(SymbolCounter, R, S, T, LCtx, Count, SymbolTag); DataSet.InsertNode(SD, InsertPos); ++SymbolCounter; } @@ -247,8 +252,7 @@ SymbolManager::getCastSymbol(const SymExpr *Op, void *InsertPos; SymExpr *data = DataSet.FindNodeOrInsertPos(ID, InsertPos); if (!data) { - data = (SymbolCast*) BPAlloc.Allocate<SymbolCast>(); - new (data) SymbolCast(Op, From, To); + data = new (BPAlloc) SymbolCast(Op, From, To); DataSet.InsertNode(data, InsertPos); } @@ -265,8 +269,7 @@ const SymIntExpr *SymbolManager::getSymIntExpr(const SymExpr *lhs, SymExpr *data = DataSet.FindNodeOrInsertPos(ID, InsertPos); if (!data) { - data = (SymIntExpr*) BPAlloc.Allocate<SymIntExpr>(); - new (data) SymIntExpr(lhs, op, v, t); + data = new (BPAlloc) SymIntExpr(lhs, op, v, t); DataSet.InsertNode(data, InsertPos); } @@ -283,8 +286,7 @@ const IntSymExpr *SymbolManager::getIntSymExpr(const llvm::APSInt& lhs, SymExpr *data = DataSet.FindNodeOrInsertPos(ID, InsertPos); if (!data) { - data = (IntSymExpr*) BPAlloc.Allocate<IntSymExpr>(); - new (data) IntSymExpr(lhs, op, rhs, t); + data = new (BPAlloc) IntSymExpr(lhs, op, rhs, t); DataSet.InsertNode(data, InsertPos); } @@ -301,14 +303,28 @@ const SymSymExpr *SymbolManager::getSymSymExpr(const SymExpr *lhs, SymExpr *data = DataSet.FindNodeOrInsertPos(ID, InsertPos); if (!data) { - data = (SymSymExpr*) BPAlloc.Allocate<SymSymExpr>(); - new (data) SymSymExpr(lhs, op, rhs, t); + data = new (BPAlloc) SymSymExpr(lhs, op, rhs, t); DataSet.InsertNode(data, InsertPos); } return cast<SymSymExpr>(data); } +const UnarySymExpr *SymbolManager::getUnarySymExpr(const SymExpr *Operand, + UnaryOperator::Opcode Opc, + QualType T) { + llvm::FoldingSetNodeID ID; + UnarySymExpr::Profile(ID, Operand, Opc, T); + void *InsertPos; + SymExpr *data = DataSet.FindNodeOrInsertPos(ID, InsertPos); + if (!data) { + data = new (BPAlloc) UnarySymExpr(Operand, Opc, T); + DataSet.InsertNode(data, InsertPos); + } + + return cast<UnarySymExpr>(data); +} + QualType SymbolConjured::getType() const { return T; } @@ -372,7 +388,7 @@ void SymbolReaper::markDependentsLive(SymbolRef sym) { if (const SymbolRefSmallVectorTy *Deps = SymMgr.getDependentSymbols(sym)) { for (const auto I : *Deps) { - if (TheLiving.find(I) != TheLiving.end()) + if (TheLiving.contains(I)) continue; markLive(I); } @@ -385,17 +401,21 @@ void SymbolReaper::markLive(SymbolRef sym) { } void SymbolReaper::markLive(const MemRegion *region) { - RegionRoots.insert(region->getBaseRegion()); + LiveRegionRoots.insert(region->getBaseRegion()); markElementIndicesLive(region); } +void SymbolReaper::markLazilyCopied(const clang::ento::MemRegion *region) { + LazilyCopiedRegionRoots.insert(region->getBaseRegion()); +} + void SymbolReaper::markElementIndicesLive(const MemRegion *region) { for (auto SR = dyn_cast<SubRegion>(region); SR; SR = dyn_cast<SubRegion>(SR->getSuperRegion())) { if (const auto ER = dyn_cast<ElementRegion>(SR)) { SVal Idx = ER->getIndex(); - for (auto SI = Idx.symbol_begin(), SE = Idx.symbol_end(); SI != SE; ++SI) - markLive(*SI); + for (SymbolRef Sym : Idx.symbols()) + markLive(Sym); } } } @@ -411,8 +431,7 @@ bool SymbolReaper::isLiveRegion(const MemRegion *MR) { // is not used later in the path, we can diagnose a leak of a value within // that field earlier than, say, the variable that contains the field dies. MR = MR->getBaseRegion(); - - if (RegionRoots.count(MR)) + if (LiveRegionRoots.count(MR)) return true; if (const auto *SR = dyn_cast<SymbolicRegion>(MR)) @@ -425,19 +444,16 @@ bool SymbolReaper::isLiveRegion(const MemRegion *MR) { // tell if anything still refers to this region. Unlike SymbolicRegions, // AllocaRegions don't have associated symbols, though, so we don't actually // have a way to track their liveness. - if (isa<AllocaRegion>(MR)) - return true; - - if (isa<CXXThisRegion>(MR)) - return true; - - if (isa<MemSpaceRegion>(MR)) - return true; + return isa<AllocaRegion, CXXThisRegion, MemSpaceRegion, CodeTextRegion>(MR); +} - if (isa<CodeTextRegion>(MR)) - return true; +bool SymbolReaper::isLazilyCopiedRegion(const MemRegion *MR) const { + // TODO: See comment in isLiveRegion. + return LazilyCopiedRegionRoots.count(MR->getBaseRegion()); +} - return false; +bool SymbolReaper::isReadableRegion(const MemRegion *MR) { + return isLiveRegion(MR) || isLazilyCopiedRegion(MR); } bool SymbolReaper::isLive(SymbolRef sym) { @@ -450,7 +466,7 @@ bool SymbolReaper::isLive(SymbolRef sym) { switch (sym->getKind()) { case SymExpr::SymbolRegionValueKind: - KnownLive = isLiveRegion(cast<SymbolRegionValue>(sym)->getRegion()); + KnownLive = isReadableRegion(cast<SymbolRegionValue>(sym)->getRegion()); break; case SymExpr::SymbolConjuredKind: KnownLive = false; @@ -480,6 +496,9 @@ bool SymbolReaper::isLive(SymbolRef sym) { case SymExpr::SymbolCastKind: KnownLive = isLive(cast<SymbolCast>(sym)->getOperand()); break; + case SymExpr::UnarySymExprKind: + KnownLive = isLive(cast<UnarySymExpr>(sym)->getOperand()); + break; } if (KnownLive) diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp index 4f3be7cae331..71268af22e24 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp @@ -31,8 +31,8 @@ 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 +/// Emits minimal diagnostics (report message + notes) for the 'none' output +/// type to the standard error, or to complement many others. Emits detailed /// diagnostics in textual format for the 'text' output type. class TextDiagnostics : public PathDiagnosticConsumer { PathDiagnosticConsumerOptions DiagOpts; @@ -86,10 +86,7 @@ public: } }; - for (std::vector<const PathDiagnostic *>::iterator I = Diags.begin(), - E = Diags.end(); - I != E; ++I) { - const PathDiagnostic *PD = *I; + for (const PathDiagnostic *PD : Diags) { std::string WarningMsg = (DiagOpts.ShouldDisplayDiagnosticName ? " [" + PD->getCheckerName() + "]" : "") @@ -129,7 +126,7 @@ public: Rewriter Rewrite(SM, LO); if (!applyAllReplacements(Repls, Rewrite)) { - llvm::errs() << "An error occured during applying fix-it.\n"; + llvm::errs() << "An error occurred during applying fix-it.\n"; } Rewrite.overwriteChangedFiles(); diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/WorkList.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/WorkList.cpp index 348552ba73a9..7042a9020837 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/WorkList.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/WorkList.cpp @@ -205,12 +205,6 @@ class UnexploredFirstPriorityQueue : public WorkList { using QueuePriority = std::pair<int, unsigned long>; using QueueItem = std::pair<WorkListUnit, QueuePriority>; - struct ExplorationComparator { - bool operator() (const QueueItem &LHS, const QueueItem &RHS) { - return LHS.second < RHS.second; - } - }; - // Number of inserted nodes, used to emulate DFS ordering in the priority // queue when insertions are equal. unsigned long Counter = 0; @@ -219,7 +213,7 @@ class UnexploredFirstPriorityQueue : public WorkList { VisitedTimesMap NumReached; // The top item is the largest one. - llvm::PriorityQueue<QueueItem, std::vector<QueueItem>, ExplorationComparator> + llvm::PriorityQueue<QueueItem, std::vector<QueueItem>, llvm::less_second> queue; public: @@ -267,12 +261,6 @@ class UnexploredFirstPriorityLocationQueue : public WorkList { using QueuePriority = std::pair<int, unsigned long>; using QueueItem = std::pair<WorkListUnit, QueuePriority>; - struct ExplorationComparator { - bool operator() (const QueueItem &LHS, const QueueItem &RHS) { - return LHS.second < RHS.second; - } - }; - // Number of inserted nodes, used to emulate DFS ordering in the priority // queue when insertions are equal. unsigned long Counter = 0; @@ -281,7 +269,7 @@ class UnexploredFirstPriorityLocationQueue : public WorkList { VisitedTimesMap NumReached; // The top item is the largest one. - llvm::PriorityQueue<QueueItem, std::vector<QueueItem>, ExplorationComparator> + llvm::PriorityQueue<QueueItem, std::vector<QueueItem>, llvm::less_second> queue; public: diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index 31de49033ac2..b6ef40595e3c 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -27,7 +27,6 @@ #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" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -35,6 +34,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "llvm/ADT/PostOrderIterator.h" +#include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/Statistic.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" @@ -87,7 +87,7 @@ public: ASTContext *Ctx; Preprocessor &PP; const std::string OutDir; - AnalyzerOptionsRef Opts; + AnalyzerOptions &Opts; ArrayRef<std::string> Plugins; CodeInjector *Injector; cross_tu::CrossTranslationUnitContext CTU; @@ -121,15 +121,15 @@ public: FunctionSummariesTy FunctionSummaries; AnalysisConsumer(CompilerInstance &CI, const std::string &outdir, - AnalyzerOptionsRef opts, ArrayRef<std::string> plugins, + AnalyzerOptions &opts, ArrayRef<std::string> plugins, CodeInjector *injector) : RecVisitorMode(0), RecVisitorBR(nullptr), Ctx(nullptr), - PP(CI.getPreprocessor()), OutDir(outdir), Opts(std::move(opts)), + PP(CI.getPreprocessor()), OutDir(outdir), Opts(opts), Plugins(plugins), Injector(injector), CTU(CI), MacroExpansions(CI.getLangOpts()) { DigestAnalyzerOptions(); - if (Opts->AnalyzerDisplayProgress || Opts->PrintStats || - Opts->ShouldSerializeStats) { + if (Opts.AnalyzerDisplayProgress || Opts.PrintStats || + Opts.ShouldSerializeStats) { AnalyzerTimers = std::make_unique<llvm::TimerGroup>( "analyzer", "Analyzer timers"); SyntaxCheckTimer = std::make_unique<llvm::Timer>( @@ -141,27 +141,27 @@ public: *AnalyzerTimers); } - if (Opts->PrintStats || Opts->ShouldSerializeStats) { - llvm::EnableStatistics(/* PrintOnExit= */ false); + if (Opts.PrintStats || Opts.ShouldSerializeStats) { + llvm::EnableStatistics(/* DoPrintOnExit= */ false); } - if (Opts->ShouldDisplayMacroExpansions) + if (Opts.ShouldDisplayMacroExpansions) MacroExpansions.registerForPreprocessor(PP); } ~AnalysisConsumer() override { - if (Opts->PrintStats) { + if (Opts.PrintStats) { llvm::PrintStatistics(); } } void DigestAnalyzerOptions() { - switch (Opts->AnalysisDiagOpt) { + switch (Opts.AnalysisDiagOpt) { case PD_NONE: break; #define ANALYSIS_DIAGNOSTICS(NAME, CMDFLAG, DESC, CREATEFN) \ case PD_##NAME: \ - CREATEFN(Opts->getDiagOpts(), PathConsumers, OutDir, PP, CTU, \ + CREATEFN(Opts.getDiagOpts(), PathConsumers, OutDir, PP, CTU, \ MacroExpansions); \ break; #include "clang/StaticAnalyzer/Core/Analyses.def" @@ -170,15 +170,9 @@ public: } // Create the analyzer component creators. - switch (Opts->AnalysisStoreOpt) { - default: - llvm_unreachable("Unknown store manager."); -#define ANALYSIS_STORE(NAME, CMDFLAG, DESC, CREATEFN) \ - case NAME##Model: CreateStoreMgr = CREATEFN; break; -#include "clang/StaticAnalyzer/Core/Analyses.def" - } + CreateStoreMgr = &CreateRegionStoreManager; - switch (Opts->AnalysisConstraintsOpt) { + switch (Opts.AnalysisConstraintsOpt) { default: llvm_unreachable("Unknown constraint manager."); #define ANALYSIS_CONSTRAINTS(NAME, CMDFLAG, DESC, CREATEFN) \ @@ -188,7 +182,7 @@ public: } void DisplayTime(llvm::TimeRecord &Time) { - if (!Opts->AnalyzerDisplayProgress) { + if (!Opts.AnalyzerDisplayProgress) { return; } llvm::errs() << " : " << llvm::format("%1.1f", Time.getWallTime() * 1000) @@ -197,7 +191,7 @@ public: void DisplayFunction(const Decl *D, AnalysisMode Mode, ExprEngine::InliningModes IMode) { - if (!Opts->AnalyzerDisplayProgress) + if (!Opts.AnalyzerDisplayProgress) return; SourceManager &SM = Mgr->getASTContext().getSourceManager(); @@ -228,12 +222,12 @@ public: void Initialize(ASTContext &Context) override { Ctx = &Context; - checkerMgr = std::make_unique<CheckerManager>(*Ctx, *Opts, PP, Plugins, + checkerMgr = std::make_unique<CheckerManager>(*Ctx, Opts, PP, Plugins, CheckerRegistrationFns); Mgr = std::make_unique<AnalysisManager>(*Ctx, PP, PathConsumers, CreateStoreMgr, CreateConstraintMgr, - checkerMgr.get(), *Opts, Injector); + checkerMgr.get(), Opts, Injector); } /// Store the top level decls in the set to be processed later on. @@ -284,11 +278,11 @@ public: } bool VisitVarDecl(VarDecl *VD) { - if (!Opts->IsNaiveCTUEnabled) + if (!Opts.IsNaiveCTUEnabled) return true; if (VD->hasExternalStorage() || VD->isStaticDataMember()) { - if (!cross_tu::containsConst(VD, *Ctx)) + if (!cross_tu::shouldImport(VD, *Ctx)) return true; } else { // Cannot be initialized in another TU. @@ -299,8 +293,8 @@ public: return true; llvm::Expected<const VarDecl *> CTUDeclOrError = - CTU.getCrossTUDefinition(VD, Opts->CTUDir, Opts->CTUIndexName, - Opts->DisplayCTUProgress); + CTU.getCrossTUDefinition(VD, Opts.CTUDir, Opts.CTUIndexName, + Opts.DisplayCTUProgress); if (!CTUDeclOrError) { handleAllErrors(CTUDeclOrError.takeError(), @@ -314,7 +308,7 @@ public: bool VisitFunctionDecl(FunctionDecl *FD) { IdentifierInfo *II = FD->getIdentifier(); - if (II && II->getName().startswith("__inline")) + if (II && II->getName().starts_with("__inline")) return true; // We skip function template definitions, as their semantics is @@ -357,13 +351,12 @@ public: private: void storeTopLevelDecls(DeclGroupRef DG); - std::string getFunctionName(const Decl *D); /// Check if we should skip (not analyze) the given function. AnalysisMode getModeForDecl(Decl *D, AnalysisMode Mode); void runAnalysisOnTranslationUnit(ASTContext &C); - /// Print \p S to stderr if \c Opts->AnalyzerDisplayProgress is set. + /// Print \p S to stderr if \c Opts.AnalyzerDisplayProgress is set. void reportAnalyzerProgress(StringRef S); }; // namespace } // end anonymous namespace @@ -382,14 +375,14 @@ void AnalysisConsumer::HandleTopLevelDeclInObjCContainer(DeclGroupRef DG) { } void AnalysisConsumer::storeTopLevelDecls(DeclGroupRef DG) { - for (DeclGroupRef::iterator I = DG.begin(), E = DG.end(); I != E; ++I) { + for (auto &I : DG) { // Skip ObjCMethodDecl, wait for the objc container to avoid // analyzing twice. - if (isa<ObjCMethodDecl>(*I)) + if (isa<ObjCMethodDecl>(I)) continue; - LocalTUDecls.push_back(*I); + LocalTUDecls.push_back(I); } } @@ -461,11 +454,9 @@ void AnalysisConsumer::HandleDeclsCallGraph(const unsigned LocalTUDeclsSize) { SetOfConstDecls Visited; SetOfConstDecls VisitedAsTopLevel; llvm::ReversePostOrderTraversal<clang::CallGraph*> RPOT(&CG); - for (llvm::ReversePostOrderTraversal<clang::CallGraph*>::rpo_iterator - I = RPOT.begin(), E = RPOT.end(); I != E; ++I) { + for (auto &N : RPOT) { NumFunctionTopLevel++; - CallGraphNode *N = *I; Decl *D = N->getDecl(); // Skip the abstract root node. @@ -477,6 +468,18 @@ void AnalysisConsumer::HandleDeclsCallGraph(const unsigned LocalTUDeclsSize) { if (shouldSkipFunction(D, Visited, VisitedAsTopLevel)) continue; + // The CallGraph might have declarations as callees. However, during CTU + // the declaration might form a declaration chain with the newly imported + // definition from another TU. In this case we don't want to analyze the + // function definition as toplevel. + if (const auto *FD = dyn_cast<FunctionDecl>(D)) { + // Calling 'hasBody' replaces 'FD' in place with the FunctionDecl + // that has the body. + FD->hasBody(FD); + if (CTU.isImportedAsNew(FD)) + continue; + } + // Analyze the function. SetOfConstDecls VisitedCallees; @@ -493,13 +496,33 @@ void AnalysisConsumer::HandleDeclsCallGraph(const unsigned LocalTUDeclsSize) { } } -static bool isBisonFile(ASTContext &C) { +static bool fileContainsString(StringRef Substring, ASTContext &C) { const SourceManager &SM = C.getSourceManager(); FileID FID = SM.getMainFileID(); StringRef Buffer = SM.getBufferOrFake(FID).getBuffer(); - if (Buffer.startswith("/* A Bison parser, made by")) - return true; - return false; + return Buffer.contains(Substring); +} + +static void reportAnalyzerFunctionMisuse(const AnalyzerOptions &Opts, + const ASTContext &Ctx) { + llvm::errs() << "Every top-level function was skipped.\n"; + + if (!Opts.AnalyzerDisplayProgress) + llvm::errs() << "Pass the -analyzer-display-progress for tracking which " + "functions are analyzed.\n"; + + bool HasBrackets = + Opts.AnalyzeSpecificFunction.find("(") != std::string::npos; + + if (Ctx.getLangOpts().CPlusPlus && !HasBrackets) { + llvm::errs() + << "For analyzing C++ code you need to pass the function parameter " + "list: -analyze-function=\"foobar(int, _Bool)\"\n"; + } else if (!Ctx.getLangOpts().CPlusPlus && HasBrackets) { + llvm::errs() << "For analyzing C code you shouldn't pass the function " + "parameter list, only the name of the function: " + "-analyze-function=foobar\n"; + } } void AnalysisConsumer::runAnalysisOnTranslationUnit(ASTContext &C) { @@ -538,52 +561,70 @@ void AnalysisConsumer::runAnalysisOnTranslationUnit(ASTContext &C) { BR.FlushReports(); RecVisitorBR = nullptr; + + // If the user wanted to analyze a specific function and the number of basic + // blocks analyzed is zero, than the user might not specified the function + // name correctly. + // FIXME: The user might have analyzed the requested function in Syntax mode, + // but we are unaware of that. + if (!Opts.AnalyzeSpecificFunction.empty() && NumFunctionsAnalyzed == 0) + reportAnalyzerFunctionMisuse(Opts, *Ctx); } void AnalysisConsumer::reportAnalyzerProgress(StringRef S) { - if (Opts->AnalyzerDisplayProgress) + if (Opts.AnalyzerDisplayProgress) llvm::errs() << S; } void AnalysisConsumer::HandleTranslationUnit(ASTContext &C) { - // Don't run the actions if an error has occurred with parsing the file. DiagnosticsEngine &Diags = PP.getDiagnostics(); if (Diags.hasErrorOccurred() || Diags.hasFatalErrorOccurred()) return; - if (isBisonFile(C)) { + // Explicitly destroy the PathDiagnosticConsumer. This will flush its output. + // FIXME: This should be replaced with something that doesn't rely on + // side-effects in PathDiagnosticConsumer's destructor. This is required when + // used with option -disable-free. + const auto DiagFlusherScopeExit = + llvm::make_scope_exit([this] { Mgr.reset(); }); + + if (Opts.ShouldIgnoreBisonGeneratedFiles && + fileContainsString("/* A Bison parser, made by", C)) { reportAnalyzerProgress("Skipping bison-generated file\n"); - } else if (Opts->DisableAllCheckers) { + return; + } - // Don't analyze if the user explicitly asked for no checks to be performed - // on this file. + if (Opts.ShouldIgnoreFlexGeneratedFiles && + fileContainsString("/* A lexical scanner generated by flex", C)) { + reportAnalyzerProgress("Skipping flex-generated file\n"); + return; + } + + // Don't analyze if the user explicitly asked for no checks to be performed + // on this file. + if (Opts.DisableAllCheckers) { reportAnalyzerProgress("All checks are disabled using a supplied option\n"); - } else { - // Otherwise, just run the analysis. - runAnalysisOnTranslationUnit(C); + return; } + // Otherwise, just run the analysis. + runAnalysisOnTranslationUnit(C); + // Count how many basic blocks we have not covered. NumBlocksInAnalyzedFunctions = FunctionSummaries.getTotalNumBasicBlocks(); NumVisitedBlocksInAnalyzedFunctions = FunctionSummaries.getTotalNumVisitedBasicBlocks(); if (NumBlocksInAnalyzedFunctions > 0) PercentReachableBlocks = - (FunctionSummaries.getTotalNumVisitedBasicBlocks() * 100) / + (FunctionSummaries.getTotalNumVisitedBasicBlocks() * 100) / NumBlocksInAnalyzedFunctions; - - // Explicitly destroy the PathDiagnosticConsumer. This will flush its output. - // FIXME: This should be replaced with something that doesn't rely on - // side-effects in PathDiagnosticConsumer's destructor. This is required when - // used with option -disable-free. - Mgr.reset(); } AnalysisConsumer::AnalysisMode AnalysisConsumer::getModeForDecl(Decl *D, AnalysisMode Mode) { - if (!Opts->AnalyzeSpecificFunction.empty() && - AnalysisDeclContext::getFunctionName(D) != Opts->AnalyzeSpecificFunction) + if (!Opts.AnalyzeSpecificFunction.empty() && + AnalysisDeclContext::getFunctionName(D) != Opts.AnalyzeSpecificFunction) return AM_None; // Unless -analyze-all is specified, treat decls differently depending on @@ -591,16 +632,24 @@ AnalysisConsumer::getModeForDecl(Decl *D, AnalysisMode Mode) { // - Main source file: run both path-sensitive and non-path-sensitive checks. // - Header files: run non-path-sensitive checks only. // - System headers: don't run any checks. - SourceManager &SM = Ctx->getSourceManager(); - const Stmt *Body = D->getBody(); - SourceLocation SL = Body ? Body->getBeginLoc() : D->getLocation(); - SL = SM.getExpansionLoc(SL); - - if (!Opts->AnalyzeAll && !Mgr->isInCodeFile(SL)) { - if (SL.isInvalid() || SM.isInSystemHeader(SL)) - return AM_None; + if (Opts.AnalyzeAll) + return Mode; + + const SourceManager &SM = Ctx->getSourceManager(); + + const SourceLocation Loc = [&SM](Decl *D) -> SourceLocation { + const Stmt *Body = D->getBody(); + SourceLocation SL = Body ? Body->getBeginLoc() : D->getLocation(); + return SM.getExpansionLoc(SL); + }(D); + + // Ignore system headers. + if (Loc.isInvalid() || SM.isInSystemHeader(Loc)) + return AM_None; + + // Disable path sensitive analysis in user-headers. + if (!Mgr->isInCodeFile(Loc)) return Mode & ~AM_Path; - } return Mode; } @@ -708,8 +757,8 @@ ento::CreateAnalysisConsumer(CompilerInstance &CI) { // Disable the effects of '-Werror' when using the AnalysisConsumer. CI.getPreprocessor().getDiagnostics().setWarningsAsErrors(false); - AnalyzerOptionsRef analyzerOpts = CI.getAnalyzerOpts(); - bool hasModelPath = analyzerOpts->Config.count("model-path") > 0; + AnalyzerOptions &analyzerOpts = CI.getAnalyzerOpts(); + bool hasModelPath = analyzerOpts.Config.count("model-path") > 0; return std::make_unique<AnalysisConsumer>( CI, CI.getFrontendOpts().OutputFile, analyzerOpts, diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/AnalyzerHelpFlags.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/AnalyzerHelpFlags.cpp index eb6014a0629d..ea75c794f0b7 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/AnalyzerHelpFlags.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/AnalyzerHelpFlags.cpp @@ -30,18 +30,18 @@ void ento::printCheckerHelp(raw_ostream &out, CompilerInstance &CI) { out << "USAGE: -analyzer-checker <CHECKER or PACKAGE,...>\n\n"; auto CheckerMgr = std::make_unique<CheckerManager>( - *CI.getAnalyzerOpts(), CI.getLangOpts(), CI.getDiagnostics(), + CI.getAnalyzerOpts(), CI.getLangOpts(), CI.getDiagnostics(), CI.getFrontendOpts().Plugins); CheckerMgr->getCheckerRegistryData().printCheckerWithDescList( - *CI.getAnalyzerOpts(), out); + CI.getAnalyzerOpts(), out); } void ento::printEnabledCheckerList(raw_ostream &out, CompilerInstance &CI) { out << "OVERVIEW: Clang Static Analyzer Enabled Checkers List\n\n"; auto CheckerMgr = std::make_unique<CheckerManager>( - *CI.getAnalyzerOpts(), CI.getLangOpts(), CI.getDiagnostics(), + CI.getAnalyzerOpts(), CI.getLangOpts(), CI.getDiagnostics(), CI.getFrontendOpts().Plugins); CheckerMgr->getCheckerRegistryData().printEnabledCheckerList(out); @@ -50,11 +50,11 @@ void ento::printEnabledCheckerList(raw_ostream &out, CompilerInstance &CI) { void ento::printCheckerConfigList(raw_ostream &out, CompilerInstance &CI) { auto CheckerMgr = std::make_unique<CheckerManager>( - *CI.getAnalyzerOpts(), CI.getLangOpts(), CI.getDiagnostics(), + CI.getAnalyzerOpts(), CI.getLangOpts(), CI.getDiagnostics(), CI.getFrontendOpts().Plugins); CheckerMgr->getCheckerRegistryData().printCheckerOptionList( - *CI.getAnalyzerOpts(), out); + CI.getAnalyzerOpts(), out); } void ento::printAnalyzerConfigList(raw_ostream &out) { @@ -101,10 +101,7 @@ OPTIONS: #undef ANALYZER_OPTION_DEPENDS_ON_USER_MODE }; - llvm::sort(PrintableOptions, [](const OptionAndDescriptionTy &LHS, - const OptionAndDescriptionTy &RHS) { - return LHS.first < RHS.first; - }); + llvm::sort(PrintableOptions, llvm::less_first()); for (const auto &Pair : PrintableOptions) { AnalyzerOptions::printFormattedEntry(out, Pair, /*InitialPad*/ 2, diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp index 528284ca8985..317df90a7781 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp @@ -15,7 +15,6 @@ #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/SetVector.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/DynamicLibrary.h" @@ -234,7 +233,7 @@ void CheckerRegistry::initializeRegistry(const CheckerManager &Mgr) { // 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); + return Tmp.contains(Checker); }; for (const CheckerInfo &Checker : Data.Checkers) { if (!Checker.isEnabled(Mgr)) @@ -311,8 +310,8 @@ template <bool IsWeak> void CheckerRegistry::resolveDependencies() { "Failed to find the dependency of a checker!"); // We do allow diagnostics from unit test/example dependency checkers. - assert((DependencyIt->FullName.startswith("test") || - DependencyIt->FullName.startswith("example") || IsWeak || + assert((DependencyIt->FullName.starts_with("test") || + DependencyIt->FullName.starts_with("example") || IsWeak || DependencyIt->IsHidden) && "Strong dependencies are modeling checkers, and as such " "non-user facing! Mark them hidden in Checkers.td!"); @@ -480,9 +479,7 @@ static void isOptionContainedIn(const CmdLineOptionList &OptionList, return Opt.OptionName == SuppliedOption; }; - const auto *OptionIt = llvm::find_if(OptionList, SameOptName); - - if (OptionIt == OptionList.end()) { + if (llvm::none_of(OptionList, SameOptName)) { Diags.Report(diag::err_analyzer_checker_option_unknown) << SuppliedChecker << SuppliedOption; return; @@ -528,4 +525,3 @@ void CheckerRegistry::validateCheckerOptions() const { << SuppliedCheckerOrPackage; } } - diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp index 276f7313b08f..0f1039d81d52 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/ModelConsumer.cpp @@ -28,11 +28,10 @@ using namespace ento; ModelConsumer::ModelConsumer(llvm::StringMap<Stmt *> &Bodies) : Bodies(Bodies) {} -bool ModelConsumer::HandleTopLevelDecl(DeclGroupRef D) { - for (DeclGroupRef::iterator I = D.begin(), E = D.end(); I != E; ++I) { - +bool ModelConsumer::HandleTopLevelDecl(DeclGroupRef DeclGroup) { + for (const Decl *D : DeclGroup) { // Only interested in definitions. - const FunctionDecl *func = llvm::dyn_cast<FunctionDecl>(*I); + const auto *func = llvm::dyn_cast<FunctionDecl>(D); if (func && func->hasBody()) { Bodies.insert(std::make_pair(func->getName(), func->getBody())); } diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/ModelInjector.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/ModelInjector.cpp index 7baae6778ebd..ae11fbbe32b7 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/ModelInjector.cpp +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/ModelInjector.cpp @@ -48,8 +48,7 @@ void ModelInjector::onBodySynthesis(const NamedDecl *D) { SourceManager &SM = CI.getSourceManager(); FileID mainFileID = SM.getMainFileID(); - AnalyzerOptionsRef analyzerOpts = CI.getAnalyzerOpts(); - llvm::StringRef modelPath = analyzerOpts->ModelPath; + llvm::StringRef modelPath = CI.getAnalyzerOpts().ModelPath; llvm::SmallString<128> fileName; diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/ModelInjector.h b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/ModelInjector.h index d2016c3b112c..4db26028362f 100644 --- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/ModelInjector.h +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Frontend/ModelInjector.h @@ -29,10 +29,7 @@ namespace clang { class CompilerInstance; -class ASTUnit; -class ASTReader; class NamedDecl; -class Module; namespace ento { class ModelInjector : public CodeInjector { |