[Static Analyzer] Lambda support.

Differential Revision: http://reviews.llvm.org/D12652

llvm-svn: 247426
This commit is contained in:
Gabor Horvath 2015-09-11 16:55:01 +00:00
parent 6292128698
commit 15843343b6
10 changed files with 495 additions and 13 deletions

View File

@ -256,6 +256,9 @@ private:
/// \sa getMaxNodesPerTopLevelFunction
Optional<unsigned> MaxNodesPerTopLevelFunction;
/// \sa shouldInlineLambdas
Optional<bool> InlineLambdas;
/// A helper function that retrieves option for a given full-qualified
/// checker name.
/// Options for checkers can be specified via 'analyzer-config' command-line
@ -509,6 +512,10 @@ public:
/// This is controlled by the 'max-nodes' config option.
unsigned getMaxNodesPerTopLevelFunction();
/// Returns true if lambdas should be inlined. Otherwise a sink node will be
/// generated each time a LambdaExpr is visited.
bool shouldInlineLambdas();
public:
AnalyzerOptions() :
AnalysisStoreOpt(RegionStoreModel),

View File

@ -341,6 +341,10 @@ public:
void VisitBlockExpr(const BlockExpr *BE, ExplodedNode *Pred,
ExplodedNodeSet &Dst);
/// VisitLambdaExpr - Transfer function logic for LambdaExprs.
void VisitLambdaExpr(const LambdaExpr *LE, ExplodedNode *Pred,
ExplodedNodeSet &Dst);
/// VisitBinaryOperator - Transfer function logic for binary operators.
void VisitBinaryOperator(const BinaryOperator* B, ExplodedNode *Pred,
ExplodedNodeSet &Dst);

View File

