lok: getSlideShowInfo: interactions: check that properties are available
[LibreOffice.git] / compilerplugins / clang / casttovoid.cxx
blob87c2f4852aac9ad60850a7bd0c3022c1d7c60464
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 if (var->getType().isVolatileQualified()) {
207 return true;
209 auto & usage = vars_[var->getCanonicalDecl()];
210 if (!castToVoid_.empty() && castToVoid_.top().sub == expr) {
211 usage.castToVoid.push_back(castToVoid_.top().cast);
212 } else {
213 usage.mentioned = true;
215 return true;
218 bool VisitImplicitCastExpr(ImplicitCastExpr const * expr) {
219 if (ignoreLocation(expr)) {
220 return true;
222 if (expr->getCastKind() != CK_LValueToRValue) {
223 return true;
225 recordConsumption(expr->getSubExpr());
226 return true;
229 bool VisitCallExpr(CallExpr const * expr) {
230 if (ignoreLocation(expr)) {
231 return true;
233 unsigned firstArg = 0;
234 if (auto const cmce = dyn_cast<CXXMemberCallExpr>(expr)) {
235 if (auto const e1 = cmce->getMethodDecl()) {
236 if (e1->isConst() || e1->isStatic()) {
237 recordConsumption(cmce->getImplicitObjectArgument());
239 } else if (auto const e2 = dyn_cast<BinaryOperator>(
240 cmce->getCallee()->IgnoreParenImpCasts()))
242 switch (e2->getOpcode()) {
243 case BO_PtrMemD:
244 case BO_PtrMemI:
245 if (e2->getRHS()->getType()->getAs<MemberPointerType>()
246 ->getPointeeType()->getAs<FunctionProtoType>()
247 ->isConst())
249 recordConsumption(e2->getLHS());
251 break;
252 default:
253 break;
256 } else if (isa<CXXOperatorCallExpr>(expr)) {
257 if (auto const cmd = dyn_cast_or_null<CXXMethodDecl>(
258 expr->getDirectCallee()))
260 if (!cmd->isStatic()) {
261 assert(expr->getNumArgs() != 0);
262 if (cmd->isConst()) {
263 recordConsumption(expr->getArg(0));
265 firstArg = 1;
269 auto fun = expr->getDirectCallee();
270 if (fun == nullptr) {
271 return true;
273 unsigned const n = std::min(fun->getNumParams(), expr->getNumArgs());
274 for (unsigned i = firstArg; i < n; ++i) {
275 if (!loplugin::TypeCheck(fun->getParamDecl(i)->getType())
276 .LvalueReference().Const())
278 continue;
280 recordConsumption(lookThroughInitListExpr(expr->getArg(i)));
282 return true;
285 bool VisitCXXConstructExpr(CXXConstructExpr const * expr) {
286 if (ignoreLocation(expr)) {
287 return true;
289 auto const ctor = expr->getConstructor();
290 unsigned const n = std::min(ctor->getNumParams(), expr->getNumArgs());
291 for (unsigned i = 0; i != n; ++i) {
292 if (!loplugin::TypeCheck(ctor->getParamDecl(i)->getType())
293 .LvalueReference().Const())
295 continue;
297 recordConsumption(lookThroughInitListExpr(expr->getArg(i)));
299 return true;
302 bool VisitReturnStmt(ReturnStmt const * stmt) {
303 if (ignoreLocation(stmt)) {
304 return true;
306 assert(!returnTypes_.empty());
307 if (!loplugin::TypeCheck(returnTypes_.top()).LvalueReference().Const())
309 return true;
311 auto const ret = stmt->getRetValue();
312 if (ret == nullptr) {
313 return true;
315 recordConsumption(lookThroughInitListExpr(ret));
316 return true;
319 bool VisitVarDecl(VarDecl const * decl) {
320 if (ignoreLocation(decl)) {
321 return true;
323 if (!loplugin::TypeCheck(decl->getType()).LvalueReference()) {
324 return true;
326 auto const init = decl->getInit();
327 if (init == nullptr) {
328 return true;
330 recordConsumption(lookThroughInitListExpr(init));
331 return true;
334 bool VisitFieldDecl(FieldDecl const * decl) {
335 if (ignoreLocation(decl)) {
336 return true;
338 if (!loplugin::TypeCheck(decl->getType()).LvalueReference()) {
339 return true;
341 auto const init = decl->getInClassInitializer();
342 if (init == nullptr) {
343 return true;
345 recordConsumption(lookThroughInitListExpr(init));
346 return true;
349 private:
350 struct Usage {
351 std::vector<ExplicitCastExpr const *> castToVoid;
352 bool mentioned = false;
353 DeclRefExpr const * firstConsumption = nullptr;
356 struct Cast {
357 ExplicitCastExpr const * cast;
358 DeclRefExpr const * sub;
361 std::map<VarDecl const *, Usage> vars_;
362 std::stack<Cast> castToVoid_;
363 std::stack<QualType> returnTypes_;
365 void run() override {
366 if (compiler.getPreprocessor().getIdentifierInfo("NDEBUG")->hasMacroDefinition()) {
367 return;
369 if (!TraverseDecl(compiler.getASTContext().getTranslationUnitDecl())) {
370 return;
372 for (auto const & i: vars_) {
373 if (i.second.firstConsumption == nullptr) {
374 if (i.second.mentioned) {
375 continue;
377 if (isa<ParmVarDecl>(i.first)) {
378 if (!compiler.getLangOpts().CPlusPlus
379 || isSharedCAndCppCode(i.first))
381 continue;
383 auto const ctxt = i.first->getDeclContext();
384 if (dyn_cast_or_null<ObjCMethodDecl>(ctxt) != nullptr) {
385 continue;
387 auto const fun = dyn_cast_or_null<FunctionDecl>(ctxt);
388 assert(fun != nullptr);
389 if (containsPreprocessingConditionalInclusion(
390 fun->getSourceRange()))
392 continue;
394 auto const meth = dyn_cast<CXXMethodDecl>(fun);
395 report(
396 DiagnosticsEngine::Warning,
397 "unused%select{| virtual function}0 parameter name",
398 i.first->getLocation())
399 << (meth != nullptr && meth->isVirtual())
400 << i.first->getSourceRange();
401 for (auto const j: i.second.castToVoid) {
402 report(
403 DiagnosticsEngine::Note, "cast to void here",
404 j->getExprLoc())
405 << j->getSourceRange();
407 } else if (!i.second.castToVoid.empty()
408 && !isWarnUnusedType(i.first->getType()))
410 auto const fun = dyn_cast_or_null<FunctionDecl>(i.first->getDeclContext());
411 assert(fun != nullptr);
412 if (containsPreprocessingConditionalInclusion(fun->getSourceRange())) {
413 continue;
415 report(
416 DiagnosticsEngine::Warning,
417 "unused variable %select{declaration|name}0",
418 i.first->getLocation())
419 << i.first->isExceptionVariable()
420 << i.first->getSourceRange();
421 for (auto const j: i.second.castToVoid) {
422 report(
423 DiagnosticsEngine::Note, "cast to void here",
424 j->getExprLoc())
425 << j->getSourceRange();
428 } else {
429 if (auto const fun = dyn_cast_or_null<FunctionDecl>(i.first->getDeclContext())) {
430 if (containsPreprocessingConditionalInclusion(fun->getSourceRange())) {
431 continue;
434 for (auto const j: i.second.castToVoid) {
435 report(
436 DiagnosticsEngine::Warning, "unnecessary cast to void",
437 j->getExprLoc())
438 << j->getSourceRange();
439 report(
440 DiagnosticsEngine::Note, "first consumption is here",
441 i.second.firstConsumption->getExprLoc())
442 << i.second.firstConsumption->getSourceRange();
448 bool isFromCIncludeFile(SourceLocation spellingLocation) const {
449 return !compiler.getSourceManager().isInMainFile(spellingLocation)
450 && compat::ends_with(
451 StringRef(
452 compiler.getSourceManager().getPresumedLoc(spellingLocation).getFilename()),
453 ".h");
456 bool isSharedCAndCppCode(VarDecl const * decl) const {
457 auto loc = decl->getLocation();
458 while (compiler.getSourceManager().isMacroArgExpansion(loc)) {
459 loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
461 // Assume that code is intended to be shared between C and C++ if it
462 // comes from an include file ending in .h, and is either in an extern
463 // "C" context or the body of a macro definition:
464 return
465 isFromCIncludeFile(compiler.getSourceManager().getSpellingLoc(loc))
466 && (decl->isInExternCContext()
467 || compiler.getSourceManager().isMacroBodyExpansion(loc));
470 DeclRefExpr const * checkCast(ExplicitCastExpr const * expr) {
471 if (!loplugin::TypeCheck(expr->getTypeAsWritten()).Void()) {
472 return nullptr;
474 if (compiler.getSourceManager().isMacroBodyExpansion(
475 expr->getBeginLoc()))
477 return nullptr;
479 return dyn_cast<DeclRefExpr>(expr->getSubExpr()->IgnoreParenImpCasts());
482 void recordConsumption(Expr const * expr) {
483 for (;;) {
484 expr = expr->IgnoreParenImpCasts();
485 if (auto const e = dyn_cast<MemberExpr>(expr)) {
486 expr = e->getBase();
487 continue;
489 if (auto const e = dyn_cast<ArraySubscriptExpr>(expr)) {
490 expr = e->getBase();
491 continue;
493 if (auto const e = dyn_cast<BinaryOperator>(expr)) {
494 if (e->getOpcode() == BO_PtrMemD) {
495 expr = e->getLHS();
496 continue;
499 break;
501 auto const dre = dyn_cast<DeclRefExpr>(expr);
502 if (dre == nullptr) {
503 return;
505 // In C (but not in C++)
507 // (void) x
509 // contains an implicit lvalue-to-rvalue cast, so VisitImplicitCastExpr
510 // would record that as a consumption if we didn't filter it out here:
511 if (!castToVoid_.empty() && castToVoid_.top().sub == dre) {
512 return;
514 auto const var = dyn_cast<VarDecl>(dre->getDecl());
515 if (var == nullptr) {
516 return;
518 if (var->getType().isVolatileQualified()) {
519 return;
521 auto & usage = vars_[var->getCanonicalDecl()];
522 if (usage.firstConsumption != nullptr) {
523 return;
525 auto const loc = dre->getBeginLoc();
526 if (compiler.getSourceManager().isMacroArgExpansion(loc)
527 && (Lexer::getImmediateMacroNameForDiagnostics(
528 loc, compiler.getSourceManager(), compiler.getLangOpts())
529 == "assert"))
531 return;
533 usage.firstConsumption = dre;
537 static loplugin::Plugin::Registration<CastToVoid> reg("casttovoid");
541 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */