/* -*- 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 #include #include #include #include #include #include #include #include "plugin.hxx" #include "check.hxx" #include "compat.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 { 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() || compat::starts_with(s, "\\\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() || compat::starts_with(s, "\\\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 contextuallyConvertedExprs_; }; bool SimplifyPointerToBool::VisitImplicitCastExpr(ImplicitCastExpr const* castExpr) { if (ignoreLocation(castExpr)) return true; if (castExpr->getCastKind() != CK_PointerToBoolean) return true; auto memberCallExpr = dyn_cast(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(callExpr)) // return true; // const FunctionDecl* functionDecl; // if (isa(callExpr)) // { // functionDecl = dyn_cast(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(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(arg->IgnoreParenImpCasts())) // continue; // // something like: pbEmbolden ? FcTrue : FcFalse // if (isa(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(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(binOp->getLHS()->IgnoreParenImpCasts())) possibleMemberCall = binOp->getRHS(); else if (isa(binOp->getRHS()->IgnoreParenImpCasts())) possibleMemberCall = binOp->getLHS(); else return true; auto memberCallExpr = dyn_cast(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", true); } // namespace /* vim:set shiftwidth=4 softtabstop=4 expandtab: */