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 RecursiveASTVisitor
<CastToVoid
>, public loplugin::Plugin
50 explicit CastToVoid(loplugin::InstantiationData
const & 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 #if CLANG_VERSION >= 50000
109 bool TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl
* decl
) {
110 returnTypes_
.push(decl
->getReturnType());
111 auto const ret
= RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(
113 assert(!returnTypes_
.empty());
114 assert(returnTypes_
.top() == decl
->getReturnType());
120 bool TraverseCXXMethodDecl(CXXMethodDecl
* decl
) {
121 returnTypes_
.push(decl
->getReturnType());
122 auto const ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(decl
);
123 assert(!returnTypes_
.empty());
124 assert(returnTypes_
.top() == decl
->getReturnType());
129 bool TraverseCXXConstructorDecl(CXXConstructorDecl
* decl
) {
130 returnTypes_
.push(decl
->getReturnType());
131 auto const ret
= RecursiveASTVisitor::TraverseCXXConstructorDecl(decl
);
132 assert(!returnTypes_
.empty());
133 assert(returnTypes_
.top() == decl
->getReturnType());
138 bool TraverseCXXDestructorDecl(CXXDestructorDecl
* decl
) {
139 returnTypes_
.push(decl
->getReturnType());
140 auto const ret
= RecursiveASTVisitor::TraverseCXXDestructorDecl(decl
);
141 assert(!returnTypes_
.empty());
142 assert(returnTypes_
.top() == decl
->getReturnType());
147 bool TraverseCXXConversionDecl(CXXConversionDecl
* decl
) {
148 returnTypes_
.push(decl
->getReturnType());
149 auto const ret
= RecursiveASTVisitor::TraverseCXXConversionDecl(decl
);
150 assert(!returnTypes_
.empty());
151 assert(returnTypes_
.top() == decl
->getReturnType());
156 bool TraverseObjCMethodDecl(ObjCMethodDecl
* decl
) {
157 returnTypes_
.push(decl
->getReturnType());
158 auto const ret
= RecursiveASTVisitor::TraverseObjCMethodDecl(decl
);
159 assert(!returnTypes_
.empty());
160 assert(returnTypes_
.top() == decl
->getReturnType());
165 bool TraverseConstructorInitializer(CXXCtorInitializer
* init
) {
166 if (auto const field
= init
->getAnyMember()) {
167 if (loplugin::TypeCheck(field
->getType()).LvalueReference()) {
168 recordConsumption(lookThroughInitListExpr(init
->getInit()));
171 return RecursiveASTVisitor::TraverseConstructorInitializer(init
);
174 bool VisitDeclRefExpr(DeclRefExpr
const * expr
) {
175 if (ignoreLocation(expr
)) {
178 auto const var
= dyn_cast
<VarDecl
>(expr
->getDecl());
179 if (var
== nullptr) {
182 auto & usage
= vars_
[var
->getCanonicalDecl()];
183 if (!castToVoid_
.empty() && castToVoid_
.top().sub
== expr
) {
184 usage
.castToVoid
.push_back(castToVoid_
.top().cast
);
186 usage
.mentioned
= true;
191 bool VisitImplicitCastExpr(ImplicitCastExpr
const * expr
) {
192 if (ignoreLocation(expr
)) {
195 if (expr
->getCastKind() != CK_LValueToRValue
) {
198 recordConsumption(expr
->getSubExpr());
202 bool VisitCallExpr(CallExpr
const * expr
) {
203 if (ignoreLocation(expr
)) {
206 unsigned firstArg
= 0;
207 if (auto const cmce
= dyn_cast
<CXXMemberCallExpr
>(expr
)) {
208 if (auto const e1
= cmce
->getMethodDecl()) {
209 if (e1
->isConst() || e1
->isStatic()) {
210 recordConsumption(cmce
->getImplicitObjectArgument());
212 } else if (auto const e2
= dyn_cast
<BinaryOperator
>(
213 cmce
->getCallee()->IgnoreParenImpCasts()))
215 switch (e2
->getOpcode()) {
218 if (e2
->getRHS()->getType()->getAs
<MemberPointerType
>()
219 ->getPointeeType()->getAs
<FunctionProtoType
>()
222 recordConsumption(e2
->getLHS());
229 } else if (isa
<CXXOperatorCallExpr
>(expr
)) {
230 if (auto const cmd
= dyn_cast_or_null
<CXXMethodDecl
>(
231 expr
->getDirectCallee()))
233 if (!cmd
->isStatic()) {
234 assert(expr
->getNumArgs() != 0);
235 if (cmd
->isConst()) {
236 recordConsumption(expr
->getArg(0));
242 auto fun
= expr
->getDirectCallee();
243 if (fun
== nullptr) {
246 unsigned const n
= std::min(fun
->getNumParams(), expr
->getNumArgs());
247 for (unsigned i
= firstArg
; i
< n
; ++i
) {
248 if (!loplugin::TypeCheck(fun
->getParamDecl(i
)->getType())
249 .LvalueReference().Const())
253 recordConsumption(lookThroughInitListExpr(expr
->getArg(i
)));
258 bool VisitCXXConstructExpr(CXXConstructExpr
const * expr
) {
259 if (ignoreLocation(expr
)) {
262 auto const ctor
= expr
->getConstructor();
263 unsigned const n
= std::min(ctor
->getNumParams(), expr
->getNumArgs());
264 for (unsigned i
= 0; i
!= n
; ++i
) {
265 if (!loplugin::TypeCheck(ctor
->getParamDecl(i
)->getType())
266 .LvalueReference().Const())
270 recordConsumption(lookThroughInitListExpr(expr
->getArg(i
)));
275 bool VisitReturnStmt(ReturnStmt
const * stmt
) {
276 if (ignoreLocation(stmt
)) {
279 assert(!returnTypes_
.empty());
280 if (!loplugin::TypeCheck(returnTypes_
.top()).LvalueReference().Const())
284 auto const ret
= stmt
->getRetValue();
285 if (ret
== nullptr) {
288 recordConsumption(lookThroughInitListExpr(ret
));
292 bool VisitVarDecl(VarDecl
const * decl
) {
293 if (ignoreLocation(decl
)) {
296 if (!loplugin::TypeCheck(decl
->getType()).LvalueReference()) {
299 auto const init
= decl
->getInit();
300 if (init
== nullptr) {
303 recordConsumption(lookThroughInitListExpr(init
));
307 bool VisitFieldDecl(FieldDecl
const * decl
) {
308 if (ignoreLocation(decl
)) {
311 if (!loplugin::TypeCheck(decl
->getType()).LvalueReference()) {
314 auto const init
= decl
->getInClassInitializer();
315 if (init
== nullptr) {
318 recordConsumption(lookThroughInitListExpr(init
));
324 std::vector
<ExplicitCastExpr
const *> castToVoid
;
325 bool mentioned
= false;
326 DeclRefExpr
const * firstConsumption
= nullptr;
330 ExplicitCastExpr
const * cast
;
331 DeclRefExpr
const * sub
;
334 std::map
<VarDecl
const *, Usage
> vars_
;
335 std::stack
<Cast
> castToVoid_
;
336 std::stack
<QualType
> returnTypes_
;
338 void run() override
{
339 if (compiler
.getPreprocessor().getIdentifierInfo("NDEBUG")->hasMacroDefinition()) {
342 if (!TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl())) {
345 for (auto const & i
: vars_
) {
346 if (i
.second
.firstConsumption
== nullptr) {
347 if (i
.second
.mentioned
) {
350 if (isa
<ParmVarDecl
>(i
.first
)) {
351 if (!compiler
.getLangOpts().CPlusPlus
352 || isSharedCAndCppCode(i
.first
))
356 auto const ctxt
= i
.first
->getDeclContext();
357 if (dyn_cast_or_null
<ObjCMethodDecl
>(ctxt
) != nullptr) {
360 auto const fun
= dyn_cast_or_null
<FunctionDecl
>(ctxt
);
361 assert(fun
!= nullptr);
362 if (containsPreprocessingConditionalInclusion(
363 fun
->getSourceRange()))
367 auto const meth
= dyn_cast
<CXXMethodDecl
>(fun
);
369 DiagnosticsEngine::Warning
,
370 "unused%select{| virtual function}0 parameter name",
371 i
.first
->getLocation())
372 << (meth
!= nullptr && meth
->isVirtual())
373 << i
.first
->getSourceRange();
374 for (auto const j
: i
.second
.castToVoid
) {
376 DiagnosticsEngine::Note
, "cast to void here",
378 << j
->getSourceRange();
380 } else if (!i
.second
.castToVoid
.empty()
381 && !isWarnUnusedType(i
.first
->getType()))
383 auto const fun
= dyn_cast_or_null
<FunctionDecl
>(i
.first
->getDeclContext());
384 assert(fun
!= nullptr);
385 if (containsPreprocessingConditionalInclusion(fun
->getSourceRange())) {
389 DiagnosticsEngine::Warning
,
390 "unused variable %select{declaration|name}0",
391 i
.first
->getLocation())
392 << i
.first
->isExceptionVariable()
393 << i
.first
->getSourceRange();
394 for (auto const j
: i
.second
.castToVoid
) {
396 DiagnosticsEngine::Note
, "cast to void here",
398 << j
->getSourceRange();
402 for (auto const j
: i
.second
.castToVoid
) {
404 DiagnosticsEngine::Warning
, "unnecessary cast to void",
406 << j
->getSourceRange();
408 DiagnosticsEngine::Note
, "first consumption is here",
409 i
.second
.firstConsumption
->getExprLoc())
410 << i
.second
.firstConsumption
->getSourceRange();
416 bool isFromCIncludeFile(SourceLocation spellingLocation
) const {
417 return !compiler
.getSourceManager().isInMainFile(spellingLocation
)
419 compiler
.getSourceManager().getPresumedLoc(spellingLocation
)
424 bool isSharedCAndCppCode(VarDecl
const * decl
) const {
425 auto loc
= decl
->getLocation();
426 while (compiler
.getSourceManager().isMacroArgExpansion(loc
)) {
427 loc
= compiler
.getSourceManager().getImmediateMacroCallerLoc(loc
);
429 // Assume that code is intended to be shared between C and C++ if it
430 // comes from an include file ending in .h, and is either in an extern
431 // "C" context or the body of a macro definition:
433 isFromCIncludeFile(compiler
.getSourceManager().getSpellingLoc(loc
))
434 && (decl
->isInExternCContext()
435 || compiler
.getSourceManager().isMacroBodyExpansion(loc
));
438 DeclRefExpr
const * checkCast(ExplicitCastExpr
const * expr
) {
439 if (!loplugin::TypeCheck(expr
->getTypeAsWritten()).Void()) {
442 if (compiler
.getSourceManager().isMacroBodyExpansion(
443 expr
->getLocStart()))
447 return dyn_cast
<DeclRefExpr
>(expr
->getSubExpr()->IgnoreParenImpCasts());
450 void recordConsumption(Expr
const * expr
) {
452 expr
= expr
->IgnoreParenImpCasts();
453 if (auto const e
= dyn_cast
<MemberExpr
>(expr
)) {
457 if (auto const e
= dyn_cast
<ArraySubscriptExpr
>(expr
)) {
461 if (auto const e
= dyn_cast
<BinaryOperator
>(expr
)) {
462 if (e
->getOpcode() == BO_PtrMemD
) {
469 auto const dre
= dyn_cast
<DeclRefExpr
>(expr
);
470 if (dre
== nullptr) {
473 // In C (but not in C++)
477 // contains an implicit lvalue-to-rvalue cast, so VisitImplicitCastExpr
478 // would record that as a consumption if we didn't filter it out here:
479 if (!castToVoid_
.empty() && castToVoid_
.top().sub
== dre
) {
482 auto const var
= dyn_cast
<VarDecl
>(dre
->getDecl());
483 if (var
== nullptr) {
486 auto & usage
= vars_
[var
->getCanonicalDecl()];
487 if (usage
.firstConsumption
!= nullptr) {
490 auto const loc
= dre
->getLocStart();
491 if (compiler
.getSourceManager().isMacroArgExpansion(loc
)
492 && (compat::getImmediateMacroNameForDiagnostics(
493 loc
, compiler
.getSourceManager(), compiler
.getLangOpts())
498 usage
.firstConsumption
= dre
;
502 static loplugin::Plugin::Registration
<CastToVoid
> reg("casttovoid");
506 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */