bump product version to 7.2.5.1
[LibreOffice.git] / compilerplugins / clang / bufferadd.cxx
blobbc8c7065b2a135aa24998e87babb06bff1fa9e7c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
9 #ifndef LO_CLANG_SHARED_PLUGINS
11 #include <cassert>
12 #include <string>
13 #include <iostream>
14 #include <unordered_set>
16 #include "plugin.hxx"
17 #include "check.hxx"
18 #include "clang/AST/CXXInheritance.h"
19 #include "clang/AST/StmtVisitor.h"
21 /**
22 Look for *StringBuffer append sequences which can be converted to *String + sequences.
25 namespace
27 class BufferAdd : public loplugin::FilteringPlugin<BufferAdd>
29 public:
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/"))
40 return false;
41 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/oustringbuffer/"))
42 return false;
43 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/strings/"))
44 return false;
45 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/OStringBuffer/"))
46 return false;
47 // some false +
48 if (loplugin::isSamePathname(fn, SRCDIR "/unoidl/source/sourcetreeprovider.cxx"))
49 return false;
50 if (loplugin::isSamePathname(fn, SRCDIR "/writerfilter/source/dmapper/StyleSheetTable.cxx"))
51 return false;
52 if (loplugin::isSamePathname(fn, SRCDIR "/writerfilter/source/dmapper/GraphicImport.cxx"))
53 return false;
54 if (loplugin::isSamePathname(fn, SRCDIR "/sdext/source/pdfimport/pdfparse/pdfparse.cxx"))
55 return false;
56 return true;
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
74 if (!preRun())
75 return;
76 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
77 postRun();
80 bool VisitStmt(Stmt const*);
81 bool VisitCallExpr(CallExpr const*);
82 bool VisitCXXConstructExpr(CXXConstructExpr const*);
83 bool VisitUnaryOperator(UnaryOperator const*);
85 private:
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))
99 return true;
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))
104 return true;
106 for (auto it = stmt->child_begin(); it != stmt->child_end(); ++it)
107 if (*it)
108 findBufferAssignOrAdd(stmt, *it);
110 return true;
113 bool BufferAdd::VisitCallExpr(CallExpr const* callExpr)
115 if (ignoreLocation(callExpr))
116 return true;
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);
125 return true;
128 bool BufferAdd::VisitCXXConstructExpr(CXXConstructExpr const* callExpr)
130 if (ignoreLocation(callExpr))
131 return true;
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);
140 return true;
143 bool BufferAdd::VisitUnaryOperator(const UnaryOperator* unaryOp)
145 if (ignoreLocation(unaryOp))
146 return true;
147 if (unaryOp->getOpcode() != UO_AddrOf)
148 return true;
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);
153 return true;
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())
170 return;
171 if (varDeclLHS->getStorageDuration() == SD_Static)
172 return;
173 if (!varDeclLHS->hasInit())
174 return;
175 auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(ignore(varDeclLHS->getInit()));
176 if (cxxConstructExpr)
178 addToGoodMap(varDeclLHS, parentStmt);
179 return;
181 if (!isSideEffectFree(varDeclLHS->getInit()))
182 badMap.insert(varDeclLHS);
183 else
184 addToGoodMap(varDeclLHS, parentStmt);
186 return;
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);
206 else
207 badMap.insert(varDeclLHS);
210 return;
214 // now check for chained append calls
216 auto expr = dyn_cast<Expr>(stmt);
217 if (!expr)
218 return;
219 auto tc = loplugin::TypeCheck(expr->getType());
220 if (!tc.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
221 && !tc.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
222 return;
224 // unwrap the chain (which runs from right to left)
225 const VarDecl* varDeclLHS = nullptr;
226 bool good = true;
227 while (true)
229 auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(expr);
230 if (!memberCallExpr)
231 break;
232 good &= isMethodOkToMerge(memberCallExpr);
234 if (auto declRefExprLHS
235 = dyn_cast<DeclRefExpr>(ignore(memberCallExpr->getImplicitObjectArgument())))
237 varDeclLHS = dyn_cast<VarDecl>(declRefExprLHS->getDecl());
238 break;
240 expr = memberCallExpr->getImplicitObjectArgument();
243 if (varDeclLHS)
245 if (good)
246 addToGoodMap(varDeclLHS, parentStmt);
247 else
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)
259 return;
260 // don't treat these as parents, otherwise we eliminate .append.append sequences
261 if (isa<MemberExpr>(parentStmt))
262 return;
263 if (isa<CXXMemberCallExpr>(parentStmt))
264 return;
265 badMap.insert(varDecl);
267 else
268 goodMap.emplace(varDecl, parentStmt);
271 bool BufferAdd::isMethodOkToMerge(CXXMemberCallExpr const* memberCall)
273 auto methodDecl = memberCall->getMethodDecl();
274 if (methodDecl->getNumParams() == 0)
275 return true;
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")
282 return false;
285 auto rhs = memberCall->getArg(0);
286 if (!isSideEffectFree(rhs))
287 return false;
288 return true;
291 Expr const* BufferAdd::ignore(Expr const* expr)
293 return compat::IgnoreImplicit(compat::IgnoreImplicit(expr)->IgnoreParens());
296 bool BufferAdd::isSideEffectFree(Expr const* expr)
298 expr = ignore(expr);
299 // I don't think the OUStringAppend functionality can handle this efficiently
300 if (isa<ConditionalOperator>(expr))
301 return false;
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()))
309 return true;
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)))
318 return true;
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)))
333 return true;
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)))
344 return true;
345 // allowlist some known-safe methods
346 if (name.endswith("ResId") || name == "GetXMLToken")
347 if (isSideEffectFree(callExpr->getArg(0)))
348 return true;
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)))
360 return true;
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())
364 return true;
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());
375 return false;
378 loplugin::Plugin::Registration<BufferAdd> bufferadd("bufferadd");
381 #endif // LO_CLANG_SHARED_PLUGINS
383 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */