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 @@ -183,6 +183,29 @@ const Summary &Summary) const override; }; + class NotNullConstraint : public ValueConstraint { + using ValueConstraint::ValueConstraint; + // This variable has a role when we negate the constraint. + bool CannotBeNull = true; + + public: + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary) const override { + SVal V = getArgSVal(Call, getArgNo()); + DefinedOrUnknownSVal L = V.castAs(); + if (!L.getAs()) + return State; + + return State->assume(L, CannotBeNull); + } + + ValueConstraintPtr negate() const override { + NotNullConstraint Tmp(*this); + Tmp.CannotBeNull = !this->CannotBeNull; + return std::make_shared(Tmp); + } + }; + /// The complete list of constraints that defines a single branch. typedef std::vector ConstraintSet; @@ -233,9 +256,6 @@ "We should have had no significant void types in the spec"); assert(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"); } public: @@ -602,6 +622,9 @@ const QualType LongTy = ACtx.LongTy; const QualType LongLongTy = ACtx.LongLongTy; const QualType SizeTy = ACtx.getSizeType(); + const QualType VoidPtrTy = ACtx.VoidPtrTy; // void *T + const QualType ConstVoidPtrTy = + ACtx.getPointerType(ACtx.VoidTy.withConst()); // const void *T const RangeInt IntMax = BVF.getMaxValue(IntTy).getLimitedValue(); const RangeInt LongMax = BVF.getMaxValue(LongTy).getLimitedValue(); @@ -672,6 +695,9 @@ return IntRangeVector{std::pair{v, v}}; }; auto LessThanOrEq = BO_LE; + auto NotNull = [&](ArgNo ArgN) { + return std::make_shared(ArgN); + }; using RetType = QualType; // Templates for summaries that are reused by many functions. @@ -687,11 +713,20 @@ ReturnValueCondition(WithinRange, Range(-1, Max))}); }; auto Fread = [&]() { - return Summary(ArgTypes{Irrelevant, Irrelevant, SizeTy, Irrelevant}, + return Summary(ArgTypes{VoidPtrTy, Irrelevant, SizeTy, Irrelevant}, + RetType{SizeTy}, NoEvalCall) + .Case({ + ReturnValueCondition(LessThanOrEq, ArgNo(2)), + }) + .ArgConstraint(NotNull(ArgNo(0))); + }; + auto Fwrite = [&]() { + return Summary(ArgTypes{ConstVoidPtrTy, 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}, @@ -893,7 +928,7 @@ {"write", Summaries{Read(IntTy, IntMax), Read(LongTy, LongMax), Read(LongLongTy, LongLongMax)}}, {"fread", Summaries{Fread()}}, - {"fwrite", Summaries{Fread()}}, + {"fwrite", Summaries{Fwrite()}}, // getline()-like functions either fail or read at least the delimiter. {"getline", Summaries{Getline(IntTy, IntMax), Getline(LongTy, LongMax), Getline(LongLongTy, LongLongMax)}}, 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 @@ -59,3 +59,29 @@ (void)ret; } } + +typedef struct FILE FILE; +typedef typeof(sizeof(int)) size_t; +size_t fread(void *, size_t, size_t, FILE *); +void test_notnull_concrete(FILE *fp) { + fread(0, sizeof(int), 10, fp); // \ + // report-warning{{Function argument constraint is not satisfied}} \ + // bugpath-warning{{Function argument constraint is not satisfied}} \ + // bugpath-note{{Function argument constraint is not satisfied}} +} +void test_notnull_symbolic(FILE *fp, int *buf) { + fread(buf, sizeof(int), 10, fp); + clang_analyzer_eval(buf != 0); // \ + // report-warning{{TRUE}} \ + // bugpath-warning{{TRUE}} \ + // bugpath-note{{TRUE}} \ + // bugpath-note{{'buf' is not equal to null}} +} +void test_notnull_symbolic2(FILE *fp, int *buf) { + if (!buf) // bugpath-note{{Assuming 'buf' is null}} \ + // bugpath-note{{Taking true branch}} + fread(buf, sizeof(int), 10, fp); // \ + // report-warning{{Function argument constraint is not satisfied}} \ + // bugpath-warning{{Function argument constraint is not satisfied}} \ + // bugpath-note{{Function argument constraint is not satisfied}} +} diff --git a/clang/test/Analysis/std-c-library-functions.c b/clang/test/Analysis/std-c-library-functions.c --- a/clang/test/Analysis/std-c-library-functions.c +++ b/clang/test/Analysis/std-c-library-functions.c @@ -78,10 +78,13 @@ size_t fread(void *, size_t, size_t, FILE *); size_t fwrite(const void *restrict, size_t, size_t, FILE *restrict); void test_fread_fwrite(FILE *fp, int *buf) { + size_t x = fwrite(buf, sizeof(int), 10, fp); clang_analyzer_eval(x <= 10); // expected-warning{{TRUE}} + size_t y = fread(buf, sizeof(int), 10, fp); clang_analyzer_eval(y <= 10); // expected-warning{{TRUE}} + size_t z = fwrite(buf, sizeof(int), y, fp); clang_analyzer_eval(z <= y); // expected-warning{{TRUE}} }