1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 #ifndef LO_CLANG_SHARED_PLUGINS
14 #include <unordered_map>
15 #include <unordered_set>
19 #include "clang/AST/CXXInheritance.h"
20 #include "clang/AST/StmtVisitor.h"
23 Look for repeated addition to OUString/OString.
29 which can be simplified to
32 which is more efficient, because of the OUStringConcat magic.
37 class StringAdd
: public loplugin::FilteringPlugin
<StringAdd
>
40 explicit StringAdd(loplugin::InstantiationData
const& data
)
41 : FilteringPlugin(data
)
45 bool preRun() override
47 std::string
fn(handler
.getMainFileName());
48 loplugin::normalizeDotDotInFilePath(fn
);
49 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/rtl/oustring/"))
51 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/rtl/oustringbuffer/"))
53 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/rtl/strings/"))
55 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/OStringBuffer/"))
57 // there is an ifdef here, but my check is not working, not sure why
58 if (fn
== SRCDIR
"/pyuno/source/module/pyuno_runtime.cxx")
60 // TODO the += depends on the result of the preceding assign, so can't merge
61 if (fn
== SRCDIR
"/editeng/source/misc/svxacorr.cxx")
66 virtual void run() override
70 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
73 bool VisitCompoundStmt(CompoundStmt
const*);
74 bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr
const*);
79 OnlyCompileTimeConstants
,
84 struct VarDeclAndSummands
86 const VarDecl
* varDecl
;
90 VarDeclAndSummands
findAssignOrAdd(Stmt
const*);
91 bool checkForCompoundAssign(Stmt
const* stmt1
, Stmt
const* stmt2
, VarDeclAndSummands
& varDecl
);
93 Expr
const* ignore(Expr
const*);
94 bool isSideEffectFree(Expr
const*);
95 bool isCompileTimeConstant(Expr
const*);
98 bool StringAdd::VisitCompoundStmt(CompoundStmt
const* compoundStmt
)
100 if (ignoreLocation(compoundStmt
))
103 auto it
= compoundStmt
->body_begin();
106 if (it
== compoundStmt
->body_end())
108 VarDeclAndSummands foundVar
= findAssignOrAdd(*it
);
109 // reference types have slightly weird behaviour
110 if (foundVar
.varDecl
&& !foundVar
.varDecl
->getType()->isReferenceType())
114 while (it
!= compoundStmt
->body_end())
116 if (!checkForCompoundAssign(stmt1
, *it
, foundVar
))
131 StringAdd::VarDeclAndSummands
StringAdd::findAssignOrAdd(Stmt
const* stmt
)
133 if (auto exprCleanup
= dyn_cast
<ExprWithCleanups
>(stmt
))
134 stmt
= exprCleanup
->getSubExpr();
135 if (auto switchCase
= dyn_cast
<SwitchCase
>(stmt
))
136 stmt
= switchCase
->getSubStmt();
138 if (auto declStmt
= dyn_cast
<DeclStmt
>(stmt
))
139 if (declStmt
->isSingleDecl())
140 if (auto varDeclLHS
= dyn_cast_or_null
<VarDecl
>(declStmt
->getSingleDecl()))
142 auto tc
= loplugin::TypeCheck(varDeclLHS
->getType());
143 if (!tc
.Class("OUString").Namespace("rtl").GlobalNamespace()
144 && !tc
.Class("OString").Namespace("rtl").GlobalNamespace())
146 if (varDeclLHS
->getStorageDuration() == SD_Static
)
148 if (!varDeclLHS
->hasInit())
150 return { varDeclLHS
, (isCompileTimeConstant(varDeclLHS
->getInit())
151 ? Summands::OnlyCompileTimeConstants
152 : (isSideEffectFree(varDeclLHS
->getInit())
153 ? Summands::OnlySideEffectFree
154 : Summands::SideEffect
)) };
156 if (auto operatorCall
= dyn_cast
<CXXOperatorCallExpr
>(stmt
))
157 if (operatorCall
->getOperator() == OO_Equal
|| operatorCall
->getOperator() == OO_PlusEqual
)
158 if (auto declRefExprLHS
= dyn_cast
<DeclRefExpr
>(ignore(operatorCall
->getArg(0))))
159 if (auto varDeclLHS
= dyn_cast
<VarDecl
>(declRefExprLHS
->getDecl()))
161 auto tc
= loplugin::TypeCheck(varDeclLHS
->getType());
162 if (!tc
.Class("OUString").Namespace("rtl").GlobalNamespace()
163 && !tc
.Class("OString").Namespace("rtl").GlobalNamespace())
165 auto rhs
= operatorCall
->getArg(1);
167 (isCompileTimeConstant(rhs
)
168 ? Summands::OnlyCompileTimeConstants
169 : (isSideEffectFree(rhs
) ? Summands::OnlySideEffectFree
170 : Summands::SideEffect
)) };
175 bool StringAdd::checkForCompoundAssign(Stmt
const* stmt1
, Stmt
const* stmt2
,
176 VarDeclAndSummands
& varDecl
)
178 // OString additions are frequently wrapped in these
179 if (auto exprCleanup
= dyn_cast
<ExprWithCleanups
>(stmt2
))
180 stmt2
= exprCleanup
->getSubExpr();
181 if (auto switchCase
= dyn_cast
<SwitchCase
>(stmt2
))
182 stmt2
= switchCase
->getSubStmt();
183 auto operatorCall
= dyn_cast
<CXXOperatorCallExpr
>(stmt2
);
186 if (operatorCall
->getOperator() != OO_PlusEqual
)
188 auto declRefExprLHS
= dyn_cast
<DeclRefExpr
>(ignore(operatorCall
->getArg(0)));
191 if (declRefExprLHS
->getDecl() != varDecl
.varDecl
)
193 // if either side is a compile-time-constant, then we don't care about
195 auto rhs
= operatorCall
->getArg(1);
196 auto const ctcRhs
= isCompileTimeConstant(rhs
);
199 auto const sefRhs
= isSideEffectFree(rhs
);
200 auto const oldSummands
= varDecl
.summands
;
201 varDecl
.summands
= sefRhs
? Summands::OnlySideEffectFree
: Summands::SideEffect
;
202 if (oldSummands
!= Summands::OnlyCompileTimeConstants
203 && (oldSummands
== Summands::SideEffect
|| !sefRhs
))
208 // if we cross a #ifdef boundary
209 if (containsPreprocessingConditionalInclusion(
210 SourceRange(stmt1
->getSourceRange().getBegin(), stmt2
->getSourceRange().getEnd())))
213 = ctcRhs
? Summands::OnlyCompileTimeConstants
214 : isSideEffectFree(rhs
) ? Summands::OnlySideEffectFree
: Summands::SideEffect
;
217 report(DiagnosticsEngine::Warning
, "simplify by merging with the preceding assignment",
218 compat::getBeginLoc(stmt2
))
219 << stmt2
->getSourceRange();
223 // Check for generating temporaries when adding strings
225 bool StringAdd::VisitCXXOperatorCallExpr(CXXOperatorCallExpr
const* operatorCall
)
227 if (ignoreLocation(operatorCall
))
229 if (operatorCall
->getOperator() != OO_Plus
)
231 auto tc
= loplugin::TypeCheck(operatorCall
->getType()->getUnqualifiedDesugaredType());
232 if (!tc
.Struct("OUStringConcat").Namespace("rtl").GlobalNamespace()
233 && !tc
.Struct("OStringConcat").Namespace("rtl").GlobalNamespace()
234 && !tc
.Class("OUString").Namespace("rtl").GlobalNamespace()
235 && !tc
.Class("OString").Namespace("rtl").GlobalNamespace())
238 auto check
= [operatorCall
, this](unsigned arg
) {
240 = dyn_cast
<CXXFunctionalCastExpr
>(operatorCall
->getArg(arg
)->IgnoreParenImpCasts());
243 auto tc3
= loplugin::TypeCheck(e
->getType());
244 if (!tc3
.Class("OUString").Namespace("rtl").GlobalNamespace()
245 && !tc3
.Class("OString").Namespace("rtl").GlobalNamespace())
247 report(DiagnosticsEngine::Warning
,
248 ("avoid constructing %0 from %1 on %select{L|R}2HS of + (where %select{R|L}2HS is of"
250 compat::getBeginLoc(e
))
251 << e
->getType().getLocalUnqualifiedType() << e
->getSubExprAsWritten()->getType() << arg
252 << operatorCall
->getArg(1 - arg
)->IgnoreImpCasts()->getType() << e
->getSourceRange();
260 Expr
const* StringAdd::ignore(Expr
const* expr
)
262 return compat::IgnoreImplicit(compat::IgnoreImplicit(expr
)->IgnoreParens());
265 bool StringAdd::isSideEffectFree(Expr
const* expr
)
268 // I don't think the OUStringAppend functionality can handle this efficiently
269 if (isa
<ConditionalOperator
>(expr
))
271 // Multiple statements have a well defined evaluation order (sequence points between them)
272 // but a single expression may be evaluated in arbitrary order;
273 // if there are side effects in one of the sub-expressions that have an effect on another subexpression,
274 // the result may be incorrect, and you don't necessarily notice in tests because the order is compiler-dependent.
275 // for example see commit afd743141f7a7dd05914d0872c9afe079f16fe0c where such a refactoring introduced such a bug.
276 // So only consider simple RHS expressions.
277 if (!expr
->HasSideEffects(compiler
.getASTContext()))
280 // check for chained adds which are side-effect free
281 if (auto operatorCall
= dyn_cast
<CXXOperatorCallExpr
>(expr
))
283 auto op
= operatorCall
->getOperator();
284 if (op
== OO_PlusEqual
|| op
== OO_Plus
)
285 if (isSideEffectFree(operatorCall
->getArg(0))
286 && isSideEffectFree(operatorCall
->getArg(1)))
290 if (auto callExpr
= dyn_cast
<CallExpr
>(expr
))
292 // check for calls through OUString::number/OUString::unacquired
293 if (auto calleeMethodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(callExpr
->getCalleeDecl()))
294 if (calleeMethodDecl
&& calleeMethodDecl
->getIdentifier())
296 auto name
= calleeMethodDecl
->getName();
297 if (callExpr
->getNumArgs() > 0
298 && (name
== "number" || name
== "unacquired" || name
== "boolean"
301 auto tc
= loplugin::TypeCheck(calleeMethodDecl
->getParent());
302 if (tc
.Class("OUString") || tc
.Class("OString"))
304 if (isSideEffectFree(callExpr
->getArg(0)))
309 if (auto calleeFunctionDecl
= dyn_cast_or_null
<FunctionDecl
>(callExpr
->getCalleeDecl()))
310 if (calleeFunctionDecl
&& calleeFunctionDecl
->getIdentifier())
312 auto name
= calleeFunctionDecl
->getName();
313 // check for calls through OUStringToOString
314 if (name
== "OUStringToOString" || name
== "OStringToOUString")
315 if (isSideEffectFree(callExpr
->getArg(0)))
317 // whitelist some known-safe methods
318 if (name
.endswith("ResId") || name
== "GetXMLToken")
319 if (isSideEffectFree(callExpr
->getArg(0)))
324 // sometimes we have a constructor call on the RHS
325 if (auto constructExpr
= dyn_cast
<CXXConstructExpr
>(expr
))
327 auto dc
= loplugin::DeclCheck(constructExpr
->getConstructor());
328 if (dc
.MemberFunction().Class("OUString") || dc
.MemberFunction().Class("OString"))
329 if (constructExpr
->getNumArgs() == 0 || isSideEffectFree(constructExpr
->getArg(0)))
331 // Expr::HasSideEffects does not like stuff that passes through OUStringLiteral
332 auto dc2
= loplugin::DeclCheck(constructExpr
->getConstructor()->getParent());
333 if (dc2
.Struct("OUStringLiteral").Namespace("rtl").GlobalNamespace())
337 // when adding literals, we sometimes get this
338 if (auto functionalCastExpr
= dyn_cast
<CXXFunctionalCastExpr
>(expr
))
340 auto tc
= loplugin::TypeCheck(functionalCastExpr
->getType());
341 if (tc
.Struct("OUStringLiteral").Namespace("rtl").GlobalNamespace())
342 return isSideEffectFree(functionalCastExpr
->getSubExpr());
348 bool StringAdd::isCompileTimeConstant(Expr
const* expr
)
350 expr
= compat::IgnoreImplicit(expr
);
351 if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(expr
))
352 if (cxxConstructExpr
->getNumArgs() > 0)
353 expr
= cxxConstructExpr
->getArg(0);
354 return isa
<clang::StringLiteral
>(expr
);
357 loplugin::Plugin::Registration
<StringAdd
> stringadd("stringadd");
360 #endif // LO_CLANG_SHARED_PLUGINS
362 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */