1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
26 // Define a "string constant" to be a constant expression either of type "array
27 // of N char" where each array element is a non-NULL ASCII character---except
28 // that the last array element may be NULL, or, in some situations, of type char
29 // with an ASCII value (including NULL). Note that the former includes
30 // expressions denoting narrow string literals like "foo", and, with toolchains
31 // that support constexpr, constexpr variables declared like
33 // constexpr char str[] = "bar";
35 // This plugin flags uses of OUString functions with string constant arguments
36 // that can be rewritten more directly, like
38 // OUString::createFromAscii("foo") -> "foo"
42 // s.equals(OUString("bar")) -> s == "bar"
46 SourceLocation
getMemberLocation(Expr
const * expr
) {
47 CallExpr
const * e1
= dyn_cast
<CallExpr
>(expr
);
48 MemberExpr
const * e2
= e1
== nullptr
49 ? nullptr : dyn_cast
<MemberExpr
>(e1
->getCallee());
50 return e2
== nullptr ? expr
->getExprLoc()/*TODO*/ : e2
->getMemberLoc();
53 bool isLhsOfAssignment(FunctionDecl
const * decl
, unsigned parameter
) {
57 auto oo
= decl
->getOverloadedOperator();
59 || (oo
>= OO_PlusEqual
&& oo
<= OO_GreaterGreaterEqual
);
62 bool hasOverloads(FunctionDecl
const * decl
, unsigned arguments
) {
64 auto ctx
= decl
->getDeclContext();
65 if (ctx
->getDeclKind() == Decl::LinkageSpec
) {
66 ctx
= ctx
->getParent();
68 auto res
= ctx
->lookup(decl
->getDeclName());
69 for (auto d
= res
.begin(); d
!= res
.end(); ++d
) {
70 FunctionDecl
const * f
= dyn_cast
<FunctionDecl
>(*d
);
71 if (f
!= nullptr && f
->getMinRequiredArguments() <= arguments
72 && f
->getNumParams() >= arguments
)
74 auto consDecl
= dyn_cast
<CXXConstructorDecl
>(f
);
75 if (consDecl
&& consDecl
->isCopyOrMoveConstructor()) {
87 CXXConstructExpr
const * lookForCXXConstructExpr(Expr
const * expr
) {
88 if (auto e
= dyn_cast
<MaterializeTemporaryExpr
>(expr
)) {
89 expr
= e
->getSubExpr();
91 if (auto e
= dyn_cast
<CXXFunctionalCastExpr
>(expr
)) {
92 expr
= e
->getSubExpr();
94 if (auto e
= dyn_cast
<CXXBindTemporaryExpr
>(expr
)) {
95 expr
= e
->getSubExpr();
97 if (auto const e
= dyn_cast
<CXXMemberCallExpr
>(expr
)) {
98 // Look through OString::operator std::string_view:
99 if (auto const d
= dyn_cast_or_null
<CXXConversionDecl
>(e
->getCalleeDecl())) {
100 return lookForCXXConstructExpr(e
->getImplicitObjectArgument()->IgnoreParenImpCasts());
103 return dyn_cast
<CXXConstructExpr
>(expr
);
106 char const * adviseNonArray(bool nonArray
) {
108 ? ", and turn the non-array string constant into an array" : "";
111 class StringConstant
:
112 public loplugin::FilteringRewritePlugin
<StringConstant
>
115 explicit StringConstant(loplugin::InstantiationData
const & data
):
116 FilteringRewritePlugin(data
) {}
120 bool TraverseFunctionDecl(FunctionDecl
* decl
) {
121 returnTypes_
.push(decl
->getDeclaredReturnType());
122 auto const ret
= RecursiveASTVisitor::TraverseFunctionDecl(decl
);
123 assert(!returnTypes_
.empty());
124 assert(returnTypes_
.top() == decl
->getDeclaredReturnType());
129 bool TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl
* decl
) {
130 returnTypes_
.push(decl
->getDeclaredReturnType());
131 auto const ret
= RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(
133 assert(!returnTypes_
.empty());
134 assert(returnTypes_
.top() == decl
->getDeclaredReturnType());
139 bool TraverseCXXMethodDecl(CXXMethodDecl
* decl
) {
140 returnTypes_
.push(decl
->getDeclaredReturnType());
141 auto const ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(decl
);
142 assert(!returnTypes_
.empty());
143 assert(returnTypes_
.top() == decl
->getDeclaredReturnType());
148 bool TraverseCXXConstructorDecl(CXXConstructorDecl
* decl
) {
149 returnTypes_
.push(decl
->getDeclaredReturnType());
150 auto const ret
= RecursiveASTVisitor::TraverseCXXConstructorDecl(decl
);
151 assert(!returnTypes_
.empty());
152 assert(returnTypes_
.top() == decl
->getDeclaredReturnType());
157 bool TraverseCXXDestructorDecl(CXXDestructorDecl
* decl
) {
158 returnTypes_
.push(decl
->getDeclaredReturnType());
159 auto const ret
= RecursiveASTVisitor::TraverseCXXDestructorDecl(decl
);
160 assert(!returnTypes_
.empty());
161 assert(returnTypes_
.top() == decl
->getDeclaredReturnType());
166 bool TraverseCXXConversionDecl(CXXConversionDecl
* decl
) {
167 returnTypes_
.push(decl
->getDeclaredReturnType());
168 auto const ret
= RecursiveASTVisitor::TraverseCXXConversionDecl(decl
);
169 assert(!returnTypes_
.empty());
170 assert(returnTypes_
.top() == decl
->getDeclaredReturnType());
175 bool TraverseObjCMethodDecl(ObjCMethodDecl
* decl
) {
176 returnTypes_
.push(decl
->getReturnType());
177 auto const ret
= RecursiveASTVisitor::TraverseObjCMethodDecl(decl
);
178 assert(!returnTypes_
.empty());
179 assert(returnTypes_
.top() == decl
->getReturnType());
184 bool TraverseCallExpr(CallExpr
* expr
);
186 bool TraverseCXXMemberCallExpr(CXXMemberCallExpr
* expr
);
188 bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr
* expr
);
190 bool TraverseCXXConstructExpr(CXXConstructExpr
* expr
);
192 bool VisitCallExpr(CallExpr
const * expr
);
194 bool VisitCXXConstructExpr(CXXConstructExpr
const * expr
);
196 bool VisitReturnStmt(ReturnStmt
const * stmt
);
199 enum class ContentKind
{ Ascii
, Utf8
, Arbitrary
};
201 enum class TreatEmpty
{ DefaultCtor
, CheckEmpty
, Error
};
203 enum class ChangeKind
{ Char
, CharLen
, SingleChar
, OUStringChar
};
205 enum class PassThrough
{ No
, EmptyConstantString
, NonEmptyConstantString
};
207 std::string
describeChangeKind(ChangeKind kind
);
209 bool isStringConstant(
210 Expr
const * expr
, unsigned * size
, bool * nonArray
,
211 ContentKind
* content
, bool * embeddedNuls
, bool * terminatingNul
,
212 std::vector
<char32_t
> * utf8Content
= nullptr);
214 bool isZero(Expr
const * expr
);
217 Expr
const * expr
, ChangeKind kind
, std::string
const & original
,
218 std::string
const & replacement
, PassThrough pass
, bool nonArray
,
219 char const * rewriteFrom
, char const * rewriteTo
);
222 CallExpr
const * expr
, FunctionDecl
const * callee
,
223 TreatEmpty treatEmpty
, unsigned size
, std::string
* replacement
);
226 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
227 std::string
const & replacement
, TreatEmpty treatEmpty
, bool literal
,
228 char const * rewriteFrom
= nullptr, char const * rewriteTo
= nullptr);
231 CallExpr
const * expr
, unsigned arg1
, unsigned arg2
,
232 FunctionDecl
const * callee
, std::string
const & replacement
,
233 TreatEmpty treatEmpty
);
235 void handleOUStringCtor(
236 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
237 bool explicitFunctionalCastNotation
);
239 void handleOStringCtor(
240 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
241 bool explicitFunctionalCastNotation
);
243 void handleOUStringCtor(
244 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
245 bool explicitFunctionalCastNotation
);
247 void handleOStringCtor(
248 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
249 bool explicitFunctionalCastNotation
);
251 enum class StringKind
{ Unicode
, Char
};
252 void handleStringCtor(
253 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
254 bool explicitFunctionalCastNotation
, StringKind stringKind
);
256 void handleFunArgOstring(
257 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
);
259 std::stack
<QualType
> returnTypes_
;
260 std::stack
<Expr
const *> calls_
;
263 void StringConstant::run() {
264 if (compiler
.getLangOpts().CPlusPlus
265 && compiler
.getPreprocessor().getIdentifierInfo(
266 "LIBO_INTERNAL_ONLY")->hasMacroDefinition())
267 //TODO: some parts of it are useful for external code, too
269 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
273 bool StringConstant::TraverseCallExpr(CallExpr
* expr
) {
274 if (!WalkUpFromCallExpr(expr
)) {
279 for (auto * e
: expr
->children()) {
280 if (!TraverseStmt(e
)) {
289 bool StringConstant::TraverseCXXMemberCallExpr(CXXMemberCallExpr
* expr
) {
290 if (!WalkUpFromCXXMemberCallExpr(expr
)) {
295 for (auto * e
: expr
->children()) {
296 if (!TraverseStmt(e
)) {
305 bool StringConstant::TraverseCXXOperatorCallExpr(CXXOperatorCallExpr
* expr
)
307 if (!WalkUpFromCXXOperatorCallExpr(expr
)) {
312 for (auto * e
: expr
->children()) {
313 if (!TraverseStmt(e
)) {
322 bool StringConstant::TraverseCXXConstructExpr(CXXConstructExpr
* expr
) {
323 if (!WalkUpFromCXXConstructExpr(expr
)) {
328 for (auto * e
: expr
->children()) {
329 if (!TraverseStmt(e
)) {
338 bool StringConstant::VisitCallExpr(CallExpr
const * expr
) {
339 if (ignoreLocation(expr
)) {
342 FunctionDecl
const * fdecl
= expr
->getDirectCallee();
343 if (fdecl
== nullptr) {
346 for (unsigned i
= 0; i
!= fdecl
->getNumParams(); ++i
) {
347 auto t
= fdecl
->getParamDecl(i
)->getType();
348 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
349 .LvalueReference().Const().NotSubstTemplateTypeParmType()
350 .Class("OUString").Namespace("rtl").GlobalNamespace())
352 if (!(isLhsOfAssignment(fdecl
, i
)
353 || hasOverloads(fdecl
, expr
->getNumArgs())))
355 handleOUStringCtor(expr
, i
, fdecl
, true);
358 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
359 .LvalueReference().Const().NotSubstTemplateTypeParmType()
360 .Class("OString").Namespace("rtl").GlobalNamespace())
362 if (!(isLhsOfAssignment(fdecl
, i
)
363 || hasOverloads(fdecl
, expr
->getNumArgs())))
365 handleOStringCtor(expr
, i
, fdecl
, true);
369 loplugin::DeclCheck
dc(fdecl
);
370 //TODO: u.compareToAscii("foo") -> u.???("foo")
371 //TODO: u.compareToIgnoreAsciiCaseAscii("foo") -> u.???("foo")
372 if ((dc
.Function("createFromAscii").Class("OUString").Namespace("rtl")
374 && fdecl
->getNumParams() == 1)
376 // OUString::createFromAscii("foo") -> OUString("foo")
378 expr
, 0, fdecl
, "rtl::OUString constructor",
379 TreatEmpty::DefaultCtor
, true);
382 if ((dc
.Function("endsWithAsciiL").Class("OUString").Namespace("rtl")
384 && fdecl
->getNumParams() == 2)
386 // u.endsWithAsciiL("foo", 3) -> u.endsWith("foo"):
388 expr
, 0, 1, fdecl
, "rtl::OUString::endsWith", TreatEmpty::Error
);
391 if ((dc
.Function("endsWithIgnoreAsciiCaseAsciiL").Class("OUString")
392 .Namespace("rtl").GlobalNamespace())
393 && fdecl
->getNumParams() == 2)
395 // u.endsWithIgnoreAsciiCaseAsciiL("foo", 3) ->
396 // u.endsWithIgnoreAsciiCase("foo"):
398 expr
, 0, 1, fdecl
, "rtl::OUString::endsWithIgnoreAsciiCase",
402 if ((dc
.Function("equalsAscii").Class("OUString").Namespace("rtl")
404 && fdecl
->getNumParams() == 1)
406 // u.equalsAscii("foo") -> u == "foo":
408 expr
, 0, fdecl
, "operator ==", TreatEmpty::CheckEmpty
, false);
411 if ((dc
.Function("equalsAsciiL").Class("OUString").Namespace("rtl")
413 && fdecl
->getNumParams() == 2)
415 // u.equalsAsciiL("foo", 3) -> u == "foo":
416 handleCharLen(expr
, 0, 1, fdecl
, "operator ==", TreatEmpty::CheckEmpty
);
419 if ((dc
.Function("equalsIgnoreAsciiCaseAscii").Class("OUString")
420 .Namespace("rtl").GlobalNamespace())
421 && fdecl
->getNumParams() == 1)
423 // u.equalsIgnoreAsciiCaseAscii("foo") ->
424 // u.equalsIngoreAsciiCase("foo"):
426 auto file
= getFilenameOfLocation(
427 compiler
.getSourceManager().getSpellingLoc(expr
->getBeginLoc()));
428 if (loplugin::isSamePathname(
429 file
, SRCDIR
"/sal/qa/rtl/strings/test_oustring_compare.cxx"))
434 expr
, 0, fdecl
, "rtl::OUString::equalsIgnoreAsciiCase",
435 TreatEmpty::CheckEmpty
, false);
438 if ((dc
.Function("equalsIgnoreAsciiCaseAsciiL").Class("OUString")
439 .Namespace("rtl").GlobalNamespace())
440 && fdecl
->getNumParams() == 2)
442 // u.equalsIgnoreAsciiCaseAsciiL("foo", 3) ->
443 // u.equalsIngoreAsciiCase("foo"):
444 auto file
= getFilenameOfLocation(
445 compiler
.getSourceManager().getSpellingLoc(expr
->getBeginLoc()));
446 if (loplugin::isSamePathname(
447 file
, SRCDIR
"/sal/qa/rtl/strings/test_oustring_compare.cxx"))
452 expr
, 0, 1, fdecl
, "rtl::OUString::equalsIgnoreAsciiCase",
453 TreatEmpty::CheckEmpty
);
456 if ((dc
.Function("indexOfAsciiL").Class("OUString").Namespace("rtl")
458 && fdecl
->getNumParams() == 3)
460 assert(expr
->getNumArgs() == 3);
461 // u.indexOfAsciiL("foo", 3, i) -> u.indexOf("foo", i):
463 expr
, 0, 1, fdecl
, "rtl::OUString::indexOf", TreatEmpty::Error
);
466 if ((dc
.Function("lastIndexOfAsciiL").Class("OUString").Namespace("rtl")
468 && fdecl
->getNumParams() == 2)
470 // u.lastIndexOfAsciiL("foo", 3) -> u.lastIndexOf("foo"):
472 expr
, 0, 1, fdecl
, "rtl::OUString::lastIndexOf", TreatEmpty::Error
);
475 if ((dc
.Function("matchAsciiL").Class("OUString").Namespace("rtl")
477 && fdecl
->getNumParams() == 3)
479 assert(expr
->getNumArgs() == 3);
480 // u.matchAsciiL("foo", 3, i) -> u.match("foo", i):
483 (isZero(expr
->getArg(2))
484 ? std::string("rtl::OUString::startsWith")
485 : std::string("rtl::OUString::match")),
489 if ((dc
.Function("matchIgnoreAsciiCaseAsciiL").Class("OUString")
490 .Namespace("rtl").GlobalNamespace())
491 && fdecl
->getNumParams() == 3)
493 assert(expr
->getNumArgs() == 3);
494 // u.matchIgnoreAsciiCaseAsciiL("foo", 3, i) ->
495 // u.matchIgnoreAsciiCase("foo", i):
498 (isZero(expr
->getArg(2))
499 ? std::string("rtl::OUString::startsWithIgnoreAsciiCase")
500 : std::string("rtl::OUString::matchIgnoreAsciiCase")),
504 if ((dc
.Function("reverseCompareToAsciiL").Class("OUString")
505 .Namespace("rtl").GlobalNamespace())
506 && fdecl
->getNumParams() == 2)
508 // u.reverseCompareToAsciiL("foo", 3) -> u.reverseCompareTo("foo"):
510 expr
, 0, 1, fdecl
, "rtl::OUString::reverseCompareTo",
514 if ((dc
.Function("reverseCompareTo").Class("OUString").Namespace("rtl")
516 && fdecl
->getNumParams() == 1)
518 handleOUStringCtor(expr
, 0, fdecl
, false);
521 if ((dc
.Function("equalsIgnoreAsciiCase").Class("OUString").Namespace("rtl")
523 && fdecl
->getNumParams() == 1)
525 handleOUStringCtor(expr
, 0, fdecl
, false);
528 if ((dc
.Function("match").Class("OUString").Namespace("rtl")
530 && fdecl
->getNumParams() == 2)
532 handleOUStringCtor(expr
, 0, fdecl
, false);
535 if ((dc
.Function("matchIgnoreAsciiCase").Class("OUString").Namespace("rtl")
537 && fdecl
->getNumParams() == 2)
539 handleOUStringCtor(expr
, 0, fdecl
, false);
542 if ((dc
.Function("startsWith").Class("OUString").Namespace("rtl")
544 && fdecl
->getNumParams() == 2)
546 handleOUStringCtor(expr
, 0, fdecl
, false);
549 if ((dc
.Function("startsWithIgnoreAsciiCase").Class("OUString")
550 .Namespace("rtl").GlobalNamespace())
551 && fdecl
->getNumParams() == 2)
553 handleOUStringCtor(expr
, 0, fdecl
, false);
556 if ((dc
.Function("endsWith").Class("OUString").Namespace("rtl")
558 && fdecl
->getNumParams() == 2)
560 handleOUStringCtor(expr
, 0, fdecl
, false);
563 if ((dc
.Function("endsWithIgnoreAsciiCase").Class("OUString")
564 .Namespace("rtl").GlobalNamespace())
565 && fdecl
->getNumParams() == 2)
567 handleOUStringCtor(expr
, 0, fdecl
, false);
570 if ((dc
.Function("indexOf").Class("OUString").Namespace("rtl")
572 && fdecl
->getNumParams() == 2)
574 handleOUStringCtor(expr
, 0, fdecl
, false);
577 if ((dc
.Function("lastIndexOf").Class("OUString").Namespace("rtl")
579 && fdecl
->getNumParams() == 1)
581 handleOUStringCtor(expr
, 0, fdecl
, false);
584 if ((dc
.Function("replaceFirst").Class("OUString").Namespace("rtl")
586 && fdecl
->getNumParams() == 3)
588 handleOUStringCtor(expr
, 0, fdecl
, false);
589 handleOUStringCtor(expr
, 1, fdecl
, false);
592 if ((dc
.Function("replaceAll").Class("OUString").Namespace("rtl")
594 && (fdecl
->getNumParams() == 2 || fdecl
->getNumParams() == 3))
596 handleOUStringCtor(expr
, 0, fdecl
, false);
597 handleOUStringCtor(expr
, 1, fdecl
, false);
600 if ((dc
.Operator(OO_PlusEqual
).Class("OUString").Namespace("rtl")
602 && fdecl
->getNumParams() == 1)
605 expr
, dyn_cast
<CXXOperatorCallExpr
>(expr
) == nullptr ? 0 : 1,
609 if ((dc
.Function("equals").Class("OUString").Namespace("rtl")
611 && fdecl
->getNumParams() == 1)
618 if (!isStringConstant(
619 expr
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
624 if (cont
!= ContentKind::Ascii
) {
626 DiagnosticsEngine::Warning
,
627 ("call of '%0' with string constant argument containing"
628 " non-ASCII characters"),
630 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
634 DiagnosticsEngine::Warning
,
635 ("call of '%0' with string constant argument containing"
638 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
642 DiagnosticsEngine::Warning
,
643 ("rewrite call of '%0' with empty string constant argument as"
644 " call of 'rtl::OUString::isEmpty'"),
646 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
650 if (dc
.Operator(OO_EqualEqual
).Namespace("rtl").GlobalNamespace()
651 && fdecl
->getNumParams() == 2)
653 for (unsigned i
= 0; i
!= 2; ++i
) {
659 if (!isStringConstant(
660 expr
->getArg(i
)->IgnoreParenImpCasts(), &n
, &nonArray
,
665 if (cont
!= ContentKind::Ascii
) {
667 DiagnosticsEngine::Warning
,
668 ("call of '%0' with string constant argument containing"
669 " non-ASCII characters"),
671 << fdecl
->getQualifiedNameAsString()
672 << expr
->getSourceRange();
676 DiagnosticsEngine::Warning
,
677 ("call of '%0' with string constant argument containing"
680 << fdecl
->getQualifiedNameAsString()
681 << expr
->getSourceRange();
685 DiagnosticsEngine::Warning
,
686 ("rewrite call of '%0' with empty string constant argument"
687 " as call of 'rtl::OUString::isEmpty'"),
689 << fdecl
->getQualifiedNameAsString()
690 << expr
->getSourceRange();
695 if (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl").GlobalNamespace()
696 && fdecl
->getNumParams() == 2)
698 for (unsigned i
= 0; i
!= 2; ++i
) {
704 if (!isStringConstant(
705 expr
->getArg(i
)->IgnoreParenImpCasts(), &n
, &nonArray
,
710 if (cont
!= ContentKind::Ascii
) {
712 DiagnosticsEngine::Warning
,
713 ("call of '%0' with string constant argument containing"
714 " non-ASCII characters"),
716 << fdecl
->getQualifiedNameAsString()
717 << expr
->getSourceRange();
721 DiagnosticsEngine::Warning
,
722 ("call of '%0' with string constant argument containing"
725 << fdecl
->getQualifiedNameAsString()
726 << expr
->getSourceRange();
730 DiagnosticsEngine::Warning
,
731 ("rewrite call of '%0' with empty string constant argument"
732 " as call of '!rtl::OUString::isEmpty'"),
734 << fdecl
->getQualifiedNameAsString()
735 << expr
->getSourceRange();
740 if (dc
.Operator(OO_Equal
).Namespace("rtl").GlobalNamespace()
741 && fdecl
->getNumParams() == 1)
748 if (!isStringConstant(
749 expr
->getArg(1)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
754 if (cont
!= ContentKind::Ascii
) {
756 DiagnosticsEngine::Warning
,
757 ("call of '%0' with string constant argument containing"
758 " non-ASCII characters"),
760 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
764 DiagnosticsEngine::Warning
,
765 ("call of '%0' with string constant argument containing"
768 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
772 DiagnosticsEngine::Warning
,
773 ("rewrite call of '%0' with empty string constant argument as"
774 " call of 'rtl::OUString::clear'"),
776 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
781 if (dc
.Function("append").Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
782 && fdecl
->getNumParams() == 1)
784 handleChar(expr
, 0, fdecl
, "", TreatEmpty::Error
, false);
787 if ((dc
.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
789 && fdecl
->getNumParams() == 1)
791 // u.appendAscii("foo") -> u.append("foo")
793 expr
, 0, fdecl
, "rtl::OUStringBuffer::append", TreatEmpty::Error
,
794 true, "appendAscii", "append");
797 if ((dc
.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
799 && fdecl
->getNumParams() == 2)
801 // u.appendAscii("foo", 3) -> u.append("foo"):
803 expr
, 0, 1, fdecl
, "rtl::OUStringBuffer::append",
807 if (dc
.Function("append").Class("OStringBuffer").Namespace("rtl")
810 switch (fdecl
->getNumParams()) {
812 handleFunArgOstring(expr
, 0, fdecl
);
816 // b.append("foo", 3) -> b.append("foo"):
817 auto file
= getFilenameOfLocation(
818 compiler
.getSourceManager().getSpellingLoc(
819 expr
->getBeginLoc()));
820 if (loplugin::isSamePathname(
822 SRCDIR
"/sal/qa/OStringBuffer/rtl_OStringBuffer.cxx"))
827 expr
, 0, 1, fdecl
, "rtl::OStringBuffer::append",
836 if (dc
.Function("insert").Class("OStringBuffer").Namespace("rtl")
839 switch (fdecl
->getNumParams()) {
841 handleFunArgOstring(expr
, 1, fdecl
);
845 // b.insert(i, "foo", 3) -> b.insert(i, "foo"):
847 expr
, 1, 2, fdecl
, "rtl::OStringBuffer::insert",
859 bool StringConstant::VisitCXXConstructExpr(CXXConstructExpr
const * expr
) {
860 if (ignoreLocation(expr
)) {
863 auto classdecl
= expr
->getConstructor()->getParent();
864 if (loplugin::DeclCheck(classdecl
)
865 .Class("OUString").Namespace("rtl").GlobalNamespace())
870 switch (expr
->getConstructor()->getNumParams()) {
872 if (!loplugin::TypeCheck(
873 expr
->getConstructor()->getParamDecl(0)->getType())
874 .Typedef("sal_Unicode").GlobalNamespace())
878 kind
= ChangeKind::SingleChar
;
879 pass
= PassThrough::NonEmptyConstantString
;
884 auto arg
= expr
->getArg(0);
885 if (loplugin::TypeCheck(arg
->getType())
886 .Class("OUStringChar_").Namespace("rtl")
889 kind
= ChangeKind::OUStringChar
;
890 pass
= PassThrough::NonEmptyConstantString
;
898 if (!isStringConstant(
899 arg
->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
904 if (cont
!= ContentKind::Ascii
) {
906 DiagnosticsEngine::Warning
,
907 ("construction of %0 with string constant argument"
908 " containing non-ASCII characters"),
910 << classdecl
<< expr
->getSourceRange();
914 DiagnosticsEngine::Warning
,
915 ("construction of %0 with string constant argument"
916 " containing embedded NULLs"),
918 << classdecl
<< expr
->getSourceRange();
920 kind
= ChangeKind::Char
;
922 ? PassThrough::EmptyConstantString
923 : PassThrough::NonEmptyConstantString
;
935 std::vector
<char32_t
> utf8Cont
;
936 if (!isStringConstant(
937 expr
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
,
938 &cont
, &emb
, &trm
, &utf8Cont
))
943 if (!compat::EvaluateAsInt(expr
->getArg(1),
944 res
, compiler
.getASTContext()))
950 DiagnosticsEngine::Warning
,
951 ("suspicious 'rtl::OUString' constructor with literal"
952 " of length %0 and non-matching length argument %1"),
954 << n
<< compat::toString(res
, 10) << expr
->getSourceRange();
958 if (!compat::EvaluateAsInt(expr
->getArg(2),
959 enc
, compiler
.getASTContext()))
963 auto const encIsAscii
= enc
== 11; // RTL_TEXTENCODING_ASCII_US
964 auto const encIsUtf8
= enc
== 76; // RTL_TEXTENCODING_UTF8
965 if (!compat::EvaluateAsInt(expr
->getArg(3),
966 res
, compiler
.getASTContext())
967 || res
!= 0x333) // OSTRING_TO_OUSTRING_CVTFLAGS
971 if (!encIsAscii
&& cont
== ContentKind::Ascii
) {
973 DiagnosticsEngine::Warning
,
974 ("suspicious 'rtl::OUString' constructor with text"
975 " encoding %0 but plain ASCII content; use"
976 " 'RTL_TEXTENCODING_ASCII_US' instead"),
977 expr
->getArg(2)->getExprLoc())
978 << compat::toString(enc
, 10) << expr
->getSourceRange();
982 if (cont
== ContentKind::Arbitrary
) {
984 DiagnosticsEngine::Warning
,
985 ("suspicious 'rtl::OUString' constructor with text"
986 " encoding 'RTL_TEXTENCODING_UTF8' but non-UTF-8"
988 expr
->getArg(0)->getExprLoc())
989 << expr
->getSourceRange();
991 assert(cont
== ContentKind::Utf8
);
992 //TODO: keep original content as much as possible
993 std::ostringstream s
;
994 for (auto const c
: utf8Cont
) {
997 } else if (c
== '"') {
999 } else if (c
== '\a') {
1001 } else if (c
== '\b') {
1003 } else if (c
== '\f') {
1005 } else if (c
== '\n') {
1007 } else if (c
== '\r') {
1009 } else if (c
== '\t') {
1011 } else if (c
== '\v') {
1013 } else if (c
<= 0x1F || c
== 0x7F) {
1014 s
<< "\\x" << std::oct
<< std::setw(3)
1015 << std::setfill('0')
1016 << static_cast<std::uint_least32_t>(c
);
1017 } else if (c
< 0x7F) {
1019 } else if (c
<= 0xFFFF) {
1020 s
<< "\\u" << std::hex
<< std::uppercase
1021 << std::setw(4) << std::setfill('0')
1022 << static_cast<std::uint_least32_t>(c
);
1024 assert(c
<= 0x10FFFF);
1025 s
<< "\\U" << std::hex
<< std::uppercase
1026 << std::setw(8) << std::setfill('0')
1027 << static_cast<std::uint_least32_t>(c
);
1031 DiagnosticsEngine::Warning
,
1032 ("simplify construction of %0 with UTF-8 content as"
1033 " OUString(u\"%1\")"),
1035 << classdecl
<< s
.str() << expr
->getSourceRange();
1040 if (cont
!= ContentKind::Ascii
|| emb
) {
1041 // cf. remaining uses of RTL_CONSTASCII_USTRINGPARAM
1044 kind
= ChangeKind::Char
;
1046 ? PassThrough::EmptyConstantString
1047 : PassThrough::NonEmptyConstantString
;
1054 if (!calls_
.empty()) {
1055 Expr
const * call
= calls_
.top();
1056 CallExpr::const_arg_iterator argsBeg
;
1057 CallExpr::const_arg_iterator argsEnd
;
1058 if (isa
<CallExpr
>(call
)) {
1059 argsBeg
= cast
<CallExpr
>(call
)->arg_begin();
1060 argsEnd
= cast
<CallExpr
>(call
)->arg_end();
1061 } else if (isa
<CXXConstructExpr
>(call
)) {
1062 argsBeg
= cast
<CXXConstructExpr
>(call
)->arg_begin();
1063 argsEnd
= cast
<CXXConstructExpr
>(call
)->arg_end();
1067 for (auto i(argsBeg
); i
!= argsEnd
; ++i
) {
1068 Expr
const * e
= (*i
)->IgnoreParenImpCasts();
1069 if (isa
<MaterializeTemporaryExpr
>(e
)) {
1070 e
= cast
<MaterializeTemporaryExpr
>(e
)->getSubExpr()
1071 ->IgnoreParenImpCasts();
1073 if (isa
<CXXFunctionalCastExpr
>(e
)) {
1074 e
= cast
<CXXFunctionalCastExpr
>(e
)->getSubExpr()
1075 ->IgnoreParenImpCasts();
1077 if (isa
<CXXBindTemporaryExpr
>(e
)) {
1078 e
= cast
<CXXBindTemporaryExpr
>(e
)->getSubExpr()
1079 ->IgnoreParenImpCasts();
1082 if (isa
<CallExpr
>(call
)) {
1083 FunctionDecl
const * fdecl
1084 = cast
<CallExpr
>(call
)->getDirectCallee();
1085 if (fdecl
== nullptr) {
1088 loplugin::DeclCheck
dc(fdecl
);
1089 if (pass
== PassThrough::EmptyConstantString
) {
1090 if ((dc
.Function("equals").Class("OUString")
1091 .Namespace("rtl").GlobalNamespace())
1092 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1093 .GlobalNamespace()))
1096 DiagnosticsEngine::Warning
,
1097 ("rewrite call of '%0' with construction of"
1098 " %1 with empty string constant argument"
1099 " as call of 'rtl::OUString::isEmpty'"),
1100 getMemberLocation(call
))
1101 << fdecl
->getQualifiedNameAsString()
1102 << classdecl
<< call
->getSourceRange();
1105 if (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl")
1109 DiagnosticsEngine::Warning
,
1110 ("rewrite call of '%0' with construction of"
1111 " %1 with empty string constant argument"
1112 " as call of '!rtl::OUString::isEmpty'"),
1113 getMemberLocation(call
))
1114 << fdecl
->getQualifiedNameAsString()
1115 << classdecl
<< call
->getSourceRange();
1118 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
1120 || (dc
.Operator(OO_Plus
).Class("OUString")
1121 .Namespace("rtl").GlobalNamespace()))
1124 DiagnosticsEngine::Warning
,
1125 ("call of '%0' with suspicious construction"
1126 " of %1 with empty string constant"
1128 getMemberLocation(call
))
1129 << fdecl
->getQualifiedNameAsString()
1130 << classdecl
<< call
->getSourceRange();
1133 if (dc
.Operator(OO_Equal
).Class("OUString")
1134 .Namespace("rtl").GlobalNamespace())
1137 DiagnosticsEngine::Warning
,
1138 ("rewrite call of '%0' with construction of"
1139 " %1 with empty string constant argument"
1140 " as call of 'rtl::OUString::clear'"),
1141 getMemberLocation(call
))
1142 << fdecl
->getQualifiedNameAsString()
1143 << classdecl
<< call
->getSourceRange();
1147 assert(pass
== PassThrough::NonEmptyConstantString
);
1148 if (dc
.Function("equals").Class("OUString")
1149 .Namespace("rtl").GlobalNamespace())
1152 DiagnosticsEngine::Warning
,
1153 ("rewrite call of '%0' with construction of"
1154 " %1 with %2 as 'operator =='"),
1155 getMemberLocation(call
))
1156 << fdecl
->getQualifiedNameAsString()
1157 << classdecl
<< describeChangeKind(kind
)
1158 << call
->getSourceRange();
1161 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
1163 || (dc
.Operator(OO_Plus
).Class("OUString")
1164 .Namespace("rtl").GlobalNamespace())
1165 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1167 || (dc
.Operator(OO_ExclaimEqual
)
1168 .Namespace("rtl").GlobalNamespace()))
1170 if (dc
.Operator(OO_Plus
).Namespace("rtl")
1173 auto file
= getFilenameOfLocation(
1174 compiler
.getSourceManager()
1176 expr
->getBeginLoc()));
1177 if (loplugin::isSamePathname(
1180 "/sal/qa/rtl/strings/test_ostring_concat.cxx"))
1181 || loplugin::isSamePathname(
1184 "/sal/qa/rtl/strings/test_oustring_concat.cxx")))
1189 auto loc
= expr
->getArg(0)->getBeginLoc();
1190 while (compiler
.getSourceManager()
1191 .isMacroArgExpansion(loc
))
1193 loc
= compiler
.getSourceManager()
1194 .getImmediateMacroCallerLoc(loc
);
1196 if (kind
== ChangeKind::SingleChar
) {
1198 DiagnosticsEngine::Warning
,
1199 ("rewrite construction of %0 with %1 in"
1200 " call of '%2' as construction of"
1202 getMemberLocation(expr
))
1203 << classdecl
<< describeChangeKind(kind
)
1204 << fdecl
->getQualifiedNameAsString()
1205 << expr
->getSourceRange();
1208 DiagnosticsEngine::Warning
,
1209 ("elide construction of %0 with %1 in"
1211 getMemberLocation(expr
))
1212 << classdecl
<< describeChangeKind(kind
)
1213 << fdecl
->getQualifiedNameAsString()
1214 << expr
->getSourceRange();
1219 } else if (isa
<CXXConstructExpr
>(call
)) {
1228 DiagnosticsEngine::Warning
,
1229 "simplify construction of %0 with %1", expr
->getExprLoc())
1230 << classdecl
<< describeChangeKind(kind
)
1231 << expr
->getSourceRange();
1236 auto consDecl
= expr
->getConstructor();
1237 for (unsigned i
= 0; i
!= consDecl
->getNumParams(); ++i
) {
1238 auto t
= consDecl
->getParamDecl(i
)->getType();
1239 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
1240 .LvalueReference().Const().NotSubstTemplateTypeParmType()
1241 .Class("OUString").Namespace("rtl").GlobalNamespace())
1243 auto argExpr
= expr
->getArg(i
);
1244 if (argExpr
&& i
<= consDecl
->getNumParams())
1246 if (!hasOverloads(consDecl
, expr
->getNumArgs()))
1248 handleOUStringCtor(expr
, argExpr
, consDecl
, true);
1252 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
1253 .LvalueReference().Const().NotSubstTemplateTypeParmType()
1254 .Class("OString").Namespace("rtl").GlobalNamespace())
1256 auto argExpr
= expr
->getArg(i
);
1257 if (argExpr
&& i
<= consDecl
->getNumParams())
1259 if (!hasOverloads(consDecl
, expr
->getNumArgs()))
1261 handleOStringCtor(expr
, argExpr
, consDecl
, true);
1270 bool StringConstant::VisitReturnStmt(ReturnStmt
const * stmt
) {
1271 if (ignoreLocation(stmt
)) {
1274 auto const e1
= stmt
->getRetValue();
1275 if (e1
== nullptr) {
1278 auto const tc1
= loplugin::TypeCheck(e1
->getType().getTypePtr());
1279 if (!(tc1
.Class("OString").Namespace("rtl").GlobalNamespace()
1280 || tc1
.Class("OUString").Namespace("rtl").GlobalNamespace()))
1284 assert(!returnTypes_
.empty());
1285 auto const tc2
= loplugin::TypeCheck(returnTypes_
.top().getTypePtr());
1286 if (!(tc2
.Class("OString").Namespace("rtl").GlobalNamespace()
1287 || tc2
.Class("OUString").Namespace("rtl").GlobalNamespace()))
1291 auto const e2
= dyn_cast
<CXXFunctionalCastExpr
>(e1
->IgnoreImplicit());
1292 if (e2
== nullptr) {
1295 auto const e3
= dyn_cast
<CXXBindTemporaryExpr
>(e2
->getSubExpr());
1296 if (e3
== nullptr) {
1299 auto const e4
= dyn_cast
<CXXConstructExpr
>(e3
->getSubExpr());
1300 if (e4
== nullptr) {
1303 if (e4
->getNumArgs() != 2) {
1306 auto const t
= e4
->getArg(0)->getType();
1307 if (!(t
.isConstQualified() && t
->isConstantArrayType())) {
1310 auto const e5
= e4
->getArg(1);
1311 if (!(isa
<CXXDefaultArgExpr
>(e5
)
1312 && (loplugin::TypeCheck(e5
->getType()).Struct("Dummy").Namespace("libreoffice_internal")
1313 .Namespace("rtl").GlobalNamespace())))
1317 report(DiagnosticsEngine::Warning
, "elide constructor call", e1
->getBeginLoc())
1318 << e1
->getSourceRange();
1322 std::string
StringConstant::describeChangeKind(ChangeKind kind
) {
1324 case ChangeKind::Char
:
1325 return "string constant argument";
1326 case ChangeKind::CharLen
:
1327 return "string constant and matching length arguments";
1328 case ChangeKind::SingleChar
:
1329 return "sal_Unicode argument";
1330 case ChangeKind::OUStringChar
:
1331 return "OUStringChar argument";
1333 llvm_unreachable("unknown change kind");
1336 bool StringConstant::isStringConstant(
1337 Expr
const * expr
, unsigned * size
, bool * nonArray
, ContentKind
* content
,
1338 bool * embeddedNuls
, bool * terminatingNul
,
1339 std::vector
<char32_t
> * utf8Content
)
1341 assert(expr
!= nullptr);
1342 assert(size
!= nullptr);
1343 assert(nonArray
!= nullptr);
1344 assert(content
!= nullptr);
1345 assert(embeddedNuls
!= nullptr);
1346 assert(terminatingNul
!= nullptr);
1347 QualType t
= expr
->getType();
1348 // Look inside RTL_CONSTASCII_STRINGPARAM:
1349 if (loplugin::TypeCheck(t
).Pointer().Const().Char()) {
1350 auto e2
= dyn_cast
<UnaryOperator
>(expr
);
1351 if (e2
!= nullptr && e2
->getOpcode() == UO_AddrOf
) {
1352 auto e3
= dyn_cast
<ArraySubscriptExpr
>(
1353 e2
->getSubExpr()->IgnoreParenImpCasts());
1354 if (e3
== nullptr || !isZero(e3
->getIdx()->IgnoreParenImpCasts())) {
1357 expr
= e3
->getBase()->IgnoreParenImpCasts();
1358 t
= expr
->getType();
1361 if (!t
.isConstQualified()) {
1364 DeclRefExpr
const * dre
= dyn_cast
<DeclRefExpr
>(expr
);
1365 if (dre
!= nullptr) {
1366 VarDecl
const * var
= dyn_cast
<VarDecl
>(dre
->getDecl());
1367 if (var
!= nullptr) {
1368 Expr
const * init
= var
->getAnyInitializer();
1369 if (init
!= nullptr) {
1370 expr
= init
->IgnoreParenImpCasts();
1375 if (loplugin::TypeCheck(t
).Pointer().Const().Char()) {
1377 } else if (t
->isConstantArrayType()
1378 && (loplugin::TypeCheck(
1379 t
->getAsArrayTypeUnsafe()->getElementType())
1386 clang::StringLiteral
const * lit
= dyn_cast
<clang::StringLiteral
>(expr
);
1387 if (lit
!= nullptr) {
1388 if (!(compat::isOrdinary(lit
) || lit
->isUTF8())) {
1391 unsigned n
= lit
->getLength();
1392 ContentKind cont
= ContentKind::Ascii
;
1395 enum class Utf8State
{ Start
, E0
, EB
, F0
, F4
, Trail1
, Trail2
, Trail3
};
1396 Utf8State s
= Utf8State::Start
;
1397 StringRef str
= lit
->getString();
1398 for (unsigned i
= 0; i
!= n
; ++i
) {
1399 auto const c
= static_cast<unsigned char>(str
[i
]);
1404 case Utf8State::Start
:
1406 if (c
>= 0xC2 && c
<= 0xDF) {
1408 s
= Utf8State::Trail1
;
1409 } else if (c
== 0xE0) {
1412 } else if ((c
>= 0xE1 && c
<= 0xEA)
1413 || (c
>= 0xEE && c
<= 0xEF))
1416 s
= Utf8State::Trail2
;
1417 } else if (c
== 0xEB) {
1420 } else if (c
== 0xF0) {
1423 } else if (c
>= 0xF1 && c
<= 0xF3) {
1425 s
= Utf8State::Trail3
;
1426 } else if (c
== 0xF4) {
1430 cont
= ContentKind::Arbitrary
;
1432 } else if (utf8Content
!= nullptr
1433 && cont
!= ContentKind::Arbitrary
)
1435 utf8Content
->push_back(c
);
1439 if (c
>= 0xA0 && c
<= 0xBF) {
1440 val
= (val
<< 6) | (c
& 0x3F);
1441 s
= Utf8State::Trail1
;
1443 cont
= ContentKind::Arbitrary
;
1444 s
= Utf8State::Start
;
1448 if (c
>= 0x80 && c
<= 0x9F) {
1449 val
= (val
<< 6) | (c
& 0x3F);
1450 s
= Utf8State::Trail1
;
1452 cont
= ContentKind::Arbitrary
;
1453 s
= Utf8State::Start
;
1457 if (c
>= 0x90 && c
<= 0xBF) {
1458 val
= (val
<< 6) | (c
& 0x3F);
1459 s
= Utf8State::Trail2
;
1461 cont
= ContentKind::Arbitrary
;
1462 s
= Utf8State::Start
;
1466 if (c
>= 0x80 && c
<= 0x8F) {
1467 val
= (val
<< 6) | (c
& 0x3F);
1468 s
= Utf8State::Trail2
;
1470 cont
= ContentKind::Arbitrary
;
1471 s
= Utf8State::Start
;
1474 case Utf8State::Trail1
:
1475 if (c
>= 0x80 && c
<= 0xBF) {
1476 cont
= ContentKind::Utf8
;
1477 if (utf8Content
!= nullptr)
1479 utf8Content
->push_back((val
<< 6) | (c
& 0x3F));
1483 cont
= ContentKind::Arbitrary
;
1485 s
= Utf8State::Start
;
1487 case Utf8State::Trail2
:
1488 if (c
>= 0x80 && c
<= 0xBF) {
1489 val
= (val
<< 6) | (c
& 0x3F);
1490 s
= Utf8State::Trail1
;
1492 cont
= ContentKind::Arbitrary
;
1493 s
= Utf8State::Start
;
1496 case Utf8State::Trail3
:
1497 if (c
>= 0x80 && c
<= 0xBF) {
1498 val
= (val
<< 6) | (c
& 0x3F);
1499 s
= Utf8State::Trail2
;
1501 cont
= ContentKind::Arbitrary
;
1502 s
= Utf8State::Start
;
1510 *embeddedNuls
= emb
;
1511 *terminatingNul
= true;
1515 if (!expr
->isCXX11ConstantExpr(compiler
.getASTContext(), &v
)) {
1518 switch (v
.getKind()) {
1519 case APValue::LValue
:
1521 Expr
const * e
= v
.getLValueBase().dyn_cast
<Expr
const *>();
1525 if (!v
.getLValueOffset().isZero()) {
1526 return false; //TODO
1528 Expr
const * e2
= e
->IgnoreParenImpCasts();
1530 return isStringConstant(
1531 e2
, size
, nonArray
, content
, embeddedNuls
, terminatingNul
);
1533 //TODO: string literals are represented as recursive LValues???
1535 = compiler
.getASTContext().getAsConstantArrayType(t
)->getSize();
1538 assert(n
.ule(std::numeric_limits
<unsigned>::max()));
1539 *size
= static_cast<unsigned>(n
.getLimitedValue());
1540 *nonArray
= isPtr
|| *nonArray
;
1541 *content
= ContentKind::Ascii
; //TODO
1542 *embeddedNuls
= false; //TODO
1543 *terminatingNul
= true;
1546 case APValue::Array
:
1548 if (v
.hasArrayFiller()) { //TODO: handle final NULL filler?
1551 unsigned n
= v
.getArraySize();
1553 ContentKind cont
= ContentKind::Ascii
;
1555 //TODO: check for ContentType::Utf8
1556 for (unsigned i
= 0; i
!= n
- 1; ++i
) {
1557 APValue
e(v
.getArrayInitializedElt(i
));
1558 if (!e
.isInt()) { //TODO: assert?
1561 APSInt iv
= e
.getInt();
1564 } else if (iv
.uge(0x80)) {
1565 cont
= ContentKind::Arbitrary
;
1568 APValue
e(v
.getArrayInitializedElt(n
- 1));
1569 if (!e
.isInt()) { //TODO: assert?
1572 bool trm
= e
.getInt() == 0;
1573 *size
= trm
? n
- 1 : n
;
1576 *embeddedNuls
= emb
;
1577 *terminatingNul
= trm
;
1581 assert(false); //TODO???
1586 bool StringConstant::isZero(Expr
const * expr
) {
1588 return compat::EvaluateAsInt(expr
, res
, compiler
.getASTContext()) && res
== 0;
1591 void StringConstant::reportChange(
1592 Expr
const * expr
, ChangeKind kind
, std::string
const & original
,
1593 std::string
const & replacement
, PassThrough pass
, bool nonArray
,
1594 char const * rewriteFrom
, char const * rewriteTo
)
1596 assert((rewriteFrom
== nullptr) == (rewriteTo
== nullptr));
1597 if (pass
!= PassThrough::No
&& !calls_
.empty()) {
1598 Expr
const * call
= calls_
.top();
1599 CallExpr::const_arg_iterator argsBeg
;
1600 CallExpr::const_arg_iterator argsEnd
;
1601 if (isa
<CallExpr
>(call
)) {
1602 argsBeg
= cast
<CallExpr
>(call
)->arg_begin();
1603 argsEnd
= cast
<CallExpr
>(call
)->arg_end();
1604 } else if (isa
<CXXConstructExpr
>(call
)) {
1605 argsBeg
= cast
<CXXConstructExpr
>(call
)->arg_begin();
1606 argsEnd
= cast
<CXXConstructExpr
>(call
)->arg_end();
1610 for (auto i(argsBeg
); i
!= argsEnd
; ++i
) {
1611 Expr
const * e
= (*i
)->IgnoreParenImpCasts();
1612 if (isa
<CXXBindTemporaryExpr
>(e
)) {
1613 e
= cast
<CXXBindTemporaryExpr
>(e
)->getSubExpr()
1614 ->IgnoreParenImpCasts();
1617 if (isa
<CallExpr
>(call
)) {
1618 FunctionDecl
const * fdecl
1619 = cast
<CallExpr
>(call
)->getDirectCallee();
1620 if (fdecl
== nullptr) {
1623 loplugin::DeclCheck
dc(fdecl
);
1624 if (pass
== PassThrough::EmptyConstantString
) {
1625 if ((dc
.Function("equals").Class("OUString")
1626 .Namespace("rtl").GlobalNamespace())
1627 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1628 .GlobalNamespace()))
1631 DiagnosticsEngine::Warning
,
1632 ("rewrite call of '%0' with call of %1 with"
1633 " empty string constant argument as call of"
1634 " 'rtl::OUString::isEmpty'"),
1635 getMemberLocation(call
))
1636 << fdecl
->getQualifiedNameAsString() << original
1637 << call
->getSourceRange();
1640 if (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl")
1644 DiagnosticsEngine::Warning
,
1645 ("rewrite call of '%0' with call of %1 with"
1646 " empty string constant argument as call of"
1647 " '!rtl::OUString::isEmpty'"),
1648 getMemberLocation(call
))
1649 << fdecl
->getQualifiedNameAsString() << original
1650 << call
->getSourceRange();
1653 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
1655 || (dc
.Operator(OO_Plus
).Class("OUString")
1656 .Namespace("rtl").GlobalNamespace()))
1659 DiagnosticsEngine::Warning
,
1660 ("call of '%0' with suspicious call of %1 with"
1661 " empty string constant argument"),
1662 getMemberLocation(call
))
1663 << fdecl
->getQualifiedNameAsString() << original
1664 << call
->getSourceRange();
1667 if (dc
.Operator(OO_Equal
).Class("OUString")
1668 .Namespace("rtl").GlobalNamespace())
1671 DiagnosticsEngine::Warning
,
1672 ("rewrite call of '%0' with call of %1 with"
1673 " empty string constant argument as call of"
1674 " rtl::OUString::call"),
1675 getMemberLocation(call
))
1676 << fdecl
->getQualifiedNameAsString() << original
1677 << call
->getSourceRange();
1681 DiagnosticsEngine::Warning
,
1682 "TODO call inside %0", getMemberLocation(expr
))
1683 << fdecl
->getQualifiedNameAsString()
1684 << expr
->getSourceRange();
1687 assert(pass
== PassThrough::NonEmptyConstantString
);
1688 if ((dc
.Function("equals").Class("OUString")
1689 .Namespace("rtl").GlobalNamespace())
1690 || (dc
.Operator(OO_Equal
).Class("OUString")
1691 .Namespace("rtl").GlobalNamespace())
1692 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1694 || (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl")
1695 .GlobalNamespace()))
1698 DiagnosticsEngine::Warning
,
1699 "elide call of %0 with %1 in call of '%2'",
1700 getMemberLocation(expr
))
1701 << original
<< describeChangeKind(kind
)
1702 << fdecl
->getQualifiedNameAsString()
1703 << expr
->getSourceRange();
1707 DiagnosticsEngine::Warning
,
1708 ("rewrite call of %0 with %1 in call of '%2' as"
1709 " (implicit) construction of 'OUString'"),
1710 getMemberLocation(expr
))
1711 << original
<< describeChangeKind(kind
)
1712 << fdecl
->getQualifiedNameAsString()
1713 << expr
->getSourceRange();
1716 } else if (isa
<CXXConstructExpr
>(call
)) {
1717 auto classdecl
= cast
<CXXConstructExpr
>(call
)
1718 ->getConstructor()->getParent();
1719 loplugin::DeclCheck
dc(classdecl
);
1720 if (dc
.Class("OUString").Namespace("rtl").GlobalNamespace()
1721 || (dc
.Class("OUStringBuffer").Namespace("rtl")
1722 .GlobalNamespace()))
1724 //TODO: propagate further out?
1725 if (pass
== PassThrough::EmptyConstantString
) {
1727 DiagnosticsEngine::Warning
,
1728 ("rewrite construction of %0 with call of %1"
1729 " with empty string constant argument as"
1730 " default construction of %0"),
1731 getMemberLocation(call
))
1732 << classdecl
<< original
1733 << call
->getSourceRange();
1735 assert(pass
== PassThrough::NonEmptyConstantString
);
1737 DiagnosticsEngine::Warning
,
1738 ("elide call of %0 with %1 in construction of"
1740 getMemberLocation(expr
))
1741 << original
<< describeChangeKind(kind
)
1742 << classdecl
<< expr
->getSourceRange();
1752 if (rewriter
!= nullptr && !nonArray
&& rewriteFrom
!= nullptr) {
1753 SourceLocation loc
= getMemberLocation(expr
);
1754 while (compiler
.getSourceManager().isMacroArgExpansion(loc
)) {
1755 loc
= compiler
.getSourceManager().getImmediateMacroCallerLoc(loc
);
1757 if (compiler
.getSourceManager().isMacroBodyExpansion(loc
)) {
1758 loc
= compiler
.getSourceManager().getSpellingLoc(loc
);
1760 unsigned n
= Lexer::MeasureTokenLength(
1761 loc
, compiler
.getSourceManager(), compiler
.getLangOpts());
1762 if ((std::string(compiler
.getSourceManager().getCharacterData(loc
), n
)
1764 && replaceText(loc
, n
, rewriteTo
))
1770 DiagnosticsEngine::Warning
,
1771 "rewrite call of '%0' with %1 as call of '%2'%3",
1772 getMemberLocation(expr
))
1773 << original
<< describeChangeKind(kind
) << replacement
1774 << adviseNonArray(nonArray
) << expr
->getSourceRange();
1777 void StringConstant::checkEmpty(
1778 CallExpr
const * expr
, FunctionDecl
const * callee
, TreatEmpty treatEmpty
,
1779 unsigned size
, std::string
* replacement
)
1781 assert(replacement
!= nullptr);
1783 switch (treatEmpty
) {
1784 case TreatEmpty::DefaultCtor
:
1785 *replacement
= "rtl::OUString default constructor";
1787 case TreatEmpty::CheckEmpty
:
1788 *replacement
= "rtl::OUString::isEmpty";
1790 case TreatEmpty::Error
:
1792 DiagnosticsEngine::Warning
,
1793 "call of '%0' with suspicious empty string constant argument",
1794 getMemberLocation(expr
))
1795 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1801 void StringConstant::handleChar(
1802 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
1803 std::string
const & replacement
, TreatEmpty treatEmpty
, bool literal
,
1804 char const * rewriteFrom
, char const * rewriteTo
)
1811 if (!isStringConstant(
1812 expr
->getArg(arg
)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
1817 if (cont
!= ContentKind::Ascii
) {
1819 DiagnosticsEngine::Warning
,
1820 ("call of '%0' with string constant argument containing non-ASCII"
1822 getMemberLocation(expr
))
1823 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1828 DiagnosticsEngine::Warning
,
1829 ("call of '%0' with string constant argument containing embedded"
1831 getMemberLocation(expr
))
1832 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1837 DiagnosticsEngine::Warning
,
1838 ("call of '%0' with string constant argument lacking a terminating"
1840 getMemberLocation(expr
))
1841 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1844 std::string
repl(replacement
);
1845 checkEmpty(expr
, callee
, treatEmpty
, n
, &repl
);
1846 if (!repl
.empty()) {
1848 expr
, ChangeKind::Char
, callee
->getQualifiedNameAsString(), repl
,
1851 ? PassThrough::EmptyConstantString
1852 : PassThrough::NonEmptyConstantString
)
1854 nonArray
, rewriteFrom
, rewriteTo
);
1858 void StringConstant::handleCharLen(
1859 CallExpr
const * expr
, unsigned arg1
, unsigned arg2
,
1860 FunctionDecl
const * callee
, std::string
const & replacement
,
1861 TreatEmpty treatEmpty
)
1863 // Especially for f(RTL_CONSTASCII_STRINGPARAM("foo")), where
1864 // RTL_CONSTASCII_STRINGPARAM expands to complicated expressions involving
1865 // (&(X)[0] sub-expressions (and it might or might not be better to handle
1866 // that at the level of non-expanded macros instead, but I have not found
1867 // out how to do that yet anyway):
1873 if (!(isStringConstant(
1874 expr
->getArg(arg1
)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
1881 if (compat::EvaluateAsInt(expr
->getArg(arg2
), res
, compiler
.getASTContext())) {
1886 UnaryOperator
const * op
= dyn_cast
<UnaryOperator
>(
1887 expr
->getArg(arg1
)->IgnoreParenImpCasts());
1888 if (op
== nullptr || op
->getOpcode() != UO_AddrOf
) {
1891 ArraySubscriptExpr
const * subs
= dyn_cast
<ArraySubscriptExpr
>(
1892 op
->getSubExpr()->IgnoreParenImpCasts());
1893 if (subs
== nullptr) {
1901 if (!(isStringConstant(
1902 subs
->getBase()->IgnoreParenImpCasts(), &n2
, &nonArray2
,
1903 &cont2
, &emb2
, &trm2
)
1904 && n2
== n
&& cont2
== cont
&& emb2
== emb
&& trm2
== trm
1905 //TODO: same strings
1906 && compat::EvaluateAsInt(subs
->getIdx(), res
, compiler
.getASTContext())
1912 if (cont
!= ContentKind::Ascii
) {
1914 DiagnosticsEngine::Warning
,
1915 ("call of '%0' with string constant argument containing non-ASCII"
1917 getMemberLocation(expr
))
1918 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1923 std::string
repl(replacement
);
1924 checkEmpty(expr
, callee
, treatEmpty
, n
, &repl
);
1926 expr
, ChangeKind::CharLen
, callee
->getQualifiedNameAsString(), repl
,
1927 PassThrough::No
, nonArray
, nullptr, nullptr);
1930 void StringConstant::handleOUStringCtor(
1931 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
1932 bool explicitFunctionalCastNotation
)
1934 handleOUStringCtor(expr
, expr
->getArg(arg
), callee
, explicitFunctionalCastNotation
);
1937 void StringConstant::handleOStringCtor(
1938 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
1939 bool explicitFunctionalCastNotation
)
1941 handleOStringCtor(expr
, expr
->getArg(arg
), callee
, explicitFunctionalCastNotation
);
1944 void StringConstant::handleOUStringCtor(
1945 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
1946 bool explicitFunctionalCastNotation
)
1948 handleStringCtor(expr
, argExpr
, callee
, explicitFunctionalCastNotation
, StringKind::Unicode
);
1951 void StringConstant::handleOStringCtor(
1952 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
1953 bool explicitFunctionalCastNotation
)
1955 handleStringCtor(expr
, argExpr
, callee
, explicitFunctionalCastNotation
, StringKind::Char
);
1958 void StringConstant::handleStringCtor(
1959 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
1960 bool explicitFunctionalCastNotation
, StringKind stringKind
)
1962 auto e0
= argExpr
->IgnoreParenImpCasts();
1963 if (auto const e1
= dyn_cast
<CXXMemberCallExpr
>(e0
)) {
1964 if (auto const e2
= dyn_cast
<CXXConversionDecl
>(e1
->getMethodDecl())) {
1965 if (loplugin::TypeCheck(e2
->getConversionType()).ClassOrStruct("basic_string_view")
1968 e0
= e1
->getImplicitObjectArgument()->IgnoreParenImpCasts();
1972 auto e1
= dyn_cast
<CXXFunctionalCastExpr
>(e0
);
1973 if (e1
== nullptr) {
1974 if (explicitFunctionalCastNotation
) {
1978 e0
= e1
->getSubExpr()->IgnoreParenImpCasts();
1980 auto e2
= dyn_cast
<CXXBindTemporaryExpr
>(e0
);
1981 if (e2
== nullptr) {
1984 auto e3
= dyn_cast
<CXXConstructExpr
>(
1985 e2
->getSubExpr()->IgnoreParenImpCasts());
1986 if (e3
== nullptr) {
1989 if (!loplugin::DeclCheck(e3
->getConstructor()).MemberFunction()
1990 .Class(stringKind
== StringKind::Unicode
? "OUString" : "OString").Namespace("rtl").GlobalNamespace())
1994 if (e3
->getNumArgs() == 0) {
1996 DiagnosticsEngine::Warning
,
1997 ("in call of '%0', replace default-constructed 'OUString' with an"
1998 " empty string literal"),
2000 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2003 if (e3
->getNumArgs() == 1
2004 && e3
->getConstructor()->getNumParams() == 1
2005 && (loplugin::TypeCheck(
2006 e3
->getConstructor()->getParamDecl(0)->getType())
2007 .Typedef(stringKind
== StringKind::Unicode
? "sal_Unicode" : "char").GlobalNamespace()))
2009 // It may not be easy to rewrite OUString(c), esp. given there is no
2010 // OUString ctor taking an OUStringChar arg, so don't warn there:
2011 if (!explicitFunctionalCastNotation
) {
2013 DiagnosticsEngine::Warning
,
2014 ("in call of '%0', replace 'OUString' constructed from a"
2015 " 'sal_Unicode' with an 'OUStringChar'"),
2017 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2021 if (e3
->getNumArgs() != 2) {
2029 if (!isStringConstant(
2030 e3
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
, &emb
,
2035 //TODO: cont, emb, trm
2036 if (rewriter
!= nullptr) {
2037 auto loc1
= e3
->getBeginLoc();
2038 auto range
= e3
->getParenOrBraceRange();
2039 if (loc1
.isFileID() && range
.getBegin().isFileID()
2040 && range
.getEnd().isFileID())
2042 auto loc2
= range
.getBegin();
2043 for (bool first
= true;; first
= false) {
2044 unsigned n
= Lexer::MeasureTokenLength(
2045 loc2
, compiler
.getSourceManager(), compiler
.getLangOpts());
2048 compiler
.getSourceManager().getCharacterData(loc2
), n
);
2049 while (s
.startswith("\\\n")) {
2050 s
= s
.drop_front(2);
2052 && (s
.front() == ' ' || s
.front() == '\t'
2053 || s
.front() == '\n' || s
.front() == '\v'
2054 || s
.front() == '\f'))
2056 s
= s
.drop_front(1);
2059 if (!(s
.empty() || s
.startswith("/*") || s
.startswith("//")
2065 loc2
= loc2
.getLocWithOffset(std::max
<unsigned>(n
, 1));
2067 auto loc3
= range
.getEnd();
2069 auto l
= Lexer::GetBeginningOfToken(
2070 loc3
.getLocWithOffset(-1), compiler
.getSourceManager(),
2071 compiler
.getLangOpts());
2072 unsigned n
= Lexer::MeasureTokenLength(
2073 l
, compiler
.getSourceManager(), compiler
.getLangOpts());
2074 StringRef
s(compiler
.getSourceManager().getCharacterData(l
), n
);
2075 while (s
.startswith("\\\n")) {
2076 s
= s
.drop_front(2);
2078 && (s
.front() == ' ' || s
.front() == '\t'
2079 || s
.front() == '\n' || s
.front() == '\v'
2080 || s
.front() == '\f'))
2082 s
= s
.drop_front(1);
2085 if (!(s
.empty() || s
.startswith("/*") || s
.startswith("//")
2092 if (removeText(CharSourceRange(SourceRange(loc1
, loc2
), false))) {
2093 if (removeText(SourceRange(loc3
, range
.getEnd()))) {
2096 report(DiagnosticsEngine::Fatal
, "Corrupt rewrite", loc3
)
2097 << expr
->getSourceRange();
2103 DiagnosticsEngine::Warning
,
2104 ("in call of '%0', replace 'OUString' constructed from a string literal"
2105 " directly with the string literal"),
2107 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2110 void StringConstant::handleFunArgOstring(
2111 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
)
2113 auto argExpr
= expr
->getArg(arg
)->IgnoreParenImpCasts();
2119 if (isStringConstant(argExpr
, &n
, &nonArray
, &cont
, &emb
, &trm
)) {
2120 if (cont
!= ContentKind::Ascii
|| emb
) {
2125 DiagnosticsEngine::Warning
,
2126 ("call of '%0' with string constant argument lacking a"
2127 " terminating NULL"),
2128 getMemberLocation(expr
))
2129 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2133 checkEmpty(expr
, callee
, TreatEmpty::Error
, n
, &repl
);
2136 DiagnosticsEngine::Warning
,
2137 ("in call of '%0' with non-array string constant argument,"
2138 " turn the non-array string constant into an array"),
2139 getMemberLocation(expr
))
2140 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2142 } else if (auto cexpr
= lookForCXXConstructExpr(argExpr
)) {
2143 auto classdecl
= cexpr
->getConstructor()->getParent();
2144 if (loplugin::DeclCheck(classdecl
).Class("OString").Namespace("rtl")
2147 switch (cexpr
->getConstructor()->getNumParams()) {
2150 DiagnosticsEngine::Warning
,
2151 ("in call of '%0', replace empty %1 constructor with empty"
2153 cexpr
->getLocation())
2154 << callee
->getQualifiedNameAsString() << classdecl
2155 << expr
->getSourceRange();
2158 if (isStringConstant(
2159 cexpr
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
,
2163 if (compat::EvaluateAsInt(cexpr
->getArg(1),
2164 res
, compiler
.getASTContext()))
2166 if (res
== n
&& !emb
&& trm
) {
2168 DiagnosticsEngine::Warning
,
2169 ("in call of '%0', elide explicit %1"
2171 cexpr
->getLocation())
2172 << callee
->getQualifiedNameAsString()
2173 << classdecl
<< adviseNonArray(nonArray
)
2174 << expr
->getSourceRange();
2179 DiagnosticsEngine::Warning
,
2180 ("call of %0 constructor with string constant"
2181 " argument containing embedded NULLs"),
2182 cexpr
->getLocation())
2183 << classdecl
<< cexpr
->getSourceRange();
2188 DiagnosticsEngine::Warning
,
2189 ("call of %0 constructor with string constant"
2190 " argument lacking a terminating NULL"),
2191 cexpr
->getLocation())
2192 << classdecl
<< cexpr
->getSourceRange();
2196 DiagnosticsEngine::Warning
,
2197 "in call of '%0', elide explicit %1 constructor%2",
2198 cexpr
->getLocation())
2199 << callee
->getQualifiedNameAsString() << classdecl
2200 << adviseNonArray(nonArray
)
2201 << expr
->getSourceRange();
2212 loplugin::Plugin::Registration
< StringConstant
> X("stringconstant", true);
2216 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */