Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / compilerplugins / clang / casttovoid.cxx
blobbddfcb4da5035723cd6341a61a1b821e2c21ade8
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 "plugin.hxx"
20 namespace {
22 bool isWarnUnusedType(QualType type) {
23 if (auto const t = type->getAs<TypedefType>()) {
24 if (t->getDecl()->hasAttr<WarnUnusedAttr>()) {
25 return true;
28 if (auto const t = type->getAs<RecordType>()) {
29 if (t->getDecl()->hasAttr<WarnUnusedAttr>()) {
30 return true;
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);
42 return expr;
45 class CastToVoid final:
46 public loplugin::FilteringPlugin<CastToVoid>
48 public:
49 explicit CastToVoid(loplugin::InstantiationData const & data):
50 FilteringPlugin(data) {}
52 bool TraverseCStyleCastExpr(CStyleCastExpr * expr) {
53 auto const dre = checkCast(expr);
54 if (dre != nullptr) {
55 castToVoid_.push({expr, dre});
57 auto const ret = RecursiveASTVisitor::TraverseCStyleCastExpr(expr);
58 if (dre != nullptr) {
59 assert(!castToVoid_.empty());
60 assert(castToVoid_.top().cast == expr);
61 assert(castToVoid_.top().sub == dre);
62 castToVoid_.pop();
64 return ret;
67 bool TraverseCXXStaticCastExpr(CXXStaticCastExpr * expr) {
68 auto const dre = checkCast(expr);
69 if (dre != nullptr) {
70 castToVoid_.push({expr, dre});
72 auto const ret = RecursiveASTVisitor::TraverseCXXStaticCastExpr(expr);
73 if (dre != nullptr) {
74 assert(!castToVoid_.empty());
75 assert(castToVoid_.top().cast == expr);
76 assert(castToVoid_.top().sub == dre);
77 castToVoid_.pop();
79 return ret;
82 bool TraverseCXXFunctionalCastExpr(CXXFunctionalCastExpr * expr) {
83 auto const dre = checkCast(expr);
84 if (dre != nullptr) {
85 castToVoid_.push({expr, dre});
87 auto const ret = RecursiveASTVisitor::TraverseCXXFunctionalCastExpr(
88 expr);
89 if (dre != nullptr) {
90 assert(!castToVoid_.empty());
91 assert(castToVoid_.top().cast == expr);
92 assert(castToVoid_.top().sub == dre);
93 castToVoid_.pop();
95 return ret;
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());
103 returnTypes_.pop();
104 return ret;
107 bool TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl * decl) {
108 returnTypes_.push(decl->getReturnType());
109 auto const ret = RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(
110 decl);
111 assert(!returnTypes_.empty());
112 assert(returnTypes_.top() == decl->getReturnType());
113 returnTypes_.pop();
114 return ret;
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());
122 returnTypes_.pop();
123 return ret;
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());
131 returnTypes_.pop();
132 return ret;
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());
140 returnTypes_.pop();
141 return ret;
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());
149 returnTypes_.pop();
150 return ret;
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());
158 returnTypes_.pop();
159 return ret;
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)) {
174 return false;
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])) {
182 return false;
186 if (!TraverseCXXRecordDecl(expr->getLambdaClass())) {
187 return false;
189 if (!queue && shouldTraversePostOrder()) {
190 if (!WalkUpFromLambdaExpr(expr)) {
191 return false;
194 return true;
197 bool VisitDeclRefExpr(DeclRefExpr const * expr) {
198 if (ignoreLocation(expr)) {
199 return true;
201 auto const var = dyn_cast<VarDecl>(expr->getDecl());
202 if (var == nullptr) {
203 return true;
205 if (var->getType().isVolatileQualified()) {
206 return true;
208 auto & usage = vars_[var->getCanonicalDecl()];
209 if (!castToVoid_.empty() && castToVoid_.top().sub == expr) {
210 usage.castToVoid.push_back(castToVoid_.top().cast);
211 } else {
212 usage.mentioned = true;
214 return true;
217 bool VisitImplicitCastExpr(ImplicitCastExpr const * expr) {
218 if (ignoreLocation(expr)) {
219 return true;
221 if (expr->getCastKind() != CK_LValueToRValue) {
222 return true;
224 recordConsumption(expr->getSubExpr());
225 return true;
228 bool VisitCallExpr(CallExpr const * expr) {
229 if (ignoreLocation(expr)) {
230 return true;
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()) {
242 case BO_PtrMemD:
243 case BO_PtrMemI:
244 if (e2->getRHS()->getType()->getAs<MemberPointerType>()
245 ->getPointeeType()->getAs<FunctionProtoType>()
246 ->isConst())
248 recordConsumption(e2->getLHS());
250 break;
251 default:
252 break;
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));
264 firstArg = 1;
268 auto fun = expr->getDirectCallee();
269 if (fun == nullptr) {
270 return true;
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())
277 continue;
279 recordConsumption(lookThroughInitListExpr(expr->getArg(i)));
281 return true;
284 bool VisitCXXConstructExpr(CXXConstructExpr const * expr) {
285 if (ignoreLocation(expr)) {
286 return true;
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())
294 continue;
296 recordConsumption(lookThroughInitListExpr(expr->getArg(i)));
298 return true;
301 bool VisitReturnStmt(ReturnStmt const * stmt) {
302 if (ignoreLocation(stmt)) {
303 return true;
305 assert(!returnTypes_.empty());
306 if (!loplugin::TypeCheck(returnTypes_.top()).LvalueReference().Const())
308 return true;
310 auto const ret = stmt->getRetValue();
311 if (ret == nullptr) {
312 return true;
314 recordConsumption(lookThroughInitListExpr(ret));
315 return true;
318 bool VisitVarDecl(VarDecl const * decl) {
319 if (ignoreLocation(decl)) {
320 return true;
322 if (!loplugin::TypeCheck(decl->getType()).LvalueReference()) {
323 return true;
325 auto const init = decl->getInit();
326 if (init == nullptr) {
327 return true;
329 recordConsumption(lookThroughInitListExpr(init));
330 return true;
333 bool VisitFieldDecl(FieldDecl const * decl) {
334 if (ignoreLocation(decl)) {
335 return true;
337 if (!loplugin::TypeCheck(decl->getType()).LvalueReference()) {
338 return true;
340 auto const init = decl->getInClassInitializer();
341 if (init == nullptr) {
342 return true;
344 recordConsumption(lookThroughInitListExpr(init));
345 return true;
348 private:
349 struct Usage {
350 std::vector<ExplicitCastExpr const *> castToVoid;
351 bool mentioned = false;
352 DeclRefExpr const * firstConsumption = nullptr;
355 struct Cast {
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()) {
366 return;
368 if (!TraverseDecl(compiler.getASTContext().getTranslationUnitDecl())) {
369 return;
371 for (auto const & i: vars_) {
372 if (i.second.firstConsumption == nullptr) {
373 if (i.second.mentioned) {
374 continue;
376 if (isa<ParmVarDecl>(i.first)) {
377 if (!compiler.getLangOpts().CPlusPlus
378 || isSharedCAndCppCode(i.first))
380 continue;
382 auto const ctxt = i.first->getDeclContext();
383 if (dyn_cast_or_null<ObjCMethodDecl>(ctxt) != nullptr) {
384 continue;
386 auto const fun = dyn_cast_or_null<FunctionDecl>(ctxt);
387 assert(fun != nullptr);
388 if (containsPreprocessingConditionalInclusion(
389 fun->getSourceRange()))
391 continue;
393 auto const meth = dyn_cast<CXXMethodDecl>(fun);
394 report(
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) {
401 report(
402 DiagnosticsEngine::Note, "cast to void here",
403 j->getExprLoc())
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())) {
412 continue;
414 report(
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) {
421 report(
422 DiagnosticsEngine::Note, "cast to void here",
423 j->getExprLoc())
424 << j->getSourceRange();
427 } else {
428 for (auto const j: i.second.castToVoid) {
429 report(
430 DiagnosticsEngine::Warning, "unnecessary cast to void",
431 j->getExprLoc())
432 << j->getSourceRange();
433 report(
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)
444 && (StringRef(
445 compiler.getSourceManager().getPresumedLoc(spellingLocation)
446 .getFilename())
447 .endswith(".h"));
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:
458 return
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()) {
466 return nullptr;
468 if (compiler.getSourceManager().isMacroBodyExpansion(
469 expr->getBeginLoc()))
471 return nullptr;
473 return dyn_cast<DeclRefExpr>(expr->getSubExpr()->IgnoreParenImpCasts());
476 void recordConsumption(Expr const * expr) {
477 for (;;) {
478 expr = expr->IgnoreParenImpCasts();
479 if (auto const e = dyn_cast<MemberExpr>(expr)) {
480 expr = e->getBase();
481 continue;
483 if (auto const e = dyn_cast<ArraySubscriptExpr>(expr)) {
484 expr = e->getBase();
485 continue;
487 if (auto const e = dyn_cast<BinaryOperator>(expr)) {
488 if (e->getOpcode() == BO_PtrMemD) {
489 expr = e->getLHS();
490 continue;
493 break;
495 auto const dre = dyn_cast<DeclRefExpr>(expr);
496 if (dre == nullptr) {
497 return;
499 // In C (but not in C++)
501 // (void) x
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) {
506 return;
508 auto const var = dyn_cast<VarDecl>(dre->getDecl());
509 if (var == nullptr) {
510 return;
512 if (var->getType().isVolatileQualified()) {
513 return;
515 auto & usage = vars_[var->getCanonicalDecl()];
516 if (usage.firstConsumption != nullptr) {
517 return;
519 auto const loc = dre->getBeginLoc();
520 if (compiler.getSourceManager().isMacroArgExpansion(loc)
521 && (Lexer::getImmediateMacroNameForDiagnostics(
522 loc, compiler.getSourceManager(), compiler.getLangOpts())
523 == "assert"))
525 return;
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: */