Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / compilerplugins / clang / stringviewvar.cxx
blob5acc09d3fe7851f5e3e9ecdbf4c7c2ec3e8bcf97
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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 */
10 #include <cassert>
11 #include <set>
12 #include <vector>
14 #include "config_clang.h"
16 #include "check.hxx"
17 #include "functionaddress.hxx"
18 #include "plugin.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");
25 // aToken = sSave;
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.
29 namespace
31 enum class StringType
33 None,
34 RtlOstring,
35 RtlOustring
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;
49 else
51 return StringType::None;
55 bool relevantVarDecl(VarDecl const* decl)
57 auto const t1 = decl->getType();
58 if (relevantStringType(t1.getNonReferenceType()) == StringType::None)
60 return false;
62 if (isa<ParmVarDecl>(decl))
64 return false;
66 return true;
69 DeclRefExpr const* relevantDeclRefExpr(Expr const* expr)
71 //TODO: Look through BO_Comma and AbstractConditionalOperator
72 auto const e = dyn_cast<DeclRefExpr>(expr->IgnoreParenImpCasts());
73 if (e == nullptr)
75 return nullptr;
77 auto const d = dyn_cast<VarDecl>(e->getDecl());
78 if (d == nullptr)
80 return nullptr;
82 if (!relevantVarDecl(d))
84 return nullptr;
86 return e;
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()))
98 return nullptr;
100 return relevantDeclRefExpr(expr->getSubExprAsWritten());
103 DeclRefExpr const* relevantCStyleCastExpr(CStyleCastExpr const* expr)
105 if (expr->getCastKind() != CK_ToVoid)
107 return nullptr;
109 return relevantDeclRefExpr(expr->getSubExprAsWritten());
112 DeclRefExpr const* relevantCXXMemberCallExpr(CXXMemberCallExpr const* expr)
114 StringType t = relevantStringType(expr->getObjectType());
115 if (t == StringType::None)
117 return nullptr;
119 bool good = false;
120 auto const d = expr->getMethodDecl();
121 if (d->getOverloadedOperator() == OO_Subscript)
123 good = true;
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")
134 good = true;
136 #if 0
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"))
146 good = true;
148 #endif
150 if (!good)
152 return nullptr;
154 return relevantDeclRefExpr(expr->getImplicitObjectArgument());
157 SmallVector<DeclRefExpr const*, 2> wrap(DeclRefExpr const* expr)
159 if (expr == nullptr)
161 return {};
163 return { 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)
174 return {};
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)))
183 v.push_back(e);
185 if (auto const e = relevantDeclRefExpr(expr->getArg(1)))
187 v.push_back(e);
189 return v;
191 if (op == OO_PlusEqual)
193 if (relevantStringType(expr->getArg(0)->getType()) != StringType::RtlOustring)
195 return {};
197 return wrap(relevantDeclRefExpr(expr->getArg(1)));
199 if (op == OO_Equal)
201 if (!isStringView(expr->getArg(1)->getType()))
203 return {};
205 return wrap(relevantDeclRefExpr(expr->getArg(0)));
207 return {};
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();
221 return expr;
224 class StringViewVar final
225 : public loplugin::FunctionAddress<loplugin::FilteringPlugin<StringViewVar>>
227 public:
228 explicit StringViewVar(loplugin::InstantiationData const& data)
229 : FunctionAddress(data)
233 bool VisitVarDecl(VarDecl* decl)
235 if (ignoreLocation(decl))
237 return true;
239 if (decl->hasGlobalStorage())
241 return true;
243 if (!decl->isThisDeclarationADefinition())
245 return true;
247 if (!relevantVarDecl(decl))
249 return true;
251 if (decl->getInit())
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);
267 return true;
270 bool TraverseImplicitCastExpr(ImplicitCastExpr* expr)
272 if (ignoreLocation(expr))
274 return true;
276 auto const e = relevantImplicitCastExpr(expr);
277 if (e == nullptr)
279 return FunctionAddress::TraverseImplicitCastExpr(expr);
281 currentGoodUses_.insert(e);
282 auto const ret = FunctionAddress::TraverseImplicitCastExpr(expr);
283 currentGoodUses_.erase(e);
284 return ret;
287 bool TraverseCStyleCastExpr(CStyleCastExpr* expr)
289 if (ignoreLocation(expr))
291 return true;
293 auto const e = relevantCStyleCastExpr(expr);
294 if (e == nullptr)
296 return FunctionAddress::TraverseCStyleCastExpr(expr);
298 currentGoodUses_.insert(e);
299 auto const ret = FunctionAddress::TraverseCStyleCastExpr(expr);
300 currentGoodUses_.erase(e);
301 return ret;
304 bool TraverseCXXMemberCallExpr(CXXMemberCallExpr* expr)
306 if (ignoreLocation(expr))
308 return true;
310 auto const e = relevantCXXMemberCallExpr(expr);
311 if (e == nullptr)
313 return FunctionAddress::TraverseCXXMemberCallExpr(expr);
315 currentGoodUses_.insert(e);
316 auto const ret = FunctionAddress::TraverseCXXMemberCallExpr(expr);
317 currentGoodUses_.erase(e);
318 return ret;
321 bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr* expr)
323 if (ignoreLocation(expr))
325 return true;
327 auto const es = relevantCXXOperatorCallExpr(expr);
328 if (es.empty())
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);
338 return ret;
341 bool VisitDeclRefExpr(DeclRefExpr* expr)
343 if (!FunctionAddress::VisitDeclRefExpr(expr))
345 return false;
347 if (ignoreLocation(expr))
349 return true;
351 if (currentGoodUses_.find(expr) != currentGoodUses_.end())
353 return true;
355 if (auto const d = dyn_cast<VarDecl>(expr->getDecl()))
357 currentVars_.erase(d);
359 return true;
362 private:
363 void run() override
365 if (!compiler.getLangOpts().CPlusPlus)
367 return;
369 if (compiler.getPreprocessor().getIdentifierInfo("NDEBUG")->hasMacroDefinition())
371 return;
373 StringRef fn(handler.getMainFileName());
374 // leave the string QA tests alone
375 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/"))
377 return;
379 // false +
380 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/svtools/source/svrtf/parrtf.cxx"))
382 return;
384 if (!TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()))
386 return;
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'",
394 i->getLocation())
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: */