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"
22 bool isWarnUnusedType(QualType type
) {
23 if (auto const t
= type
->getAs
<TypedefType
>()) {
24 if (t
->getDecl()->hasAttr
<WarnUnusedAttr
>()) {
28 if (auto const t
= type
->getAs
<RecordType
>()) {
29 if (t
->getDecl()->hasAttr
<WarnUnusedAttr
>()) {
33 return loplugin::isExtraWarnUnusedType(type
);
36 Expr
const * lookThroughInitListExpr(Expr
const * expr
) {
37 if (auto const ile
= dyn_cast
<InitListExpr
>(expr
->IgnoreParenImpCasts())) {
38 if (ile
->getNumInits() == 1) {
39 return ile
->getInit(0);
45 class CastToVoid final
:
46 public loplugin::FilteringPlugin
<CastToVoid
>
49 explicit CastToVoid(loplugin::InstantiationData
const & data
):
50 FilteringPlugin(data
) {}
52 bool TraverseCStyleCastExpr(CStyleCastExpr
* expr
) {
53 auto const dre
= checkCast(expr
);
55 castToVoid_
.push({expr
, dre
});
57 auto const ret
= RecursiveASTVisitor::TraverseCStyleCastExpr(expr
);
59 assert(!castToVoid_
.empty());
60 assert(castToVoid_
.top().cast
== expr
);
61 assert(castToVoid_
.top().sub
== dre
);
67 bool TraverseCXXStaticCastExpr(CXXStaticCastExpr
* expr
) {
68 auto const dre
= checkCast(expr
);
70 castToVoid_
.push({expr
, dre
});
72 auto const ret
= RecursiveASTVisitor::TraverseCXXStaticCastExpr(expr
);
74 assert(!castToVoid_
.empty());
75 assert(castToVoid_
.top().cast
== expr
);
76 assert(castToVoid_
.top().sub
== dre
);
82 bool TraverseCXXFunctionalCastExpr(CXXFunctionalCastExpr
* expr
) {
83 auto const dre
= checkCast(expr
);
85 castToVoid_
.push({expr
, dre
});
87 auto const ret
= RecursiveASTVisitor::TraverseCXXFunctionalCastExpr(
90 assert(!castToVoid_
.empty());
91 assert(castToVoid_
.top().cast
== expr
);
92 assert(castToVoid_
.top().sub
== dre
);
98 bool TraverseFunctionDecl(FunctionDecl
* decl
) {
99 returnTypes_
.push(decl
->getReturnType());
100 auto const ret
= RecursiveASTVisitor::TraverseFunctionDecl(decl
);
101 assert(!returnTypes_
.empty());
102 assert(returnTypes_
.top() == decl
->getReturnType());
107 bool TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl
* decl
) {
108 returnTypes_
.push(decl
->getReturnType());
109 auto const ret
= RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(
111 assert(!returnTypes_
.empty());
112 assert(returnTypes_
.top() == decl
->getReturnType());
117 bool TraverseCXXMethodDecl(CXXMethodDecl
* decl
) {
118 returnTypes_
.push(decl
->getReturnType());
119 auto const ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(decl
);
120 assert(!returnTypes_
.empty());
121 assert(returnTypes_
.top() == decl
->getReturnType());
126 bool TraverseCXXConstructorDecl(CXXConstructorDecl
* decl
) {
127 returnTypes_
.push(decl
->getReturnType());
128 auto const ret
= RecursiveASTVisitor::TraverseCXXConstructorDecl(decl
);
129 assert(!returnTypes_
.empty());
130 assert(returnTypes_
.top() == decl
->getReturnType());
135 bool TraverseCXXDestructorDecl(CXXDestructorDecl
* decl
) {
136 returnTypes_
.push(decl
->getReturnType());
137 auto const ret
= RecursiveASTVisitor::TraverseCXXDestructorDecl(decl
);
138 assert(!returnTypes_
.empty());
139 assert(returnTypes_
.top() == decl
->getReturnType());
144 bool TraverseCXXConversionDecl(CXXConversionDecl
* decl
) {
145 returnTypes_
.push(decl
->getReturnType());
146 auto const ret
= RecursiveASTVisitor::TraverseCXXConversionDecl(decl
);
147 assert(!returnTypes_
.empty());
148 assert(returnTypes_
.top() == decl
->getReturnType());
153 bool TraverseObjCMethodDecl(ObjCMethodDecl
* decl
) {
154 returnTypes_
.push(decl
->getReturnType());
155 auto const ret
= RecursiveASTVisitor::TraverseObjCMethodDecl(decl
);
156 assert(!returnTypes_
.empty());
157 assert(returnTypes_
.top() == decl
->getReturnType());
162 bool TraverseConstructorInitializer(CXXCtorInitializer
* init
) {
163 if (auto const field
= init
->getAnyMember()) {
164 if (loplugin::TypeCheck(field
->getType()).LvalueReference()) {
165 recordConsumption(lookThroughInitListExpr(init
->getInit()));
168 return RecursiveASTVisitor::TraverseConstructorInitializer(init
);
171 bool TraverseLambdaExpr(LambdaExpr
* expr
, DataRecursionQueue
* queue
= nullptr) {
172 if (!shouldTraversePostOrder()) {
173 if (!WalkUpFromLambdaExpr(expr
)) {
177 auto const n
= expr
->capture_size();
178 for (unsigned i
= 0; i
!= n
; ++i
) {
179 auto const c
= expr
->capture_begin() + i
;
180 if (c
->isExplicit() || shouldVisitImplicitCode()) {
181 if (!TraverseLambdaCapture(expr
, c
, expr
->capture_init_begin()[i
])) {
186 if (!TraverseCXXRecordDecl(expr
->getLambdaClass())) {
189 if (!queue
&& shouldTraversePostOrder()) {
190 if (!WalkUpFromLambdaExpr(expr
)) {
197 bool VisitDeclRefExpr(DeclRefExpr
const * expr
) {
198 if (ignoreLocation(expr
)) {
201 auto const var
= dyn_cast
<VarDecl
>(expr
->getDecl());
202 if (var
== nullptr) {
205 if (var
->getType().isVolatileQualified()) {
208 auto & usage
= vars_
[var
->getCanonicalDecl()];
209 if (!castToVoid_
.empty() && castToVoid_
.top().sub
== expr
) {
210 usage
.castToVoid
.push_back(castToVoid_
.top().cast
);
212 usage
.mentioned
= true;
217 bool VisitImplicitCastExpr(ImplicitCastExpr
const * expr
) {
218 if (ignoreLocation(expr
)) {
221 if (expr
->getCastKind() != CK_LValueToRValue
) {
224 recordConsumption(expr
->getSubExpr());
228 bool VisitCallExpr(CallExpr
const * expr
) {
229 if (ignoreLocation(expr
)) {
232 unsigned firstArg
= 0;
233 if (auto const cmce
= dyn_cast
<CXXMemberCallExpr
>(expr
)) {
234 if (auto const e1
= cmce
->getMethodDecl()) {
235 if (e1
->isConst() || e1
->isStatic()) {
236 recordConsumption(cmce
->getImplicitObjectArgument());
238 } else if (auto const e2
= dyn_cast
<BinaryOperator
>(
239 cmce
->getCallee()->IgnoreParenImpCasts()))
241 switch (e2
->getOpcode()) {
244 if (e2
->getRHS()->getType()->getAs
<MemberPointerType
>()
245 ->getPointeeType()->getAs
<FunctionProtoType
>()
248 recordConsumption(e2
->getLHS());
255 } else if (isa
<CXXOperatorCallExpr
>(expr
)) {
256 if (auto const cmd
= dyn_cast_or_null
<CXXMethodDecl
>(
257 expr
->getDirectCallee()))
259 if (!cmd
->isStatic()) {
260 assert(expr
->getNumArgs() != 0);
261 if (cmd
->isConst()) {
262 recordConsumption(expr
->getArg(0));
268 auto fun
= expr
->getDirectCallee();
269 if (fun
== nullptr) {
272 unsigned const n
= std::min(fun
->getNumParams(), expr
->getNumArgs());
273 for (unsigned i
= firstArg
; i
< n
; ++i
) {
274 if (!loplugin::TypeCheck(fun
->getParamDecl(i
)->getType())
275 .LvalueReference().Const())
279 recordConsumption(lookThroughInitListExpr(expr
->getArg(i
)));
284 bool VisitCXXConstructExpr(CXXConstructExpr
const * expr
) {
285 if (ignoreLocation(expr
)) {
288 auto const ctor
= expr
->getConstructor();
289 unsigned const n
= std::min(ctor
->getNumParams(), expr
->getNumArgs());
290 for (unsigned i
= 0; i
!= n
; ++i
) {
291 if (!loplugin::TypeCheck(ctor
->getParamDecl(i
)->getType())
292 .LvalueReference().Const())
296 recordConsumption(lookThroughInitListExpr(expr
->getArg(i
)));
301 bool VisitReturnStmt(ReturnStmt
const * stmt
) {
302 if (ignoreLocation(stmt
)) {
305 assert(!returnTypes_
.empty());
306 if (!loplugin::TypeCheck(returnTypes_
.top()).LvalueReference().Const())
310 auto const ret
= stmt
->getRetValue();
311 if (ret
== nullptr) {
314 recordConsumption(lookThroughInitListExpr(ret
));
318 bool VisitVarDecl(VarDecl
const * decl
) {
319 if (ignoreLocation(decl
)) {
322 if (!loplugin::TypeCheck(decl
->getType()).LvalueReference()) {
325 auto const init
= decl
->getInit();
326 if (init
== nullptr) {
329 recordConsumption(lookThroughInitListExpr(init
));
333 bool VisitFieldDecl(FieldDecl
const * decl
) {
334 if (ignoreLocation(decl
)) {
337 if (!loplugin::TypeCheck(decl
->getType()).LvalueReference()) {
340 auto const init
= decl
->getInClassInitializer();
341 if (init
== nullptr) {
344 recordConsumption(lookThroughInitListExpr(init
));
350 std::vector
<ExplicitCastExpr
const *> castToVoid
;
351 bool mentioned
= false;
352 DeclRefExpr
const * firstConsumption
= nullptr;
356 ExplicitCastExpr
const * cast
;
357 DeclRefExpr
const * sub
;
360 std::map
<VarDecl
const *, Usage
> vars_
;
361 std::stack
<Cast
> castToVoid_
;
362 std::stack
<QualType
> returnTypes_
;
364 void run() override
{
365 if (compiler
.getPreprocessor().getIdentifierInfo("NDEBUG")->hasMacroDefinition()) {
368 if (!TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl())) {
371 for (auto const & i
: vars_
) {
372 if (i
.second
.firstConsumption
== nullptr) {
373 if (i
.second
.mentioned
) {
376 if (isa
<ParmVarDecl
>(i
.first
)) {
377 if (!compiler
.getLangOpts().CPlusPlus
378 || isSharedCAndCppCode(i
.first
))
382 auto const ctxt
= i
.first
->getDeclContext();
383 if (dyn_cast_or_null
<ObjCMethodDecl
>(ctxt
) != nullptr) {
386 auto const fun
= dyn_cast_or_null
<FunctionDecl
>(ctxt
);
387 assert(fun
!= nullptr);
388 if (containsPreprocessingConditionalInclusion(
389 fun
->getSourceRange()))
393 auto const meth
= dyn_cast
<CXXMethodDecl
>(fun
);
395 DiagnosticsEngine::Warning
,
396 "unused%select{| virtual function}0 parameter name",
397 i
.first
->getLocation())
398 << (meth
!= nullptr && meth
->isVirtual())
399 << i
.first
->getSourceRange();
400 for (auto const j
: i
.second
.castToVoid
) {
402 DiagnosticsEngine::Note
, "cast to void here",
404 << j
->getSourceRange();
406 } else if (!i
.second
.castToVoid
.empty()
407 && !isWarnUnusedType(i
.first
->getType()))
409 auto const fun
= dyn_cast_or_null
<FunctionDecl
>(i
.first
->getDeclContext());
410 assert(fun
!= nullptr);
411 if (containsPreprocessingConditionalInclusion(fun
->getSourceRange())) {
415 DiagnosticsEngine::Warning
,
416 "unused variable %select{declaration|name}0",
417 i
.first
->getLocation())
418 << i
.first
->isExceptionVariable()
419 << i
.first
->getSourceRange();
420 for (auto const j
: i
.second
.castToVoid
) {
422 DiagnosticsEngine::Note
, "cast to void here",
424 << j
->getSourceRange();
428 for (auto const j
: i
.second
.castToVoid
) {
430 DiagnosticsEngine::Warning
, "unnecessary cast to void",
432 << j
->getSourceRange();
434 DiagnosticsEngine::Note
, "first consumption is here",
435 i
.second
.firstConsumption
->getExprLoc())
436 << i
.second
.firstConsumption
->getSourceRange();
442 bool isFromCIncludeFile(SourceLocation spellingLocation
) const {
443 return !compiler
.getSourceManager().isInMainFile(spellingLocation
)
445 compiler
.getSourceManager().getPresumedLoc(spellingLocation
)
450 bool isSharedCAndCppCode(VarDecl
const * decl
) const {
451 auto loc
= decl
->getLocation();
452 while (compiler
.getSourceManager().isMacroArgExpansion(loc
)) {
453 loc
= compiler
.getSourceManager().getImmediateMacroCallerLoc(loc
);
455 // Assume that code is intended to be shared between C and C++ if it
456 // comes from an include file ending in .h, and is either in an extern
457 // "C" context or the body of a macro definition:
459 isFromCIncludeFile(compiler
.getSourceManager().getSpellingLoc(loc
))
460 && (decl
->isInExternCContext()
461 || compiler
.getSourceManager().isMacroBodyExpansion(loc
));
464 DeclRefExpr
const * checkCast(ExplicitCastExpr
const * expr
) {
465 if (!loplugin::TypeCheck(expr
->getTypeAsWritten()).Void()) {
468 if (compiler
.getSourceManager().isMacroBodyExpansion(
469 expr
->getBeginLoc()))
473 return dyn_cast
<DeclRefExpr
>(expr
->getSubExpr()->IgnoreParenImpCasts());
476 void recordConsumption(Expr
const * expr
) {
478 expr
= expr
->IgnoreParenImpCasts();
479 if (auto const e
= dyn_cast
<MemberExpr
>(expr
)) {
483 if (auto const e
= dyn_cast
<ArraySubscriptExpr
>(expr
)) {
487 if (auto const e
= dyn_cast
<BinaryOperator
>(expr
)) {
488 if (e
->getOpcode() == BO_PtrMemD
) {
495 auto const dre
= dyn_cast
<DeclRefExpr
>(expr
);
496 if (dre
== nullptr) {
499 // In C (but not in C++)
503 // contains an implicit lvalue-to-rvalue cast, so VisitImplicitCastExpr
504 // would record that as a consumption if we didn't filter it out here:
505 if (!castToVoid_
.empty() && castToVoid_
.top().sub
== dre
) {
508 auto const var
= dyn_cast
<VarDecl
>(dre
->getDecl());
509 if (var
== nullptr) {
512 if (var
->getType().isVolatileQualified()) {
515 auto & usage
= vars_
[var
->getCanonicalDecl()];
516 if (usage
.firstConsumption
!= nullptr) {
519 auto const loc
= dre
->getBeginLoc();
520 if (compiler
.getSourceManager().isMacroArgExpansion(loc
)
521 && (Lexer::getImmediateMacroNameForDiagnostics(
522 loc
, compiler
.getSourceManager(), compiler
.getLangOpts())
527 usage
.firstConsumption
= dre
;
531 static loplugin::Plugin::Registration
<CastToVoid
> reg("casttovoid");
535 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */