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 "config_clang.h"
19 #include "clang/AST/CXXInheritance.h"
20 #include "clang/AST/StmtVisitor.h"
23 Look for *StringBuffer append sequences which can be converted to *String + sequences.
28 class BufferAdd
: public loplugin::FilteringPlugin
<BufferAdd
>
31 explicit BufferAdd(loplugin::InstantiationData
const& data
)
32 : FilteringPlugin(data
)
36 bool preRun() override
38 std::string
fn(handler
.getMainFileName());
39 loplugin::normalizeDotDotInFilePath(fn
);
40 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/rtl/oustring/"))
42 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/rtl/oustringbuffer/"))
44 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/rtl/strings/"))
46 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/OStringBuffer/"))
49 if (loplugin::isSamePathname(fn
, SRCDIR
"/unoidl/source/sourcetreeprovider.cxx"))
51 if (loplugin::isSamePathname(fn
, SRCDIR
"/writerfilter/source/dmapper/StyleSheetTable.cxx"))
53 if (loplugin::isSamePathname(fn
, SRCDIR
"/writerfilter/source/dmapper/GraphicImport.cxx"))
55 if (loplugin::isSamePathname(fn
, SRCDIR
"/sdext/source/pdfimport/pdfparse/pdfparse.cxx"))
60 void postRun() override
62 for (auto const& pair
: goodMap
)
63 if (!isa
<ParmVarDecl
>(pair
.first
) &&
64 // reference types have slightly weird behaviour
65 !pair
.first
->getType()->isReferenceType()
66 && badMap
.find(pair
.first
) == badMap
.end())
67 report(DiagnosticsEngine::Warning
,
68 "convert this append sequence into a *String + sequence",
69 pair
.first
->getBeginLoc())
70 << pair
.first
->getSourceRange();
73 virtual void run() override
77 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
81 bool VisitStmt(Stmt
const*);
82 bool VisitCallExpr(CallExpr
const*);
83 bool VisitCXXConstructExpr(CXXConstructExpr
const*);
84 bool VisitUnaryOperator(UnaryOperator
const*);
87 void findBufferAssignOrAdd(const Stmt
* parentStmt
, Stmt
const*);
88 Expr
const* ignore(Expr
const*);
89 bool isSideEffectFree(Expr
const*);
90 bool isMethodOkToMerge(CXXMemberCallExpr
const*);
91 void addToGoodMap(const VarDecl
* varDecl
, const Stmt
* parentStmt
);
93 std::unordered_map
<const VarDecl
*, const Stmt
*> goodMap
;
94 std::unordered_set
<const VarDecl
*> badMap
;
97 bool BufferAdd::VisitStmt(Stmt
const* stmt
)
99 if (ignoreLocation(stmt
))
102 if (!isa
<CompoundStmt
>(stmt
) && !isa
<CXXCatchStmt
>(stmt
) && !isa
<CXXForRangeStmt
>(stmt
)
103 && !isa
<CXXTryStmt
>(stmt
) && !isa
<DoStmt
>(stmt
) && !isa
<ForStmt
>(stmt
) && !isa
<IfStmt
>(stmt
)
104 && !isa
<SwitchStmt
>(stmt
) && !isa
<WhileStmt
>(stmt
))
107 for (auto it
= stmt
->child_begin(); it
!= stmt
->child_end(); ++it
)
109 findBufferAssignOrAdd(stmt
, *it
);
114 bool BufferAdd::VisitCallExpr(CallExpr
const* callExpr
)
116 if (ignoreLocation(callExpr
))
119 for (unsigned i
= 0; i
!= callExpr
->getNumArgs(); ++i
)
121 auto a
= ignore(callExpr
->getArg(i
));
122 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(a
))
123 if (auto varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl()))
124 badMap
.insert(varDecl
);
129 bool BufferAdd::VisitCXXConstructExpr(CXXConstructExpr
const* callExpr
)
131 if (ignoreLocation(callExpr
))
134 for (unsigned i
= 0; i
!= callExpr
->getNumArgs(); ++i
)
136 auto a
= ignore(callExpr
->getArg(i
));
137 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(a
))
138 if (auto varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl()))
139 badMap
.insert(varDecl
);
144 bool BufferAdd::VisitUnaryOperator(const UnaryOperator
* unaryOp
)
146 if (ignoreLocation(unaryOp
))
148 if (unaryOp
->getOpcode() != UO_AddrOf
)
150 auto a
= ignore(unaryOp
->getSubExpr());
151 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(a
))
152 if (auto varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl()))
153 badMap
.insert(varDecl
);
157 void BufferAdd::findBufferAssignOrAdd(const Stmt
* parentStmt
, Stmt
const* stmt
)
159 if (auto exprCleanup
= dyn_cast
<ExprWithCleanups
>(stmt
))
160 stmt
= exprCleanup
->getSubExpr();
161 if (auto switchCase
= dyn_cast
<SwitchCase
>(stmt
))
162 stmt
= switchCase
->getSubStmt();
163 if (auto declStmt
= dyn_cast
<DeclStmt
>(stmt
))
165 if (declStmt
->isSingleDecl())
166 if (auto varDeclLHS
= dyn_cast_or_null
<VarDecl
>(declStmt
->getSingleDecl()))
168 auto tc
= loplugin::TypeCheck(varDeclLHS
->getType());
169 if (!tc
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
170 && !tc
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
172 if (varDeclLHS
->getStorageDuration() == SD_Static
)
174 if (!varDeclLHS
->hasInit())
176 auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(ignore(varDeclLHS
->getInit()));
177 if (cxxConstructExpr
)
179 addToGoodMap(varDeclLHS
, parentStmt
);
182 if (!isSideEffectFree(varDeclLHS
->getInit()))
183 badMap
.insert(varDeclLHS
);
185 addToGoodMap(varDeclLHS
, parentStmt
);
190 // check for single calls to buffer method
192 if (auto memberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(stmt
))
194 if (auto declRefExprLHS
195 = dyn_cast
<DeclRefExpr
>(ignore(memberCallExpr
->getImplicitObjectArgument())))
197 auto methodDecl
= memberCallExpr
->getMethodDecl();
198 if (methodDecl
&& methodDecl
->getIdentifier())
199 if (auto varDeclLHS
= dyn_cast
<VarDecl
>(declRefExprLHS
->getDecl()))
201 auto tc
= loplugin::TypeCheck(varDeclLHS
->getType());
202 if (tc
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
203 || tc
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
205 if (isMethodOkToMerge(memberCallExpr
))
206 addToGoodMap(varDeclLHS
, parentStmt
);
208 badMap
.insert(varDeclLHS
);
215 // now check for chained append calls
217 auto expr
= dyn_cast
<Expr
>(stmt
);
220 auto tc
= loplugin::TypeCheck(expr
->getType());
221 if (!tc
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
222 && !tc
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
225 // unwrap the chain (which runs from right to left)
226 const VarDecl
* varDeclLHS
= nullptr;
230 auto memberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(expr
);
233 good
&= isMethodOkToMerge(memberCallExpr
);
235 if (auto declRefExprLHS
236 = dyn_cast
<DeclRefExpr
>(ignore(memberCallExpr
->getImplicitObjectArgument())))
238 varDeclLHS
= dyn_cast
<VarDecl
>(declRefExprLHS
->getDecl());
241 expr
= memberCallExpr
->getImplicitObjectArgument();
247 addToGoodMap(varDeclLHS
, parentStmt
);
249 badMap
.insert(varDeclLHS
);
253 void BufferAdd::addToGoodMap(const VarDecl
* varDecl
, const Stmt
* parentStmt
)
255 // check that vars are all inside the same compoundstmt, if they are not, we cannot combine them
256 auto it
= goodMap
.find(varDecl
);
257 if (it
!= goodMap
.end())
259 if (it
->second
== parentStmt
)
261 // don't treat these as parents, otherwise we eliminate .append.append sequences
262 if (isa
<MemberExpr
>(parentStmt
))
264 if (isa
<CXXMemberCallExpr
>(parentStmt
))
266 badMap
.insert(varDecl
);
269 goodMap
.emplace(varDecl
, parentStmt
);
272 bool BufferAdd::isMethodOkToMerge(CXXMemberCallExpr
const* memberCall
)
274 auto methodDecl
= memberCall
->getMethodDecl();
275 if (methodDecl
->getNumParams() == 0)
278 if (auto const id
= methodDecl
->getIdentifier())
280 auto name
= id
->getName();
281 if (name
== "appendUninitialized" || name
== "setLength" || name
== "remove"
282 || name
== "insert" || name
== "appendAscii" || name
== "appendUtf32")
286 auto rhs
= memberCall
->getArg(0);
287 if (!isSideEffectFree(rhs
))
292 Expr
const* BufferAdd::ignore(Expr
const* expr
)
294 return expr
->IgnoreImplicit()->IgnoreParens()->IgnoreImplicit();
297 bool BufferAdd::isSideEffectFree(Expr
const* expr
)
300 // I don't think the OUStringAppend functionality can handle this efficiently
301 if (isa
<ConditionalOperator
>(expr
))
303 // Multiple statements have a well defined evaluation order (sequence points between them)
304 // but a single expression may be evaluated in arbitrary order;
305 // if there are side effects in one of the sub-expressions that have an effect on another subexpression,
306 // the result may be incorrect, and you don't necessarily notice in tests because the order is compiler-dependent.
307 // for example see commit afd743141f7a7dd05914d0872c9afe079f16fe0c where such a refactoring introduced such a bug.
308 // So only consider simple RHS expressions.
309 if (!expr
->HasSideEffects(compiler
.getASTContext()))
312 // check for chained adds which are side-effect free
313 if (auto operatorCall
= dyn_cast
<CXXOperatorCallExpr
>(expr
))
315 auto op
= operatorCall
->getOperator();
316 if (op
== OO_PlusEqual
|| op
== OO_Plus
)
317 if (isSideEffectFree(operatorCall
->getArg(0))
318 && isSideEffectFree(operatorCall
->getArg(1)))
322 if (auto callExpr
= dyn_cast
<CallExpr
>(expr
))
324 // check for calls through OUString::number/OUString::unacquired
325 if (auto calleeMethodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(callExpr
->getCalleeDecl()))
326 if (calleeMethodDecl
&& calleeMethodDecl
->getIdentifier())
328 if (callExpr
->getNumArgs() > 0)
330 auto tc
= loplugin::TypeCheck(calleeMethodDecl
->getParent());
331 if (tc
.Class("OUString") || tc
.Class("OString"))
333 if (isSideEffectFree(callExpr
->getArg(0)))
338 if (auto calleeFunctionDecl
= dyn_cast_or_null
<FunctionDecl
>(callExpr
->getCalleeDecl()))
339 if (calleeFunctionDecl
&& calleeFunctionDecl
->getIdentifier())
341 auto name
= calleeFunctionDecl
->getName();
342 // check for calls through OUStringToOString
343 if (name
== "OUStringToOString" || name
== "OStringToOUString")
344 if (isSideEffectFree(callExpr
->getArg(0)))
346 // allowlist some known-safe methods
347 if (name
.endswith("ResId") || name
== "GetXMLToken")
348 if (isSideEffectFree(callExpr
->getArg(0)))
351 // O[U]String::operator std::[u16]string_view:
352 if (auto const d
= dyn_cast_or_null
<CXXConversionDecl
>(callExpr
->getCalleeDecl()))
354 auto tc
= loplugin::TypeCheck(d
->getParent());
355 if (tc
.Class("OString") || tc
.Class("OUString"))
362 // sometimes we have a constructor call on the RHS
363 if (auto constructExpr
= dyn_cast
<CXXConstructExpr
>(expr
))
365 auto dc
= loplugin::DeclCheck(constructExpr
->getConstructor());
366 if (dc
.MemberFunction().Class("OUString") || dc
.MemberFunction().Class("OString")
367 || dc
.MemberFunction().Class("OUStringBuffer")
368 || dc
.MemberFunction().Class("OStringBuffer"))
369 if (constructExpr
->getNumArgs() == 0 || isSideEffectFree(constructExpr
->getArg(0)))
371 // Expr::HasSideEffects does not like stuff that passes through OUStringLiteral
372 auto dc2
= loplugin::DeclCheck(constructExpr
->getConstructor()->getParent());
373 if (dc2
.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace())
377 // when adding literals, we sometimes get this
378 if (auto functionalCastExpr
= dyn_cast
<CXXFunctionalCastExpr
>(expr
))
380 auto tc
= loplugin::TypeCheck(functionalCastExpr
->getType());
381 if (tc
.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace())
382 return isSideEffectFree(functionalCastExpr
->getSubExpr());
388 loplugin::Plugin::Registration
<BufferAdd
> bufferadd("bufferadd");
391 #endif // LO_CLANG_SHARED_PLUGINS
393 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */