Version 24.2.2.2, tag libreoffice-24.2.2.2
[LibreOffice.git] / compilerplugins / clang / stringview.cxx
blob9484f3ace957304610bb82aee9b33005ef3612a0
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 "config_clang.h"
20 #include "clang/AST/CXXInheritance.h"
21 #include "clang/AST/StmtVisitor.h"
23 /**
24 Look for places where we are making a substring copy of an OUString, and then passing it to a
25 function that takes a u16string_view, in which case it is more efficient to pass a view
26 of the OUString, rather than making a copy.
28 TODO currently does not check if there is some other visible overload of the callee, that can take
29 a string_view.
30 TODO handle OUStringBuffer/OStringBuffer similarly
33 namespace
35 class StringView : public loplugin::FilteringPlugin<StringView>
37 public:
38 explicit StringView(loplugin::InstantiationData const& data)
39 : FilteringPlugin(data)
43 bool preRun() override
45 auto const fn = handler.getMainFileName();
46 return !(loplugin::isSamePathname(fn, SRCDIR "/sal/qa/OStringBuffer/rtl_OStringBuffer.cxx")
47 || loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/strings/")
48 || loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/oustring/"));
51 virtual void run() override
53 if (!preRun())
54 return;
55 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
58 bool VisitFunctionDecl(FunctionDecl const*);
59 bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr const*);
60 bool VisitImplicitCastExpr(ImplicitCastExpr const*);
61 bool VisitCXXMemberCallExpr(CXXMemberCallExpr const*);
62 bool VisitCXXConstructExpr(CXXConstructExpr const*);
64 private:
65 void handleSubExprThatCouldBeView(Expr const* expr);
66 void handleCXXConstructExpr(CXXConstructExpr const* expr);
67 void handleCXXMemberCallExpr(CXXMemberCallExpr const* expr);
70 bool StringView::VisitCXXOperatorCallExpr(CXXOperatorCallExpr const* cxxOperatorCallExpr)
72 if (ignoreLocation(cxxOperatorCallExpr))
73 return true;
75 auto op = cxxOperatorCallExpr->getOperator();
76 if (op == OO_Plus && cxxOperatorCallExpr->getNumArgs() == 2)
78 handleSubExprThatCouldBeView(cxxOperatorCallExpr->getArg(0));
79 handleSubExprThatCouldBeView(cxxOperatorCallExpr->getArg(1));
81 if (cxxOperatorCallExpr->isComparisonOp())
83 handleSubExprThatCouldBeView(cxxOperatorCallExpr->getArg(0));
84 handleSubExprThatCouldBeView(cxxOperatorCallExpr->getArg(1));
86 else if (op == OO_PlusEqual)
87 handleSubExprThatCouldBeView(cxxOperatorCallExpr->getArg(1));
88 else if (op == OO_Subscript)
89 handleSubExprThatCouldBeView(cxxOperatorCallExpr->getArg(0));
90 else if (op == OO_Equal)
92 if (loplugin::TypeCheck(cxxOperatorCallExpr->getType())
93 .Class("OUStringBuffer")
94 .Namespace("rtl")
95 .GlobalNamespace()
96 || loplugin::TypeCheck(cxxOperatorCallExpr->getType())
97 .Class("OStringBuffer")
98 .Namespace("rtl")
99 .GlobalNamespace())
101 handleSubExprThatCouldBeView(cxxOperatorCallExpr->getArg(1));
104 return true;
107 bool StringView::VisitFunctionDecl(FunctionDecl const* functionDecl)
109 if (ignoreLocation(functionDecl))
110 return true;
111 // debugging
112 // if (functionDecl->getIdentifier() && functionDecl->getName() == "f1")
113 // functionDecl->dump();
114 return true;
117 bool StringView::VisitImplicitCastExpr(ImplicitCastExpr const* expr)
119 if (ignoreLocation(expr))
121 return true;
123 if (!loplugin::TypeCheck(expr->getType()).ClassOrStruct("basic_string_view").StdNamespace())
125 return true;
127 handleSubExprThatCouldBeView(expr->getSubExprAsWritten());
128 return true;
131 void StringView::handleSubExprThatCouldBeView(Expr const* subExpr)
133 auto const e0 = subExpr->IgnoreImplicit();
134 auto const e = e0->IgnoreParens();
135 auto const tc = loplugin::TypeCheck(e->getType());
136 if (!(tc.Class("OString").Namespace("rtl").GlobalNamespace()
137 || tc.Class("OUString").Namespace("rtl").GlobalNamespace()
138 || tc.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()))
140 return;
142 if (auto const e1 = dyn_cast<CXXConstructExpr>(e))
144 if (e0 == subExpr)
146 handleCXXConstructExpr(e1);
149 else if (auto const e2 = dyn_cast<CXXFunctionalCastExpr>(e))
151 auto e3 = e2->getSubExpr();
152 if (auto const e4 = dyn_cast<CXXBindTemporaryExpr>(e3))
154 e3 = e4->getSubExpr();
156 if (auto const e4 = dyn_cast<CXXConstructExpr>(e3))
158 handleCXXConstructExpr(e4);
161 else if (auto const e3 = dyn_cast<CXXMemberCallExpr>(e))
163 handleCXXMemberCallExpr(e3);
167 void StringView::handleCXXConstructExpr(CXXConstructExpr const* expr)
169 QualType argType;
170 enum
172 None,
173 OrChar,
174 ViaConcatenation
175 } extra
176 = None;
177 auto const d = expr->getConstructor();
178 switch (d->getNumParams())
180 case 0:
181 break;
182 case 1:
184 auto const t = d->getParamDecl(0)->getType();
185 if (t->isAnyCharacterType())
187 argType = expr->getArg(0)->IgnoreImplicit()->getType();
188 extra = OrChar;
189 break;
191 loplugin::TypeCheck tc(t);
192 if (tc.RvalueReference().Struct("StringNumber").Namespace("rtl").GlobalNamespace()
193 || tc.ClassOrStruct("basic_string_view").StdNamespace())
195 argType = expr->getArg(0)->IgnoreImplicit()->getType();
196 break;
198 if (tc.RvalueReference().Struct("StringConcat").Namespace("rtl").GlobalNamespace())
200 argType = expr->getArg(0)->IgnoreImplicit()->getType();
201 extra = ViaConcatenation;
202 break;
204 return;
206 case 2:
208 auto const t0 = d->getParamDecl(0)->getType();
209 if (t0->isPointerType() && t0->getPointeeType()->isAnyCharacterType())
211 auto const t = d->getParamDecl(1)->getType();
212 if (t->isIntegralType(compiler.getASTContext())
213 && !(t->isBooleanType() || t->isAnyCharacterType()))
215 auto const arg = expr->getArg(1);
216 if (!arg->isValueDependent())
218 if (auto const val = arg->getIntegerConstantExpr(compiler.getASTContext()))
220 if (val->getExtValue() == 1)
222 extra = OrChar;
226 argType = expr->getArg(0)->IgnoreImplicit()->getType();
227 break;
230 if (loplugin::TypeCheck(d->getParamDecl(1)->getType())
231 .Struct("Dummy")
232 .Namespace("libreoffice_internal")
233 .Namespace("rtl")
234 .GlobalNamespace())
236 argType = expr->getArg(0)->IgnoreImplicit()->getType();
237 break;
239 return;
241 default:
242 return;
244 report(DiagnosticsEngine::Warning,
245 "instead of an %0%select{| constructed from a %2}1, pass a"
246 " '%select{std::string_view|std::u16string_view}3'"
247 "%select{| (or an '%select{rtl::OStringChar|rtl::OUStringChar}3')|"
248 " via 'rtl::Concat2View'}4",
249 expr->getExprLoc())
250 << expr->getType() << (argType.isNull() ? 0 : 1) << argType
251 << (loplugin::TypeCheck(expr->getType()).Class("OString").Namespace("rtl").GlobalNamespace()
253 : 1)
254 << extra << expr->getSourceRange();
257 void StringView::handleCXXMemberCallExpr(CXXMemberCallExpr const* expr)
259 auto const dc1 = loplugin::DeclCheck(expr->getMethodDecl());
260 if (auto const dc2 = dc1.Function("copy"))
262 if (dc2.Class("OString").Namespace("rtl").GlobalNamespace()
263 || dc2.Class("OUString").Namespace("rtl").GlobalNamespace()
264 || dc2.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace())
266 report(DiagnosticsEngine::Warning, "rather than copy, pass with a view using subView()",
267 expr->getExprLoc())
268 << expr->getSourceRange();
270 return;
272 if (auto const dc2 = dc1.Function("getToken"))
274 if (dc2.Class("OString").Namespace("rtl").GlobalNamespace()
275 || dc2.Class("OUString").Namespace("rtl").GlobalNamespace()
276 || dc2.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace())
278 report(DiagnosticsEngine::Warning,
279 "rather than getToken, pass with a view using o3tl::getToken()",
280 expr->getExprLoc())
281 << expr->getSourceRange();
283 return;
285 if (auto const dc2 = dc1.Function("trim"))
287 if (dc2.Class("OString").Namespace("rtl").GlobalNamespace()
288 || dc2.Class("OUString").Namespace("rtl").GlobalNamespace()
289 || dc2.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace())
291 report(DiagnosticsEngine::Warning,
292 "rather than trim, pass with a view using o3tl::trim()", expr->getExprLoc())
293 << expr->getSourceRange();
295 return;
297 if (auto const dc2 = dc1.Function("makeStringAndClear"))
299 if (dc2.Class("OStringBuffer").Namespace("rtl").GlobalNamespace()
300 || dc2.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace())
302 auto const obj = expr->getImplicitObjectArgument();
303 if (!(obj->isLValue() || obj->getType()->isPointerType()))
305 report(DiagnosticsEngine::Warning,
306 "rather than call makeStringAndClear on an rvalue, pass with a view",
307 expr->getExprLoc())
308 << expr->getSourceRange();
311 return;
313 if (auto const dc2 = dc1.Function("toString"))
315 if (dc2.Class("OStringBuffer").Namespace("rtl").GlobalNamespace()
316 || dc2.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace())
318 report(DiagnosticsEngine::Warning, "rather than call toString, pass with a view",
319 expr->getExprLoc())
320 << expr->getSourceRange();
322 return;
326 bool StringView::VisitCXXMemberCallExpr(CXXMemberCallExpr const* expr)
328 if (ignoreLocation(expr))
330 return true;
332 /** check for calls to O[U]StringBuffer::append that could be passed as a
333 std::u16string_view */
334 if (loplugin::TypeCheck(expr->getType())
335 .Class("OUStringBuffer")
336 .Namespace("rtl")
337 .GlobalNamespace()
338 || loplugin::TypeCheck(expr->getType())
339 .Class("OStringBuffer")
340 .Namespace("rtl")
341 .GlobalNamespace())
343 auto const dc = loplugin::DeclCheck(expr->getMethodDecl());
344 if (dc.Function("append") || dc.Function("indexOf") || dc.Function("lastIndexOf"))
346 handleSubExprThatCouldBeView(expr->getArg(0));
348 else if (dc.Function("insert"))
350 handleSubExprThatCouldBeView(expr->getArg(1));
354 // rather than getToken...toInt32, use o3tl::toInt(o3tl::getToken(...)
355 auto tc = loplugin::TypeCheck(expr->getImplicitObjectArgument()->getType());
356 if (tc.Class("OUString").Namespace("rtl").GlobalNamespace()
357 || tc.Class("OString").Namespace("rtl").GlobalNamespace())
359 auto const dc = loplugin::DeclCheck(expr->getMethodDecl());
360 if (dc.Function("toInt32") || dc.Function("toUInt32") || dc.Function("toInt64")
361 || dc.Function("toDouble") || dc.Function("equalsAscii") || dc.Function("equalsAsciiL")
362 || dc.Function("equalsIgnoreAsciiCase") || dc.Function("compareToIgnoreAsciiCase")
363 || dc.Function("matchIgnoreAsciiCase") || dc.Function("trim")
364 || dc.Function("startsWith") || dc.Function("endsWith") || dc.Function("match")
365 || dc.Function("isEmpty") || dc.Function("getLength")
366 || dc.Function("iterateCodePoints"))
368 handleSubExprThatCouldBeView(expr->getImplicitObjectArgument());
371 return true;
374 /** check for calls to O[U]StringBuffer constructor that could be passed as a
375 std::u16string_view */
376 bool StringView::VisitCXXConstructExpr(CXXConstructExpr const* expr)
378 if (ignoreLocation(expr))
380 return true;
382 if (!loplugin::TypeCheck(expr->getType())
383 .Class("OUStringBuffer")
384 .Namespace("rtl")
385 .GlobalNamespace()
386 && !loplugin::TypeCheck(expr->getType())
387 .Class("OStringBuffer")
388 .Namespace("rtl")
389 .GlobalNamespace())
391 return true;
393 if (!compiler.getLangOpts().CPlusPlus17 && expr->isElidable()) // external C++03 code
395 return true;
397 if (expr->getNumArgs() > 0)
398 handleSubExprThatCouldBeView(expr->getArg(0));
399 return true;
402 loplugin::Plugin::Registration<StringView> stringview("stringview");
405 #endif // LO_CLANG_SHARED_PLUGINS
407 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */