summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/casttovoid.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'compilerplugins/clang/casttovoid.cxx')
-rw-r--r--compilerplugins/clang/casttovoid.cxx504
1 files changed, 504 insertions, 0 deletions
diff --git a/compilerplugins/clang/casttovoid.cxx b/compilerplugins/clang/casttovoid.cxx
new file mode 100644
index 000000000..e6da5b6d7
--- /dev/null
+++ b/compilerplugins/clang/casttovoid.cxx
@@ -0,0 +1,504 @@
+/* -*- 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 <algorithm>
+#include <cassert>
+#include <map>
+#include <stack>
+
+#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<TypedefType>()) {
+ if (t->getDecl()->hasAttr<WarnUnusedAttr>()) {
+ return true;
+ }
+ }
+ if (auto const t = type->getAs<RecordType>()) {
+ if (t->getDecl()->hasAttr<WarnUnusedAttr>()) {
+ return true;
+ }
+ }
+ return loplugin::isExtraWarnUnusedType(type);
+}
+
+Expr const * lookThroughInitListExpr(Expr const * expr) {
+ if (auto const ile = dyn_cast<InitListExpr>(expr->IgnoreParenImpCasts())) {
+ if (ile->getNumInits() == 1) {
+ return ile->getInit(0);
+ }
+ }
+ return expr;
+}
+
+class CastToVoid final:
+ public loplugin::FilteringPlugin<CastToVoid>
+{
+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 VisitDeclRefExpr(DeclRefExpr const * expr) {
+ if (ignoreLocation(expr)) {
+ return true;
+ }
+ auto const var = dyn_cast<VarDecl>(expr->getDecl());
+ if (var == nullptr) {
+ 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<CXXMemberCallExpr>(expr)) {
+ if (auto const e1 = cmce->getMethodDecl()) {
+ if (e1->isConst() || e1->isStatic()) {
+ recordConsumption(cmce->getImplicitObjectArgument());
+ }
+ } else if (auto const e2 = dyn_cast<BinaryOperator>(
+ cmce->getCallee()->IgnoreParenImpCasts()))
+ {
+ switch (e2->getOpcode()) {
+ case BO_PtrMemD:
+ case BO_PtrMemI:
+ if (e2->getRHS()->getType()->getAs<MemberPointerType>()
+ ->getPointeeType()->getAs<FunctionProtoType>()
+ ->isConst())
+ {
+ recordConsumption(e2->getLHS());
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ } else if (isa<CXXOperatorCallExpr>(expr)) {
+ if (auto const cmd = dyn_cast_or_null<CXXMethodDecl>(
+ 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<ExplicitCastExpr const *> castToVoid;
+ bool mentioned = false;
+ DeclRefExpr const * firstConsumption = nullptr;
+ };
+
+ struct Cast {
+ ExplicitCastExpr const * cast;
+ DeclRefExpr const * sub;
+ };
+
+ std::map<VarDecl const *, Usage> vars_;
+ std::stack<Cast> castToVoid_;
+ std::stack<QualType> 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<ParmVarDecl>(i.first)) {
+ if (!compiler.getLangOpts().CPlusPlus
+ || isSharedCAndCppCode(i.first))
+ {
+ continue;
+ }
+ auto const ctxt = i.first->getDeclContext();
+ if (dyn_cast_or_null<ObjCMethodDecl>(ctxt) != nullptr) {
+ continue;
+ }
+ auto const fun = dyn_cast_or_null<FunctionDecl>(ctxt);
+ assert(fun != nullptr);
+ if (containsPreprocessingConditionalInclusion(
+ fun->getSourceRange()))
+ {
+ continue;
+ }
+ auto const meth = dyn_cast<CXXMethodDecl>(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<FunctionDecl>(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 {
+ 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)
+ && (StringRef(
+ compiler.getSourceManager().getPresumedLoc(spellingLocation)
+ .getFilename())
+ .endswith(".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(
+ compat::getBeginLoc(expr)))
+ {
+ return nullptr;
+ }
+ return dyn_cast<DeclRefExpr>(expr->getSubExpr()->IgnoreParenImpCasts());
+ }
+
+ void recordConsumption(Expr const * expr) {
+ for (;;) {
+ expr = expr->IgnoreParenImpCasts();
+ if (auto const e = dyn_cast<MemberExpr>(expr)) {
+ expr = e->getBase();
+ continue;
+ }
+ if (auto const e = dyn_cast<ArraySubscriptExpr>(expr)) {
+ expr = e->getBase();
+ continue;
+ }
+ if (auto const e = dyn_cast<BinaryOperator>(expr)) {
+ if (e->getOpcode() == BO_PtrMemD) {
+ expr = e->getLHS();
+ continue;
+ }
+ }
+ break;
+ }
+ auto const dre = dyn_cast<DeclRefExpr>(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<VarDecl>(dre->getDecl());
+ if (var == nullptr) {
+ return;
+ }
+ auto & usage = vars_[var->getCanonicalDecl()];
+ if (usage.firstConsumption != nullptr) {
+ return;
+ }
+ auto const loc = compat::getBeginLoc(dre);
+ if (compiler.getSourceManager().isMacroArgExpansion(loc)
+ && (Lexer::getImmediateMacroNameForDiagnostics(
+ loc, compiler.getSourceManager(), compiler.getLangOpts())
+ == "assert"))
+ {
+ return;
+ }
+ usage.firstConsumption = dre;
+ }
+};
+
+static loplugin::Plugin::Registration<CastToVoid> reg("casttovoid");
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */