diff --git a/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp b/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp --- a/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp +++ b/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp @@ -79,14 +79,17 @@ return State; } -static internal::Matcher simpleCondition(StringRef BindName) { - return binaryOperator(anyOf(hasOperatorName("<"), hasOperatorName(">"), - hasOperatorName("<="), hasOperatorName(">="), - hasOperatorName("!=")), - hasEitherOperand(ignoringParenImpCasts(declRefExpr( - to(varDecl(hasType(isInteger())).bind(BindName))))), - hasEitherOperand(ignoringParenImpCasts( - integerLiteral().bind("boundNum")))) +static internal::Matcher simpleCondition(StringRef BindName, + StringRef RefName) { + return binaryOperator( + anyOf(hasOperatorName("<"), hasOperatorName(">"), + hasOperatorName("<="), hasOperatorName(">="), + hasOperatorName("!=")), + hasEitherOperand(ignoringParenImpCasts( + declRefExpr(to(varDecl(hasType(isInteger())).bind(BindName))) + .bind(RefName))), + hasEitherOperand( + ignoringParenImpCasts(integerLiteral().bind("boundNum")))) .bind("conditionOperator"); } @@ -138,7 +141,7 @@ static internal::Matcher forLoopMatcher() { return forStmt( - hasCondition(simpleCondition("initVarName")), + hasCondition(simpleCondition("initVarName", "initVarRef")), // Initialization should match the form: 'int i = 6' or 'i = 42'. hasLoopInit( anyOf(declStmt(hasSingleDecl( @@ -156,17 +159,52 @@ hasUnaryOperand(declRefExpr( to(varDecl(allOf(equalsBoundNode("initVarName"), hasType(isInteger())))))))), - unless(hasBody(hasSuspiciousStmt("initVarName")))).bind("forLoop"); + unless(hasBody(hasSuspiciousStmt("initVarName")))) + .bind("forLoop"); } -static bool isPossiblyEscaped(const VarDecl *VD, ExplodedNode *N) { +static bool isCapturedByReference(const VarDecl *VD, ExplodedNode *N, + const DeclRefExpr *DR) { + assert(DR->refersToEnclosingVariableOrCapture()); + + const LocationContext *LocCtxt = N->getLocationContext(); + const Decl *D = LocCtxt->getDecl(); + const auto *MD = dyn_cast_or_null(D); + + if (MD && MD->getParent()->isLambda()) { + // Lookup the field of the lambda. + const CXXRecordDecl *CXXRec = MD->getParent(); + llvm::DenseMap LambdaCaptureFields; + FieldDecl *LambdaThisCaptureField; + CXXRec->getCaptureFields(LambdaCaptureFields, LambdaThisCaptureField); + + // Sema follows a sequence of complex rules to determine whether the + // variable should be captured. + if (const FieldDecl *FD = LambdaCaptureFields[VD]) { + return FD->getType()->isReferenceType(); + } else { + assert(false && "Unknown captured variable"); + } + } else { + assert(false && + "Captured variable should only be seen while evaluating a lambda"); + } + return true; // When in doubt, assume the most conservative answer, which will + // disable loop unrolling +} + +static bool isPossiblyEscaped(const VarDecl *VD, ExplodedNode *N, + const DeclRefExpr *DR) { // Global variables assumed as escaped variables. if (VD->hasGlobalStorage()) return true; - const bool isParm = isa(VD); + const bool IsEntryValue = + isa(VD) || DR->refersToEnclosingVariableOrCapture(); // Reference parameters are assumed as escaped variables. - if (isParm && VD->getType()->isReferenceType()) + if ((DR->refersToEnclosingVariableOrCapture() && + isCapturedByReference(VD, N, DR)) || + (IsEntryValue && VD->getType()->isReferenceType())) return true; while (!N->pred_empty()) { @@ -199,8 +237,8 @@ N = N->getFirstPred(); } - // Parameter declaration will not be found. - if (isParm) + // Parameter declaration and lambda captures will not be found. + if (IsEntryValue) return false; llvm_unreachable("Reached root without finding the declaration of VD"); @@ -219,6 +257,7 @@ return false; auto CounterVar = Matches[0].getNodeAs("initVarName"); + auto CounterVarRef = Matches[0].getNodeAs("initVarRef"); llvm::APInt BoundNum = Matches[0].getNodeAs("boundNum")->getValue(); llvm::APInt InitNum = @@ -235,7 +274,8 @@ maxStep = (BoundNum - InitNum).abs().getZExtValue(); // Check if the counter of the loop is not escaped before. - return !isPossiblyEscaped(CounterVar->getCanonicalDecl(), Pred); + return !isPossiblyEscaped(CounterVar->getCanonicalDecl(), Pred, + CounterVarRef); } bool madeNewBranch(ExplodedNode *N, const Stmt *LoopStmt) { diff --git a/clang/test/Analysis/loop-unrolling.cpp b/clang/test/Analysis/loop-unrolling.cpp --- a/clang/test/Analysis/loop-unrolling.cpp +++ b/clang/test/Analysis/loop-unrolling.cpp @@ -1,5 +1,5 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config unroll-loops=true,cfg-loopexit=true -verify -std=c++11 -analyzer-config exploration_strategy=unexplored_first_queue %s -// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config unroll-loops=true,cfg-loopexit=true,exploration_strategy=dfs -verify -std=c++11 -DDFS=1 %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config unroll-loops=true,cfg-loopexit=true -verify -std=c++14 -analyzer-config exploration_strategy=unexplored_first_queue %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config unroll-loops=true,cfg-loopexit=true,exploration_strategy=dfs -verify -std=c++14 -DDFS=1 %s void clang_analyzer_numTimesReached(); void clang_analyzer_warnIfReached(); @@ -511,3 +511,39 @@ clang_analyzer_numTimesReached(); // expected-warning {{4}} } } + +void capture_by_value_as_loop_counter() { + int out = 0; + auto l = [i = out]() mutable { + for (i = 0; i < 10; ++i) { + clang_analyzer_numTimesReached(); // expected-warning {{10}} + } + }; +} + +void capture_by_ref_as_loop_counter() { + int out = 0; + auto l = [&i = out]() { + for (i = 0; i < 10; ++i) { + clang_analyzer_numTimesReached(); // expected-warning {{4}} + } + }; +} + +void capture_implicitly_by_value_as_loop_counter() { + int i = 0; + auto l = [=]() mutable { + for (i = 0; i < 10; ++i) { + clang_analyzer_numTimesReached(); // expected-warning {{10}} + } + }; +} + +void capture_implicitly_by_ref_as_loop_counter() { + int i = 0; + auto l = [&]() mutable { + for (i = 0; i < 10; ++i) { + clang_analyzer_numTimesReached(); // expected-warning {{4}} + } + }; +}