avoid windres preprocessor quoting-messups with current cygwin
[LibreOffice.git] / compilerplugins / clang / casttovoid.cxx
blobe8b080dab2a59f74d247e2536ed1ba379faddc8b
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 RecursiveASTVisitor<CastToVoid>, public loplugin::Plugin
49 public:
50 explicit CastToVoid(loplugin::InstantiationData const & data):
51 Plugin(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 #if CLANG_VERSION >= 50000
109 bool TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl * decl) {
110 returnTypes_.push(decl->getReturnType());
111 auto const ret = RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(
112 decl);
113 assert(!returnTypes_.empty());
114 assert(returnTypes_.top() == decl->getReturnType());
115 returnTypes_.pop();
116 return ret;
118 #endif
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());
125 returnTypes_.pop();
126 return ret;
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());
134 returnTypes_.pop();
135 return ret;
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());
143 returnTypes_.pop();
144 return ret;
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());
152 returnTypes_.pop();
153 return ret;
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());
161 returnTypes_.pop();
162 return ret;
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)) {
176 return true;
178 auto const var = dyn_cast<VarDecl>(expr->getDecl());
179 if (var == nullptr) {
180 return true;
182 auto & usage = vars_[var->getCanonicalDecl()];
183 if (!castToVoid_.empty() && castToVoid_.top().sub == expr) {
184 usage.castToVoid.push_back(castToVoid_.top().cast);
185 } else {
186 usage.mentioned = true;
188 return true;
191 bool VisitImplicitCastExpr(ImplicitCastExpr const * expr) {
192 if (ignoreLocation(expr)) {
193 return true;
195 if (expr->getCastKind() != CK_LValueToRValue) {
196 return true;
198 recordConsumption(expr->getSubExpr());
199 return true;
202 bool VisitCallExpr(CallExpr const * expr) {
203 if (ignoreLocation(expr)) {
204 return true;
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()) {
216 case BO_PtrMemD:
217 case BO_PtrMemI:
218 if (e2->getRHS()->getType()->getAs<MemberPointerType>()
219 ->getPointeeType()->getAs<FunctionProtoType>()
220 ->isConst())
222 recordConsumption(e2->getLHS());
224 break;
225 default:
226 break;
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));
238 firstArg = 1;
242 auto fun = expr->getDirectCallee();
243 if (fun == nullptr) {
244 return true;
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())
251 continue;
253 recordConsumption(lookThroughInitListExpr(expr->getArg(i)));
255 return true;
258 bool VisitCXXConstructExpr(CXXConstructExpr const * expr) {
259 if (ignoreLocation(expr)) {
260 return true;
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())
268 continue;
270 recordConsumption(lookThroughInitListExpr(expr->getArg(i)));
272 return true;
275 bool VisitReturnStmt(ReturnStmt const * stmt) {
276 if (ignoreLocation(stmt)) {
277 return true;
279 assert(!returnTypes_.empty());
280 if (!loplugin::TypeCheck(returnTypes_.top()).LvalueReference().Const())
282 return true;
284 auto const ret = stmt->getRetValue();
285 if (ret == nullptr) {
286 return true;
288 recordConsumption(lookThroughInitListExpr(ret));
289 return true;
292 bool VisitVarDecl(VarDecl const * decl) {
293 if (ignoreLocation(decl)) {
294 return true;
296 if (!loplugin::TypeCheck(decl->getType()).LvalueReference()) {
297 return true;
299 auto const init = decl->getInit();
300 if (init == nullptr) {
301 return true;
303 recordConsumption(lookThroughInitListExpr(init));
304 return true;
307 bool VisitFieldDecl(FieldDecl const * decl) {
308 if (ignoreLocation(decl)) {
309 return true;
311 if (!loplugin::TypeCheck(decl->getType()).LvalueReference()) {
312 return true;
314 auto const init = decl->getInClassInitializer();
315 if (init == nullptr) {
316 return true;
318 recordConsumption(lookThroughInitListExpr(init));
319 return true;
322 private:
323 struct Usage {
324 std::vector<ExplicitCastExpr const *> castToVoid;
325 bool mentioned = false;
326 DeclRefExpr const * firstConsumption = nullptr;
329 struct Cast {
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()) {
340 return;
342 if (!TraverseDecl(compiler.getASTContext().getTranslationUnitDecl())) {
343 return;
345 for (auto const & i: vars_) {
346 if (i.second.firstConsumption == nullptr) {
347 if (i.second.mentioned) {
348 continue;
350 if (isa<ParmVarDecl>(i.first)) {
351 if (!compiler.getLangOpts().CPlusPlus
352 || isSharedCAndCppCode(i.first))
354 continue;
356 auto const ctxt = i.first->getDeclContext();
357 if (dyn_cast_or_null<ObjCMethodDecl>(ctxt) != nullptr) {
358 continue;
360 auto const fun = dyn_cast_or_null<FunctionDecl>(ctxt);
361 assert(fun != nullptr);
362 if (containsPreprocessingConditionalInclusion(
363 fun->getSourceRange()))
365 continue;
367 auto const meth = dyn_cast<CXXMethodDecl>(fun);
368 report(
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) {
375 report(
376 DiagnosticsEngine::Note, "cast to void here",
377 j->getExprLoc())
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())) {
386 continue;
388 report(
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) {
395 report(
396 DiagnosticsEngine::Note, "cast to void here",
397 j->getExprLoc())
398 << j->getSourceRange();
401 } else {
402 for (auto const j: i.second.castToVoid) {
403 report(
404 DiagnosticsEngine::Warning, "unnecessary cast to void",
405 j->getExprLoc())
406 << j->getSourceRange();
407 report(
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)
418 && (StringRef(
419 compiler.getSourceManager().getPresumedLoc(spellingLocation)
420 .getFilename())
421 .endswith(".h"));
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:
432 return
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()) {
440 return nullptr;
442 if (compiler.getSourceManager().isMacroBodyExpansion(
443 expr->getLocStart()))
445 return nullptr;
447 return dyn_cast<DeclRefExpr>(expr->getSubExpr()->IgnoreParenImpCasts());
450 void recordConsumption(Expr const * expr) {
451 for (;;) {
452 expr = expr->IgnoreParenImpCasts();
453 if (auto const e = dyn_cast<MemberExpr>(expr)) {
454 expr = e->getBase();
455 continue;
457 if (auto const e = dyn_cast<ArraySubscriptExpr>(expr)) {
458 expr = e->getBase();
459 continue;
461 if (auto const e = dyn_cast<BinaryOperator>(expr)) {
462 if (e->getOpcode() == BO_PtrMemD) {
463 expr = e->getLHS();
464 continue;
467 break;
469 auto const dre = dyn_cast<DeclRefExpr>(expr);
470 if (dre == nullptr) {
471 return;
473 // In C (but not in C++)
475 // (void) x
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) {
480 return;
482 auto const var = dyn_cast<VarDecl>(dre->getDecl());
483 if (var == nullptr) {
484 return;
486 auto & usage = vars_[var->getCanonicalDecl()];
487 if (usage.firstConsumption != nullptr) {
488 return;
490 auto const loc = dre->getLocStart();
491 if (compiler.getSourceManager().isMacroArgExpansion(loc)
492 && (compat::getImmediateMacroNameForDiagnostics(
493 loc, compiler.getSourceManager(), compiler.getLangOpts())
494 == "assert"))
496 return;
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: */