bump product version to 7.2.5.1
[LibreOffice.git] / compilerplugins / clang / casttovoid.cxx
blob1717fa09a516b40d9835130cf89121f00e0f8736
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
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/.
8 */
10 #include <algorithm>
11 #include <cassert>
12 #include <map>
13 #include <stack>
15 #include "clang/AST/Attr.h"
17 #include "check.hxx"
18 #include "compat.hxx"
19 #include "plugin.hxx"
21 namespace {
23 bool isWarnUnusedType(QualType type) {
24 if (auto const t = type->getAs<TypedefType>()) {
25 if (t->getDecl()->hasAttr<WarnUnusedAttr>()) {
26 return true;
29 if (auto const t = type->getAs<RecordType>()) {
30 if (t->getDecl()->hasAttr<WarnUnusedAttr>()) {
31 return true;
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);
43 return expr;
46 class CastToVoid final:
47 public loplugin::FilteringPlugin<CastToVoid>
49 public:
50 explicit CastToVoid(loplugin::InstantiationData const & data):
51 FilteringPlugin(data) {}
53 bool TraverseCStyleCastExpr(CStyleCastExpr * expr) {
54 auto const dre = checkCast(expr);
55 if (dre != nullptr) {
56 castToVoid_.push({expr, dre});
58 auto const ret = RecursiveASTVisitor::TraverseCStyleCastExpr(expr);
59 if (dre != nullptr) {
60 assert(!castToVoid_.empty());
61 assert(castToVoid_.top().cast == expr);
62 assert(castToVoid_.top().sub == dre);
63 castToVoid_.pop();
65 return ret;
68 bool TraverseCXXStaticCastExpr(CXXStaticCastExpr * expr) {
69 auto const dre = checkCast(expr);
70 if (dre != nullptr) {
71 castToVoid_.push({expr, dre});
73 auto const ret = RecursiveASTVisitor::TraverseCXXStaticCastExpr(expr);
74 if (dre != nullptr) {
75 assert(!castToVoid_.empty());
76 assert(castToVoid_.top().cast == expr);
77 assert(castToVoid_.top().sub == dre);
78 castToVoid_.pop();
80 return ret;
83 bool TraverseCXXFunctionalCastExpr(CXXFunctionalCastExpr * expr) {
84 auto const dre = checkCast(expr);
85 if (dre != nullptr) {
86 castToVoid_.push({expr, dre});
88 auto const ret = RecursiveASTVisitor::TraverseCXXFunctionalCastExpr(
89 expr);
90 if (dre != nullptr) {
91 assert(!castToVoid_.empty());
92 assert(castToVoid_.top().cast == expr);
93 assert(castToVoid_.top().sub == dre);
94 castToVoid_.pop();
96 return ret;
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());
104 returnTypes_.pop();
105 return ret;
108 bool TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl * decl) {
109 returnTypes_.push(decl->getReturnType());
110 auto const ret = RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(
111 decl);
112 assert(!returnTypes_.empty());
113 assert(returnTypes_.top() == decl->getReturnType());
114 returnTypes_.pop();
115 return ret;
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());
123 returnTypes_.pop();
124 return ret;
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());
132 returnTypes_.pop();
133 return ret;
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());
141 returnTypes_.pop();
142 return ret;
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());
150 returnTypes_.pop();
151 return ret;
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());
159 returnTypes_.pop();
160 return ret;
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)) {
175 return false;
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])) {
183 return false;
187 if (!TraverseCXXRecordDecl(expr->getLambdaClass())) {
188 return false;
190 if (!queue && shouldTraversePostOrder()) {
191 if (!WalkUpFromLambdaExpr(expr)) {
192 return false;
195 return true;
198 bool VisitDeclRefExpr(DeclRefExpr const * expr) {
199 if (ignoreLocation(expr)) {
200 return true;
202 auto const var = dyn_cast<VarDecl>(expr->getDecl());
203 if (var == nullptr) {
204 return true;
206 auto & usage = vars_[var->getCanonicalDecl()];
207 if (!castToVoid_.empty() && castToVoid_.top().sub == expr) {
208 usage.castToVoid.push_back(castToVoid_.top().cast);
209 } else {
210 usage.mentioned = true;
212 return true;
215 bool VisitImplicitCastExpr(ImplicitCastExpr const * expr) {
216 if (ignoreLocation(expr)) {
217 return true;
219 if (expr->getCastKind() != CK_LValueToRValue) {
220 return true;
222 recordConsumption(expr->getSubExpr());
223 return true;
226 bool VisitCallExpr(CallExpr const * expr) {
227 if (ignoreLocation(expr)) {
228 return true;
230 unsigned firstArg = 0;
231 if (auto const cmce = dyn_cast<CXXMemberCallExpr>(expr)) {
232 if (auto const e1 = cmce->getMethodDecl()) {
233 if (e1->isConst() || e1->isStatic()) {
234 recordConsumption(cmce->getImplicitObjectArgument());
236 } else if (auto const e2 = dyn_cast<BinaryOperator>(
237 cmce->getCallee()->IgnoreParenImpCasts()))
239 switch (e2->getOpcode()) {
240 case BO_PtrMemD:
241 case BO_PtrMemI:
242 if (e2->getRHS()->getType()->getAs<MemberPointerType>()
243 ->getPointeeType()->getAs<FunctionProtoType>()
244 ->isConst())
246 recordConsumption(e2->getLHS());
248 break;
249 default:
250 break;
253 } else if (isa<CXXOperatorCallExpr>(expr)) {
254 if (auto const cmd = dyn_cast_or_null<CXXMethodDecl>(
255 expr->getDirectCallee()))
257 if (!cmd->isStatic()) {
258 assert(expr->getNumArgs() != 0);
259 if (cmd->isConst()) {
260 recordConsumption(expr->getArg(0));
262 firstArg = 1;
266 auto fun = expr->getDirectCallee();
267 if (fun == nullptr) {
268 return true;
270 unsigned const n = std::min(fun->getNumParams(), expr->getNumArgs());
271 for (unsigned i = firstArg; i < n; ++i) {
272 if (!loplugin::TypeCheck(fun->getParamDecl(i)->getType())
273 .LvalueReference().Const())
275 continue;
277 recordConsumption(lookThroughInitListExpr(expr->getArg(i)));
279 return true;
282 bool VisitCXXConstructExpr(CXXConstructExpr const * expr) {
283 if (ignoreLocation(expr)) {
284 return true;
286 auto const ctor = expr->getConstructor();
287 unsigned const n = std::min(ctor->getNumParams(), expr->getNumArgs());
288 for (unsigned i = 0; i != n; ++i) {
289 if (!loplugin::TypeCheck(ctor->getParamDecl(i)->getType())
290 .LvalueReference().Const())
292 continue;
294 recordConsumption(lookThroughInitListExpr(expr->getArg(i)));
296 return true;
299 bool VisitReturnStmt(ReturnStmt const * stmt) {
300 if (ignoreLocation(stmt)) {
301 return true;
303 assert(!returnTypes_.empty());
304 if (!loplugin::TypeCheck(returnTypes_.top()).LvalueReference().Const())
306 return true;
308 auto const ret = stmt->getRetValue();
309 if (ret == nullptr) {
310 return true;
312 recordConsumption(lookThroughInitListExpr(ret));
313 return true;
316 bool VisitVarDecl(VarDecl const * decl) {
317 if (ignoreLocation(decl)) {
318 return true;
320 if (!loplugin::TypeCheck(decl->getType()).LvalueReference()) {
321 return true;
323 auto const init = decl->getInit();
324 if (init == nullptr) {
325 return true;
327 recordConsumption(lookThroughInitListExpr(init));
328 return true;
331 bool VisitFieldDecl(FieldDecl const * decl) {
332 if (ignoreLocation(decl)) {
333 return true;
335 if (!loplugin::TypeCheck(decl->getType()).LvalueReference()) {
336 return true;
338 auto const init = decl->getInClassInitializer();
339 if (init == nullptr) {
340 return true;
342 recordConsumption(lookThroughInitListExpr(init));
343 return true;
346 private:
347 struct Usage {
348 std::vector<ExplicitCastExpr const *> castToVoid;
349 bool mentioned = false;
350 DeclRefExpr const * firstConsumption = nullptr;
353 struct Cast {
354 ExplicitCastExpr const * cast;
355 DeclRefExpr const * sub;
358 std::map<VarDecl const *, Usage> vars_;
359 std::stack<Cast> castToVoid_;
360 std::stack<QualType> returnTypes_;
362 void run() override {
363 if (compiler.getPreprocessor().getIdentifierInfo("NDEBUG")->hasMacroDefinition()) {
364 return;
366 if (!TraverseDecl(compiler.getASTContext().getTranslationUnitDecl())) {
367 return;
369 for (auto const & i: vars_) {
370 if (i.second.firstConsumption == nullptr) {
371 if (i.second.mentioned) {
372 continue;
374 if (isa<ParmVarDecl>(i.first)) {
375 if (!compiler.getLangOpts().CPlusPlus
376 || isSharedCAndCppCode(i.first))
378 continue;
380 auto const ctxt = i.first->getDeclContext();
381 if (dyn_cast_or_null<ObjCMethodDecl>(ctxt) != nullptr) {
382 continue;
384 auto const fun = dyn_cast_or_null<FunctionDecl>(ctxt);
385 assert(fun != nullptr);
386 if (containsPreprocessingConditionalInclusion(
387 fun->getSourceRange()))
389 continue;
391 auto const meth = dyn_cast<CXXMethodDecl>(fun);
392 report(
393 DiagnosticsEngine::Warning,
394 "unused%select{| virtual function}0 parameter name",
395 i.first->getLocation())
396 << (meth != nullptr && meth->isVirtual())
397 << i.first->getSourceRange();
398 for (auto const j: i.second.castToVoid) {
399 report(
400 DiagnosticsEngine::Note, "cast to void here",
401 j->getExprLoc())
402 << j->getSourceRange();
404 } else if (!i.second.castToVoid.empty()
405 && !isWarnUnusedType(i.first->getType()))
407 auto const fun = dyn_cast_or_null<FunctionDecl>(i.first->getDeclContext());
408 assert(fun != nullptr);
409 if (containsPreprocessingConditionalInclusion(fun->getSourceRange())) {
410 continue;
412 report(
413 DiagnosticsEngine::Warning,
414 "unused variable %select{declaration|name}0",
415 i.first->getLocation())
416 << i.first->isExceptionVariable()
417 << i.first->getSourceRange();
418 for (auto const j: i.second.castToVoid) {
419 report(
420 DiagnosticsEngine::Note, "cast to void here",
421 j->getExprLoc())
422 << j->getSourceRange();
425 } else {
426 for (auto const j: i.second.castToVoid) {
427 report(
428 DiagnosticsEngine::Warning, "unnecessary cast to void",
429 j->getExprLoc())
430 << j->getSourceRange();
431 report(
432 DiagnosticsEngine::Note, "first consumption is here",
433 i.second.firstConsumption->getExprLoc())
434 << i.second.firstConsumption->getSourceRange();
440 bool isFromCIncludeFile(SourceLocation spellingLocation) const {
441 return !compiler.getSourceManager().isInMainFile(spellingLocation)
442 && (StringRef(
443 compiler.getSourceManager().getPresumedLoc(spellingLocation)
444 .getFilename())
445 .endswith(".h"));
448 bool isSharedCAndCppCode(VarDecl const * decl) const {
449 auto loc = decl->getLocation();
450 while (compiler.getSourceManager().isMacroArgExpansion(loc)) {
451 loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
453 // Assume that code is intended to be shared between C and C++ if it
454 // comes from an include file ending in .h, and is either in an extern
455 // "C" context or the body of a macro definition:
456 return
457 isFromCIncludeFile(compiler.getSourceManager().getSpellingLoc(loc))
458 && (decl->isInExternCContext()
459 || compiler.getSourceManager().isMacroBodyExpansion(loc));
462 DeclRefExpr const * checkCast(ExplicitCastExpr const * expr) {
463 if (!loplugin::TypeCheck(expr->getTypeAsWritten()).Void()) {
464 return nullptr;
466 if (compiler.getSourceManager().isMacroBodyExpansion(
467 compat::getBeginLoc(expr)))
469 return nullptr;
471 return dyn_cast<DeclRefExpr>(expr->getSubExpr()->IgnoreParenImpCasts());
474 void recordConsumption(Expr const * expr) {
475 for (;;) {
476 expr = expr->IgnoreParenImpCasts();
477 if (auto const e = dyn_cast<MemberExpr>(expr)) {
478 expr = e->getBase();
479 continue;
481 if (auto const e = dyn_cast<ArraySubscriptExpr>(expr)) {
482 expr = e->getBase();
483 continue;
485 if (auto const e = dyn_cast<BinaryOperator>(expr)) {
486 if (e->getOpcode() == BO_PtrMemD) {
487 expr = e->getLHS();
488 continue;
491 break;
493 auto const dre = dyn_cast<DeclRefExpr>(expr);
494 if (dre == nullptr) {
495 return;
497 // In C (but not in C++)
499 // (void) x
501 // contains an implicit lvalue-to-rvalue cast, so VisitImplicitCastExpr
502 // would record that as a consumption if we didn't filter it out here:
503 if (!castToVoid_.empty() && castToVoid_.top().sub == dre) {
504 return;
506 auto const var = dyn_cast<VarDecl>(dre->getDecl());
507 if (var == nullptr) {
508 return;
510 auto & usage = vars_[var->getCanonicalDecl()];
511 if (usage.firstConsumption != nullptr) {
512 return;
514 auto const loc = compat::getBeginLoc(dre);
515 if (compiler.getSourceManager().isMacroArgExpansion(loc)
516 && (Lexer::getImmediateMacroNameForDiagnostics(
517 loc, compiler.getSourceManager(), compiler.getLangOpts())
518 == "assert"))
520 return;
522 usage.firstConsumption = dre;
526 static loplugin::Plugin::Registration<CastToVoid> reg("casttovoid");
530 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */