forked from OSchip/llvm-project
[Sema] Add warning for unused lambda captures
Summary: Warn when a lambda explicitly captures something that is not used in its body. The warning is part of -Wunused and can be enabled with -Wunused-lambda-capture. Reviewers: rsmith, arphaman, jbcoe, aaron.ballman Subscribers: Quuxplusone, arphaman, cfe-commits Differential Revision: https://reviews.llvm.org/D28467 llvm-svn: 291905
This commit is contained in:
parent
061f4a5fe6
commit
87a036259b
|
@ -480,6 +480,7 @@ def UnusedFunction : DiagGroup<"unused-function", [UnneededInternalDecl]>;
|
|||
def UnusedMemberFunction : DiagGroup<"unused-member-function",
|
||||
[UnneededMemberFunction]>;
|
||||
def UnusedLabel : DiagGroup<"unused-label">;
|
||||
def UnusedLambdaCapture : DiagGroup<"unused-lambda-capture">;
|
||||
def UnusedParameter : DiagGroup<"unused-parameter">;
|
||||
def UnusedResult : DiagGroup<"unused-result">;
|
||||
def PotentiallyEvaluatedExpression : DiagGroup<"potentially-evaluated-expression">;
|
||||
|
@ -617,8 +618,9 @@ def Unused : DiagGroup<"unused",
|
|||
[UnusedArgument, UnusedFunction, UnusedLabel,
|
||||
// UnusedParameter, (matches GCC's behavior)
|
||||
// UnusedMemberFunction, (clean-up llvm before enabling)
|
||||
UnusedPrivateField, UnusedLocalTypedef,
|
||||
UnusedValue, UnusedVariable, UnusedPropertyIvar]>,
|
||||
UnusedPrivateField, UnusedLambdaCapture,
|
||||
UnusedLocalTypedef, UnusedValue, UnusedVariable,
|
||||
UnusedPropertyIvar]>,
|
||||
DiagCategory<"Unused Entity Issue">;
|
||||
|
||||
// Format settings.
|
||||
|
|
|
@ -316,6 +316,9 @@ def warn_unneeded_member_function : Warning<
|
|||
InGroup<UnneededMemberFunction>, DefaultIgnore;
|
||||
def warn_unused_private_field: Warning<"private field %0 is not used">,
|
||||
InGroup<UnusedPrivateField>, DefaultIgnore;
|
||||
def warn_unused_lambda_capture: Warning<"lambda capture %0 is not "
|
||||
"%select{used|required to be captured for use in an unevaluated context}1">,
|
||||
InGroup<UnusedLambdaCapture>, DefaultIgnore;
|
||||
|
||||
def warn_parameter_size: Warning<
|
||||
"%0 is a large (%1 bytes) pass-by-value argument; "
|
||||
|
|
|
@ -452,6 +452,14 @@ public:
|
|||
/// non-static data member that would hold the capture.
|
||||
QualType CaptureType;
|
||||
|
||||
/// \brief Whether an explicit capture has been odr-used in the body of the
|
||||
/// lambda.
|
||||
bool ODRUsed;
|
||||
|
||||
/// \brief Whether an explicit capture has been non-odr-used in the body of
|
||||
/// the lambda.
|
||||
bool NonODRUsed;
|
||||
|
||||
public:
|
||||
Capture(VarDecl *Var, bool Block, bool ByRef, bool IsNested,
|
||||
SourceLocation Loc, SourceLocation EllipsisLoc,
|
||||
|
@ -460,7 +468,8 @@ public:
|
|||
InitExprAndCaptureKind(
|
||||
Cpy, !Var ? Cap_VLA : Block ? Cap_Block : ByRef ? Cap_ByRef
|
||||
: Cap_ByCopy),
|
||||
Loc(Loc), EllipsisLoc(EllipsisLoc), CaptureType(CaptureType) {}
|
||||
Loc(Loc), EllipsisLoc(EllipsisLoc), CaptureType(CaptureType),
|
||||
ODRUsed(false), NonODRUsed(false) {}
|
||||
|
||||
enum IsThisCapture { ThisCapture };
|
||||
Capture(IsThisCapture, bool IsNested, SourceLocation Loc,
|
||||
|
@ -468,7 +477,8 @@ public:
|
|||
: VarAndNestedAndThis(
|
||||
nullptr, (IsThisCaptured | (IsNested ? IsNestedCapture : 0))),
|
||||
InitExprAndCaptureKind(Cpy, ByCopy ? Cap_ByCopy : Cap_ByRef),
|
||||
Loc(Loc), EllipsisLoc(), CaptureType(CaptureType) {}
|
||||
Loc(Loc), EllipsisLoc(), CaptureType(CaptureType), ODRUsed(false),
|
||||
NonODRUsed(false) {}
|
||||
|
||||
bool isThisCapture() const {
|
||||
return VarAndNestedAndThis.getInt() & IsThisCaptured;
|
||||
|
@ -491,6 +501,9 @@ public:
|
|||
bool isNested() const {
|
||||
return VarAndNestedAndThis.getInt() & IsNestedCapture;
|
||||
}
|
||||
bool isODRUsed() const { return ODRUsed; }
|
||||
bool isNonODRUsed() const { return NonODRUsed; }
|
||||
void markUsed(bool IsODRUse) { (IsODRUse ? ODRUsed : NonODRUsed) = true; }
|
||||
|
||||
VarDecl *getVariable() const {
|
||||
return VarAndNestedAndThis.getPointer();
|
||||
|
|
|
@ -5323,6 +5323,9 @@ public:
|
|||
ExprResult ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body,
|
||||
Scope *CurScope);
|
||||
|
||||
/// \brief Diagnose if an explicit lambda capture is unused.
|
||||
void DiagnoseUnusedLambdaCapture(const sema::LambdaScopeInfo::Capture &From);
|
||||
|
||||
/// \brief Complete a lambda-expression having processed and attached the
|
||||
/// lambda body.
|
||||
ExprResult BuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc,
|
||||
|
|
|
@ -13916,8 +13916,10 @@ bool Sema::tryCaptureVariable(
|
|||
|
||||
// Check whether we've already captured it.
|
||||
if (isVariableAlreadyCapturedInScopeInfo(CSI, Var, Nested, CaptureType,
|
||||
DeclRefType))
|
||||
DeclRefType)) {
|
||||
CSI->getCapture(Var).markUsed(BuildAndDiagnose);
|
||||
break;
|
||||
}
|
||||
// If we are instantiating a generic lambda call operator body,
|
||||
// we do not want to capture new variables. What was captured
|
||||
// during either a lambdas transformation or initial parsing
|
||||
|
|
|
@ -1106,6 +1106,7 @@ bool Sema::CheckCXXThisCapture(SourceLocation Loc, const bool Explicit,
|
|||
dyn_cast<CapturingScopeInfo>(FunctionScopes[idx])) {
|
||||
if (CSI->CXXThisCaptureIndex != 0) {
|
||||
// 'this' is already being captured; there isn't anything more to do.
|
||||
CSI->Captures[CSI->CXXThisCaptureIndex - 1].markUsed(BuildAndDiagnose);
|
||||
break;
|
||||
}
|
||||
LambdaScopeInfo *LSI = dyn_cast<LambdaScopeInfo>(CSI);
|
||||
|
|
|
@ -1384,7 +1384,7 @@ static void addBlockPointerConversion(Sema &S,
|
|||
}
|
||||
|
||||
static ExprResult performLambdaVarCaptureInitialization(
|
||||
Sema &S, LambdaScopeInfo::Capture &Capture, FieldDecl *Field) {
|
||||
Sema &S, const LambdaScopeInfo::Capture &Capture, FieldDecl *Field) {
|
||||
assert(Capture.isVariableCapture() && "not a variable capture");
|
||||
|
||||
auto *Var = Capture.getVariable();
|
||||
|
@ -1438,6 +1438,21 @@ mapImplicitCaptureStyle(CapturingScopeInfo::ImplicitCaptureStyle ICS) {
|
|||
llvm_unreachable("Unknown implicit capture style");
|
||||
}
|
||||
|
||||
void Sema::DiagnoseUnusedLambdaCapture(const LambdaScopeInfo::Capture &From) {
|
||||
if (!From.isVLATypeCapture()) {
|
||||
Expr *Init = From.getInitExpr();
|
||||
if (Init && Init->HasSideEffects(Context))
|
||||
return;
|
||||
}
|
||||
|
||||
auto diag = Diag(From.getLocation(), diag::warn_unused_lambda_capture);
|
||||
if (From.isThisCapture())
|
||||
diag << "'this'";
|
||||
else
|
||||
diag << From.getVariable();
|
||||
diag << From.isNonODRUsed();
|
||||
}
|
||||
|
||||
ExprResult Sema::BuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc,
|
||||
LambdaScopeInfo *LSI) {
|
||||
// Collect information from the lambda scope.
|
||||
|
@ -1476,10 +1491,14 @@ ExprResult Sema::BuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc,
|
|||
// Translate captures.
|
||||
auto CurField = Class->field_begin();
|
||||
for (unsigned I = 0, N = LSI->Captures.size(); I != N; ++I, ++CurField) {
|
||||
LambdaScopeInfo::Capture From = LSI->Captures[I];
|
||||
const LambdaScopeInfo::Capture &From = LSI->Captures[I];
|
||||
assert(!From.isBlockCapture() && "Cannot capture __block variables");
|
||||
bool IsImplicit = I >= LSI->NumExplicitCaptures;
|
||||
|
||||
// Warn about unused explicit captures.
|
||||
if (!CurContext->isDependentContext() && !IsImplicit && !From.isODRUsed())
|
||||
DiagnoseUnusedLambdaCapture(From);
|
||||
|
||||
// Handle 'this' capture.
|
||||
if (From.isThisCapture()) {
|
||||
Captures.push_back(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// RUN: %clang_cc1 -std=c++11 %s -Wunused -verify
|
||||
// RUN: %clang_cc1 -std=c++11 %s -Wunused -Wno-unused-lambda-capture -verify
|
||||
|
||||
void odr_used() {
|
||||
int i = 17;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// RUN: %clang_cc1 -std=c++11 %s -Wunused -verify
|
||||
// RUN: %clang_cc1 -std=c++11 %s -Wunused -Wno-unused-lambda-capture -verify
|
||||
|
||||
void f2() {
|
||||
int i = 1;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// RUN: %clang_cc1 -std=c++11 %s -Wunused -verify
|
||||
// RUN: %clang_cc1 -std=c++11 %s -Wunused -Wno-unused-lambda-capture -verify
|
||||
|
||||
|
||||
struct X {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// RUN: %clang_cc1 -std=c++11 %s -Wunused -verify
|
||||
// RUN: %clang_cc1 -std=c++11 %s -Wunused -Wno-unused-lambda-capture -verify
|
||||
// expected-no-diagnostics
|
||||
|
||||
template<typename T, typename U>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// RUN: %clang_cc1 -std=c++11 %s -Wunused -verify
|
||||
// RUN: %clang_cc1 -std=c++11 %s -Wunused -Wno-unused-lambda-capture -verify
|
||||
|
||||
struct MoveOnly {
|
||||
MoveOnly(MoveOnly&&);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// RUN: %clang_cc1 -fsyntax-only -Wall -Wuninitialized -Wno-unused-value -std=c++11 -verify %s
|
||||
// RUN: %clang_cc1 -fsyntax-only -Wall -Wuninitialized -Wno-unused-value -Wno-unused-lambda-capture -std=c++11 -verify %s
|
||||
|
||||
// definitions for std::move
|
||||
namespace std {
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
// RUN: %clang_cc1 -fsyntax-only -Wunused-lambda-capture -Wused-but-marked-unused -Wno-uninitialized -verify -std=c++14 %s
|
||||
|
||||
class NonTrivialConstructor {
|
||||
public:
|
||||
NonTrivialConstructor() {}
|
||||
};
|
||||
|
||||
class NonTrivialDestructor {
|
||||
public:
|
||||
~NonTrivialDestructor() {}
|
||||
};
|
||||
|
||||
class Trivial {
|
||||
public:
|
||||
Trivial() = default;
|
||||
Trivial(int a) {}
|
||||
};
|
||||
|
||||
int side_effect() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
void test() {
|
||||
int i = 0;
|
||||
|
||||
auto captures_nothing = [] {};
|
||||
|
||||
auto captures_nothing_by_value = [=] {};
|
||||
auto captures_nothing_by_reference = [&] {};
|
||||
|
||||
auto implicit_by_value = [=]() mutable { i++; };
|
||||
auto implicit_by_reference = [&] { i++; };
|
||||
|
||||
auto explicit_by_value_used = [i] { return i + 1; };
|
||||
auto explicit_by_value_used_void = [i] { (void)i; };
|
||||
auto explicit_by_value_unused = [i] {}; // expected-warning{{lambda capture 'i' is not used}}
|
||||
auto explicit_by_value_unused_sizeof = [i] { return sizeof(i); }; // expected-warning{{lambda capture 'i' is not required to be captured for use in an unevaluated context}}
|
||||
auto explicit_by_value_unused_decltype = [i] { decltype(i) j = 0; }; // expected-warning{{lambda capture 'i' is not required to be captured for use in an unevaluated context}}
|
||||
|
||||
auto explicit_by_reference_used = [&i] { i++; };
|
||||
auto explicit_by_reference_unused = [&i] {}; // expected-warning{{lambda capture 'i' is not used}}
|
||||
|
||||
auto explicit_initialized_reference_used = [&j = i] { return j + 1; };
|
||||
auto explicit_initialized_reference_unused = [&j = i]{}; // expected-warning{{lambda capture 'j' is not used}}
|
||||
|
||||
auto explicit_initialized_value_used = [j = 1] { return j + 1; };
|
||||
auto explicit_initialized_value_unused = [j = 1] {}; // expected-warning{{lambda capture 'j' is not used}}
|
||||
auto explicit_initialized_value_non_trivial_constructor = [j = NonTrivialConstructor()]{};
|
||||
auto explicit_initialized_value_non_trivial_destructor = [j = NonTrivialDestructor()]{};
|
||||
auto explicit_initialized_value_trivial_init = [j = Trivial()]{}; // expected-warning{{lambda capture 'j' is not used}}
|
||||
auto explicit_initialized_value_non_trivial_init = [j = Trivial(42)]{};
|
||||
auto explicit_initialized_value_with_side_effect = [j = side_effect()]{};
|
||||
|
||||
auto nested = [&i] {
|
||||
auto explicit_by_value_used = [i] { return i + 1; };
|
||||
auto explicit_by_value_unused = [i] {}; // expected-warning{{lambda capture 'i' is not used}}
|
||||
};
|
||||
}
|
||||
|
||||
class Foo
|
||||
{
|
||||
void test() {
|
||||
auto explicit_this_used = [this] { return i; };
|
||||
auto explicit_this_used_void = [this] { (void)this; };
|
||||
auto explicit_this_unused = [this] {}; // expected-warning{{lambda capture 'this' is not used}}
|
||||
}
|
||||
int i;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void test_templated() {
|
||||
int i = 0;
|
||||
|
||||
auto captures_nothing = [] {};
|
||||
|
||||
auto captures_nothing_by_value = [=] {};
|
||||
auto captures_nothing_by_reference = [&] {};
|
||||
|
||||
auto implicit_by_value = [=]() mutable { i++; };
|
||||
auto implicit_by_reference = [&] { i++; };
|
||||
|
||||
auto explicit_by_value_used = [i] { return i + 1; };
|
||||
auto explicit_by_value_used_void = [i] { (void)i; };
|
||||
auto explicit_by_value_unused = [i] {}; // expected-warning{{lambda capture 'i' is not used}}
|
||||
auto explicit_by_value_unused_sizeof = [i] { return sizeof(i); }; // expected-warning{{lambda capture 'i' is not required to be captured for use in an unevaluated context}}
|
||||
auto explicit_by_value_unused_decltype = [i] { decltype(i) j = 0; }; // expected-warning{{lambda capture 'i' is not used}}
|
||||
|
||||
auto explicit_by_reference_used = [&i] { i++; };
|
||||
auto explicit_by_reference_unused = [&i] {}; // expected-warning{{lambda capture 'i' is not used}}
|
||||
|
||||
auto explicit_initialized_reference_used = [&j = i] { return j + 1; };
|
||||
auto explicit_initialized_reference_unused = [&j = i]{}; // expected-warning{{lambda capture 'j' is not used}}
|
||||
|
||||
auto explicit_initialized_value_used = [j = 1] { return j + 1; };
|
||||
auto explicit_initialized_value_unused = [j = 1] {}; // expected-warning{{lambda capture 'j' is not used}}
|
||||
auto explicit_initialized_value_non_trivial_constructor = [j = NonTrivialConstructor()]{};
|
||||
auto explicit_initialized_value_non_trivial_destructor = [j = NonTrivialDestructor()]{};
|
||||
auto explicit_initialized_value_trivial_init = [j = Trivial()]{}; // expected-warning{{lambda capture 'j' is not used}}
|
||||
auto explicit_initialized_value_non_trivial_init = [j = Trivial(42)]{};
|
||||
auto explicit_initialized_value_with_side_effect = [j = side_effect()]{};
|
||||
|
||||
auto nested = [&i] {
|
||||
auto explicit_by_value_used = [i] { return i + 1; };
|
||||
auto explicit_by_value_unused = [i] {}; // expected-warning{{lambda capture 'i' is not used}}
|
||||
};
|
||||
}
|
||||
|
||||
void test_use_template() {
|
||||
test_templated<int>(); // expected-note{{in instantiation of function template specialization 'test_templated<int>' requested here}}
|
||||
}
|
Loading…
Reference in New Issue