@ -325,3 +325,7 @@ bool AnalyzerOptions::shouldPrunePaths() {
bool AnalyzerOptions::shouldConditionalizeStaticInitializers() {
return getBooleanOption("cfg-conditional-static-initializers", true);
}
bool AnalyzerOptions::shouldInlineLambdas() {
return getBooleanOption("inline-lambdas", /*Default=*/true);
}

View File

@ -769,7 +769,6 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
case Stmt::SEHTryStmtClass:
case Stmt::SEHExceptStmtClass:
case Stmt::SEHLeaveStmtClass:
case Stmt::LambdaExprClass:
case Stmt::SEHFinallyStmtClass: {
const ExplodedNode *node = Bldr.generateSink(S, Pred, Pred->getState());
Engine.addAbortedBlock(node, currBldrCtx->getBlock());
@ -1013,6 +1012,17 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
Bldr.addNodes(Dst);
break;
case Stmt::LambdaExprClass:
if (AMgr.options.shouldInlineLambdas()) {
Bldr.takeNodes(Pred);
VisitLambdaExpr(cast<LambdaExpr>(S), Pred, Dst);
Bldr.addNodes(Dst);
} else {
const ExplodedNode *node = Bldr.generateSink(S, Pred, Pred->getState());
Engine.addAbortedBlock(node, currBldrCtx->getBlock());
}
break;
case Stmt::BinaryOperatorClass: {
const BinaryOperator* B = cast<BinaryOperator>(S);
if (B->isLogicalOp()) {
@ -1853,11 +1863,35 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D,
// C permits "extern void v", and if you cast the address to a valid type,
// you can even do things with it. We simply pretend
assert(Ex->isGLValue() || VD->getType()->isVoidType());
SVal V = state->getLValue(VD, Pred->getLocationContext());
const LocationContext *LocCtxt = Pred->getLocationContext();
const Decl *D = LocCtxt->getDecl();
const auto *MD = D ? dyn_cast<CXXMethodDecl>(D) : nullptr;
const auto *DeclRefEx = dyn_cast<DeclRefExpr>(Ex);
SVal V;
bool CaptureByReference = false;
if (AMgr.options.shouldInlineLambdas() && DeclRefEx &&
DeclRefEx->refersToEnclosingVariableOrCapture() && MD &&
MD->getParent()->isLambda()) {
// Lookup the field of the lambda.
const CXXRecordDecl *CXXRec = MD->getParent();
llvm::DenseMap<const VarDecl *, FieldDecl *> LambdaCaptureFields;
FieldDecl *LambdaThisCaptureField;
CXXRec->getCaptureFields(LambdaCaptureFields, LambdaThisCaptureField);
const FieldDecl *FD = LambdaCaptureFields[VD];
Loc CXXThis =
svalBuilder.getCXXThis(MD, LocCtxt->getCurrentStackFrame());
SVal CXXThisVal = state->getSVal(CXXThis);
V = state->getLValue(FD, CXXThisVal);
if (FD->getType()->isReferenceType() &&
!VD->getType()->isReferenceType())
CaptureByReference = true;
} else {
V = state->getLValue(VD, LocCtxt);
}
// For references, the 'lvalue' is the pointer address stored in the
// reference region.
if (VD->getType()->isReferenceType()) {
if (VD->getType()->isReferenceType() || CaptureByReference) {
if (const MemRegion *R = V.getAsRegion())
V = state->getSVal(R);
else

View File

@ -513,3 +513,41 @@ void ExprEngine::VisitCXXThisExpr(const CXXThisExpr *TE, ExplodedNode *Pred,
SVal V = state->getSVal(loc::MemRegionVal(R));
Bldr.generateNode(TE, Pred, state->BindExpr(TE, LCtx, V));
}
void ExprEngine::VisitLambdaExpr(const LambdaExpr *LE, ExplodedNode *Pred,
ExplodedNodeSet &Dst) {
const LocationContext *LocCtxt = Pred->getLocationContext();
// Get the region of the lambda itself.
const MemRegion *R = svalBuilder.getRegionManager().getCXXTempObjectRegion(
LE, LocCtxt);
SVal V = loc::MemRegionVal(R);
ProgramStateRef State = Pred->getState();
// If we created a new MemRegion for the lambda, we should explicitly bind
// the captures.
CXXRecordDecl::field_iterator CurField = LE->getLambdaClass()->field_begin();
for (LambdaExpr::const_capture_init_iterator i = LE->capture_init_begin(),
e = LE->capture_init_end();
i != e; ++i, ++CurField) {
SVal Field = State->getLValue(*CurField, V);
SVal InitExpr = State->getSVal(*i, LocCtxt);
State = State->bindLoc(Field, InitExpr);
}
// Decay the Loc into an RValue, because there might be a
// MaterializeTemporaryExpr node above this one which expects the bound value
// to be an RValue.
SVal LambdaRVal = State->getSVal(R);
ExplodedNodeSet Tmp;
StmtNodeBuilder Bldr(Pred, Tmp, *currBldrCtx);
// FIXME: is this the right program point kind?
Bldr.generateNode(LE, Pred,
State->BindExpr(LE, LocCtxt, LambdaRVal),
nullptr, ProgramPoint::PostLValueKind);
// FIXME: Move all post/pre visits to ::Visit().
getCheckerManager().runCheckersForPostStmt(Dst, Tmp, LE, *this);
}

View File

@ -1013,10 +1013,21 @@ MemRegionManager::getCXXBaseObjectRegion(const CXXRecordDecl *RD,
const CXXThisRegion*
MemRegionManager::getCXXThisRegion(QualType thisPointerTy,
const LocationContext *LC) {
const StackFrameContext *STC = LC->getCurrentStackFrame();
assert(STC);
const PointerType *PT = thisPointerTy->getAs<PointerType>();
assert(PT);
// Inside the body of the operator() of a lambda a this expr might refer to an
// object in one of the parent location contexts.
const auto *D = dyn_cast<CXXMethodDecl>(LC->getDecl());
// FIXME: when operator() of lambda is analyzed as a top level function and
// 'this' refers to a this to the enclosing scope, there is no right region to
// return.
while (!LC->inTopFrame() &&
PT != D->getThisType(getContext())->getAs<PointerType>()) {
LC = LC->getParent();
D = dyn_cast<CXXMethodDecl>(LC->getDecl());
}
const StackFrameContext *STC = LC->getCurrentStackFrame();
assert(STC);
return getSubRegion<CXXThisRegion>(PT, getStackArgumentsRegion(STC));
}

View File

@ -174,3 +174,17 @@ int radar_13213575() {
return radar13213575_testit<true>(5) + radar13213575_testit<false>(3);
}
//===----------------------------------------------------------------------===//
// Dead store checking involving lambdas.
//===----------------------------------------------------------------------===//
int basicLambda(int i, int j) {
i = 5; // no warning
j = 6; // no warning
[i] { (void)i; }();
[&j] { (void)j; }();
i = 2;
j = 3;
return i + j;
}

View File

@ -0,0 +1,204 @@
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -analyze -analyzer-checker=core -analyzer-config inline-lambdas=true -analyzer-output plist -verify %s -o %t
// RUN: FileCheck --input-file=%t %s
// Diagnostic inside a lambda
void diagnosticFromLambda() {
int i = 0;
[=] {
int p = 5/i; // expected-warning{{Division by zero}}
(void)p;
}();
}
// CHECK: <array>
// CHECK: <dict>
// CHECK: <key>path</key>
// CHECK: <array>
// CHECK: <dict>
// CHECK: <key>kind</key><string>control</string>
// CHECK: <key>edges</key>
// CHECK: <array>
// CHECK: <dict>
// CHECK: <key>start</key>
// CHECK: <array>
// CHECK: <dict>
// CHECK: <key>line</key><integer>8</integer>
// CHECK: <key>col</key><integer>3</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: <dict>
// CHECK: <key>line</key><integer>8</integer>
// CHECK: <key>col</key><integer>5</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: </array>
// CHECK: <key>end</key>
// CHECK: <array>
// CHECK: <dict>
// CHECK: <key>line</key><integer>9</integer>
// CHECK: <key>col</key><integer>3</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: <dict>
// CHECK: <key>line</key><integer>9</integer>
// CHECK: <key>col</key><integer>3</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: </array>
// CHECK: </dict>
// CHECK: </array>
// CHECK: </dict>
// CHECK: <dict>
// CHECK: <key>kind</key><string>event</string>
// CHECK: <key>location</key>
// CHECK: <dict>
// CHECK: <key>line</key><integer>9</integer>
// CHECK: <key>col</key><integer>3</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: <key>ranges</key>
// CHECK: <array>
// CHECK: <array>
// CHECK: <dict>
// CHECK: <key>line</key><integer>9</integer>
// CHECK: <key>col</key><integer>3</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: <dict>
// CHECK: <key>line</key><integer>12</integer>
// CHECK: <key>col</key><integer>5</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: </array>
// CHECK: </array>
// CHECK: <key>depth</key><integer>0</integer>
// CHECK: <key>extended_message</key>
// CHECK: <string>The value 0 is assigned to field &apos;&apos;</string>
// CHECK: <key>message</key>
// CHECK: <string>The value 0 is assigned to field &apos;&apos;</string>
// CHECK: </dict>
// CHECK: <dict>
// CHECK: <key>kind</key><string>event</string>
// CHECK: <key>location</key>
// CHECK: <dict>
// CHECK: <key>line</key><integer>9</integer>
// CHECK: <key>col</key><integer>3</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: <key>ranges</key>
// CHECK: <array>
// CHECK: <array>
// CHECK: <dict>
// CHECK: <key>line</key><integer>9</integer>
// CHECK: <key>col</key><integer>3</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: <dict>
// CHECK: <key>line</key><integer>12</integer>
// CHECK: <key>col</key><integer>5</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: </array>
// CHECK: </array>
// CHECK: <key>depth</key><integer>0</integer>
// CHECK: <key>extended_message</key>
// CHECK: <string>Calling &apos;operator()&apos;</string>
// CHECK: <key>message</key>
// CHECK: <string>Calling &apos;operator()&apos;</string>
// CHECK: </dict>
// CHECK: <dict>
// CHECK: <key>kind</key><string>event</string>
// CHECK: <key>location</key>
// CHECK: <dict>
// CHECK: <key>line</key><integer>9</integer>
// CHECK: <key>col</key><integer>5</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: <key>depth</key><integer>1</integer>
// CHECK: <key>extended_message</key>
// CHECK: <string>Entered call from &apos;diagnosticFromLambda&apos;</string>
// CHECK: <key>message</key>
// CHECK: <string>Entered call from &apos;diagnosticFromLambda&apos;</string>
// CHECK: </dict>
// CHECK: <dict>
// CHECK: <key>kind</key><string>control</string>
// CHECK: <key>edges</key>
// CHECK: <array>
// CHECK: <dict>
// CHECK: <key>start</key>
// CHECK: <array>
// CHECK: <dict>
// CHECK: <key>line</key><integer>9</integer>
// CHECK: <key>col</key><integer>5</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: <dict>
// CHECK: <key>line</key><integer>9</integer>
// CHECK: <key>col</key><integer>5</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: </array>
// CHECK: <key>end</key>
// CHECK: <array>
// CHECK: <dict>
// CHECK: <key>line</key><integer>10</integer>
// CHECK: <key>col</key><integer>14</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: <dict>
// CHECK: <key>line</key><integer>10</integer>
// CHECK: <key>col</key><integer>14</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: </array>
// CHECK: </dict>
// CHECK: </array>
// CHECK: </dict>
// CHECK: <dict>
// CHECK: <key>kind</key><string>event</string>
// CHECK: <key>location</key>
// CHECK: <dict>
// CHECK: <key>line</key><integer>10</integer>
// CHECK: <key>col</key><integer>14</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: <key>ranges</key>
// CHECK: <array>
// CHECK: <array>
// CHECK: <dict>
// CHECK: <key>line</key><integer>10</integer>
// CHECK: <key>col</key><integer>13</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: <dict>
// CHECK: <key>line</key><integer>10</integer>
// CHECK: <key>col</key><integer>15</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: </array>
// CHECK: </array>
// CHECK: <key>depth</key><integer>1</integer>
// CHECK: <key>extended_message</key>
// CHECK: <string>Division by zero</string>
// CHECK: <key>message</key>
// CHECK: <string>Division by zero</string>
// CHECK: </dict>
// CHECK: </array>
// CHECK: <key>description</key><string>Division by zero</string>
// CHECK: <key>category</key><string>Logic error</string>
// CHECK: <key>type</key><string>Division by zero</string>
// CHECK: <key>check_name</key><string>core.DivideZero</string>
// CHECK: <key>issue_context_kind</key><string>C++ method</string>
// CHECK: <key>issue_context</key><string>operator()</string>
// CHECK: <key>issue_hash</key><string>1</string>
// CHECK: <key>location</key>
// CHECK: <dict>
// CHECK: <key>line</key><integer>10</integer>
// CHECK: <key>col</key><integer>14</integer>
// CHECK: <key>file</key><integer>0</integer>
// CHECK: </dict>
// CHECK: </dict>
// CHECK: </array>

View File

@ -1,9 +1,181 @@
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -analyze -analyzer-checker=debug.DumpCFG %s > %t 2>&1
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -analyze -analyzer-checker=core,debug.ExprInspection -analyzer-config inline-lambdas=true -verify %s
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -analyze -analyzer-checker=core,debug.DumpCFG -analyzer-config inline-lambdas=true %s > %t 2>&1
// RUN: FileCheck --input-file=%t %s
void clang_analyzer_warnIfReached();
void clang_analyzer_eval(int);
struct X { X(const X&); };
void f(X x) { (void) [x]{}; }
// Lambda semantics tests.
void basicCapture() {
int i = 5;
[i]() mutable {
// clang_analyzer_eval does nothing in inlined functions.
if (i != 5)
clang_analyzer_warnIfReached();
++i;
}();
[&i] {
if (i != 5)
clang_analyzer_warnIfReached();
}();
[&i] {
if (i != 5)
clang_analyzer_warnIfReached();
i++;
}();
clang_analyzer_eval(i == 6); // expected-warning{{TRUE}}
}
void deferredLambdaCall() {
int i = 5;
auto l1 = [i]() mutable {
if (i != 5)
clang_analyzer_warnIfReached();
++i;
};
auto l2 = [&i] {
if (i != 5)
clang_analyzer_warnIfReached();
};
auto l3 = [&i] {
if (i != 5)
clang_analyzer_warnIfReached();
i++;
};
l1();
l2();
l3();
clang_analyzer_eval(i == 6); // expected-warning{{TRUE}}
}
void multipleCaptures() {
int i = 5, j = 5;
[i, &j]() mutable {
if (i != 5 && j != 5)
clang_analyzer_warnIfReached();
++i;
++j;
}();
clang_analyzer_eval(i == 5); // expected-warning{{TRUE}}
clang_analyzer_eval(j == 6); // expected-warning{{TRUE}}
[=]() mutable {
if (i != 5 && j != 6)
clang_analyzer_warnIfReached();
++i;
++j;
}();
clang_analyzer_eval(i == 5); // expected-warning{{TRUE}}
clang_analyzer_eval(j == 6); // expected-warning{{TRUE}}
[&]() mutable {
if (i != 5 && j != 6)
clang_analyzer_warnIfReached();
++i;
++j;
}();
clang_analyzer_eval(i == 6); // expected-warning{{TRUE}}
clang_analyzer_eval(j == 7); // expected-warning{{TRUE}}
}
void testReturnValue() {
int i = 5;
auto l = [i] (int a) {
return i + a;
};
int b = l(3);
clang_analyzer_eval(b == 8); // expected-warning{{TRUE}}
}
// Nested lambdas.
void testNestedLambdas() {
int i = 5;
auto l = [i]() mutable {
[&i]() {
++i;
}();
if (i != 6)
clang_analyzer_warnIfReached();
};
l();
clang_analyzer_eval(i == 5); // expected-warning{{TRUE}}
}
// Captured this.
class RandomClass {
int i;
void captureFields() {
i = 5;
[this]() {
// clang_analyzer_eval does nothing in inlined functions.
if (i != 5)
clang_analyzer_warnIfReached();
++i;
}();
clang_analyzer_eval(i == 6); // expected-warning{{TRUE}}
}
};
// Nested this capture.
class RandomClass2 {
int i;
void captureFields() {
i = 5;
[this]() {
// clang_analyzer_eval does nothing in inlined functions.
if (i != 5)
clang_analyzer_warnIfReached();
++i;
[this]() {
// clang_analyzer_eval does nothing in inlined functions.
if (i != 6)
clang_analyzer_warnIfReached();
++i;
}();
}();
clang_analyzer_eval(i == 7); // expected-warning{{TRUE}}
}
};
// Captured function pointers.
void inc(int &x) {
++x;
}
void testFunctionPointerCapture() {
void (*func)(int &) = inc;
int i = 5;
[&i, func] {
func(i);
}();
clang_analyzer_eval(i == 6); // expected-warning{{TRUE}}
}
// Test inline defensive checks
int getNum();
void inlineDefensiveChecks() {
int i = getNum();
[=]() {
if (i == 0)
;
}();
int p = 5/i;
(void)p;
}
// CHECK: [B2 (ENTRY)]
// CHECK: Succs (1): B1
// CHECK: [B1]

View File

@ -299,13 +299,7 @@ namespace destructors {
void testRecursiveFramesStart() { testRecursiveFrames(false); }
void testLambdas() {
// This is the test we would like to write:
// []() { check(NoReturnDtor()); } != nullptr || check(Dtor());
// But currently the analyzer stops when it encounters a lambda:
[] {};
// The CFG for this now looks correct, but we still do not reach the line
// below.
clang_analyzer_warnIfReached(); // FIXME: Should warn.
[]() { check(NoReturnDtor()); } != nullptr || check(Dtor());
}
void testGnuExpressionStatements(int v) {