diff --git a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp @@ -131,20 +131,59 @@ virtual bool sanityCheck(const FunctionDecl *FD) const { return true; } }; + using RangeFun = RangeInt(const FunctionDecl *, ArgNo, BasicValueFactory &); + using RangeFunPtr = RangeFun *; + class LazyRangeInt { + union { + RangeInt Int; + RangeFunPtr Fun; + } Storage; + enum { K_Int, K_Fun } Kind; + + public: + LazyRangeInt(int Int) : Kind(K_Int) { Storage.Int = Int; } + LazyRangeInt(RangeFunPtr Fun) : Kind(K_Fun) { Storage.Fun = Fun; } + RangeInt eval(const FunctionDecl *FD, ArgNo ArgN, + BasicValueFactory &BVF) const { + if (Kind == K_Int) + return Storage.Int; + return Storage.Fun(FD, ArgN, BVF); + } + }; + + static RangeInt Max(const FunctionDecl *FD, ArgNo ArgN, + BasicValueFactory &BVF) { + return BVF.getMaxValue(getArgType(FD, ArgN)).getLimitedValue(); + } + + using LazyIntRangeVector = std::vector>; + /// 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. + mutable IntRangeVector Ranges; + LazyIntRangeVector LazyRanges; public: - RangeConstraint(ArgNo ArgN, RangeKind Kind, const IntRangeVector &Args) - : ValueConstraint(ArgN), Kind(Kind), Args(Args) {} - - const IntRangeVector &getRanges() const { - return Args; + RangeConstraint(ArgNo ArgN, RangeKind Kind, const IntRangeVector &Ranges) + : ValueConstraint(ArgN), Kind(Kind), Ranges(Ranges) {} + + RangeConstraint(ArgNo ArgN, RangeKind Kind, + const LazyIntRangeVector &Ranges) + : ValueConstraint(ArgN), Kind(Kind), LazyRanges(Ranges) {} + + const IntRangeVector &getRanges(const FunctionDecl *FD, + BasicValueFactory &BVF) const { + if (Ranges.size()) + return Ranges; + for (const std::pair &LazyR : LazyRanges) { + Ranges.push_back({LazyR.first.eval(FD, ArgN, BVF), + LazyR.second.eval(FD, ArgN, BVF)}); + } + return Ranges; } private: @@ -331,6 +370,7 @@ } void setFunctionDecl(const FunctionDecl *FD) { this->FD = FD; } + const FunctionDecl *getFunctionDecl() const { return FD; } InvalidationKind getInvalidationKd() const { return InvalidationKd; } const Cases &getCaseConstraints() const { return CaseConstraints; } @@ -425,7 +465,7 @@ SVal V = getArgSVal(Call, getArgNo()); if (auto N = V.getAs()) { - const IntRangeVector &R = getRanges(); + const IntRangeVector &R = getRanges(Summary.getFunctionDecl(), BVF); size_t E = R.size(); for (size_t I = 0; I != E; ++I) { const llvm::APSInt &Min = BVF.getValue(R[I].first, T); @@ -461,7 +501,7 @@ // 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()) { - const IntRangeVector &R = getRanges(); + const IntRangeVector &R = getRanges(Summary.getFunctionDecl(), BVF); size_t E = R.size(); const llvm::APSInt &MinusInf = BVF.getMinValue(T); @@ -682,22 +722,11 @@ // or long long, so three summary variants would be enough). // Of course, function variants are also useful for C++ overloads. const QualType IntTy = ACtx.IntTy; - const QualType LongTy = ACtx.LongTy; - const QualType LongLongTy = ACtx.LongLongTy; - const QualType SizeTy = ACtx.getSizeType(); const QualType VoidPtrTy = ACtx.VoidPtrTy; // void * - const QualType VoidPtrRestrictTy = - ACtx.getRestrictType(VoidPtrTy); // void *restrict - const QualType ConstVoidPtrTy = - ACtx.getPointerType(ACtx.VoidTy.withConst()); // const void * const QualType ConstCharPtrTy = ACtx.getPointerType(ACtx.CharTy.withConst()); // const char * - const QualType ConstVoidPtrRestrictTy = - ACtx.getRestrictType(ConstVoidPtrTy); // const void *restrict const RangeInt IntMax = BVF.getMaxValue(IntTy).getLimitedValue(); - const RangeInt LongMax = BVF.getMaxValue(LongTy).getLimitedValue(); - const RangeInt LongLongMax = BVF.getMaxValue(LongLongTy).getLimitedValue(); // Set UCharRangeMax to min of int or uchar maximum value. // The C standard states that the arguments of functions like isalpha must @@ -789,22 +818,22 @@ // Below are helpers functions to create the summaries. auto ArgumentCondition = [](ArgNo ArgN, RangeKind Kind, - IntRangeVector Ranges) { + LazyIntRangeVector Ranges) { return std::make_shared(ArgN, Kind, Ranges); }; struct { - auto operator()(RangeKind Kind, IntRangeVector Ranges) { + auto operator()(RangeKind Kind, LazyIntRangeVector Ranges) { return std::make_shared(Ret, Kind, Ranges); } auto operator()(BinaryOperator::Opcode Op, ArgNo OtherArgN) { return std::make_shared(Ret, Op, OtherArgN); } } ReturnValueCondition; - auto Range = [](RangeInt b, RangeInt e) { - return IntRangeVector{std::pair{b, e}}; + auto Range = [](LazyRangeInt b, LazyRangeInt e) { + return LazyIntRangeVector{std::pair{b, e}}; }; - auto SingleValue = [](RangeInt v) { - return IntRangeVector{std::pair{v, v}}; + auto SingleValue = [](LazyRangeInt v) { + return LazyIntRangeVector{std::pair{v, v}}; }; auto LessThanOrEq = BO_LE; auto NotNull = [&](ArgNo ArgN) { @@ -812,40 +841,6 @@ }; using RetType = QualType; - // Templates for summaries that are reused by many functions. - auto Getc = [&]() { - return Summary(ArgTypes{Irrelevant}, 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, Irrelevant, SizeTy, Irrelevant}, - RetType{SizeTy}, NoEvalCall) - .Case({ - ReturnValueCondition(LessThanOrEq, ArgNo(2)), - }) - .ArgConstraint(NotNull(ArgNo(0))); - }; - auto Fwrite = [&]() { - return Summary( - ArgTypes{ConstVoidPtrRestrictTy, Irrelevant, SizeTy, Irrelevant}, - 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 @@ -982,29 +977,34 @@ ReturnValueCondition(WithinRange, SingleValue(0))})); // The getc() family of functions that returns either a char or an EOF. - addToFunctionSummaryMap("getc", Getc()); - addToFunctionSummaryMap("fgetc", Getc()); + addToFunctionSummaryMap( + {"getc", "fgetc"}, + Summary(ArgTypes{Irrelevant}, RetType{IntTy}, NoEvalCall) + .Case({ReturnValueCondition(WithinRange, + {{EOFv, EOFv}, {0, UCharRangeMax}})})); addToFunctionSummaryMap( "getchar", Summary(ArgTypes{}, RetType{IntTy}, NoEvalCall) .Case({ReturnValueCondition( WithinRange, {{EOFv, EOFv}, {0, UCharRangeMax}})})); // 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. - addToFunctionSummaryMap("read", {Read(IntTy, IntMax), Read(LongTy, LongMax), - Read(LongLongTy, LongLongMax)}); - addToFunctionSummaryMap("write", {Read(IntTy, IntMax), Read(LongTy, LongMax), - Read(LongLongTy, LongLongMax)}); - addToFunctionSummaryMap("fread", Fread()); - addToFunctionSummaryMap("fwrite", Fwrite()); + addToFunctionSummaryMap( + {"read", "write"}, + Summary(NoEvalCall) + .Case({ReturnValueCondition(LessThanOrEq, ArgNo(2)), + ReturnValueCondition(WithinRange, Range(-1, Max))})); + addToFunctionSummaryMap({"fread", "fwrite"}, + Summary(NoEvalCall) + .Case({ + ReturnValueCondition(LessThanOrEq, ArgNo(2)), + }) + .ArgConstraint(NotNull(ArgNo(0)))); + // getline()-like functions either fail or read at least the delimiter. - addToFunctionSummaryMap("getline", - {Getline(IntTy, IntMax), Getline(LongTy, LongMax), - Getline(LongLongTy, LongLongMax)}); - addToFunctionSummaryMap("getdelim", - {Getline(IntTy, IntMax), Getline(LongTy, LongMax), - Getline(LongLongTy, LongLongMax)}); + addToFunctionSummaryMap( + {"getline", "getdelim"}, + Summary(NoEvalCall) + .Case({ReturnValueCondition(WithinRange, {{-1, -1}, {1, Max}})})); // Functions for testing. if (ChecksEnabled[CK_StdCLibraryFunctionsTesterChecker]) { @@ -1027,6 +1027,11 @@ RetType{IntTy}, EvalCallAsPure) .ArgConstraint(NotNull(ArgNo(0))) .ArgConstraint(NotNull(ArgNo(1)))); + addToFunctionSummaryMap( + "__lazy_range", + Summary(ArgTypes{IntTy, IntTy}, RetType{IntTy}, EvalCallAsPure) + .ArgConstraint(ArgumentCondition(0, WithinRange, Range(0, 100))) + .ArgConstraint(ArgumentCondition(1, WithinRange, Range(0, Max)))); } } diff --git a/clang/test/Analysis/std-c-library-functions-arg-constraints.c b/clang/test/Analysis/std-c-library-functions-arg-constraints.c --- a/clang/test/Analysis/std-c-library-functions-arg-constraints.c +++ b/clang/test/Analysis/std-c-library-functions-arg-constraints.c @@ -122,3 +122,19 @@ // bugpath-warning{{Function argument constraint is not satisfied}} \ // bugpath-note{{Function argument constraint is not satisfied}} } + +int __lazy_range(int, int); +void test_lazy_range(int a, int b) { + __lazy_range(a, b); + clang_analyzer_eval(a >= 0 && a <= 100 && b >= 0 && b <= __INT_MAX__); // \ + // report-warning{{TRUE}} \ + // bugpath-warning{{TRUE}} \ + // bugpath-note{{TRUE}} \ + // bugpath-note{{'a' is >= 0}} \ + // bugpath-note{{'a' is <= 100}} \ + // bugpath-note{{'b' is >= 0}} \ + // bugpath-note{{'b' is <= __INT_MAX__}} \ + // bugpath-note{{Left side of '&&' is true}} \ + // bugpath-note{{Left side of '&&' is true}} \ + // bugpath-note{{Left side of '&&' is true}} +}