LanguageTool: don't crash if REST protocol isn't set
[LibreOffice.git] / compilerplugins / clang / stringviewparam.cxx
bloba500adf65d7c10263ed830936adad47ab2f6af7b
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 "check.hxx"
15 #include "compat.hxx"
16 #include "functionaddress.hxx"
17 #include "plugin.hxx"
19 // Find functions that take rtl::O[U]String parameters that can be generalized to take
20 // std::[u16]string_view instead.
22 //TODO: At least theoretically, there are issues with replacing parameters that are being assigned
23 // to, as in
25 // void f(OUString s) {
26 // {
27 // OUString t = ...;
28 // s = t;
29 // }
30 // ... use s ... // if s is now std::u16string_view, it points into destroyed contents of t
31 // }
33 namespace
35 bool hasSalDllpublicExportAttr(FunctionDecl const* decl)
37 if (auto const attr = decl->getAttr<VisibilityAttr>())
39 return attr->getVisibility() == VisibilityAttr::Default;
41 return decl->hasAttr<DLLExportAttr>();
44 enum class StringType
46 None,
47 RtlOstring,
48 RtlOustring
51 StringType relevantStringType(QualType type)
53 loplugin::TypeCheck const c(type);
54 if (c.Class("OString").Namespace("rtl"))
56 return StringType::RtlOstring;
58 else if (c.Class("OUString").Namespace("rtl"))
60 return StringType::RtlOustring;
62 else
64 return StringType::None;
68 bool relevantParmVarDecl(ParmVarDecl const* decl)
70 auto const t1 = decl->getType();
71 if (auto const t2 = t1->getAs<LValueReferenceType>())
73 if (!t2->getPointeeType().isConstQualified())
75 return false;
78 if (relevantStringType(t1.getNonReferenceType()) == StringType::None)
80 return false;
82 if (decl->hasAttr<UnusedAttr>())
84 return false;
86 return true;
89 DeclRefExpr const* relevantDeclRefExpr(Expr const* expr)
91 //TODO: Look through BO_Comma and AbstractConditionalOperator
92 auto const e = dyn_cast<DeclRefExpr>(expr->IgnoreParenImpCasts());
93 if (e == nullptr)
95 return nullptr;
97 auto const d = dyn_cast<ParmVarDecl>(e->getDecl());
98 if (d == nullptr)
100 return nullptr;
102 if (!relevantParmVarDecl(d))
104 return nullptr;
106 return e;
109 bool isStringView(QualType qt)
111 return bool(loplugin::TypeCheck(qt).ClassOrStruct("basic_string_view").StdNamespace());
114 DeclRefExpr const* relevantImplicitCastExpr(ImplicitCastExpr const* expr)
116 if (!isStringView(expr->getType()))
118 return nullptr;
120 return relevantDeclRefExpr(expr->getSubExprAsWritten());
123 DeclRefExpr const* relevantCXXMemberCallExpr(CXXMemberCallExpr const* expr)
125 StringType t = relevantStringType(compat::getObjectType(expr));
126 if (t == StringType::None)
128 return nullptr;
130 bool good = false;
131 auto const d = expr->getMethodDecl();
132 if (d->getOverloadedOperator() == OO_Subscript)
134 good = true;
136 else if (auto const i = d->getIdentifier())
138 auto const n = i->getName();
139 if (n == "endsWith" || n == "isEmpty" || n == "startsWith" || n == "subView")
141 good = true;
143 #if 0
144 //TODO: rtl::O[U]String::getLength would be awkward to replace with
145 // std::[u16]string_view::length/size due to the sal_Int32 vs. std::size_t return type
146 // mismatch (C++20 ssize might make that easier, though); and while rtl::OString::getStr is
147 // documented to be NUL-terminated (so not eligible for replacement with
148 // std::string_view::data in general), rtl::OUString::getStr is not (so should be eligible
149 // for replacement with std::u16string_view::data, but some call sites might nevertheless
150 // incorrectly rely on NUL termination, so any replacement would need careful review):
151 if (n == "getLength" || (t == StringType::RtlOustring && n == "getStr"))
153 good = true;
155 #endif
157 if (!good)
159 return nullptr;
161 return relevantDeclRefExpr(expr->getImplicitObjectArgument());
164 SmallVector<DeclRefExpr const*, 2> wrap(DeclRefExpr const* expr)
166 if (expr == nullptr)
168 return {};
170 return { expr };
173 SmallVector<DeclRefExpr const*, 2> relevantCXXOperatorCallExpr(CXXOperatorCallExpr const* expr)
175 auto const op = expr->getOperator();
176 if (op == OO_Subscript)
178 auto const e = expr->getArg(0);
179 if (relevantStringType(e->getType()) == StringType::None)
181 return {};
183 return wrap(relevantDeclRefExpr(e));
185 if (compat::isComparisonOp(expr) || (op == OO_Plus && expr->getNumArgs() == 2))
187 SmallVector<DeclRefExpr const*, 2> v;
188 if (auto const e = relevantDeclRefExpr(expr->getArg(0)))
190 v.push_back(e);
192 if (auto const e = relevantDeclRefExpr(expr->getArg(1)))
194 v.push_back(e);
196 return v;
198 if (op == OO_PlusEqual)
200 if (relevantStringType(expr->getArg(0)->getType()) != StringType::RtlOustring)
202 return {};
204 return wrap(relevantDeclRefExpr(expr->getArg(1)));
206 return {};
209 //TODO: current implementation is not at all general, just tests what we encounter in practice:
210 bool hasStringViewOverload(ParmVarDecl const* decl)
212 auto const d1 = cast<FunctionDecl>(decl->getDeclContext());
213 auto const ctx = d1->getDeclContext();
214 if (!ctx->isLookupContext())
216 return false;
218 auto const res = ctx->lookup(d1->getDeclName());
219 auto const idx = decl->getFunctionScopeIndex();
220 auto const n = d1->getNumParams();
221 assert(n > idx);
222 for (auto i = res.begin(); i != res.end(); ++i)
224 auto const d2 = dyn_cast<FunctionDecl>(*i);
225 if (d2 == nullptr)
227 continue;
229 if (d2->getNumParams() != n)
231 continue;
233 auto match = true;
234 for (unsigned j = 0; j != n; ++j)
236 if (j == idx)
238 //TODO: check for exactly std::string_view or std::u16string_view:
239 if (!isStringView(d2->getParamDecl(j)->getType()))
241 match = false;
242 break;
245 else if (d1->getParamDecl(j)->getType().getCanonicalType()
246 != d2->getParamDecl(j)->getType().getCanonicalType())
248 match = false;
249 break;
252 if (match)
254 return true;
257 return false;
260 class StringViewParam final
261 : public loplugin::FunctionAddress<loplugin::FilteringPlugin<StringViewParam>>
263 public:
264 explicit StringViewParam(loplugin::InstantiationData const& data)
265 : FunctionAddress(data)
269 //TODO: Also check lambdas
270 bool TraverseFunctionDecl(FunctionDecl* decl)
272 if (ignoreLocation(decl))
274 return true;
276 if (!relevantFunctionDecl(decl))
278 return FunctionAddress::TraverseFunctionDecl(decl);
280 auto const oldParams = currentParams_;
281 auto const n = decl->getNumParams();
282 for (unsigned i = 0; i != n; ++i)
284 auto const d = decl->getParamDecl(i);
285 if (relevantParmVarDecl(d))
287 currentParams_.insert(d);
290 auto const ret = FunctionAddress::TraverseFunctionDecl(decl);
291 if (ret)
293 for (unsigned i = 0; i != n; ++i)
295 auto const d1 = decl->getParamDecl(i);
296 if (currentParams_.find(d1) == currentParams_.end())
298 continue;
300 if (containsPreprocessingConditionalInclusion(decl->getSourceRange()))
302 break;
304 badParams_.push_back(d1);
307 currentParams_ = oldParams;
308 return ret;
311 bool TraverseCXXMethodDecl(CXXMethodDecl* decl)
313 if (ignoreLocation(decl))
315 return true;
317 if (!relevantFunctionDecl(decl))
319 return FunctionAddress::TraverseCXXMethodDecl(decl);
321 auto const oldParams = currentParams_;
322 auto const n = decl->getNumParams();
323 for (unsigned i = 0; i != n; ++i)
325 auto const d = decl->getParamDecl(i);
326 if (relevantParmVarDecl(d))
328 currentParams_.insert(d);
331 auto const ret = FunctionAddress::TraverseCXXMethodDecl(decl);
332 if (ret)
334 for (unsigned i = 0; i != n; ++i)
336 auto const d1 = decl->getParamDecl(i);
337 if (currentParams_.find(d1) == currentParams_.end())
339 continue;
341 if (containsPreprocessingConditionalInclusion(decl->getSourceRange()))
343 break;
345 badParams_.push_back(d1);
348 currentParams_ = oldParams;
349 return ret;
352 bool TraverseCXXConstructorDecl(CXXConstructorDecl* decl)
354 if (ignoreLocation(decl))
356 return true;
358 if (!relevantFunctionDecl(decl))
360 return FunctionAddress::TraverseCXXConstructorDecl(decl);
362 auto const oldParams = currentParams_;
363 auto const n = decl->getNumParams();
364 for (unsigned i = 0; i != n; ++i)
366 auto const d = decl->getParamDecl(i);
367 if (relevantParmVarDecl(d))
369 currentParams_.insert(d);
372 auto const ret = FunctionAddress::TraverseCXXConstructorDecl(decl);
373 if (ret)
375 for (unsigned i = 0; i != n; ++i)
377 auto const d1 = decl->getParamDecl(i);
378 if (currentParams_.find(d1) == currentParams_.end())
380 continue;
382 if (containsPreprocessingConditionalInclusion(decl->getSourceRange()))
384 break;
386 badParams_.push_back(d1);
389 currentParams_ = oldParams;
390 return ret;
393 bool TraverseImplicitCastExpr(ImplicitCastExpr* expr)
395 if (ignoreLocation(expr))
397 return true;
399 auto const e = relevantImplicitCastExpr(expr);
400 if (e == nullptr)
402 return FunctionAddress::TraverseImplicitCastExpr(expr);
404 currentGoodUses_.insert(e);
405 auto const ret = FunctionAddress::TraverseImplicitCastExpr(expr);
406 currentGoodUses_.erase(e);
407 return ret;
410 bool TraverseCXXMemberCallExpr(CXXMemberCallExpr* expr)
412 if (ignoreLocation(expr))
414 return true;
416 auto const e = relevantCXXMemberCallExpr(expr);
417 if (e == nullptr)
419 return FunctionAddress::TraverseCXXMemberCallExpr(expr);
421 currentGoodUses_.insert(e);
422 auto const ret = FunctionAddress::TraverseCXXMemberCallExpr(expr);
423 currentGoodUses_.erase(e);
424 return ret;
427 bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr* expr)
429 if (ignoreLocation(expr))
431 return true;
433 auto const es = relevantCXXOperatorCallExpr(expr);
434 if (es.empty())
436 return FunctionAddress::TraverseCXXOperatorCallExpr(expr);
438 currentGoodUses_.insert(es.begin(), es.end());
439 auto const ret = FunctionAddress::TraverseCXXOperatorCallExpr(expr);
440 for (auto const i : es)
442 currentGoodUses_.erase(i);
444 return ret;
447 bool VisitDeclRefExpr(DeclRefExpr* expr)
449 if (!FunctionAddress::VisitDeclRefExpr(expr))
451 return false;
453 if (ignoreLocation(expr))
455 return true;
457 if (currentGoodUses_.find(expr) != currentGoodUses_.end())
459 return true;
461 if (auto const d = dyn_cast<ParmVarDecl>(expr->getDecl()))
463 currentParams_.erase(d);
465 return true;
468 private:
469 void run() override
471 if (!compiler.getLangOpts().CPlusPlus)
473 return;
475 if (compiler.getPreprocessor().getIdentifierInfo("NDEBUG")->hasMacroDefinition())
477 return;
479 StringRef fn(handler.getMainFileName());
480 // leave the string QA tests alone
481 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/"))
483 return;
485 if (!TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()))
487 return;
489 auto const ignoredFns = getFunctionsWithAddressTaken();
490 for (auto const i : badParams_)
492 auto const d1 = cast<FunctionDecl>(i->getDeclContext());
493 if (ignoredFns.find(d1) != ignoredFns.end())
495 continue;
497 if (hasStringViewOverload(i))
499 continue;
501 auto const t = relevantStringType(i->getType().getNonReferenceType());
502 assert(t != StringType::None);
503 report(DiagnosticsEngine::Warning,
504 "replace function parameter of type %0 with "
505 "'%select{std::string_view|std::u16string_view}1'",
506 i->getLocation())
507 << i->getType() << (int(t) - 1) << i->getSourceRange();
508 for (auto d2 = d1;;)
510 d2 = d2->getPreviousDecl();
511 if (d2 == nullptr)
513 break;
515 auto const d3 = d2->getParamDecl(i->getFunctionScopeIndex());
516 report(DiagnosticsEngine::Note, "previous declaration is here", d3->getLocation())
517 << d3->getSourceRange();
522 bool relevantFunctionDecl(FunctionDecl const* decl)
524 if (!decl->doesThisDeclarationHaveABody())
526 return false;
528 if (decl->getBody() == nullptr) // unparsed template
530 return false;
532 if (auto const d = dyn_cast<CXXMethodDecl>(decl))
534 if (d->isVirtual())
536 return false;
539 if (decl->isFunctionTemplateSpecialization())
541 return false;
543 if (decl->getLocation().isMacroID())
545 return false;
547 // Filter out functions that are presumably meant to be called dynamically (e.g., via
548 // dlopen, or backwards compatibility stubs in cppuhelper/cppu/sal compat.cxx):
549 if (decl->getPreviousDecl() == nullptr && !decl->isInlined()
550 && hasSalDllpublicExportAttr(decl)
551 && compiler.getSourceManager().isInMainFile(decl->getLocation()))
553 return false;
555 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation())))
557 return false;
559 return true;
562 std::set<ParmVarDecl const*> currentParams_;
563 std::set<DeclRefExpr const*> currentGoodUses_;
564 std::vector<ParmVarDecl const*> badParams_;
567 static loplugin::Plugin::Registration<StringViewParam> reg("stringviewparam");
570 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */