bump product version to 6.4.0.3
[LibreOffice.git] / compilerplugins / clang / stringadd.cxx
blobe9df02bd10c07dbe695bada056760ff5dfb34311
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_map>
15 #include <unordered_set>
17 #include "plugin.hxx"
18 #include "check.hxx"
19 #include "clang/AST/CXXInheritance.h"
20 #include "clang/AST/StmtVisitor.h"
22 /**
23 Look for repeated addition to OUString/OString.
25 Eg.
26 OUString x = "xxx";
27 x += b;
29 which can be simplified to
30 x = "xxx" + b
32 which is more efficient, because of the OUStringConcat magic.
35 namespace
37 class StringAdd : public loplugin::FilteringPlugin<StringAdd>
39 public:
40 explicit StringAdd(loplugin::InstantiationData const& data)
41 : FilteringPlugin(data)
45 bool preRun() override
47 std::string fn(handler.getMainFileName());
48 loplugin::normalizeDotDotInFilePath(fn);
49 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/oustring/"))
50 return false;
51 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/oustringbuffer/"))
52 return false;
53 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/strings/"))
54 return false;
55 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/OStringBuffer/"))
56 return false;
57 // there is an ifdef here, but my check is not working, not sure why
58 if (fn == SRCDIR "/pyuno/source/module/pyuno_runtime.cxx")
59 return false;
60 // TODO the += depends on the result of the preceding assign, so can't merge
61 if (fn == SRCDIR "/editeng/source/misc/svxacorr.cxx")
62 return false;
63 return true;
66 virtual void run() override
68 if (!preRun())
69 return;
70 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
73 bool VisitCompoundStmt(CompoundStmt const*);
74 bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr const*);
76 private:
77 enum class Summands
79 OnlyCompileTimeConstants,
80 OnlySideEffectFree,
81 SideEffect
84 struct VarDeclAndSummands
86 const VarDecl* varDecl;
87 Summands summands;
90 VarDeclAndSummands findAssignOrAdd(Stmt const*);
91 bool checkForCompoundAssign(Stmt const* stmt1, Stmt const* stmt2, VarDeclAndSummands& varDecl);
93 Expr const* ignore(Expr const*);
94 bool isSideEffectFree(Expr const*);
95 bool isCompileTimeConstant(Expr const*);
98 bool StringAdd::VisitCompoundStmt(CompoundStmt const* compoundStmt)
100 if (ignoreLocation(compoundStmt))
101 return true;
103 auto it = compoundStmt->body_begin();
104 while (true)
106 if (it == compoundStmt->body_end())
107 break;
108 VarDeclAndSummands foundVar = findAssignOrAdd(*it);
109 // reference types have slightly weird behaviour
110 if (foundVar.varDecl && !foundVar.varDecl->getType()->isReferenceType())
112 auto stmt1 = *it;
113 ++it;
114 while (it != compoundStmt->body_end())
116 if (!checkForCompoundAssign(stmt1, *it, foundVar))
118 break;
120 stmt1 = *it;
121 ++it;
124 else
125 ++it;
128 return true;
131 StringAdd::VarDeclAndSummands StringAdd::findAssignOrAdd(Stmt const* stmt)
133 if (auto exprCleanup = dyn_cast<ExprWithCleanups>(stmt))
134 stmt = exprCleanup->getSubExpr();
135 if (auto switchCase = dyn_cast<SwitchCase>(stmt))
136 stmt = switchCase->getSubStmt();
138 if (auto declStmt = dyn_cast<DeclStmt>(stmt))
139 if (declStmt->isSingleDecl())
140 if (auto varDeclLHS = dyn_cast_or_null<VarDecl>(declStmt->getSingleDecl()))
142 auto tc = loplugin::TypeCheck(varDeclLHS->getType());
143 if (!tc.Class("OUString").Namespace("rtl").GlobalNamespace()
144 && !tc.Class("OString").Namespace("rtl").GlobalNamespace())
145 return {};
146 if (varDeclLHS->getStorageDuration() == SD_Static)
147 return {};
148 if (!varDeclLHS->hasInit())
149 return {};
150 return { varDeclLHS, (isCompileTimeConstant(varDeclLHS->getInit())
151 ? Summands::OnlyCompileTimeConstants
152 : (isSideEffectFree(varDeclLHS->getInit())
153 ? Summands::OnlySideEffectFree
154 : Summands::SideEffect)) };
156 if (auto operatorCall = dyn_cast<CXXOperatorCallExpr>(stmt))
157 if (operatorCall->getOperator() == OO_Equal || operatorCall->getOperator() == OO_PlusEqual)
158 if (auto declRefExprLHS = dyn_cast<DeclRefExpr>(ignore(operatorCall->getArg(0))))
159 if (auto varDeclLHS = dyn_cast<VarDecl>(declRefExprLHS->getDecl()))
161 auto tc = loplugin::TypeCheck(varDeclLHS->getType());
162 if (!tc.Class("OUString").Namespace("rtl").GlobalNamespace()
163 && !tc.Class("OString").Namespace("rtl").GlobalNamespace())
164 return {};
165 auto rhs = operatorCall->getArg(1);
166 return { varDeclLHS,
167 (isCompileTimeConstant(rhs)
168 ? Summands::OnlyCompileTimeConstants
169 : (isSideEffectFree(rhs) ? Summands::OnlySideEffectFree
170 : Summands::SideEffect)) };
172 return {};
175 bool StringAdd::checkForCompoundAssign(Stmt const* stmt1, Stmt const* stmt2,
176 VarDeclAndSummands& varDecl)
178 // OString additions are frequently wrapped in these
179 if (auto exprCleanup = dyn_cast<ExprWithCleanups>(stmt2))
180 stmt2 = exprCleanup->getSubExpr();
181 if (auto switchCase = dyn_cast<SwitchCase>(stmt2))
182 stmt2 = switchCase->getSubStmt();
183 auto operatorCall = dyn_cast<CXXOperatorCallExpr>(stmt2);
184 if (!operatorCall)
185 return false;
186 if (operatorCall->getOperator() != OO_PlusEqual)
187 return false;
188 auto declRefExprLHS = dyn_cast<DeclRefExpr>(ignore(operatorCall->getArg(0)));
189 if (!declRefExprLHS)
190 return false;
191 if (declRefExprLHS->getDecl() != varDecl.varDecl)
192 return false;
193 // if either side is a compile-time-constant, then we don't care about
194 // side-effects
195 auto rhs = operatorCall->getArg(1);
196 auto const ctcRhs = isCompileTimeConstant(rhs);
197 if (!ctcRhs)
199 auto const sefRhs = isSideEffectFree(rhs);
200 auto const oldSummands = varDecl.summands;
201 varDecl.summands = sefRhs ? Summands::OnlySideEffectFree : Summands::SideEffect;
202 if (oldSummands != Summands::OnlyCompileTimeConstants
203 && (oldSummands == Summands::SideEffect || !sefRhs))
205 return true;
208 // if we cross a #ifdef boundary
209 if (containsPreprocessingConditionalInclusion(
210 SourceRange(stmt1->getSourceRange().getBegin(), stmt2->getSourceRange().getEnd())))
212 varDecl.summands
213 = ctcRhs ? Summands::OnlyCompileTimeConstants
214 : isSideEffectFree(rhs) ? Summands::OnlySideEffectFree : Summands::SideEffect;
215 return true;
217 report(DiagnosticsEngine::Warning, "simplify by merging with the preceding assignment",
218 compat::getBeginLoc(stmt2))
219 << stmt2->getSourceRange();
220 return true;
223 // Check for generating temporaries when adding strings
225 bool StringAdd::VisitCXXOperatorCallExpr(CXXOperatorCallExpr const* operatorCall)
227 if (ignoreLocation(operatorCall))
228 return true;
229 if (operatorCall->getOperator() != OO_Plus)
230 return true;
231 auto tc = loplugin::TypeCheck(operatorCall->getType()->getUnqualifiedDesugaredType());
232 if (!tc.Struct("OUStringConcat").Namespace("rtl").GlobalNamespace()
233 && !tc.Struct("OStringConcat").Namespace("rtl").GlobalNamespace()
234 && !tc.Class("OUString").Namespace("rtl").GlobalNamespace()
235 && !tc.Class("OString").Namespace("rtl").GlobalNamespace())
236 return true;
238 auto check = [operatorCall, this](unsigned arg) {
239 auto const e
240 = dyn_cast<CXXFunctionalCastExpr>(operatorCall->getArg(arg)->IgnoreParenImpCasts());
241 if (e == nullptr)
242 return;
243 auto tc3 = loplugin::TypeCheck(e->getType());
244 if (!tc3.Class("OUString").Namespace("rtl").GlobalNamespace()
245 && !tc3.Class("OString").Namespace("rtl").GlobalNamespace())
246 return;
247 report(DiagnosticsEngine::Warning,
248 ("avoid constructing %0 from %1 on %select{L|R}2HS of + (where %select{R|L}2HS is of"
249 " type %3)"),
250 compat::getBeginLoc(e))
251 << e->getType().getLocalUnqualifiedType() << e->getSubExprAsWritten()->getType() << arg
252 << operatorCall->getArg(1 - arg)->IgnoreImpCasts()->getType() << e->getSourceRange();
255 check(0);
256 check(1);
257 return true;
260 Expr const* StringAdd::ignore(Expr const* expr)
262 return compat::IgnoreImplicit(compat::IgnoreImplicit(expr)->IgnoreParens());
265 bool StringAdd::isSideEffectFree(Expr const* expr)
267 expr = ignore(expr);
268 // I don't think the OUStringAppend functionality can handle this efficiently
269 if (isa<ConditionalOperator>(expr))
270 return false;
271 // Multiple statements have a well defined evaluation order (sequence points between them)
272 // but a single expression may be evaluated in arbitrary order;
273 // if there are side effects in one of the sub-expressions that have an effect on another subexpression,
274 // the result may be incorrect, and you don't necessarily notice in tests because the order is compiler-dependent.
275 // for example see commit afd743141f7a7dd05914d0872c9afe079f16fe0c where such a refactoring introduced such a bug.
276 // So only consider simple RHS expressions.
277 if (!expr->HasSideEffects(compiler.getASTContext()))
278 return true;
280 // check for chained adds which are side-effect free
281 if (auto operatorCall = dyn_cast<CXXOperatorCallExpr>(expr))
283 auto op = operatorCall->getOperator();
284 if (op == OO_PlusEqual || op == OO_Plus)
285 if (isSideEffectFree(operatorCall->getArg(0))
286 && isSideEffectFree(operatorCall->getArg(1)))
287 return true;
290 if (auto callExpr = dyn_cast<CallExpr>(expr))
292 // check for calls through OUString::number/OUString::unacquired
293 if (auto calleeMethodDecl = dyn_cast_or_null<CXXMethodDecl>(callExpr->getCalleeDecl()))
294 if (calleeMethodDecl && calleeMethodDecl->getIdentifier())
296 auto name = calleeMethodDecl->getName();
297 if (callExpr->getNumArgs() > 0
298 && (name == "number" || name == "unacquired" || name == "boolean"
299 || name == "copy"))
301 auto tc = loplugin::TypeCheck(calleeMethodDecl->getParent());
302 if (tc.Class("OUString") || tc.Class("OString"))
304 if (isSideEffectFree(callExpr->getArg(0)))
305 return true;
309 if (auto calleeFunctionDecl = dyn_cast_or_null<FunctionDecl>(callExpr->getCalleeDecl()))
310 if (calleeFunctionDecl && calleeFunctionDecl->getIdentifier())
312 auto name = calleeFunctionDecl->getName();
313 // check for calls through OUStringToOString
314 if (name == "OUStringToOString" || name == "OStringToOUString")
315 if (isSideEffectFree(callExpr->getArg(0)))
316 return true;
317 // whitelist some known-safe methods
318 if (name.endswith("ResId") || name == "GetXMLToken")
319 if (isSideEffectFree(callExpr->getArg(0)))
320 return true;
324 // sometimes we have a constructor call on the RHS
325 if (auto constructExpr = dyn_cast<CXXConstructExpr>(expr))
327 auto dc = loplugin::DeclCheck(constructExpr->getConstructor());
328 if (dc.MemberFunction().Class("OUString") || dc.MemberFunction().Class("OString"))
329 if (constructExpr->getNumArgs() == 0 || isSideEffectFree(constructExpr->getArg(0)))
330 return true;
331 // Expr::HasSideEffects does not like stuff that passes through OUStringLiteral
332 auto dc2 = loplugin::DeclCheck(constructExpr->getConstructor()->getParent());
333 if (dc2.Struct("OUStringLiteral").Namespace("rtl").GlobalNamespace())
334 return true;
337 // when adding literals, we sometimes get this
338 if (auto functionalCastExpr = dyn_cast<CXXFunctionalCastExpr>(expr))
340 auto tc = loplugin::TypeCheck(functionalCastExpr->getType());
341 if (tc.Struct("OUStringLiteral").Namespace("rtl").GlobalNamespace())
342 return isSideEffectFree(functionalCastExpr->getSubExpr());
345 return false;
348 bool StringAdd::isCompileTimeConstant(Expr const* expr)
350 expr = compat::IgnoreImplicit(expr);
351 if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(expr))
352 if (cxxConstructExpr->getNumArgs() > 0)
353 expr = cxxConstructExpr->getArg(0);
354 return isa<clang::StringLiteral>(expr);
357 loplugin::Plugin::Registration<StringAdd> stringadd("stringadd");
360 #endif // LO_CLANG_SHARED_PLUGINS
362 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */