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/.
16 #include "functionaddress.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
25 // void f(OUString s) {
30 // ... use s ... // if s is now std::u16string_view, it points into destroyed contents of t
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
>();
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
;
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())
78 if (relevantStringType(t1
.getNonReferenceType()) == StringType::None
)
82 if (decl
->hasAttr
<UnusedAttr
>())
89 DeclRefExpr
const* relevantDeclRefExpr(Expr
const* expr
)
91 //TODO: Look through BO_Comma and AbstractConditionalOperator
92 auto const e
= dyn_cast
<DeclRefExpr
>(expr
->IgnoreParenImpCasts());
97 auto const d
= dyn_cast
<ParmVarDecl
>(e
->getDecl());
102 if (!relevantParmVarDecl(d
))
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()))
120 return relevantDeclRefExpr(expr
->getSubExprAsWritten());
123 DeclRefExpr
const* relevantCXXMemberCallExpr(CXXMemberCallExpr
const* expr
)
125 StringType t
= relevantStringType(compat::getObjectType(expr
));
126 if (t
== StringType::None
)
131 auto const d
= expr
->getMethodDecl();
132 if (d
->getOverloadedOperator() == OO_Subscript
)
136 else if (auto const i
= d
->getIdentifier())
138 auto const n
= i
->getName();
139 if (n
== "endsWith" || n
== "isEmpty" || n
== "startsWith" || n
== "subView")
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"))
161 return relevantDeclRefExpr(expr
->getImplicitObjectArgument());
164 SmallVector
<DeclRefExpr
const*, 2> wrap(DeclRefExpr
const* 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
)
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)))
192 if (auto const e
= relevantDeclRefExpr(expr
->getArg(1)))
198 if (op
== OO_PlusEqual
)
200 if (relevantStringType(expr
->getArg(0)->getType()) != StringType::RtlOustring
)
204 return wrap(relevantDeclRefExpr(expr
->getArg(1)));
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())
218 auto const res
= ctx
->lookup(d1
->getDeclName());
219 auto const idx
= decl
->getFunctionScopeIndex();
220 auto const n
= d1
->getNumParams();
222 for (auto i
= res
.begin(); i
!= res
.end(); ++i
)
224 auto const d2
= dyn_cast
<FunctionDecl
>(*i
);
229 if (d2
->getNumParams() != n
)
234 for (unsigned j
= 0; j
!= n
; ++j
)
238 //TODO: check for exactly std::string_view or std::u16string_view:
239 if (!isStringView(d2
->getParamDecl(j
)->getType()))
245 else if (d1
->getParamDecl(j
)->getType().getCanonicalType()
246 != d2
->getParamDecl(j
)->getType().getCanonicalType())
260 class StringViewParam final
261 : public loplugin::FunctionAddress
<loplugin::FilteringPlugin
<StringViewParam
>>
264 explicit StringViewParam(loplugin::InstantiationData
const& data
)
265 : FunctionAddress(data
)
269 //TODO: Also check lambdas
270 bool TraverseFunctionDecl(FunctionDecl
* decl
)
272 if (ignoreLocation(decl
))
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
);
293 for (unsigned i
= 0; i
!= n
; ++i
)
295 auto const d1
= decl
->getParamDecl(i
);
296 if (currentParams_
.find(d1
) == currentParams_
.end())
300 if (containsPreprocessingConditionalInclusion(decl
->getSourceRange()))
304 badParams_
.push_back(d1
);
307 currentParams_
= oldParams
;
311 bool TraverseCXXMethodDecl(CXXMethodDecl
* decl
)
313 if (ignoreLocation(decl
))
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
);
334 for (unsigned i
= 0; i
!= n
; ++i
)
336 auto const d1
= decl
->getParamDecl(i
);
337 if (currentParams_
.find(d1
) == currentParams_
.end())
341 if (containsPreprocessingConditionalInclusion(decl
->getSourceRange()))
345 badParams_
.push_back(d1
);
348 currentParams_
= oldParams
;
352 bool TraverseCXXConstructorDecl(CXXConstructorDecl
* decl
)
354 if (ignoreLocation(decl
))
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
);
375 for (unsigned i
= 0; i
!= n
; ++i
)
377 auto const d1
= decl
->getParamDecl(i
);
378 if (currentParams_
.find(d1
) == currentParams_
.end())
382 if (containsPreprocessingConditionalInclusion(decl
->getSourceRange()))
386 badParams_
.push_back(d1
);
389 currentParams_
= oldParams
;
393 bool TraverseImplicitCastExpr(ImplicitCastExpr
* expr
)
395 if (ignoreLocation(expr
))
399 auto const e
= relevantImplicitCastExpr(expr
);
402 return FunctionAddress::TraverseImplicitCastExpr(expr
);
404 currentGoodUses_
.insert(e
);
405 auto const ret
= FunctionAddress::TraverseImplicitCastExpr(expr
);
406 currentGoodUses_
.erase(e
);
410 bool TraverseCXXMemberCallExpr(CXXMemberCallExpr
* expr
)
412 if (ignoreLocation(expr
))
416 auto const e
= relevantCXXMemberCallExpr(expr
);
419 return FunctionAddress::TraverseCXXMemberCallExpr(expr
);
421 currentGoodUses_
.insert(e
);
422 auto const ret
= FunctionAddress::TraverseCXXMemberCallExpr(expr
);
423 currentGoodUses_
.erase(e
);
427 bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr
* expr
)
429 if (ignoreLocation(expr
))
433 auto const es
= relevantCXXOperatorCallExpr(expr
);
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
);
447 bool VisitDeclRefExpr(DeclRefExpr
* expr
)
449 if (!FunctionAddress::VisitDeclRefExpr(expr
))
453 if (ignoreLocation(expr
))
457 if (currentGoodUses_
.find(expr
) != currentGoodUses_
.end())
461 if (auto const d
= dyn_cast
<ParmVarDecl
>(expr
->getDecl()))
463 currentParams_
.erase(d
);
471 if (!compiler
.getLangOpts().CPlusPlus
)
475 if (compiler
.getPreprocessor().getIdentifierInfo("NDEBUG")->hasMacroDefinition())
479 StringRef
fn(handler
.getMainFileName());
480 // leave the string QA tests alone
481 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/"))
485 if (!TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl()))
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())
497 if (hasStringViewOverload(i
))
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'",
507 << i
->getType() << (int(t
) - 1) << i
->getSourceRange();
510 d2
= d2
->getPreviousDecl();
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())
528 if (decl
->getBody() == nullptr) // unparsed template
532 if (auto const d
= dyn_cast
<CXXMethodDecl
>(decl
))
539 if (decl
->isFunctionTemplateSpecialization())
543 if (decl
->getLocation().isMacroID())
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()))
555 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(decl
->getLocation())))
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: */