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 @@ -51,6 +51,7 @@ //===----------------------------------------------------------------------===// #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" @@ -60,7 +61,8 @@ using namespace clang::ento; namespace { -class StdLibraryFunctionsChecker : public Checker { +class StdLibraryFunctionsChecker + : public Checker { /// 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. @@ -142,6 +144,10 @@ applyAsComparesToArgument(ProgramStateRef State, const CallEvent &Call, const FunctionSummaryTy &Summary) const; + void checkAsWithinRange(ProgramStateRef State, const CallEvent &Call, + const FunctionSummaryTy &Summary, const BugType &BT, + CheckerContext &C) const; + public: ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call, const FunctionSummaryTy &Summary) const { @@ -155,6 +161,21 @@ } llvm_unreachable("Unknown ValueRange kind!"); } + + void check(ProgramStateRef State, const CallEvent &Call, + const FunctionSummaryTy &Summary, const BugType &BT, + CheckerContext &C) const { + switch (Kind) { + case OutOfRange: + llvm_unreachable("Not implemented yet!"); + case WithinRange: + checkAsWithinRange(State, Call, Summary, BT, C); + return; + case ComparesToArgument: + llvm_unreachable("Not implemented yet!"); + } + llvm_unreachable("Unknown ValueRange kind!"); + } }; /// The complete list of ranges that defines a single branch. @@ -164,15 +185,21 @@ using RetTypeTy = QualType; using RangesTy = std::vector; - /// 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. + /// Includes information about + /// * function prototype (which is necessary to + /// ensure we're modeling the right function and casting values properly), + /// * approach to invalidation, + /// * a list of branches - a list of list of ranges - + /// i.e. a list of lists of lists of segments, + /// * 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. struct FunctionSummaryTy { const ArgTypesTy ArgTypes; const RetTypeTy RetType; const InvalidationKindTy InvalidationKind; RangesTy Ranges; + ValueRangeSet ArgConstraints; FunctionSummaryTy(ArgTypesTy ArgTypes, RetTypeTy RetType, InvalidationKindTy InvalidationKind) @@ -183,6 +210,10 @@ Ranges.push_back(VRS); return *this; } + FunctionSummaryTy &ArgConstraint(ValueRange VR) { + ArgConstraints.push_back(VR); + return *this; + } private: static void assertTypeSuitableForSummary(QualType T) { @@ -219,6 +250,8 @@ typedef llvm::StringMap FunctionSummaryMapTy; mutable FunctionSummaryMapTy FunctionSummaryMap; + BugType BT{this, "Unsatisfied argument constraints", categories::LogicError}; + // Auxiliary functions to support ArgNoTy within all structures // in a unified manner. static QualType getArgType(const FunctionSummaryTy &Summary, ArgNoTy ArgNo) { @@ -237,6 +270,7 @@ } 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; @@ -350,6 +384,62 @@ return State; } +void StdLibraryFunctionsChecker::ValueRange::checkAsWithinRange( + ProgramStateRef State, const CallEvent &Call, + const FunctionSummaryTy &Summary, const BugType &BT, + CheckerContext &C) const { + + ProgramStateManager &Mgr = State->getStateManager(); + SValBuilder &SVB = Mgr.getSValBuilder(); + BasicValueFactory &BVF = SVB.getBasicValueFactory(); + QualType T = getArgType(Summary, getArgNo()); + SVal V = getArgSVal(Call, getArgNo()); + switch (V.getSubKind()) { + default: + // FIXME Handle other cases. + return; + case nonloc::ConcreteIntKind: { + const llvm::APSInt &IntVal = V.castAs().getValue(); + const IntRangeVectorTy &R = getRanges(); + const llvm::APSInt &Min = BVF.getValue(R[0].first, T); + const llvm::APSInt &Max = BVF.getValue(R[0].second, T); + assert(Min <= Max); + + // Out of range. + if (IntVal < Min || IntVal > Max) { + if (ExplodedNode *N = C.generateErrorNode(State)) { + // FIXME Add detailed diagnostic. + std::string Msg = "Function argument constraint is not satisfied"; + auto R = std::make_unique(BT, Msg, N); + bugreporter::trackExpressionValue(N, Call.getArgExpr(0), *R); + C.emitReport(std::move(R)); + } + } + } + } // end switch +} + +void StdLibraryFunctionsChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + const FunctionDecl *FD = dyn_cast_or_null(Call.getDecl()); + if (!FD) + return; + + const CallExpr *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return; + + Optional FoundSummary = findFunctionSummary(FD, CE, C); + if (!FoundSummary) + return; + + const FunctionSummaryTy &Summary = *FoundSummary; + ProgramStateRef State = C.getState(); + for (const auto &VR : Summary.ArgConstraints) { + VR.check(State, Call, Summary, BT, C); + } +} + void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { const FunctionDecl *FD = dyn_cast_or_null(Call.getDecl()); @@ -368,6 +458,7 @@ const FunctionSummaryTy &Summary = *FoundSummary; ProgramStateRef State = C.getState(); + // Apply specifications. for (const auto &VRS: Summary.Ranges) { ProgramStateRef NewState = State; for (const auto &VR: VRS) { @@ -376,6 +467,11 @@ break; } + // Apply argument constraints as well. + for (const auto &VR : Summary.ArgConstraints) + if (NewState) + NewState = VR.apply(NewState, Call, Summary); + if (NewState && NewState != State) C.addTransition(NewState); } @@ -598,6 +694,9 @@ FunctionSummaryMap = { // 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). { "isalnum", FunctionVariantsTy{ @@ -616,7 +715,9 @@ {ArgumentCondition( 0U, OutOfRange, {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}, {128, 255}}), - ReturnValueCondition(WithinRange, SingleValue(0))})}, + ReturnValueCondition(WithinRange, SingleValue(0))}) + .ArgConstraint( + ArgumentCondition(0U, WithinRange, {{-1, 255}}))}, }, { "isalpha", diff --git a/clang/test/Analysis/std-c-library-functions-arg-constraints.c b/clang/test/Analysis/std-c-library-functions-arg-constraints.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/std-c-library-functions-arg-constraints.c @@ -0,0 +1,22 @@ +// RUN: %clang_analyze_cc1 %s \ +// RUN: -analyzer-checker=apiModeling.StdCLibraryFunctions \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -triple x86_64-unknown-linux-gnu + +void clang_analyzer_eval(int); + +int glob; + +typedef struct FILE FILE; +#define EOF -1 + +int isalnum(int); +void test_alnum_concrete() { + int ret = isalnum(256); // expected-warning{{Function argument constraint is not satisfied}} + (void)ret; +} +void test_alnum_symbolic(int x) { + int ret = isalnum(x); + (void)ret; + clang_analyzer_eval(EOF <= x && x <= 255); // expected-warning{{TRUE}} +}