summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/simplifypointertobool.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'compilerplugins/clang/simplifypointertobool.cxx')
-rw-r--r--compilerplugins/clang/simplifypointertobool.cxx450
1 files changed, 450 insertions, 0 deletions
diff --git a/compilerplugins/clang/simplifypointertobool.cxx b/compilerplugins/clang/simplifypointertobool.cxx
new file mode 100644
index 0000000000..11aed0f317
--- /dev/null
+++ b/compilerplugins/clang/simplifypointertobool.cxx
@@ -0,0 +1,450 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * 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 <deque>
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <set>
+
+#include <clang/AST/CXXInheritance.h>
+
+#include "plugin.hxx"
+#include "check.hxx"
+
+/**
+ Simplify boolean expressions involving smart pointers e.g.
+ if (x.get())
+ can be
+ if (x)
+*/
+//TODO: Make this a shared plugin for Clang 12 (and possibly even for older Clang) again.
+
+namespace
+{
+class SimplifyPointerToBool : public loplugin::FilteringRewritePlugin<SimplifyPointerToBool>
+{
+public:
+ explicit SimplifyPointerToBool(loplugin::InstantiationData const& data)
+ : FilteringRewritePlugin(data)
+ {
+ }
+
+ virtual void run() override
+ {
+ if (preRun())
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+ }
+
+ bool VisitImplicitCastExpr(ImplicitCastExpr const*);
+ bool VisitBinaryOperator(BinaryOperator const*);
+
+ bool PreTraverseUnaryOperator(UnaryOperator* expr)
+ {
+ if (expr->getOpcode() == UO_LNot)
+ {
+ contextuallyConvertedExprs_.push_back(expr->getSubExpr()->IgnoreParenImpCasts());
+ }
+ return true;
+ }
+
+ bool PostTraverseUnaryOperator(UnaryOperator* expr, bool)
+ {
+ if (expr->getOpcode() == UO_LNot)
+ {
+ assert(!contextuallyConvertedExprs_.empty());
+ contextuallyConvertedExprs_.pop_back();
+ }
+ return true;
+ }
+
+ bool TraverseUnaryOperator(UnaryOperator* expr)
+ {
+ auto res = PreTraverseUnaryOperator(expr);
+ assert(res);
+ res = FilteringRewritePlugin::TraverseUnaryOperator(expr);
+ PostTraverseUnaryOperator(expr, res);
+ return res;
+ }
+
+ bool PreTraverseBinaryOperator(BinaryOperator* expr)
+ {
+ auto const op = expr->getOpcode();
+ if (op == BO_LAnd || op == BO_LOr)
+ {
+ contextuallyConvertedExprs_.push_back(expr->getLHS()->IgnoreParenImpCasts());
+ contextuallyConvertedExprs_.push_back(expr->getRHS()->IgnoreParenImpCasts());
+ }
+ return true;
+ }
+
+ bool PostTraverseBinaryOperator(BinaryOperator* expr, bool)
+ {
+ auto const op = expr->getOpcode();
+ if (op == BO_LAnd || op == BO_LOr)
+ {
+ assert(contextuallyConvertedExprs_.size() >= 2);
+ contextuallyConvertedExprs_.pop_back();
+ contextuallyConvertedExprs_.pop_back();
+ }
+ return true;
+ }
+
+ bool TraverseBinaryOperator(BinaryOperator* expr)
+ {
+ auto res = PreTraverseBinaryOperator(expr);
+ assert(res);
+ res = FilteringRewritePlugin::TraverseBinaryOperator(expr);
+ PostTraverseBinaryOperator(expr, res);
+ return res;
+ }
+
+ bool PreTraverseConditionalOperator(ConditionalOperator* expr)
+ {
+ contextuallyConvertedExprs_.push_back(expr->getCond()->IgnoreParenImpCasts());
+ return true;
+ }
+
+ bool PostTraverseConditionalOperator(ConditionalOperator*, bool)
+ {
+ assert(!contextuallyConvertedExprs_.empty());
+ contextuallyConvertedExprs_.pop_back();
+ return true;
+ }
+
+ bool TraverseConditionalOperator(ConditionalOperator* expr)
+ {
+ auto res = PreTraverseConditionalOperator(expr);
+ assert(res);
+ res = FilteringRewritePlugin::TraverseConditionalOperator(expr);
+ PostTraverseConditionalOperator(expr, res);
+ return res;
+ }
+
+ bool PreTraverseIfStmt(IfStmt* stmt)
+ {
+ if (auto const cond = stmt->getCond())
+ {
+ contextuallyConvertedExprs_.push_back(cond->IgnoreParenImpCasts());
+ }
+ return true;
+ }
+
+ bool PostTraverseIfStmt(IfStmt* stmt, bool)
+ {
+ if (stmt->getCond() != nullptr)
+ {
+ assert(!contextuallyConvertedExprs_.empty());
+ contextuallyConvertedExprs_.pop_back();
+ }
+ return true;
+ }
+
+ bool TraverseIfStmt(IfStmt* stmt)
+ {
+ auto res = PreTraverseIfStmt(stmt);
+ assert(res);
+ res = FilteringRewritePlugin::TraverseIfStmt(stmt);
+ PostTraverseIfStmt(stmt, res);
+ return res;
+ }
+
+ bool PreTraverseWhileStmt(WhileStmt* stmt)
+ {
+ contextuallyConvertedExprs_.push_back(stmt->getCond()->IgnoreParenImpCasts());
+ return true;
+ }
+
+ bool PostTraverseWhileStmt(WhileStmt*, bool)
+ {
+ assert(!contextuallyConvertedExprs_.empty());
+ contextuallyConvertedExprs_.pop_back();
+ return true;
+ }
+
+ bool TraverseWhileStmt(WhileStmt* stmt)
+ {
+ auto res = PreTraverseWhileStmt(stmt);
+ assert(res);
+ res = FilteringRewritePlugin::TraverseWhileStmt(stmt);
+ PostTraverseWhileStmt(stmt, res);
+ return res;
+ }
+
+ bool PreTraverseDoStmt(DoStmt* stmt)
+ {
+ contextuallyConvertedExprs_.push_back(stmt->getCond()->IgnoreParenImpCasts());
+ return true;
+ }
+
+ bool PostTraverseDoStmt(DoStmt*, bool)
+ {
+ assert(!contextuallyConvertedExprs_.empty());
+ contextuallyConvertedExprs_.pop_back();
+ return true;
+ }
+
+ bool TraverseDoStmt(DoStmt* stmt)
+ {
+ auto res = PreTraverseDoStmt(stmt);
+ assert(res);
+ res = FilteringRewritePlugin::TraverseDoStmt(stmt);
+ PostTraverseDoStmt(stmt, res);
+ return res;
+ }
+
+ bool PreTraverseForStmt(ForStmt* stmt)
+ {
+ auto const e = stmt->getCond();
+ if (e != nullptr)
+ {
+ contextuallyConvertedExprs_.push_back(e->IgnoreParenImpCasts());
+ }
+ return true;
+ }
+
+ bool PostTraverseForStmt(ForStmt* stmt, bool)
+ {
+ if (stmt->getCond() != nullptr)
+ {
+ assert(!contextuallyConvertedExprs_.empty());
+ contextuallyConvertedExprs_.pop_back();
+ }
+ return true;
+ }
+
+ bool TraverseForStmt(ForStmt* stmt)
+ {
+ auto res = PreTraverseForStmt(stmt);
+ assert(res);
+ res = FilteringRewritePlugin::TraverseForStmt(stmt);
+ PostTraverseForStmt(stmt, res);
+ return res;
+ }
+
+private:
+ bool isContextuallyConverted(Expr const* expr) const
+ {
+ return std::find(contextuallyConvertedExprs_.begin(), contextuallyConvertedExprs_.end(),
+ expr)
+ != contextuallyConvertedExprs_.end();
+ }
+
+ // Get the source range starting at the "."or "->" (plus any preceding non-comment white space):
+ SourceRange getCallSourceRange(CXXMemberCallExpr const* expr) const
+ {
+ if (expr->getImplicitObjectArgument() == nullptr)
+ {
+ //TODO: Arguably, such a call of a `get` member function from within some member
+ // function (so that syntactically no caller is mentioned) should already be handled
+ // differently when reporting it (just "drop the get()" does not make sense), instead of
+ // being filtered here:
+ return {};
+ }
+ // CXXMemberCallExpr::getExprLoc happens to return the location following the "." or "->":
+ auto start = compiler.getSourceManager().getSpellingLoc(expr->getExprLoc());
+ if (!start.isValid())
+ {
+ return {};
+ }
+ for (;;)
+ {
+ start = Lexer::GetBeginningOfToken(start.getLocWithOffset(-1),
+ compiler.getSourceManager(), compiler.getLangOpts());
+ auto const s = StringRef(compiler.getSourceManager().getCharacterData(start),
+ Lexer::MeasureTokenLength(start, compiler.getSourceManager(),
+ compiler.getLangOpts()));
+ if (s.empty() || s.startswith("\\\n"))
+ {
+ continue;
+ }
+ if (s != "." && s != "->")
+ {
+ return {};
+ }
+ break;
+ }
+ for (;;)
+ {
+ auto start1 = Lexer::GetBeginningOfToken(
+ start.getLocWithOffset(-1), compiler.getSourceManager(), compiler.getLangOpts());
+ auto const s = StringRef(compiler.getSourceManager().getCharacterData(start1),
+ Lexer::MeasureTokenLength(start1, compiler.getSourceManager(),
+ compiler.getLangOpts()));
+ if (!(s.empty() || s.startswith("\\\n")))
+ {
+ break;
+ }
+ start = start1;
+ }
+ return SourceRange(start, compiler.getSourceManager().getSpellingLoc(expr->getEndLoc()));
+ }
+
+ //TODO: There are some more places where an expression is contextually converted to bool, but
+ // those are probably not relevant for our needs here.
+ std::deque<Expr const*> contextuallyConvertedExprs_;
+};
+
+bool SimplifyPointerToBool::VisitImplicitCastExpr(ImplicitCastExpr const* castExpr)
+{
+ if (ignoreLocation(castExpr))
+ return true;
+ if (castExpr->getCastKind() != CK_PointerToBoolean)
+ return true;
+ auto memberCallExpr
+ = dyn_cast<CXXMemberCallExpr>(castExpr->getSubExpr()->IgnoreParenImpCasts());
+ if (!memberCallExpr)
+ return true;
+ auto methodDecl = memberCallExpr->getMethodDecl();
+ if (!methodDecl || !methodDecl->getIdentifier() || methodDecl->getName() != "get")
+ return true;
+ // castExpr->dump();
+ // methodDecl->getParent()->getTypeForDecl()->dump();
+ if (!loplugin::isSmartPointerType(memberCallExpr->getImplicitObjectArgument()))
+ return true;
+ // if (isa<CXXOperatorCallExpr>(callExpr))
+ // return true;
+ // const FunctionDecl* functionDecl;
+ // if (isa<CXXMemberCallExpr>(callExpr))
+ // {
+ // functionDecl = dyn_cast<CXXMemberCallExpr>(callExpr)->getMethodDecl();
+ // }
+ // else
+ // {
+ // functionDecl = callExpr->getDirectCallee();
+ // }
+ // if (!functionDecl)
+ // return true;
+ //
+ // unsigned len = std::min(callExpr->getNumArgs(), functionDecl->getNumParams());
+ // for (unsigned i = 0; i < len; ++i)
+ // {
+ // auto param = functionDecl->getParamDecl(i);
+ // auto paramTC = loplugin::TypeCheck(param->getType());
+ // if (!paramTC.AnyBoolean())
+ // continue;
+ // auto arg = callExpr->getArg(i)->IgnoreImpCasts();
+ // auto argTC = loplugin::TypeCheck(arg->getType());
+ // if (argTC.AnyBoolean())
+ // continue;
+ // // sal_Bool is sometimes disguised
+ // if (isa<SubstTemplateTypeParmType>(arg->getType()))
+ // if (arg->getType()->getUnqualifiedDesugaredType()->isSpecificBuiltinType(
+ // clang::BuiltinType::UChar))
+ // continue;
+ // if (arg->getType()->isDependentType())
+ // continue;
+ // if (arg->getType()->isIntegerType())
+ // {
+ // auto ret = getCallValue(arg);
+ // if (ret.hasValue() && (ret.getValue() == 1 || ret.getValue() == 0))
+ // continue;
+ // // something like: priv->m_nLOKFeatures & LOK_FEATURE_DOCUMENT_PASSWORD
+ // if (isa<BinaryOperator>(arg->IgnoreParenImpCasts()))
+ // continue;
+ // // something like: pbEmbolden ? FcTrue : FcFalse
+ // if (isa<ConditionalOperator>(arg->IgnoreParenImpCasts()))
+ // continue;
+ // }
+ if (isContextuallyConverted(memberCallExpr))
+ {
+ if (rewriter)
+ {
+ auto const range = getCallSourceRange(memberCallExpr);
+ if (range.isValid() && removeText(range))
+ {
+ return true;
+ }
+ }
+ report(DiagnosticsEngine::Warning, "simplify, drop the get()", memberCallExpr->getExprLoc())
+ << memberCallExpr->getSourceRange();
+ }
+ else if (isa<ParenExpr>(castExpr->getSubExpr()))
+ {
+ if (rewriter)
+ {
+ auto const loc
+ = compiler.getSourceManager().getSpellingLoc(memberCallExpr->getBeginLoc());
+ auto const range = getCallSourceRange(memberCallExpr);
+ if (loc.isValid() && range.isValid() && insertText(loc, "bool") && removeText(range))
+ {
+ //TODO: atomically only change both or neither
+ return true;
+ }
+ }
+ report(DiagnosticsEngine::Warning,
+ "simplify, drop the get() and turn the surrounding parentheses into a functional "
+ "cast to bool",
+ memberCallExpr->getExprLoc())
+ << memberCallExpr->getSourceRange();
+ report(DiagnosticsEngine::Note, "surrounding parentheses here",
+ castExpr->getSubExpr()->getExprLoc())
+ << castExpr->getSubExpr()->getSourceRange();
+ }
+ else
+ {
+ if (rewriter)
+ {
+ auto const loc
+ = compiler.getSourceManager().getSpellingLoc(memberCallExpr->getBeginLoc());
+ auto const range = getCallSourceRange(memberCallExpr);
+ if (loc.isValid() && range.isValid() && insertText(loc, "bool(")
+ && replaceText(range, ")"))
+ {
+ //TODO: atomically only change both or neither
+ return true;
+ }
+ }
+ report(DiagnosticsEngine::Warning,
+ "simplify, drop the get() and wrap the expression in a functional cast to bool",
+ memberCallExpr->getExprLoc())
+ << memberCallExpr->getSourceRange();
+ }
+ // report(DiagnosticsEngine::Note, "method here", param->getLocation())
+ // << param->getSourceRange();
+ return true;
+}
+
+bool SimplifyPointerToBool::VisitBinaryOperator(BinaryOperator const* binOp)
+{
+ if (ignoreLocation(binOp))
+ return true;
+ auto opCode = binOp->getOpcode();
+ if (opCode != BO_EQ && opCode != BO_NE)
+ return true;
+ const Expr* possibleMemberCall = nullptr;
+ if (isa<CXXNullPtrLiteralExpr>(binOp->getLHS()->IgnoreParenImpCasts()))
+ possibleMemberCall = binOp->getRHS();
+ else if (isa<CXXNullPtrLiteralExpr>(binOp->getRHS()->IgnoreParenImpCasts()))
+ possibleMemberCall = binOp->getLHS();
+ else
+ return true;
+ auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(possibleMemberCall);
+ if (!memberCallExpr)
+ return true;
+ auto methodDecl = memberCallExpr->getMethodDecl();
+ if (!methodDecl || !methodDecl->getIdentifier() || methodDecl->getName() != "get")
+ return true;
+ if (!loplugin::isSmartPointerType(memberCallExpr->getImplicitObjectArgument()))
+ return true;
+ report(DiagnosticsEngine::Warning,
+ std::string("simplify, convert to ") + (opCode == BO_EQ ? "'!x'" : "'x'"),
+ binOp->getExprLoc())
+ << binOp->getSourceRange();
+ return true;
+}
+
+loplugin::Plugin::Registration<SimplifyPointerToBool> simplifypointertobool("simplifypointertobool",
+ true);
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */