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_map>
15 #include <unordered_set>
19 #include "config_clang.h"
20 #include "clang/AST/CXXInheritance.h"
21 #include "clang/AST/StmtVisitor.h"
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
30 TODO handle OUStringBuffer/OStringBuffer similarly
35 class StringView
: public loplugin::FilteringPlugin
<StringView
>
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
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*);
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
))
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")
96 || loplugin::TypeCheck(cxxOperatorCallExpr
->getType())
97 .Class("OStringBuffer")
101 handleSubExprThatCouldBeView(cxxOperatorCallExpr
->getArg(1));
107 bool StringView::VisitFunctionDecl(FunctionDecl
const* functionDecl
)
109 if (ignoreLocation(functionDecl
))
112 // if (functionDecl->getIdentifier() && functionDecl->getName() == "f1")
113 // functionDecl->dump();
117 bool StringView::VisitImplicitCastExpr(ImplicitCastExpr
const* expr
)
119 if (ignoreLocation(expr
))
123 if (!loplugin::TypeCheck(expr
->getType()).ClassOrStruct("basic_string_view").StdNamespace())
127 handleSubExprThatCouldBeView(expr
->getSubExprAsWritten());
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()))
142 if (auto const e1
= dyn_cast
<CXXConstructExpr
>(e
))
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
)
177 auto const d
= expr
->getConstructor();
178 switch (d
->getNumParams())
184 auto const t
= d
->getParamDecl(0)->getType();
185 if (t
->isAnyCharacterType())
187 argType
= expr
->getArg(0)->IgnoreImplicit()->getType();
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();
198 if (tc
.RvalueReference().Struct("StringConcat").Namespace("rtl").GlobalNamespace())
200 argType
= expr
->getArg(0)->IgnoreImplicit()->getType();
201 extra
= ViaConcatenation
;
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)
226 argType
= expr
->getArg(0)->IgnoreImplicit()->getType();
230 if (loplugin::TypeCheck(d
->getParamDecl(1)->getType())
232 .Namespace("libreoffice_internal")
236 argType
= expr
->getArg(0)->IgnoreImplicit()->getType();
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",
250 << expr
->getType() << (argType
.isNull() ? 0 : 1) << argType
251 << (loplugin::TypeCheck(expr
->getType()).Class("OString").Namespace("rtl").GlobalNamespace()
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()",
268 << expr
->getSourceRange();
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()",
281 << expr
->getSourceRange();
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();
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",
308 << expr
->getSourceRange();
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",
320 << expr
->getSourceRange();
326 bool StringView::VisitCXXMemberCallExpr(CXXMemberCallExpr
const* expr
)
328 if (ignoreLocation(expr
))
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")
338 || loplugin::TypeCheck(expr
->getType())
339 .Class("OStringBuffer")
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());
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
))
382 if (!loplugin::TypeCheck(expr
->getType())
383 .Class("OUStringBuffer")
386 && !loplugin::TypeCheck(expr
->getType())
387 .Class("OStringBuffer")
393 if (!compiler
.getLangOpts().CPlusPlus17
&& expr
->isElidable()) // external C++03 code
397 if (expr
->getNumArgs() > 0)
398 handleSubExprThatCouldBeView(expr
->getArg(0));
402 loplugin::Plugin::Registration
<StringView
> stringview("stringview");
405 #endif // LO_CLANG_SHARED_PLUGINS
407 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */