1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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/.
14 #include "config_clang.h"
17 #include "functionaddress.hxx"
20 // Find various of type rtl::O[U]String that can be converted to std::[u16]string_view instead.
22 // This can generate false positives e.g.
23 // OUString sSave( aToken );
24 // aToken.append("foo");
26 // where the data that is backing the view is modified and then the view is used to assign to
27 // the same source data.
38 StringType
relevantStringType(QualType type
)
40 loplugin::TypeCheck
const c(type
);
41 if (c
.Class("OString").Namespace("rtl"))
43 return StringType::RtlOstring
;
45 else if (c
.Class("OUString").Namespace("rtl"))
47 return StringType::RtlOustring
;
51 return StringType::None
;
55 bool relevantVarDecl(VarDecl
const* decl
)
57 auto const t1
= decl
->getType();
58 if (relevantStringType(t1
.getNonReferenceType()) == StringType::None
)
62 if (isa
<ParmVarDecl
>(decl
))
69 DeclRefExpr
const* relevantDeclRefExpr(Expr
const* expr
)
71 //TODO: Look through BO_Comma and AbstractConditionalOperator
72 auto const e
= dyn_cast
<DeclRefExpr
>(expr
->IgnoreParenImpCasts());
77 auto const d
= dyn_cast
<VarDecl
>(e
->getDecl());
82 if (!relevantVarDecl(d
))
89 bool isStringView(QualType qt
)
91 return bool(loplugin::TypeCheck(qt
).ClassOrStruct("basic_string_view").StdNamespace());
94 DeclRefExpr
const* relevantImplicitCastExpr(ImplicitCastExpr
const* expr
)
96 if (!isStringView(expr
->getType()))
100 return relevantDeclRefExpr(expr
->getSubExprAsWritten());
103 DeclRefExpr
const* relevantCStyleCastExpr(CStyleCastExpr
const* expr
)
105 if (expr
->getCastKind() != CK_ToVoid
)
109 return relevantDeclRefExpr(expr
->getSubExprAsWritten());
112 DeclRefExpr
const* relevantCXXMemberCallExpr(CXXMemberCallExpr
const* expr
)
114 StringType t
= relevantStringType(expr
->getObjectType());
115 if (t
== StringType::None
)
120 auto const d
= expr
->getMethodDecl();
121 if (d
->getOverloadedOperator() == OO_Subscript
)
125 else if (auto const i
= d
->getIdentifier())
127 auto const n
= i
->getName();
128 if (n
== "endsWith" || n
== "isEmpty" || n
== "startsWith" || n
== "subView"
129 || n
== "indexOf" || n
== "lastIndexOf" || n
== "compareTo" || n
== "match"
130 || n
== "trim" || n
== "toInt32" || n
== "toInt64" || n
== "toDouble"
131 || n
== "equalsIgnoreAsciiCase" || n
== "compareToIgnoreAsciiCase" || n
== "getToken"
132 || n
== "copy" || n
== "iterateCodePoints")
137 //TODO: rtl::O[U]String::getLength would be awkward to replace with
138 // std::[u16]string_view::length/size due to the sal_Int32 vs. std::size_t return type
139 // mismatch (C++20 ssize might make that easier, though); and while rtl::OString::getStr is
140 // documented to be NUL-terminated (so not eligible for replacement with
141 // std::string_view::data in general), rtl::OUString::getStr is not (so should be eligible
142 // for replacement with std::u16string_view::data, but some call sites might nevertheless
143 // incorrectly rely on NUL termination, so any replacement would need careful review):
144 if (n
== "getLength" || (t
== StringType::RtlOustring
&& n
== "getStr"))
154 return relevantDeclRefExpr(expr
->getImplicitObjectArgument());
157 SmallVector
<DeclRefExpr
const*, 2> wrap(DeclRefExpr
const* expr
)
166 SmallVector
<DeclRefExpr
const*, 2> relevantCXXOperatorCallExpr(CXXOperatorCallExpr
const* expr
)
168 auto const op
= expr
->getOperator();
169 if (op
== OO_Subscript
)
171 auto const e
= expr
->getArg(0);
172 if (relevantStringType(e
->getType()) == StringType::None
)
176 return wrap(relevantDeclRefExpr(e
));
178 if (expr
->isComparisonOp() || (op
== OO_Plus
&& expr
->getNumArgs() == 2))
180 SmallVector
<DeclRefExpr
const*, 2> v
;
181 if (auto const e
= relevantDeclRefExpr(expr
->getArg(0)))
185 if (auto const e
= relevantDeclRefExpr(expr
->getArg(1)))
191 if (op
== OO_PlusEqual
)
193 if (relevantStringType(expr
->getArg(0)->getType()) != StringType::RtlOustring
)
197 return wrap(relevantDeclRefExpr(expr
->getArg(1)));
201 if (!isStringView(expr
->getArg(1)->getType()))
205 return wrap(relevantDeclRefExpr(expr
->getArg(0)));
210 static const Expr
* IgnoreImplicitAndConversionOperator(const Expr
* expr
)
212 expr
= expr
->IgnoreImplicit();
213 if (auto memberCall
= dyn_cast
<CXXMemberCallExpr
>(expr
))
215 if (auto conversionDecl
= dyn_cast_or_null
<CXXConversionDecl
>(memberCall
->getMethodDecl()))
217 if (!conversionDecl
->isExplicit())
218 expr
= memberCall
->getImplicitObjectArgument()->IgnoreImplicit();
224 class StringViewVar final
225 : public loplugin::FunctionAddress
<loplugin::FilteringPlugin
<StringViewVar
>>
228 explicit StringViewVar(loplugin::InstantiationData
const& data
)
229 : FunctionAddress(data
)
233 bool VisitVarDecl(VarDecl
* decl
)
235 if (ignoreLocation(decl
))
239 if (decl
->hasGlobalStorage())
243 if (!decl
->isThisDeclarationADefinition())
247 if (!relevantVarDecl(decl
))
253 auto expr
= IgnoreImplicitAndConversionOperator(decl
->getInit());
254 if (auto castExpr
= dyn_cast
<CXXFunctionalCastExpr
>(expr
))
256 expr
= IgnoreImplicitAndConversionOperator(castExpr
->getSubExpr());
258 if (auto cxxConstruct
= dyn_cast
<CXXConstructExpr
>(expr
))
260 if (cxxConstruct
->getNumArgs() == 0)
261 currentVars_
.insert(decl
); // default constructor
262 else if (cxxConstruct
->getNumArgs() == 1
263 && isStringView(cxxConstruct
->getArg(0)->getType()))
264 currentVars_
.insert(decl
);
270 bool TraverseImplicitCastExpr(ImplicitCastExpr
* expr
)
272 if (ignoreLocation(expr
))
276 auto const e
= relevantImplicitCastExpr(expr
);
279 return FunctionAddress::TraverseImplicitCastExpr(expr
);
281 currentGoodUses_
.insert(e
);
282 auto const ret
= FunctionAddress::TraverseImplicitCastExpr(expr
);
283 currentGoodUses_
.erase(e
);
287 bool TraverseCStyleCastExpr(CStyleCastExpr
* expr
)
289 if (ignoreLocation(expr
))
293 auto const e
= relevantCStyleCastExpr(expr
);
296 return FunctionAddress::TraverseCStyleCastExpr(expr
);
298 currentGoodUses_
.insert(e
);
299 auto const ret
= FunctionAddress::TraverseCStyleCastExpr(expr
);
300 currentGoodUses_
.erase(e
);
304 bool TraverseCXXMemberCallExpr(CXXMemberCallExpr
* expr
)
306 if (ignoreLocation(expr
))
310 auto const e
= relevantCXXMemberCallExpr(expr
);
313 return FunctionAddress::TraverseCXXMemberCallExpr(expr
);
315 currentGoodUses_
.insert(e
);
316 auto const ret
= FunctionAddress::TraverseCXXMemberCallExpr(expr
);
317 currentGoodUses_
.erase(e
);
321 bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr
* expr
)
323 if (ignoreLocation(expr
))
327 auto const es
= relevantCXXOperatorCallExpr(expr
);
330 return FunctionAddress::TraverseCXXOperatorCallExpr(expr
);
332 currentGoodUses_
.insert(es
.begin(), es
.end());
333 auto const ret
= FunctionAddress::TraverseCXXOperatorCallExpr(expr
);
334 for (auto const i
: es
)
336 currentGoodUses_
.erase(i
);
341 bool VisitDeclRefExpr(DeclRefExpr
* expr
)
343 if (!FunctionAddress::VisitDeclRefExpr(expr
))
347 if (ignoreLocation(expr
))
351 if (currentGoodUses_
.find(expr
) != currentGoodUses_
.end())
355 if (auto const d
= dyn_cast
<VarDecl
>(expr
->getDecl()))
357 currentVars_
.erase(d
);
365 if (!compiler
.getLangOpts().CPlusPlus
)
369 if (compiler
.getPreprocessor().getIdentifierInfo("NDEBUG")->hasMacroDefinition())
373 StringRef
fn(handler
.getMainFileName());
374 // leave the string QA tests alone
375 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/"))
380 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/svtools/source/svrtf/parrtf.cxx"))
384 if (!TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl()))
388 for (auto const i
: currentVars_
)
390 auto const t
= relevantStringType(i
->getType().getNonReferenceType());
391 report(DiagnosticsEngine::Warning
,
392 "replace var of type %0 with "
393 "'%select{std::string_view|std::u16string_view}1'",
395 << i
->getType() << (int(t
) - 1) << i
->getSourceRange();
399 std::set
<VarDecl
const*> currentVars_
;
400 std::set
<DeclRefExpr
const*> currentGoodUses_
;
403 static loplugin::Plugin::Registration
<StringViewVar
> reg("stringviewvar",
404 false); // might generate false+
407 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */