diff --git a/clang/docs/analyzer/developer-docs/DebugChecks.rst b/clang/docs/analyzer/developer-docs/DebugChecks.rst index 7a837659576d..d038cb5d7cca 100644 --- a/clang/docs/analyzer/developer-docs/DebugChecks.rst +++ b/clang/docs/analyzer/developer-docs/DebugChecks.rst @@ -309,6 +309,33 @@ ExprInspection checks clang_analyzer_dumpExtent(a); // expected-warning {{8 S64b}} clang_analyzer_dumpElementCount(a); // expected-warning {{2 S64b}} } + +- ``clang_analyzer_value(a single argument of integer or pointer type)`` + + Prints an associated value for the given argument. + Supported argument types are integers, enums and pointers. + The value can be represented either as a range set or as a concrete integer. + For the rest of the types function prints ``n/a`` (aka not available). + + **Note:** This function will print nothing for clang built with Z3 constraint manager. + This may cause crashes of your tests. To manage this use one of the test constraining + techniques: + + * llvm-lit commands ``REQUIRES no-z3`` or ``UNSUPPORTED z3`` `See for details. `_ + + * a preprocessor directive ``#ifndef ANALYZER_CM_Z3`` + + * a clang command argument ``-analyzer-constraints=range`` + + Example usage:: + + void print(char c, unsigned u) { + clang_analyzer_value(c); // expected-warning {{8s:{ [-128, 127] }}} + if(u != 42) + clang_analyzer_value(u); // expected-warning {{32u:{ [0, 41], [43, 4294967295] }}} + else + clang_analyzer_value(u); // expected-warning {{32u:42}} + } Statistics ========== diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ConstraintManager.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ConstraintManager.h index 22b405919bc1..ca6d7849d621 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ConstraintManager.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ConstraintManager.h @@ -119,6 +119,9 @@ public: const char *NL, unsigned int Space, bool IsDot) const = 0; + virtual void printValue(raw_ostream &Out, ProgramStateRef State, + SymbolRef Sym) {} + /// Convenience method to query the state to see if a symbol is null or /// not null, or if neither assumption can be made. ConditionTruthVal isNull(ProgramStateRef State, SymbolRef Sym) { diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h index 69f19f7d8565..c9c21fcf230e 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h @@ -169,6 +169,11 @@ public: /// should continue to the base regions if the region is not symbolic. SymbolRef getAsSymbol(bool IncludeBaseRegions = false) const; + /// If this SVal is loc::ConcreteInt or nonloc::ConcreteInt, + /// return a pointer to APSInt which is held in it. + /// Otherwise, return nullptr. + const llvm::APSInt *getAsInteger() const; + const MemRegion *getAsRegion() const; /// printJson - Pretty-prints in JSON format. diff --git a/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp index 1c33648b2b32..ec1b0a70d7d3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp @@ -40,6 +40,7 @@ class ExprInspectionChecker void analyzerNumTimesReached(const CallExpr *CE, CheckerContext &C) const; void analyzerCrash(const CallExpr *CE, CheckerContext &C) const; void analyzerWarnOnDeadSymbol(const CallExpr *CE, CheckerContext &C) const; + void analyzerValue(const CallExpr *CE, CheckerContext &C) const; void analyzerDumpSValType(const CallExpr *CE, CheckerContext &C) const; void analyzerDump(const CallExpr *CE, CheckerContext &C) const; void analyzerExplain(const CallExpr *CE, CheckerContext &C) const; @@ -60,6 +61,7 @@ class ExprInspectionChecker Optional ExprVal = None) const; ExplodedNode *reportBug(llvm::StringRef Msg, BugReporter &BR, ExplodedNode *N, Optional ExprVal = None) const; + template void printAndReport(CheckerContext &C, T What) const; const Expr *getArgExpr(const CallExpr *CE, CheckerContext &C) const; const MemRegion *getArgRegion(const CallExpr *CE, CheckerContext &C) const; @@ -99,6 +101,7 @@ bool ExprInspectionChecker::evalCall(const CallEvent &Call, &ExprInspectionChecker::analyzerDumpExtent) .Case("clang_analyzer_dumpElementCount", &ExprInspectionChecker::analyzerDumpElementCount) + .Case("clang_analyzer_value", &ExprInspectionChecker::analyzerValue) .StartsWith("clang_analyzer_dumpSvalType", &ExprInspectionChecker::analyzerDumpSValType) .StartsWith("clang_analyzer_dump", @@ -258,6 +261,45 @@ void ExprInspectionChecker::analyzerExplain(const CallExpr *CE, reportBug(Ex.Visit(V), C); } +static void printHelper(llvm::raw_svector_ostream &Out, CheckerContext &C, + const llvm::APSInt &I) { + Out << I.getBitWidth() << (I.isUnsigned() ? "u:" : "s:"); + Out << I; +} + +static void printHelper(llvm::raw_svector_ostream &Out, CheckerContext &C, + SymbolRef Sym) { + C.getConstraintManager().printValue(Out, C.getState(), Sym); +} + +static void printHelper(llvm::raw_svector_ostream &Out, CheckerContext &C, + SVal V) { + Out << V; +} + +template +void ExprInspectionChecker::printAndReport(CheckerContext &C, T What) const { + llvm::SmallString<64> Str; + llvm::raw_svector_ostream OS(Str); + printHelper(OS, C, What); + reportBug(OS.str(), C); +} + +void ExprInspectionChecker::analyzerValue(const CallExpr *CE, + CheckerContext &C) const { + const Expr *Arg = getArgExpr(CE, C); + if (!Arg) + return; + + SVal V = C.getSVal(Arg); + if (const SymbolRef Sym = V.getAsSymbol()) + printAndReport(C, Sym); + else if (const llvm::APSInt *I = V.getAsInteger()) + printAndReport(C, *I); + else + reportBug("n/a", C); +} + void ExprInspectionChecker::analyzerDumpSValType(const CallExpr *CE, CheckerContext &C) const { const Expr *Arg = getArgExpr(CE, C); @@ -275,11 +317,7 @@ void ExprInspectionChecker::analyzerDump(const CallExpr *CE, return; SVal V = C.getSVal(Arg); - - llvm::SmallString<32> Str; - llvm::raw_svector_ostream OS(Str); - V.dumpToStream(OS); - reportBug(OS.str(), C); + printAndReport(C, V); } void ExprInspectionChecker::analyzerGetExtent(const CallExpr *CE, @@ -303,11 +341,7 @@ void ExprInspectionChecker::analyzerDumpExtent(const CallExpr *CE, DefinedOrUnknownSVal Size = getDynamicExtent(C.getState(), MR, C.getSValBuilder()); - - SmallString<64> Msg; - llvm::raw_svector_ostream Out(Msg); - Out << Size; - reportBug(Out.str(), C); + printAndReport(C, Size); } void ExprInspectionChecker::analyzerDumpElementCount(const CallExpr *CE, @@ -328,11 +362,7 @@ void ExprInspectionChecker::analyzerDumpElementCount(const CallExpr *CE, DefinedOrUnknownSVal ElementCount = getDynamicElementCount(C.getState(), MR, C.getSValBuilder(), ElementTy); - - SmallString<128> Msg; - llvm::raw_svector_ostream Out(Msg); - Out << ElementCount; - reportBug(Out.str(), C); + printAndReport(C, ElementCount); } void ExprInspectionChecker::analyzerPrintState(const CallExpr *CE, diff --git a/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp b/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp index 8f1e3aa03975..2d4dfae1e750 100644 --- a/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp @@ -1819,6 +1819,8 @@ public: void printJson(raw_ostream &Out, ProgramStateRef State, const char *NL = "\n", unsigned int Space = 0, bool IsDot = false) const override; + void printValue(raw_ostream &Out, ProgramStateRef State, + SymbolRef Sym) override; void printConstraints(raw_ostream &Out, ProgramStateRef State, const char *NL = "\n", unsigned int Space = 0, bool IsDot = false) const; @@ -3172,6 +3174,13 @@ void RangeConstraintManager::printJson(raw_ostream &Out, ProgramStateRef State, printDisequalities(Out, State, NL, Space, IsDot); } +void RangeConstraintManager::printValue(raw_ostream &Out, ProgramStateRef State, + SymbolRef Sym) { + const RangeSet RS = getRange(State, Sym); + Out << RS.getBitWidth() << (RS.isUnsigned() ? "u:" : "s:"); + RS.dump(Out); +} + static std::string toString(const SymbolRef &Sym) { std::string S; llvm::raw_string_ostream O(S); diff --git a/clang/lib/StaticAnalyzer/Core/SVals.cpp b/clang/lib/StaticAnalyzer/Core/SVals.cpp index 78d1b41abc91..31725926cd0d 100644 --- a/clang/lib/StaticAnalyzer/Core/SVals.cpp +++ b/clang/lib/StaticAnalyzer/Core/SVals.cpp @@ -109,6 +109,14 @@ SymbolRef SVal::getAsSymbol(bool IncludeBaseRegions) const { return getAsLocSymbol(IncludeBaseRegions); } +const llvm::APSInt *SVal::getAsInteger() const { + if (auto CI = getAs()) + return &CI->getValue(); + if (auto CI = getAs()) + return &CI->getValue(); + return nullptr; +} + const MemRegion *SVal::getAsRegion() const { if (Optional X = getAs()) return X->getRegion(); diff --git a/clang/test/Analysis/print-ranges.cpp b/clang/test/Analysis/print-ranges.cpp new file mode 100644 index 000000000000..89a4249ebfaa --- /dev/null +++ b/clang/test/Analysis/print-ranges.cpp @@ -0,0 +1,47 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config eagerly-assume=false -verify %s +// UNSUPPORTED z3 + +template +void clang_analyzer_value(T x); +void clang_analyzer_value(); +template +void clang_analyzer_value(T1 x, T2 y); + +void test1(char x) { + clang_analyzer_value(x); // expected-warning{{8s:{ [-128, 127] }}} + if (x > 42) + clang_analyzer_value(x); // expected-warning{{8s:{ [43, 127] }}} + if (x == 42) + clang_analyzer_value(x); // expected-warning{{8s:42}} +} + +void test2(short x) { + clang_analyzer_value(x); // expected-warning{{16s:{ [-32768, 32767] }}} + if (x < 4200) + clang_analyzer_value(x); // expected-warning{{16s:{ [-32768, 4199] }}} + if (x == 4200) + clang_analyzer_value(x); // expected-warning{{16s:4200}} +} + +void test3(unsigned long long x) { + clang_analyzer_value(x); // expected-warning{{64u:{ [0, 18446744073709551615] }}} + if (x != 42000000) + clang_analyzer_value(x); // expected-warning{{64u:{ [0, 41999999], [42000001, 18446744073709551615] }}} + if (x == 18446744073709551615ull) + clang_analyzer_value(x); // expected-warning{{64u:18446744073709551615}} +} + +struct S {}; +void test4(S s) { + clang_analyzer_value(s); // expected-warning{{n/a}} +} + +void test5() { + clang_analyzer_value(); // expected-warning{{Missing argument}} +} + +void test6(int x, int y) { + if (x == 42 && y == 24) + // Ignore 'y'. + clang_analyzer_value(x, y); // expected-warning{{32s:42}} +}