[analyzer] Add new function clang_analyzer_value to ExprInspectionChecker

Summary: Introduce a new function 'clang_analyzer_value'. It emits a report that in turn prints a RangeSet or APSInt associated with SVal. If there is no associated value, prints "n/a".
This commit is contained in:
Denys Petrov 2022-07-09 21:21:10 +03:00
parent 4c85a01758
commit bc08c3cb7f
7 changed files with 144 additions and 15 deletions

View file

@ -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. <https://llvm.org/docs/TestingGuide.html#constraining-test-execution>`_
* 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
==========

View file

@ -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) {

View file

@ -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.

View file

@ -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<SVal> ExprVal = None) const;
ExplodedNode *reportBug(llvm::StringRef Msg, BugReporter &BR, ExplodedNode *N,
Optional<SVal> ExprVal = None) const;
template <typename T> 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 <typename T>
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,

View file

@ -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);

View file

@ -109,6 +109,14 @@ SymbolRef SVal::getAsSymbol(bool IncludeBaseRegions) const {
return getAsLocSymbol(IncludeBaseRegions);
}
const llvm::APSInt *SVal::getAsInteger() const {
if (auto CI = getAs<nonloc::ConcreteInt>())
return &CI->getValue();
if (auto CI = getAs<loc::ConcreteInt>())
return &CI->getValue();
return nullptr;
}
const MemRegion *SVal::getAsRegion() const {
if (Optional<loc::MemRegionVal> X = getAs<loc::MemRegionVal>())
return X->getRegion();

View file

@ -0,0 +1,47 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config eagerly-assume=false -verify %s
// UNSUPPORTED z3
template <typename T>
void clang_analyzer_value(T x);
void clang_analyzer_value();
template <typename T1, typename T2>
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}}
}