diff options
author | Balázs Kéri <balazs.keri@ericsson.com> | 2023-07-19 09:23:32 +0200 |
---|---|---|
committer | Balázs Kéri <balazs.keri@ericsson.com> | 2023-07-19 09:58:14 +0200 |
commit | e271049bc6a1408aa4e53771321117b3da6440ab (patch) | |
tree | a75518bfd77fb1a952f9a071609fabd80e68d022 /clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp | |
parent | 4e43ba25998075c5facd98675e75268e72259d71 (diff) | |
download | llvm-e271049bc6a1408aa4e53771321117b3da6440ab.zip llvm-e271049bc6a1408aa4e53771321117b3da6440ab.tar.gz llvm-e271049bc6a1408aa4e53771321117b3da6440ab.tar.bz2 |
[clang][analyzer] StdLibraryFunctionsChecker: Allow NULL buffer in `fread` and `fwrite` if size is zero.
Reviewed By: donat.nagy
Differential Revision: https://reviews.llvm.org/D154509
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp')
-rw-r--r-- | clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp | 110 |
1 files changed, 106 insertions, 4 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp index 683b036..d18e6f63 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp @@ -403,6 +403,53 @@ class StdLibraryFunctionsChecker } }; + /// Check null or non-null-ness of an argument that is of pointer type. + /// The argument is meant to be a buffer that has a size constraint, and it + /// is allowed to have a NULL value if the size is 0. The size can depend on + /// 1 or 2 additional arguments, if one of these is 0 the buffer is allowed to + /// be NULL. This is useful for functions like `fread` which have this special + /// property. + class NotNullBufferConstraint : public ValueConstraint { + using ValueConstraint::ValueConstraint; + ArgNo SizeArg1N; + std::optional<ArgNo> SizeArg2N; + // This variable has a role when we negate the constraint. + bool CannotBeNull = true; + + public: + NotNullBufferConstraint(ArgNo ArgN, ArgNo SizeArg1N, + std::optional<ArgNo> SizeArg2N, + bool CannotBeNull = true) + : ValueConstraint(ArgN), SizeArg1N(SizeArg1N), SizeArg2N(SizeArg2N), + CannotBeNull(CannotBeNull) {} + + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override; + + void describe(DescriptionKind DK, const CallEvent &Call, + ProgramStateRef State, const Summary &Summary, + llvm::raw_ostream &Out) const override; + + bool describeArgumentValue(const CallEvent &Call, ProgramStateRef State, + const Summary &Summary, + llvm::raw_ostream &Out) const override; + + ValueConstraintPtr negate() const override { + NotNullBufferConstraint Tmp(*this); + Tmp.CannotBeNull = !this->CannotBeNull; + return std::make_shared<NotNullBufferConstraint>(Tmp); + } + + protected: + bool checkSpecificValidity(const FunctionDecl *FD) const override { + const bool ValidArg = getArgType(FD, ArgN)->isPointerType(); + assert(ValidArg && + "This constraint should be applied only on a pointer type"); + return ValidArg; + } + }; + // Represents a buffer argument with an additional size constraint. The // constraint may be a concrete value, or a symbolic value in an argument. // Example 1. Concrete value as the minimum buffer size. @@ -1140,6 +1187,54 @@ bool StdLibraryFunctionsChecker::NotNullConstraint::describeArgumentValue( return true; } +ProgramStateRef StdLibraryFunctionsChecker::NotNullBufferConstraint::apply( + ProgramStateRef State, const CallEvent &Call, const Summary &Summary, + CheckerContext &C) const { + SVal V = getArgSVal(Call, getArgNo()); + if (V.isUndef()) + return State; + DefinedOrUnknownSVal L = V.castAs<DefinedOrUnknownSVal>(); + if (!isa<Loc>(L)) + return State; + + std::optional<DefinedOrUnknownSVal> SizeArg1 = + getArgSVal(Call, SizeArg1N).getAs<DefinedOrUnknownSVal>(); + std::optional<DefinedOrUnknownSVal> SizeArg2; + if (SizeArg2N) + SizeArg2 = getArgSVal(Call, *SizeArg2N).getAs<DefinedOrUnknownSVal>(); + + auto IsArgZero = [State](std::optional<DefinedOrUnknownSVal> Val) { + if (!Val) + return false; + auto [IsNonNull, IsNull] = State->assume(*Val); + return IsNull && !IsNonNull; + }; + + if (IsArgZero(SizeArg1) || IsArgZero(SizeArg2)) + return State; + + return State->assume(L, CannotBeNull); +} + +void StdLibraryFunctionsChecker::NotNullBufferConstraint::describe( + DescriptionKind DK, const CallEvent &Call, ProgramStateRef State, + const Summary &Summary, llvm::raw_ostream &Out) const { + assert(CannotBeNull && + "Describe should not be used when the value must be NULL"); + if (DK == Violation) + Out << "should not be NULL"; + else + Out << "is not NULL"; +} + +bool StdLibraryFunctionsChecker::NotNullBufferConstraint::describeArgumentValue( + const CallEvent &Call, ProgramStateRef State, const Summary &Summary, + llvm::raw_ostream &Out) const { + assert(!CannotBeNull && "This function is used when the value is NULL"); + Out << "is NULL"; + return true; +} + ProgramStateRef StdLibraryFunctionsChecker::BufferSizeConstraint::apply( ProgramStateRef State, const CallEvent &Call, const Summary &Summary, CheckerContext &C) const { @@ -1681,6 +1776,10 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( auto IsNull = [&](ArgNo ArgN) { return std::make_shared<NotNullConstraint>(ArgN, false); }; + auto NotNullBuffer = [&](ArgNo ArgN, ArgNo SizeArg1N, ArgNo SizeArg2N) { + return std::make_shared<NotNullBufferConstraint>(ArgN, SizeArg1N, + SizeArg2N); + }; std::optional<QualType> FileTy = lookupTy("FILE"); std::optional<QualType> FilePtrTy = getPointerTy(FileTy); @@ -1935,11 +2034,8 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( .Case({ArgumentCondition(1U, WithinRange, SingleValue(0)), ReturnValueCondition(WithinRange, SingleValue(0))}, ErrnoMustNotBeChecked, GenericSuccessMsg) - .ArgConstraint(NotNull(ArgNo(0))) + .ArgConstraint(NotNullBuffer(ArgNo(0), ArgNo(1), ArgNo(2))) .ArgConstraint(NotNull(ArgNo(3))) - // FIXME: It should be allowed to have a null buffer if any of - // args 1 or 2 are zero. Remove NotNull check of arg 0, add a check - // for non-null buffer if non-zero size to BufferSizeConstraint? .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(0), /*BufSize=*/ArgNo(1), /*BufSizeMultiplier=*/ArgNo(2))); @@ -3452,6 +3548,12 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( "__not_null", Signature(ArgTypes{IntPtrTy}, RetType{IntTy}), Summary(EvalCallAsPure).ArgConstraint(NotNull(ArgNo(0)))); + addToFunctionSummaryMap( + "__not_null_buffer", + Signature(ArgTypes{VoidPtrTy, IntTy, IntTy}, RetType{IntTy}), + Summary(EvalCallAsPure) + .ArgConstraint(NotNullBuffer(ArgNo(0), ArgNo(1), ArgNo(2)))); + // Test inside range constraints. addToFunctionSummaryMap( "__single_val_0", Signature(ArgTypes{IntTy}, RetType{IntTy}), |