1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
15 #include "clang/AST/Attr.h"
23 bool isWarnUnusedType(QualType type
) {
24 if (auto const t
= type
->getAs
<TypedefType
>()) {
25 if (t
->getDecl()->hasAttr
<WarnUnusedAttr
>()) {
29 if (auto const t
= type
->getAs
<RecordType
>()) {
30 if (t
->getDecl()->hasAttr
<WarnUnusedAttr
>()) {
34 return loplugin::isExtraWarnUnusedType(type
);
37 Expr
const * lookThroughInitListExpr(Expr
const * expr
) {
38 if (auto const ile
= dyn_cast
<InitListExpr
>(expr
->IgnoreParenImpCasts())) {
39 if (ile
->getNumInits() == 1) {
40 return ile
->getInit(0);
46 class CastToVoid final
:
47 public loplugin::FilteringPlugin
<CastToVoid
>
50 explicit CastToVoid(loplugin::InstantiationData
const & data
):
51 FilteringPlugin(data
) {}
53 bool TraverseCStyleCastExpr(CStyleCastExpr
* expr
) {
54 auto const dre
= checkCast(expr
);
56 castToVoid_
.push({expr
, dre
});
58 auto const ret
= RecursiveASTVisitor::TraverseCStyleCastExpr(expr
);
60 assert(!castToVoid_
.empty());
61 assert(castToVoid_
.top().cast
== expr
);
62 assert(castToVoid_
.top().sub
== dre
);
68 bool TraverseCXXStaticCastExpr(CXXStaticCastExpr
* expr
) {
69 auto const dre
= checkCast(expr
);
71 castToVoid_
.push({expr
, dre
});
73 auto const ret
= RecursiveASTVisitor::TraverseCXXStaticCastExpr(expr
);
75 assert(!castToVoid_
.empty());
76 assert(castToVoid_
.top().cast
== expr
);
77 assert(castToVoid_
.top().sub
== dre
);
83 bool TraverseCXXFunctionalCastExpr(CXXFunctionalCastExpr
* expr
) {
84 auto const dre
= checkCast(expr
);
86 castToVoid_
.push({expr
, dre
});
88 auto const ret
= RecursiveASTVisitor::TraverseCXXFunctionalCastExpr(
91 assert(!castToVoid_
.empty());
92 assert(castToVoid_
.top().cast
== expr
);
93 assert(castToVoid_
.top().sub
== dre
);
99 bool TraverseFunctionDecl(FunctionDecl
* decl
) {
100 returnTypes_
.push(decl
->getReturnType());
101 auto const ret
= RecursiveASTVisitor::TraverseFunctionDecl(decl
);
102 assert(!returnTypes_
.empty());
103 assert(returnTypes_
.top() == decl
->getReturnType());
108 bool TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl
* decl
) {
109 returnTypes_
.push(decl
->getReturnType());
110 auto const ret
= RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(
112 assert(!returnTypes_
.empty());
113 assert(returnTypes_
.top() == decl
->getReturnType());
118 bool TraverseCXXMethodDecl(CXXMethodDecl
* decl
) {
119 returnTypes_
.push(decl
->getReturnType());
120 auto const ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(decl
);
121 assert(!returnTypes_
.empty());
122 assert(returnTypes_
.top() == decl
->getReturnType());
127 bool TraverseCXXConstructorDecl(CXXConstructorDecl
* decl
) {
128 returnTypes_
.push(decl
->getReturnType());
129 auto const ret
= RecursiveASTVisitor::TraverseCXXConstructorDecl(decl
);
130 assert(!returnTypes_
.empty());
131 assert(returnTypes_
.top() == decl
->getReturnType());
136 bool TraverseCXXDestructorDecl(CXXDestructorDecl
* decl
) {
137 returnTypes_
.push(decl
->getReturnType());
138 auto const ret
= RecursiveASTVisitor::TraverseCXXDestructorDecl(decl
);
139 assert(!returnTypes_
.empty());
140 assert(returnTypes_
.top() == decl
->getReturnType());
145 bool TraverseCXXConversionDecl(CXXConversionDecl
* decl
) {
146 returnTypes_
.push(decl
->getReturnType());
147 auto const ret
= RecursiveASTVisitor::TraverseCXXConversionDecl(decl
);
148 assert(!returnTypes_
.empty());
149 assert(returnTypes_
.top() == decl
->getReturnType());
154 bool TraverseObjCMethodDecl(ObjCMethodDecl
* decl
) {
155 returnTypes_
.push(decl
->getReturnType());
156 auto const ret
= RecursiveASTVisitor::TraverseObjCMethodDecl(decl
);
157 assert(!returnTypes_
.empty());
158 assert(returnTypes_
.top() == decl
->getReturnType());
163 bool TraverseConstructorInitializer(CXXCtorInitializer
* init
) {
164 if (auto const field
= init
->getAnyMember()) {
165 if (loplugin::TypeCheck(field
->getType()).LvalueReference()) {
166 recordConsumption(lookThroughInitListExpr(init
->getInit()));
169 return RecursiveASTVisitor::TraverseConstructorInitializer(init
);
172 bool TraverseLambdaExpr(LambdaExpr
* expr
, DataRecursionQueue
* queue
= nullptr) {
173 if (!shouldTraversePostOrder()) {
174 if (!WalkUpFromLambdaExpr(expr
)) {
178 auto const n
= expr
->capture_size();
179 for (unsigned i
= 0; i
!= n
; ++i
) {
180 auto const c
= expr
->capture_begin() + i
;
181 if (c
->isExplicit() || shouldVisitImplicitCode()) {
182 if (!TraverseLambdaCapture(expr
, c
, expr
->capture_init_begin()[i
])) {
187 if (!TraverseCXXRecordDecl(expr
->getLambdaClass())) {
190 if (!queue
&& shouldTraversePostOrder()) {
191 if (!WalkUpFromLambdaExpr(expr
)) {
198 bool VisitDeclRefExpr(DeclRefExpr
const * expr
) {
199 if (ignoreLocation(expr
)) {
202 auto const var
= dyn_cast
<VarDecl
>(expr
->getDecl());
203 if (var
== nullptr) {
206 if (var
->getType().isVolatileQualified()) {
209 auto & usage
= vars_
[var
->getCanonicalDecl()];
210 if (!castToVoid_
.empty() && castToVoid_
.top().sub
== expr
) {
211 usage
.castToVoid
.push_back(castToVoid_
.top().cast
);
213 usage
.mentioned
= true;
218 bool VisitImplicitCastExpr(ImplicitCastExpr
const * expr
) {
219 if (ignoreLocation(expr
)) {
222 if (expr
->getCastKind() != CK_LValueToRValue
) {
225 recordConsumption(expr
->getSubExpr());
229 bool VisitCallExpr(CallExpr
const * expr
) {
230 if (ignoreLocation(expr
)) {
233 unsigned firstArg
= 0;
234 if (auto const cmce
= dyn_cast
<CXXMemberCallExpr
>(expr
)) {
235 if (auto const e1
= cmce
->getMethodDecl()) {
236 if (e1
->isConst() || e1
->isStatic()) {
237 recordConsumption(cmce
->getImplicitObjectArgument());
239 } else if (auto const e2
= dyn_cast
<BinaryOperator
>(
240 cmce
->getCallee()->IgnoreParenImpCasts()))
242 switch (e2
->getOpcode()) {
245 if (e2
->getRHS()->getType()->getAs
<MemberPointerType
>()
246 ->getPointeeType()->getAs
<FunctionProtoType
>()
249 recordConsumption(e2
->getLHS());
256 } else if (isa
<CXXOperatorCallExpr
>(expr
)) {
257 if (auto const cmd
= dyn_cast_or_null
<CXXMethodDecl
>(
258 expr
->getDirectCallee()))
260 if (!cmd
->isStatic()) {
261 assert(expr
->getNumArgs() != 0);
262 if (cmd
->isConst()) {
263 recordConsumption(expr
->getArg(0));
269 auto fun
= expr
->getDirectCallee();
270 if (fun
== nullptr) {
273 unsigned const n
= std::min(fun
->getNumParams(), expr
->getNumArgs());
274 for (unsigned i
= firstArg
; i
< n
; ++i
) {
275 if (!loplugin::TypeCheck(fun
->getParamDecl(i
)->getType())
276 .LvalueReference().Const())
280 recordConsumption(lookThroughInitListExpr(expr
->getArg(i
)));
285 bool VisitCXXConstructExpr(CXXConstructExpr
const * expr
) {
286 if (ignoreLocation(expr
)) {
289 auto const ctor
= expr
->getConstructor();
290 unsigned const n
= std::min(ctor
->getNumParams(), expr
->getNumArgs());
291 for (unsigned i
= 0; i
!= n
; ++i
) {
292 if (!loplugin::TypeCheck(ctor
->getParamDecl(i
)->getType())
293 .LvalueReference().Const())
297 recordConsumption(lookThroughInitListExpr(expr
->getArg(i
)));
302 bool VisitReturnStmt(ReturnStmt
const * stmt
) {
303 if (ignoreLocation(stmt
)) {
306 assert(!returnTypes_
.empty());
307 if (!loplugin::TypeCheck(returnTypes_
.top()).LvalueReference().Const())
311 auto const ret
= stmt
->getRetValue();
312 if (ret
== nullptr) {
315 recordConsumption(lookThroughInitListExpr(ret
));
319 bool VisitVarDecl(VarDecl
const * decl
) {
320 if (ignoreLocation(decl
)) {
323 if (!loplugin::TypeCheck(decl
->getType()).LvalueReference()) {
326 auto const init
= decl
->getInit();
327 if (init
== nullptr) {
330 recordConsumption(lookThroughInitListExpr(init
));
334 bool VisitFieldDecl(FieldDecl
const * decl
) {
335 if (ignoreLocation(decl
)) {
338 if (!loplugin::TypeCheck(decl
->getType()).LvalueReference()) {
341 auto const init
= decl
->getInClassInitializer();
342 if (init
== nullptr) {
345 recordConsumption(lookThroughInitListExpr(init
));
351 std::vector
<ExplicitCastExpr
const *> castToVoid
;
352 bool mentioned
= false;
353 DeclRefExpr
const * firstConsumption
= nullptr;
357 ExplicitCastExpr
const * cast
;
358 DeclRefExpr
const * sub
;
361 std::map
<VarDecl
const *, Usage
> vars_
;
362 std::stack
<Cast
> castToVoid_
;
363 std::stack
<QualType
> returnTypes_
;
365 void run() override
{
366 if (compiler
.getPreprocessor().getIdentifierInfo("NDEBUG")->hasMacroDefinition()) {
369 if (!TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl())) {
372 for (auto const & i
: vars_
) {
373 if (i
.second
.firstConsumption
== nullptr) {
374 if (i
.second
.mentioned
) {
377 if (isa
<ParmVarDecl
>(i
.first
)) {
378 if (!compiler
.getLangOpts().CPlusPlus
379 || isSharedCAndCppCode(i
.first
))
383 auto const ctxt
= i
.first
->getDeclContext();
384 if (dyn_cast_or_null
<ObjCMethodDecl
>(ctxt
) != nullptr) {
387 auto const fun
= dyn_cast_or_null
<FunctionDecl
>(ctxt
);
388 assert(fun
!= nullptr);
389 if (containsPreprocessingConditionalInclusion(
390 fun
->getSourceRange()))
394 auto const meth
= dyn_cast
<CXXMethodDecl
>(fun
);
396 DiagnosticsEngine::Warning
,
397 "unused%select{| virtual function}0 parameter name",
398 i
.first
->getLocation())
399 << (meth
!= nullptr && meth
->isVirtual())
400 << i
.first
->getSourceRange();
401 for (auto const j
: i
.second
.castToVoid
) {
403 DiagnosticsEngine::Note
, "cast to void here",
405 << j
->getSourceRange();
407 } else if (!i
.second
.castToVoid
.empty()
408 && !isWarnUnusedType(i
.first
->getType()))
410 auto const fun
= dyn_cast_or_null
<FunctionDecl
>(i
.first
->getDeclContext());
411 assert(fun
!= nullptr);
412 if (containsPreprocessingConditionalInclusion(fun
->getSourceRange())) {
416 DiagnosticsEngine::Warning
,
417 "unused variable %select{declaration|name}0",
418 i
.first
->getLocation())
419 << i
.first
->isExceptionVariable()
420 << i
.first
->getSourceRange();
421 for (auto const j
: i
.second
.castToVoid
) {
423 DiagnosticsEngine::Note
, "cast to void here",
425 << j
->getSourceRange();
429 if (auto const fun
= dyn_cast_or_null
<FunctionDecl
>(i
.first
->getDeclContext())) {
430 if (containsPreprocessingConditionalInclusion(fun
->getSourceRange())) {
434 for (auto const j
: i
.second
.castToVoid
) {
436 DiagnosticsEngine::Warning
, "unnecessary cast to void",
438 << j
->getSourceRange();
440 DiagnosticsEngine::Note
, "first consumption is here",
441 i
.second
.firstConsumption
->getExprLoc())
442 << i
.second
.firstConsumption
->getSourceRange();
448 bool isFromCIncludeFile(SourceLocation spellingLocation
) const {
449 return !compiler
.getSourceManager().isInMainFile(spellingLocation
)
450 && compat::ends_with(
452 compiler
.getSourceManager().getPresumedLoc(spellingLocation
).getFilename()),
456 bool isSharedCAndCppCode(VarDecl
const * decl
) const {
457 auto loc
= decl
->getLocation();
458 while (compiler
.getSourceManager().isMacroArgExpansion(loc
)) {
459 loc
= compiler
.getSourceManager().getImmediateMacroCallerLoc(loc
);
461 // Assume that code is intended to be shared between C and C++ if it
462 // comes from an include file ending in .h, and is either in an extern
463 // "C" context or the body of a macro definition:
465 isFromCIncludeFile(compiler
.getSourceManager().getSpellingLoc(loc
))
466 && (decl
->isInExternCContext()
467 || compiler
.getSourceManager().isMacroBodyExpansion(loc
));
470 DeclRefExpr
const * checkCast(ExplicitCastExpr
const * expr
) {
471 if (!loplugin::TypeCheck(expr
->getTypeAsWritten()).Void()) {
474 if (compiler
.getSourceManager().isMacroBodyExpansion(
475 expr
->getBeginLoc()))
479 return dyn_cast
<DeclRefExpr
>(expr
->getSubExpr()->IgnoreParenImpCasts());
482 void recordConsumption(Expr
const * expr
) {
484 expr
= expr
->IgnoreParenImpCasts();
485 if (auto const e
= dyn_cast
<MemberExpr
>(expr
)) {
489 if (auto const e
= dyn_cast
<ArraySubscriptExpr
>(expr
)) {
493 if (auto const e
= dyn_cast
<BinaryOperator
>(expr
)) {
494 if (e
->getOpcode() == BO_PtrMemD
) {
501 auto const dre
= dyn_cast
<DeclRefExpr
>(expr
);
502 if (dre
== nullptr) {
505 // In C (but not in C++)
509 // contains an implicit lvalue-to-rvalue cast, so VisitImplicitCastExpr
510 // would record that as a consumption if we didn't filter it out here:
511 if (!castToVoid_
.empty() && castToVoid_
.top().sub
== dre
) {
514 auto const var
= dyn_cast
<VarDecl
>(dre
->getDecl());
515 if (var
== nullptr) {
518 if (var
->getType().isVolatileQualified()) {
521 auto & usage
= vars_
[var
->getCanonicalDecl()];
522 if (usage
.firstConsumption
!= nullptr) {
525 auto const loc
= dre
->getBeginLoc();
526 if (compiler
.getSourceManager().isMacroArgExpansion(loc
)
527 && (Lexer::getImmediateMacroNameForDiagnostics(
528 loc
, compiler
.getSourceManager(), compiler
.getLangOpts())
533 usage
.firstConsumption
= dre
;
537 static loplugin::Plugin::Registration
<CastToVoid
> reg("casttovoid");
541 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */