/* -*- 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/.
*/ #ifndef LO_CLANG_SHARED_PLUGINS
/** Look for repeated addition to OUString/OString/OUStringBuffer/OStringBuffer.
Eg. OUString x = "xxx"; x += b;
which can be simplified to x = "xxx" + b
which is more efficient, because of the OUStringConcat magic.
*/
namespace
{ class StringAdd : public loplugin::FilteringPlugin<StringAdd>
{ public: explicit StringAdd(loplugin::InstantiationData const& data)
: FilteringPlugin(data)
{
}
bool preRun() override
{
std::string fn(handler.getMainFileName());
loplugin::normalizeDotDotInFilePath(fn); if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/oustring/")) returnfalse; if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/oustringbuffer/")) returnfalse; if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/strings/")) returnfalse; if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/OStringBuffer/")) returnfalse; // there is an ifdef here, but my check is not working, not sure why if (fn == SRCDIR "/pyuno/source/module/pyuno_runtime.cxx") returnfalse; // TODO the += depends on the result of the preceding assign, so can't merge if (fn == SRCDIR "/editeng/source/misc/svxacorr.cxx") returnfalse; // TODO this file has a boatload of buffer appends' and I don't feel like fixing them all now if (fn == SRCDIR "/vcl/source/gdi/pdfwriter_impl.cxx") returnfalse; returntrue;
}
virtualvoid run() override
{ if (!preRun()) return;
TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
}
if (auto declStmt = dyn_cast<DeclStmt>(stmt)) if (declStmt->isSingleDecl()) if (auto varDeclLHS = dyn_cast_or_null<VarDecl>(declStmt->getSingleDecl()))
{ auto tc = loplugin::TypeCheck(varDeclLHS->getType()); if (!tc.Class("OUString").Namespace("rtl").GlobalNamespace()
&& !tc.Class("OString").Namespace("rtl").GlobalNamespace()
&& !tc.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
&& !tc.Class("OStringBuffer").Namespace("rtl").GlobalNamespace()) return {}; if (varDeclLHS->getStorageDuration() == SD_Static) return {}; if (!varDeclLHS->hasInit()) return {}; if (tc.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
|| tc.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
{ // ignore the constructor that gives the buffer a default size if (auto cxxConstructor = dyn_cast<CXXConstructExpr>(varDeclLHS->getInit())) if (auto constructorDecl = cxxConstructor->getConstructor()) if ((constructorDecl->getNumParams() == 1
&& loplugin::TypeCheck(constructorDecl->getParamDecl(0)->getType())
.Typedef("sal_Int32")
.GlobalNamespace())
|| (constructorDecl->getNumParams() == 2
&& constructorDecl->getParamDecl(0)->getType()->isIntegralType(
compiler.getASTContext())
&& constructorDecl->getParamDecl(1)
->getType()
->isSpecificBuiltinType(BuiltinType::Int))) return {};
} return { varDeclLHS, (isCompileTimeConstant(varDeclLHS->getInit())
? Summands::OnlyCompileTimeConstants
: (isSideEffectFree(varDeclLHS->getInit())
? Summands::OnlySideEffectFree
: Summands::SideEffect)) };
} if (auto operatorCall = dyn_cast<CXXOperatorCallExpr>(stmt)) if (operatorCall->getOperator() == OO_Equal || operatorCall->getOperator() == OO_PlusEqual) if (auto declRefExprLHS = dyn_cast<DeclRefExpr>(ignore(operatorCall->getArg(0)))) if (auto varDeclLHS = dyn_cast<VarDecl>(declRefExprLHS->getDecl()))
{ auto tc = loplugin::TypeCheck(varDeclLHS->getType()); if (!tc.Class("OUString").Namespace("rtl").GlobalNamespace()
&& !tc.Class("OString").Namespace("rtl").GlobalNamespace()) return {}; auto rhs = operatorCall->getArg(1); return { varDeclLHS,
(isCompileTimeConstant(rhs)
? Summands::OnlyCompileTimeConstants
: (isSideEffectFree(rhs) ? Summands::OnlySideEffectFree
: Summands::SideEffect)) };
} if (auto memberCall = dyn_cast<CXXMemberCallExpr>(stmt)) if (auto cxxMethodDecl = dyn_cast_or_null<CXXMethodDecl>(memberCall->getDirectCallee())) if (cxxMethodDecl->getIdentifier() && cxxMethodDecl->getName() == "append") if (auto declRefExprLHS
= dyn_cast<DeclRefExpr>(ignore(memberCall->getImplicitObjectArgument()))) if (auto varDeclLHS = dyn_cast<VarDecl>(declRefExprLHS->getDecl()))
{ auto tc = loplugin::TypeCheck(varDeclLHS->getType()); if (!tc.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
&& !tc.Class("OStringBuffer").Namespace("rtl").GlobalNamespace()) return {}; auto rhs = memberCall->getArg(0); return { varDeclLHS,
(isCompileTimeConstant(rhs)
? Summands::OnlyCompileTimeConstants
: (isSideEffectFree(rhs) ? Summands::OnlySideEffectFree
: Summands::SideEffect)) };
} return {};
}
bool StringAdd::checkForCompoundAssign(Stmt const* stmt1, Stmt const* stmt2,
VarDeclAndSummands& varDecl)
{ // OString additions are frequently wrapped in these if (auto exprCleanup = dyn_cast<ExprWithCleanups>(stmt2))
stmt2 = exprCleanup->getSubExpr(); if (auto switchCase = dyn_cast<SwitchCase>(stmt2))
stmt2 = switchCase->getSubStmt();
const DeclRefExpr* declRefExprLHS; const Expr* rhs; auto tc = loplugin::TypeCheck(varDecl.varDecl->getType()); if (tc.Class("OString") || tc.Class("OUString"))
{ auto operatorCall = dyn_cast<CXXOperatorCallExpr>(stmt2); if (!operatorCall) returnfalse; if (operatorCall->getOperator() != OO_PlusEqual) returnfalse;
declRefExprLHS = dyn_cast<DeclRefExpr>(ignore(operatorCall->getArg(0)));
rhs = operatorCall->getArg(1);
} else
{ // OUStringBuffer, OStringBuffer auto memberCall = dyn_cast<CXXMemberCallExpr>(stmt2); if (!memberCall) returnfalse; auto cxxMethodDecl = dyn_cast_or_null<CXXMethodDecl>(memberCall->getDirectCallee()); if (!cxxMethodDecl) returnfalse; if (!cxxMethodDecl->getIdentifier() || cxxMethodDecl->getName() != "append") returnfalse;
declRefExprLHS = dyn_cast<DeclRefExpr>(ignore(memberCall->getImplicitObjectArgument()));
rhs = memberCall->getArg(0);
} if (!declRefExprLHS) returnfalse; if (declRefExprLHS->getDecl() != varDecl.varDecl) returnfalse; // if either side is a compile-time-constant, then we don't care about // side-effects boolconst ctcRhs = isCompileTimeConstant(rhs); if (!ctcRhs)
{ autoconst sefRhs = isSideEffectFree(rhs); autoconst oldSummands = varDecl.summands;
varDecl.summands = sefRhs ? Summands::OnlySideEffectFree : Summands::SideEffect; if (oldSummands != Summands::OnlyCompileTimeConstants
&& (oldSummands == Summands::SideEffect || !sefRhs))
{ returntrue;
}
}
SourceRange mergeRange(stmt1->getSourceRange().getBegin(), stmt2->getSourceRange().getEnd()); // if we cross a #ifdef boundary if (containsPreprocessingConditionalInclusion(mergeRange))
{
varDecl.summands
= ctcRhs ? Summands::OnlyCompileTimeConstants
: isSideEffectFree(rhs) ? Summands::OnlySideEffectFree : Summands::SideEffect; returntrue;
} // If there is a comment between two calls, rather don't suggest merge // IMO, code clarity trumps efficiency (as far as plugin warnings go, anyway). if (containsComment(mergeRange)) returntrue; // I don't think the OUStringAppend functionality can handle this efficiently if (isa<ConditionalOperator>(ignore(rhs))) returnfalse;
report(DiagnosticsEngine::Warning, "simplify by merging with the preceding assign/append",
stmt2->getBeginLoc())
<< stmt2->getSourceRange(); returntrue;
}
// Check for generating temporaries when adding strings // bool StringAdd::VisitCXXOperatorCallExpr(CXXOperatorCallExpr const* operatorCall)
{ if (ignoreLocation(operatorCall)) returntrue; if (operatorCall->getOperator() != OO_Plus) returntrue; auto tc = loplugin::TypeCheck(operatorCall->getType()->getUnqualifiedDesugaredType()); if (!tc.Struct("StringConcat").Namespace("rtl").GlobalNamespace()
&& !tc.Class("OUString").Namespace("rtl").GlobalNamespace()
&& !tc.Class("OString").Namespace("rtl").GlobalNamespace()) returntrue;
auto check = [operatorCall, this](unsigned arg) { autoconst e
= dyn_cast<CXXFunctionalCastExpr>(operatorCall->getArg(arg)->IgnoreParenImpCasts()); if (e == nullptr) return; auto tc3 = loplugin::TypeCheck(e->getType()); if (!tc3.Class("OUString").Namespace("rtl").GlobalNamespace()
&& !tc3.Class("OString").Namespace("rtl").GlobalNamespace()
&& !tc3.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace()
&& !tc3.Class("OStringLiteral").Namespace("rtl").GlobalNamespace()
&& !tc3.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
&& !tc3.Class("OStringBuffer").Namespace("rtl").GlobalNamespace()) return;
report(DiagnosticsEngine::Warning,
("rather use O[U]String::Concat than constructing %0 from %1 on %select{L|R}2HS of " "+ (where %select{R|L}2HS is of" " type %3)"),
e->getBeginLoc())
<< e->getType().getLocalUnqualifiedType() << e->getSubExprAsWritten()->getType() << arg
<< operatorCall->getArg(1 - arg)->IgnoreImpCasts()->getType() << e->getSourceRange();
};
check(0);
check(1); returntrue;
}
bool StringAdd::VisitCXXMemberCallExpr(CXXMemberCallExpr const* methodCall)
{ if (ignoreLocation(methodCall)) returntrue;
auto methodDecl = methodCall->getMethodDecl(); if (!methodDecl || !methodDecl->getIdentifier() || methodDecl->getName() != "append"
|| methodCall->getNumArgs() == 0) returntrue; auto tc1 = loplugin::TypeCheck(methodCall->getType()); if (!tc1.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
&& !tc1.Class("OStringBuffer").Namespace("rtl").GlobalNamespace()) returntrue; auto arg = methodCall->getArg(0); // I don't think the OUStringAppend functionality can handle this efficiently if (isa<ConditionalOperator>(ignore(arg))) returntrue;
auto methodCall2 = dyn_cast<CXXMemberCallExpr>(ignore(methodCall->getImplicitObjectArgument())); if (!methodCall2) returntrue; auto tc = loplugin::TypeCheck(methodCall2->getType()); if (!tc.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
&& !tc.Class("OStringBuffer").Namespace("rtl").GlobalNamespace()) returntrue; auto methodDecl2 = methodCall2->getMethodDecl(); if (!methodDecl2->getIdentifier() || methodDecl2->getName() != "append"
|| methodCall2->getNumArgs() == 0) returntrue;
arg = methodCall2->getArg(0); // I don't think the OUStringAppend functionality can handle this efficiently if (isa<ConditionalOperator>(ignore(arg))) returntrue;
report(DiagnosticsEngine::Warning, "chained append, rather use single append call and + operator",
methodCall2->getBeginLoc())
<< methodCall2->getSourceRange();
bool StringAdd::isSideEffectFree(Expr const* expr)
{
expr = ignore(expr); // I don't think the OUStringAppend functionality can handle this efficiently if (isa<ConditionalOperator>(expr)) returnfalse; // Multiple statements have a well defined evaluation order (sequence points between them) // but a single expression may be evaluated in arbitrary order; // if there are side effects in one of the sub-expressions that have an effect on another subexpression, // the result may be incorrect, and you don't necessarily notice in tests because the order is compiler-dependent. // for example see commit afd743141f7a7dd05914d0872c9afe079f16fe0c where such a refactoring introduced such a bug. // So only consider simple RHS expressions. if (!expr->HasSideEffects(compiler.getASTContext())) returntrue;
// check for chained adds which are side-effect free if (auto operatorCall = dyn_cast<CXXOperatorCallExpr>(expr))
{ auto op = operatorCall->getOperator(); if (op == OO_PlusEqual || op == OO_Plus) if (isSideEffectFree(operatorCall->getArg(0))
&& isSideEffectFree(operatorCall->getArg(1))) returntrue;
}
if (auto callExpr = dyn_cast<CallExpr>(expr))
{ // check for calls through OUString::number/OUString::unacquired if (auto calleeMethodDecl = dyn_cast_or_null<CXXMethodDecl>(callExpr->getCalleeDecl()))
{ if (calleeMethodDecl->getIdentifier())
{ auto name = calleeMethodDecl->getName(); if (callExpr->getNumArgs() > 0
&& (name == "number" || name == "unacquired" || name == "boolean"
|| name == "copy"))
{ auto tc = loplugin::TypeCheck(calleeMethodDecl->getParent()); if (tc.Class("OUString") || tc.Class("OString"))
{ if (isSideEffectFree(callExpr->getArg(0))) returntrue;
}
}
} elseif (autoconst d = dyn_cast<CXXConversionDecl>(calleeMethodDecl))
{ if (loplugin::TypeCheck(d->getConversionType())
.ClassOrStruct("basic_string_view")
.StdNamespace())
{ autoconst tc = loplugin::TypeCheck(calleeMethodDecl->getParent()); if (tc.Class("OUString").Namespace("rtl").GlobalNamespace()
|| tc.Class("OString").Namespace("rtl").GlobalNamespace())
{ if (isSideEffectFree(callExpr->getCallee())) returntrue;
}
}
} // Aggressively assume that calls to const member functions are side effect free (if // all of the call's sub-expressions are): if (calleeMethodDecl->isConst())
{ auto sef = true; // Other options besides CXXMemberCallExpr are e.g. CXXOperatorCallExpr which // does not have such a target expression: if (autoconst mce = dyn_cast<CXXMemberCallExpr>(callExpr))
{ if (!isSideEffectFree(mce->getImplicitObjectArgument()))
{
sef = false;
}
} if (sef)
{ for (unsigned i = 0; i != callExpr->getNumArgs(); ++i)
{ if (!isSideEffectFree(callExpr->getArg(i)))
{
sef = false; break;
}
}
} if (sef)
{ returntrue;
}
}
} if (auto calleeFunctionDecl = dyn_cast_or_null<FunctionDecl>(callExpr->getCalleeDecl())) if (calleeFunctionDecl && calleeFunctionDecl->getIdentifier())
{ auto name = calleeFunctionDecl->getName(); // check for calls through OUStringToOString if (name == "OUStringToOString" || name == "OStringToOUString") if (isSideEffectFree(callExpr->getArg(0))) returntrue; // allowlist some known-safe methods if (compat::ends_with(name, "ResId") || name == "GetXMLToken") if (isSideEffectFree(callExpr->getArg(0))) returntrue;
}
}
// sometimes we have a constructor call on the RHS if (auto constructExpr = dyn_cast<CXXConstructExpr>(expr))
{ auto dc = loplugin::DeclCheck(constructExpr->getConstructor()); if (dc.MemberFunction().Class("OUString") || dc.MemberFunction().Class("OString")
|| dc.MemberFunction().Class("OUStringBuffer")
|| dc.MemberFunction().Class("OStringBuffer")) if (constructExpr->getNumArgs() == 0 || isSideEffectFree(constructExpr->getArg(0))) returntrue; // Expr::HasSideEffects does not like stuff that passes through OUStringLiteral auto dc2 = loplugin::DeclCheck(constructExpr->getConstructor()->getParent()); if (dc2.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace()
|| dc2.Class("OStringLiteral").Namespace("rtl").GlobalNamespace()) returntrue;
}
// when adding literals, we sometimes get this if (auto functionalCastExpr = dyn_cast<CXXFunctionalCastExpr>(expr))
{ auto tc = loplugin::TypeCheck(functionalCastExpr->getType()); if (tc.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace()
|| tc.Class("OStringLiteral").Namespace("rtl").GlobalNamespace()) return isSideEffectFree(functionalCastExpr->getSubExpr());
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.