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>
19 #include "config_clang.h"
20 #include "clang/AST/CXXInheritance.h"
21 #include "clang/AST/StmtVisitor.h"
24 Look for *StringBuffer append sequences which can be converted to *String + sequences.
29 class BufferAdd
: public loplugin::FilteringPlugin
<BufferAdd
>
32 explicit BufferAdd(loplugin::InstantiationData
const& data
)
33 : FilteringPlugin(data
)
37 bool preRun() override
39 std::string
fn(handler
.getMainFileName());
40 loplugin::normalizeDotDotInFilePath(fn
);
41 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/rtl/oustring/"))
43 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/rtl/oustringbuffer/"))
45 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/rtl/strings/"))
47 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/OStringBuffer/"))
50 if (loplugin::isSamePathname(fn
, SRCDIR
"/unoidl/source/sourcetreeprovider.cxx"))
52 if (loplugin::isSamePathname(fn
,
53 SRCDIR
"/sw/source/writerfilter/dmapper/StyleSheetTable.cxx"))
55 if (loplugin::isSamePathname(fn
,
56 SRCDIR
"/sw/source/writerfilter/dmapper/GraphicImport.cxx"))
58 if (loplugin::isSamePathname(fn
, SRCDIR
"/sdext/source/pdfimport/pdfparse/pdfparse.cxx"))
63 void postRun() override
65 for (auto const& pair
: goodMap
)
66 if (!isa
<ParmVarDecl
>(pair
.first
) &&
67 // reference types have slightly weird behaviour
68 !pair
.first
->getType()->isReferenceType()
69 && badMap
.find(pair
.first
) == badMap
.end())
70 report(DiagnosticsEngine::Warning
,
71 "convert this append sequence into a *String + sequence",
72 pair
.first
->getBeginLoc())
73 << pair
.first
->getSourceRange();
76 virtual void run() override
80 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
84 bool VisitStmt(Stmt
const*);
85 bool VisitCallExpr(CallExpr
const*);
86 bool VisitCXXConstructExpr(CXXConstructExpr
const*);
87 bool VisitUnaryOperator(UnaryOperator
const*);
90 void findBufferAssignOrAdd(const Stmt
* parentStmt
, Stmt
const*);
91 Expr
const* ignore(Expr
const*);
92 bool isSideEffectFree(Expr
const*);
93 bool isMethodOkToMerge(CXXMemberCallExpr
const*);
94 void addToGoodMap(const VarDecl
* varDecl
, const Stmt
* parentStmt
);
96 std::unordered_map
<const VarDecl
*, const Stmt
*> goodMap
;
97 std::unordered_set
<const VarDecl
*> badMap
;
100 bool BufferAdd::VisitStmt(Stmt
const* stmt
)
102 if (ignoreLocation(stmt
))
105 if (!isa
<CompoundStmt
>(stmt
) && !isa
<CXXCatchStmt
>(stmt
) && !isa
<CXXForRangeStmt
>(stmt
)
106 && !isa
<CXXTryStmt
>(stmt
) && !isa
<DoStmt
>(stmt
) && !isa
<ForStmt
>(stmt
) && !isa
<IfStmt
>(stmt
)
107 && !isa
<SwitchStmt
>(stmt
) && !isa
<WhileStmt
>(stmt
))
110 for (auto it
= stmt
->child_begin(); it
!= stmt
->child_end(); ++it
)
112 findBufferAssignOrAdd(stmt
, *it
);
117 bool BufferAdd::VisitCallExpr(CallExpr
const* callExpr
)
119 if (ignoreLocation(callExpr
))
122 for (unsigned i
= 0; i
!= callExpr
->getNumArgs(); ++i
)
124 auto a
= ignore(callExpr
->getArg(i
));
125 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(a
))
126 if (auto varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl()))
127 badMap
.insert(varDecl
);
132 bool BufferAdd::VisitCXXConstructExpr(CXXConstructExpr
const* callExpr
)
134 if (ignoreLocation(callExpr
))
137 for (unsigned i
= 0; i
!= callExpr
->getNumArgs(); ++i
)
139 auto a
= ignore(callExpr
->getArg(i
));
140 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(a
))
141 if (auto varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl()))
142 badMap
.insert(varDecl
);
147 bool BufferAdd::VisitUnaryOperator(const UnaryOperator
* unaryOp
)
149 if (ignoreLocation(unaryOp
))
151 if (unaryOp
->getOpcode() != UO_AddrOf
)
153 auto a
= ignore(unaryOp
->getSubExpr());
154 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(a
))
155 if (auto varDecl
= dyn_cast
<VarDecl
>(declRefExpr
->getDecl()))
156 badMap
.insert(varDecl
);
160 void BufferAdd::findBufferAssignOrAdd(const Stmt
* parentStmt
, Stmt
const* stmt
)
162 if (auto exprCleanup
= dyn_cast
<ExprWithCleanups
>(stmt
))
163 stmt
= exprCleanup
->getSubExpr();
164 if (auto switchCase
= dyn_cast
<SwitchCase
>(stmt
))
165 stmt
= switchCase
->getSubStmt();
166 if (auto declStmt
= dyn_cast
<DeclStmt
>(stmt
))
168 if (declStmt
->isSingleDecl())
169 if (auto varDeclLHS
= dyn_cast_or_null
<VarDecl
>(declStmt
->getSingleDecl()))
171 auto tc
= loplugin::TypeCheck(varDeclLHS
->getType());
172 if (!tc
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
173 && !tc
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
175 if (varDeclLHS
->getStorageDuration() == SD_Static
)
177 if (!varDeclLHS
->hasInit())
179 auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(ignore(varDeclLHS
->getInit()));
180 if (cxxConstructExpr
)
182 addToGoodMap(varDeclLHS
, parentStmt
);
185 if (!isSideEffectFree(varDeclLHS
->getInit()))
186 badMap
.insert(varDeclLHS
);
188 addToGoodMap(varDeclLHS
, parentStmt
);
193 // check for single calls to buffer method
195 if (auto memberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(stmt
))
197 if (auto declRefExprLHS
198 = dyn_cast
<DeclRefExpr
>(ignore(memberCallExpr
->getImplicitObjectArgument())))
200 auto methodDecl
= memberCallExpr
->getMethodDecl();
201 if (methodDecl
&& methodDecl
->getIdentifier())
202 if (auto varDeclLHS
= dyn_cast
<VarDecl
>(declRefExprLHS
->getDecl()))
204 auto tc
= loplugin::TypeCheck(varDeclLHS
->getType());
205 if (tc
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
206 || tc
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
208 if (isMethodOkToMerge(memberCallExpr
))
209 addToGoodMap(varDeclLHS
, parentStmt
);
211 badMap
.insert(varDeclLHS
);
218 // now check for chained append calls
220 auto expr
= dyn_cast
<Expr
>(stmt
);
223 auto tc
= loplugin::TypeCheck(expr
->getType());
224 if (!tc
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
225 && !tc
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
228 // unwrap the chain (which runs from right to left)
229 const VarDecl
* varDeclLHS
= nullptr;
233 auto memberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(expr
);
236 good
&= isMethodOkToMerge(memberCallExpr
);
238 if (auto declRefExprLHS
239 = dyn_cast
<DeclRefExpr
>(ignore(memberCallExpr
->getImplicitObjectArgument())))
241 varDeclLHS
= dyn_cast
<VarDecl
>(declRefExprLHS
->getDecl());
244 expr
= memberCallExpr
->getImplicitObjectArgument();
250 addToGoodMap(varDeclLHS
, parentStmt
);
252 badMap
.insert(varDeclLHS
);
256 void BufferAdd::addToGoodMap(const VarDecl
* varDecl
, const Stmt
* parentStmt
)
258 // check that vars are all inside the same compoundstmt, if they are not, we cannot combine them
259 auto it
= goodMap
.find(varDecl
);
260 if (it
!= goodMap
.end())
262 if (it
->second
== parentStmt
)
264 // don't treat these as parents, otherwise we eliminate .append.append sequences
265 if (isa
<MemberExpr
>(parentStmt
))
267 if (isa
<CXXMemberCallExpr
>(parentStmt
))
269 badMap
.insert(varDecl
);
272 goodMap
.emplace(varDecl
, parentStmt
);
275 bool BufferAdd::isMethodOkToMerge(CXXMemberCallExpr
const* memberCall
)
277 auto methodDecl
= memberCall
->getMethodDecl();
278 if (methodDecl
->getNumParams() == 0)
281 if (auto const id
= methodDecl
->getIdentifier())
283 auto name
= id
->getName();
284 if (name
== "appendUninitialized" || name
== "setLength" || name
== "remove"
285 || name
== "insert" || name
== "appendAscii" || name
== "appendUtf32")
289 auto rhs
= memberCall
->getArg(0);
290 if (!isSideEffectFree(rhs
))
295 Expr
const* BufferAdd::ignore(Expr
const* expr
)
297 return expr
->IgnoreImplicit()->IgnoreParens()->IgnoreImplicit();
300 bool BufferAdd::isSideEffectFree(Expr
const* expr
)
303 // I don't think the OUStringAppend functionality can handle this efficiently
304 if (isa
<ConditionalOperator
>(expr
))
306 // Multiple statements have a well defined evaluation order (sequence points between them)
307 // but a single expression may be evaluated in arbitrary order;
308 // if there are side effects in one of the sub-expressions that have an effect on another subexpression,
309 // the result may be incorrect, and you don't necessarily notice in tests because the order is compiler-dependent.
310 // for example see commit afd743141f7a7dd05914d0872c9afe079f16fe0c where such a refactoring introduced such a bug.
311 // So only consider simple RHS expressions.
312 if (!expr
->HasSideEffects(compiler
.getASTContext()))
315 // check for chained adds which are side-effect free
316 if (auto operatorCall
= dyn_cast
<CXXOperatorCallExpr
>(expr
))
318 auto op
= operatorCall
->getOperator();
319 if (op
== OO_PlusEqual
|| op
== OO_Plus
)
320 if (isSideEffectFree(operatorCall
->getArg(0))
321 && isSideEffectFree(operatorCall
->getArg(1)))
325 if (auto callExpr
= dyn_cast
<CallExpr
>(expr
))
327 // check for calls through OUString::number/OUString::unacquired
328 if (auto calleeMethodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(callExpr
->getCalleeDecl()))
329 if (calleeMethodDecl
&& calleeMethodDecl
->getIdentifier())
331 if (callExpr
->getNumArgs() > 0)
333 auto tc
= loplugin::TypeCheck(calleeMethodDecl
->getParent());
334 if (tc
.Class("OUString") || tc
.Class("OString"))
336 if (isSideEffectFree(callExpr
->getArg(0)))
341 if (auto calleeFunctionDecl
= dyn_cast_or_null
<FunctionDecl
>(callExpr
->getCalleeDecl()))
342 if (calleeFunctionDecl
&& calleeFunctionDecl
->getIdentifier())
344 auto name
= calleeFunctionDecl
->getName();
345 // check for calls through OUStringToOString
346 if (name
== "OUStringToOString" || name
== "OStringToOUString")
347 if (isSideEffectFree(callExpr
->getArg(0)))
349 // allowlist some known-safe methods
350 if (compat::ends_with(name
, "ResId") || name
== "GetXMLToken")
351 if (isSideEffectFree(callExpr
->getArg(0)))
354 // O[U]String::operator std::[u16]string_view:
355 if (auto const d
= dyn_cast_or_null
<CXXConversionDecl
>(callExpr
->getCalleeDecl()))
357 auto tc
= loplugin::TypeCheck(d
->getParent());
358 if (tc
.Class("OString") || tc
.Class("OUString"))
365 // sometimes we have a constructor call on the RHS
366 if (auto constructExpr
= dyn_cast
<CXXConstructExpr
>(expr
))
368 auto dc
= loplugin::DeclCheck(constructExpr
->getConstructor());
369 if (dc
.MemberFunction().Class("OUString") || dc
.MemberFunction().Class("OString")
370 || dc
.MemberFunction().Class("OUStringBuffer")
371 || dc
.MemberFunction().Class("OStringBuffer"))
372 if (constructExpr
->getNumArgs() == 0 || isSideEffectFree(constructExpr
->getArg(0)))
374 // Expr::HasSideEffects does not like stuff that passes through OUStringLiteral
375 auto dc2
= loplugin::DeclCheck(constructExpr
->getConstructor()->getParent());
376 if (dc2
.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace())
380 // when adding literals, we sometimes get this
381 if (auto functionalCastExpr
= dyn_cast
<CXXFunctionalCastExpr
>(expr
))
383 auto tc
= loplugin::TypeCheck(functionalCastExpr
->getType());
384 if (tc
.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace())
385 return isSideEffectFree(functionalCastExpr
->getSubExpr());
391 loplugin::Plugin::Registration
<BufferAdd
> bufferadd("bufferadd");
394 #endif // LO_CLANG_SHARED_PLUGINS
396 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */