diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -720,6 +720,8 @@ def UnusedLabel : DiagGroup<"unused-label">; def UnusedLambdaCapture : DiagGroup<"unused-lambda-capture">; def UnusedParameter : DiagGroup<"unused-parameter">; +def UnusedButSetParameter : DiagGroup<"unused-but-set-parameter">; +def UnusedButSetVariable : DiagGroup<"unused-but-set-variable">; def UnusedResult : DiagGroup<"unused-result">; def PotentiallyEvaluatedExpression : DiagGroup<"potentially-evaluated-expression">; def UnevaluatedExpression : DiagGroup<"unevaluated-expression", diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -310,8 +310,12 @@ "repeated RISC-V 'interrupt' attribute is here">; def warn_unused_parameter : Warning<"unused parameter %0">, InGroup, DefaultIgnore; +def warn_unused_but_set_parameter : Warning<"parameter %0 set but not used">, + InGroup, DefaultIgnore; def warn_unused_variable : Warning<"unused variable %0">, InGroup, DefaultIgnore; +def warn_unused_but_set_variable : Warning<"variable %0 set but not used">, + InGroup, DefaultIgnore; def warn_unused_local_typedef : Warning< "unused %select{typedef|type alias}0 %1">, InGroup, DefaultIgnore; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2827,6 +2827,13 @@ /// ParmVarDecl pointers. void DiagnoseUnusedParameters(ArrayRef Parameters); + /// Diagnose any unused but set parameters in the given sequence of + /// ParmVarDecl pointers. + void DiagnoseUnusedButSetParameters(ArrayRef Parameters); + + /// Diagnose any unused but set variables declared in this CompoundStmt + void DiagnoseUnusedButSetVariables(CompoundStmt *CS); + /// Diagnose whether the size of parameters or return value of a /// function or obj-c method definition is pass-by-value and larger than a /// specified threshold. diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -24,6 +24,7 @@ #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/NonTrivialTypeVisitor.h" +#include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/StmtCXX.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/PartialDiagnostic.h" @@ -43,6 +44,7 @@ #include "clang/Sema/ScopeInfo.h" #include "clang/Sema/SemaInternal.h" #include "clang/Sema/Template.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/Triple.h" #include @@ -13733,6 +13735,143 @@ } } +// values in Map should be true. traverses Body; if any key is used in any way +// other than assigning to it, sets the corresponding value to false. +static void AreAllUsesSets(Stmt *Body, + llvm::SmallDenseMap *Map) { + + struct AllUsesAreSetsVisitor : RecursiveASTVisitor { + llvm::SmallDenseMap *M; + unsigned FalseCount = 0; + + AllUsesAreSetsVisitor(llvm::SmallDenseMap *Map) + : M(Map) {} + + bool TraverseBinaryOperator(BinaryOperator *BO) { + auto *LHS = BO->getLHS(); + auto *DRE = dyn_cast(LHS); + if (!DRE || M->find(DRE->getFoundDecl()) == M->end()) { + // the LHS is not a reference to one of our NamedDecls + TraverseStmt(LHS); + } + TraverseStmt(BO->getRHS()); + return true; + } + + bool VisitDeclRefExpr(const DeclRefExpr *DRE) { + auto iter = M->find(DRE->getFoundDecl()); + if (iter != M->end() && iter->second) { + // we've found a use of this Decl that's not the LHS of an assignment + iter->second = false; + ++FalseCount; + if (FalseCount == M->size()) { + // we've found uses for every Decl in Map; no need to continue walking + // the AST + return false; + } + } + return true; + } + }; + + if (!Map->size()) + return; + + AllUsesAreSetsVisitor Visitor(Map); + Visitor.TraverseStmt(Body); +} + +template +static void DiagnoseUnusedButSetDecls(Sema *Se, Stmt *Body, R Decls, + unsigned DiagID) { + // put the Decls in a map so we only have to traverse the body once for all of + // them + llvm::SmallDenseMap AllUsesAreSets; + + for (NamedDecl *ND : Decls) { + AllUsesAreSets.insert(std::make_pair(ND, true)); + } + + AreAllUsesSets(Body, &AllUsesAreSets); + + for (const auto Pair : AllUsesAreSets) { + if (Pair.second) { + Se->Diag(Pair.first->getLocation(), DiagID) << Pair.first->getDeclName(); + } + } +} + +void Sema::DiagnoseUnusedButSetParameters(ArrayRef Parameters) { + // Don't diagnose unused-but-set-parameter errors in template instantiations; + // we will already have done so in the template itself. + if (inTemplateInstantiation()) + return; + + bool CPlusPlus = getLangOpts().CPlusPlus; + + auto IsCandidate = [CPlusPlus, &Diags = Diags](ParmVarDecl *P) { + // check for Ignored here, because if we have no candidates we can avoid + // walking the AST + if (Diags.getDiagnosticLevel(diag::warn_unused_but_set_parameter, + P->getLocation()) == + DiagnosticsEngine::Ignored) + return false; + if (!P->isReferenced() || !P->getDeclName() || P->hasAttr()) + return false; + // mimic gcc's behavior regarding nonscalar types + if (CPlusPlus && !P->getType()->isScalarType()) + return false; + return true; + }; + + auto Candidates = llvm::make_filter_range(Parameters, IsCandidate); + + if (Parameters.empty()) + return; + + Decl *D = Decl::castFromDeclContext((*Parameters.begin())->getDeclContext()); + Stmt *Body = D->getBody(); + if (Body) + DiagnoseUnusedDecls(this, Body, Candidates, + diag::warn_unused_but_set_parameter); +} + +void Sema::DiagnoseUnusedButSetVariables(CompoundStmt *CS) { + bool CPlusPlus = getLangOpts().CPlusPlus; + + auto IsCandidate = [CPlusPlus, &Diags = Diags](Stmt *S) { + DeclStmt *SD = dyn_cast(S); + if (!SD || !SD->isSingleDecl()) + return false; + VarDecl *VD = dyn_cast(SD->getSingleDecl()); + if (!VD || !VD->isReferenced() || !VD->getDeclName() || + VD->hasAttr()) + return false; + // check for Ignored here, because if we have no candidates we can avoid + // walking the AST + if (Diags.getDiagnosticLevel(diag::warn_unused_but_set_variable, + VD->getLocation()) == + DiagnosticsEngine::Ignored) + return false; + // mimic gcc's behavior regarding nonscalar types + if (CPlusPlus && !VD->getType()->isScalarType()) + return false; + return true; + }; + + auto Candidates = llvm::make_filter_range(CS->body(), IsCandidate); + + auto ToNamedDecl = [](Stmt *S) { + DeclStmt *SD = dyn_cast(S); + return dyn_cast(SD->getSingleDecl()); + }; + + auto CandidateDecls = llvm::map_range(Candidates, ToNamedDecl); + + DiagnoseUnusedDecls(this, CS, CandidateDecls, + diag::warn_unused_but_set_variable); +} + void Sema::DiagnoseSizeOfParametersAndReturnValue( ArrayRef Parameters, QualType ReturnTy, NamedDecl *D) { if (LangOpts.NumLargeByValueCopy == 0) // No check. @@ -14435,8 +14574,10 @@ if (!FD->isInvalidDecl()) { // Don't diagnose unused parameters of defaulted or deleted functions. - if (!FD->isDeleted() && !FD->isDefaulted() && !FD->hasSkippedBody()) + if (!FD->isDeleted() && !FD->isDefaulted() && !FD->hasSkippedBody()) { DiagnoseUnusedParameters(FD->parameters()); + DiagnoseUnusedButSetParameters(FD->parameters()); + } DiagnoseSizeOfParametersAndReturnValue(FD->parameters(), FD->getReturnType(), FD); diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -15498,6 +15498,9 @@ BD->setBody(cast(Body)); + // wait to diagnose unused but set parameters until after setBody + DiagnoseUnusedButSetParameters(BD->parameters()); + if (Body && getCurFunction()->HasPotentialAvailabilityViolations) DiagnoseUnguardedAvailabilityViolations(BD); diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -433,7 +433,9 @@ DiagnoseEmptyLoopBody(Elts[i], Elts[i + 1]); } - return CompoundStmt::Create(Context, Elts, L, R); + CompoundStmt *CS = CompoundStmt::Create(Context, Elts, L, R); + DiagnoseUnusedButSetVariables(CS); + return CS; } ExprResult diff --git a/clang/test/Sema/vector-gcc-compat.c b/clang/test/Sema/vector-gcc-compat.c --- a/clang/test/Sema/vector-gcc-compat.c +++ b/clang/test/Sema/vector-gcc-compat.c @@ -35,7 +35,7 @@ void arithmeticTest(void) { v2i64 v2i64_a = (v2i64){0, 1}; - v2i64 v2i64_r; + v2i64 v2i64_r; // expected-warning{{variable 'v2i64_r' set but not used}} v2i64_r = v2i64_a + 1; v2i64_r = v2i64_a - 1; @@ -58,7 +58,7 @@ void comparisonTest(void) { v2i64 v2i64_a = (v2i64){0, 1}; - v2i64 v2i64_r; + v2i64 v2i64_r; // expected-warning{{variable 'v2i64_r' set but not used}} v2i64_r = v2i64_a == 1; v2i64_r = v2i64_a != 1; @@ -78,8 +78,8 @@ void logicTest(void) { v2i64 v2i64_a = (v2i64){0, 1}; v2i64 v2i64_b = (v2i64){2, 1}; - v2i64 v2i64_c = (v2i64){3, 1}; - v2i64 v2i64_r; + v2i64 v2i64_c = (v2i64){3, 1}; // expected-warning{{variable 'v2i64_c' set but not used}} + v2i64 v2i64_r; // expected-warning{{variable 'v2i64_r' set but not used}} v2i64_r = !v2i64_a; // expected-error {{invalid argument type 'v2i64' (vector of 2 'long long' values) to unary expression}} v2i64_r = ~v2i64_a; @@ -132,7 +132,7 @@ void floatTestConstantComparison(void) { v4f32 v4f32_a = {0.4f, 0.4f, 0.4f, 0.4f}; - v4i32 v4i32_r; + v4i32 v4i32_r; // expected-warning{{variable 'v4i32_r' set but not used}} v4i32_r = v4f32_a > 0.4f; v4i32_r = v4f32_a >= 0.4f; v4i32_r = v4f32_a < 0.4f; @@ -143,7 +143,7 @@ void doubleTestConstantComparison(void) { v2f64 v2f64_a = {0.4, 0.4}; - v2i64 v2i64_r; + v2i64 v2i64_r; // expected-warning{{variable 'v2i64_r' set but not used}} v2i64_r = v2f64_a > 0.4; v2i64_r = v2f64_a >= 0.4; v2i64_r = v2f64_a < 0.4; diff --git a/clang/test/Sema/warn-unused-but-set-parameters-cpp.cpp b/clang/test/Sema/warn-unused-but-set-parameters-cpp.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Sema/warn-unused-but-set-parameters-cpp.cpp @@ -0,0 +1,27 @@ +// RUN: %clang_cc1 -fblocks -fsyntax-only -Wunused-but-set-parameter -verify %s + +int f0(int x, + int y, // expected-warning{{parameter 'y' set but not used}} + int z __attribute__((unused))) { + y = 0; + return x; +} + +void f1(void) { + (void)^(int x, + int y, // expected-warning{{parameter 'y' set but not used}} + int z __attribute__((unused))) { + y = 0; + return x; + }; +} + +struct S { + int i; +}; + +// in C++, don't warn for a struct +void f3(struct S s) { + struct S t; + s = t; +} diff --git a/clang/test/Sema/warn-unused-but-set-parameters.c b/clang/test/Sema/warn-unused-but-set-parameters.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/warn-unused-but-set-parameters.c @@ -0,0 +1,26 @@ +// RUN: %clang_cc1 -fblocks -fsyntax-only -Wunused-but-set-parameter -verify %s + +int f0(int x, + int y, // expected-warning{{parameter 'y' set but not used}} + int z __attribute__((unused))) { + y = 0; + return x; +} + +void f1(void) { + (void)^(int x, + int y, // expected-warning{{parameter 'y' set but not used}} + int z __attribute__((unused))) { + y = 0; + return x; + }; +} + +struct S { + int i; +}; + +void f3(struct S s) { // expected-warning{{parameter 's' set but not used}} + struct S t; + s = t; +} diff --git a/clang/test/Sema/warn-unused-but-set-variables-cpp.cpp b/clang/test/Sema/warn-unused-but-set-variables-cpp.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Sema/warn-unused-but-set-variables-cpp.cpp @@ -0,0 +1,33 @@ +// RUN: %clang_cc1 -fblocks -fsyntax-only -Wunused-but-set-variable -verify %s + +struct S { + int i; +}; + +int f0() { + int y; // expected-warning{{variable 'y' set but not used}} + y = 0; + + int z __attribute__((unused)); + z = 0; + + // no warning for structs in C++ + struct S s; + struct S t; + s = t; + + int x; + x = 0; + return x; +} + +void f1(void) { + (void)^() { + int y; // expected-warning{{variable 'y' set but not used}} + y = 0; + + int x; + x = 0; + return x; + }; +} diff --git a/clang/test/Sema/warn-unused-but-set-variables.c b/clang/test/Sema/warn-unused-but-set-variables.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/warn-unused-but-set-variables.c @@ -0,0 +1,32 @@ +// RUN: %clang_cc1 -fblocks -fsyntax-only -Wunused-but-set-variable -verify %s + +struct S { + int i; +}; + +int f0() { + int y; // expected-warning{{variable 'y' set but not used}} + y = 0; + + int z __attribute__((unused)); + z = 0; + + struct S s; // expected-warning{{variable 's' set but not used}} + struct S t; + s = t; + + int x; + x = 0; + return x; +} + +void f1(void) { + (void)^() { + int y; // expected-warning{{variable 'y' set but not used}} + y = 0; + + int x; + x = 0; + return x; + }; +}