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
->GetTemporaryExpr();
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 return dyn_cast
<CXXConstructExpr
>(expr
);
100 char const * adviseNonArray(bool nonArray
) {
102 ? ", and turn the non-array string constant into an array" : "";
105 class StringConstant
:
106 public loplugin::FilteringRewritePlugin
<StringConstant
>
109 explicit StringConstant(loplugin::InstantiationData
const & data
):
110 FilteringRewritePlugin(data
) {}
114 bool TraverseFunctionDecl(FunctionDecl
* decl
) {
115 returnTypes_
.push(compat::getDeclaredReturnType(decl
));
116 auto const ret
= RecursiveASTVisitor::TraverseFunctionDecl(decl
);
117 assert(!returnTypes_
.empty());
118 assert(returnTypes_
.top() == compat::getDeclaredReturnType(decl
));
123 bool TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl
* decl
) {
124 returnTypes_
.push(compat::getDeclaredReturnType(decl
));
125 auto const ret
= RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(
127 assert(!returnTypes_
.empty());
128 assert(returnTypes_
.top() == compat::getDeclaredReturnType(decl
));
133 bool TraverseCXXMethodDecl(CXXMethodDecl
* decl
) {
134 returnTypes_
.push(compat::getDeclaredReturnType(decl
));
135 auto const ret
= RecursiveASTVisitor::TraverseCXXMethodDecl(decl
);
136 assert(!returnTypes_
.empty());
137 assert(returnTypes_
.top() == compat::getDeclaredReturnType(decl
));
142 bool TraverseCXXConstructorDecl(CXXConstructorDecl
* decl
) {
143 returnTypes_
.push(compat::getDeclaredReturnType(decl
));
144 auto const ret
= RecursiveASTVisitor::TraverseCXXConstructorDecl(decl
);
145 assert(!returnTypes_
.empty());
146 assert(returnTypes_
.top() == compat::getDeclaredReturnType(decl
));
151 bool TraverseCXXDestructorDecl(CXXDestructorDecl
* decl
) {
152 returnTypes_
.push(compat::getDeclaredReturnType(decl
));
153 auto const ret
= RecursiveASTVisitor::TraverseCXXDestructorDecl(decl
);
154 assert(!returnTypes_
.empty());
155 assert(returnTypes_
.top() == compat::getDeclaredReturnType(decl
));
160 bool TraverseCXXConversionDecl(CXXConversionDecl
* decl
) {
161 returnTypes_
.push(compat::getDeclaredReturnType(decl
));
162 auto const ret
= RecursiveASTVisitor::TraverseCXXConversionDecl(decl
);
163 assert(!returnTypes_
.empty());
164 assert(returnTypes_
.top() == compat::getDeclaredReturnType(decl
));
169 bool TraverseObjCMethodDecl(ObjCMethodDecl
* decl
) {
170 returnTypes_
.push(decl
->getReturnType());
171 auto const ret
= RecursiveASTVisitor::TraverseObjCMethodDecl(decl
);
172 assert(!returnTypes_
.empty());
173 assert(returnTypes_
.top() == decl
->getReturnType());
178 bool TraverseCallExpr(CallExpr
* expr
);
180 bool TraverseCXXMemberCallExpr(CXXMemberCallExpr
* expr
);
182 bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr
* expr
);
184 bool TraverseCXXConstructExpr(CXXConstructExpr
* expr
);
186 bool VisitCallExpr(CallExpr
const * expr
);
188 bool VisitCXXConstructExpr(CXXConstructExpr
const * expr
);
190 bool VisitReturnStmt(ReturnStmt
const * stmt
);
193 enum class ContentKind
{ Ascii
, Utf8
, Arbitrary
};
195 enum class TreatEmpty
{ DefaultCtor
, CheckEmpty
, Error
};
197 enum class ChangeKind
{ Char
, CharLen
, SingleChar
, OUStringChar
};
199 enum class PassThrough
{ No
, EmptyConstantString
, NonEmptyConstantString
};
201 std::string
describeChangeKind(ChangeKind kind
);
203 bool isStringConstant(
204 Expr
const * expr
, unsigned * size
, bool * nonArray
,
205 ContentKind
* content
, bool * embeddedNuls
, bool * terminatingNul
,
206 std::vector
<char32_t
> * utf8Content
= nullptr);
208 bool isZero(Expr
const * expr
);
211 Expr
const * expr
, ChangeKind kind
, std::string
const & original
,
212 std::string
const & replacement
, PassThrough pass
, bool nonArray
,
213 char const * rewriteFrom
, char const * rewriteTo
);
216 CallExpr
const * expr
, FunctionDecl
const * callee
,
217 TreatEmpty treatEmpty
, unsigned size
, std::string
* replacement
);
220 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
221 std::string
const & replacement
, TreatEmpty treatEmpty
, bool literal
,
222 char const * rewriteFrom
= nullptr, char const * rewriteTo
= nullptr);
225 CallExpr
const * expr
, unsigned arg1
, unsigned arg2
,
226 FunctionDecl
const * callee
, std::string
const & replacement
,
227 TreatEmpty treatEmpty
);
229 void handleOUStringCtor(
230 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
231 bool explicitFunctionalCastNotation
);
233 void handleOStringCtor(
234 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
235 bool explicitFunctionalCastNotation
);
237 void handleOUStringCtor(
238 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
239 bool explicitFunctionalCastNotation
);
241 void handleOStringCtor(
242 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
243 bool explicitFunctionalCastNotation
);
245 enum class StringKind
{ Unicode
, Char
};
246 void handleStringCtor(
247 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
248 bool explicitFunctionalCastNotation
, StringKind stringKind
);
250 void handleFunArgOstring(
251 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
);
253 std::stack
<QualType
> returnTypes_
;
254 std::stack
<Expr
const *> calls_
;
257 void StringConstant::run() {
258 if (compiler
.getLangOpts().CPlusPlus
259 && compiler
.getPreprocessor().getIdentifierInfo(
260 "LIBO_INTERNAL_ONLY")->hasMacroDefinition())
261 //TODO: some parts of it are useful for external code, too
263 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
267 bool StringConstant::TraverseCallExpr(CallExpr
* expr
) {
268 if (!WalkUpFromCallExpr(expr
)) {
273 for (auto * e
: expr
->children()) {
274 if (!TraverseStmt(e
)) {
283 bool StringConstant::TraverseCXXMemberCallExpr(CXXMemberCallExpr
* expr
) {
284 if (!WalkUpFromCXXMemberCallExpr(expr
)) {
289 for (auto * e
: expr
->children()) {
290 if (!TraverseStmt(e
)) {
299 bool StringConstant::TraverseCXXOperatorCallExpr(CXXOperatorCallExpr
* expr
)
301 if (!WalkUpFromCXXOperatorCallExpr(expr
)) {
306 for (auto * e
: expr
->children()) {
307 if (!TraverseStmt(e
)) {
316 bool StringConstant::TraverseCXXConstructExpr(CXXConstructExpr
* expr
) {
317 if (!WalkUpFromCXXConstructExpr(expr
)) {
322 for (auto * e
: expr
->children()) {
323 if (!TraverseStmt(e
)) {
332 bool StringConstant::VisitCallExpr(CallExpr
const * expr
) {
333 if (ignoreLocation(expr
)) {
336 FunctionDecl
const * fdecl
= expr
->getDirectCallee();
337 if (fdecl
== nullptr) {
340 for (unsigned i
= 0; i
!= fdecl
->getNumParams(); ++i
) {
341 auto t
= fdecl
->getParamDecl(i
)->getType();
342 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
343 .LvalueReference().Const().NotSubstTemplateTypeParmType()
344 .Class("OUString").Namespace("rtl").GlobalNamespace())
346 if (!(isLhsOfAssignment(fdecl
, i
)
347 || hasOverloads(fdecl
, expr
->getNumArgs())))
349 handleOUStringCtor(expr
, i
, fdecl
, true);
352 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
353 .LvalueReference().Const().NotSubstTemplateTypeParmType()
354 .Class("OString").Namespace("rtl").GlobalNamespace())
356 if (!(isLhsOfAssignment(fdecl
, i
)
357 || hasOverloads(fdecl
, expr
->getNumArgs())))
359 handleOStringCtor(expr
, i
, fdecl
, true);
363 loplugin::DeclCheck
dc(fdecl
);
364 //TODO: u.compareToAscii("foo") -> u.???("foo")
365 //TODO: u.compareToIgnoreAsciiCaseAscii("foo") -> u.???("foo")
366 if ((dc
.Function("createFromAscii").Class("OUString").Namespace("rtl")
368 && fdecl
->getNumParams() == 1)
370 // OUString::createFromAscii("foo") -> OUString("foo")
372 expr
, 0, fdecl
, "rtl::OUString constructor",
373 TreatEmpty::DefaultCtor
, true);
376 if ((dc
.Function("endsWithAsciiL").Class("OUString").Namespace("rtl")
378 && fdecl
->getNumParams() == 2)
380 // u.endsWithAsciiL("foo", 3) -> u.endsWith("foo"):
382 expr
, 0, 1, fdecl
, "rtl::OUString::endsWith", TreatEmpty::Error
);
385 if ((dc
.Function("endsWithIgnoreAsciiCaseAsciiL").Class("OUString")
386 .Namespace("rtl").GlobalNamespace())
387 && fdecl
->getNumParams() == 2)
389 // u.endsWithIgnoreAsciiCaseAsciiL("foo", 3) ->
390 // u.endsWithIgnoreAsciiCase("foo"):
392 expr
, 0, 1, fdecl
, "rtl::OUString::endsWithIgnoreAsciiCase",
396 if ((dc
.Function("equalsAscii").Class("OUString").Namespace("rtl")
398 && fdecl
->getNumParams() == 1)
400 // u.equalsAscii("foo") -> u == "foo":
402 expr
, 0, fdecl
, "operator ==", TreatEmpty::CheckEmpty
, false);
405 if ((dc
.Function("equalsAsciiL").Class("OUString").Namespace("rtl")
407 && fdecl
->getNumParams() == 2)
409 // u.equalsAsciiL("foo", 3) -> u == "foo":
410 handleCharLen(expr
, 0, 1, fdecl
, "operator ==", TreatEmpty::CheckEmpty
);
413 if ((dc
.Function("equalsIgnoreAsciiCaseAscii").Class("OUString")
414 .Namespace("rtl").GlobalNamespace())
415 && fdecl
->getNumParams() == 1)
417 // u.equalsIgnoreAsciiCaseAscii("foo") ->
418 // u.equalsIngoreAsciiCase("foo"):
420 auto file
= getFilenameOfLocation(
421 compiler
.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr
)));
422 if (loplugin::isSamePathname(
423 file
, SRCDIR
"/sal/qa/rtl/strings/test_oustring_compare.cxx"))
428 expr
, 0, fdecl
, "rtl::OUString::equalsIgnoreAsciiCase",
429 TreatEmpty::CheckEmpty
, false);
432 if ((dc
.Function("equalsIgnoreAsciiCaseAsciiL").Class("OUString")
433 .Namespace("rtl").GlobalNamespace())
434 && fdecl
->getNumParams() == 2)
436 // u.equalsIgnoreAsciiCaseAsciiL("foo", 3) ->
437 // u.equalsIngoreAsciiCase("foo"):
438 auto file
= getFilenameOfLocation(
439 compiler
.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr
)));
440 if (loplugin::isSamePathname(
441 file
, SRCDIR
"/sal/qa/rtl/strings/test_oustring_compare.cxx"))
446 expr
, 0, 1, fdecl
, "rtl::OUString::equalsIgnoreAsciiCase",
447 TreatEmpty::CheckEmpty
);
450 if ((dc
.Function("indexOfAsciiL").Class("OUString").Namespace("rtl")
452 && fdecl
->getNumParams() == 3)
454 assert(expr
->getNumArgs() == 3);
455 // u.indexOfAsciiL("foo", 3, i) -> u.indexOf("foo", i):
457 expr
, 0, 1, fdecl
, "rtl::OUString::indexOf", TreatEmpty::Error
);
460 if ((dc
.Function("lastIndexOfAsciiL").Class("OUString").Namespace("rtl")
462 && fdecl
->getNumParams() == 2)
464 // u.lastIndexOfAsciiL("foo", 3) -> u.lastIndexOf("foo"):
466 expr
, 0, 1, fdecl
, "rtl::OUString::lastIndexOf", TreatEmpty::Error
);
469 if ((dc
.Function("matchAsciiL").Class("OUString").Namespace("rtl")
471 && fdecl
->getNumParams() == 3)
473 assert(expr
->getNumArgs() == 3);
474 // u.matchAsciiL("foo", 3, i) -> u.match("foo", i):
477 (isZero(expr
->getArg(2))
478 ? std::string("rtl::OUString::startsWith")
479 : std::string("rtl::OUString::match")),
483 if ((dc
.Function("matchIgnoreAsciiCaseAsciiL").Class("OUString")
484 .Namespace("rtl").GlobalNamespace())
485 && fdecl
->getNumParams() == 3)
487 assert(expr
->getNumArgs() == 3);
488 // u.matchIgnoreAsciiCaseAsciiL("foo", 3, i) ->
489 // u.matchIgnoreAsciiCase("foo", i):
492 (isZero(expr
->getArg(2))
493 ? std::string("rtl::OUString::startsWithIgnoreAsciiCase")
494 : std::string("rtl::OUString::matchIgnoreAsciiCase")),
498 if ((dc
.Function("reverseCompareToAsciiL").Class("OUString")
499 .Namespace("rtl").GlobalNamespace())
500 && fdecl
->getNumParams() == 2)
502 // u.reverseCompareToAsciiL("foo", 3) -> u.reverseCompareTo("foo"):
504 expr
, 0, 1, fdecl
, "rtl::OUString::reverseCompareTo",
508 if ((dc
.Function("reverseCompareTo").Class("OUString").Namespace("rtl")
510 && fdecl
->getNumParams() == 1)
512 handleOUStringCtor(expr
, 0, fdecl
, false);
515 if ((dc
.Function("equalsIgnoreAsciiCase").Class("OUString").Namespace("rtl")
517 && fdecl
->getNumParams() == 1)
519 handleOUStringCtor(expr
, 0, fdecl
, false);
522 if ((dc
.Function("match").Class("OUString").Namespace("rtl")
524 && fdecl
->getNumParams() == 2)
526 handleOUStringCtor(expr
, 0, fdecl
, false);
529 if ((dc
.Function("matchIgnoreAsciiCase").Class("OUString").Namespace("rtl")
531 && fdecl
->getNumParams() == 2)
533 handleOUStringCtor(expr
, 0, fdecl
, false);
536 if ((dc
.Function("startsWith").Class("OUString").Namespace("rtl")
538 && fdecl
->getNumParams() == 2)
540 handleOUStringCtor(expr
, 0, fdecl
, false);
543 if ((dc
.Function("startsWithIgnoreAsciiCase").Class("OUString")
544 .Namespace("rtl").GlobalNamespace())
545 && fdecl
->getNumParams() == 2)
547 handleOUStringCtor(expr
, 0, fdecl
, false);
550 if ((dc
.Function("endsWith").Class("OUString").Namespace("rtl")
552 && fdecl
->getNumParams() == 2)
554 handleOUStringCtor(expr
, 0, fdecl
, false);
557 if ((dc
.Function("endsWithIgnoreAsciiCase").Class("OUString")
558 .Namespace("rtl").GlobalNamespace())
559 && fdecl
->getNumParams() == 2)
561 handleOUStringCtor(expr
, 0, fdecl
, false);
564 if ((dc
.Function("indexOf").Class("OUString").Namespace("rtl")
566 && fdecl
->getNumParams() == 2)
568 handleOUStringCtor(expr
, 0, fdecl
, false);
571 if ((dc
.Function("lastIndexOf").Class("OUString").Namespace("rtl")
573 && fdecl
->getNumParams() == 1)
575 handleOUStringCtor(expr
, 0, fdecl
, false);
578 if ((dc
.Function("replaceFirst").Class("OUString").Namespace("rtl")
580 && fdecl
->getNumParams() == 3)
582 handleOUStringCtor(expr
, 0, fdecl
, false);
583 handleOUStringCtor(expr
, 1, fdecl
, false);
586 if ((dc
.Function("replaceAll").Class("OUString").Namespace("rtl")
588 && (fdecl
->getNumParams() == 2 || fdecl
->getNumParams() == 3))
590 handleOUStringCtor(expr
, 0, fdecl
, false);
591 handleOUStringCtor(expr
, 1, fdecl
, false);
594 if ((dc
.Operator(OO_PlusEqual
).Class("OUString").Namespace("rtl")
596 && fdecl
->getNumParams() == 1)
599 expr
, dyn_cast
<CXXOperatorCallExpr
>(expr
) == nullptr ? 0 : 1,
603 if ((dc
.Function("equals").Class("OUString").Namespace("rtl")
605 && fdecl
->getNumParams() == 1)
612 if (!isStringConstant(
613 expr
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
618 if (cont
!= ContentKind::Ascii
) {
620 DiagnosticsEngine::Warning
,
621 ("call of '%0' with string constant argument containing"
622 " non-ASCII characters"),
624 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
628 DiagnosticsEngine::Warning
,
629 ("call of '%0' with string constant argument containing"
632 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
636 DiagnosticsEngine::Warning
,
637 ("rewrite call of '%0' with empty string constant argument as"
638 " call of 'rtl::OUString::isEmpty'"),
640 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
644 if (dc
.Operator(OO_EqualEqual
).Namespace("rtl").GlobalNamespace()
645 && fdecl
->getNumParams() == 2)
647 for (unsigned i
= 0; i
!= 2; ++i
) {
653 if (!isStringConstant(
654 expr
->getArg(i
)->IgnoreParenImpCasts(), &n
, &nonArray
,
659 if (cont
!= ContentKind::Ascii
) {
661 DiagnosticsEngine::Warning
,
662 ("call of '%0' with string constant argument containing"
663 " non-ASCII characters"),
665 << fdecl
->getQualifiedNameAsString()
666 << expr
->getSourceRange();
670 DiagnosticsEngine::Warning
,
671 ("call of '%0' with string constant argument containing"
674 << fdecl
->getQualifiedNameAsString()
675 << expr
->getSourceRange();
679 DiagnosticsEngine::Warning
,
680 ("rewrite call of '%0' with empty string constant argument"
681 " as call of 'rtl::OUString::isEmpty'"),
683 << fdecl
->getQualifiedNameAsString()
684 << expr
->getSourceRange();
689 if (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl").GlobalNamespace()
690 && fdecl
->getNumParams() == 2)
692 for (unsigned i
= 0; i
!= 2; ++i
) {
698 if (!isStringConstant(
699 expr
->getArg(i
)->IgnoreParenImpCasts(), &n
, &nonArray
,
704 if (cont
!= ContentKind::Ascii
) {
706 DiagnosticsEngine::Warning
,
707 ("call of '%0' with string constant argument containing"
708 " non-ASCII characters"),
710 << fdecl
->getQualifiedNameAsString()
711 << expr
->getSourceRange();
715 DiagnosticsEngine::Warning
,
716 ("call of '%0' with string constant argument containing"
719 << fdecl
->getQualifiedNameAsString()
720 << expr
->getSourceRange();
724 DiagnosticsEngine::Warning
,
725 ("rewrite call of '%0' with empty string constant argument"
726 " as call of '!rtl::OUString::isEmpty'"),
728 << fdecl
->getQualifiedNameAsString()
729 << expr
->getSourceRange();
734 if (dc
.Operator(OO_Equal
).Namespace("rtl").GlobalNamespace()
735 && fdecl
->getNumParams() == 1)
742 if (!isStringConstant(
743 expr
->getArg(1)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
748 if (cont
!= ContentKind::Ascii
) {
750 DiagnosticsEngine::Warning
,
751 ("call of '%0' with string constant argument containing"
752 " non-ASCII characters"),
754 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
758 DiagnosticsEngine::Warning
,
759 ("call of '%0' with string constant argument containing"
762 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
766 DiagnosticsEngine::Warning
,
767 ("rewrite call of '%0' with empty string constant argument as"
768 " call of 'rtl::OUString::clear'"),
770 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
775 if (dc
.Function("append").Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
776 && fdecl
->getNumParams() == 1)
778 handleChar(expr
, 0, fdecl
, "", TreatEmpty::Error
, false);
781 if ((dc
.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
783 && fdecl
->getNumParams() == 1)
785 // u.appendAscii("foo") -> u.append("foo")
787 expr
, 0, fdecl
, "rtl::OUStringBuffer::append", TreatEmpty::Error
,
788 true, "appendAscii", "append");
791 if ((dc
.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
793 && fdecl
->getNumParams() == 2)
795 // u.appendAscii("foo", 3) -> u.append("foo"):
797 expr
, 0, 1, fdecl
, "rtl::OUStringBuffer::append",
801 if (dc
.Function("append").Class("OStringBuffer").Namespace("rtl")
804 switch (fdecl
->getNumParams()) {
806 handleFunArgOstring(expr
, 0, fdecl
);
810 // b.append("foo", 3) -> b.append("foo"):
811 auto file
= getFilenameOfLocation(
812 compiler
.getSourceManager().getSpellingLoc(
813 compat::getBeginLoc(expr
)));
814 if (loplugin::isSamePathname(
816 SRCDIR
"/sal/qa/OStringBuffer/rtl_OStringBuffer.cxx"))
821 expr
, 0, 1, fdecl
, "rtl::OStringBuffer::append",
830 if (dc
.Function("insert").Class("OStringBuffer").Namespace("rtl")
833 switch (fdecl
->getNumParams()) {
835 handleFunArgOstring(expr
, 1, fdecl
);
839 // b.insert(i, "foo", 3) -> b.insert(i, "foo"):
841 expr
, 1, 2, fdecl
, "rtl::OStringBuffer::insert",
853 bool StringConstant::VisitCXXConstructExpr(CXXConstructExpr
const * expr
) {
854 if (ignoreLocation(expr
)) {
857 auto classdecl
= expr
->getConstructor()->getParent();
858 if (loplugin::DeclCheck(classdecl
)
859 .Class("OUString").Namespace("rtl").GlobalNamespace())
864 switch (expr
->getConstructor()->getNumParams()) {
866 if (!loplugin::TypeCheck(
867 expr
->getConstructor()->getParamDecl(0)->getType())
868 .Typedef("sal_Unicode").GlobalNamespace())
872 kind
= ChangeKind::SingleChar
;
873 pass
= PassThrough::NonEmptyConstantString
;
878 auto arg
= expr
->getArg(0);
879 if (loplugin::TypeCheck(arg
->getType())
880 .Class("OUStringChar_").Namespace("rtl")
883 kind
= ChangeKind::OUStringChar
;
884 pass
= PassThrough::NonEmptyConstantString
;
892 if (!isStringConstant(
893 arg
->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
898 // OSL_THIS_FUNC may be defined as "" or as something other
899 // than a string literal in include/osl/diagnose.h:
900 auto loc
= compat::getBeginLoc(arg
);
901 if (compiler
.getSourceManager().isMacroBodyExpansion(loc
)
902 && (Lexer::getImmediateMacroName(
903 loc
, compiler
.getSourceManager(),
904 compiler
.getLangOpts())
909 if (cont
!= ContentKind::Ascii
) {
911 DiagnosticsEngine::Warning
,
912 ("construction of %0 with string constant argument"
913 " containing non-ASCII characters"),
915 << classdecl
<< expr
->getSourceRange();
919 DiagnosticsEngine::Warning
,
920 ("construction of %0 with string constant argument"
921 " containing embedded NULLs"),
923 << classdecl
<< expr
->getSourceRange();
925 kind
= ChangeKind::Char
;
927 ? PassThrough::EmptyConstantString
928 : PassThrough::NonEmptyConstantString
;
940 std::vector
<char32_t
> utf8Cont
;
941 if (!isStringConstant(
942 expr
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
,
943 &cont
, &emb
, &trm
, &utf8Cont
))
948 if (!compat::EvaluateAsInt(expr
->getArg(1),
949 res
, compiler
.getASTContext()))
955 DiagnosticsEngine::Warning
,
956 ("suspicious 'rtl::OUString' constructor with literal"
957 " of length %0 and non-matching length argument %1"),
959 << n
<< res
.toString(10) << expr
->getSourceRange();
963 if (!compat::EvaluateAsInt(expr
->getArg(2),
964 enc
, compiler
.getASTContext()))
968 auto const encIsAscii
= enc
== 11; // RTL_TEXTENCODING_ASCII_US
969 auto const encIsUtf8
= enc
== 76; // RTL_TEXTENCODING_UTF8
970 if (!compat::EvaluateAsInt(expr
->getArg(3),
971 res
, compiler
.getASTContext())
972 || res
!= 0x333) // OSTRING_TO_OUSTRING_CVTFLAGS
976 if (!encIsAscii
&& cont
== ContentKind::Ascii
) {
978 DiagnosticsEngine::Warning
,
979 ("suspicious 'rtl::OUString' constructor with text"
980 " encoding %0 but plain ASCII content; use"
981 " 'RTL_TEXTENCODING_ASCII_US' instead"),
982 expr
->getArg(2)->getExprLoc())
983 << enc
.toString(10) << expr
->getSourceRange();
987 if (cont
== ContentKind::Arbitrary
) {
989 DiagnosticsEngine::Warning
,
990 ("suspicious 'rtl::OUString' constructor with text"
991 " encoding 'RTL_TEXTENCODING_UTF8' but non-UTF-8"
993 expr
->getArg(0)->getExprLoc())
994 << expr
->getSourceRange();
996 assert(cont
== ContentKind::Utf8
);
997 //TODO: keep original content as much as possible
998 std::ostringstream s
;
999 for (auto const c
: utf8Cont
) {
1002 } else if (c
== '"') {
1004 } else if (c
== '\a') {
1006 } else if (c
== '\b') {
1008 } else if (c
== '\f') {
1010 } else if (c
== '\n') {
1012 } else if (c
== '\r') {
1014 } else if (c
== '\t') {
1016 } else if (c
== '\v') {
1018 } else if (c
<= 0x1F || c
== 0x7F) {
1019 s
<< "\\x" << std::oct
<< std::setw(3)
1020 << std::setfill('0')
1021 << static_cast<std::uint_least32_t>(c
);
1022 } else if (c
< 0x7F) {
1024 } else if (c
<= 0xFFFF) {
1025 s
<< "\\u" << std::hex
<< std::uppercase
1026 << std::setw(4) << std::setfill('0')
1027 << static_cast<std::uint_least32_t>(c
);
1029 assert(c
<= 0x10FFFF);
1030 s
<< "\\U" << std::hex
<< std::uppercase
1031 << std::setw(8) << std::setfill('0')
1032 << static_cast<std::uint_least32_t>(c
);
1036 DiagnosticsEngine::Warning
,
1037 ("simplify construction of %0 with UTF-8 content as"
1038 " OUString(u\"%1\")"),
1040 << classdecl
<< s
.str() << expr
->getSourceRange();
1045 if (cont
!= ContentKind::Ascii
|| emb
) {
1046 // cf. remaining uses of RTL_CONSTASCII_USTRINGPARAM
1049 kind
= ChangeKind::Char
;
1051 ? PassThrough::EmptyConstantString
1052 : PassThrough::NonEmptyConstantString
;
1059 if (!calls_
.empty()) {
1060 Expr
const * call
= calls_
.top();
1061 CallExpr::const_arg_iterator argsBeg
;
1062 CallExpr::const_arg_iterator argsEnd
;
1063 if (isa
<CallExpr
>(call
)) {
1064 argsBeg
= cast
<CallExpr
>(call
)->arg_begin();
1065 argsEnd
= cast
<CallExpr
>(call
)->arg_end();
1066 } else if (isa
<CXXConstructExpr
>(call
)) {
1067 argsBeg
= cast
<CXXConstructExpr
>(call
)->arg_begin();
1068 argsEnd
= cast
<CXXConstructExpr
>(call
)->arg_end();
1072 for (auto i(argsBeg
); i
!= argsEnd
; ++i
) {
1073 Expr
const * e
= (*i
)->IgnoreParenImpCasts();
1074 if (isa
<MaterializeTemporaryExpr
>(e
)) {
1075 e
= cast
<MaterializeTemporaryExpr
>(e
)->GetTemporaryExpr()
1076 ->IgnoreParenImpCasts();
1078 if (isa
<CXXFunctionalCastExpr
>(e
)) {
1079 e
= cast
<CXXFunctionalCastExpr
>(e
)->getSubExpr()
1080 ->IgnoreParenImpCasts();
1082 if (isa
<CXXBindTemporaryExpr
>(e
)) {
1083 e
= cast
<CXXBindTemporaryExpr
>(e
)->getSubExpr()
1084 ->IgnoreParenImpCasts();
1087 if (isa
<CallExpr
>(call
)) {
1088 FunctionDecl
const * fdecl
1089 = cast
<CallExpr
>(call
)->getDirectCallee();
1090 if (fdecl
== nullptr) {
1093 loplugin::DeclCheck
dc(fdecl
);
1094 if (pass
== PassThrough::EmptyConstantString
) {
1095 if ((dc
.Function("equals").Class("OUString")
1096 .Namespace("rtl").GlobalNamespace())
1097 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1098 .GlobalNamespace()))
1101 DiagnosticsEngine::Warning
,
1102 ("rewrite call of '%0' with construction of"
1103 " %1 with empty string constant argument"
1104 " as call of 'rtl::OUString::isEmpty'"),
1105 getMemberLocation(call
))
1106 << fdecl
->getQualifiedNameAsString()
1107 << classdecl
<< call
->getSourceRange();
1110 if (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl")
1114 DiagnosticsEngine::Warning
,
1115 ("rewrite call of '%0' with construction of"
1116 " %1 with empty string constant argument"
1117 " as call of '!rtl::OUString::isEmpty'"),
1118 getMemberLocation(call
))
1119 << fdecl
->getQualifiedNameAsString()
1120 << classdecl
<< call
->getSourceRange();
1123 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
1125 || (dc
.Operator(OO_Plus
).Class("OUString")
1126 .Namespace("rtl").GlobalNamespace()))
1129 DiagnosticsEngine::Warning
,
1130 ("call of '%0' with suspicious construction"
1131 " of %1 with empty string constant"
1133 getMemberLocation(call
))
1134 << fdecl
->getQualifiedNameAsString()
1135 << classdecl
<< call
->getSourceRange();
1138 if (dc
.Operator(OO_Equal
).Class("OUString")
1139 .Namespace("rtl").GlobalNamespace())
1142 DiagnosticsEngine::Warning
,
1143 ("rewrite call of '%0' with construction of"
1144 " %1 with empty string constant argument"
1145 " as call of 'rtl::OUString::clear'"),
1146 getMemberLocation(call
))
1147 << fdecl
->getQualifiedNameAsString()
1148 << classdecl
<< call
->getSourceRange();
1152 assert(pass
== PassThrough::NonEmptyConstantString
);
1153 if (dc
.Function("equals").Class("OUString")
1154 .Namespace("rtl").GlobalNamespace())
1157 DiagnosticsEngine::Warning
,
1158 ("rewrite call of '%0' with construction of"
1159 " %1 with %2 as 'operator =='"),
1160 getMemberLocation(call
))
1161 << fdecl
->getQualifiedNameAsString()
1162 << classdecl
<< describeChangeKind(kind
)
1163 << call
->getSourceRange();
1166 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
1168 || (dc
.Operator(OO_Plus
).Class("OUString")
1169 .Namespace("rtl").GlobalNamespace())
1170 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1172 || (dc
.Operator(OO_ExclaimEqual
)
1173 .Namespace("rtl").GlobalNamespace()))
1175 if (dc
.Operator(OO_Plus
).Namespace("rtl")
1178 auto file
= getFilenameOfLocation(
1179 compiler
.getSourceManager()
1181 compat::getBeginLoc(expr
)));
1182 if (loplugin::isSamePathname(
1185 "/sal/qa/rtl/strings/test_ostring_concat.cxx"))
1186 || loplugin::isSamePathname(
1189 "/sal/qa/rtl/strings/test_oustring_concat.cxx")))
1194 auto loc
= compat::getBeginLoc(expr
->getArg(0));
1195 while (compiler
.getSourceManager()
1196 .isMacroArgExpansion(loc
))
1198 loc
= compiler
.getSourceManager()
1199 .getImmediateMacroCallerLoc(loc
);
1201 if ((compiler
.getSourceManager()
1202 .isMacroBodyExpansion(loc
))
1203 && (Lexer::getImmediateMacroName(
1204 loc
, compiler
.getSourceManager(),
1205 compiler
.getLangOpts())
1206 == "OSL_THIS_FUNC"))
1210 if (kind
== ChangeKind::SingleChar
) {
1212 DiagnosticsEngine::Warning
,
1213 ("rewrite construction of %0 with %1 in"
1214 " call of '%2' as construction of"
1216 getMemberLocation(expr
))
1217 << classdecl
<< describeChangeKind(kind
)
1218 << fdecl
->getQualifiedNameAsString()
1219 << expr
->getSourceRange();
1222 DiagnosticsEngine::Warning
,
1223 ("elide construction of %0 with %1 in"
1225 getMemberLocation(expr
))
1226 << classdecl
<< describeChangeKind(kind
)
1227 << fdecl
->getQualifiedNameAsString()
1228 << expr
->getSourceRange();
1233 } else if (isa
<CXXConstructExpr
>(call
)) {
1242 DiagnosticsEngine::Warning
,
1243 "simplify construction of %0 with %1", expr
->getExprLoc())
1244 << classdecl
<< describeChangeKind(kind
)
1245 << expr
->getSourceRange();
1250 auto consDecl
= expr
->getConstructor();
1251 for (unsigned i
= 0; i
!= consDecl
->getNumParams(); ++i
) {
1252 auto t
= consDecl
->getParamDecl(i
)->getType();
1253 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
1254 .LvalueReference().Const().NotSubstTemplateTypeParmType()
1255 .Class("OUString").Namespace("rtl").GlobalNamespace())
1257 auto argExpr
= expr
->getArg(i
);
1258 if (argExpr
&& i
<= consDecl
->getNumParams())
1260 if (!hasOverloads(consDecl
, expr
->getNumArgs()))
1262 handleOUStringCtor(expr
, argExpr
, consDecl
, true);
1266 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
1267 .LvalueReference().Const().NotSubstTemplateTypeParmType()
1268 .Class("OString").Namespace("rtl").GlobalNamespace())
1270 auto argExpr
= expr
->getArg(i
);
1271 if (argExpr
&& i
<= consDecl
->getNumParams())
1273 if (!hasOverloads(consDecl
, expr
->getNumArgs()))
1275 handleOStringCtor(expr
, argExpr
, consDecl
, true);
1284 bool StringConstant::VisitReturnStmt(ReturnStmt
const * stmt
) {
1285 if (ignoreLocation(stmt
)) {
1288 auto const e1
= stmt
->getRetValue();
1289 if (e1
== nullptr) {
1292 auto const tc1
= loplugin::TypeCheck(e1
->getType().getTypePtr());
1293 if (!(tc1
.Class("OString").Namespace("rtl").GlobalNamespace()
1294 || tc1
.Class("OUString").Namespace("rtl").GlobalNamespace()))
1298 assert(!returnTypes_
.empty());
1299 auto const tc2
= loplugin::TypeCheck(returnTypes_
.top().getTypePtr());
1300 if (!(tc2
.Class("OString").Namespace("rtl").GlobalNamespace()
1301 || tc2
.Class("OUString").Namespace("rtl").GlobalNamespace()))
1305 auto const e2
= dyn_cast
<CXXFunctionalCastExpr
>(e1
->IgnoreImplicit());
1306 if (e2
== nullptr) {
1309 auto const e3
= dyn_cast
<CXXBindTemporaryExpr
>(e2
->getSubExpr());
1310 if (e3
== nullptr) {
1313 auto const e4
= dyn_cast
<CXXConstructExpr
>(e3
->getSubExpr());
1314 if (e4
== nullptr) {
1317 if (e4
->getNumArgs() != 2) {
1320 auto const e5
= e4
->getArg(1);
1321 if (!(isa
<CXXDefaultArgExpr
>(e5
)
1322 && (loplugin::TypeCheck(e5
->getType()).Struct("Dummy").Namespace("libreoffice_internal")
1323 .Namespace("rtl").GlobalNamespace())))
1327 report(DiagnosticsEngine::Warning
, "elide constructor call", compat::getBeginLoc(e1
))
1328 << e1
->getSourceRange();
1332 std::string
StringConstant::describeChangeKind(ChangeKind kind
) {
1334 case ChangeKind::Char
:
1335 return "string constant argument";
1336 case ChangeKind::CharLen
:
1337 return "string constant and matching length arguments";
1338 case ChangeKind::SingleChar
:
1339 return "sal_Unicode argument";
1340 case ChangeKind::OUStringChar
:
1341 return "OUStringChar argument";
1343 llvm_unreachable("unknown change kind");
1346 bool StringConstant::isStringConstant(
1347 Expr
const * expr
, unsigned * size
, bool * nonArray
, ContentKind
* content
,
1348 bool * embeddedNuls
, bool * terminatingNul
,
1349 std::vector
<char32_t
> * utf8Content
)
1351 assert(expr
!= nullptr);
1352 assert(size
!= nullptr);
1353 assert(nonArray
!= nullptr);
1354 assert(content
!= nullptr);
1355 assert(embeddedNuls
!= nullptr);
1356 assert(terminatingNul
!= nullptr);
1357 QualType t
= expr
->getType();
1358 // Look inside RTL_CONSTASCII_STRINGPARAM:
1359 if (loplugin::TypeCheck(t
).Pointer().Const().Char()) {
1360 auto e2
= dyn_cast
<UnaryOperator
>(expr
);
1361 if (e2
!= nullptr && e2
->getOpcode() == UO_AddrOf
) {
1362 auto e3
= dyn_cast
<ArraySubscriptExpr
>(
1363 e2
->getSubExpr()->IgnoreParenImpCasts());
1364 if (e3
== nullptr || !isZero(e3
->getIdx()->IgnoreParenImpCasts())) {
1367 expr
= e3
->getBase()->IgnoreParenImpCasts();
1368 t
= expr
->getType();
1371 if (!t
.isConstQualified()) {
1374 DeclRefExpr
const * dre
= dyn_cast
<DeclRefExpr
>(expr
);
1375 if (dre
!= nullptr) {
1376 VarDecl
const * var
= dyn_cast
<VarDecl
>(dre
->getDecl());
1377 if (var
!= nullptr) {
1378 Expr
const * init
= var
->getAnyInitializer();
1379 if (init
!= nullptr) {
1380 expr
= init
->IgnoreParenImpCasts();
1385 if (loplugin::TypeCheck(t
).Pointer().Const().Char()) {
1387 } else if (t
->isConstantArrayType()
1388 && (loplugin::TypeCheck(
1389 t
->getAsArrayTypeUnsafe()->getElementType())
1396 clang::StringLiteral
const * lit
= dyn_cast
<clang::StringLiteral
>(expr
);
1397 if (lit
!= nullptr) {
1398 if (!(lit
->isAscii() || lit
->isUTF8())) {
1401 unsigned n
= lit
->getLength();
1402 ContentKind cont
= ContentKind::Ascii
;
1405 enum class Utf8State
{ Start
, E0
, EB
, F0
, F4
, Trail1
, Trail2
, Trail3
};
1406 Utf8State s
= Utf8State::Start
;
1407 StringRef str
= lit
->getString();
1408 for (unsigned i
= 0; i
!= n
; ++i
) {
1409 auto const c
= static_cast<unsigned char>(str
[i
]);
1414 case Utf8State::Start
:
1416 if (c
>= 0xC2 && c
<= 0xDF) {
1418 s
= Utf8State::Trail1
;
1419 } else if (c
== 0xE0) {
1422 } else if ((c
>= 0xE1 && c
<= 0xEA)
1423 || (c
>= 0xEE && c
<= 0xEF))
1426 s
= Utf8State::Trail2
;
1427 } else if (c
== 0xEB) {
1430 } else if (c
== 0xF0) {
1433 } else if (c
>= 0xF1 && c
<= 0xF3) {
1435 s
= Utf8State::Trail3
;
1436 } else if (c
== 0xF4) {
1440 cont
= ContentKind::Arbitrary
;
1442 } else if (utf8Content
!= nullptr
1443 && cont
!= ContentKind::Arbitrary
)
1445 utf8Content
->push_back(c
);
1449 if (c
>= 0xA0 && c
<= 0xBF) {
1450 val
= (val
<< 6) | (c
& 0x3F);
1451 s
= Utf8State::Trail1
;
1453 cont
= ContentKind::Arbitrary
;
1454 s
= Utf8State::Start
;
1458 if (c
>= 0x80 && c
<= 0x9F) {
1459 val
= (val
<< 6) | (c
& 0x3F);
1460 s
= Utf8State::Trail1
;
1462 cont
= ContentKind::Arbitrary
;
1463 s
= Utf8State::Start
;
1467 if (c
>= 0x90 && c
<= 0xBF) {
1468 val
= (val
<< 6) | (c
& 0x3F);
1469 s
= Utf8State::Trail2
;
1471 cont
= ContentKind::Arbitrary
;
1472 s
= Utf8State::Start
;
1476 if (c
>= 0x80 && c
<= 0x8F) {
1477 val
= (val
<< 6) | (c
& 0x3F);
1478 s
= Utf8State::Trail2
;
1480 cont
= ContentKind::Arbitrary
;
1481 s
= Utf8State::Start
;
1484 case Utf8State::Trail1
:
1485 if (c
>= 0x80 && c
<= 0xBF) {
1486 cont
= ContentKind::Utf8
;
1487 if (utf8Content
!= nullptr)
1489 utf8Content
->push_back((val
<< 6) | (c
& 0x3F));
1493 cont
= ContentKind::Arbitrary
;
1495 s
= Utf8State::Start
;
1497 case Utf8State::Trail2
:
1498 if (c
>= 0x80 && c
<= 0xBF) {
1499 val
= (val
<< 6) | (c
& 0x3F);
1500 s
= Utf8State::Trail1
;
1502 cont
= ContentKind::Arbitrary
;
1503 s
= Utf8State::Start
;
1506 case Utf8State::Trail3
:
1507 if (c
>= 0x80 && c
<= 0xBF) {
1508 val
= (val
<< 6) | (c
& 0x3F);
1509 s
= Utf8State::Trail2
;
1511 cont
= ContentKind::Arbitrary
;
1512 s
= Utf8State::Start
;
1520 *embeddedNuls
= emb
;
1521 *terminatingNul
= true;
1525 if (!expr
->isCXX11ConstantExpr(compiler
.getASTContext(), &v
)) {
1528 switch (v
.getKind()) {
1529 case APValue::LValue
:
1531 Expr
const * e
= v
.getLValueBase().dyn_cast
<Expr
const *>();
1535 if (!v
.getLValueOffset().isZero()) {
1536 return false; //TODO
1538 Expr
const * e2
= e
->IgnoreParenImpCasts();
1540 return isStringConstant(
1541 e2
, size
, nonArray
, content
, embeddedNuls
, terminatingNul
);
1543 //TODO: string literals are represented as recursive LValues???
1545 = compiler
.getASTContext().getAsConstantArrayType(t
)->getSize();
1548 assert(n
.ule(std::numeric_limits
<unsigned>::max()));
1549 *size
= static_cast<unsigned>(n
.getLimitedValue());
1550 *nonArray
= isPtr
|| *nonArray
;
1551 *content
= ContentKind::Ascii
; //TODO
1552 *embeddedNuls
= false; //TODO
1553 *terminatingNul
= true;
1556 case APValue::Array
:
1558 if (v
.hasArrayFiller()) { //TODO: handle final NULL filler?
1561 unsigned n
= v
.getArraySize();
1563 ContentKind cont
= ContentKind::Ascii
;
1565 //TODO: check for ContentType::Utf8
1566 for (unsigned i
= 0; i
!= n
- 1; ++i
) {
1567 APValue
e(v
.getArrayInitializedElt(i
));
1568 if (!e
.isInt()) { //TODO: assert?
1571 APSInt iv
= e
.getInt();
1574 } else if (iv
.uge(0x80)) {
1575 cont
= ContentKind::Arbitrary
;
1578 APValue
e(v
.getArrayInitializedElt(n
- 1));
1579 if (!e
.isInt()) { //TODO: assert?
1582 bool trm
= e
.getInt() == 0;
1583 *size
= trm
? n
- 1 : n
;
1586 *embeddedNuls
= emb
;
1587 *terminatingNul
= trm
;
1591 assert(false); //TODO???
1596 bool StringConstant::isZero(Expr
const * expr
) {
1598 return compat::EvaluateAsInt(expr
, res
, compiler
.getASTContext()) && res
== 0;
1601 void StringConstant::reportChange(
1602 Expr
const * expr
, ChangeKind kind
, std::string
const & original
,
1603 std::string
const & replacement
, PassThrough pass
, bool nonArray
,
1604 char const * rewriteFrom
, char const * rewriteTo
)
1606 assert((rewriteFrom
== nullptr) == (rewriteTo
== nullptr));
1607 if (pass
!= PassThrough::No
&& !calls_
.empty()) {
1608 Expr
const * call
= calls_
.top();
1609 CallExpr::const_arg_iterator argsBeg
;
1610 CallExpr::const_arg_iterator argsEnd
;
1611 if (isa
<CallExpr
>(call
)) {
1612 argsBeg
= cast
<CallExpr
>(call
)->arg_begin();
1613 argsEnd
= cast
<CallExpr
>(call
)->arg_end();
1614 } else if (isa
<CXXConstructExpr
>(call
)) {
1615 argsBeg
= cast
<CXXConstructExpr
>(call
)->arg_begin();
1616 argsEnd
= cast
<CXXConstructExpr
>(call
)->arg_end();
1620 for (auto i(argsBeg
); i
!= argsEnd
; ++i
) {
1621 Expr
const * e
= (*i
)->IgnoreParenImpCasts();
1622 if (isa
<CXXBindTemporaryExpr
>(e
)) {
1623 e
= cast
<CXXBindTemporaryExpr
>(e
)->getSubExpr()
1624 ->IgnoreParenImpCasts();
1627 if (isa
<CallExpr
>(call
)) {
1628 FunctionDecl
const * fdecl
1629 = cast
<CallExpr
>(call
)->getDirectCallee();
1630 if (fdecl
== nullptr) {
1633 loplugin::DeclCheck
dc(fdecl
);
1634 if (pass
== PassThrough::EmptyConstantString
) {
1635 if ((dc
.Function("equals").Class("OUString")
1636 .Namespace("rtl").GlobalNamespace())
1637 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1638 .GlobalNamespace()))
1641 DiagnosticsEngine::Warning
,
1642 ("rewrite call of '%0' with call of %1 with"
1643 " empty string constant argument as call of"
1644 " 'rtl::OUString::isEmpty'"),
1645 getMemberLocation(call
))
1646 << fdecl
->getQualifiedNameAsString() << original
1647 << call
->getSourceRange();
1650 if (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl")
1654 DiagnosticsEngine::Warning
,
1655 ("rewrite call of '%0' with call of %1 with"
1656 " empty string constant argument as call of"
1657 " '!rtl::OUString::isEmpty'"),
1658 getMemberLocation(call
))
1659 << fdecl
->getQualifiedNameAsString() << original
1660 << call
->getSourceRange();
1663 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
1665 || (dc
.Operator(OO_Plus
).Class("OUString")
1666 .Namespace("rtl").GlobalNamespace()))
1669 DiagnosticsEngine::Warning
,
1670 ("call of '%0' with suspicious call of %1 with"
1671 " empty string constant argument"),
1672 getMemberLocation(call
))
1673 << fdecl
->getQualifiedNameAsString() << original
1674 << call
->getSourceRange();
1677 if (dc
.Operator(OO_Equal
).Class("OUString")
1678 .Namespace("rtl").GlobalNamespace())
1681 DiagnosticsEngine::Warning
,
1682 ("rewrite call of '%0' with call of %1 with"
1683 " empty string constant argument as call of"
1684 " rtl::OUString::call"),
1685 getMemberLocation(call
))
1686 << fdecl
->getQualifiedNameAsString() << original
1687 << call
->getSourceRange();
1691 DiagnosticsEngine::Warning
,
1692 "TODO call inside %0", getMemberLocation(expr
))
1693 << fdecl
->getQualifiedNameAsString()
1694 << expr
->getSourceRange();
1697 assert(pass
== PassThrough::NonEmptyConstantString
);
1698 if ((dc
.Function("equals").Class("OUString")
1699 .Namespace("rtl").GlobalNamespace())
1700 || (dc
.Operator(OO_Equal
).Class("OUString")
1701 .Namespace("rtl").GlobalNamespace())
1702 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1704 || (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl")
1705 .GlobalNamespace()))
1708 DiagnosticsEngine::Warning
,
1709 "elide call of %0 with %1 in call of '%2'",
1710 getMemberLocation(expr
))
1711 << original
<< describeChangeKind(kind
)
1712 << fdecl
->getQualifiedNameAsString()
1713 << expr
->getSourceRange();
1717 DiagnosticsEngine::Warning
,
1718 ("rewrite call of %0 with %1 in call of '%2' as"
1719 " (implicit) construction of 'OUString'"),
1720 getMemberLocation(expr
))
1721 << original
<< describeChangeKind(kind
)
1722 << fdecl
->getQualifiedNameAsString()
1723 << expr
->getSourceRange();
1726 } else if (isa
<CXXConstructExpr
>(call
)) {
1727 auto classdecl
= cast
<CXXConstructExpr
>(call
)
1728 ->getConstructor()->getParent();
1729 loplugin::DeclCheck
dc(classdecl
);
1730 if (dc
.Class("OUString").Namespace("rtl").GlobalNamespace()
1731 || (dc
.Class("OUStringBuffer").Namespace("rtl")
1732 .GlobalNamespace()))
1734 //TODO: propagate further out?
1735 if (pass
== PassThrough::EmptyConstantString
) {
1737 DiagnosticsEngine::Warning
,
1738 ("rewrite construction of %0 with call of %1"
1739 " with empty string constant argument as"
1740 " default construction of %0"),
1741 getMemberLocation(call
))
1742 << classdecl
<< original
1743 << call
->getSourceRange();
1745 assert(pass
== PassThrough::NonEmptyConstantString
);
1747 DiagnosticsEngine::Warning
,
1748 ("elide call of %0 with %1 in construction of"
1750 getMemberLocation(expr
))
1751 << original
<< describeChangeKind(kind
)
1752 << classdecl
<< expr
->getSourceRange();
1762 if (rewriter
!= nullptr && !nonArray
&& rewriteFrom
!= nullptr) {
1763 SourceLocation loc
= getMemberLocation(expr
);
1764 while (compiler
.getSourceManager().isMacroArgExpansion(loc
)) {
1765 loc
= compiler
.getSourceManager().getImmediateMacroCallerLoc(loc
);
1767 if (compiler
.getSourceManager().isMacroBodyExpansion(loc
)) {
1768 loc
= compiler
.getSourceManager().getSpellingLoc(loc
);
1770 unsigned n
= Lexer::MeasureTokenLength(
1771 loc
, compiler
.getSourceManager(), compiler
.getLangOpts());
1772 if ((std::string(compiler
.getSourceManager().getCharacterData(loc
), n
)
1774 && replaceText(loc
, n
, rewriteTo
))
1780 DiagnosticsEngine::Warning
,
1781 "rewrite call of '%0' with %1 as call of '%2'%3",
1782 getMemberLocation(expr
))
1783 << original
<< describeChangeKind(kind
) << replacement
1784 << adviseNonArray(nonArray
) << expr
->getSourceRange();
1787 void StringConstant::checkEmpty(
1788 CallExpr
const * expr
, FunctionDecl
const * callee
, TreatEmpty treatEmpty
,
1789 unsigned size
, std::string
* replacement
)
1791 assert(replacement
!= nullptr);
1793 switch (treatEmpty
) {
1794 case TreatEmpty::DefaultCtor
:
1795 *replacement
= "rtl::OUString default constructor";
1797 case TreatEmpty::CheckEmpty
:
1798 *replacement
= "rtl::OUString::isEmpty";
1800 case TreatEmpty::Error
:
1802 DiagnosticsEngine::Warning
,
1803 "call of '%0' with suspicious empty string constant argument",
1804 getMemberLocation(expr
))
1805 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1811 void StringConstant::handleChar(
1812 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
1813 std::string
const & replacement
, TreatEmpty treatEmpty
, bool literal
,
1814 char const * rewriteFrom
, char const * rewriteTo
)
1821 if (!isStringConstant(
1822 expr
->getArg(arg
)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
1827 if (cont
!= ContentKind::Ascii
) {
1829 DiagnosticsEngine::Warning
,
1830 ("call of '%0' with string constant argument containing non-ASCII"
1832 getMemberLocation(expr
))
1833 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1838 DiagnosticsEngine::Warning
,
1839 ("call of '%0' with string constant argument containing embedded"
1841 getMemberLocation(expr
))
1842 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1847 DiagnosticsEngine::Warning
,
1848 ("call of '%0' with string constant argument lacking a terminating"
1850 getMemberLocation(expr
))
1851 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1854 std::string
repl(replacement
);
1855 checkEmpty(expr
, callee
, treatEmpty
, n
, &repl
);
1856 if (!repl
.empty()) {
1858 expr
, ChangeKind::Char
, callee
->getQualifiedNameAsString(), repl
,
1861 ? PassThrough::EmptyConstantString
1862 : PassThrough::NonEmptyConstantString
)
1864 nonArray
, rewriteFrom
, rewriteTo
);
1868 void StringConstant::handleCharLen(
1869 CallExpr
const * expr
, unsigned arg1
, unsigned arg2
,
1870 FunctionDecl
const * callee
, std::string
const & replacement
,
1871 TreatEmpty treatEmpty
)
1873 // Especially for f(RTL_CONSTASCII_STRINGPARAM("foo")), where
1874 // RTL_CONSTASCII_STRINGPARAM expands to complicated expressions involving
1875 // (&(X)[0] sub-expressions (and it might or might not be better to handle
1876 // that at the level of non-expanded macros instead, but I have not found
1877 // out how to do that yet anyway):
1883 if (!(isStringConstant(
1884 expr
->getArg(arg1
)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
1891 if (compat::EvaluateAsInt(expr
->getArg(arg2
), res
, compiler
.getASTContext())) {
1896 UnaryOperator
const * op
= dyn_cast
<UnaryOperator
>(
1897 expr
->getArg(arg1
)->IgnoreParenImpCasts());
1898 if (op
== nullptr || op
->getOpcode() != UO_AddrOf
) {
1901 ArraySubscriptExpr
const * subs
= dyn_cast
<ArraySubscriptExpr
>(
1902 op
->getSubExpr()->IgnoreParenImpCasts());
1903 if (subs
== nullptr) {
1911 if (!(isStringConstant(
1912 subs
->getBase()->IgnoreParenImpCasts(), &n2
, &nonArray2
,
1913 &cont2
, &emb2
, &trm2
)
1914 && n2
== n
&& cont2
== cont
&& emb2
== emb
&& trm2
== trm
1915 //TODO: same strings
1916 && compat::EvaluateAsInt(subs
->getIdx(), res
, compiler
.getASTContext())
1922 if (cont
!= ContentKind::Ascii
) {
1924 DiagnosticsEngine::Warning
,
1925 ("call of '%0' with string constant argument containing non-ASCII"
1927 getMemberLocation(expr
))
1928 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1933 std::string
repl(replacement
);
1934 checkEmpty(expr
, callee
, treatEmpty
, n
, &repl
);
1936 expr
, ChangeKind::CharLen
, callee
->getQualifiedNameAsString(), repl
,
1937 PassThrough::No
, nonArray
, nullptr, nullptr);
1940 void StringConstant::handleOUStringCtor(
1941 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
1942 bool explicitFunctionalCastNotation
)
1944 handleOUStringCtor(expr
, expr
->getArg(arg
), callee
, explicitFunctionalCastNotation
);
1947 void StringConstant::handleOStringCtor(
1948 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
1949 bool explicitFunctionalCastNotation
)
1951 handleOStringCtor(expr
, expr
->getArg(arg
), callee
, explicitFunctionalCastNotation
);
1954 void StringConstant::handleOUStringCtor(
1955 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
1956 bool explicitFunctionalCastNotation
)
1958 handleStringCtor(expr
, argExpr
, callee
, explicitFunctionalCastNotation
, StringKind::Unicode
);
1961 void StringConstant::handleOStringCtor(
1962 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
1963 bool explicitFunctionalCastNotation
)
1965 handleStringCtor(expr
, argExpr
, callee
, explicitFunctionalCastNotation
, StringKind::Char
);
1968 void StringConstant::handleStringCtor(
1969 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
1970 bool explicitFunctionalCastNotation
, StringKind stringKind
)
1972 auto e0
= argExpr
->IgnoreParenImpCasts();
1973 auto e1
= dyn_cast
<CXXFunctionalCastExpr
>(e0
);
1974 if (e1
== nullptr) {
1975 if (explicitFunctionalCastNotation
) {
1979 e0
= e1
->getSubExpr()->IgnoreParenImpCasts();
1981 auto e2
= dyn_cast
<CXXBindTemporaryExpr
>(e0
);
1982 if (e2
== nullptr) {
1985 auto e3
= dyn_cast
<CXXConstructExpr
>(
1986 e2
->getSubExpr()->IgnoreParenImpCasts());
1987 if (e3
== nullptr) {
1990 if (!loplugin::DeclCheck(e3
->getConstructor()).MemberFunction()
1991 .Class(stringKind
== StringKind::Unicode
? "OUString" : "OString").Namespace("rtl").GlobalNamespace())
1995 if (e3
->getNumArgs() == 0) {
1997 DiagnosticsEngine::Warning
,
1998 ("in call of '%0', replace default-constructed 'OUString' with an"
1999 " empty string literal"),
2001 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2004 if (e3
->getNumArgs() == 1
2005 && e3
->getConstructor()->getNumParams() == 1
2006 && (loplugin::TypeCheck(
2007 e3
->getConstructor()->getParamDecl(0)->getType())
2008 .Typedef(stringKind
== StringKind::Unicode
? "sal_Unicode" : "char").GlobalNamespace()))
2010 // It may not be easy to rewrite OUString(c), esp. given there is no
2011 // OUString ctor taking an OUStringChar arg, so don't warn there:
2012 if (!explicitFunctionalCastNotation
) {
2014 DiagnosticsEngine::Warning
,
2015 ("in call of '%0', replace 'OUString' constructed from a"
2016 " 'sal_Unicode' with an 'OUStringChar'"),
2018 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2022 if (e3
->getNumArgs() != 2) {
2030 if (!isStringConstant(
2031 e3
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
, &emb
,
2036 //TODO: cont, emb, trm
2037 if (rewriter
!= nullptr) {
2038 auto loc1
= compat::getBeginLoc(e3
);
2039 auto range
= e3
->getParenOrBraceRange();
2040 if (loc1
.isFileID() && range
.getBegin().isFileID()
2041 && range
.getEnd().isFileID())
2043 auto loc2
= range
.getBegin();
2044 for (bool first
= true;; first
= false) {
2045 unsigned n
= Lexer::MeasureTokenLength(
2046 loc2
, compiler
.getSourceManager(), compiler
.getLangOpts());
2049 compiler
.getSourceManager().getCharacterData(loc2
), n
);
2050 while (s
.startswith("\\\n")) {
2051 s
= s
.drop_front(2);
2053 && (s
.front() == ' ' || s
.front() == '\t'
2054 || s
.front() == '\n' || s
.front() == '\v'
2055 || s
.front() == '\f'))
2057 s
= s
.drop_front(1);
2060 if (!(s
.empty() || s
.startswith("/*") || s
.startswith("//")
2066 loc2
= loc2
.getLocWithOffset(std::max
<unsigned>(n
, 1));
2068 auto loc3
= range
.getEnd();
2070 auto l
= Lexer::GetBeginningOfToken(
2071 loc3
.getLocWithOffset(-1), compiler
.getSourceManager(),
2072 compiler
.getLangOpts());
2073 unsigned n
= Lexer::MeasureTokenLength(
2074 l
, compiler
.getSourceManager(), compiler
.getLangOpts());
2075 StringRef
s(compiler
.getSourceManager().getCharacterData(l
), n
);
2076 while (s
.startswith("\\\n")) {
2077 s
= s
.drop_front(2);
2079 && (s
.front() == ' ' || s
.front() == '\t'
2080 || s
.front() == '\n' || s
.front() == '\v'
2081 || s
.front() == '\f'))
2083 s
= s
.drop_front(1);
2086 if (!(s
.empty() || s
.startswith("/*") || s
.startswith("//")
2093 if (removeText(CharSourceRange(SourceRange(loc1
, loc2
), false))) {
2094 if (removeText(SourceRange(loc3
, range
.getEnd()))) {
2097 report(DiagnosticsEngine::Fatal
, "Corrupt rewrite", loc3
)
2098 << expr
->getSourceRange();
2104 DiagnosticsEngine::Warning
,
2105 ("in call of '%0', replace 'OUString' constructed from a string literal"
2106 " directly with the string literal"),
2108 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2111 void StringConstant::handleFunArgOstring(
2112 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
)
2114 auto argExpr
= expr
->getArg(arg
)->IgnoreParenImpCasts();
2120 if (isStringConstant(argExpr
, &n
, &nonArray
, &cont
, &emb
, &trm
)) {
2121 if (cont
!= ContentKind::Ascii
|| emb
) {
2126 DiagnosticsEngine::Warning
,
2127 ("call of '%0' with string constant argument lacking a"
2128 " terminating NULL"),
2129 getMemberLocation(expr
))
2130 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2134 checkEmpty(expr
, callee
, TreatEmpty::Error
, n
, &repl
);
2137 DiagnosticsEngine::Warning
,
2138 ("in call of '%0' with non-array string constant argument,"
2139 " turn the non-array string constant into an array"),
2140 getMemberLocation(expr
))
2141 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2143 } else if (auto cexpr
= lookForCXXConstructExpr(argExpr
)) {
2144 auto classdecl
= cexpr
->getConstructor()->getParent();
2145 if (loplugin::DeclCheck(classdecl
).Class("OString").Namespace("rtl")
2148 switch (cexpr
->getConstructor()->getNumParams()) {
2151 DiagnosticsEngine::Warning
,
2152 ("in call of '%0', replace empty %1 constructor with empty"
2154 cexpr
->getLocation())
2155 << callee
->getQualifiedNameAsString() << classdecl
2156 << expr
->getSourceRange();
2159 if (isStringConstant(
2160 cexpr
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
,
2164 if (compat::EvaluateAsInt(cexpr
->getArg(1),
2165 res
, compiler
.getASTContext()))
2167 if (res
== n
&& !emb
&& trm
) {
2169 DiagnosticsEngine::Warning
,
2170 ("in call of '%0', elide explicit %1"
2172 cexpr
->getLocation())
2173 << callee
->getQualifiedNameAsString()
2174 << classdecl
<< adviseNonArray(nonArray
)
2175 << expr
->getSourceRange();
2180 DiagnosticsEngine::Warning
,
2181 ("call of %0 constructor with string constant"
2182 " argument containing embedded NULLs"),
2183 cexpr
->getLocation())
2184 << classdecl
<< cexpr
->getSourceRange();
2189 DiagnosticsEngine::Warning
,
2190 ("call of %0 constructor with string constant"
2191 " argument lacking a terminating NULL"),
2192 cexpr
->getLocation())
2193 << classdecl
<< cexpr
->getSourceRange();
2197 DiagnosticsEngine::Warning
,
2198 "in call of '%0', elide explicit %1 constructor%2",
2199 cexpr
->getLocation())
2200 << callee
->getQualifiedNameAsString() << classdecl
2201 << adviseNonArray(nonArray
)
2202 << expr
->getSourceRange();
2213 loplugin::Plugin::Registration
< StringConstant
> X("stringconstant", true);
2217 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */