bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / casttovoid.cxx
blobe6da5b6d7445f4e791480ebd9842155ca68e0f62
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 VisitDeclRefExpr(DeclRefExpr const * expr) {
173 if (ignoreLocation(expr)) {
174 return true;
176 auto const var = dyn_cast<VarDecl>(expr->getDecl());
177 if (var == nullptr) {
178 return true;
180 auto & usage = vars_[var->getCanonicalDecl()];
181 if (!castToVoid_.empty() && castToVoid_.top().sub == expr) {
182 usage.castToVoid.push_back(castToVoid_.top().cast);
183 } else {
184 usage.mentioned = true;
186 return true;
189 bool VisitImplicitCastExpr(ImplicitCastExpr const * expr) {
190 if (ignoreLocation(expr)) {
191 return true;
193 if (expr->getCastKind() != CK_LValueToRValue) {
194 return true;
196 recordConsumption(expr->getSubExpr());
197 return true;
200 bool VisitCallExpr(CallExpr const * expr) {
201 if (ignoreLocation(expr)) {
202 return true;
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()) {
214 case BO_PtrMemD:
215 case BO_PtrMemI:
216 if (e2->getRHS()->getType()->getAs<MemberPointerType>()
217 ->getPointeeType()->getAs<FunctionProtoType>()
218 ->isConst())
220 recordConsumption(e2->getLHS());
222 break;
223 default:
224 break;
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));
236 firstArg = 1;
240 auto fun = expr->getDirectCallee();
241 if (fun == nullptr) {
242 return true;
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())
249 continue;
251 recordConsumption(lookThroughInitListExpr(expr->getArg(i)));
253 return true;
256 bool VisitCXXConstructExpr(CXXConstructExpr const * expr) {
257 if (ignoreLocation(expr)) {
258 return true;
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())
266 continue;
268 recordConsumption(lookThroughInitListExpr(expr->getArg(i)));
270 return true;
273 bool VisitReturnStmt(ReturnStmt const * stmt) {
274 if (ignoreLocation(stmt)) {
275 return true;
277 assert(!returnTypes_.empty());
278 if (!loplugin::TypeCheck(returnTypes_.top()).LvalueReference().Const())
280 return true;
282 auto const ret = stmt->getRetValue();
283 if (ret == nullptr) {
284 return true;
286 recordConsumption(lookThroughInitListExpr(ret));
287 return true;
290 bool VisitVarDecl(VarDecl const * decl) {
291 if (ignoreLocation(decl)) {
292 return true;
294 if (!loplugin::TypeCheck(decl->getType()).LvalueReference()) {
295 return true;
297 auto const init = decl->getInit();
298 if (init == nullptr) {
299 return true;
301 recordConsumption(lookThroughInitListExpr(init));
302 return true;
305 bool VisitFieldDecl(FieldDecl const * decl) {
306 if (ignoreLocation(decl)) {
307 return true;
309 if (!loplugin::TypeCheck(decl->getType()).LvalueReference()) {
310 return true;
312 auto const init = decl->getInClassInitializer();
313 if (init == nullptr) {
314 return true;
316 recordConsumption(lookThroughInitListExpr(init));
317 return true;
320 private:
321 struct Usage {
322 std::vector<ExplicitCastExpr const *> castToVoid;
323 bool mentioned = false;
324 DeclRefExpr const * firstConsumption = nullptr;
327 struct Cast {
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()) {
338 return;
340 if (!TraverseDecl(compiler.getASTContext().getTranslationUnitDecl())) {
341 return;
343 for (auto const & i: vars_) {
344 if (i.second.firstConsumption == nullptr) {
345 if (i.second.mentioned) {
346 continue;
348 if (isa<ParmVarDecl>(i.first)) {
349 if (!compiler.getLangOpts().CPlusPlus
350 || isSharedCAndCppCode(i.first))
352 continue;
354 auto const ctxt = i.first->getDeclContext();
355 if (dyn_cast_or_null<ObjCMethodDecl>(ctxt) != nullptr) {
356 continue;
358 auto const fun = dyn_cast_or_null<FunctionDecl>(ctxt);
359 assert(fun != nullptr);
360 if (containsPreprocessingConditionalInclusion(
361 fun->getSourceRange()))
363 continue;
365 auto const meth = dyn_cast<CXXMethodDecl>(fun);
366 report(
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) {
373 report(
374 DiagnosticsEngine::Note, "cast to void here",
375 j->getExprLoc())
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())) {
384 continue;
386 report(
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) {
393 report(
394 DiagnosticsEngine::Note, "cast to void here",
395 j->getExprLoc())
396 << j->getSourceRange();
399 } else {
400 for (auto const j: i.second.castToVoid) {
401 report(
402 DiagnosticsEngine::Warning, "unnecessary cast to void",
403 j->getExprLoc())
404 << j->getSourceRange();
405 report(
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)
416 && (StringRef(
417 compiler.getSourceManager().getPresumedLoc(spellingLocation)
418 .getFilename())
419 .endswith(".h"));
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:
430 return
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()) {
438 return nullptr;
440 if (compiler.getSourceManager().isMacroBodyExpansion(
441 compat::getBeginLoc(expr)))
443 return nullptr;
445 return dyn_cast<DeclRefExpr>(expr->getSubExpr()->IgnoreParenImpCasts());
448 void recordConsumption(Expr const * expr) {
449 for (;;) {
450 expr = expr->IgnoreParenImpCasts();
451 if (auto const e = dyn_cast<MemberExpr>(expr)) {
452 expr = e->getBase();
453 continue;
455 if (auto const e = dyn_cast<ArraySubscriptExpr>(expr)) {
456 expr = e->getBase();
457 continue;
459 if (auto const e = dyn_cast<BinaryOperator>(expr)) {
460 if (e->getOpcode() == BO_PtrMemD) {
461 expr = e->getLHS();
462 continue;
465 break;
467 auto const dre = dyn_cast<DeclRefExpr>(expr);
468 if (dre == nullptr) {
469 return;
471 // In C (but not in C++)
473 // (void) x
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) {
478 return;
480 auto const var = dyn_cast<VarDecl>(dre->getDecl());
481 if (var == nullptr) {
482 return;
484 auto & usage = vars_[var->getCanonicalDecl()];
485 if (usage.firstConsumption != nullptr) {
486 return;
488 auto const loc = compat::getBeginLoc(dre);
489 if (compiler.getSourceManager().isMacroArgExpansion(loc)
490 && (Lexer::getImmediateMacroNameForDiagnostics(
491 loc, compiler.getSourceManager(), compiler.getLangOpts())
492 == "assert"))
494 return;
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: */