/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include "clang/AST/Attr.h" #include "check.hxx" #include "compat.hxx" #include "plugin.hxx" namespace { bool isWarnUnusedType(QualType type) { if (auto const t = type->getAs()) { if (t->getDecl()->hasAttr()) { return true; } } if (auto const t = type->getAs()) { if (t->getDecl()->hasAttr()) { return true; } } return loplugin::isExtraWarnUnusedType(type); } Expr const * lookThroughInitListExpr(Expr const * expr) { if (auto const ile = dyn_cast(expr->IgnoreParenImpCasts())) { if (ile->getNumInits() == 1) { return ile->getInit(0); } } return expr; } class CastToVoid final: public loplugin::FilteringPlugin { public: explicit CastToVoid(loplugin::InstantiationData const & data): FilteringPlugin(data) {} bool TraverseCStyleCastExpr(CStyleCastExpr * expr) { auto const dre = checkCast(expr); if (dre != nullptr) { castToVoid_.push({expr, dre}); } auto const ret = RecursiveASTVisitor::TraverseCStyleCastExpr(expr); if (dre != nullptr) { assert(!castToVoid_.empty()); assert(castToVoid_.top().cast == expr); assert(castToVoid_.top().sub == dre); castToVoid_.pop(); } return ret; } bool TraverseCXXStaticCastExpr(CXXStaticCastExpr * expr) { auto const dre = checkCast(expr); if (dre != nullptr) { castToVoid_.push({expr, dre}); } auto const ret = RecursiveASTVisitor::TraverseCXXStaticCastExpr(expr); if (dre != nullptr) { assert(!castToVoid_.empty()); assert(castToVoid_.top().cast == expr); assert(castToVoid_.top().sub == dre); castToVoid_.pop(); } return ret; } bool TraverseCXXFunctionalCastExpr(CXXFunctionalCastExpr * expr) { auto const dre = checkCast(expr); if (dre != nullptr) { castToVoid_.push({expr, dre}); } auto const ret = RecursiveASTVisitor::TraverseCXXFunctionalCastExpr( expr); if (dre != nullptr) { assert(!castToVoid_.empty()); assert(castToVoid_.top().cast == expr); assert(castToVoid_.top().sub == dre); castToVoid_.pop(); } return ret; } bool TraverseFunctionDecl(FunctionDecl * decl) { returnTypes_.push(decl->getReturnType()); auto const ret = RecursiveASTVisitor::TraverseFunctionDecl(decl); assert(!returnTypes_.empty()); assert(returnTypes_.top() == decl->getReturnType()); returnTypes_.pop(); return ret; } bool TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl * decl) { returnTypes_.push(decl->getReturnType()); auto const ret = RecursiveASTVisitor::TraverseCXXDeductionGuideDecl( decl); assert(!returnTypes_.empty()); assert(returnTypes_.top() == decl->getReturnType()); returnTypes_.pop(); return ret; } bool TraverseCXXMethodDecl(CXXMethodDecl * decl) { returnTypes_.push(decl->getReturnType()); auto const ret = RecursiveASTVisitor::TraverseCXXMethodDecl(decl); assert(!returnTypes_.empty()); assert(returnTypes_.top() == decl->getReturnType()); returnTypes_.pop(); return ret; } bool TraverseCXXConstructorDecl(CXXConstructorDecl * decl) { returnTypes_.push(decl->getReturnType()); auto const ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(decl); assert(!returnTypes_.empty()); assert(returnTypes_.top() == decl->getReturnType()); returnTypes_.pop(); return ret; } bool TraverseCXXDestructorDecl(CXXDestructorDecl * decl) { returnTypes_.push(decl->getReturnType()); auto const ret = RecursiveASTVisitor::TraverseCXXDestructorDecl(decl); assert(!returnTypes_.empty()); assert(returnTypes_.top() == decl->getReturnType()); returnTypes_.pop(); return ret; } bool TraverseCXXConversionDecl(CXXConversionDecl * decl) { returnTypes_.push(decl->getReturnType()); auto const ret = RecursiveASTVisitor::TraverseCXXConversionDecl(decl); assert(!returnTypes_.empty()); assert(returnTypes_.top() == decl->getReturnType()); returnTypes_.pop(); return ret; } bool TraverseObjCMethodDecl(ObjCMethodDecl * decl) { returnTypes_.push(decl->getReturnType()); auto const ret = RecursiveASTVisitor::TraverseObjCMethodDecl(decl); assert(!returnTypes_.empty()); assert(returnTypes_.top() == decl->getReturnType()); returnTypes_.pop(); return ret; } bool TraverseConstructorInitializer(CXXCtorInitializer * init) { if (auto const field = init->getAnyMember()) { if (loplugin::TypeCheck(field->getType()).LvalueReference()) { recordConsumption(lookThroughInitListExpr(init->getInit())); } } return RecursiveASTVisitor::TraverseConstructorInitializer(init); } bool TraverseLambdaExpr(LambdaExpr * expr, DataRecursionQueue * queue = nullptr) { if (!shouldTraversePostOrder()) { if (!WalkUpFromLambdaExpr(expr)) { return false; } } auto const n = expr->capture_size(); for (unsigned i = 0; i != n; ++i) { auto const c = expr->capture_begin() + i; if (c->isExplicit() || shouldVisitImplicitCode()) { if (!TraverseLambdaCapture(expr, c, expr->capture_init_begin()[i])) { return false; } } } if (!TraverseCXXRecordDecl(expr->getLambdaClass())) { return false; } if (!queue && shouldTraversePostOrder()) { if (!WalkUpFromLambdaExpr(expr)) { return false; } } return true; } bool VisitDeclRefExpr(DeclRefExpr const * expr) { if (ignoreLocation(expr)) { return true; } auto const var = dyn_cast(expr->getDecl()); if (var == nullptr) { return true; } if (var->getType().isVolatileQualified()) { return true; } auto & usage = vars_[var->getCanonicalDecl()]; if (!castToVoid_.empty() && castToVoid_.top().sub == expr) { usage.castToVoid.push_back(castToVoid_.top().cast); } else { usage.mentioned = true; } return true; } bool VisitImplicitCastExpr(ImplicitCastExpr const * expr) { if (ignoreLocation(expr)) { return true; } if (expr->getCastKind() != CK_LValueToRValue) { return true; } recordConsumption(expr->getSubExpr()); return true; } bool VisitCallExpr(CallExpr const * expr) { if (ignoreLocation(expr)) { return true; } unsigned firstArg = 0; if (auto const cmce = dyn_cast(expr)) { if (auto const e1 = cmce->getMethodDecl()) { if (e1->isConst() || e1->isStatic()) { recordConsumption(cmce->getImplicitObjectArgument()); } } else if (auto const e2 = dyn_cast( cmce->getCallee()->IgnoreParenImpCasts())) { switch (e2->getOpcode()) { case BO_PtrMemD: case BO_PtrMemI: if (e2->getRHS()->getType()->getAs() ->getPointeeType()->getAs() ->isConst()) { recordConsumption(e2->getLHS()); } break; default: break; } } } else if (isa(expr)) { if (auto const cmd = dyn_cast_or_null( expr->getDirectCallee())) { if (!cmd->isStatic()) { assert(expr->getNumArgs() != 0); if (cmd->isConst()) { recordConsumption(expr->getArg(0)); } firstArg = 1; } } } auto fun = expr->getDirectCallee(); if (fun == nullptr) { return true; } unsigned const n = std::min(fun->getNumParams(), expr->getNumArgs()); for (unsigned i = firstArg; i < n; ++i) { if (!loplugin::TypeCheck(fun->getParamDecl(i)->getType()) .LvalueReference().Const()) { continue; } recordConsumption(lookThroughInitListExpr(expr->getArg(i))); } return true; } bool VisitCXXConstructExpr(CXXConstructExpr const * expr) { if (ignoreLocation(expr)) { return true; } auto const ctor = expr->getConstructor(); unsigned const n = std::min(ctor->getNumParams(), expr->getNumArgs()); for (unsigned i = 0; i != n; ++i) { if (!loplugin::TypeCheck(ctor->getParamDecl(i)->getType()) .LvalueReference().Const()) { continue; } recordConsumption(lookThroughInitListExpr(expr->getArg(i))); } return true; } bool VisitReturnStmt(ReturnStmt const * stmt) { if (ignoreLocation(stmt)) { return true; } assert(!returnTypes_.empty()); if (!loplugin::TypeCheck(returnTypes_.top()).LvalueReference().Const()) { return true; } auto const ret = stmt->getRetValue(); if (ret == nullptr) { return true; } recordConsumption(lookThroughInitListExpr(ret)); return true; } bool VisitVarDecl(VarDecl const * decl) { if (ignoreLocation(decl)) { return true; } if (!loplugin::TypeCheck(decl->getType()).LvalueReference()) { return true; } auto const init = decl->getInit(); if (init == nullptr) { return true; } recordConsumption(lookThroughInitListExpr(init)); return true; } bool VisitFieldDecl(FieldDecl const * decl) { if (ignoreLocation(decl)) { return true; } if (!loplugin::TypeCheck(decl->getType()).LvalueReference()) { return true; } auto const init = decl->getInClassInitializer(); if (init == nullptr) { return true; } recordConsumption(lookThroughInitListExpr(init)); return true; } private: struct Usage { std::vector castToVoid; bool mentioned = false; DeclRefExpr const * firstConsumption = nullptr; }; struct Cast { ExplicitCastExpr const * cast; DeclRefExpr const * sub; }; std::map vars_; std::stack castToVoid_; std::stack returnTypes_; void run() override { if (compiler.getPreprocessor().getIdentifierInfo("NDEBUG")->hasMacroDefinition()) { return; } if (!TraverseDecl(compiler.getASTContext().getTranslationUnitDecl())) { return; } for (auto const & i: vars_) { if (i.second.firstConsumption == nullptr) { if (i.second.mentioned) { continue; } if (isa(i.first)) { if (!compiler.getLangOpts().CPlusPlus || isSharedCAndCppCode(i.first)) { continue; } auto const ctxt = i.first->getDeclContext(); if (dyn_cast_or_null(ctxt) != nullptr) { continue; } auto const fun = dyn_cast_or_null(ctxt); assert(fun != nullptr); if (containsPreprocessingConditionalInclusion( fun->getSourceRange())) { continue; } auto const meth = dyn_cast(fun); report( DiagnosticsEngine::Warning, "unused%select{| virtual function}0 parameter name", i.first->getLocation()) << (meth != nullptr && meth->isVirtual()) << i.first->getSourceRange(); for (auto const j: i.second.castToVoid) { report( DiagnosticsEngine::Note, "cast to void here", j->getExprLoc()) << j->getSourceRange(); } } else if (!i.second.castToVoid.empty() && !isWarnUnusedType(i.first->getType())) { auto const fun = dyn_cast_or_null(i.first->getDeclContext()); assert(fun != nullptr); if (containsPreprocessingConditionalInclusion(fun->getSourceRange())) { continue; } report( DiagnosticsEngine::Warning, "unused variable %select{declaration|name}0", i.first->getLocation()) << i.first->isExceptionVariable() << i.first->getSourceRange(); for (auto const j: i.second.castToVoid) { report( DiagnosticsEngine::Note, "cast to void here", j->getExprLoc()) << j->getSourceRange(); } } } else { if (auto const fun = dyn_cast_or_null(i.first->getDeclContext())) { if (containsPreprocessingConditionalInclusion(fun->getSourceRange())) { continue; } } for (auto const j: i.second.castToVoid) { report( DiagnosticsEngine::Warning, "unnecessary cast to void", j->getExprLoc()) << j->getSourceRange(); report( DiagnosticsEngine::Note, "first consumption is here", i.second.firstConsumption->getExprLoc()) << i.second.firstConsumption->getSourceRange(); } } } } bool isFromCIncludeFile(SourceLocation spellingLocation) const { return !compiler.getSourceManager().isInMainFile(spellingLocation) && compat::ends_with( StringRef( compiler.getSourceManager().getPresumedLoc(spellingLocation).getFilename()), ".h"); } bool isSharedCAndCppCode(VarDecl const * decl) const { auto loc = decl->getLocation(); while (compiler.getSourceManager().isMacroArgExpansion(loc)) { loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc); } // Assume that code is intended to be shared between C and C++ if it // comes from an include file ending in .h, and is either in an extern // "C" context or the body of a macro definition: return isFromCIncludeFile(compiler.getSourceManager().getSpellingLoc(loc)) && (decl->isInExternCContext() || compiler.getSourceManager().isMacroBodyExpansion(loc)); } DeclRefExpr const * checkCast(ExplicitCastExpr const * expr) { if (!loplugin::TypeCheck(expr->getTypeAsWritten()).Void()) { return nullptr; } if (compiler.getSourceManager().isMacroBodyExpansion( expr->getBeginLoc())) { return nullptr; } return dyn_cast(expr->getSubExpr()->IgnoreParenImpCasts()); } void recordConsumption(Expr const * expr) { for (;;) { expr = expr->IgnoreParenImpCasts(); if (auto const e = dyn_cast(expr)) { expr = e->getBase(); continue; } if (auto const e = dyn_cast(expr)) { expr = e->getBase(); continue; } if (auto const e = dyn_cast(expr)) { if (e->getOpcode() == BO_PtrMemD) { expr = e->getLHS(); continue; } } break; } auto const dre = dyn_cast(expr); if (dre == nullptr) { return; } // In C (but not in C++) // // (void) x // // contains an implicit lvalue-to-rvalue cast, so VisitImplicitCastExpr // would record that as a consumption if we didn't filter it out here: if (!castToVoid_.empty() && castToVoid_.top().sub == dre) { return; } auto const var = dyn_cast(dre->getDecl()); if (var == nullptr) { return; } if (var->getType().isVolatileQualified()) { return; } auto & usage = vars_[var->getCanonicalDecl()]; if (usage.firstConsumption != nullptr) { return; } auto const loc = dre->getBeginLoc(); if (compiler.getSourceManager().isMacroArgExpansion(loc) && (Lexer::getImmediateMacroNameForDiagnostics( loc, compiler.getSourceManager(), compiler.getLangOpts()) == "assert")) { return; } usage.firstConsumption = dre; } }; static loplugin::Plugin::Registration reg("casttovoid"); } /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */