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_set>
18 #include "clang/AST/CXXInheritance.h"
19 #include "clang/AST/StmtVisitor.h"
22 Look for *StringBuffer append sequences which can be converted to *String + sequences.
27 class BufferAdd
: public loplugin::FilteringPlugin
<BufferAdd
>
30 explicit BufferAdd(loplugin::InstantiationData
const& data
)
31 : FilteringPlugin(data
)
35 bool preRun() override
37 std::string
fn(handler
.getMainFileName());
38 loplugin::normalizeDotDotInFilePath(fn
);
39 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/rtl/oustring/"))
41 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/rtl/oustringbuffer/"))
43 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/rtl/strings/"))
45 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/OStringBuffer/"))
48 if (loplugin::isSamePathname(fn
, SRCDIR
"/unoidl/source/sourcetreeprovider.cxx"))
50 if (loplugin::isSamePathname(fn
, SRCDIR
"/writerfilter/source/dmapper/StyleSheetTable.cxx"))
52 if (loplugin::isSamePathname(fn
, SRCDIR
"/writerfilter/source/dmapper/GraphicImport.cxx"))
54 if (loplugin::isSamePathname(fn
, SRCDIR
"/sdext/source/pdfimport/pdfparse/pdfparse.cxx"))
59 void postRun() override
61 for (auto const& pair
: goodMap
)
62 if (!isa
<ParmVarDecl
>(pair
.first
) &&
63 // reference types have slightly weird behaviour
64 !pair
.first
->getType()->isReferenceType()
65 && badMap
.find(pair
.first
) == badMap
.end())
66 report(DiagnosticsEngine::Warning
,
67 "convert this append sequence into a *String + sequence",
68 compat::getBeginLoc(pair
.first
))
69 << pair
.first
->getSourceRange();
72 virtual void run() override
76 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
80 bool VisitStmt(Stmt
const*);
81 bool VisitCallExpr(CallExpr
const*);
82 bool VisitCXXConstructExpr(CXXConstructExpr
const*);
83 bool VisitUnaryOperator(UnaryOperator
const*);
86 void findBufferAssignOrAdd(const Stmt
* parentStmt
, Stmt
const*);
87 Expr
const* ignore(Expr
const*);
88 bool isSideEffectFree(Expr
const*);
89 bool isMethodOkToMerge(CXXMemberCallExpr
const*);
90 void addToGoodMap(const VarDecl
* varDecl
, const Stmt
* parentStmt
);
92 std::unordered_map
<const VarDecl
*, const Stmt
*> goodMap
;
93 std::unordered_set
<const VarDecl
*> badMap
;
96 bool BufferAdd::VisitStmt(Stmt
const* stmt
)
98 if (ignoreLocation(stmt
))
101 if (!isa
<CompoundStmt
>(stmt
) && !isa
<CXXCatchStmt
>(stmt
) && !isa
<CXXForRangeStmt
>(stmt
)
102 && !isa
<CXXTryStmt
>(stmt
) && !isa
<DoStmt
>(stmt
) && !isa
<ForStmt
>(stmt
) && !isa
<IfStmt
>(stmt
)
103 && !isa
<SwitchStmt
>(stmt
) && !isa
<WhileStmt
>(stmt
))
106 for (auto it
= stmt
->child_begin(); it
!= stmt
->child_end(); ++it
)
108 findBufferAssignOrAdd(stmt
, *it
);
113 bool BufferAdd::VisitCallExpr(CallExpr
const* callExpr
)
115 if (ignoreLocation(callExpr
))
118 for (unsigned i
= 0; i
!= callExpr
->getNumArgs(); ++i
)
120 auto a
= ignore(callExpr
->getArg(i
));
121 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(a
))
122 if (auto varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl()))
123 badMap
.insert(varDecl
);
128 bool BufferAdd::VisitCXXConstructExpr(CXXConstructExpr
const* callExpr
)
130 if (ignoreLocation(callExpr
))
133 for (unsigned i
= 0; i
!= callExpr
->getNumArgs(); ++i
)
135 auto a
= ignore(callExpr
->getArg(i
));
136 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(a
))
137 if (auto varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl()))
138 badMap
.insert(varDecl
);
143 bool BufferAdd::VisitUnaryOperator(const UnaryOperator
* unaryOp
)
145 if (ignoreLocation(unaryOp
))
147 if (unaryOp
->getOpcode() != UO_AddrOf
)
149 auto a
= ignore(unaryOp
->getSubExpr());
150 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(a
))
151 if (auto varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl()))
152 badMap
.insert(varDecl
);
156 void BufferAdd::findBufferAssignOrAdd(const Stmt
* parentStmt
, Stmt
const* stmt
)
158 if (auto exprCleanup
= dyn_cast
<ExprWithCleanups
>(stmt
))
159 stmt
= exprCleanup
->getSubExpr();
160 if (auto switchCase
= dyn_cast
<SwitchCase
>(stmt
))
161 stmt
= switchCase
->getSubStmt();
162 if (auto declStmt
= dyn_cast
<DeclStmt
>(stmt
))
164 if (declStmt
->isSingleDecl())
165 if (auto varDeclLHS
= dyn_cast_or_null
<VarDecl
>(declStmt
->getSingleDecl()))
167 auto tc
= loplugin::TypeCheck(varDeclLHS
->getType());
168 if (!tc
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
169 && !tc
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
171 if (varDeclLHS
->getStorageDuration() == SD_Static
)
173 if (!varDeclLHS
->hasInit())
175 auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(ignore(varDeclLHS
->getInit()));
176 if (cxxConstructExpr
)
178 addToGoodMap(varDeclLHS
, parentStmt
);
181 if (!isSideEffectFree(varDeclLHS
->getInit()))
182 badMap
.insert(varDeclLHS
);
184 addToGoodMap(varDeclLHS
, parentStmt
);
189 // check for single calls to buffer method
191 if (auto memberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(stmt
))
193 if (auto declRefExprLHS
194 = dyn_cast
<DeclRefExpr
>(ignore(memberCallExpr
->getImplicitObjectArgument())))
196 auto methodDecl
= memberCallExpr
->getMethodDecl();
197 if (methodDecl
&& methodDecl
->getIdentifier())
198 if (auto varDeclLHS
= dyn_cast
<VarDecl
>(declRefExprLHS
->getDecl()))
200 auto tc
= loplugin::TypeCheck(varDeclLHS
->getType());
201 if (tc
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
202 || tc
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
204 if (isMethodOkToMerge(memberCallExpr
))
205 addToGoodMap(varDeclLHS
, parentStmt
);
207 badMap
.insert(varDeclLHS
);
214 // now check for chained append calls
216 auto expr
= dyn_cast
<Expr
>(stmt
);
219 auto tc
= loplugin::TypeCheck(expr
->getType());
220 if (!tc
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
221 && !tc
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
224 // unwrap the chain (which runs from right to left)
225 const VarDecl
* varDeclLHS
= nullptr;
229 auto memberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(expr
);
232 good
&= isMethodOkToMerge(memberCallExpr
);
234 if (auto declRefExprLHS
235 = dyn_cast
<DeclRefExpr
>(ignore(memberCallExpr
->getImplicitObjectArgument())))
237 varDeclLHS
= dyn_cast
<VarDecl
>(declRefExprLHS
->getDecl());
240 expr
= memberCallExpr
->getImplicitObjectArgument();
246 addToGoodMap(varDeclLHS
, parentStmt
);
248 badMap
.insert(varDeclLHS
);
252 void BufferAdd::addToGoodMap(const VarDecl
* varDecl
, const Stmt
* parentStmt
)
254 // check that vars are all inside the same compoundstmt, if they are not, we cannot combine them
255 auto it
= goodMap
.find(varDecl
);
256 if (it
!= goodMap
.end())
258 if (it
->second
== parentStmt
)
260 // don't treat these as parents, otherwise we eliminate .append.append sequences
261 if (isa
<MemberExpr
>(parentStmt
))
263 if (isa
<CXXMemberCallExpr
>(parentStmt
))
265 badMap
.insert(varDecl
);
268 goodMap
.emplace(varDecl
, parentStmt
);
271 bool BufferAdd::isMethodOkToMerge(CXXMemberCallExpr
const* memberCall
)
273 auto methodDecl
= memberCall
->getMethodDecl();
274 if (methodDecl
->getNumParams() == 0)
277 if (auto const id
= methodDecl
->getIdentifier())
279 auto name
= id
->getName();
280 if (name
== "appendUninitialized" || name
== "setLength" || name
== "remove"
281 || name
== "insert" || name
== "appendAscii" || name
== "appendUtf32")
285 auto rhs
= memberCall
->getArg(0);
286 if (!isSideEffectFree(rhs
))
291 Expr
const* BufferAdd::ignore(Expr
const* expr
)
293 return compat::IgnoreImplicit(compat::IgnoreImplicit(expr
)->IgnoreParens());
296 bool BufferAdd::isSideEffectFree(Expr
const* expr
)
299 // I don't think the OUStringAppend functionality can handle this efficiently
300 if (isa
<ConditionalOperator
>(expr
))
302 // Multiple statements have a well defined evaluation order (sequence points between them)
303 // but a single expression may be evaluated in arbitrary order;
304 // if there are side effects in one of the sub-expressions that have an effect on another subexpression,
305 // the result may be incorrect, and you don't necessarily notice in tests because the order is compiler-dependent.
306 // for example see commit afd743141f7a7dd05914d0872c9afe079f16fe0c where such a refactoring introduced such a bug.
307 // So only consider simple RHS expressions.
308 if (!expr
->HasSideEffects(compiler
.getASTContext()))
311 // check for chained adds which are side-effect free
312 if (auto operatorCall
= dyn_cast
<CXXOperatorCallExpr
>(expr
))
314 auto op
= operatorCall
->getOperator();
315 if (op
== OO_PlusEqual
|| op
== OO_Plus
)
316 if (isSideEffectFree(operatorCall
->getArg(0))
317 && isSideEffectFree(operatorCall
->getArg(1)))
321 if (auto callExpr
= dyn_cast
<CallExpr
>(expr
))
323 // check for calls through OUString::number/OUString::unacquired
324 if (auto calleeMethodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(callExpr
->getCalleeDecl()))
325 if (calleeMethodDecl
&& calleeMethodDecl
->getIdentifier())
327 if (callExpr
->getNumArgs() > 0)
329 auto tc
= loplugin::TypeCheck(calleeMethodDecl
->getParent());
330 if (tc
.Class("OUString") || tc
.Class("OString"))
332 if (isSideEffectFree(callExpr
->getArg(0)))
337 if (auto calleeFunctionDecl
= dyn_cast_or_null
<FunctionDecl
>(callExpr
->getCalleeDecl()))
338 if (calleeFunctionDecl
&& calleeFunctionDecl
->getIdentifier())
340 auto name
= calleeFunctionDecl
->getName();
341 // check for calls through OUStringToOString
342 if (name
== "OUStringToOString" || name
== "OStringToOUString")
343 if (isSideEffectFree(callExpr
->getArg(0)))
345 // allowlist some known-safe methods
346 if (name
.endswith("ResId") || name
== "GetXMLToken")
347 if (isSideEffectFree(callExpr
->getArg(0)))
352 // sometimes we have a constructor call on the RHS
353 if (auto constructExpr
= dyn_cast
<CXXConstructExpr
>(expr
))
355 auto dc
= loplugin::DeclCheck(constructExpr
->getConstructor());
356 if (dc
.MemberFunction().Class("OUString") || dc
.MemberFunction().Class("OString")
357 || dc
.MemberFunction().Class("OUStringBuffer")
358 || dc
.MemberFunction().Class("OStringBuffer"))
359 if (constructExpr
->getNumArgs() == 0 || isSideEffectFree(constructExpr
->getArg(0)))
361 // Expr::HasSideEffects does not like stuff that passes through OUStringLiteral
362 auto dc2
= loplugin::DeclCheck(constructExpr
->getConstructor()->getParent());
363 if (dc2
.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace())
367 // when adding literals, we sometimes get this
368 if (auto functionalCastExpr
= dyn_cast
<CXXFunctionalCastExpr
>(expr
))
370 auto tc
= loplugin::TypeCheck(functionalCastExpr
->getType());
371 if (tc
.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace())
372 return isSideEffectFree(functionalCastExpr
->getSubExpr());
378 loplugin::Plugin::Registration
<BufferAdd
> bufferadd("bufferadd");
381 #endif // LO_CLANG_SHARED_PLUGINS
383 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */