[analyzer] Add clang_analyzer_checkInlined for debugging purposes.

This check is also accessible through the debug.ExprInspection checker.
Like clang_analyzer_eval, you can use it to test the analyzer engine's
current state; the argument should be true or false to indicate whether or
not you expect the function to be inlined.

When used in the positive case (clang_analyzer_checkInlined(true)), the
analyzer prints the message "TRUE" if the function is ever inlined. However,
clang_analyzer_checkInlined(false) should never print a message; this asserts
that there should be no paths on which the current function is inlined, but
then there are no paths on which to print a message! (If the assertion is
violated, the message "FALSE" will be printed.)

This asymmetry comes from the fact that the only other chance to print a
message is when the function is analyzed as a top-level function. However,
when we do that, we can't be sure it isn't also inlined elsewhere (such as
in a recursive function, or if we want to analyze in both general or
specialized cases). Rather than have all checkInlined calls have an appended,
meaningless "FALSE" or "TOP-LEVEL" case, there is just no message printed.

void clang_analyzer_checkInlined(int);

For debugging purposes only!

llvm-svn: 161708
This commit is contained in:
Jordan Rose 2012-08-10 22:26:29 +00:00
parent 29622c1f0b
commit 13937b1d7a
2 changed files with 85 additions and 41 deletions

View File

@ -18,65 +18,102 @@ using namespace ento;
namespace {
class ExprInspectionChecker : public Checker< eval::Call > {
mutable OwningPtr<BugType> BT;
void analyzerEval(const CallExpr *CE, CheckerContext &C) const;
void analyzerCheckInlined(const CallExpr *CE, CheckerContext &C) const;
typedef void (ExprInspectionChecker::*FnCheck)(const CallExpr *,
CheckerContext &C) const;
public:
bool evalCall(const CallExpr *CE, CheckerContext &C) const;
};
}
bool ExprInspectionChecker::evalCall(const CallExpr *CE,
CheckerContext &C) const {
CheckerContext &C) const {
// These checks should have no effect on the surrounding environment
// (globals should not be evaluated, etc), hence the use of evalCall.
// (globals should not be invalidated, etc), hence the use of evalCall.
FnCheck Handler = llvm::StringSwitch<FnCheck>(C.getCalleeName(CE))
.Case("clang_analyzer_eval", &ExprInspectionChecker::analyzerEval)
.Case("clang_analyzer_checkInlined",
&ExprInspectionChecker::analyzerCheckInlined)
.Default(0);
if (!Handler)
return false;
(this->*Handler)(CE, C);
return true;
}
static const char *getArgumentValueString(const CallExpr *CE,
CheckerContext &C) {
if (CE->getNumArgs() == 0)
return "Missing assertion argument";
ExplodedNode *N = C.getPredecessor();
const LocationContext *LC = N->getLocationContext();
ProgramStateRef State = N->getState();
const Expr *Assertion = CE->getArg(0);
SVal AssertionVal = State->getSVal(Assertion, LC);
if (AssertionVal.isUndef())
return "UNDEFINED";
ProgramStateRef StTrue, StFalse;
llvm::tie(StTrue, StFalse) =
State->assume(cast<DefinedOrUnknownSVal>(AssertionVal));
if (StTrue) {
if (StFalse)
return "UNKNOWN";
else
return "TRUE";
} else {
if (StFalse)
return "FALSE";
else
llvm_unreachable("Invalid constraint; neither true or false.");
}
}
void ExprInspectionChecker::analyzerEval(const CallExpr *CE,
CheckerContext &C) const {
ExplodedNode *N = C.getPredecessor();
const LocationContext *LC = N->getLocationContext();
if (!C.getCalleeName(CE).equals("clang_analyzer_eval"))
return false;
// A specific instantiation of an inlined function may have more constrained
// values than can generally be assumed. Skip the check.
if (LC->getParent() != 0)
return true;
const char *Msg = 0;
if (CE->getNumArgs() == 0)
Msg = "Missing assertion argument";
else {
ProgramStateRef State = N->getState();
const Expr *Assertion = CE->getArg(0);
SVal AssertionVal = State->getSVal(Assertion, LC);
if (AssertionVal.isUndef())
Msg = "UNDEFINED";
else {
ProgramStateRef StTrue, StFalse;
llvm::tie(StTrue, StFalse) =
State->assume(cast<DefinedOrUnknownSVal>(AssertionVal));
if (StTrue) {
if (StFalse)
Msg = "UNKNOWN";
else
Msg = "TRUE";
} else {
if (StFalse)
Msg = "FALSE";
else
llvm_unreachable("Invalid constraint; neither true or false.");
}
}
}
assert(Msg);
if (LC->getCurrentStackFrame()->getParent() != 0)
return;
if (!BT)
BT.reset(new BugType("Checking analyzer assumptions", "debug"));
BugReport *R = new BugReport(*BT, Msg, N);
BugReport *R = new BugReport(*BT, getArgumentValueString(CE, C), N);
C.EmitReport(R);
}
return true;
void ExprInspectionChecker::analyzerCheckInlined(const CallExpr *CE,
CheckerContext &C) const {
ExplodedNode *N = C.getPredecessor();
const LocationContext *LC = N->getLocationContext();
// An inlined function could conceivably also be analyzed as a top-level
// function. We ignore this case and only emit a message (TRUE or FALSE)
// when we are analyzing it as an inlined function. This means that
// clang_analyzer_checkInlined(true) should always print TRUE, but
// clang_analyzer_checkInlined(false) should never actually print anything.
if (LC->getCurrentStackFrame()->getParent() == 0)
return;
if (!BT)
BT.reset(new BugType("Checking analyzer assumptions", "debug"));
BugReport *R = new BugReport(*BT, getArgumentValueString(CE, C), N);
C.EmitReport(R);
}
void ento::registerExprInspectionChecker(CheckerManager &Mgr) {

View File

@ -1,10 +1,12 @@
// RUN: %clang_cc1 -analyze -analyzer-checker=core,debug.ExprInspection -analyzer-ipa=inlining -analyzer-store region -verify %s
void clang_analyzer_eval(int);
void clang_analyzer_checkInlined(int);
int test1_f1() {
int y = 1;
y++;
clang_analyzer_checkInlined(1); // expected-warning{{TRUE}}
return y;
}
@ -103,3 +105,8 @@ int plus1(int x) {
return x + 1;
}
void never_called_by_anyone() {
clang_analyzer_checkInlined(0); // no-warning
}