| /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| /* |
| * This file is part of the Collabora Office 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.starts_with("\\\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.starts_with("\\\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_nKitFeatures & KIT_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: */ |