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>
20 #include "config_clang.h"
21 #include "clang/AST/CXXInheritance.h"
22 #include "clang/AST/StmtVisitor.h"
25 Look for repeated addition to OUString/OString/OUStringBuffer/OStringBuffer.
31 which can be simplified to
34 which is more efficient, because of the OUStringConcat magic.
39 class StringAdd
: public loplugin::FilteringPlugin
<StringAdd
>
42 explicit StringAdd(loplugin::InstantiationData
const& data
)
43 : FilteringPlugin(data
)
47 bool preRun() override
49 std::string
fn(handler
.getMainFileName());
50 loplugin::normalizeDotDotInFilePath(fn
);
51 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/rtl/oustring/"))
53 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/rtl/oustringbuffer/"))
55 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/rtl/strings/"))
57 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/OStringBuffer/"))
59 // there is an ifdef here, but my check is not working, not sure why
60 if (fn
== SRCDIR
"/pyuno/source/module/pyuno_runtime.cxx")
62 // TODO the += depends on the result of the preceding assign, so can't merge
63 if (fn
== SRCDIR
"/editeng/source/misc/svxacorr.cxx")
65 // TODO this file has a boatload of buffer appends' and I don't feel like fixing them all now
66 if (fn
== SRCDIR
"/vcl/source/gdi/pdfwriter_impl.cxx")
71 virtual void run() override
75 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
78 bool VisitCompoundStmt(CompoundStmt
const*);
79 bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr
const*);
80 bool VisitCXXMemberCallExpr(CXXMemberCallExpr
const*);
85 OnlyCompileTimeConstants
,
90 struct VarDeclAndSummands
92 const VarDecl
* varDecl
;
96 VarDeclAndSummands
findAssignOrAdd(Stmt
const*);
97 bool checkForCompoundAssign(Stmt
const* stmt1
, Stmt
const* stmt2
, VarDeclAndSummands
& varDecl
);
99 Expr
const* ignore(Expr
const*);
100 bool isSideEffectFree(Expr
const*);
101 bool isCompileTimeConstant(Expr
const*);
104 bool StringAdd::VisitCompoundStmt(CompoundStmt
const* compoundStmt
)
106 if (ignoreLocation(compoundStmt
))
109 auto it
= compoundStmt
->body_begin();
112 if (it
== compoundStmt
->body_end())
114 VarDeclAndSummands foundVar
= findAssignOrAdd(*it
);
115 // reference types have slightly weird behaviour
116 if (foundVar
.varDecl
&& !foundVar
.varDecl
->getType()->isReferenceType())
120 while (it
!= compoundStmt
->body_end())
122 if (!checkForCompoundAssign(stmt1
, *it
, foundVar
))
137 StringAdd::VarDeclAndSummands
StringAdd::findAssignOrAdd(Stmt
const* stmt
)
139 if (auto exprCleanup
= dyn_cast
<ExprWithCleanups
>(stmt
))
140 stmt
= exprCleanup
->getSubExpr();
141 if (auto switchCase
= dyn_cast
<SwitchCase
>(stmt
))
142 stmt
= switchCase
->getSubStmt();
144 if (auto declStmt
= dyn_cast
<DeclStmt
>(stmt
))
145 if (declStmt
->isSingleDecl())
146 if (auto varDeclLHS
= dyn_cast_or_null
<VarDecl
>(declStmt
->getSingleDecl()))
148 auto tc
= loplugin::TypeCheck(varDeclLHS
->getType());
149 if (!tc
.Class("OUString").Namespace("rtl").GlobalNamespace()
150 && !tc
.Class("OString").Namespace("rtl").GlobalNamespace()
151 && !tc
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
152 && !tc
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
154 if (varDeclLHS
->getStorageDuration() == SD_Static
)
156 if (!varDeclLHS
->hasInit())
158 if (tc
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
159 || tc
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
161 // ignore the constructor that gives the buffer a default size
162 if (auto cxxConstructor
= dyn_cast
<CXXConstructExpr
>(varDeclLHS
->getInit()))
163 if (auto constructorDecl
= cxxConstructor
->getConstructor())
164 if ((constructorDecl
->getNumParams() == 1
165 && loplugin::TypeCheck(constructorDecl
->getParamDecl(0)->getType())
166 .Typedef("sal_Int32")
168 || (constructorDecl
->getNumParams() == 2
169 && constructorDecl
->getParamDecl(0)->getType()->isIntegralType(
170 compiler
.getASTContext())
171 && constructorDecl
->getParamDecl(1)
173 ->isSpecificBuiltinType(BuiltinType::Int
)))
176 return { varDeclLHS
, (isCompileTimeConstant(varDeclLHS
->getInit())
177 ? Summands::OnlyCompileTimeConstants
178 : (isSideEffectFree(varDeclLHS
->getInit())
179 ? Summands::OnlySideEffectFree
180 : Summands::SideEffect
)) };
182 if (auto operatorCall
= dyn_cast
<CXXOperatorCallExpr
>(stmt
))
183 if (operatorCall
->getOperator() == OO_Equal
|| operatorCall
->getOperator() == OO_PlusEqual
)
184 if (auto declRefExprLHS
= dyn_cast
<DeclRefExpr
>(ignore(operatorCall
->getArg(0))))
185 if (auto varDeclLHS
= dyn_cast
<VarDecl
>(declRefExprLHS
->getDecl()))
187 auto tc
= loplugin::TypeCheck(varDeclLHS
->getType());
188 if (!tc
.Class("OUString").Namespace("rtl").GlobalNamespace()
189 && !tc
.Class("OString").Namespace("rtl").GlobalNamespace())
191 auto rhs
= operatorCall
->getArg(1);
193 (isCompileTimeConstant(rhs
)
194 ? Summands::OnlyCompileTimeConstants
195 : (isSideEffectFree(rhs
) ? Summands::OnlySideEffectFree
196 : Summands::SideEffect
)) };
198 if (auto memberCall
= dyn_cast
<CXXMemberCallExpr
>(stmt
))
199 if (auto cxxMethodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(memberCall
->getDirectCallee()))
200 if (cxxMethodDecl
->getIdentifier() && cxxMethodDecl
->getName() == "append")
201 if (auto declRefExprLHS
202 = dyn_cast
<DeclRefExpr
>(ignore(memberCall
->getImplicitObjectArgument())))
203 if (auto varDeclLHS
= dyn_cast
<VarDecl
>(declRefExprLHS
->getDecl()))
205 auto tc
= loplugin::TypeCheck(varDeclLHS
->getType());
206 if (!tc
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
207 && !tc
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
209 auto rhs
= memberCall
->getArg(0);
211 (isCompileTimeConstant(rhs
)
212 ? Summands::OnlyCompileTimeConstants
213 : (isSideEffectFree(rhs
) ? Summands::OnlySideEffectFree
214 : Summands::SideEffect
)) };
219 bool StringAdd::checkForCompoundAssign(Stmt
const* stmt1
, Stmt
const* stmt2
,
220 VarDeclAndSummands
& varDecl
)
222 // OString additions are frequently wrapped in these
223 if (auto exprCleanup
= dyn_cast
<ExprWithCleanups
>(stmt2
))
224 stmt2
= exprCleanup
->getSubExpr();
225 if (auto switchCase
= dyn_cast
<SwitchCase
>(stmt2
))
226 stmt2
= switchCase
->getSubStmt();
228 const DeclRefExpr
* declRefExprLHS
;
230 auto tc
= loplugin::TypeCheck(varDecl
.varDecl
->getType());
231 if (tc
.Class("OString") || tc
.Class("OUString"))
233 auto operatorCall
= dyn_cast
<CXXOperatorCallExpr
>(stmt2
);
236 if (operatorCall
->getOperator() != OO_PlusEqual
)
238 declRefExprLHS
= dyn_cast
<DeclRefExpr
>(ignore(operatorCall
->getArg(0)));
239 rhs
= operatorCall
->getArg(1);
243 // OUStringBuffer, OStringBuffer
244 auto memberCall
= dyn_cast
<CXXMemberCallExpr
>(stmt2
);
247 auto cxxMethodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(memberCall
->getDirectCallee());
250 if (!cxxMethodDecl
->getIdentifier() || cxxMethodDecl
->getName() != "append")
252 declRefExprLHS
= dyn_cast
<DeclRefExpr
>(ignore(memberCall
->getImplicitObjectArgument()));
253 rhs
= memberCall
->getArg(0);
257 if (declRefExprLHS
->getDecl() != varDecl
.varDecl
)
259 // if either side is a compile-time-constant, then we don't care about
261 bool const ctcRhs
= isCompileTimeConstant(rhs
);
264 auto const sefRhs
= isSideEffectFree(rhs
);
265 auto const oldSummands
= varDecl
.summands
;
266 varDecl
.summands
= sefRhs
? Summands::OnlySideEffectFree
: Summands::SideEffect
;
267 if (oldSummands
!= Summands::OnlyCompileTimeConstants
268 && (oldSummands
== Summands::SideEffect
|| !sefRhs
))
273 SourceRange
mergeRange(stmt1
->getSourceRange().getBegin(), stmt2
->getSourceRange().getEnd());
274 // if we cross a #ifdef boundary
275 if (containsPreprocessingConditionalInclusion(mergeRange
))
278 = ctcRhs
? Summands::OnlyCompileTimeConstants
279 : isSideEffectFree(rhs
) ? Summands::OnlySideEffectFree
: Summands::SideEffect
;
282 // If there is a comment between two calls, rather don't suggest merge
283 // IMO, code clarity trumps efficiency (as far as plugin warnings go, anyway).
284 if (containsComment(mergeRange
))
286 // I don't think the OUStringAppend functionality can handle this efficiently
287 if (isa
<ConditionalOperator
>(ignore(rhs
)))
289 report(DiagnosticsEngine::Warning
, "simplify by merging with the preceding assign/append",
290 stmt2
->getBeginLoc())
291 << stmt2
->getSourceRange();
295 // Check for generating temporaries when adding strings
297 bool StringAdd::VisitCXXOperatorCallExpr(CXXOperatorCallExpr
const* operatorCall
)
299 if (ignoreLocation(operatorCall
))
301 if (operatorCall
->getOperator() != OO_Plus
)
303 auto tc
= loplugin::TypeCheck(operatorCall
->getType()->getUnqualifiedDesugaredType());
304 if (!tc
.Struct("StringConcat").Namespace("rtl").GlobalNamespace()
305 && !tc
.Class("OUString").Namespace("rtl").GlobalNamespace()
306 && !tc
.Class("OString").Namespace("rtl").GlobalNamespace())
309 auto check
= [operatorCall
, this](unsigned arg
) {
311 = dyn_cast
<CXXFunctionalCastExpr
>(operatorCall
->getArg(arg
)->IgnoreParenImpCasts());
314 auto tc3
= loplugin::TypeCheck(e
->getType());
315 if (!tc3
.Class("OUString").Namespace("rtl").GlobalNamespace()
316 && !tc3
.Class("OString").Namespace("rtl").GlobalNamespace()
317 && !tc3
.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace()
318 && !tc3
.Class("OStringLiteral").Namespace("rtl").GlobalNamespace()
319 && !tc3
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
320 && !tc3
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
322 report(DiagnosticsEngine::Warning
,
323 ("rather use O[U]String::Concat than constructing %0 from %1 on %select{L|R}2HS of "
324 "+ (where %select{R|L}2HS is of"
327 << e
->getType().getLocalUnqualifiedType() << e
->getSubExprAsWritten()->getType() << arg
328 << operatorCall
->getArg(1 - arg
)->IgnoreImpCasts()->getType() << e
->getSourceRange();
336 bool StringAdd::VisitCXXMemberCallExpr(CXXMemberCallExpr
const* methodCall
)
338 if (ignoreLocation(methodCall
))
341 auto methodDecl
= methodCall
->getMethodDecl();
342 if (!methodDecl
|| !methodDecl
->getIdentifier() || methodDecl
->getName() != "append"
343 || methodCall
->getNumArgs() == 0)
345 auto tc1
= loplugin::TypeCheck(methodCall
->getType());
346 if (!tc1
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
347 && !tc1
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
349 auto arg
= methodCall
->getArg(0);
350 // I don't think the OUStringAppend functionality can handle this efficiently
351 if (isa
<ConditionalOperator
>(ignore(arg
)))
354 auto methodCall2
= dyn_cast
<CXXMemberCallExpr
>(ignore(methodCall
->getImplicitObjectArgument()));
357 auto tc
= loplugin::TypeCheck(methodCall2
->getType());
358 if (!tc
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
359 && !tc
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
361 auto methodDecl2
= methodCall2
->getMethodDecl();
362 if (!methodDecl2
->getIdentifier() || methodDecl2
->getName() != "append"
363 || methodCall2
->getNumArgs() == 0)
365 arg
= methodCall2
->getArg(0);
366 // I don't think the OUStringAppend functionality can handle this efficiently
367 if (isa
<ConditionalOperator
>(ignore(arg
)))
369 report(DiagnosticsEngine::Warning
,
370 "chained append, rather use single append call and + operator",
371 methodCall2
->getBeginLoc())
372 << methodCall2
->getSourceRange();
377 Expr
const* StringAdd::ignore(Expr
const* expr
)
379 return expr
->IgnoreImplicit()->IgnoreParens()->IgnoreImplicit();
382 bool StringAdd::isSideEffectFree(Expr
const* expr
)
385 // I don't think the OUStringAppend functionality can handle this efficiently
386 if (isa
<ConditionalOperator
>(expr
))
388 // Multiple statements have a well defined evaluation order (sequence points between them)
389 // but a single expression may be evaluated in arbitrary order;
390 // if there are side effects in one of the sub-expressions that have an effect on another subexpression,
391 // the result may be incorrect, and you don't necessarily notice in tests because the order is compiler-dependent.
392 // for example see commit afd743141f7a7dd05914d0872c9afe079f16fe0c where such a refactoring introduced such a bug.
393 // So only consider simple RHS expressions.
394 if (!expr
->HasSideEffects(compiler
.getASTContext()))
397 // check for chained adds which are side-effect free
398 if (auto operatorCall
= dyn_cast
<CXXOperatorCallExpr
>(expr
))
400 auto op
= operatorCall
->getOperator();
401 if (op
== OO_PlusEqual
|| op
== OO_Plus
)
402 if (isSideEffectFree(operatorCall
->getArg(0))
403 && isSideEffectFree(operatorCall
->getArg(1)))
407 if (auto callExpr
= dyn_cast
<CallExpr
>(expr
))
409 // check for calls through OUString::number/OUString::unacquired
410 if (auto calleeMethodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(callExpr
->getCalleeDecl()))
412 if (calleeMethodDecl
->getIdentifier())
414 auto name
= calleeMethodDecl
->getName();
415 if (callExpr
->getNumArgs() > 0
416 && (name
== "number" || name
== "unacquired" || name
== "boolean"
419 auto tc
= loplugin::TypeCheck(calleeMethodDecl
->getParent());
420 if (tc
.Class("OUString") || tc
.Class("OString"))
422 if (isSideEffectFree(callExpr
->getArg(0)))
427 else if (auto const d
= dyn_cast
<CXXConversionDecl
>(calleeMethodDecl
))
429 if (loplugin::TypeCheck(d
->getConversionType())
430 .ClassOrStruct("basic_string_view")
433 auto const tc
= loplugin::TypeCheck(calleeMethodDecl
->getParent());
434 if (tc
.Class("OUString").Namespace("rtl").GlobalNamespace()
435 || tc
.Class("OString").Namespace("rtl").GlobalNamespace())
437 if (isSideEffectFree(callExpr
->getCallee()))
442 // Aggressively assume that calls to const member functions are side effect free (if
443 // all of the call's sub-expressions are):
444 if (calleeMethodDecl
->isConst())
447 // Other options besides CXXMemberCallExpr are e.g. CXXOperatorCallExpr which
448 // does not have such a target expression:
449 if (auto const mce
= dyn_cast
<CXXMemberCallExpr
>(callExpr
))
451 if (!isSideEffectFree(mce
->getImplicitObjectArgument()))
458 for (unsigned i
= 0; i
!= callExpr
->getNumArgs(); ++i
)
460 if (!isSideEffectFree(callExpr
->getArg(i
)))
473 if (auto calleeFunctionDecl
= dyn_cast_or_null
<FunctionDecl
>(callExpr
->getCalleeDecl()))
474 if (calleeFunctionDecl
&& calleeFunctionDecl
->getIdentifier())
476 auto name
= calleeFunctionDecl
->getName();
477 // check for calls through OUStringToOString
478 if (name
== "OUStringToOString" || name
== "OStringToOUString")
479 if (isSideEffectFree(callExpr
->getArg(0)))
481 // allowlist some known-safe methods
482 if (compat::ends_with(name
, "ResId") || name
== "GetXMLToken")
483 if (isSideEffectFree(callExpr
->getArg(0)))
488 // sometimes we have a constructor call on the RHS
489 if (auto constructExpr
= dyn_cast
<CXXConstructExpr
>(expr
))
491 auto dc
= loplugin::DeclCheck(constructExpr
->getConstructor());
492 if (dc
.MemberFunction().Class("OUString") || dc
.MemberFunction().Class("OString")
493 || dc
.MemberFunction().Class("OUStringBuffer")
494 || dc
.MemberFunction().Class("OStringBuffer"))
495 if (constructExpr
->getNumArgs() == 0 || isSideEffectFree(constructExpr
->getArg(0)))
497 // Expr::HasSideEffects does not like stuff that passes through OUStringLiteral
498 auto dc2
= loplugin::DeclCheck(constructExpr
->getConstructor()->getParent());
499 if (dc2
.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace()
500 || dc2
.Class("OStringLiteral").Namespace("rtl").GlobalNamespace())
504 // when adding literals, we sometimes get this
505 if (auto functionalCastExpr
= dyn_cast
<CXXFunctionalCastExpr
>(expr
))
507 auto tc
= loplugin::TypeCheck(functionalCastExpr
->getType());
508 if (tc
.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace()
509 || tc
.Class("OStringLiteral").Namespace("rtl").GlobalNamespace())
510 return isSideEffectFree(functionalCastExpr
->getSubExpr());
516 bool StringAdd::isCompileTimeConstant(Expr
const* expr
)
518 expr
= expr
->IgnoreImplicit();
519 if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(expr
))
520 if (cxxConstructExpr
->getNumArgs() > 0)
521 expr
= cxxConstructExpr
->getArg(0);
522 return isa
<clang::StringLiteral
>(expr
);
525 loplugin::Plugin::Registration
<StringAdd
> stringadd("stringadd");
528 #endif // LO_CLANG_SHARED_PLUGINS
530 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */