Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / compilerplugins / clang / bufferadd.cxx
blob7b3eaf7e416e7a5146b15a3c5ef4f839994037d4
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 "config_clang.h"
19 #include "clang/AST/CXXInheritance.h"
20 #include "clang/AST/StmtVisitor.h"
22 /**
23 Look for *StringBuffer append sequences which can be converted to *String + sequences.
26 namespace
28 class BufferAdd : public loplugin::FilteringPlugin<BufferAdd>
30 public:
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/"))
41 return false;
42 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/oustringbuffer/"))
43 return false;
44 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/strings/"))
45 return false;
46 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/OStringBuffer/"))
47 return false;
48 // some false +
49 if (loplugin::isSamePathname(fn, SRCDIR "/unoidl/source/sourcetreeprovider.cxx"))
50 return false;
51 if (loplugin::isSamePathname(fn, SRCDIR "/writerfilter/source/dmapper/StyleSheetTable.cxx"))
52 return false;
53 if (loplugin::isSamePathname(fn, SRCDIR "/writerfilter/source/dmapper/GraphicImport.cxx"))
54 return false;
55 if (loplugin::isSamePathname(fn, SRCDIR "/sdext/source/pdfimport/pdfparse/pdfparse.cxx"))
56 return false;
57 return true;
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
75 if (!preRun())
76 return;
77 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
78 postRun();
81 bool VisitStmt(Stmt const*);
82 bool VisitCallExpr(CallExpr const*);
83 bool VisitCXXConstructExpr(CXXConstructExpr const*);
84 bool VisitUnaryOperator(UnaryOperator const*);
86 private:
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))
100 return true;
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))
105 return true;
107 for (auto it = stmt->child_begin(); it != stmt->child_end(); ++it)
108 if (*it)
109 findBufferAssignOrAdd(stmt, *it);
111 return true;
114 bool BufferAdd::VisitCallExpr(CallExpr const* callExpr)
116 if (ignoreLocation(callExpr))
117 return true;
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);
126 return true;
129 bool BufferAdd::VisitCXXConstructExpr(CXXConstructExpr const* callExpr)
131 if (ignoreLocation(callExpr))
132 return true;
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);
141 return true;
144 bool BufferAdd::VisitUnaryOperator(const UnaryOperator* unaryOp)
146 if (ignoreLocation(unaryOp))
147 return true;
148 if (unaryOp->getOpcode() != UO_AddrOf)
149 return true;
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);
154 return true;
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())
171 return;
172 if (varDeclLHS->getStorageDuration() == SD_Static)
173 return;
174 if (!varDeclLHS->hasInit())
175 return;
176 auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(ignore(varDeclLHS->getInit()));
177 if (cxxConstructExpr)
179 addToGoodMap(varDeclLHS, parentStmt);
180 return;
182 if (!isSideEffectFree(varDeclLHS->getInit()))
183 badMap.insert(varDeclLHS);
184 else
185 addToGoodMap(varDeclLHS, parentStmt);
187 return;
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);
207 else
208 badMap.insert(varDeclLHS);
211 return;
215 // now check for chained append calls
217 auto expr = dyn_cast<Expr>(stmt);
218 if (!expr)
219 return;
220 auto tc = loplugin::TypeCheck(expr->getType());
221 if (!tc.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
222 && !tc.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
223 return;
225 // unwrap the chain (which runs from right to left)
226 const VarDecl* varDeclLHS = nullptr;
227 bool good = true;
228 while (true)
230 auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(expr);
231 if (!memberCallExpr)
232 break;
233 good &= isMethodOkToMerge(memberCallExpr);
235 if (auto declRefExprLHS
236 = dyn_cast<DeclRefExpr>(ignore(memberCallExpr->getImplicitObjectArgument())))
238 varDeclLHS = dyn_cast<VarDecl>(declRefExprLHS->getDecl());
239 break;
241 expr = memberCallExpr->getImplicitObjectArgument();
244 if (varDeclLHS)
246 if (good)
247 addToGoodMap(varDeclLHS, parentStmt);
248 else
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)
260 return;
261 // don't treat these as parents, otherwise we eliminate .append.append sequences
262 if (isa<MemberExpr>(parentStmt))
263 return;
264 if (isa<CXXMemberCallExpr>(parentStmt))
265 return;
266 badMap.insert(varDecl);
268 else
269 goodMap.emplace(varDecl, parentStmt);
272 bool BufferAdd::isMethodOkToMerge(CXXMemberCallExpr const* memberCall)
274 auto methodDecl = memberCall->getMethodDecl();
275 if (methodDecl->getNumParams() == 0)
276 return true;
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")
283 return false;
286 auto rhs = memberCall->getArg(0);
287 if (!isSideEffectFree(rhs))
288 return false;
289 return true;
292 Expr const* BufferAdd::ignore(Expr const* expr)
294 return expr->IgnoreImplicit()->IgnoreParens()->IgnoreImplicit();
297 bool BufferAdd::isSideEffectFree(Expr const* expr)
299 expr = ignore(expr);
300 // I don't think the OUStringAppend functionality can handle this efficiently
301 if (isa<ConditionalOperator>(expr))
302 return false;
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()))
310 return true;
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)))
319 return true;
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)))
334 return true;
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)))
345 return true;
346 // allowlist some known-safe methods
347 if (name.endswith("ResId") || name == "GetXMLToken")
348 if (isSideEffectFree(callExpr->getArg(0)))
349 return true;
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"))
357 return true;
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)))
370 return true;
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())
374 return true;
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());
385 return false;
388 loplugin::Plugin::Registration<BufferAdd> bufferadd("bufferadd");
391 #endif // LO_CLANG_SHARED_PLUGINS
393 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */