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 (isa_and_nonnull
<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 VisitCXXMemberCallExpr(CXXMemberCallExpr
const * expr
);
196 bool VisitCXXConstructExpr(CXXConstructExpr
const * expr
);
198 bool VisitReturnStmt(ReturnStmt
const * stmt
);
201 enum class ContentKind
{ Ascii
, Utf8
, Arbitrary
};
203 enum class TreatEmpty
{ DefaultCtor
, CheckEmpty
, Error
};
205 enum class ChangeKind
{ Char
, CharLen
, SingleChar
, OUStringChar
};
207 enum class PassThrough
{ No
, EmptyConstantString
, NonEmptyConstantString
};
209 std::string
describeChangeKind(ChangeKind kind
);
211 bool isStringConstant(
212 Expr
const * expr
, unsigned * size
, bool * nonArray
,
213 ContentKind
* content
, bool * embeddedNuls
, bool * terminatingNul
,
214 std::vector
<char32_t
> * utf8Content
= nullptr);
216 bool isZero(Expr
const * expr
);
219 Expr
const * expr
, ChangeKind kind
, std::string
const & original
,
220 std::string
const & replacement
, PassThrough pass
, bool nonArray
,
221 char const * rewriteFrom
, char const * rewriteTo
);
224 CallExpr
const * expr
, FunctionDecl
const * callee
,
225 TreatEmpty treatEmpty
, unsigned size
, std::string
* replacement
);
228 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
229 std::string
const & replacement
, TreatEmpty treatEmpty
, bool literal
,
230 char const * rewriteFrom
= nullptr, char const * rewriteTo
= nullptr);
233 CallExpr
const * expr
, unsigned arg1
, unsigned arg2
,
234 FunctionDecl
const * callee
, std::string
const & replacement
,
235 TreatEmpty treatEmpty
);
237 void handleOUStringCtor(
238 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
239 bool explicitFunctionalCastNotation
);
241 void handleOStringCtor(
242 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
243 bool explicitFunctionalCastNotation
);
245 void handleOUStringCtor(
246 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
247 bool explicitFunctionalCastNotation
);
249 void handleOStringCtor(
250 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
251 bool explicitFunctionalCastNotation
);
253 enum class StringKind
{ Unicode
, Char
};
254 void handleStringCtor(
255 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
256 bool explicitFunctionalCastNotation
, StringKind stringKind
);
258 void handleFunArgOstring(
259 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
);
261 std::stack
<QualType
> returnTypes_
;
262 std::stack
<Expr
const *> calls_
;
265 void StringConstant::run() {
266 if (compiler
.getLangOpts().CPlusPlus
267 && compiler
.getPreprocessor().getIdentifierInfo(
268 "LIBO_INTERNAL_ONLY")->hasMacroDefinition())
269 //TODO: some parts of it are useful for external code, too
271 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
275 bool StringConstant::TraverseCallExpr(CallExpr
* expr
) {
276 if (!WalkUpFromCallExpr(expr
)) {
281 for (auto * e
: expr
->children()) {
282 if (!TraverseStmt(e
)) {
291 bool StringConstant::TraverseCXXMemberCallExpr(CXXMemberCallExpr
* expr
) {
292 if (!WalkUpFromCXXMemberCallExpr(expr
)) {
297 for (auto * e
: expr
->children()) {
298 if (!TraverseStmt(e
)) {
307 bool StringConstant::TraverseCXXOperatorCallExpr(CXXOperatorCallExpr
* expr
)
309 if (!WalkUpFromCXXOperatorCallExpr(expr
)) {
314 for (auto * e
: expr
->children()) {
315 if (!TraverseStmt(e
)) {
324 bool StringConstant::TraverseCXXConstructExpr(CXXConstructExpr
* expr
) {
325 if (!WalkUpFromCXXConstructExpr(expr
)) {
330 for (auto * e
: expr
->children()) {
331 if (!TraverseStmt(e
)) {
340 bool StringConstant::VisitCallExpr(CallExpr
const * expr
) {
341 if (ignoreLocation(expr
)) {
344 FunctionDecl
const * fdecl
= expr
->getDirectCallee();
345 if (fdecl
== nullptr) {
348 for (unsigned i
= 0; i
!= fdecl
->getNumParams(); ++i
) {
349 auto t
= fdecl
->getParamDecl(i
)->getType();
350 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
351 .LvalueReference().Const().NotSubstTemplateTypeParmType()
352 .Class("OUString").Namespace("rtl").GlobalNamespace())
354 if (!(isLhsOfAssignment(fdecl
, i
)
355 || hasOverloads(fdecl
, expr
->getNumArgs())))
357 handleOUStringCtor(expr
, i
, fdecl
, true);
360 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
361 .LvalueReference().Const().NotSubstTemplateTypeParmType()
362 .Class("OString").Namespace("rtl").GlobalNamespace())
364 if (!(isLhsOfAssignment(fdecl
, i
)
365 || hasOverloads(fdecl
, expr
->getNumArgs())))
367 handleOStringCtor(expr
, i
, fdecl
, true);
371 loplugin::DeclCheck
dc(fdecl
);
372 //TODO: u.compareToAscii("foo") -> u.???("foo")
373 //TODO: u.compareToIgnoreAsciiCaseAscii("foo") -> u.???("foo")
374 if ((dc
.Function("createFromAscii").Class("OUString").Namespace("rtl")
376 && fdecl
->getNumParams() == 1)
378 // OUString::createFromAscii("foo") -> OUString("foo")
380 expr
, 0, fdecl
, "rtl::OUString constructor",
381 TreatEmpty::DefaultCtor
, true);
384 if ((dc
.Function("endsWithAsciiL").Class("OUString").Namespace("rtl")
386 && fdecl
->getNumParams() == 2)
388 // u.endsWithAsciiL("foo", 3) -> u.endsWith("foo"):
390 expr
, 0, 1, fdecl
, "rtl::OUString::endsWith", TreatEmpty::Error
);
393 if ((dc
.Function("endsWithIgnoreAsciiCaseAsciiL").Class("OUString")
394 .Namespace("rtl").GlobalNamespace())
395 && fdecl
->getNumParams() == 2)
397 // u.endsWithIgnoreAsciiCaseAsciiL("foo", 3) ->
398 // u.endsWithIgnoreAsciiCase("foo"):
400 expr
, 0, 1, fdecl
, "rtl::OUString::endsWithIgnoreAsciiCase",
404 if ((dc
.Function("equalsAscii").Class("OUString").Namespace("rtl")
406 && fdecl
->getNumParams() == 1)
408 // u.equalsAscii("foo") -> u == "foo":
410 expr
, 0, fdecl
, "operator ==", TreatEmpty::CheckEmpty
, false);
413 if ((dc
.Function("equalsAsciiL").Class("OUString").Namespace("rtl")
415 && fdecl
->getNumParams() == 2)
417 // u.equalsAsciiL("foo", 3) -> u == "foo":
418 handleCharLen(expr
, 0, 1, fdecl
, "operator ==", TreatEmpty::CheckEmpty
);
421 if ((dc
.Function("equalsIgnoreAsciiCaseAscii").Class("OUString")
422 .Namespace("rtl").GlobalNamespace())
423 && fdecl
->getNumParams() == 1)
425 // u.equalsIgnoreAsciiCaseAscii("foo") ->
426 // u.equalsIngoreAsciiCase("foo"):
428 auto file
= getFilenameOfLocation(
429 compiler
.getSourceManager().getSpellingLoc(expr
->getBeginLoc()));
430 if (loplugin::isSamePathname(
431 file
, SRCDIR
"/sal/qa/rtl/strings/test_oustring_compare.cxx"))
436 expr
, 0, fdecl
, "rtl::OUString::equalsIgnoreAsciiCase",
437 TreatEmpty::CheckEmpty
, false);
440 if ((dc
.Function("equalsIgnoreAsciiCaseAsciiL").Class("OUString")
441 .Namespace("rtl").GlobalNamespace())
442 && fdecl
->getNumParams() == 2)
444 // u.equalsIgnoreAsciiCaseAsciiL("foo", 3) ->
445 // u.equalsIngoreAsciiCase("foo"):
446 auto file
= getFilenameOfLocation(
447 compiler
.getSourceManager().getSpellingLoc(expr
->getBeginLoc()));
448 if (loplugin::isSamePathname(
449 file
, SRCDIR
"/sal/qa/rtl/strings/test_oustring_compare.cxx"))
454 expr
, 0, 1, fdecl
, "rtl::OUString::equalsIgnoreAsciiCase",
455 TreatEmpty::CheckEmpty
);
458 if ((dc
.Function("indexOfAsciiL").Class("OUString").Namespace("rtl")
460 && fdecl
->getNumParams() == 3)
462 assert(expr
->getNumArgs() == 3);
463 // u.indexOfAsciiL("foo", 3, i) -> u.indexOf("foo", i):
465 expr
, 0, 1, fdecl
, "rtl::OUString::indexOf", TreatEmpty::Error
);
468 if ((dc
.Function("lastIndexOfAsciiL").Class("OUString").Namespace("rtl")
470 && fdecl
->getNumParams() == 2)
472 // u.lastIndexOfAsciiL("foo", 3) -> u.lastIndexOf("foo"):
474 expr
, 0, 1, fdecl
, "rtl::OUString::lastIndexOf", TreatEmpty::Error
);
477 if ((dc
.Function("matchAsciiL").Class("OUString").Namespace("rtl")
479 && fdecl
->getNumParams() == 3)
481 assert(expr
->getNumArgs() == 3);
482 // u.matchAsciiL("foo", 3, i) -> u.match("foo", i):
485 (isZero(expr
->getArg(2))
486 ? std::string("rtl::OUString::startsWith")
487 : std::string("rtl::OUString::match")),
491 if ((dc
.Function("matchIgnoreAsciiCaseAsciiL").Class("OUString")
492 .Namespace("rtl").GlobalNamespace())
493 && fdecl
->getNumParams() == 3)
495 assert(expr
->getNumArgs() == 3);
496 // u.matchIgnoreAsciiCaseAsciiL("foo", 3, i) ->
497 // u.matchIgnoreAsciiCase("foo", i):
500 (isZero(expr
->getArg(2))
501 ? std::string("rtl::OUString::startsWithIgnoreAsciiCase")
502 : std::string("rtl::OUString::matchIgnoreAsciiCase")),
506 if ((dc
.Function("reverseCompareToAsciiL").Class("OUString")
507 .Namespace("rtl").GlobalNamespace())
508 && fdecl
->getNumParams() == 2)
510 // u.reverseCompareToAsciiL("foo", 3) -> u.reverseCompareTo("foo"):
512 expr
, 0, 1, fdecl
, "rtl::OUString::reverseCompareTo",
516 if ((dc
.Function("reverseCompareTo").Class("OUString").Namespace("rtl")
518 && fdecl
->getNumParams() == 1)
520 handleOUStringCtor(expr
, 0, fdecl
, false);
523 if ((dc
.Function("equalsIgnoreAsciiCase").Class("OUString").Namespace("rtl")
525 && fdecl
->getNumParams() == 1)
527 handleOUStringCtor(expr
, 0, fdecl
, false);
530 if ((dc
.Function("match").Class("OUString").Namespace("rtl")
532 && fdecl
->getNumParams() == 2)
534 handleOUStringCtor(expr
, 0, fdecl
, false);
537 if ((dc
.Function("matchIgnoreAsciiCase").Class("OUString").Namespace("rtl")
539 && fdecl
->getNumParams() == 2)
541 handleOUStringCtor(expr
, 0, fdecl
, false);
544 if ((dc
.Function("startsWith").Class("OUString").Namespace("rtl")
546 && fdecl
->getNumParams() == 2)
548 handleOUStringCtor(expr
, 0, fdecl
, false);
551 if ((dc
.Function("startsWithIgnoreAsciiCase").Class("OUString")
552 .Namespace("rtl").GlobalNamespace())
553 && fdecl
->getNumParams() == 2)
555 handleOUStringCtor(expr
, 0, fdecl
, false);
558 if ((dc
.Function("endsWith").Class("OUString").Namespace("rtl")
560 && fdecl
->getNumParams() == 2)
562 handleOUStringCtor(expr
, 0, fdecl
, false);
565 if ((dc
.Function("endsWithIgnoreAsciiCase").Class("OUString")
566 .Namespace("rtl").GlobalNamespace())
567 && fdecl
->getNumParams() == 2)
569 handleOUStringCtor(expr
, 0, fdecl
, false);
572 if ((dc
.Function("indexOf").Class("OUString").Namespace("rtl")
574 && fdecl
->getNumParams() == 2)
576 handleOUStringCtor(expr
, 0, fdecl
, false);
579 if ((dc
.Function("lastIndexOf").Class("OUString").Namespace("rtl")
581 && fdecl
->getNumParams() == 1)
583 handleOUStringCtor(expr
, 0, fdecl
, false);
586 if ((dc
.Function("replaceFirst").Class("OUString").Namespace("rtl")
588 && fdecl
->getNumParams() == 3)
590 handleOUStringCtor(expr
, 0, fdecl
, false);
591 handleOUStringCtor(expr
, 1, fdecl
, false);
594 if ((dc
.Function("replaceAll").Class("OUString").Namespace("rtl")
596 && (fdecl
->getNumParams() == 2 || fdecl
->getNumParams() == 3))
598 handleOUStringCtor(expr
, 0, fdecl
, false);
599 handleOUStringCtor(expr
, 1, fdecl
, false);
602 if ((dc
.Operator(OO_PlusEqual
).Class("OUString").Namespace("rtl")
604 && fdecl
->getNumParams() == 1)
607 expr
, dyn_cast
<CXXOperatorCallExpr
>(expr
) == nullptr ? 0 : 1,
611 if ((dc
.Function("equals").Class("OUString").Namespace("rtl")
613 && fdecl
->getNumParams() == 1)
620 if (!isStringConstant(
621 expr
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
626 if (cont
!= ContentKind::Ascii
) {
628 DiagnosticsEngine::Warning
,
629 ("call of '%0' with string constant argument containing"
630 " non-ASCII characters"),
632 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
636 DiagnosticsEngine::Warning
,
637 ("call of '%0' with string constant argument containing"
640 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
644 DiagnosticsEngine::Warning
,
645 ("rewrite call of '%0' with empty string constant argument as"
646 " call of 'rtl::OUString::isEmpty'"),
648 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
652 if (dc
.Operator(OO_EqualEqual
).Namespace("rtl").GlobalNamespace()
653 && fdecl
->getNumParams() == 2)
655 for (unsigned i
= 0; i
!= 2; ++i
) {
661 if (!isStringConstant(
662 expr
->getArg(i
)->IgnoreParenImpCasts(), &n
, &nonArray
,
667 if (cont
!= ContentKind::Ascii
) {
669 DiagnosticsEngine::Warning
,
670 ("call of '%0' with string constant argument containing"
671 " non-ASCII characters"),
673 << fdecl
->getQualifiedNameAsString()
674 << expr
->getSourceRange();
678 DiagnosticsEngine::Warning
,
679 ("call of '%0' with string constant argument containing"
682 << fdecl
->getQualifiedNameAsString()
683 << expr
->getSourceRange();
687 DiagnosticsEngine::Warning
,
688 ("rewrite call of '%0' with empty string constant argument"
689 " as call of 'rtl::OUString::isEmpty'"),
691 << fdecl
->getQualifiedNameAsString()
692 << expr
->getSourceRange();
697 if (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl").GlobalNamespace()
698 && fdecl
->getNumParams() == 2)
700 for (unsigned i
= 0; i
!= 2; ++i
) {
706 if (!isStringConstant(
707 expr
->getArg(i
)->IgnoreParenImpCasts(), &n
, &nonArray
,
712 if (cont
!= ContentKind::Ascii
) {
714 DiagnosticsEngine::Warning
,
715 ("call of '%0' with string constant argument containing"
716 " non-ASCII characters"),
718 << fdecl
->getQualifiedNameAsString()
719 << expr
->getSourceRange();
723 DiagnosticsEngine::Warning
,
724 ("call of '%0' with string constant argument containing"
727 << fdecl
->getQualifiedNameAsString()
728 << expr
->getSourceRange();
732 DiagnosticsEngine::Warning
,
733 ("rewrite call of '%0' with empty string constant argument"
734 " as call of '!rtl::OUString::isEmpty'"),
736 << fdecl
->getQualifiedNameAsString()
737 << expr
->getSourceRange();
742 if (dc
.Operator(OO_Equal
).Namespace("rtl").GlobalNamespace()
743 && fdecl
->getNumParams() == 1)
750 if (!isStringConstant(
751 expr
->getArg(1)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
756 if (cont
!= ContentKind::Ascii
) {
758 DiagnosticsEngine::Warning
,
759 ("call of '%0' with string constant argument containing"
760 " non-ASCII characters"),
762 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
766 DiagnosticsEngine::Warning
,
767 ("call of '%0' with string constant argument containing"
770 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
774 DiagnosticsEngine::Warning
,
775 ("rewrite call of '%0' with empty string constant argument as"
776 " call of 'rtl::OUString::clear'"),
778 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
783 if (dc
.Function("append").Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
784 && fdecl
->getNumParams() == 1)
786 handleChar(expr
, 0, fdecl
, "", TreatEmpty::Error
, false);
789 if ((dc
.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
791 && fdecl
->getNumParams() == 1)
793 // u.appendAscii("foo") -> u.append("foo")
795 expr
, 0, fdecl
, "rtl::OUStringBuffer::append", TreatEmpty::Error
,
796 true, "appendAscii", "append");
799 if ((dc
.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
801 && fdecl
->getNumParams() == 2)
803 // u.appendAscii("foo", 3) -> u.append("foo"):
805 expr
, 0, 1, fdecl
, "rtl::OUStringBuffer::append",
809 if (dc
.Function("append").Class("OStringBuffer").Namespace("rtl")
812 switch (fdecl
->getNumParams()) {
814 handleFunArgOstring(expr
, 0, fdecl
);
818 // b.append("foo", 3) -> b.append("foo"):
819 auto file
= getFilenameOfLocation(
820 compiler
.getSourceManager().getSpellingLoc(
821 expr
->getBeginLoc()));
822 if (loplugin::isSamePathname(
824 SRCDIR
"/sal/qa/OStringBuffer/rtl_OStringBuffer.cxx"))
829 expr
, 0, 1, fdecl
, "rtl::OStringBuffer::append",
838 if (dc
.Function("insert").Class("OStringBuffer").Namespace("rtl")
841 switch (fdecl
->getNumParams()) {
843 handleFunArgOstring(expr
, 1, fdecl
);
847 // b.insert(i, "foo", 3) -> b.insert(i, "foo"):
849 expr
, 1, 2, fdecl
, "rtl::OStringBuffer::insert",
861 bool StringConstant::VisitCXXMemberCallExpr(CXXMemberCallExpr
const * expr
) {
862 if (ignoreLocation(expr
)) {
865 FunctionDecl
const * fdecl
= expr
->getDirectCallee();
866 if (fdecl
== nullptr) {
869 auto const c
= loplugin::DeclCheck(fdecl
).Function("getStr");
870 if ((c
.Class("OString").Namespace("rtl").GlobalNamespace()
871 || c
.Class("OUString").Namespace("rtl").GlobalNamespace())
872 && fdecl
->getNumParams() == 0)
874 auto const e1
= expr
->getImplicitObjectArgument()->IgnoreImplicit()->IgnoreParens();
875 if (auto const e2
= dyn_cast
<CXXTemporaryObjectExpr
>(e1
)) {
876 if (e2
->getNumArgs() != 0) {
880 DiagnosticsEngine::Warning
,
881 "in call of '%0', replace default-constructed %1 directly with an empty %select{ordinary|UTF-16}2 string literal",
883 << fdecl
->getQualifiedNameAsString() << e2
->getType() << bool(loplugin::TypeCheck(e2
->getType()).Class("OUString")) << expr
->getSourceRange();
886 if (auto const e2
= dyn_cast
<CXXFunctionalCastExpr
>(e1
)) {
887 auto const e3
= dyn_cast
<clang::StringLiteral
>(e2
->getSubExprAsWritten()->IgnoreParens());
892 DiagnosticsEngine::Warning
,
893 "in call of '%0', replace %1 constructed from a string literal directly with %select{the|a UTF-16}2 string literal",
895 << fdecl
->getQualifiedNameAsString() << e2
->getType() << (loplugin::TypeCheck(e2
->getType()).Class("OUString") && !e3
->isUTF16()) << expr
->getSourceRange();
902 bool StringConstant::VisitCXXConstructExpr(CXXConstructExpr
const * expr
) {
903 if (ignoreLocation(expr
)) {
906 auto classdecl
= expr
->getConstructor()->getParent();
907 if (loplugin::DeclCheck(classdecl
)
908 .Class("OUString").Namespace("rtl").GlobalNamespace())
913 switch (expr
->getConstructor()->getNumParams()) {
915 if (!loplugin::TypeCheck(
916 expr
->getConstructor()->getParamDecl(0)->getType())
917 .Typedef("sal_Unicode").GlobalNamespace())
921 kind
= ChangeKind::SingleChar
;
922 pass
= PassThrough::NonEmptyConstantString
;
927 auto arg
= expr
->getArg(0);
928 if (loplugin::TypeCheck(arg
->getType())
929 .Class("OUStringChar_").Namespace("rtl")
932 kind
= ChangeKind::OUStringChar
;
933 pass
= PassThrough::NonEmptyConstantString
;
941 if (!isStringConstant(
942 arg
->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
947 if (cont
!= ContentKind::Ascii
) {
949 DiagnosticsEngine::Warning
,
950 ("construction of %0 with string constant argument"
951 " containing non-ASCII characters"),
953 << classdecl
<< expr
->getSourceRange();
957 DiagnosticsEngine::Warning
,
958 ("construction of %0 with string constant argument"
959 " containing embedded NULLs"),
961 << classdecl
<< expr
->getSourceRange();
963 kind
= ChangeKind::Char
;
965 ? PassThrough::EmptyConstantString
966 : PassThrough::NonEmptyConstantString
;
978 std::vector
<char32_t
> utf8Cont
;
979 if (!isStringConstant(
980 expr
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
,
981 &cont
, &emb
, &trm
, &utf8Cont
))
986 if (!compat::EvaluateAsInt(expr
->getArg(1),
987 res
, compiler
.getASTContext()))
993 DiagnosticsEngine::Warning
,
994 ("suspicious 'rtl::OUString' constructor with literal"
995 " of length %0 and non-matching length argument %1"),
997 << n
<< compat::toString(res
, 10) << expr
->getSourceRange();
1001 if (!compat::EvaluateAsInt(expr
->getArg(2),
1002 enc
, compiler
.getASTContext()))
1006 auto const encIsAscii
= enc
== 11; // RTL_TEXTENCODING_ASCII_US
1007 auto const encIsUtf8
= enc
== 76; // RTL_TEXTENCODING_UTF8
1008 if (!compat::EvaluateAsInt(expr
->getArg(3),
1009 res
, compiler
.getASTContext())
1010 || res
!= 0x333) // OSTRING_TO_OUSTRING_CVTFLAGS
1014 if (!encIsAscii
&& cont
== ContentKind::Ascii
) {
1016 DiagnosticsEngine::Warning
,
1017 ("suspicious 'rtl::OUString' constructor with text"
1018 " encoding %0 but plain ASCII content; use"
1019 " 'RTL_TEXTENCODING_ASCII_US' instead"),
1020 expr
->getArg(2)->getExprLoc())
1021 << compat::toString(enc
, 10) << expr
->getSourceRange();
1025 if (cont
== ContentKind::Arbitrary
) {
1027 DiagnosticsEngine::Warning
,
1028 ("suspicious 'rtl::OUString' constructor with text"
1029 " encoding 'RTL_TEXTENCODING_UTF8' but non-UTF-8"
1031 expr
->getArg(0)->getExprLoc())
1032 << expr
->getSourceRange();
1034 assert(cont
== ContentKind::Utf8
);
1035 //TODO: keep original content as much as possible
1036 std::ostringstream s
;
1037 for (auto const c
: utf8Cont
) {
1040 } else if (c
== '"') {
1042 } else if (c
== '\a') {
1044 } else if (c
== '\b') {
1046 } else if (c
== '\f') {
1048 } else if (c
== '\n') {
1050 } else if (c
== '\r') {
1052 } else if (c
== '\t') {
1054 } else if (c
== '\v') {
1056 } else if (c
<= 0x1F || c
== 0x7F) {
1057 s
<< "\\x" << std::oct
<< std::setw(3)
1058 << std::setfill('0')
1059 << static_cast<std::uint_least32_t>(c
);
1060 } else if (c
< 0x7F) {
1062 } else if (c
<= 0xFFFF) {
1063 s
<< "\\u" << std::hex
<< std::uppercase
1064 << std::setw(4) << std::setfill('0')
1065 << static_cast<std::uint_least32_t>(c
);
1067 assert(c
<= 0x10FFFF);
1068 s
<< "\\U" << std::hex
<< std::uppercase
1069 << std::setw(8) << std::setfill('0')
1070 << static_cast<std::uint_least32_t>(c
);
1074 DiagnosticsEngine::Warning
,
1075 ("simplify construction of %0 with UTF-8 content as"
1076 " OUString(u\"%1\")"),
1078 << classdecl
<< s
.str() << expr
->getSourceRange();
1083 if (cont
!= ContentKind::Ascii
|| emb
) {
1084 // cf. remaining uses of RTL_CONSTASCII_USTRINGPARAM
1087 kind
= ChangeKind::Char
;
1089 ? PassThrough::EmptyConstantString
1090 : PassThrough::NonEmptyConstantString
;
1097 if (!calls_
.empty()) {
1098 Expr
const * call
= calls_
.top();
1099 CallExpr::const_arg_iterator argsBeg
;
1100 CallExpr::const_arg_iterator argsEnd
;
1101 if (isa
<CallExpr
>(call
)) {
1102 argsBeg
= cast
<CallExpr
>(call
)->arg_begin();
1103 argsEnd
= cast
<CallExpr
>(call
)->arg_end();
1104 } else if (isa
<CXXConstructExpr
>(call
)) {
1105 argsBeg
= cast
<CXXConstructExpr
>(call
)->arg_begin();
1106 argsEnd
= cast
<CXXConstructExpr
>(call
)->arg_end();
1110 for (auto i(argsBeg
); i
!= argsEnd
; ++i
) {
1111 Expr
const * e
= (*i
)->IgnoreParenImpCasts();
1112 if (isa
<MaterializeTemporaryExpr
>(e
)) {
1113 e
= cast
<MaterializeTemporaryExpr
>(e
)->getSubExpr()
1114 ->IgnoreParenImpCasts();
1116 if (isa
<CXXFunctionalCastExpr
>(e
)) {
1117 e
= cast
<CXXFunctionalCastExpr
>(e
)->getSubExpr()
1118 ->IgnoreParenImpCasts();
1120 if (isa
<CXXBindTemporaryExpr
>(e
)) {
1121 e
= cast
<CXXBindTemporaryExpr
>(e
)->getSubExpr()
1122 ->IgnoreParenImpCasts();
1125 if (isa
<CallExpr
>(call
)) {
1126 FunctionDecl
const * fdecl
1127 = cast
<CallExpr
>(call
)->getDirectCallee();
1128 if (fdecl
== nullptr) {
1131 loplugin::DeclCheck
dc(fdecl
);
1132 if (pass
== PassThrough::EmptyConstantString
) {
1133 if ((dc
.Function("equals").Class("OUString")
1134 .Namespace("rtl").GlobalNamespace())
1135 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1136 .GlobalNamespace()))
1139 DiagnosticsEngine::Warning
,
1140 ("rewrite call of '%0' with construction of"
1141 " %1 with empty string constant argument"
1142 " as call of 'rtl::OUString::isEmpty'"),
1143 getMemberLocation(call
))
1144 << fdecl
->getQualifiedNameAsString()
1145 << classdecl
<< call
->getSourceRange();
1148 if (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl")
1152 DiagnosticsEngine::Warning
,
1153 ("rewrite call of '%0' with construction of"
1154 " %1 with empty string constant argument"
1155 " as call of '!rtl::OUString::isEmpty'"),
1156 getMemberLocation(call
))
1157 << fdecl
->getQualifiedNameAsString()
1158 << classdecl
<< call
->getSourceRange();
1161 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
1163 || (dc
.Operator(OO_Plus
).Class("OUString")
1164 .Namespace("rtl").GlobalNamespace()))
1167 DiagnosticsEngine::Warning
,
1168 ("call of '%0' with suspicious construction"
1169 " of %1 with empty string constant"
1171 getMemberLocation(call
))
1172 << fdecl
->getQualifiedNameAsString()
1173 << classdecl
<< call
->getSourceRange();
1176 if (dc
.Operator(OO_Equal
).Class("OUString")
1177 .Namespace("rtl").GlobalNamespace())
1180 DiagnosticsEngine::Warning
,
1181 ("rewrite call of '%0' with construction of"
1182 " %1 with empty string constant argument"
1183 " as call of 'rtl::OUString::clear'"),
1184 getMemberLocation(call
))
1185 << fdecl
->getQualifiedNameAsString()
1186 << classdecl
<< call
->getSourceRange();
1190 assert(pass
== PassThrough::NonEmptyConstantString
);
1191 if (dc
.Function("equals").Class("OUString")
1192 .Namespace("rtl").GlobalNamespace())
1195 DiagnosticsEngine::Warning
,
1196 ("rewrite call of '%0' with construction of"
1197 " %1 with %2 as 'operator =='"),
1198 getMemberLocation(call
))
1199 << fdecl
->getQualifiedNameAsString()
1200 << classdecl
<< describeChangeKind(kind
)
1201 << call
->getSourceRange();
1204 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
1206 || (dc
.Operator(OO_Plus
).Class("OUString")
1207 .Namespace("rtl").GlobalNamespace())
1208 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1210 || (dc
.Operator(OO_ExclaimEqual
)
1211 .Namespace("rtl").GlobalNamespace()))
1213 if (dc
.Operator(OO_Plus
).Namespace("rtl")
1216 auto file
= getFilenameOfLocation(
1217 compiler
.getSourceManager()
1219 expr
->getBeginLoc()));
1220 if (loplugin::isSamePathname(
1223 "/sal/qa/rtl/strings/test_ostring_concat.cxx"))
1224 || loplugin::isSamePathname(
1227 "/sal/qa/rtl/strings/test_oustring_concat.cxx")))
1232 auto loc
= expr
->getArg(0)->getBeginLoc();
1233 while (compiler
.getSourceManager()
1234 .isMacroArgExpansion(loc
))
1236 loc
= compiler
.getSourceManager()
1237 .getImmediateMacroCallerLoc(loc
);
1239 if (kind
== ChangeKind::SingleChar
) {
1241 DiagnosticsEngine::Warning
,
1242 ("rewrite construction of %0 with %1 in"
1243 " call of '%2' as construction of"
1245 getMemberLocation(expr
))
1246 << classdecl
<< describeChangeKind(kind
)
1247 << fdecl
->getQualifiedNameAsString()
1248 << expr
->getSourceRange();
1251 DiagnosticsEngine::Warning
,
1252 ("elide construction of %0 with %1 in"
1254 getMemberLocation(expr
))
1255 << classdecl
<< describeChangeKind(kind
)
1256 << fdecl
->getQualifiedNameAsString()
1257 << expr
->getSourceRange();
1262 } else if (isa
<CXXConstructExpr
>(call
)) {
1271 DiagnosticsEngine::Warning
,
1272 "simplify construction of %0 with %1", expr
->getExprLoc())
1273 << classdecl
<< describeChangeKind(kind
)
1274 << expr
->getSourceRange();
1279 auto consDecl
= expr
->getConstructor();
1280 for (unsigned i
= 0; i
!= consDecl
->getNumParams(); ++i
) {
1281 auto t
= consDecl
->getParamDecl(i
)->getType();
1282 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
1283 .LvalueReference().Const().NotSubstTemplateTypeParmType()
1284 .Class("OUString").Namespace("rtl").GlobalNamespace())
1286 auto argExpr
= expr
->getArg(i
);
1287 if (argExpr
&& i
<= consDecl
->getNumParams())
1289 if (!hasOverloads(consDecl
, expr
->getNumArgs()))
1291 handleOUStringCtor(expr
, argExpr
, consDecl
, true);
1295 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
1296 .LvalueReference().Const().NotSubstTemplateTypeParmType()
1297 .Class("OString").Namespace("rtl").GlobalNamespace())
1299 auto argExpr
= expr
->getArg(i
);
1300 if (argExpr
&& i
<= consDecl
->getNumParams())
1302 if (!hasOverloads(consDecl
, expr
->getNumArgs()))
1304 handleOStringCtor(expr
, argExpr
, consDecl
, true);
1313 bool StringConstant::VisitReturnStmt(ReturnStmt
const * stmt
) {
1314 if (ignoreLocation(stmt
)) {
1317 auto const e1
= stmt
->getRetValue();
1318 if (e1
== nullptr) {
1321 auto const tc1
= loplugin::TypeCheck(e1
->getType().getTypePtr());
1322 if (!(tc1
.Class("OString").Namespace("rtl").GlobalNamespace()
1323 || tc1
.Class("OUString").Namespace("rtl").GlobalNamespace()))
1327 assert(!returnTypes_
.empty());
1328 auto const tc2
= loplugin::TypeCheck(returnTypes_
.top().getTypePtr());
1329 if (!(tc2
.Class("OString").Namespace("rtl").GlobalNamespace()
1330 || tc2
.Class("OUString").Namespace("rtl").GlobalNamespace()))
1334 auto const e2
= dyn_cast
<CXXFunctionalCastExpr
>(e1
->IgnoreImplicit());
1335 if (e2
== nullptr) {
1338 auto const e3
= dyn_cast
<CXXBindTemporaryExpr
>(e2
->getSubExpr());
1339 if (e3
== nullptr) {
1342 auto const e4
= dyn_cast
<CXXConstructExpr
>(e3
->getSubExpr());
1343 if (e4
== nullptr) {
1346 if (e4
->getNumArgs() != 2) {
1349 auto const t
= e4
->getArg(0)->getType();
1350 if (!(t
.isConstQualified() && t
->isConstantArrayType())) {
1353 auto const e5
= e4
->getArg(1);
1354 if (!(isa
<CXXDefaultArgExpr
>(e5
)
1355 && (loplugin::TypeCheck(e5
->getType()).Struct("Dummy").Namespace("libreoffice_internal")
1356 .Namespace("rtl").GlobalNamespace())))
1360 report(DiagnosticsEngine::Warning
, "elide constructor call", e1
->getBeginLoc())
1361 << e1
->getSourceRange();
1365 std::string
StringConstant::describeChangeKind(ChangeKind kind
) {
1367 case ChangeKind::Char
:
1368 return "string constant argument";
1369 case ChangeKind::CharLen
:
1370 return "string constant and matching length arguments";
1371 case ChangeKind::SingleChar
:
1372 return "sal_Unicode argument";
1373 case ChangeKind::OUStringChar
:
1374 return "OUStringChar argument";
1376 llvm_unreachable("unknown change kind");
1379 bool StringConstant::isStringConstant(
1380 Expr
const * expr
, unsigned * size
, bool * nonArray
, ContentKind
* content
,
1381 bool * embeddedNuls
, bool * terminatingNul
,
1382 std::vector
<char32_t
> * utf8Content
)
1384 assert(expr
!= nullptr);
1385 assert(size
!= nullptr);
1386 assert(nonArray
!= nullptr);
1387 assert(content
!= nullptr);
1388 assert(embeddedNuls
!= nullptr);
1389 assert(terminatingNul
!= nullptr);
1390 QualType t
= expr
->getType();
1391 // Look inside RTL_CONSTASCII_STRINGPARAM:
1392 if (loplugin::TypeCheck(t
).Pointer().Const().Char()) {
1393 auto e2
= dyn_cast
<UnaryOperator
>(expr
);
1394 if (e2
!= nullptr && e2
->getOpcode() == UO_AddrOf
) {
1395 auto e3
= dyn_cast
<ArraySubscriptExpr
>(
1396 e2
->getSubExpr()->IgnoreParenImpCasts());
1397 if (e3
== nullptr || !isZero(e3
->getIdx()->IgnoreParenImpCasts())) {
1400 expr
= e3
->getBase()->IgnoreParenImpCasts();
1401 t
= expr
->getType();
1404 if (!t
.isConstQualified()) {
1407 DeclRefExpr
const * dre
= dyn_cast
<DeclRefExpr
>(expr
);
1408 if (dre
!= nullptr) {
1409 VarDecl
const * var
= dyn_cast
<VarDecl
>(dre
->getDecl());
1410 if (var
!= nullptr) {
1411 Expr
const * init
= var
->getAnyInitializer();
1412 if (init
!= nullptr) {
1413 expr
= init
->IgnoreParenImpCasts();
1418 if (loplugin::TypeCheck(t
).Pointer().Const().Char()) {
1420 } else if (t
->isConstantArrayType()
1421 && (loplugin::TypeCheck(
1422 t
->getAsArrayTypeUnsafe()->getElementType())
1429 clang::StringLiteral
const * lit
= dyn_cast
<clang::StringLiteral
>(expr
);
1430 if (lit
!= nullptr) {
1431 if (!(compat::isOrdinary(lit
) || lit
->isUTF8())) {
1434 unsigned n
= lit
->getLength();
1435 ContentKind cont
= ContentKind::Ascii
;
1438 enum class Utf8State
{ Start
, E0
, EB
, F0
, F4
, Trail1
, Trail2
, Trail3
};
1439 Utf8State s
= Utf8State::Start
;
1440 StringRef str
= lit
->getString();
1441 for (unsigned i
= 0; i
!= n
; ++i
) {
1442 auto const c
= static_cast<unsigned char>(str
[i
]);
1447 case Utf8State::Start
:
1449 if (c
>= 0xC2 && c
<= 0xDF) {
1451 s
= Utf8State::Trail1
;
1452 } else if (c
== 0xE0) {
1455 } else if ((c
>= 0xE1 && c
<= 0xEA)
1456 || (c
>= 0xEE && c
<= 0xEF))
1459 s
= Utf8State::Trail2
;
1460 } else if (c
== 0xEB) {
1463 } else if (c
== 0xF0) {
1466 } else if (c
>= 0xF1 && c
<= 0xF3) {
1468 s
= Utf8State::Trail3
;
1469 } else if (c
== 0xF4) {
1473 cont
= ContentKind::Arbitrary
;
1475 } else if (utf8Content
!= nullptr
1476 && cont
!= ContentKind::Arbitrary
)
1478 utf8Content
->push_back(c
);
1482 if (c
>= 0xA0 && c
<= 0xBF) {
1483 val
= (val
<< 6) | (c
& 0x3F);
1484 s
= Utf8State::Trail1
;
1486 cont
= ContentKind::Arbitrary
;
1487 s
= Utf8State::Start
;
1491 if (c
>= 0x80 && c
<= 0x9F) {
1492 val
= (val
<< 6) | (c
& 0x3F);
1493 s
= Utf8State::Trail1
;
1495 cont
= ContentKind::Arbitrary
;
1496 s
= Utf8State::Start
;
1500 if (c
>= 0x90 && c
<= 0xBF) {
1501 val
= (val
<< 6) | (c
& 0x3F);
1502 s
= Utf8State::Trail2
;
1504 cont
= ContentKind::Arbitrary
;
1505 s
= Utf8State::Start
;
1509 if (c
>= 0x80 && c
<= 0x8F) {
1510 val
= (val
<< 6) | (c
& 0x3F);
1511 s
= Utf8State::Trail2
;
1513 cont
= ContentKind::Arbitrary
;
1514 s
= Utf8State::Start
;
1517 case Utf8State::Trail1
:
1518 if (c
>= 0x80 && c
<= 0xBF) {
1519 cont
= ContentKind::Utf8
;
1520 if (utf8Content
!= nullptr)
1522 utf8Content
->push_back((val
<< 6) | (c
& 0x3F));
1526 cont
= ContentKind::Arbitrary
;
1528 s
= Utf8State::Start
;
1530 case Utf8State::Trail2
:
1531 if (c
>= 0x80 && c
<= 0xBF) {
1532 val
= (val
<< 6) | (c
& 0x3F);
1533 s
= Utf8State::Trail1
;
1535 cont
= ContentKind::Arbitrary
;
1536 s
= Utf8State::Start
;
1539 case Utf8State::Trail3
:
1540 if (c
>= 0x80 && c
<= 0xBF) {
1541 val
= (val
<< 6) | (c
& 0x3F);
1542 s
= Utf8State::Trail2
;
1544 cont
= ContentKind::Arbitrary
;
1545 s
= Utf8State::Start
;
1553 *embeddedNuls
= emb
;
1554 *terminatingNul
= true;
1558 if (!expr
->isCXX11ConstantExpr(compiler
.getASTContext(), &v
)) {
1561 switch (v
.getKind()) {
1562 case APValue::LValue
:
1564 Expr
const * e
= v
.getLValueBase().dyn_cast
<Expr
const *>();
1568 if (!v
.getLValueOffset().isZero()) {
1569 return false; //TODO
1571 Expr
const * e2
= e
->IgnoreParenImpCasts();
1573 return isStringConstant(
1574 e2
, size
, nonArray
, content
, embeddedNuls
, terminatingNul
);
1576 //TODO: string literals are represented as recursive LValues???
1578 = compiler
.getASTContext().getAsConstantArrayType(t
)->getSize();
1581 assert(n
.ule(std::numeric_limits
<unsigned>::max()));
1582 *size
= static_cast<unsigned>(n
.getLimitedValue());
1583 *nonArray
= isPtr
|| *nonArray
;
1584 *content
= ContentKind::Ascii
; //TODO
1585 *embeddedNuls
= false; //TODO
1586 *terminatingNul
= true;
1589 case APValue::Array
:
1591 if (v
.hasArrayFiller()) { //TODO: handle final NULL filler?
1594 unsigned n
= v
.getArraySize();
1596 ContentKind cont
= ContentKind::Ascii
;
1598 //TODO: check for ContentType::Utf8
1599 for (unsigned i
= 0; i
!= n
- 1; ++i
) {
1600 APValue
e(v
.getArrayInitializedElt(i
));
1601 if (!e
.isInt()) { //TODO: assert?
1604 APSInt iv
= e
.getInt();
1607 } else if (iv
.uge(0x80)) {
1608 cont
= ContentKind::Arbitrary
;
1611 APValue
e(v
.getArrayInitializedElt(n
- 1));
1612 if (!e
.isInt()) { //TODO: assert?
1615 bool trm
= e
.getInt() == 0;
1616 *size
= trm
? n
- 1 : n
;
1619 *embeddedNuls
= emb
;
1620 *terminatingNul
= trm
;
1624 assert(false); //TODO???
1629 bool StringConstant::isZero(Expr
const * expr
) {
1631 return compat::EvaluateAsInt(expr
, res
, compiler
.getASTContext()) && res
== 0;
1634 void StringConstant::reportChange(
1635 Expr
const * expr
, ChangeKind kind
, std::string
const & original
,
1636 std::string
const & replacement
, PassThrough pass
, bool nonArray
,
1637 char const * rewriteFrom
, char const * rewriteTo
)
1639 assert((rewriteFrom
== nullptr) == (rewriteTo
== nullptr));
1640 if (pass
!= PassThrough::No
&& !calls_
.empty()) {
1641 Expr
const * call
= calls_
.top();
1642 CallExpr::const_arg_iterator argsBeg
;
1643 CallExpr::const_arg_iterator argsEnd
;
1644 if (isa
<CallExpr
>(call
)) {
1645 argsBeg
= cast
<CallExpr
>(call
)->arg_begin();
1646 argsEnd
= cast
<CallExpr
>(call
)->arg_end();
1647 } else if (isa
<CXXConstructExpr
>(call
)) {
1648 argsBeg
= cast
<CXXConstructExpr
>(call
)->arg_begin();
1649 argsEnd
= cast
<CXXConstructExpr
>(call
)->arg_end();
1653 for (auto i(argsBeg
); i
!= argsEnd
; ++i
) {
1654 Expr
const * e
= (*i
)->IgnoreParenImpCasts();
1655 if (isa
<CXXBindTemporaryExpr
>(e
)) {
1656 e
= cast
<CXXBindTemporaryExpr
>(e
)->getSubExpr()
1657 ->IgnoreParenImpCasts();
1660 if (isa
<CallExpr
>(call
)) {
1661 FunctionDecl
const * fdecl
1662 = cast
<CallExpr
>(call
)->getDirectCallee();
1663 if (fdecl
== nullptr) {
1666 loplugin::DeclCheck
dc(fdecl
);
1667 if (pass
== PassThrough::EmptyConstantString
) {
1668 if ((dc
.Function("equals").Class("OUString")
1669 .Namespace("rtl").GlobalNamespace())
1670 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1671 .GlobalNamespace()))
1674 DiagnosticsEngine::Warning
,
1675 ("rewrite call of '%0' with call of %1 with"
1676 " empty string constant argument as call of"
1677 " 'rtl::OUString::isEmpty'"),
1678 getMemberLocation(call
))
1679 << fdecl
->getQualifiedNameAsString() << original
1680 << call
->getSourceRange();
1683 if (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl")
1687 DiagnosticsEngine::Warning
,
1688 ("rewrite call of '%0' with call of %1 with"
1689 " empty string constant argument as call of"
1690 " '!rtl::OUString::isEmpty'"),
1691 getMemberLocation(call
))
1692 << fdecl
->getQualifiedNameAsString() << original
1693 << call
->getSourceRange();
1696 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
1698 || (dc
.Operator(OO_Plus
).Class("OUString")
1699 .Namespace("rtl").GlobalNamespace()))
1702 DiagnosticsEngine::Warning
,
1703 ("call of '%0' with suspicious call of %1 with"
1704 " empty string constant argument"),
1705 getMemberLocation(call
))
1706 << fdecl
->getQualifiedNameAsString() << original
1707 << call
->getSourceRange();
1710 if (dc
.Operator(OO_Equal
).Class("OUString")
1711 .Namespace("rtl").GlobalNamespace())
1714 DiagnosticsEngine::Warning
,
1715 ("rewrite call of '%0' with call of %1 with"
1716 " empty string constant argument as call of"
1717 " rtl::OUString::call"),
1718 getMemberLocation(call
))
1719 << fdecl
->getQualifiedNameAsString() << original
1720 << call
->getSourceRange();
1724 DiagnosticsEngine::Warning
,
1725 "TODO call inside %0", getMemberLocation(expr
))
1726 << fdecl
->getQualifiedNameAsString()
1727 << expr
->getSourceRange();
1730 assert(pass
== PassThrough::NonEmptyConstantString
);
1731 if ((dc
.Function("equals").Class("OUString")
1732 .Namespace("rtl").GlobalNamespace())
1733 || (dc
.Operator(OO_Equal
).Class("OUString")
1734 .Namespace("rtl").GlobalNamespace())
1735 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1737 || (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl")
1738 .GlobalNamespace()))
1741 DiagnosticsEngine::Warning
,
1742 "elide call of %0 with %1 in call of '%2'",
1743 getMemberLocation(expr
))
1744 << original
<< describeChangeKind(kind
)
1745 << fdecl
->getQualifiedNameAsString()
1746 << expr
->getSourceRange();
1750 DiagnosticsEngine::Warning
,
1751 ("rewrite call of %0 with %1 in call of '%2' as"
1752 " (implicit) construction of 'OUString'"),
1753 getMemberLocation(expr
))
1754 << original
<< describeChangeKind(kind
)
1755 << fdecl
->getQualifiedNameAsString()
1756 << expr
->getSourceRange();
1759 } else if (isa
<CXXConstructExpr
>(call
)) {
1760 auto classdecl
= cast
<CXXConstructExpr
>(call
)
1761 ->getConstructor()->getParent();
1762 loplugin::DeclCheck
dc(classdecl
);
1763 if (dc
.Class("OUString").Namespace("rtl").GlobalNamespace()
1764 || (dc
.Class("OUStringBuffer").Namespace("rtl")
1765 .GlobalNamespace()))
1767 //TODO: propagate further out?
1768 if (pass
== PassThrough::EmptyConstantString
) {
1770 DiagnosticsEngine::Warning
,
1771 ("rewrite construction of %0 with call of %1"
1772 " with empty string constant argument as"
1773 " default construction of %0"),
1774 getMemberLocation(call
))
1775 << classdecl
<< original
1776 << call
->getSourceRange();
1778 assert(pass
== PassThrough::NonEmptyConstantString
);
1780 DiagnosticsEngine::Warning
,
1781 ("elide call of %0 with %1 in construction of"
1783 getMemberLocation(expr
))
1784 << original
<< describeChangeKind(kind
)
1785 << classdecl
<< expr
->getSourceRange();
1795 if (rewriter
!= nullptr && !nonArray
&& rewriteFrom
!= nullptr) {
1796 SourceLocation loc
= getMemberLocation(expr
);
1797 while (compiler
.getSourceManager().isMacroArgExpansion(loc
)) {
1798 loc
= compiler
.getSourceManager().getImmediateMacroCallerLoc(loc
);
1800 if (compiler
.getSourceManager().isMacroBodyExpansion(loc
)) {
1801 loc
= compiler
.getSourceManager().getSpellingLoc(loc
);
1803 unsigned n
= Lexer::MeasureTokenLength(
1804 loc
, compiler
.getSourceManager(), compiler
.getLangOpts());
1805 if ((std::string(compiler
.getSourceManager().getCharacterData(loc
), n
)
1807 && replaceText(loc
, n
, rewriteTo
))
1813 DiagnosticsEngine::Warning
,
1814 "rewrite call of '%0' with %1 as call of '%2'%3",
1815 getMemberLocation(expr
))
1816 << original
<< describeChangeKind(kind
) << replacement
1817 << adviseNonArray(nonArray
) << expr
->getSourceRange();
1820 void StringConstant::checkEmpty(
1821 CallExpr
const * expr
, FunctionDecl
const * callee
, TreatEmpty treatEmpty
,
1822 unsigned size
, std::string
* replacement
)
1824 assert(replacement
!= nullptr);
1826 switch (treatEmpty
) {
1827 case TreatEmpty::DefaultCtor
:
1828 *replacement
= "rtl::OUString default constructor";
1830 case TreatEmpty::CheckEmpty
:
1831 *replacement
= "rtl::OUString::isEmpty";
1833 case TreatEmpty::Error
:
1835 DiagnosticsEngine::Warning
,
1836 "call of '%0' with suspicious empty string constant argument",
1837 getMemberLocation(expr
))
1838 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1844 void StringConstant::handleChar(
1845 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
1846 std::string
const & replacement
, TreatEmpty treatEmpty
, bool literal
,
1847 char const * rewriteFrom
, char const * rewriteTo
)
1854 if (!isStringConstant(
1855 expr
->getArg(arg
)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
1860 if (cont
!= ContentKind::Ascii
) {
1862 DiagnosticsEngine::Warning
,
1863 ("call of '%0' with string constant argument containing non-ASCII"
1865 getMemberLocation(expr
))
1866 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1871 DiagnosticsEngine::Warning
,
1872 ("call of '%0' with string constant argument containing embedded"
1874 getMemberLocation(expr
))
1875 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1880 DiagnosticsEngine::Warning
,
1881 ("call of '%0' with string constant argument lacking a terminating"
1883 getMemberLocation(expr
))
1884 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1887 std::string
repl(replacement
);
1888 checkEmpty(expr
, callee
, treatEmpty
, n
, &repl
);
1889 if (!repl
.empty()) {
1891 expr
, ChangeKind::Char
, callee
->getQualifiedNameAsString(), repl
,
1894 ? PassThrough::EmptyConstantString
1895 : PassThrough::NonEmptyConstantString
)
1897 nonArray
, rewriteFrom
, rewriteTo
);
1901 void StringConstant::handleCharLen(
1902 CallExpr
const * expr
, unsigned arg1
, unsigned arg2
,
1903 FunctionDecl
const * callee
, std::string
const & replacement
,
1904 TreatEmpty treatEmpty
)
1906 // Especially for f(RTL_CONSTASCII_STRINGPARAM("foo")), where
1907 // RTL_CONSTASCII_STRINGPARAM expands to complicated expressions involving
1908 // (&(X)[0] sub-expressions (and it might or might not be better to handle
1909 // that at the level of non-expanded macros instead, but I have not found
1910 // out how to do that yet anyway):
1916 if (!(isStringConstant(
1917 expr
->getArg(arg1
)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
1924 if (compat::EvaluateAsInt(expr
->getArg(arg2
), res
, compiler
.getASTContext())) {
1929 UnaryOperator
const * op
= dyn_cast
<UnaryOperator
>(
1930 expr
->getArg(arg1
)->IgnoreParenImpCasts());
1931 if (op
== nullptr || op
->getOpcode() != UO_AddrOf
) {
1934 ArraySubscriptExpr
const * subs
= dyn_cast
<ArraySubscriptExpr
>(
1935 op
->getSubExpr()->IgnoreParenImpCasts());
1936 if (subs
== nullptr) {
1944 if (!(isStringConstant(
1945 subs
->getBase()->IgnoreParenImpCasts(), &n2
, &nonArray2
,
1946 &cont2
, &emb2
, &trm2
)
1947 && n2
== n
&& cont2
== cont
&& emb2
== emb
&& trm2
== trm
1948 //TODO: same strings
1949 && compat::EvaluateAsInt(subs
->getIdx(), res
, compiler
.getASTContext())
1955 if (cont
!= ContentKind::Ascii
) {
1957 DiagnosticsEngine::Warning
,
1958 ("call of '%0' with string constant argument containing non-ASCII"
1960 getMemberLocation(expr
))
1961 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1966 std::string
repl(replacement
);
1967 checkEmpty(expr
, callee
, treatEmpty
, n
, &repl
);
1969 expr
, ChangeKind::CharLen
, callee
->getQualifiedNameAsString(), repl
,
1970 PassThrough::No
, nonArray
, nullptr, nullptr);
1973 void StringConstant::handleOUStringCtor(
1974 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
1975 bool explicitFunctionalCastNotation
)
1977 handleOUStringCtor(expr
, expr
->getArg(arg
), callee
, explicitFunctionalCastNotation
);
1980 void StringConstant::handleOStringCtor(
1981 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
1982 bool explicitFunctionalCastNotation
)
1984 handleOStringCtor(expr
, expr
->getArg(arg
), callee
, explicitFunctionalCastNotation
);
1987 void StringConstant::handleOUStringCtor(
1988 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
1989 bool explicitFunctionalCastNotation
)
1991 handleStringCtor(expr
, argExpr
, callee
, explicitFunctionalCastNotation
, StringKind::Unicode
);
1994 void StringConstant::handleOStringCtor(
1995 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
1996 bool explicitFunctionalCastNotation
)
1998 handleStringCtor(expr
, argExpr
, callee
, explicitFunctionalCastNotation
, StringKind::Char
);
2001 void StringConstant::handleStringCtor(
2002 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
2003 bool explicitFunctionalCastNotation
, StringKind stringKind
)
2005 auto e0
= argExpr
->IgnoreParenImpCasts();
2006 if (auto const e1
= dyn_cast
<CXXMemberCallExpr
>(e0
)) {
2007 if (auto const e2
= dyn_cast
<CXXConversionDecl
>(e1
->getMethodDecl())) {
2008 if (loplugin::TypeCheck(e2
->getConversionType()).ClassOrStruct("basic_string_view")
2011 e0
= e1
->getImplicitObjectArgument()->IgnoreParenImpCasts();
2015 auto e1
= dyn_cast
<CXXFunctionalCastExpr
>(e0
);
2016 if (e1
== nullptr) {
2017 if (explicitFunctionalCastNotation
) {
2021 e0
= e1
->getSubExpr()->IgnoreParenImpCasts();
2023 auto e2
= dyn_cast
<CXXBindTemporaryExpr
>(e0
);
2024 if (e2
== nullptr) {
2027 auto e3
= dyn_cast
<CXXConstructExpr
>(
2028 e2
->getSubExpr()->IgnoreParenImpCasts());
2029 if (e3
== nullptr) {
2032 if (!loplugin::DeclCheck(e3
->getConstructor()).MemberFunction()
2033 .Class(stringKind
== StringKind::Unicode
? "OUString" : "OString").Namespace("rtl").GlobalNamespace())
2037 if (e3
->getNumArgs() == 0) {
2039 DiagnosticsEngine::Warning
,
2040 ("in call of '%0', replace default-constructed 'OUString' with an"
2041 " empty string literal"),
2043 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2046 if (e3
->getNumArgs() == 1
2047 && e3
->getConstructor()->getNumParams() == 1
2048 && (loplugin::TypeCheck(
2049 e3
->getConstructor()->getParamDecl(0)->getType())
2050 .Typedef(stringKind
== StringKind::Unicode
? "sal_Unicode" : "char").GlobalNamespace()))
2052 // It may not be easy to rewrite OUString(c), esp. given there is no
2053 // OUString ctor taking an OUStringChar arg, so don't warn there:
2054 if (!explicitFunctionalCastNotation
) {
2056 DiagnosticsEngine::Warning
,
2057 ("in call of '%0', replace 'OUString' constructed from a"
2058 " 'sal_Unicode' with an 'OUStringChar'"),
2060 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2064 if (e3
->getNumArgs() != 2) {
2072 if (!isStringConstant(
2073 e3
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
, &emb
,
2078 //TODO: cont, emb, trm
2079 if (rewriter
!= nullptr) {
2080 auto loc1
= e3
->getBeginLoc();
2081 auto range
= e3
->getParenOrBraceRange();
2082 if (loc1
.isFileID() && range
.getBegin().isFileID()
2083 && range
.getEnd().isFileID())
2085 auto loc2
= range
.getBegin();
2086 for (bool first
= true;; first
= false) {
2087 unsigned n
= Lexer::MeasureTokenLength(
2088 loc2
, compiler
.getSourceManager(), compiler
.getLangOpts());
2091 compiler
.getSourceManager().getCharacterData(loc2
), n
);
2092 while (compat::starts_with(s
, "\\\n")) {
2093 s
= s
.drop_front(2);
2095 && (s
.front() == ' ' || s
.front() == '\t'
2096 || s
.front() == '\n' || s
.front() == '\v'
2097 || s
.front() == '\f'))
2099 s
= s
.drop_front(1);
2102 if (!(s
.empty() || compat::starts_with(s
, "/*") || compat::starts_with(s
, "//")
2108 loc2
= loc2
.getLocWithOffset(std::max
<unsigned>(n
, 1));
2110 auto loc3
= range
.getEnd();
2112 auto l
= Lexer::GetBeginningOfToken(
2113 loc3
.getLocWithOffset(-1), compiler
.getSourceManager(),
2114 compiler
.getLangOpts());
2115 unsigned n
= Lexer::MeasureTokenLength(
2116 l
, compiler
.getSourceManager(), compiler
.getLangOpts());
2117 StringRef
s(compiler
.getSourceManager().getCharacterData(l
), n
);
2118 while (compat::starts_with(s
, "\\\n")) {
2119 s
= s
.drop_front(2);
2121 && (s
.front() == ' ' || s
.front() == '\t'
2122 || s
.front() == '\n' || s
.front() == '\v'
2123 || s
.front() == '\f'))
2125 s
= s
.drop_front(1);
2128 if (!(s
.empty() || compat::starts_with(s
, "/*") || compat::starts_with(s
, "//")
2135 if (removeText(CharSourceRange(SourceRange(loc1
, loc2
), false))) {
2136 if (removeText(SourceRange(loc3
, range
.getEnd()))) {
2139 report(DiagnosticsEngine::Fatal
, "Corrupt rewrite", loc3
)
2140 << expr
->getSourceRange();
2146 DiagnosticsEngine::Warning
,
2147 ("in call of '%0', replace 'OUString' constructed from a string literal"
2148 " directly with the string literal"),
2150 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2153 void StringConstant::handleFunArgOstring(
2154 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
)
2156 auto argExpr
= expr
->getArg(arg
)->IgnoreParenImpCasts();
2162 if (isStringConstant(argExpr
, &n
, &nonArray
, &cont
, &emb
, &trm
)) {
2163 if (cont
!= ContentKind::Ascii
|| emb
) {
2168 DiagnosticsEngine::Warning
,
2169 ("call of '%0' with string constant argument lacking a"
2170 " terminating NULL"),
2171 getMemberLocation(expr
))
2172 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2176 checkEmpty(expr
, callee
, TreatEmpty::Error
, n
, &repl
);
2179 DiagnosticsEngine::Warning
,
2180 ("in call of '%0' with non-array string constant argument,"
2181 " turn the non-array string constant into an array"),
2182 getMemberLocation(expr
))
2183 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2185 } else if (auto cexpr
= lookForCXXConstructExpr(argExpr
)) {
2186 auto classdecl
= cexpr
->getConstructor()->getParent();
2187 if (loplugin::DeclCheck(classdecl
).Class("OString").Namespace("rtl")
2190 switch (cexpr
->getConstructor()->getNumParams()) {
2193 DiagnosticsEngine::Warning
,
2194 ("in call of '%0', replace empty %1 constructor with empty"
2196 cexpr
->getLocation())
2197 << callee
->getQualifiedNameAsString() << classdecl
2198 << expr
->getSourceRange();
2201 if (isStringConstant(
2202 cexpr
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
,
2206 if (compat::EvaluateAsInt(cexpr
->getArg(1),
2207 res
, compiler
.getASTContext()))
2209 if (res
== n
&& !emb
&& trm
) {
2211 DiagnosticsEngine::Warning
,
2212 ("in call of '%0', elide explicit %1"
2214 cexpr
->getLocation())
2215 << callee
->getQualifiedNameAsString()
2216 << classdecl
<< adviseNonArray(nonArray
)
2217 << expr
->getSourceRange();
2222 DiagnosticsEngine::Warning
,
2223 ("call of %0 constructor with string constant"
2224 " argument containing embedded NULLs"),
2225 cexpr
->getLocation())
2226 << classdecl
<< cexpr
->getSourceRange();
2231 DiagnosticsEngine::Warning
,
2232 ("call of %0 constructor with string constant"
2233 " argument lacking a terminating NULL"),
2234 cexpr
->getLocation())
2235 << classdecl
<< cexpr
->getSourceRange();
2239 DiagnosticsEngine::Warning
,
2240 "in call of '%0', elide explicit %1 constructor%2",
2241 cexpr
->getLocation())
2242 << callee
->getQualifiedNameAsString() << classdecl
2243 << adviseNonArray(nonArray
)
2244 << expr
->getSourceRange();
2255 loplugin::Plugin::Registration
< StringConstant
> X("stringconstant", true);
2259 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */