diff options
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp')
-rw-r--r-- | clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp | 2223 |
1 files changed, 1463 insertions, 760 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp index 2cdee8da375e..8b575f4f4759 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp @@ -7,9 +7,8 @@ //===----------------------------------------------------------------------===// // // This checker improves modeling of a few simple library functions. -// It does not generate warnings. // -// This checker provides a specification format - `FunctionSummaryTy' - and +// This checker provides a specification format - `Summary' - and // contains descriptions of some library functions in this format. Each // specification contains a list of branches for splitting the program state // upon call, and range constraints on argument and return-value symbols that @@ -21,7 +20,7 @@ // consider standard C function `ispunct(int x)', which returns a non-zero value // iff `x' is a punctuation character, that is, when `x' is in range // ['!', '/'] [':', '@'] U ['[', '\`'] U ['{', '~']. -// `FunctionSummaryTy' provides only two branches for this function. However, +// `Summary' provides only two branches for this function. However, // any attempt to describe this range with if-statements in the body farm // would result in many more branches. Because each branch needs to be analyzed // independently, this significantly reduces performance. Additionally, @@ -30,13 +29,13 @@ // which may lead to false positives because considering this particular path // was not consciously intended, and therefore it might have been unreachable. // -// This checker uses eval::Call for modeling "pure" functions, for which -// their `FunctionSummaryTy' is a precise model. This avoids unnecessary -// invalidation passes. Conflicts with other checkers are unlikely because -// if the function has no other effects, other checkers would probably never -// want to improve upon the modeling done by this checker. +// This checker uses eval::Call for modeling pure functions (functions without +// side effets), for which their `Summary' is a precise model. This avoids +// unnecessary invalidation passes. Conflicts with other checkers are unlikely +// because if the function has no other effects, other checkers would probably +// never want to improve upon the modeling done by this checker. // -// Non-"pure" functions, for which only partial improvement over the default +// Non-pure functions, for which only partial improvement over the default // behavior is expected, are modeled via check::PostCall, non-intrusively. // // The following standard C functions are currently supported: @@ -51,203 +50,461 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" using namespace clang; using namespace clang::ento; namespace { -class StdLibraryFunctionsChecker : public Checker<check::PostCall, eval::Call> { - /// Below is a series of typedefs necessary to define function specs. - /// We avoid nesting types here because each additional qualifier - /// would need to be repeated in every function spec. - struct FunctionSummaryTy; +class StdLibraryFunctionsChecker + : public Checker<check::PreCall, check::PostCall, eval::Call> { + + class Summary; /// Specify how much the analyzer engine should entrust modeling this function /// to us. If he doesn't, he performs additional invalidations. - enum InvalidationKindTy { NoEvalCall, EvalCallAsPure }; - - /// A pair of ValueRangeKindTy and IntRangeVectorTy would describe a range - /// imposed on a particular argument or return value symbol. - /// - /// Given a range, should the argument stay inside or outside this range? - /// The special `ComparesToArgument' value indicates that we should - /// impose a constraint that involves other argument or return value symbols. - enum ValueRangeKindTy { OutOfRange, WithinRange, ComparesToArgument }; + enum InvalidationKind { NoEvalCall, EvalCallAsPure }; // The universal integral type to use in value range descriptions. // Unsigned to make sure overflows are well-defined. - typedef uint64_t RangeIntTy; + typedef uint64_t RangeInt; /// Normally, describes a single range constraint, eg. {{0, 1}, {3, 4}} is /// a non-negative integer, which less than 5 and not equal to 2. For /// `ComparesToArgument', holds information about how exactly to compare to /// the argument. - typedef std::vector<std::pair<RangeIntTy, RangeIntTy>> IntRangeVectorTy; + typedef std::vector<std::pair<RangeInt, RangeInt>> IntRangeVector; /// A reference to an argument or return value by its number. /// ArgNo in CallExpr and CallEvent is defined as Unsigned, but /// obviously uint32_t should be enough for all practical purposes. - typedef uint32_t ArgNoTy; - static const ArgNoTy Ret = std::numeric_limits<ArgNoTy>::max(); - - /// Incapsulates a single range on a single symbol within a branch. - class ValueRange { - ArgNoTy ArgNo; // Argument to which we apply the range. - ValueRangeKindTy Kind; // Kind of range definition. - IntRangeVectorTy Args; // Polymorphic arguments. - + typedef uint32_t ArgNo; + static const ArgNo Ret; + + class ValueConstraint; + + // Pointer to the ValueConstraint. We need a copyable, polymorphic and + // default initialize able type (vector needs that). A raw pointer was good, + // however, we cannot default initialize that. unique_ptr makes the Summary + // class non-copyable, therefore not an option. Releasing the copyability + // requirement would render the initialization of the Summary map infeasible. + using ValueConstraintPtr = std::shared_ptr<ValueConstraint>; + + /// Polymorphic base class that represents a constraint on a given argument + /// (or return value) of a function. Derived classes implement different kind + /// of constraints, e.g range constraints or correlation between two + /// arguments. + class ValueConstraint { public: - ValueRange(ArgNoTy ArgNo, ValueRangeKindTy Kind, - const IntRangeVectorTy &Args) - : ArgNo(ArgNo), Kind(Kind), Args(Args) {} - - ArgNoTy getArgNo() const { return ArgNo; } - ValueRangeKindTy getKind() const { return Kind; } - - BinaryOperator::Opcode getOpcode() const { - assert(Kind == ComparesToArgument); - assert(Args.size() == 1); - BinaryOperator::Opcode Op = - static_cast<BinaryOperator::Opcode>(Args[0].first); - assert(BinaryOperator::isComparisonOp(Op) && - "Only comparison ops are supported for ComparesToArgument"); - return Op; + ValueConstraint(ArgNo ArgN) : ArgN(ArgN) {} + virtual ~ValueConstraint() {} + /// Apply the effects of the constraint on the given program state. If null + /// is returned then the constraint is not feasible. + virtual ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const = 0; + virtual ValueConstraintPtr negate() const { + llvm_unreachable("Not implemented"); + }; + + // Check whether the constraint is malformed or not. It is malformed if the + // specified argument has a mismatch with the given FunctionDecl (e.g. the + // arg number is out-of-range of the function's argument list). + bool checkValidity(const FunctionDecl *FD) const { + const bool ValidArg = ArgN == Ret || ArgN < FD->getNumParams(); + assert(ValidArg && "Arg out of range!"); + if (!ValidArg) + return false; + // Subclasses may further refine the validation. + return checkSpecificValidity(FD); } + ArgNo getArgNo() const { return ArgN; } - ArgNoTy getOtherArgNo() const { - assert(Kind == ComparesToArgument); - assert(Args.size() == 1); - return static_cast<ArgNoTy>(Args[0].second); + protected: + ArgNo ArgN; // Argument to which we apply the constraint. + + /// Do polymorphic sanity check on the constraint. + virtual bool checkSpecificValidity(const FunctionDecl *FD) const { + return true; } + }; + + /// Given a range, should the argument stay inside or outside this range? + enum RangeKind { OutOfRange, WithinRange }; + + /// Encapsulates a single range on a single symbol within a branch. + class RangeConstraint : public ValueConstraint { + RangeKind Kind; // Kind of range definition. + IntRangeVector Args; // Polymorphic arguments. - const IntRangeVectorTy &getRanges() const { - assert(Kind != ComparesToArgument); + public: + RangeConstraint(ArgNo ArgN, RangeKind Kind, const IntRangeVector &Args) + : ValueConstraint(ArgN), Kind(Kind), Args(Args) {} + + const IntRangeVector &getRanges() const { return Args; } - // We avoid creating a virtual apply() method because - // it makes initializer lists harder to write. private: - ProgramStateRef - applyAsOutOfRange(ProgramStateRef State, const CallEvent &Call, - const FunctionSummaryTy &Summary) const; - ProgramStateRef - applyAsWithinRange(ProgramStateRef State, const CallEvent &Call, - const FunctionSummaryTy &Summary) const; - ProgramStateRef - applyAsComparesToArgument(ProgramStateRef State, const CallEvent &Call, - const FunctionSummaryTy &Summary) const; - + ProgramStateRef applyAsOutOfRange(ProgramStateRef State, + const CallEvent &Call, + const Summary &Summary) const; + ProgramStateRef applyAsWithinRange(ProgramStateRef State, + const CallEvent &Call, + const Summary &Summary) const; public: ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, - const FunctionSummaryTy &Summary) const { + const Summary &Summary, + CheckerContext &C) const override { switch (Kind) { case OutOfRange: return applyAsOutOfRange(State, Call, Summary); case WithinRange: return applyAsWithinRange(State, Call, Summary); - case ComparesToArgument: - return applyAsComparesToArgument(State, Call, Summary); } - llvm_unreachable("Unknown ValueRange kind!"); + llvm_unreachable("Unknown range kind!"); + } + + ValueConstraintPtr negate() const override { + RangeConstraint Tmp(*this); + switch (Kind) { + case OutOfRange: + Tmp.Kind = WithinRange; + break; + case WithinRange: + Tmp.Kind = OutOfRange; + break; + } + return std::make_shared<RangeConstraint>(Tmp); + } + + bool checkSpecificValidity(const FunctionDecl *FD) const override { + const bool ValidArg = + getArgType(FD, ArgN)->isIntegralType(FD->getASTContext()); + assert(ValidArg && + "This constraint should be applied on an integral type"); + return ValidArg; } }; - /// The complete list of ranges that defines a single branch. - typedef std::vector<ValueRange> ValueRangeSet; + class ComparisonConstraint : public ValueConstraint { + BinaryOperator::Opcode Opcode; + ArgNo OtherArgN; + + public: + ComparisonConstraint(ArgNo ArgN, BinaryOperator::Opcode Opcode, + ArgNo OtherArgN) + : ValueConstraint(ArgN), Opcode(Opcode), OtherArgN(OtherArgN) {} + ArgNo getOtherArgNo() const { return OtherArgN; } + BinaryOperator::Opcode getOpcode() const { return Opcode; } + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override; + }; + + class NotNullConstraint : public ValueConstraint { + using ValueConstraint::ValueConstraint; + // This variable has a role when we negate the constraint. + bool CannotBeNull = true; - /// Includes information about function prototype (which is necessary to - /// ensure we're modeling the right function and casting values properly), - /// approach to invalidation, and a list of branches - essentially, a list - /// of list of ranges - essentially, a list of lists of lists of segments. - struct FunctionSummaryTy { - const std::vector<QualType> ArgTypes; - const QualType RetType; - const InvalidationKindTy InvalidationKind; - const std::vector<ValueRangeSet> Ranges; + public: + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override { + SVal V = getArgSVal(Call, getArgNo()); + if (V.isUndef()) + return State; + + DefinedOrUnknownSVal L = V.castAs<DefinedOrUnknownSVal>(); + if (!L.getAs<Loc>()) + return State; + + return State->assume(L, CannotBeNull); + } + + ValueConstraintPtr negate() const override { + NotNullConstraint Tmp(*this); + Tmp.CannotBeNull = !this->CannotBeNull; + return std::make_shared<NotNullConstraint>(Tmp); + } + + bool checkSpecificValidity(const FunctionDecl *FD) const override { + const bool ValidArg = getArgType(FD, ArgN)->isPointerType(); + assert(ValidArg && + "This constraint should be applied only on a pointer type"); + return ValidArg; + } + }; + + // Represents a buffer argument with an additional size argument. + // E.g. the first two arguments here: + // ctime_s(char *buffer, rsize_t bufsz, const time_t *time); + // Another example: + // size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); + // // Here, ptr is the buffer, and its minimum size is `size * nmemb`. + class BufferSizeConstraint : public ValueConstraint { + // The argument which holds the size of the buffer. + ArgNo SizeArgN; + // The argument which is a multiplier to size. This is set in case of + // `fread` like functions where the size is computed as a multiplication of + // two arguments. + llvm::Optional<ArgNo> SizeMultiplierArgN; + // The operator we use in apply. This is negated in negate(). + BinaryOperator::Opcode Op = BO_LE; + + public: + BufferSizeConstraint(ArgNo Buffer, ArgNo BufSize) + : ValueConstraint(Buffer), SizeArgN(BufSize) {} + + BufferSizeConstraint(ArgNo Buffer, ArgNo BufSize, ArgNo BufSizeMultiplier) + : ValueConstraint(Buffer), SizeArgN(BufSize), + SizeMultiplierArgN(BufSizeMultiplier) {} + + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override { + SValBuilder &SvalBuilder = C.getSValBuilder(); + // The buffer argument. + SVal BufV = getArgSVal(Call, getArgNo()); + // The size argument. + SVal SizeV = getArgSVal(Call, SizeArgN); + // Multiply with another argument if given. + if (SizeMultiplierArgN) { + SVal SizeMulV = getArgSVal(Call, *SizeMultiplierArgN); + SizeV = SvalBuilder.evalBinOp(State, BO_Mul, SizeV, SizeMulV, + Summary.getArgType(SizeArgN)); + } + // The dynamic size of the buffer argument, got from the analyzer engine. + SVal BufDynSize = getDynamicSizeWithOffset(State, BufV); + + SVal Feasible = SvalBuilder.evalBinOp(State, Op, SizeV, BufDynSize, + SvalBuilder.getContext().BoolTy); + if (auto F = Feasible.getAs<DefinedOrUnknownSVal>()) + return State->assume(*F, true); + + // We can get here only if the size argument or the dynamic size is + // undefined. But the dynamic size should never be undefined, only + // unknown. So, here, the size of the argument is undefined, i.e. we + // cannot apply the constraint. Actually, other checkers like + // CallAndMessage should catch this situation earlier, because we call a + // function with an uninitialized argument. + llvm_unreachable("Size argument or the dynamic size is Undefined"); + } + + ValueConstraintPtr negate() const override { + BufferSizeConstraint Tmp(*this); + Tmp.Op = BinaryOperator::negateComparisonOp(Op); + return std::make_shared<BufferSizeConstraint>(Tmp); + } + }; + + /// The complete list of constraints that defines a single branch. + typedef std::vector<ValueConstraintPtr> ConstraintSet; + + using ArgTypes = std::vector<QualType>; + + // A placeholder type, we use it whenever we do not care about the concrete + // type in a Signature. + const QualType Irrelevant{}; + bool static isIrrelevant(QualType T) { return T.isNull(); } + + // The signature of a function we want to describe with a summary. This is a + // concessive signature, meaning there may be irrelevant types in the + // signature which we do not check against a function with concrete types. + struct Signature { + const ArgTypes ArgTys; + const QualType RetTy; + Signature(ArgTypes ArgTys, QualType RetTy) : ArgTys(ArgTys), RetTy(RetTy) { + assertRetTypeSuitableForSignature(RetTy); + for (size_t I = 0, E = ArgTys.size(); I != E; ++I) { + QualType ArgTy = ArgTys[I]; + assertArgTypeSuitableForSignature(ArgTy); + } + } + bool matches(const FunctionDecl *FD) const; private: - static void assertTypeSuitableForSummary(QualType T) { - assert(!T->isVoidType() && - "We should have had no significant void types in the spec"); - assert(T.isCanonical() && + static void assertArgTypeSuitableForSignature(QualType T) { + assert((T.isNull() || !T->isVoidType()) && + "We should have no void types in the spec"); + assert((T.isNull() || T.isCanonical()) && + "We should only have canonical types in the spec"); + } + static void assertRetTypeSuitableForSignature(QualType T) { + assert((T.isNull() || T.isCanonical()) && "We should only have canonical types in the spec"); - // FIXME: lift this assert (but not the ones above!) - assert(T->isIntegralOrEnumerationType() && - "We only support integral ranges in the spec"); } + }; + + static QualType getArgType(const FunctionDecl *FD, ArgNo ArgN) { + assert(FD && "Function must be set"); + QualType T = (ArgN == Ret) + ? FD->getReturnType().getCanonicalType() + : FD->getParamDecl(ArgN)->getType().getCanonicalType(); + return T; + } + + using Cases = std::vector<ConstraintSet>; + + /// A summary includes information about + /// * function prototype (signature) + /// * approach to invalidation, + /// * a list of branches - a list of list of ranges - + /// A branch represents a path in the exploded graph of a function (which + /// is a tree). So, a branch is a series of assumptions. In other words, + /// branches represent split states and additional assumptions on top of + /// the splitting assumption. + /// For example, consider the branches in `isalpha(x)` + /// Branch 1) + /// x is in range ['A', 'Z'] or in ['a', 'z'] + /// then the return value is not 0. (I.e. out-of-range [0, 0]) + /// Branch 2) + /// x is out-of-range ['A', 'Z'] and out-of-range ['a', 'z'] + /// then the return value is 0. + /// * a list of argument constraints, that must be true on every branch. + /// If these constraints are not satisfied that means a fatal error + /// usually resulting in undefined behaviour. + /// + /// Application of a summary: + /// The signature and argument constraints together contain information + /// about which functions are handled by the summary. The signature can use + /// "wildcards", i.e. Irrelevant types. Irrelevant type of a parameter in + /// a signature means that type is not compared to the type of the parameter + /// in the found FunctionDecl. Argument constraints may specify additional + /// rules for the given parameter's type, those rules are checked once the + /// signature is matched. + class Summary { + const Signature Sign; + const InvalidationKind InvalidationKd; + Cases CaseConstraints; + ConstraintSet ArgConstraints; + + // The function to which the summary applies. This is set after lookup and + // match to the signature. + const FunctionDecl *FD = nullptr; public: - QualType getArgType(ArgNoTy ArgNo) const { - QualType T = (ArgNo == Ret) ? RetType : ArgTypes[ArgNo]; - assertTypeSuitableForSummary(T); - return T; + Summary(ArgTypes ArgTys, QualType RetTy, InvalidationKind InvalidationKd) + : Sign(ArgTys, RetTy), InvalidationKd(InvalidationKd) {} + + Summary &Case(ConstraintSet&& CS) { + CaseConstraints.push_back(std::move(CS)); + return *this; + } + Summary &ArgConstraint(ValueConstraintPtr VC) { + ArgConstraints.push_back(VC); + return *this; } - /// Try our best to figure out if the call expression is the call of - /// *the* library function to which this specification applies. - bool matchesCall(const CallExpr *CE) const; - }; + InvalidationKind getInvalidationKd() const { return InvalidationKd; } + const Cases &getCaseConstraints() const { return CaseConstraints; } + const ConstraintSet &getArgConstraints() const { return ArgConstraints; } - // The same function (as in, function identifier) may have different - // summaries assigned to it, with different argument and return value types. - // We call these "variants" of the function. This can be useful for handling - // C++ function overloads, and also it can be used when the same function - // may have different definitions on different platforms. - typedef std::vector<FunctionSummaryTy> FunctionVariantsTy; + QualType getArgType(ArgNo ArgN) const { + return StdLibraryFunctionsChecker::getArgType(FD, ArgN); + } + + // Returns true if the summary should be applied to the given function. + // And if yes then store the function declaration. + bool matchesAndSet(const FunctionDecl *FD) { + bool Result = Sign.matches(FD) && validateByConstraints(FD); + if (Result) { + assert(!this->FD && "FD must not be set more than once"); + this->FD = FD; + } + return Result; + } + + private: + // Once we know the exact type of the function then do sanity check on all + // the given constraints. + bool validateByConstraints(const FunctionDecl *FD) const { + for (const ConstraintSet &Case : CaseConstraints) + for (const ValueConstraintPtr &Constraint : Case) + if (!Constraint->checkValidity(FD)) + return false; + for (const ValueConstraintPtr &Constraint : ArgConstraints) + if (!Constraint->checkValidity(FD)) + return false; + return true; + } + }; // The map of all functions supported by the checker. It is initialized // lazily, and it doesn't change after initialization. - typedef llvm::StringMap<FunctionVariantsTy> FunctionSummaryMapTy; - mutable FunctionSummaryMapTy FunctionSummaryMap; + using FunctionSummaryMapType = llvm::DenseMap<const FunctionDecl *, Summary>; + mutable FunctionSummaryMapType FunctionSummaryMap; - // Auxiliary functions to support ArgNoTy within all structures - // in a unified manner. - static QualType getArgType(const FunctionSummaryTy &Summary, ArgNoTy ArgNo) { - return Summary.getArgType(ArgNo); - } - static QualType getArgType(const CallEvent &Call, ArgNoTy ArgNo) { - return ArgNo == Ret ? Call.getResultType().getCanonicalType() - : Call.getArgExpr(ArgNo)->getType().getCanonicalType(); - } - static QualType getArgType(const CallExpr *CE, ArgNoTy ArgNo) { - return ArgNo == Ret ? CE->getType().getCanonicalType() - : CE->getArg(ArgNo)->getType().getCanonicalType(); - } - static SVal getArgSVal(const CallEvent &Call, ArgNoTy ArgNo) { - return ArgNo == Ret ? Call.getReturnValue() : Call.getArgSVal(ArgNo); + mutable std::unique_ptr<BugType> BT_InvalidArg; + + static SVal getArgSVal(const CallEvent &Call, ArgNo ArgN) { + return ArgN == Ret ? Call.getReturnValue() : Call.getArgSVal(ArgN); } public: + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; bool evalCall(const CallEvent &Call, CheckerContext &C) const; -private: - Optional<FunctionSummaryTy> findFunctionSummary(const FunctionDecl *FD, - const CallExpr *CE, - CheckerContext &C) const; + enum CheckKind { + CK_StdCLibraryFunctionArgsChecker, + CK_StdCLibraryFunctionsTesterChecker, + CK_NumCheckKinds + }; + DefaultBool ChecksEnabled[CK_NumCheckKinds]; + CheckerNameRef CheckNames[CK_NumCheckKinds]; + + bool DisplayLoadedSummaries = false; + bool ModelPOSIX = false; - void initFunctionSummaries(BasicValueFactory &BVF) const; +private: + Optional<Summary> findFunctionSummary(const FunctionDecl *FD, + CheckerContext &C) const; + Optional<Summary> findFunctionSummary(const CallEvent &Call, + CheckerContext &C) const; + + void initFunctionSummaries(CheckerContext &C) const; + + void reportBug(const CallEvent &Call, ExplodedNode *N, + CheckerContext &C) const { + if (!ChecksEnabled[CK_StdCLibraryFunctionArgsChecker]) + return; + // TODO Add detailed diagnostic. + StringRef Msg = "Function argument constraint is not satisfied"; + if (!BT_InvalidArg) + BT_InvalidArg = std::make_unique<BugType>( + CheckNames[CK_StdCLibraryFunctionArgsChecker], + "Unsatisfied argument constraints", categories::LogicError); + auto R = std::make_unique<PathSensitiveBugReport>(*BT_InvalidArg, Msg, N); + bugreporter::trackExpressionValue(N, Call.getArgExpr(0), *R); + C.emitReport(std::move(R)); + } }; + +const StdLibraryFunctionsChecker::ArgNo StdLibraryFunctionsChecker::Ret = + std::numeric_limits<ArgNo>::max(); + } // end of anonymous namespace -ProgramStateRef StdLibraryFunctionsChecker::ValueRange::applyAsOutOfRange( +ProgramStateRef StdLibraryFunctionsChecker::RangeConstraint::applyAsOutOfRange( ProgramStateRef State, const CallEvent &Call, - const FunctionSummaryTy &Summary) const { + const Summary &Summary) const { ProgramStateManager &Mgr = State->getStateManager(); SValBuilder &SVB = Mgr.getSValBuilder(); BasicValueFactory &BVF = SVB.getBasicValueFactory(); ConstraintManager &CM = Mgr.getConstraintManager(); - QualType T = getArgType(Summary, getArgNo()); + QualType T = Summary.getArgType(getArgNo()); SVal V = getArgSVal(Call, getArgNo()); if (auto N = V.getAs<NonLoc>()) { - const IntRangeVectorTy &R = getRanges(); + const IntRangeVector &R = getRanges(); size_t E = R.size(); for (size_t I = 0; I != E; ++I) { const llvm::APSInt &Min = BVF.getValue(R[I].first, T); @@ -262,23 +519,28 @@ ProgramStateRef StdLibraryFunctionsChecker::ValueRange::applyAsOutOfRange( return State; } -ProgramStateRef -StdLibraryFunctionsChecker::ValueRange::applyAsWithinRange( +ProgramStateRef StdLibraryFunctionsChecker::RangeConstraint::applyAsWithinRange( ProgramStateRef State, const CallEvent &Call, - const FunctionSummaryTy &Summary) const { + const Summary &Summary) const { ProgramStateManager &Mgr = State->getStateManager(); SValBuilder &SVB = Mgr.getSValBuilder(); BasicValueFactory &BVF = SVB.getBasicValueFactory(); ConstraintManager &CM = Mgr.getConstraintManager(); - QualType T = getArgType(Summary, getArgNo()); + QualType T = Summary.getArgType(getArgNo()); SVal V = getArgSVal(Call, getArgNo()); // "WithinRange R" is treated as "outside [T_MIN, T_MAX] \ R". // We cut off [T_MIN, min(R) - 1] and [max(R) + 1, T_MAX] if necessary, // and then cut away all holes in R one by one. + // + // E.g. consider a range list R as [A, B] and [C, D] + // -------+--------+------------------+------------+-----------> + // A B C D + // Then we assume that the value is not in [-inf, A - 1], + // then not in [D + 1, +inf], then not in [B + 1, C - 1] if (auto N = V.getAs<NonLoc>()) { - const IntRangeVectorTy &R = getRanges(); + const IntRangeVector &R = getRanges(); size_t E = R.size(); const llvm::APSInt &MinusInf = BVF.getMinValue(T); @@ -303,31 +565,31 @@ StdLibraryFunctionsChecker::ValueRange::applyAsWithinRange( for (size_t I = 1; I != E; ++I) { const llvm::APSInt &Min = BVF.getValue(R[I - 1].second + 1ULL, T); const llvm::APSInt &Max = BVF.getValue(R[I].first - 1ULL, T); - assert(Min <= Max); - State = CM.assumeInclusiveRange(State, *N, Min, Max, false); - if (!State) - return nullptr; + if (Min <= Max) { + State = CM.assumeInclusiveRange(State, *N, Min, Max, false); + if (!State) + return nullptr; + } } } return State; } -ProgramStateRef -StdLibraryFunctionsChecker::ValueRange::applyAsComparesToArgument( - ProgramStateRef State, const CallEvent &Call, - const FunctionSummaryTy &Summary) const { +ProgramStateRef StdLibraryFunctionsChecker::ComparisonConstraint::apply( + ProgramStateRef State, const CallEvent &Call, const Summary &Summary, + CheckerContext &C) const { ProgramStateManager &Mgr = State->getStateManager(); SValBuilder &SVB = Mgr.getSValBuilder(); QualType CondT = SVB.getConditionType(); - QualType T = getArgType(Summary, getArgNo()); + QualType T = Summary.getArgType(getArgNo()); SVal V = getArgSVal(Call, getArgNo()); BinaryOperator::Opcode Op = getOpcode(); - ArgNoTy OtherArg = getOtherArgNo(); + ArgNo OtherArg = getOtherArgNo(); SVal OtherV = getArgSVal(Call, OtherArg); - QualType OtherT = getArgType(Call, OtherArg); + QualType OtherT = Summary.getArgType(OtherArg); // Note: we avoid integral promotion for comparison. OtherV = SVB.evalCast(OtherV, T, OtherT); if (auto CompV = SVB.evalBinOp(State, Op, V, OtherV, CondT) @@ -336,28 +598,53 @@ StdLibraryFunctionsChecker::ValueRange::applyAsComparesToArgument( return State; } -void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call, - CheckerContext &C) const { - const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); - if (!FD) +void StdLibraryFunctionsChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + Optional<Summary> FoundSummary = findFunctionSummary(Call, C); + if (!FoundSummary) return; - const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); - if (!CE) - return; + const Summary &Summary = *FoundSummary; + ProgramStateRef State = C.getState(); + + ProgramStateRef NewState = State; + for (const ValueConstraintPtr &Constraint : Summary.getArgConstraints()) { + ProgramStateRef SuccessSt = Constraint->apply(NewState, Call, Summary, C); + ProgramStateRef FailureSt = + Constraint->negate()->apply(NewState, Call, Summary, C); + // The argument constraint is not satisfied. + if (FailureSt && !SuccessSt) { + if (ExplodedNode *N = C.generateErrorNode(NewState)) + reportBug(Call, N, C); + break; + } else { + // We will apply the constraint even if we cannot reason about the + // argument. This means both SuccessSt and FailureSt can be true. If we + // weren't applying the constraint that would mean that symbolic + // execution continues on a code whose behaviour is undefined. + assert(SuccessSt); + NewState = SuccessSt; + } + } + if (NewState && NewState != State) + C.addTransition(NewState); +} - Optional<FunctionSummaryTy> FoundSummary = findFunctionSummary(FD, CE, C); +void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + Optional<Summary> FoundSummary = findFunctionSummary(Call, C); if (!FoundSummary) return; - // Now apply ranges. - const FunctionSummaryTy &Summary = *FoundSummary; + // Now apply the constraints. + const Summary &Summary = *FoundSummary; ProgramStateRef State = C.getState(); - for (const auto &VRS: Summary.Ranges) { + // Apply case/branch specifications. + for (const ConstraintSet &Case : Summary.getCaseConstraints()) { ProgramStateRef NewState = State; - for (const auto &VR: VRS) { - NewState = VR.apply(NewState, Call, Summary); + for (const ValueConstraintPtr &Constraint : Case) { + NewState = Constraint->apply(NewState, Call, Summary, C); if (!NewState) break; } @@ -369,23 +656,16 @@ void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call, bool StdLibraryFunctionsChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { - const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); - if (!FD) - return false; - - const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); - if (!CE) - return false; - - Optional<FunctionSummaryTy> FoundSummary = findFunctionSummary(FD, CE, C); + Optional<Summary> FoundSummary = findFunctionSummary(Call, C); if (!FoundSummary) return false; - const FunctionSummaryTy &Summary = *FoundSummary; - switch (Summary.InvalidationKind) { + const Summary &Summary = *FoundSummary; + switch (Summary.getInvalidationKd()) { case EvalCallAsPure: { ProgramStateRef State = C.getState(); const LocationContext *LC = C.getLocationContext(); + const auto *CE = cast_or_null<CallExpr>(Call.getOriginExpr()); SVal V = C.getSValBuilder().conjureSymbolVal( CE, LC, CE->getType().getCanonicalType(), C.blockCount()); State = State->BindExpr(CE, LC, V); @@ -400,79 +680,86 @@ bool StdLibraryFunctionsChecker::evalCall(const CallEvent &Call, llvm_unreachable("Unknown invalidation kind!"); } -bool StdLibraryFunctionsChecker::FunctionSummaryTy::matchesCall( - const CallExpr *CE) const { +bool StdLibraryFunctionsChecker::Signature::matches( + const FunctionDecl *FD) const { // Check number of arguments: - if (CE->getNumArgs() != ArgTypes.size()) + if (FD->param_size() != ArgTys.size()) return false; - // Check return type if relevant: - if (!RetType.isNull() && RetType != CE->getType().getCanonicalType()) - return false; + // Check return type. + if (!isIrrelevant(RetTy)) + if (RetTy != FD->getReturnType().getCanonicalType()) + return false; - // Check argument types when relevant: - for (size_t I = 0, E = ArgTypes.size(); I != E; ++I) { - QualType FormalT = ArgTypes[I]; - // Null type marks irrelevant arguments. - if (FormalT.isNull()) + // Check argument types. + for (size_t I = 0, E = ArgTys.size(); I != E; ++I) { + QualType ArgTy = ArgTys[I]; + if (isIrrelevant(ArgTy)) continue; - - assertTypeSuitableForSummary(FormalT); - - QualType ActualT = StdLibraryFunctionsChecker::getArgType(CE, I); - assert(ActualT.isCanonical()); - if (ActualT != FormalT) + if (ArgTy != FD->getParamDecl(I)->getType().getCanonicalType()) return false; } return true; } -Optional<StdLibraryFunctionsChecker::FunctionSummaryTy> +Optional<StdLibraryFunctionsChecker::Summary> StdLibraryFunctionsChecker::findFunctionSummary(const FunctionDecl *FD, - const CallExpr *CE, CheckerContext &C) const { - // Note: we cannot always obtain FD from CE - // (eg. virtual call, or call by pointer). - assert(CE); - if (!FD) return None; - SValBuilder &SVB = C.getSValBuilder(); - BasicValueFactory &BVF = SVB.getBasicValueFactory(); - initFunctionSummaries(BVF); + initFunctionSummaries(C); - IdentifierInfo *II = FD->getIdentifier(); - if (!II) - return None; - StringRef Name = II->getName(); - if (Name.empty() || !C.isCLibraryFunction(FD, Name)) + auto FSMI = FunctionSummaryMap.find(FD->getCanonicalDecl()); + if (FSMI == FunctionSummaryMap.end()) return None; + return FSMI->second; +} - auto FSMI = FunctionSummaryMap.find(Name); - if (FSMI == FunctionSummaryMap.end()) +Optional<StdLibraryFunctionsChecker::Summary> +StdLibraryFunctionsChecker::findFunctionSummary(const CallEvent &Call, + CheckerContext &C) const { + const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + if (!FD) return None; + return findFunctionSummary(FD, C); +} - // Verify that function signature matches the spec in advance. - // Otherwise we might be modeling the wrong function. - // Strict checking is important because we will be conducting - // very integral-type-sensitive operations on arguments and - // return values. - const FunctionVariantsTy &SpecVariants = FSMI->second; - for (const FunctionSummaryTy &Spec : SpecVariants) - if (Spec.matchesCall(CE)) - return Spec; +static llvm::Optional<QualType> lookupType(StringRef Name, + const ASTContext &ACtx) { + IdentifierInfo &II = ACtx.Idents.get(Name); + auto LookupRes = ACtx.getTranslationUnitDecl()->lookup(&II); + if (LookupRes.size() == 0) + return None; + // Prioritze typedef declarations. + // This is needed in case of C struct typedefs. E.g.: + // typedef struct FILE FILE; + // In this case, we have a RecordDecl 'struct FILE' with the name 'FILE' and + // we have a TypedefDecl with the name 'FILE'. + for (Decl *D : LookupRes) + if (auto *TD = dyn_cast<TypedefNameDecl>(D)) + return ACtx.getTypeDeclType(TD).getCanonicalType(); + + // Find the first TypeDecl. + // There maybe cases when a function has the same name as a struct. + // E.g. in POSIX: `struct stat` and the function `stat()`: + // int stat(const char *restrict path, struct stat *restrict buf); + for (Decl *D : LookupRes) + if (auto *TD = dyn_cast<TypeDecl>(D)) + return ACtx.getTypeDeclType(TD).getCanonicalType(); return None; } void StdLibraryFunctionsChecker::initFunctionSummaries( - BasicValueFactory &BVF) const { + CheckerContext &C) const { if (!FunctionSummaryMap.empty()) return; - ASTContext &ACtx = BVF.getContext(); + SValBuilder &SVB = C.getSValBuilder(); + BasicValueFactory &BVF = SVB.getBasicValueFactory(); + const ASTContext &ACtx = BVF.getContext(); // These types are useful for writing specifications quickly, // New specifications should probably introduce more types. @@ -481,15 +768,105 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( // of function summary for common cases (eg. ssize_t could be int or long // or long long, so three summary variants would be enough). // Of course, function variants are also useful for C++ overloads. - QualType Irrelevant; // A placeholder, whenever we do not care about the type. - QualType IntTy = ACtx.IntTy; - QualType LongTy = ACtx.LongTy; - QualType LongLongTy = ACtx.LongLongTy; - QualType SizeTy = ACtx.getSizeType(); + const QualType VoidTy = ACtx.VoidTy; + const QualType IntTy = ACtx.IntTy; + const QualType UnsignedIntTy = ACtx.UnsignedIntTy; + const QualType LongTy = ACtx.LongTy; + const QualType LongLongTy = ACtx.LongLongTy; + const QualType SizeTy = ACtx.getSizeType(); + + const QualType VoidPtrTy = ACtx.VoidPtrTy; // void * + const QualType IntPtrTy = ACtx.getPointerType(IntTy); // int * + const QualType UnsignedIntPtrTy = + ACtx.getPointerType(UnsignedIntTy); // unsigned int * + const QualType VoidPtrRestrictTy = + ACtx.getLangOpts().C99 ? ACtx.getRestrictType(VoidPtrTy) // void *restrict + : VoidPtrTy; + const QualType ConstVoidPtrTy = + ACtx.getPointerType(ACtx.VoidTy.withConst()); // const void * + const QualType CharPtrTy = ACtx.getPointerType(ACtx.CharTy); // char * + const QualType CharPtrRestrictTy = + ACtx.getLangOpts().C99 ? ACtx.getRestrictType(CharPtrTy) // char *restrict + : CharPtrTy; + const QualType ConstCharPtrTy = + ACtx.getPointerType(ACtx.CharTy.withConst()); // const char * + const QualType ConstCharPtrRestrictTy = + ACtx.getLangOpts().C99 + ? ACtx.getRestrictType(ConstCharPtrTy) // const char *restrict + : ConstCharPtrTy; + const QualType Wchar_tPtrTy = ACtx.getPointerType(ACtx.WCharTy); // wchar_t * + const QualType ConstWchar_tPtrTy = + ACtx.getPointerType(ACtx.WCharTy.withConst()); // const wchar_t * + const QualType ConstVoidPtrRestrictTy = + ACtx.getLangOpts().C99 + ? ACtx.getRestrictType(ConstVoidPtrTy) // const void *restrict + : ConstVoidPtrTy; + + const RangeInt IntMax = BVF.getMaxValue(IntTy).getLimitedValue(); + const RangeInt UnsignedIntMax = + BVF.getMaxValue(UnsignedIntTy).getLimitedValue(); + const RangeInt LongMax = BVF.getMaxValue(LongTy).getLimitedValue(); + const RangeInt LongLongMax = BVF.getMaxValue(LongLongTy).getLimitedValue(); + const RangeInt SizeMax = BVF.getMaxValue(SizeTy).getLimitedValue(); + + // Set UCharRangeMax to min of int or uchar maximum value. + // The C standard states that the arguments of functions like isalpha must + // be representable as an unsigned char. Their type is 'int', so the max + // value of the argument should be min(UCharMax, IntMax). This just happen + // to be true for commonly used and well tested instruction set + // architectures, but not for others. + const RangeInt UCharRangeMax = + std::min(BVF.getMaxValue(ACtx.UnsignedCharTy).getLimitedValue(), IntMax); + + // The platform dependent value of EOF. + // Try our best to parse this from the Preprocessor, otherwise fallback to -1. + const auto EOFv = [&C]() -> RangeInt { + if (const llvm::Optional<int> OptInt = + tryExpandAsInteger("EOF", C.getPreprocessor())) + return *OptInt; + return -1; + }(); + + // Auxiliary class to aid adding summaries to the summary map. + struct AddToFunctionSummaryMap { + const ASTContext &ACtx; + FunctionSummaryMapType ⤅ + bool DisplayLoadedSummaries; + AddToFunctionSummaryMap(const ASTContext &ACtx, FunctionSummaryMapType &FSM, + bool DisplayLoadedSummaries) + : ACtx(ACtx), Map(FSM), DisplayLoadedSummaries(DisplayLoadedSummaries) { + } - RangeIntTy IntMax = BVF.getMaxValue(IntTy).getLimitedValue(); - RangeIntTy LongMax = BVF.getMaxValue(LongTy).getLimitedValue(); - RangeIntTy LongLongMax = BVF.getMaxValue(LongLongTy).getLimitedValue(); + // Add a summary to a FunctionDecl found by lookup. The lookup is performed + // by the given Name, and in the global scope. The summary will be attached + // to the found FunctionDecl only if the signatures match. + void operator()(StringRef Name, Summary S) { + IdentifierInfo &II = ACtx.Idents.get(Name); + auto LookupRes = ACtx.getTranslationUnitDecl()->lookup(&II); + if (LookupRes.size() == 0) + return; + for (Decl *D : LookupRes) { + if (auto *FD = dyn_cast<FunctionDecl>(D)) { + if (S.matchesAndSet(FD)) { + auto Res = Map.insert({FD->getCanonicalDecl(), S}); + assert(Res.second && "Function already has a summary set!"); + (void)Res; + if (DisplayLoadedSummaries) { + llvm::errs() << "Loaded summary for: "; + FD->print(llvm::errs()); + llvm::errs() << "\n"; + } + return; + } + } + } + } + // Add several summaries for the given name. + void operator()(StringRef Name, const std::vector<Summary> &Summaries) { + for (const Summary &S : Summaries) + operator()(Name, S); + } + } addToFunctionSummaryMap(ACtx, FunctionSummaryMap, DisplayLoadedSummaries); // We are finally ready to define specifications for all supported functions. // @@ -516,550 +893,876 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( // return value, however the correct range is [-1, 10]. // // Please update the list of functions in the header after editing! - // - // The format is as follows: - // - //{ "function name", - // { spec: - // { argument types list, ... }, - // return type, purity, { range set list: - // { range list: - // { argument index, within or out of, {{from, to}, ...} }, - // { argument index, compares to argument, {{how, which}} }, - // ... - // } - // } - // } - //} - -#define SUMMARY_WITH_VARIANTS(identifier) {#identifier, { -#define END_SUMMARY_WITH_VARIANTS }}, -#define VARIANT(argument_types, return_type, invalidation_approach) \ - { argument_types, return_type, invalidation_approach, { -#define END_VARIANT } }, -#define SUMMARY(identifier, argument_types, return_type, \ - invalidation_approach) \ - { #identifier, { { argument_types, return_type, invalidation_approach, { -#define END_SUMMARY } } } }, -#define ARGUMENT_TYPES(...) { __VA_ARGS__ } -#define RETURN_TYPE(x) x -#define INVALIDATION_APPROACH(x) x -#define CASE { -#define END_CASE }, -#define ARGUMENT_CONDITION(argument_number, condition_kind) \ - { argument_number, condition_kind, { -#define END_ARGUMENT_CONDITION }}, -#define RETURN_VALUE_CONDITION(condition_kind) \ - { Ret, condition_kind, { -#define END_RETURN_VALUE_CONDITION }}, -#define ARG_NO(x) x##U -#define RANGE(x, y) { x, y }, -#define SINGLE_VALUE(x) RANGE(x, x) -#define IS_LESS_THAN(arg) { BO_LE, arg } - - FunctionSummaryMap = { - // The isascii() family of functions. - SUMMARY(isalnum, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // Boils down to isupper() or islower() or isdigit() - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE('0', '9') - RANGE('A', 'Z') - RANGE('a', 'z') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE // The locale-specific range. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(128, 255) - END_ARGUMENT_CONDITION - // No post-condition. We are completely unaware of - // locale-specific return values. - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE('0', '9') - RANGE('A', 'Z') - RANGE('a', 'z') - RANGE(128, 255) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isalpha, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // isupper() or islower(). Note that 'Z' is less than 'a'. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE('A', 'Z') - RANGE('a', 'z') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE // The locale-specific range. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(128, 255) - END_ARGUMENT_CONDITION - END_CASE - CASE // Other. - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE('A', 'Z') - RANGE('a', 'z') - RANGE(128, 255) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isascii, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // Is ASCII. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(0, 127) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE(0, 127) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isblank, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - SINGLE_VALUE('\t') - SINGLE_VALUE(' ') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - SINGLE_VALUE('\t') - SINGLE_VALUE(' ') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(iscntrl, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // 0..31 or 127 - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(0, 32) - SINGLE_VALUE(127) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE(0, 32) - SINGLE_VALUE(127) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isdigit, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // Is a digit. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE('0', '9') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE('0', '9') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isgraph, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(33, 126) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE(33, 126) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(islower, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // Is certainly lowercase. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE('a', 'z') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE // Is ascii but not lowercase. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(0, 127) - END_ARGUMENT_CONDITION - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE('a', 'z') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE // The locale-specific range. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(128, 255) - END_ARGUMENT_CONDITION - END_CASE - CASE // Is not an unsigned char. - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE(0, 255) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isprint, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(32, 126) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE(32, 126) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(ispunct, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE('!', '/') - RANGE(':', '@') - RANGE('[', '`') - RANGE('{', '~') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE('!', '/') - RANGE(':', '@') - RANGE('[', '`') - RANGE('{', '~') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isspace, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // Space, '\f', '\n', '\r', '\t', '\v'. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(9, 13) - SINGLE_VALUE(' ') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE // The locale-specific range. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(128, 255) - END_ARGUMENT_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE(9, 13) - SINGLE_VALUE(' ') - RANGE(128, 255) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isupper, ARGUMENT_TYPES(IntTy), RETURN_TYPE (IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE // Is certainly uppercase. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE('A', 'Z') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE // The locale-specific range. - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE(128, 255) - END_ARGUMENT_CONDITION - END_CASE - CASE // Other. - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE('A', 'Z') RANGE(128, 255) - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(isxdigit, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(EvalCallAsPure)) - CASE - ARGUMENT_CONDITION(ARG_NO(0), WithinRange) - RANGE('0', '9') - RANGE('A', 'F') - RANGE('a', 'f') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(OutOfRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - CASE - ARGUMENT_CONDITION(ARG_NO(0), OutOfRange) - RANGE('0', '9') - RANGE('A', 'F') - RANGE('a', 'f') - END_ARGUMENT_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(0) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - - // The getc() family of functions that returns either a char or an EOF. - SUMMARY(getc, ARGUMENT_TYPES(Irrelevant), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(NoEvalCall)) - CASE // FIXME: EOF is assumed to be defined as -1. - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, 255) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(fgetc, ARGUMENT_TYPES(Irrelevant), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(NoEvalCall)) - CASE // FIXME: EOF is assumed to be defined as -1. - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, 255) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(getchar, ARGUMENT_TYPES(), RETURN_TYPE(IntTy), - INVALIDATION_APPROACH(NoEvalCall)) - CASE // FIXME: EOF is assumed to be defined as -1. - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, 255) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - - // read()-like functions that never return more than buffer size. - // We are not sure how ssize_t is defined on every platform, so we provide - // three variants that should cover common cases. - SUMMARY_WITH_VARIANTS(read) - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy), - RETURN_TYPE(IntTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, IntMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy), - RETURN_TYPE(LongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, LongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy), - RETURN_TYPE(LongLongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, LongLongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - END_SUMMARY_WITH_VARIANTS - SUMMARY_WITH_VARIANTS(write) - // Again, due to elusive nature of ssize_t, we have duplicate - // our summaries to cover different variants. - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy), - RETURN_TYPE(IntTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, IntMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy), - RETURN_TYPE(LongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, LongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy), - RETURN_TYPE(LongLongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - RETURN_VALUE_CONDITION(WithinRange) - RANGE(-1, LongLongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - END_SUMMARY_WITH_VARIANTS - SUMMARY(fread, - ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy, Irrelevant), - RETURN_TYPE(SizeTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - SUMMARY(fwrite, - ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy, Irrelevant), - RETURN_TYPE(SizeTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(ComparesToArgument) - IS_LESS_THAN(ARG_NO(2)) - END_RETURN_VALUE_CONDITION - END_CASE - END_SUMMARY - - // getline()-like functions either fail or read at least the delimiter. - SUMMARY_WITH_VARIANTS(getline) - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant), - RETURN_TYPE(IntTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(-1) - RANGE(1, IntMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant), - RETURN_TYPE(LongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(-1) - RANGE(1, LongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant), - RETURN_TYPE(LongLongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(-1) - RANGE(1, LongLongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - END_SUMMARY_WITH_VARIANTS - SUMMARY_WITH_VARIANTS(getdelim) - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant, Irrelevant), - RETURN_TYPE(IntTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(-1) - RANGE(1, IntMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant, Irrelevant), - RETURN_TYPE(LongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(-1) - RANGE(1, LongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant, Irrelevant), - RETURN_TYPE(LongLongTy), INVALIDATION_APPROACH(NoEvalCall)) - CASE - RETURN_VALUE_CONDITION(WithinRange) - SINGLE_VALUE(-1) - RANGE(1, LongLongMax) - END_RETURN_VALUE_CONDITION - END_CASE - END_VARIANT - END_SUMMARY_WITH_VARIANTS + + // Below are helpers functions to create the summaries. + auto ArgumentCondition = [](ArgNo ArgN, RangeKind Kind, + IntRangeVector Ranges) { + return std::make_shared<RangeConstraint>(ArgN, Kind, Ranges); + }; + auto BufferSize = [](auto... Args) { + return std::make_shared<BufferSizeConstraint>(Args...); + }; + struct { + auto operator()(RangeKind Kind, IntRangeVector Ranges) { + return std::make_shared<RangeConstraint>(Ret, Kind, Ranges); + } + auto operator()(BinaryOperator::Opcode Op, ArgNo OtherArgN) { + return std::make_shared<ComparisonConstraint>(Ret, Op, OtherArgN); + } + } ReturnValueCondition; + auto Range = [](RangeInt b, RangeInt e) { + return IntRangeVector{std::pair<RangeInt, RangeInt>{b, e}}; + }; + auto SingleValue = [](RangeInt v) { + return IntRangeVector{std::pair<RangeInt, RangeInt>{v, v}}; }; + auto LessThanOrEq = BO_LE; + auto NotNull = [&](ArgNo ArgN) { + return std::make_shared<NotNullConstraint>(ArgN); + }; + + Optional<QualType> FileTy = lookupType("FILE", ACtx); + Optional<QualType> FilePtrTy, FilePtrRestrictTy; + if (FileTy) { + // FILE * + FilePtrTy = ACtx.getPointerType(*FileTy); + // FILE *restrict + FilePtrRestrictTy = + ACtx.getLangOpts().C99 ? ACtx.getRestrictType(*FilePtrTy) : *FilePtrTy; + } + + using RetType = QualType; + // Templates for summaries that are reused by many functions. + auto Getc = [&]() { + return Summary(ArgTypes{*FilePtrTy}, RetType{IntTy}, NoEvalCall) + .Case({ReturnValueCondition(WithinRange, + {{EOFv, EOFv}, {0, UCharRangeMax}})}); + }; + auto Read = [&](RetType R, RangeInt Max) { + return Summary(ArgTypes{Irrelevant, Irrelevant, SizeTy}, RetType{R}, + NoEvalCall) + .Case({ReturnValueCondition(LessThanOrEq, ArgNo(2)), + ReturnValueCondition(WithinRange, Range(-1, Max))}); + }; + auto Fread = [&]() { + return Summary( + ArgTypes{VoidPtrRestrictTy, SizeTy, SizeTy, *FilePtrRestrictTy}, + RetType{SizeTy}, NoEvalCall) + .Case({ + ReturnValueCondition(LessThanOrEq, ArgNo(2)), + }) + .ArgConstraint(NotNull(ArgNo(0))); + }; + auto Fwrite = [&]() { + return Summary(ArgTypes{ConstVoidPtrRestrictTy, SizeTy, SizeTy, + *FilePtrRestrictTy}, + RetType{SizeTy}, NoEvalCall) + .Case({ + ReturnValueCondition(LessThanOrEq, ArgNo(2)), + }) + .ArgConstraint(NotNull(ArgNo(0))); + }; + auto Getline = [&](RetType R, RangeInt Max) { + return Summary(ArgTypes{Irrelevant, Irrelevant, Irrelevant}, RetType{R}, + NoEvalCall) + .Case({ReturnValueCondition(WithinRange, {{-1, -1}, {1, Max}})}); + }; + + // The isascii() family of functions. + // The behavior is undefined if the value of the argument is not + // representable as unsigned char or is not equal to EOF. See e.g. C99 + // 7.4.1.2 The isalpha function (p: 181-182). + addToFunctionSummaryMap( + "isalnum", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + // Boils down to isupper() or islower() or isdigit(). + .Case({ArgumentCondition(0U, WithinRange, + {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}}), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + // The locale-specific range. + // No post-condition. We are completely unaware of + // locale-specific return values. + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}) + .Case( + {ArgumentCondition( + 0U, OutOfRange, + {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}, {128, UCharRangeMax}}), + ReturnValueCondition(WithinRange, SingleValue(0))}) + .ArgConstraint(ArgumentCondition( + 0U, WithinRange, {{EOFv, EOFv}, {0, UCharRangeMax}}))); + addToFunctionSummaryMap( + "isalpha", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, {{'A', 'Z'}, {'a', 'z'}}), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + // The locale-specific range. + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}) + .Case({ArgumentCondition( + 0U, OutOfRange, + {{'A', 'Z'}, {'a', 'z'}, {128, UCharRangeMax}}), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isascii", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, Range(0, 127)), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition(0U, OutOfRange, Range(0, 127)), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isblank", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, {{'\t', '\t'}, {' ', ' '}}), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition(0U, OutOfRange, {{'\t', '\t'}, {' ', ' '}}), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "iscntrl", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, {{0, 32}, {127, 127}}), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition(0U, OutOfRange, {{0, 32}, {127, 127}}), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isdigit", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, Range('0', '9')), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition(0U, OutOfRange, Range('0', '9')), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isgraph", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, Range(33, 126)), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition(0U, OutOfRange, Range(33, 126)), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "islower", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + // Is certainly lowercase. + .Case({ArgumentCondition(0U, WithinRange, Range('a', 'z')), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + // Is ascii but not lowercase. + .Case({ArgumentCondition(0U, WithinRange, Range(0, 127)), + ArgumentCondition(0U, OutOfRange, Range('a', 'z')), + ReturnValueCondition(WithinRange, SingleValue(0))}) + // The locale-specific range. + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}) + // Is not an unsigned char. + .Case({ArgumentCondition(0U, OutOfRange, Range(0, UCharRangeMax)), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isprint", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, Range(32, 126)), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition(0U, OutOfRange, Range(32, 126)), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "ispunct", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition( + 0U, WithinRange, + {{'!', '/'}, {':', '@'}, {'[', '`'}, {'{', '~'}}), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition( + 0U, OutOfRange, + {{'!', '/'}, {':', '@'}, {'[', '`'}, {'{', '~'}}), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isspace", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + // Space, '\f', '\n', '\r', '\t', '\v'. + .Case({ArgumentCondition(0U, WithinRange, {{9, 13}, {' ', ' '}}), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + // The locale-specific range. + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}) + .Case({ArgumentCondition(0U, OutOfRange, + {{9, 13}, {' ', ' '}, {128, UCharRangeMax}}), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isupper", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + // Is certainly uppercase. + .Case({ArgumentCondition(0U, WithinRange, Range('A', 'Z')), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + // The locale-specific range. + .Case({ArgumentCondition(0U, WithinRange, {{128, UCharRangeMax}})}) + // Other. + .Case({ArgumentCondition(0U, OutOfRange, + {{'A', 'Z'}, {128, UCharRangeMax}}), + ReturnValueCondition(WithinRange, SingleValue(0))})); + addToFunctionSummaryMap( + "isxdigit", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .Case({ArgumentCondition(0U, WithinRange, + {{'0', '9'}, {'A', 'F'}, {'a', 'f'}}), + ReturnValueCondition(OutOfRange, SingleValue(0))}) + .Case({ArgumentCondition(0U, OutOfRange, + {{'0', '9'}, {'A', 'F'}, {'a', 'f'}}), + ReturnValueCondition(WithinRange, SingleValue(0))})); + + // The getc() family of functions that returns either a char or an EOF. + if (FilePtrTy) { + addToFunctionSummaryMap("getc", Getc()); + addToFunctionSummaryMap("fgetc", Getc()); + } + addToFunctionSummaryMap( + "getchar", Summary(ArgTypes{}, RetType{IntTy}, NoEvalCall) + .Case({ReturnValueCondition( + WithinRange, {{EOFv, EOFv}, {0, UCharRangeMax}})})); + + // read()-like functions that never return more than buffer size. + if (FilePtrRestrictTy) { + addToFunctionSummaryMap("fread", Fread()); + addToFunctionSummaryMap("fwrite", Fwrite()); + } + + // We are not sure how ssize_t is defined on every platform, so we + // provide three variants that should cover common cases. + // FIXME these are actually defined by POSIX and not by the C standard, we + // should handle them together with the rest of the POSIX functions. + addToFunctionSummaryMap("read", {Read(IntTy, IntMax), Read(LongTy, LongMax), + Read(LongLongTy, LongLongMax)}); + addToFunctionSummaryMap("write", {Read(IntTy, IntMax), Read(LongTy, LongMax), + Read(LongLongTy, LongLongMax)}); + + // getline()-like functions either fail or read at least the delimiter. + // FIXME these are actually defined by POSIX and not by the C standard, we + // should handle them together with the rest of the POSIX functions. + addToFunctionSummaryMap("getline", + {Getline(IntTy, IntMax), Getline(LongTy, LongMax), + Getline(LongLongTy, LongLongMax)}); + addToFunctionSummaryMap("getdelim", + {Getline(IntTy, IntMax), Getline(LongTy, LongMax), + Getline(LongLongTy, LongLongMax)}); + + if (ModelPOSIX) { + + // long a64l(const char *str64); + addToFunctionSummaryMap( + "a64l", Summary(ArgTypes{ConstCharPtrTy}, RetType{LongTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // char *l64a(long value); + addToFunctionSummaryMap( + "l64a", Summary(ArgTypes{LongTy}, RetType{CharPtrTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, LongMax)))); + + // int access(const char *pathname, int amode); + addToFunctionSummaryMap("access", Summary(ArgTypes{ConstCharPtrTy, IntTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int faccessat(int dirfd, const char *pathname, int mode, int flags); + addToFunctionSummaryMap( + "faccessat", Summary(ArgTypes{IntTy, ConstCharPtrTy, IntTy, IntTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int dup(int fildes); + addToFunctionSummaryMap( + "dup", Summary(ArgTypes{IntTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + + // int dup2(int fildes1, int filedes2); + addToFunctionSummaryMap( + "dup2", + Summary(ArgTypes{IntTy, IntTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .ArgConstraint( + ArgumentCondition(1, WithinRange, Range(0, IntMax)))); + + // int fdatasync(int fildes); + addToFunctionSummaryMap( + "fdatasync", Summary(ArgTypes{IntTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, + Range(0, IntMax)))); + + // int fnmatch(const char *pattern, const char *string, int flags); + addToFunctionSummaryMap( + "fnmatch", Summary(ArgTypes{ConstCharPtrTy, ConstCharPtrTy, IntTy}, + RetType{IntTy}, EvalCallAsPure) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int fsync(int fildes); + addToFunctionSummaryMap( + "fsync", Summary(ArgTypes{IntTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + + Optional<QualType> Off_tTy = lookupType("off_t", ACtx); + + if (Off_tTy) + // int truncate(const char *path, off_t length); + addToFunctionSummaryMap("truncate", + Summary(ArgTypes{ConstCharPtrTy, *Off_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int symlink(const char *oldpath, const char *newpath); + addToFunctionSummaryMap("symlink", + Summary(ArgTypes{ConstCharPtrTy, ConstCharPtrTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int symlinkat(const char *oldpath, int newdirfd, const char *newpath); + addToFunctionSummaryMap( + "symlinkat", + Summary(ArgTypes{ConstCharPtrTy, IntTy, ConstCharPtrTy}, RetType{IntTy}, + NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(ArgumentCondition(1, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(2)))); + + if (Off_tTy) + // int lockf(int fd, int cmd, off_t len); + addToFunctionSummaryMap( + "lockf", + Summary(ArgTypes{IntTy, IntTy, *Off_tTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + + Optional<QualType> Mode_tTy = lookupType("mode_t", ACtx); + + if (Mode_tTy) + // int creat(const char *pathname, mode_t mode); + addToFunctionSummaryMap("creat", + Summary(ArgTypes{ConstCharPtrTy, *Mode_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // unsigned int sleep(unsigned int seconds); + addToFunctionSummaryMap( + "sleep", + Summary(ArgTypes{UnsignedIntTy}, RetType{UnsignedIntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, UnsignedIntMax)))); + + Optional<QualType> DirTy = lookupType("DIR", ACtx); + Optional<QualType> DirPtrTy; + if (DirTy) + DirPtrTy = ACtx.getPointerType(*DirTy); + + if (DirPtrTy) + // int dirfd(DIR *dirp); + addToFunctionSummaryMap( + "dirfd", Summary(ArgTypes{*DirPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // unsigned int alarm(unsigned int seconds); + addToFunctionSummaryMap( + "alarm", + Summary(ArgTypes{UnsignedIntTy}, RetType{UnsignedIntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, UnsignedIntMax)))); + + if (DirPtrTy) + // int closedir(DIR *dir); + addToFunctionSummaryMap( + "closedir", Summary(ArgTypes{*DirPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // char *strdup(const char *s); + addToFunctionSummaryMap("strdup", Summary(ArgTypes{ConstCharPtrTy}, + RetType{CharPtrTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // char *strndup(const char *s, size_t n); + addToFunctionSummaryMap( + "strndup", Summary(ArgTypes{ConstCharPtrTy, SizeTy}, RetType{CharPtrTy}, + NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(ArgumentCondition(1, WithinRange, + Range(0, SizeMax)))); + + // wchar_t *wcsdup(const wchar_t *s); + addToFunctionSummaryMap("wcsdup", Summary(ArgTypes{ConstWchar_tPtrTy}, + RetType{Wchar_tPtrTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int mkstemp(char *template); + addToFunctionSummaryMap( + "mkstemp", Summary(ArgTypes{CharPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // char *mkdtemp(char *template); + addToFunctionSummaryMap( + "mkdtemp", Summary(ArgTypes{CharPtrTy}, RetType{CharPtrTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // char *getcwd(char *buf, size_t size); + addToFunctionSummaryMap( + "getcwd", + Summary(ArgTypes{CharPtrTy, SizeTy}, RetType{CharPtrTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(1, WithinRange, Range(0, SizeMax)))); + + if (Mode_tTy) { + // int mkdir(const char *pathname, mode_t mode); + addToFunctionSummaryMap("mkdir", + Summary(ArgTypes{ConstCharPtrTy, *Mode_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int mkdirat(int dirfd, const char *pathname, mode_t mode); + addToFunctionSummaryMap( + "mkdirat", Summary(ArgTypes{IntTy, ConstCharPtrTy, *Mode_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(1)))); + } + + Optional<QualType> Dev_tTy = lookupType("dev_t", ACtx); + + if (Mode_tTy && Dev_tTy) { + // int mknod(const char *pathname, mode_t mode, dev_t dev); + addToFunctionSummaryMap( + "mknod", Summary(ArgTypes{ConstCharPtrTy, *Mode_tTy, *Dev_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int mknodat(int dirfd, const char *pathname, mode_t mode, dev_t dev); + addToFunctionSummaryMap("mknodat", Summary(ArgTypes{IntTy, ConstCharPtrTy, + *Mode_tTy, *Dev_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(1)))); + } + + if (Mode_tTy) { + // int chmod(const char *path, mode_t mode); + addToFunctionSummaryMap("chmod", + Summary(ArgTypes{ConstCharPtrTy, *Mode_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int fchmodat(int dirfd, const char *pathname, mode_t mode, int flags); + addToFunctionSummaryMap( + "fchmodat", Summary(ArgTypes{IntTy, ConstCharPtrTy, *Mode_tTy, IntTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, + Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int fchmod(int fildes, mode_t mode); + addToFunctionSummaryMap( + "fchmod", + Summary(ArgTypes{IntTy, *Mode_tTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + } + + Optional<QualType> Uid_tTy = lookupType("uid_t", ACtx); + Optional<QualType> Gid_tTy = lookupType("gid_t", ACtx); + + if (Uid_tTy && Gid_tTy) { + // int fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, + // int flags); + addToFunctionSummaryMap( + "fchownat", + Summary(ArgTypes{IntTy, ConstCharPtrTy, *Uid_tTy, *Gid_tTy, IntTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int chown(const char *path, uid_t owner, gid_t group); + addToFunctionSummaryMap( + "chown", Summary(ArgTypes{ConstCharPtrTy, *Uid_tTy, *Gid_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int lchown(const char *path, uid_t owner, gid_t group); + addToFunctionSummaryMap( + "lchown", Summary(ArgTypes{ConstCharPtrTy, *Uid_tTy, *Gid_tTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int fchown(int fildes, uid_t owner, gid_t group); + addToFunctionSummaryMap( + "fchown", Summary(ArgTypes{IntTy, *Uid_tTy, *Gid_tTy}, RetType{IntTy}, + NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, + Range(0, IntMax)))); + } + + // int rmdir(const char *pathname); + addToFunctionSummaryMap( + "rmdir", Summary(ArgTypes{ConstCharPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int chdir(const char *path); + addToFunctionSummaryMap( + "chdir", Summary(ArgTypes{ConstCharPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int link(const char *oldpath, const char *newpath); + addToFunctionSummaryMap("link", + Summary(ArgTypes{ConstCharPtrTy, ConstCharPtrTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int linkat(int fd1, const char *path1, int fd2, const char *path2, + // int flag); + addToFunctionSummaryMap( + "linkat", + Summary(ArgTypes{IntTy, ConstCharPtrTy, IntTy, ConstCharPtrTy, IntTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(ArgumentCondition(2, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(3)))); + + // int unlink(const char *pathname); + addToFunctionSummaryMap( + "unlink", Summary(ArgTypes{ConstCharPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int unlinkat(int fd, const char *path, int flag); + addToFunctionSummaryMap( + "unlinkat", + Summary(ArgTypes{IntTy, ConstCharPtrTy, IntTy}, RetType{IntTy}, + NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1)))); + + Optional<QualType> StructStatTy = lookupType("stat", ACtx); + Optional<QualType> StructStatPtrTy, StructStatPtrRestrictTy; + if (StructStatTy) { + StructStatPtrTy = ACtx.getPointerType(*StructStatTy); + StructStatPtrRestrictTy = ACtx.getLangOpts().C99 + ? ACtx.getRestrictType(*StructStatPtrTy) + : *StructStatPtrTy; + } + + if (StructStatPtrTy) + // int fstat(int fd, struct stat *statbuf); + addToFunctionSummaryMap( + "fstat", + Summary(ArgTypes{IntTy, *StructStatPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1)))); + + if (StructStatPtrRestrictTy) { + // int stat(const char *restrict path, struct stat *restrict buf); + addToFunctionSummaryMap( + "stat", + Summary(ArgTypes{ConstCharPtrRestrictTy, *StructStatPtrRestrictTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int lstat(const char *restrict path, struct stat *restrict buf); + addToFunctionSummaryMap( + "lstat", + Summary(ArgTypes{ConstCharPtrRestrictTy, *StructStatPtrRestrictTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int fstatat(int fd, const char *restrict path, + // struct stat *restrict buf, int flag); + addToFunctionSummaryMap( + "fstatat", Summary(ArgTypes{IntTy, ConstCharPtrRestrictTy, + *StructStatPtrRestrictTy, IntTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, + Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(NotNull(ArgNo(2)))); + } + + if (DirPtrTy) { + // DIR *opendir(const char *name); + addToFunctionSummaryMap("opendir", Summary(ArgTypes{ConstCharPtrTy}, + RetType{*DirPtrTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // DIR *fdopendir(int fd); + addToFunctionSummaryMap( + "fdopendir", Summary(ArgTypes{IntTy}, RetType{*DirPtrTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, + Range(0, IntMax)))); + } + + // int isatty(int fildes); + addToFunctionSummaryMap( + "isatty", Summary(ArgTypes{IntTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + + if (FilePtrTy) { + // FILE *popen(const char *command, const char *type); + addToFunctionSummaryMap("popen", + Summary(ArgTypes{ConstCharPtrTy, ConstCharPtrTy}, + RetType{*FilePtrTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int pclose(FILE *stream); + addToFunctionSummaryMap( + "pclose", Summary(ArgTypes{*FilePtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + } + + // int close(int fildes); + addToFunctionSummaryMap( + "close", Summary(ArgTypes{IntTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + + // long fpathconf(int fildes, int name); + addToFunctionSummaryMap( + "fpathconf", + Summary(ArgTypes{IntTy, IntTy}, RetType{LongTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + + // long pathconf(const char *path, int name); + addToFunctionSummaryMap("pathconf", Summary(ArgTypes{ConstCharPtrTy, IntTy}, + RetType{LongTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + if (FilePtrTy) + // FILE *fdopen(int fd, const char *mode); + addToFunctionSummaryMap( + "fdopen", Summary(ArgTypes{IntTy, ConstCharPtrTy}, + RetType{*FilePtrTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1)))); + + if (DirPtrTy) { + // void rewinddir(DIR *dir); + addToFunctionSummaryMap( + "rewinddir", Summary(ArgTypes{*DirPtrTy}, RetType{VoidTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // void seekdir(DIR *dirp, long loc); + addToFunctionSummaryMap("seekdir", Summary(ArgTypes{*DirPtrTy, LongTy}, + RetType{VoidTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + } + + // int rand_r(unsigned int *seedp); + addToFunctionSummaryMap("rand_r", Summary(ArgTypes{UnsignedIntPtrTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int strcasecmp(const char *s1, const char *s2); + addToFunctionSummaryMap("strcasecmp", + Summary(ArgTypes{ConstCharPtrTy, ConstCharPtrTy}, + RetType{IntTy}, EvalCallAsPure) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + + // int strncasecmp(const char *s1, const char *s2, size_t n); + addToFunctionSummaryMap( + "strncasecmp", Summary(ArgTypes{ConstCharPtrTy, ConstCharPtrTy, SizeTy}, + RetType{IntTy}, EvalCallAsPure) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(ArgumentCondition( + 2, WithinRange, Range(0, SizeMax)))); + + if (FilePtrTy && Off_tTy) { + + // int fileno(FILE *stream); + addToFunctionSummaryMap( + "fileno", Summary(ArgTypes{*FilePtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int fseeko(FILE *stream, off_t offset, int whence); + addToFunctionSummaryMap("fseeko", + Summary(ArgTypes{*FilePtrTy, *Off_tTy, IntTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // off_t ftello(FILE *stream); + addToFunctionSummaryMap( + "ftello", Summary(ArgTypes{*FilePtrTy}, RetType{*Off_tTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + } + + if (Off_tTy) { + Optional<RangeInt> Off_tMax = BVF.getMaxValue(*Off_tTy).getLimitedValue(); + + // void *mmap(void *addr, size_t length, int prot, int flags, int fd, + // off_t offset); + addToFunctionSummaryMap( + "mmap", + Summary(ArgTypes{VoidPtrTy, SizeTy, IntTy, IntTy, IntTy, *Off_tTy}, + RetType{VoidPtrTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(1, WithinRange, Range(1, SizeMax))) + .ArgConstraint( + ArgumentCondition(4, WithinRange, Range(0, *Off_tMax)))); + } + + Optional<QualType> Off64_tTy = lookupType("off64_t", ACtx); + Optional<RangeInt> Off64_tMax; + if (Off64_tTy) { + Off64_tMax = BVF.getMaxValue(*Off_tTy).getLimitedValue(); + // void *mmap64(void *addr, size_t length, int prot, int flags, int fd, + // off64_t offset); + addToFunctionSummaryMap( + "mmap64", + Summary(ArgTypes{VoidPtrTy, SizeTy, IntTy, IntTy, IntTy, *Off64_tTy}, + RetType{VoidPtrTy}, NoEvalCall) + .ArgConstraint( + ArgumentCondition(1, WithinRange, Range(1, SizeMax))) + .ArgConstraint( + ArgumentCondition(4, WithinRange, Range(0, *Off64_tMax)))); + } + + // int pipe(int fildes[2]); + addToFunctionSummaryMap( + "pipe", Summary(ArgTypes{IntPtrTy}, RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + if (Off_tTy) + // off_t lseek(int fildes, off_t offset, int whence); + addToFunctionSummaryMap( + "lseek", Summary(ArgTypes{IntTy, *Off_tTy, IntTy}, RetType{*Off_tTy}, + NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, + Range(0, IntMax)))); + + Optional<QualType> Ssize_tTy = lookupType("ssize_t", ACtx); + + if (Ssize_tTy) { + // ssize_t readlink(const char *restrict path, char *restrict buf, + // size_t bufsize); + addToFunctionSummaryMap( + "readlink", + Summary(ArgTypes{ConstCharPtrRestrictTy, CharPtrRestrictTy, SizeTy}, + RetType{*Ssize_tTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(1), + /*BufSize=*/ArgNo(2))) + .ArgConstraint( + ArgumentCondition(2, WithinRange, Range(0, SizeMax)))); + + // ssize_t readlinkat(int fd, const char *restrict path, + // char *restrict buf, size_t bufsize); + addToFunctionSummaryMap( + "readlinkat", Summary(ArgTypes{IntTy, ConstCharPtrRestrictTy, + CharPtrRestrictTy, SizeTy}, + RetType{*Ssize_tTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, + Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(NotNull(ArgNo(2))) + .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(2), + /*BufSize=*/ArgNo(3))) + .ArgConstraint(ArgumentCondition( + 3, WithinRange, Range(0, SizeMax)))); + } + + // int renameat(int olddirfd, const char *oldpath, int newdirfd, const char + // *newpath); + addToFunctionSummaryMap("renameat", Summary(ArgTypes{IntTy, ConstCharPtrTy, + IntTy, ConstCharPtrTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(NotNull(ArgNo(3)))); + + // char *realpath(const char *restrict file_name, + // char *restrict resolved_name); + addToFunctionSummaryMap( + "realpath", Summary(ArgTypes{ConstCharPtrRestrictTy, CharPtrRestrictTy}, + RetType{CharPtrTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + QualType CharPtrConstPtr = ACtx.getPointerType(CharPtrTy.withConst()); + + // int execv(const char *path, char *const argv[]); + addToFunctionSummaryMap("execv", + Summary(ArgTypes{ConstCharPtrTy, CharPtrConstPtr}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int execvp(const char *file, char *const argv[]); + addToFunctionSummaryMap("execvp", + Summary(ArgTypes{ConstCharPtrTy, CharPtrConstPtr}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(NotNull(ArgNo(0)))); + + // int getopt(int argc, char * const argv[], const char *optstring); + addToFunctionSummaryMap( + "getopt", + Summary(ArgTypes{IntTy, CharPtrConstPtr, ConstCharPtrTy}, + RetType{IntTy}, NoEvalCall) + .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, IntMax))) + .ArgConstraint(NotNull(ArgNo(1))) + .ArgConstraint(NotNull(ArgNo(2)))); + } + + // Functions for testing. + if (ChecksEnabled[CK_StdCLibraryFunctionsTesterChecker]) { + addToFunctionSummaryMap( + "__two_constrained_args", + Summary(ArgTypes{IntTy, IntTy}, RetType{IntTy}, EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0U, WithinRange, SingleValue(1))) + .ArgConstraint(ArgumentCondition(1U, WithinRange, SingleValue(1)))); + addToFunctionSummaryMap( + "__arg_constrained_twice", + Summary(ArgTypes{IntTy}, RetType{IntTy}, EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0U, OutOfRange, SingleValue(1))) + .ArgConstraint(ArgumentCondition(0U, OutOfRange, SingleValue(2)))); + addToFunctionSummaryMap( + "__defaultparam", + Summary(ArgTypes{Irrelevant, IntTy}, RetType{IntTy}, EvalCallAsPure) + .ArgConstraint(NotNull(ArgNo(0)))); + addToFunctionSummaryMap("__variadic", + Summary(ArgTypes{VoidPtrTy, ConstCharPtrTy}, + RetType{IntTy}, EvalCallAsPure) + .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNull(ArgNo(1)))); + addToFunctionSummaryMap( + "__buf_size_arg_constraint", + Summary(ArgTypes{ConstVoidPtrTy, SizeTy}, RetType{IntTy}, + EvalCallAsPure) + .ArgConstraint( + BufferSize(/*Buffer=*/ArgNo(0), /*BufSize=*/ArgNo(1)))); + addToFunctionSummaryMap( + "__buf_size_arg_constraint_mul", + Summary(ArgTypes{ConstVoidPtrTy, SizeTy, SizeTy}, RetType{IntTy}, + EvalCallAsPure) + .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(0), /*BufSize=*/ArgNo(1), + /*BufSizeMultiplier=*/ArgNo(2)))); + } } void ento::registerStdCLibraryFunctionsChecker(CheckerManager &mgr) { - // If this checker grows large enough to support C++, Objective-C, or other - // standard libraries, we could use multiple register...Checker() functions, - // which would register various checkers with the help of the same Checker - // class, turning on different function summaries. - mgr.registerChecker<StdLibraryFunctionsChecker>(); + auto *Checker = mgr.registerChecker<StdLibraryFunctionsChecker>(); + Checker->DisplayLoadedSummaries = + mgr.getAnalyzerOptions().getCheckerBooleanOption( + Checker, "DisplayLoadedSummaries"); + Checker->ModelPOSIX = + mgr.getAnalyzerOptions().getCheckerBooleanOption(Checker, "ModelPOSIX"); } -bool ento::shouldRegisterStdCLibraryFunctionsChecker(const LangOptions &LO) { +bool ento::shouldRegisterStdCLibraryFunctionsChecker(const CheckerManager &mgr) { return true; } + +#define REGISTER_CHECKER(name) \ + void ento::register##name(CheckerManager &mgr) { \ + StdLibraryFunctionsChecker *checker = \ + mgr.getChecker<StdLibraryFunctionsChecker>(); \ + checker->ChecksEnabled[StdLibraryFunctionsChecker::CK_##name] = true; \ + checker->CheckNames[StdLibraryFunctionsChecker::CK_##name] = \ + mgr.getCurrentCheckerName(); \ + } \ + \ + bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; } + +REGISTER_CHECKER(StdCLibraryFunctionArgsChecker) +REGISTER_CHECKER(StdCLibraryFunctionsTesterChecker) |