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 VisitDeclRefExpr(DeclRefExpr
const * expr
) {
173 if (ignoreLocation(expr
)) {
176 auto const var
= dyn_cast
<VarDecl
>(expr
->getDecl());
177 if (var
== nullptr) {
180 auto & usage
= vars_
[var
->getCanonicalDecl()];
181 if (!castToVoid_
.empty() && castToVoid_
.top().sub
== expr
) {
182 usage
.castToVoid
.push_back(castToVoid_
.top().cast
);
184 usage
.mentioned
= true;
189 bool VisitImplicitCastExpr(ImplicitCastExpr
const * expr
) {
190 if (ignoreLocation(expr
)) {
193 if (expr
->getCastKind() != CK_LValueToRValue
) {
196 recordConsumption(expr
->getSubExpr());
200 bool VisitCallExpr(CallExpr
const * expr
) {
201 if (ignoreLocation(expr
)) {
204 unsigned firstArg
= 0;
205 if (auto const cmce
= dyn_cast
<CXXMemberCallExpr
>(expr
)) {
206 if (auto const e1
= cmce
->getMethodDecl()) {
207 if (e1
->isConst() || e1
->isStatic()) {
208 recordConsumption(cmce
->getImplicitObjectArgument());
210 } else if (auto const e2
= dyn_cast
<BinaryOperator
>(
211 cmce
->getCallee()->IgnoreParenImpCasts()))
213 switch (e2
->getOpcode()) {
216 if (e2
->getRHS()->getType()->getAs
<MemberPointerType
>()
217 ->getPointeeType()->getAs
<FunctionProtoType
>()
220 recordConsumption(e2
->getLHS());
227 } else if (isa
<CXXOperatorCallExpr
>(expr
)) {
228 if (auto const cmd
= dyn_cast_or_null
<CXXMethodDecl
>(
229 expr
->getDirectCallee()))
231 if (!cmd
->isStatic()) {
232 assert(expr
->getNumArgs() != 0);
233 if (cmd
->isConst()) {
234 recordConsumption(expr
->getArg(0));
240 auto fun
= expr
->getDirectCallee();
241 if (fun
== nullptr) {
244 unsigned const n
= std::min(fun
->getNumParams(), expr
->getNumArgs());
245 for (unsigned i
= firstArg
; i
< n
; ++i
) {
246 if (!loplugin::TypeCheck(fun
->getParamDecl(i
)->getType())
247 .LvalueReference().Const())
251 recordConsumption(lookThroughInitListExpr(expr
->getArg(i
)));
256 bool VisitCXXConstructExpr(CXXConstructExpr
const * expr
) {
257 if (ignoreLocation(expr
)) {
260 auto const ctor
= expr
->getConstructor();
261 unsigned const n
= std::min(ctor
->getNumParams(), expr
->getNumArgs());
262 for (unsigned i
= 0; i
!= n
; ++i
) {
263 if (!loplugin::TypeCheck(ctor
->getParamDecl(i
)->getType())
264 .LvalueReference().Const())
268 recordConsumption(lookThroughInitListExpr(expr
->getArg(i
)));
273 bool VisitReturnStmt(ReturnStmt
const * stmt
) {
274 if (ignoreLocation(stmt
)) {
277 assert(!returnTypes_
.empty());
278 if (!loplugin::TypeCheck(returnTypes_
.top()).LvalueReference().Const())
282 auto const ret
= stmt
->getRetValue();
283 if (ret
== nullptr) {
286 recordConsumption(lookThroughInitListExpr(ret
));
290 bool VisitVarDecl(VarDecl
const * decl
) {
291 if (ignoreLocation(decl
)) {
294 if (!loplugin::TypeCheck(decl
->getType()).LvalueReference()) {
297 auto const init
= decl
->getInit();
298 if (init
== nullptr) {
301 recordConsumption(lookThroughInitListExpr(init
));
305 bool VisitFieldDecl(FieldDecl
const * decl
) {
306 if (ignoreLocation(decl
)) {
309 if (!loplugin::TypeCheck(decl
->getType()).LvalueReference()) {
312 auto const init
= decl
->getInClassInitializer();
313 if (init
== nullptr) {
316 recordConsumption(lookThroughInitListExpr(init
));
322 std::vector
<ExplicitCastExpr
const *> castToVoid
;
323 bool mentioned
= false;
324 DeclRefExpr
const * firstConsumption
= nullptr;
328 ExplicitCastExpr
const * cast
;
329 DeclRefExpr
const * sub
;
332 std::map
<VarDecl
const *, Usage
> vars_
;
333 std::stack
<Cast
> castToVoid_
;
334 std::stack
<QualType
> returnTypes_
;
336 void run() override
{
337 if (compiler
.getPreprocessor().getIdentifierInfo("NDEBUG")->hasMacroDefinition()) {
340 if (!TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl())) {
343 for (auto const & i
: vars_
) {
344 if (i
.second
.firstConsumption
== nullptr) {
345 if (i
.second
.mentioned
) {
348 if (isa
<ParmVarDecl
>(i
.first
)) {
349 if (!compiler
.getLangOpts().CPlusPlus
350 || isSharedCAndCppCode(i
.first
))
354 auto const ctxt
= i
.first
->getDeclContext();
355 if (dyn_cast_or_null
<ObjCMethodDecl
>(ctxt
) != nullptr) {
358 auto const fun
= dyn_cast_or_null
<FunctionDecl
>(ctxt
);
359 assert(fun
!= nullptr);
360 if (containsPreprocessingConditionalInclusion(
361 fun
->getSourceRange()))
365 auto const meth
= dyn_cast
<CXXMethodDecl
>(fun
);
367 DiagnosticsEngine::Warning
,
368 "unused%select{| virtual function}0 parameter name",
369 i
.first
->getLocation())
370 << (meth
!= nullptr && meth
->isVirtual())
371 << i
.first
->getSourceRange();
372 for (auto const j
: i
.second
.castToVoid
) {
374 DiagnosticsEngine::Note
, "cast to void here",
376 << j
->getSourceRange();
378 } else if (!i
.second
.castToVoid
.empty()
379 && !isWarnUnusedType(i
.first
->getType()))
381 auto const fun
= dyn_cast_or_null
<FunctionDecl
>(i
.first
->getDeclContext());
382 assert(fun
!= nullptr);
383 if (containsPreprocessingConditionalInclusion(fun
->getSourceRange())) {
387 DiagnosticsEngine::Warning
,
388 "unused variable %select{declaration|name}0",
389 i
.first
->getLocation())
390 << i
.first
->isExceptionVariable()
391 << i
.first
->getSourceRange();
392 for (auto const j
: i
.second
.castToVoid
) {
394 DiagnosticsEngine::Note
, "cast to void here",
396 << j
->getSourceRange();
400 for (auto const j
: i
.second
.castToVoid
) {
402 DiagnosticsEngine::Warning
, "unnecessary cast to void",
404 << j
->getSourceRange();
406 DiagnosticsEngine::Note
, "first consumption is here",
407 i
.second
.firstConsumption
->getExprLoc())
408 << i
.second
.firstConsumption
->getSourceRange();
414 bool isFromCIncludeFile(SourceLocation spellingLocation
) const {
415 return !compiler
.getSourceManager().isInMainFile(spellingLocation
)
417 compiler
.getSourceManager().getPresumedLoc(spellingLocation
)
422 bool isSharedCAndCppCode(VarDecl
const * decl
) const {
423 auto loc
= decl
->getLocation();
424 while (compiler
.getSourceManager().isMacroArgExpansion(loc
)) {
425 loc
= compiler
.getSourceManager().getImmediateMacroCallerLoc(loc
);
427 // Assume that code is intended to be shared between C and C++ if it
428 // comes from an include file ending in .h, and is either in an extern
429 // "C" context or the body of a macro definition:
431 isFromCIncludeFile(compiler
.getSourceManager().getSpellingLoc(loc
))
432 && (decl
->isInExternCContext()
433 || compiler
.getSourceManager().isMacroBodyExpansion(loc
));
436 DeclRefExpr
const * checkCast(ExplicitCastExpr
const * expr
) {
437 if (!loplugin::TypeCheck(expr
->getTypeAsWritten()).Void()) {
440 if (compiler
.getSourceManager().isMacroBodyExpansion(
441 compat::getBeginLoc(expr
)))
445 return dyn_cast
<DeclRefExpr
>(expr
->getSubExpr()->IgnoreParenImpCasts());
448 void recordConsumption(Expr
const * expr
) {
450 expr
= expr
->IgnoreParenImpCasts();
451 if (auto const e
= dyn_cast
<MemberExpr
>(expr
)) {
455 if (auto const e
= dyn_cast
<ArraySubscriptExpr
>(expr
)) {
459 if (auto const e
= dyn_cast
<BinaryOperator
>(expr
)) {
460 if (e
->getOpcode() == BO_PtrMemD
) {
467 auto const dre
= dyn_cast
<DeclRefExpr
>(expr
);
468 if (dre
== nullptr) {
471 // In C (but not in C++)
475 // contains an implicit lvalue-to-rvalue cast, so VisitImplicitCastExpr
476 // would record that as a consumption if we didn't filter it out here:
477 if (!castToVoid_
.empty() && castToVoid_
.top().sub
== dre
) {
480 auto const var
= dyn_cast
<VarDecl
>(dre
->getDecl());
481 if (var
== nullptr) {
484 auto & usage
= vars_
[var
->getCanonicalDecl()];
485 if (usage
.firstConsumption
!= nullptr) {
488 auto const loc
= compat::getBeginLoc(dre
);
489 if (compiler
.getSourceManager().isMacroArgExpansion(loc
)
490 && (Lexer::getImmediateMacroNameForDiagnostics(
491 loc
, compiler
.getSourceManager(), compiler
.getLangOpts())
496 usage
.firstConsumption
= dre
;
500 static loplugin::Plugin::Registration
<CastToVoid
> reg("casttovoid");
504 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */