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 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
26 // void f(OUString s) {
31 // ... use s ... // if s is now std::u16string_view, it points into destroyed contents of t
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
>();
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
;
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())
79 if (relevantStringType(t1
.getNonReferenceType()) == StringType::None
)
83 if (decl
->hasAttr
<UnusedAttr
>())
90 DeclRefExpr
const* relevantDeclRefExpr(Expr
const* expr
)
92 //TODO: Look through BO_Comma and AbstractConditionalOperator
93 auto const e
= dyn_cast
<DeclRefExpr
>(expr
->IgnoreParenImpCasts());
98 auto const d
= dyn_cast
<ParmVarDecl
>(e
->getDecl());
103 if (!relevantParmVarDecl(d
))
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()))
121 return relevantDeclRefExpr(expr
->getSubExprAsWritten());
124 DeclRefExpr
const* relevantCXXMemberCallExpr(CXXMemberCallExpr
const* expr
)
126 StringType t
= relevantStringType(expr
->getObjectType());
127 if (t
== StringType::None
)
132 auto const d
= expr
->getMethodDecl();
133 if (d
->getOverloadedOperator() == OO_Subscript
)
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"))
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"))
166 return relevantDeclRefExpr(expr
->getImplicitObjectArgument());
169 SmallVector
<DeclRefExpr
const*, 2> wrap(DeclRefExpr
const* 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
)
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)))
197 if (auto const e
= relevantDeclRefExpr(expr
->getArg(1)))
203 if (op
== OO_PlusEqual
)
205 if (relevantStringType(expr
->getArg(0)->getType()) != StringType::RtlOustring
)
209 return wrap(relevantDeclRefExpr(expr
->getArg(1)));
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())
223 auto const res
= ctx
->lookup(d1
->getDeclName());
224 auto const idx
= decl
->getFunctionScopeIndex();
225 auto const n
= d1
->getNumParams();
227 for (auto i
= res
.begin(); i
!= res
.end(); ++i
)
229 auto const d2
= dyn_cast
<FunctionDecl
>(*i
);
234 if (d2
->getNumParams() != n
)
239 for (unsigned j
= 0; j
!= n
; ++j
)
243 //TODO: check for exactly std::string_view or std::u16string_view:
244 if (!isStringView(d2
->getParamDecl(j
)->getType()))
250 else if (d1
->getParamDecl(j
)->getType().getCanonicalType()
251 != d2
->getParamDecl(j
)->getType().getCanonicalType())
265 class StringViewParam final
266 : public loplugin::FunctionAddress
<loplugin::FilteringPlugin
<StringViewParam
>>
269 explicit StringViewParam(loplugin::InstantiationData
const& data
)
270 : FunctionAddress(data
)
274 //TODO: Also check lambdas
275 bool TraverseFunctionDecl(FunctionDecl
* decl
)
277 if (ignoreLocation(decl
))
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
);
298 for (unsigned i
= 0; i
!= n
; ++i
)
300 auto const d1
= decl
->getParamDecl(i
);
301 if (currentParams_
.find(d1
) == currentParams_
.end())
305 if (containsPreprocessingConditionalInclusion(decl
->getSourceRange()))
309 badParams_
.push_back(d1
);
312 currentParams_
= oldParams
;
316 bool TraverseCXXMethodDecl(CXXMethodDecl
* decl
)
318 if (ignoreLocation(decl
))
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
);
339 for (unsigned i
= 0; i
!= n
; ++i
)
341 auto const d1
= decl
->getParamDecl(i
);
342 if (currentParams_
.find(d1
) == currentParams_
.end())
346 if (containsPreprocessingConditionalInclusion(decl
->getSourceRange()))
350 badParams_
.push_back(d1
);
353 currentParams_
= oldParams
;
357 bool TraverseCXXConstructorDecl(CXXConstructorDecl
* decl
)
359 if (ignoreLocation(decl
))
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
);
380 for (unsigned i
= 0; i
!= n
; ++i
)
382 auto const d1
= decl
->getParamDecl(i
);
383 if (currentParams_
.find(d1
) == currentParams_
.end())
387 if (containsPreprocessingConditionalInclusion(decl
->getSourceRange()))
391 badParams_
.push_back(d1
);
394 currentParams_
= oldParams
;
398 bool TraverseImplicitCastExpr(ImplicitCastExpr
* expr
)
400 if (ignoreLocation(expr
))
404 auto const e
= relevantImplicitCastExpr(expr
);
407 return FunctionAddress::TraverseImplicitCastExpr(expr
);
409 currentGoodUses_
.insert(e
);
410 auto const ret
= FunctionAddress::TraverseImplicitCastExpr(expr
);
411 currentGoodUses_
.erase(e
);
415 bool TraverseCXXMemberCallExpr(CXXMemberCallExpr
* expr
)
417 if (ignoreLocation(expr
))
421 auto const e
= relevantCXXMemberCallExpr(expr
);
424 return FunctionAddress::TraverseCXXMemberCallExpr(expr
);
426 currentGoodUses_
.insert(e
);
427 auto const ret
= FunctionAddress::TraverseCXXMemberCallExpr(expr
);
428 currentGoodUses_
.erase(e
);
432 bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr
* expr
)
434 if (ignoreLocation(expr
))
438 auto const es
= relevantCXXOperatorCallExpr(expr
);
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
);
452 bool VisitDeclRefExpr(DeclRefExpr
* expr
)
454 if (!FunctionAddress::VisitDeclRefExpr(expr
))
458 if (ignoreLocation(expr
))
462 if (currentGoodUses_
.find(expr
) != currentGoodUses_
.end())
466 if (auto const d
= dyn_cast
<ParmVarDecl
>(expr
->getDecl()))
468 currentParams_
.erase(d
);
476 if (!compiler
.getLangOpts().CPlusPlus
)
480 if (compiler
.getPreprocessor().getIdentifierInfo("NDEBUG")->hasMacroDefinition())
484 StringRef
fn(handler
.getMainFileName());
485 // leave the string QA tests alone
486 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/qa/"))
490 if (!TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl()))
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())
502 if (hasStringViewOverload(i
))
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'",
512 << i
->getType() << (int(t
) - 1) << i
->getSourceRange();
515 d2
= d2
->getPreviousDecl();
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())
533 if (decl
->getBody() == nullptr) // unparsed template
537 if (auto const d
= dyn_cast
<CXXMethodDecl
>(decl
))
544 if (decl
->isFunctionTemplateSpecialization())
548 if (decl
->getLocation().isMacroID())
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()))
560 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(decl
->getLocation())))
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: */