Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / compilerplugins / clang / stringviewparam.cxx
blob9687bd8bbe2c119e9abc89f172f48f7e8f43736f
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 functions that take rtl::O[U]String parameters that can be generalized to take
21 // std::[u16]string_view instead.
23 //TODO: At least theoretically, there are issues with replacing parameters that are being assigned
24 // to, as in
26 // void f(OUString s) {
27 // {
28 // OUString t = ...;
29 // s = t;
30 // }
31 // ... use s ... // if s is now std::u16string_view, it points into destroyed contents of t
32 // }
34 namespace
36 bool hasSalDllpublicExportAttr(FunctionDecl const* decl)
38 if (auto const attr = decl->getAttr<VisibilityAttr>())
40 return attr->getVisibility() == VisibilityAttr::Default;
42 return decl->hasAttr<DLLExportAttr>();
45 enum class StringType
47 None,
48 RtlOstring,
49 RtlOustring
52 StringType relevantStringType(QualType type)
54 loplugin::TypeCheck const c(type);
55 if (c.Class("OString").Namespace("rtl"))
57 return StringType::RtlOstring;
59 else if (c.Class("OUString").Namespace("rtl"))
61 return StringType::RtlOustring;
63 else
65 return StringType::None;
69 bool relevantParmVarDecl(ParmVarDecl const* decl)
71 auto const t1 = decl->getType();
72 if (auto const t2 = t1->getAs<LValueReferenceType>())
74 if (!t2->getPointeeType().isConstQualified())
76 return false;
79 if (relevantStringType(t1.getNonReferenceType()) == StringType::None)
81 return false;
83 if (decl->hasAttr<UnusedAttr>())
85 return false;
87 return true;
90 DeclRefExpr const* relevantDeclRefExpr(Expr const* expr)
92 //TODO: Look through BO_Comma and AbstractConditionalOperator
93 auto const e = dyn_cast<DeclRefExpr>(expr->IgnoreParenImpCasts());
94 if (e == nullptr)
96 return nullptr;
98 auto const d = dyn_cast<ParmVarDecl>(e->getDecl());
99 if (d == nullptr)
101 return nullptr;
103 if (!relevantParmVarDecl(d))
105 return nullptr;
107 return e;
110 bool isStringView(QualType qt)
112 return bool(loplugin::TypeCheck(qt).ClassOrStruct("basic_string_view").StdNamespace());
115 DeclRefExpr const* relevantImplicitCastExpr(ImplicitCastExpr const* expr)
117 if (!isStringView(expr->getType()))
119 return nullptr;
121 return relevantDeclRefExpr(expr->getSubExprAsWritten());
124 DeclRefExpr const* relevantCXXMemberCallExpr(CXXMemberCallExpr const* expr)
126 StringType t = relevantStringType(expr->getObjectType());
127 if (t == StringType::None)
129 return nullptr;
131 bool good = false;
132 auto const d = expr->getMethodDecl();
133 if (d->getOverloadedOperator() == OO_Subscript)
135 good = true;
137 else if (auto const i = d->getIdentifier())
139 auto const n = i->getName();
140 if (!(n == "getLength" || n == "getStr" || n == "convertToString" || n == "replace"
141 || n == "replaceAll" || n == "replaceAt" || n == "replaceFirst"
142 || n == "toAsciiLowerCase" || n == "toAsciiUpperCase" || n == "toUtf8"
143 || n == "startsWithIgnoreAsciiCase" || n == "toUInt64" || n == "toFloat"
144 || n == "toBoolean"))
146 good = true;
148 #if 0
149 //TODO: rtl::O[U]String::getLength would be awkward to replace with
150 // std::[u16]string_view::length/size due to the sal_Int32 vs. std::size_t return type
151 // mismatch (C++20 ssize might make that easier, though); and while rtl::OString::getStr is
152 // documented to be NUL-terminated (so not eligible for replacement with
153 // std::string_view::data in general), rtl::OUString::getStr is not (so should be eligible
154 // for replacement with std::u16string_view::data, but some call sites might nevertheless
155 // incorrectly rely on NUL termination, so any replacement would need careful review):
156 if (n == "getLength" || (t == StringType::RtlOustring && n == "getStr"))
158 good = true;
160 #endif
162 if (!good)
164 return nullptr;
166 return relevantDeclRefExpr(expr->getImplicitObjectArgument());
169 SmallVector<DeclRefExpr const*, 2> wrap(DeclRefExpr const* expr)
171 if (expr == nullptr)
173 return {};
175 return { expr };
178 SmallVector<DeclRefExpr const*, 2> relevantCXXOperatorCallExpr(CXXOperatorCallExpr const* expr)
180 auto const op = expr->getOperator();
181 if (op == OO_Subscript)
183 auto const e = expr->getArg(0);
184 if (relevantStringType(e->getType()) == StringType::None)
186 return {};
188 return wrap(relevantDeclRefExpr(e));
190 if (expr->isComparisonOp() || (op == OO_Plus && expr->getNumArgs() == 2))
192 SmallVector<DeclRefExpr const*, 2> v;
193 if (auto const e = relevantDeclRefExpr(expr->getArg(0)))
195 v.push_back(e);
197 if (auto const e = relevantDeclRefExpr(expr->getArg(1)))
199 v.push_back(e);
201 return v;
203 if (op == OO_PlusEqual)
205 if (relevantStringType(expr->getArg(0)->getType()) != StringType::RtlOustring)
207 return {};
209 return wrap(relevantDeclRefExpr(expr->getArg(1)));
211 return {};
214 //TODO: current implementation is not at all general, just tests what we encounter in practice:
215 bool hasStringViewOverload(ParmVarDecl const* decl)
217 auto const d1 = cast<FunctionDecl>(decl->getDeclContext());
218 auto const ctx = d1->getDeclContext();
219 if (!ctx->isLookupContext())
221 return false;
223 auto const res = ctx->lookup(d1->getDeclName());
224 auto const idx = decl->getFunctionScopeIndex();
225 auto const n = d1->getNumParams();
226 assert(n > idx);
227 for (auto i = res.begin(); i != res.end(); ++i)
229 auto const d2 = dyn_cast<FunctionDecl>(*i);
230 if (d2 == nullptr)
232 continue;
234 if (d2->getNumParams() != n)
236 continue;
238 auto match = true;
239 for (unsigned j = 0; j != n; ++j)
241 if (j == idx)
243 //TODO: check for exactly std::string_view or std::u16string_view:
244 if (!isStringView(d2->getParamDecl(j)->getType()))
246 match = false;
247 break;
250 else if (d1->getParamDecl(j)->getType().getCanonicalType()
251 != d2->getParamDecl(j)->getType().getCanonicalType())
253 match = false;
254 break;
257 if (match)
259 return true;
262 return false;
265 class StringViewParam final
266 : public loplugin::FunctionAddress<loplugin::FilteringPlugin<StringViewParam>>
268 public:
269 explicit StringViewParam(loplugin::InstantiationData const& data)
270 : FunctionAddress(data)
274 //TODO: Also check lambdas
275 bool TraverseFunctionDecl(FunctionDecl* decl)
277 if (ignoreLocation(decl))
279 return true;
281 if (!relevantFunctionDecl(decl))
283 return FunctionAddress::TraverseFunctionDecl(decl);
285 auto const oldParams = currentParams_;
286 auto const n = decl->getNumParams();
287 for (unsigned i = 0; i != n; ++i)
289 auto const d = decl->getParamDecl(i);
290 if (relevantParmVarDecl(d))
292 currentParams_.insert(d);
295 auto const ret = FunctionAddress::TraverseFunctionDecl(decl);
296 if (ret)
298 for (unsigned i = 0; i != n; ++i)
300 auto const d1 = decl->getParamDecl(i);
301 if (currentParams_.find(d1) == currentParams_.end())
303 continue;
305 if (containsPreprocessingConditionalInclusion(decl->getSourceRange()))
307 break;
309 badParams_.push_back(d1);
312 currentParams_ = oldParams;
313 return ret;
316 bool TraverseCXXMethodDecl(CXXMethodDecl* decl)
318 if (ignoreLocation(decl))
320 return true;
322 if (!relevantFunctionDecl(decl))
324 return FunctionAddress::TraverseCXXMethodDecl(decl);
326 auto const oldParams = currentParams_;
327 auto const n = decl->getNumParams();
328 for (unsigned i = 0; i != n; ++i)
330 auto const d = decl->getParamDecl(i);
331 if (relevantParmVarDecl(d))
333 currentParams_.insert(d);
336 auto const ret = FunctionAddress::TraverseCXXMethodDecl(decl);
337 if (ret)
339 for (unsigned i = 0; i != n; ++i)
341 auto const d1 = decl->getParamDecl(i);
342 if (currentParams_.find(d1) == currentParams_.end())
344 continue;
346 if (containsPreprocessingConditionalInclusion(decl->getSourceRange()))
348 break;
350 badParams_.push_back(d1);
353 currentParams_ = oldParams;
354 return ret;
357 bool TraverseCXXConstructorDecl(CXXConstructorDecl* decl)
359 if (ignoreLocation(decl))
361 return true;
363 if (!relevantFunctionDecl(decl))
365 return FunctionAddress::TraverseCXXConstructorDecl(decl);
367 auto const oldParams = currentParams_;
368 auto const n = decl->getNumParams();
369 for (unsigned i = 0; i != n; ++i)
371 auto const d = decl->getParamDecl(i);
372 if (relevantParmVarDecl(d))
374 currentParams_.insert(d);
377 auto const ret = FunctionAddress::TraverseCXXConstructorDecl(decl);
378 if (ret)
380 for (unsigned i = 0; i != n; ++i)
382 auto const d1 = decl->getParamDecl(i);
383 if (currentParams_.find(d1) == currentParams_.end())
385 continue;
387 if (containsPreprocessingConditionalInclusion(decl->getSourceRange()))
389 break;
391 badParams_.push_back(d1);
394 currentParams_ = oldParams;
395 return ret;
398 bool TraverseImplicitCastExpr(ImplicitCastExpr* expr)
400 if (ignoreLocation(expr))
402 return true;
404 auto const e = relevantImplicitCastExpr(expr);
405 if (e == nullptr)
407 return FunctionAddress::TraverseImplicitCastExpr(expr);
409 currentGoodUses_.insert(e);
410 auto const ret = FunctionAddress::TraverseImplicitCastExpr(expr);
411 currentGoodUses_.erase(e);
412 return ret;
415 bool TraverseCXXMemberCallExpr(CXXMemberCallExpr* expr)
417 if (ignoreLocation(expr))
419 return true;
421 auto const e = relevantCXXMemberCallExpr(expr);
422 if (e == nullptr)
424 return FunctionAddress::TraverseCXXMemberCallExpr(expr);
426 currentGoodUses_.insert(e);
427 auto const ret = FunctionAddress::TraverseCXXMemberCallExpr(expr);
428 currentGoodUses_.erase(e);
429 return ret;
432 bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr* expr)
434 if (ignoreLocation(expr))
436 return true;
438 auto const es = relevantCXXOperatorCallExpr(expr);
439 if (es.empty())
441 return FunctionAddress::TraverseCXXOperatorCallExpr(expr);
443 currentGoodUses_.insert(es.begin(), es.end());
444 auto const ret = FunctionAddress::TraverseCXXOperatorCallExpr(expr);
445 for (auto const i : es)
447 currentGoodUses_.erase(i);
449 return ret;
452 bool VisitDeclRefExpr(DeclRefExpr* expr)
454 if (!FunctionAddress::VisitDeclRefExpr(expr))
456 return false;
458 if (ignoreLocation(expr))
460 return true;
462 if (currentGoodUses_.find(expr) != currentGoodUses_.end())
464 return true;
466 if (auto const d = dyn_cast<ParmVarDecl>(expr->getDecl()))
468 currentParams_.erase(d);
470 return true;
473 private:
474 void run() override
476 if (!compiler.getLangOpts().CPlusPlus)
478 return;
480 if (compiler.getPreprocessor().getIdentifierInfo("NDEBUG")->hasMacroDefinition())
482 return;
484 StringRef fn(handler.getMainFileName());
485 // leave the string QA tests alone
486 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/"))
488 return;
490 if (!TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()))
492 return;
494 auto const ignoredFns = getFunctionsWithAddressTaken();
495 for (auto const i : badParams_)
497 auto const d1 = cast<FunctionDecl>(i->getDeclContext());
498 if (ignoredFns.find(d1) != ignoredFns.end())
500 continue;
502 if (hasStringViewOverload(i))
504 continue;
506 auto const t = relevantStringType(i->getType().getNonReferenceType());
507 assert(t != StringType::None);
508 report(DiagnosticsEngine::Warning,
509 "replace function parameter of type %0 with "
510 "'%select{std::string_view|std::u16string_view}1'",
511 i->getLocation())
512 << i->getType() << (int(t) - 1) << i->getSourceRange();
513 for (auto d2 = d1;;)
515 d2 = d2->getPreviousDecl();
516 if (d2 == nullptr)
518 break;
520 auto const d3 = d2->getParamDecl(i->getFunctionScopeIndex());
521 report(DiagnosticsEngine::Note, "previous declaration is here", d3->getLocation())
522 << d3->getSourceRange();
527 bool relevantFunctionDecl(FunctionDecl const* decl)
529 if (!decl->doesThisDeclarationHaveABody())
531 return false;
533 if (decl->getBody() == nullptr) // unparsed template
535 return false;
537 if (auto const d = dyn_cast<CXXMethodDecl>(decl))
539 if (d->isVirtual())
541 return false;
544 if (decl->isFunctionTemplateSpecialization())
546 return false;
548 if (decl->getLocation().isMacroID())
550 return false;
552 // Filter out functions that are presumably meant to be called dynamically (e.g., via
553 // dlopen, or backwards compatibility stubs in cppuhelper/cppu/sal compat.cxx):
554 if (decl->getPreviousDecl() == nullptr && !decl->isInlined()
555 && hasSalDllpublicExportAttr(decl)
556 && compiler.getSourceManager().isInMainFile(decl->getLocation()))
558 return false;
560 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation())))
562 return false;
564 return true;
567 std::set<ParmVarDecl const*> currentParams_;
568 std::set<DeclRefExpr const*> currentGoodUses_;
569 std::vector<ParmVarDecl const*> badParams_;
572 static loplugin::Plugin::Registration<StringViewParam> reg("stringviewparam");
575 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */