diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h @@ -32,6 +32,23 @@ SValBuilder &SVB, QualType ElementTy); +class CheckerContext; +/// Get the dynamic size for a symbolic value that represents a buffer. If +/// there is an offsetting to the underlying buffer we consider that too. +/// Returns with an SVal that represents the size, this is Unknown if the +/// engine cannot deduce the size. +/// E.g. +/// char buf[3]; +/// (buf); // size is 3 +/// (buf + 1); // size is 2 +/// (buf + 3); // size is 0 +/// (buf + 4); // size is -1 +/// +/// char *bufptr; +/// (bufptr) // size is unknown +SVal getBufferDynamicSize(const SVal &BufV, ProgramStateRef State, + CheckerContext &C); + } // namespace ento } // namespace clang diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp --- a/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp @@ -41,25 +41,7 @@ SVal PlacementNewChecker::getExtentSizeOfPlace(const Expr *Place, ProgramStateRef State, CheckerContext &C) const { - const MemRegion *MRegion = C.getSVal(Place).getAsRegion(); - if (!MRegion) - return UnknownVal(); - RegionOffset Offset = MRegion->getAsOffset(); - if (Offset.hasSymbolicOffset()) - return UnknownVal(); - const MemRegion *BaseRegion = MRegion->getBaseRegion(); - if (!BaseRegion) - return UnknownVal(); - - SValBuilder &SvalBuilder = C.getSValBuilder(); - NonLoc OffsetInBytes = SvalBuilder.makeArrayIndex( - Offset.getOffset() / C.getASTContext().getCharWidth()); - DefinedOrUnknownSVal ExtentInBytes = - getDynamicSize(State, BaseRegion, SvalBuilder); - - return SvalBuilder.evalBinOp(State, BinaryOperator::Opcode::BO_Sub, - ExtentInBytes, OffsetInBytes, - SvalBuilder.getArrayIndexType()); + return getBufferDynamicSize(C.getSVal(Place), State, C); } SVal PlacementNewChecker::getExtentSizeOfNewTarget(const CXXNewExpr *NE, 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 @@ -56,6 +56,7 @@ #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; @@ -108,7 +109,8 @@ /// 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) const = 0; + const Summary &Summary, + CheckerContext &C) const = 0; virtual ValueConstraintPtr negate() const { llvm_unreachable("Not implemented"); }; @@ -143,7 +145,8 @@ const Summary &Summary) const; public: ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, - const Summary &Summary) const override { + const Summary &Summary, + CheckerContext &C) const override { switch (Kind) { case OutOfRange: return applyAsOutOfRange(State, Call, Summary); @@ -178,7 +181,8 @@ ArgNo getOtherArgNo() const { return OtherArgN; } BinaryOperator::Opcode getOpcode() const { return Opcode; } ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, - const Summary &Summary) const override; + const Summary &Summary, + CheckerContext &C) const override; }; class NotNullConstraint : public ValueConstraint { @@ -188,7 +192,8 @@ public: ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, - const Summary &Summary) const override { + const Summary &Summary, + CheckerContext &C) const override { SVal V = getArgSVal(Call, getArgNo()); DefinedOrUnknownSVal L = V.castAs(); if (!L.getAs()) @@ -204,6 +209,55 @@ } }; + // 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); + class BufferSizeConstraint : public ValueConstraint { + // The argument which holds the size of the buffer. + ArgNo SizeArgN; + // The operator we use in apply. This is negated in negate(). + BinaryOperator::Opcode Op = BO_LE; + + public: + BufferSizeConstraint(ArgNo BufArgN, ArgNo SizeArgN) + : ValueConstraint(BufArgN), SizeArgN(SizeArgN) {} + + ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, + const Summary &Summary, + CheckerContext &C) const override { + // The buffer argument. + SVal BufV = getArgSVal(Call, getArgNo()); + // The size argument. + SVal SizeV = getArgSVal(Call, SizeArgN); + // The dynamic size of the buffer argument, got from the analyzer engine. + SVal BufDynSize = getBufferDynamicSize(BufV, State, C); + + SValBuilder &SvalBuilder = C.getSValBuilder(); + SVal Feasible = SvalBuilder.evalBinOp(State, Op, SizeV, BufDynSize, + SvalBuilder.getContext().BoolTy); + if (auto F = Feasible.getAs()) + 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. + return nullptr; + } + + ValueConstraintPtr negate() const override { + BufferSizeConstraint Tmp(*this); + + // FIXME Implement a generic negate for all BO values. + assert(Tmp.Op == BO_LE && "Op should be <="); + Tmp.Op = BO_GT; + + return std::make_shared(Tmp); + } + }; + /// The complete list of constraints that defines a single branch. typedef std::vector ConstraintSet; @@ -426,8 +480,8 @@ } ProgramStateRef StdLibraryFunctionsChecker::ComparisonConstraint::apply( - ProgramStateRef State, const CallEvent &Call, - const Summary &Summary) const { + ProgramStateRef State, const CallEvent &Call, const Summary &Summary, + CheckerContext &C) const { ProgramStateManager &Mgr = State->getStateManager(); SValBuilder &SVB = Mgr.getSValBuilder(); @@ -458,8 +512,8 @@ ProgramStateRef NewState = State; for (const ValueConstraintPtr& VC : Summary.ArgConstraints) { - ProgramStateRef SuccessSt = VC->apply(NewState, Call, Summary); - ProgramStateRef FailureSt = VC->negate()->apply(NewState, Call, Summary); + ProgramStateRef SuccessSt = VC->apply(NewState, Call, Summary, C); + ProgramStateRef FailureSt = VC->negate()->apply(NewState, Call, Summary, C); // The argument constraint is not satisfied. if (FailureSt && !SuccessSt) { if (ExplodedNode *N = C.generateErrorNode(NewState)) @@ -492,7 +546,7 @@ for (const auto &VRS : Summary.CaseConstraints) { ProgramStateRef NewState = State; for (const auto &VR: VRS) { - NewState = VR->apply(NewState, Call, Summary); + NewState = VR->apply(NewState, Call, Summary, C); if (!NewState) break; } @@ -685,6 +739,9 @@ IntRangeVector Ranges) { return std::make_shared(ArgN, Kind, Ranges); }; + auto BufferSize = [](ArgNo BufArgN, ArgNo SizeArgN) { + return std::make_shared(BufArgN, SizeArgN); + }; struct { auto operator()(RangeKind Kind, IntRangeVector Ranges) { return std::make_shared(Ret, Kind, Ranges); @@ -957,6 +1014,10 @@ ArgumentCondition(0U, OutOfRange, SingleValue(1))) .ArgConstraint( ArgumentCondition(0U, OutOfRange, SingleValue(2)))}}, + {"__buf_size_arg_constraint", + Summaries{Summary(ArgTypes{ConstVoidPtrTy, SizeTy}, RetType{IntTy}, + EvalCallAsPure) + .ArgConstraint(BufferSize(0, 1))}}, }; for (auto &E : TestFunctionSummaryMap) { auto InsertRes = diff --git a/clang/lib/StaticAnalyzer/Core/DynamicSize.cpp b/clang/lib/StaticAnalyzer/Core/DynamicSize.cpp --- a/clang/lib/StaticAnalyzer/Core/DynamicSize.cpp +++ b/clang/lib/StaticAnalyzer/Core/DynamicSize.cpp @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" #include "clang/AST/Expr.h" #include "clang/Basic/LLVM.h" @@ -44,5 +45,28 @@ return DivisionV.castAs(); } +SVal getBufferDynamicSize(const SVal &BufV, ProgramStateRef State, + CheckerContext &C) { + const MemRegion *MRegion = BufV.getAsRegion(); + if (!MRegion) + return UnknownVal(); + RegionOffset Offset = MRegion->getAsOffset(); + if (Offset.hasSymbolicOffset()) + return UnknownVal(); + const MemRegion *BaseRegion = MRegion->getBaseRegion(); + if (!BaseRegion) + return UnknownVal(); + + SValBuilder &SvalBuilder = C.getSValBuilder(); + NonLoc OffsetInBytes = SvalBuilder.makeArrayIndex( + Offset.getOffset() / C.getASTContext().getCharWidth()); + DefinedOrUnknownSVal ExtentInBytes = + getDynamicSize(State, BaseRegion, SvalBuilder); + + return SvalBuilder.evalBinOp(State, BinaryOperator::Opcode::BO_Sub, + ExtentInBytes, OffsetInBytes, + SvalBuilder.getArrayIndexType()); +} + } // namespace ento } // namespace clang 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 @@ -114,3 +114,30 @@ // bugpath-note{{Assuming 'x' is < 1}} \ // bugpath-note{{Left side of '||' is true}} } + +int __buf_size_arg_constraint(const void *, size_t); +void test_buf_size_concrete() { + char buf[3]; // bugpath-note{{'buf' initialized here}} + __buf_size_arg_constraint(buf, 4); // \ + // 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_buf_size_symbolic(int s) { + char buf[3]; + __buf_size_arg_constraint(buf, s); + clang_analyzer_eval(s <= 3); // \ + // report-warning{{TRUE}} \ + // bugpath-warning{{TRUE}} \ + // bugpath-note{{TRUE}} \ + // bugpath-note{{'s' is <= 3}} +} +void test_buf_size_symbolic_and_offset(int s) { + char buf[3]; + __buf_size_arg_constraint(buf + 1, s); + clang_analyzer_eval(s <= 2); // \ + // report-warning{{TRUE}} \ + // bugpath-warning{{TRUE}} \ + // bugpath-note{{TRUE}} \ + // bugpath-note{{'s' is <= 2}} +}