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/.
25 // Define a "string constant" to be a constant expression either of type "array
26 // of N char" where each array element is a non-NULL ASCII character---except
27 // that the last array element may be NULL, or, in some situations, of type char
28 // with an ASCII value (including NULL). Note that the former includes
29 // expressions denoting narrow string literals like "foo", and, with toolchains
30 // that support constexpr, constexpr variables declared like
32 // constexpr char str[] = "bar";
34 // This plugin flags uses of OUString functions with string constant arguments
35 // that can be rewritten more directly, like
37 // OUString::createFromAscii("foo") -> "foo"
41 // s.equals(OUString("bar")) -> s == "bar"
45 SourceLocation
getMemberLocation(Expr
const * expr
) {
46 CallExpr
const * e1
= dyn_cast
<CallExpr
>(expr
);
47 MemberExpr
const * e2
= e1
== nullptr
48 ? nullptr : dyn_cast
<MemberExpr
>(e1
->getCallee());
49 return e2
== nullptr ? expr
->getExprLoc()/*TODO*/ : e2
->getMemberLoc();
52 bool isLhsOfAssignment(FunctionDecl
const * decl
, unsigned parameter
) {
56 auto oo
= decl
->getOverloadedOperator();
58 || (oo
>= OO_PlusEqual
&& oo
<= OO_GreaterGreaterEqual
);
61 bool hasOverloads(FunctionDecl
const * decl
, unsigned arguments
) {
63 auto ctx
= decl
->getDeclContext();
64 if (ctx
->getDeclKind() == Decl::LinkageSpec
) {
65 ctx
= ctx
->getParent();
67 auto res
= ctx
->lookup(decl
->getDeclName());
68 for (auto d
= res
.begin(); d
!= res
.end(); ++d
) {
69 FunctionDecl
const * f
= dyn_cast
<FunctionDecl
>(*d
);
70 if (f
!= nullptr && f
->getMinRequiredArguments() <= arguments
71 && f
->getNumParams() >= arguments
)
73 auto consDecl
= dyn_cast
<CXXConstructorDecl
>(f
);
74 if (consDecl
&& consDecl
->isCopyOrMoveConstructor()) {
86 CXXConstructExpr
const * lookForCXXConstructExpr(Expr
const * expr
) {
87 if (auto e
= dyn_cast
<MaterializeTemporaryExpr
>(expr
)) {
88 expr
= e
->GetTemporaryExpr();
90 if (auto e
= dyn_cast
<CXXFunctionalCastExpr
>(expr
)) {
91 expr
= e
->getSubExpr();
93 if (auto e
= dyn_cast
<CXXBindTemporaryExpr
>(expr
)) {
94 expr
= e
->getSubExpr();
96 return dyn_cast
<CXXConstructExpr
>(expr
);
99 char const * adviseNonArray(bool nonArray
) {
101 ? ", and turn the non-array string constant into an array" : "";
104 class StringConstant
:
105 public loplugin::FilteringRewritePlugin
<StringConstant
>
108 explicit StringConstant(loplugin::InstantiationData
const & data
):
109 FilteringRewritePlugin(data
) {}
113 bool TraverseCallExpr(CallExpr
* expr
);
115 bool TraverseCXXMemberCallExpr(CXXMemberCallExpr
* expr
);
117 bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr
* expr
);
119 bool TraverseCXXConstructExpr(CXXConstructExpr
* expr
);
121 bool VisitCallExpr(CallExpr
const * expr
);
123 bool VisitCXXConstructExpr(CXXConstructExpr
const * expr
);
126 enum class ContentKind
{ Ascii
, Utf8
, Arbitrary
};
128 enum class TreatEmpty
{ DefaultCtor
, CheckEmpty
, Error
};
130 enum class ChangeKind
{ Char
, CharLen
, SingleChar
, OUStringLiteral1
};
132 enum class PassThrough
{ No
, EmptyConstantString
, NonEmptyConstantString
};
134 std::string
describeChangeKind(ChangeKind kind
);
136 bool isStringConstant(
137 Expr
const * expr
, unsigned * size
, bool * nonArray
,
138 ContentKind
* content
, bool * embeddedNuls
, bool * terminatingNul
,
139 std::vector
<char32_t
> * utf8Content
= nullptr);
141 bool isZero(Expr
const * expr
);
144 Expr
const * expr
, ChangeKind kind
, std::string
const & original
,
145 std::string
const & replacement
, PassThrough pass
, bool nonArray
,
146 char const * rewriteFrom
, char const * rewriteTo
);
149 CallExpr
const * expr
, FunctionDecl
const * callee
,
150 TreatEmpty treatEmpty
, unsigned size
, std::string
* replacement
);
153 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
154 std::string
const & replacement
, TreatEmpty treatEmpty
, bool literal
,
155 char const * rewriteFrom
= nullptr, char const * rewriteTo
= nullptr);
158 CallExpr
const * expr
, unsigned arg1
, unsigned arg2
,
159 FunctionDecl
const * callee
, std::string
const & replacement
,
160 TreatEmpty treatEmpty
);
162 void handleOUStringCtor(
163 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
164 bool explicitFunctionalCastNotation
);
166 void handleOStringCtor(
167 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
168 bool explicitFunctionalCastNotation
);
170 void handleOUStringCtor(
171 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
172 bool explicitFunctionalCastNotation
);
174 void handleOStringCtor(
175 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
176 bool explicitFunctionalCastNotation
);
178 enum class StringKind
{ Unicode
, Char
};
179 void handleStringCtor(
180 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
181 bool explicitFunctionalCastNotation
, StringKind stringKind
);
183 void handleFunArgOstring(
184 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
);
186 std::stack
<Expr
const *> calls_
;
189 void StringConstant::run() {
190 if (compiler
.getLangOpts().CPlusPlus
191 && compiler
.getPreprocessor().getIdentifierInfo(
192 "LIBO_INTERNAL_ONLY")->hasMacroDefinition())
193 //TODO: some parts of it are useful for external code, too
195 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
199 bool StringConstant::TraverseCallExpr(CallExpr
* expr
) {
200 if (!WalkUpFromCallExpr(expr
)) {
205 for (auto * e
: expr
->children()) {
206 if (!TraverseStmt(e
)) {
215 bool StringConstant::TraverseCXXMemberCallExpr(CXXMemberCallExpr
* expr
) {
216 if (!WalkUpFromCXXMemberCallExpr(expr
)) {
221 for (auto * e
: expr
->children()) {
222 if (!TraverseStmt(e
)) {
231 bool StringConstant::TraverseCXXOperatorCallExpr(CXXOperatorCallExpr
* expr
)
233 if (!WalkUpFromCXXOperatorCallExpr(expr
)) {
238 for (auto * e
: expr
->children()) {
239 if (!TraverseStmt(e
)) {
248 bool StringConstant::TraverseCXXConstructExpr(CXXConstructExpr
* expr
) {
249 if (!WalkUpFromCXXConstructExpr(expr
)) {
254 for (auto * e
: expr
->children()) {
255 if (!TraverseStmt(e
)) {
264 bool StringConstant::VisitCallExpr(CallExpr
const * expr
) {
265 if (ignoreLocation(expr
)) {
268 FunctionDecl
const * fdecl
= expr
->getDirectCallee();
269 if (fdecl
== nullptr) {
272 for (unsigned i
= 0; i
!= fdecl
->getNumParams(); ++i
) {
273 auto t
= fdecl
->getParamDecl(i
)->getType();
274 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
275 .LvalueReference().Const().NotSubstTemplateTypeParmType()
276 .Class("OUString").Namespace("rtl").GlobalNamespace())
278 if (!(isLhsOfAssignment(fdecl
, i
)
279 || hasOverloads(fdecl
, expr
->getNumArgs())))
281 handleOUStringCtor(expr
, i
, fdecl
, true);
284 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
285 .LvalueReference().Const().NotSubstTemplateTypeParmType()
286 .Class("OString").Namespace("rtl").GlobalNamespace())
288 if (!(isLhsOfAssignment(fdecl
, i
)
289 || hasOverloads(fdecl
, expr
->getNumArgs())))
291 handleOStringCtor(expr
, i
, fdecl
, true);
295 loplugin::DeclCheck
dc(fdecl
);
296 //TODO: u.compareToAscii("foo") -> u.???("foo")
297 //TODO: u.compareToIgnoreAsciiCaseAscii("foo") -> u.???("foo")
298 if ((dc
.Function("createFromAscii").Class("OUString").Namespace("rtl")
300 && fdecl
->getNumParams() == 1)
302 // OUString::createFromAscii("foo") -> OUString("foo")
304 expr
, 0, fdecl
, "rtl::OUString constructor",
305 TreatEmpty::DefaultCtor
, true);
308 if ((dc
.Function("endsWithAsciiL").Class("OUString").Namespace("rtl")
310 && fdecl
->getNumParams() == 2)
312 // u.endsWithAsciiL("foo", 3) -> u.endsWith("foo"):
314 expr
, 0, 1, fdecl
, "rtl::OUString::endsWith", TreatEmpty::Error
);
317 if ((dc
.Function("endsWithIgnoreAsciiCaseAsciiL").Class("OUString")
318 .Namespace("rtl").GlobalNamespace())
319 && fdecl
->getNumParams() == 2)
321 // u.endsWithIgnoreAsciiCaseAsciiL("foo", 3) ->
322 // u.endsWithIgnoreAsciiCase("foo"):
324 expr
, 0, 1, fdecl
, "rtl::OUString::endsWithIgnoreAsciiCase",
328 if ((dc
.Function("equalsAscii").Class("OUString").Namespace("rtl")
330 && fdecl
->getNumParams() == 1)
332 // u.equalsAscii("foo") -> u == "foo":
334 expr
, 0, fdecl
, "operator ==", TreatEmpty::CheckEmpty
, false);
337 if ((dc
.Function("equalsAsciiL").Class("OUString").Namespace("rtl")
339 && fdecl
->getNumParams() == 2)
341 // u.equalsAsciiL("foo", 3) -> u == "foo":
342 handleCharLen(expr
, 0, 1, fdecl
, "operator ==", TreatEmpty::CheckEmpty
);
345 if ((dc
.Function("equalsIgnoreAsciiCaseAscii").Class("OUString")
346 .Namespace("rtl").GlobalNamespace())
347 && fdecl
->getNumParams() == 1)
349 // u.equalsIgnoreAsciiCaseAscii("foo") ->
350 // u.equalsIngoreAsciiCase("foo"):
352 auto file
= getFileNameOfSpellingLoc(
353 compiler
.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr
)));
354 if (loplugin::isSamePathname(
355 file
, SRCDIR
"/sal/qa/rtl/strings/test_oustring_compare.cxx"))
360 expr
, 0, fdecl
, "rtl::OUString::equalsIgnoreAsciiCase",
361 TreatEmpty::CheckEmpty
, false);
364 if ((dc
.Function("equalsIgnoreAsciiCaseAsciiL").Class("OUString")
365 .Namespace("rtl").GlobalNamespace())
366 && fdecl
->getNumParams() == 2)
368 // u.equalsIgnoreAsciiCaseAsciiL("foo", 3) ->
369 // u.equalsIngoreAsciiCase("foo"):
370 auto file
= getFileNameOfSpellingLoc(
371 compiler
.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr
)));
372 if (loplugin::isSamePathname(
373 file
, SRCDIR
"/sal/qa/rtl/strings/test_oustring_compare.cxx"))
378 expr
, 0, 1, fdecl
, "rtl::OUString::equalsIgnoreAsciiCase",
379 TreatEmpty::CheckEmpty
);
382 if ((dc
.Function("indexOfAsciiL").Class("OUString").Namespace("rtl")
384 && fdecl
->getNumParams() == 3)
386 assert(expr
->getNumArgs() == 3);
387 // u.indexOfAsciiL("foo", 3, i) -> u.indexOf("foo", i):
389 expr
, 0, 1, fdecl
, "rtl::OUString::indexOf", TreatEmpty::Error
);
392 if ((dc
.Function("lastIndexOfAsciiL").Class("OUString").Namespace("rtl")
394 && fdecl
->getNumParams() == 2)
396 // u.lastIndexOfAsciiL("foo", 3) -> u.lastIndexOf("foo"):
398 expr
, 0, 1, fdecl
, "rtl::OUString::lastIndexOf", TreatEmpty::Error
);
401 if ((dc
.Function("matchAsciiL").Class("OUString").Namespace("rtl")
403 && fdecl
->getNumParams() == 3)
405 assert(expr
->getNumArgs() == 3);
406 // u.matchAsciiL("foo", 3, i) -> u.match("foo", i):
409 (isZero(expr
->getArg(2))
410 ? std::string("rtl::OUString::startsWith")
411 : std::string("rtl::OUString::match")),
415 if ((dc
.Function("matchIgnoreAsciiCaseAsciiL").Class("OUString")
416 .Namespace("rtl").GlobalNamespace())
417 && fdecl
->getNumParams() == 3)
419 assert(expr
->getNumArgs() == 3);
420 // u.matchIgnoreAsciiCaseAsciiL("foo", 3, i) ->
421 // u.matchIgnoreAsciiCase("foo", i):
424 (isZero(expr
->getArg(2))
425 ? std::string("rtl::OUString::startsWithIgnoreAsciiCase")
426 : std::string("rtl::OUString::matchIgnoreAsciiCase")),
430 if ((dc
.Function("reverseCompareToAsciiL").Class("OUString")
431 .Namespace("rtl").GlobalNamespace())
432 && fdecl
->getNumParams() == 2)
434 // u.reverseCompareToAsciiL("foo", 3) -> u.reverseCompareTo("foo"):
436 expr
, 0, 1, fdecl
, "rtl::OUString::reverseCompareTo",
440 if ((dc
.Function("reverseCompareTo").Class("OUString").Namespace("rtl")
442 && fdecl
->getNumParams() == 1)
444 handleOUStringCtor(expr
, 0, fdecl
, false);
447 if ((dc
.Function("equalsIgnoreAsciiCase").Class("OUString").Namespace("rtl")
449 && fdecl
->getNumParams() == 1)
451 handleOUStringCtor(expr
, 0, fdecl
, false);
454 if ((dc
.Function("match").Class("OUString").Namespace("rtl")
456 && fdecl
->getNumParams() == 2)
458 handleOUStringCtor(expr
, 0, fdecl
, false);
461 if ((dc
.Function("matchIgnoreAsciiCase").Class("OUString").Namespace("rtl")
463 && fdecl
->getNumParams() == 2)
465 handleOUStringCtor(expr
, 0, fdecl
, false);
468 if ((dc
.Function("startsWith").Class("OUString").Namespace("rtl")
470 && fdecl
->getNumParams() == 2)
472 handleOUStringCtor(expr
, 0, fdecl
, false);
475 if ((dc
.Function("startsWithIgnoreAsciiCase").Class("OUString")
476 .Namespace("rtl").GlobalNamespace())
477 && fdecl
->getNumParams() == 2)
479 handleOUStringCtor(expr
, 0, fdecl
, false);
482 if ((dc
.Function("endsWith").Class("OUString").Namespace("rtl")
484 && fdecl
->getNumParams() == 2)
486 handleOUStringCtor(expr
, 0, fdecl
, false);
489 if ((dc
.Function("endsWithIgnoreAsciiCase").Class("OUString")
490 .Namespace("rtl").GlobalNamespace())
491 && fdecl
->getNumParams() == 2)
493 handleOUStringCtor(expr
, 0, fdecl
, false);
496 if ((dc
.Function("indexOf").Class("OUString").Namespace("rtl")
498 && fdecl
->getNumParams() == 2)
500 handleOUStringCtor(expr
, 0, fdecl
, false);
503 if ((dc
.Function("lastIndexOf").Class("OUString").Namespace("rtl")
505 && fdecl
->getNumParams() == 1)
507 handleOUStringCtor(expr
, 0, fdecl
, false);
510 if ((dc
.Function("replaceFirst").Class("OUString").Namespace("rtl")
512 && fdecl
->getNumParams() == 3)
514 handleOUStringCtor(expr
, 0, fdecl
, false);
515 handleOUStringCtor(expr
, 1, fdecl
, false);
518 if ((dc
.Function("replaceAll").Class("OUString").Namespace("rtl")
520 && (fdecl
->getNumParams() == 2 || fdecl
->getNumParams() == 3))
522 handleOUStringCtor(expr
, 0, fdecl
, false);
523 handleOUStringCtor(expr
, 1, fdecl
, false);
526 if ((dc
.Operator(OO_PlusEqual
).Class("OUString").Namespace("rtl")
528 && fdecl
->getNumParams() == 1)
531 expr
, dyn_cast
<CXXOperatorCallExpr
>(expr
) == nullptr ? 0 : 1,
535 if ((dc
.Function("equals").Class("OUString").Namespace("rtl")
537 && fdecl
->getNumParams() == 1)
544 if (!isStringConstant(
545 expr
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
550 if (cont
!= ContentKind::Ascii
) {
552 DiagnosticsEngine::Warning
,
553 ("call of '%0' with string constant argument containing"
554 " non-ASCII characters"),
556 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
560 DiagnosticsEngine::Warning
,
561 ("call of '%0' with string constant argument containing"
564 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
568 DiagnosticsEngine::Warning
,
569 ("rewrite call of '%0' with empty string constant argument as"
570 " call of 'rtl::OUString::isEmpty'"),
572 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
576 if (dc
.Operator(OO_EqualEqual
).Namespace("rtl").GlobalNamespace()
577 && fdecl
->getNumParams() == 2)
579 for (unsigned i
= 0; i
!= 2; ++i
) {
585 if (!isStringConstant(
586 expr
->getArg(i
)->IgnoreParenImpCasts(), &n
, &nonArray
,
591 if (cont
!= ContentKind::Ascii
) {
593 DiagnosticsEngine::Warning
,
594 ("call of '%0' with string constant argument containing"
595 " non-ASCII characters"),
597 << fdecl
->getQualifiedNameAsString()
598 << expr
->getSourceRange();
602 DiagnosticsEngine::Warning
,
603 ("call of '%0' with string constant argument containing"
606 << fdecl
->getQualifiedNameAsString()
607 << expr
->getSourceRange();
611 DiagnosticsEngine::Warning
,
612 ("rewrite call of '%0' with empty string constant argument"
613 " as call of 'rtl::OUString::isEmpty'"),
615 << fdecl
->getQualifiedNameAsString()
616 << expr
->getSourceRange();
621 if (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl").GlobalNamespace()
622 && fdecl
->getNumParams() == 2)
624 for (unsigned i
= 0; i
!= 2; ++i
) {
630 if (!isStringConstant(
631 expr
->getArg(i
)->IgnoreParenImpCasts(), &n
, &nonArray
,
636 if (cont
!= ContentKind::Ascii
) {
638 DiagnosticsEngine::Warning
,
639 ("call of '%0' with string constant argument containing"
640 " non-ASCII characters"),
642 << fdecl
->getQualifiedNameAsString()
643 << expr
->getSourceRange();
647 DiagnosticsEngine::Warning
,
648 ("call of '%0' with string constant argument containing"
651 << fdecl
->getQualifiedNameAsString()
652 << expr
->getSourceRange();
656 DiagnosticsEngine::Warning
,
657 ("rewrite call of '%0' with empty string constant argument"
658 " as call of '!rtl::OUString::isEmpty'"),
660 << fdecl
->getQualifiedNameAsString()
661 << expr
->getSourceRange();
666 if (dc
.Operator(OO_Equal
).Namespace("rtl").GlobalNamespace()
667 && fdecl
->getNumParams() == 1)
674 if (!isStringConstant(
675 expr
->getArg(1)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
680 if (cont
!= ContentKind::Ascii
) {
682 DiagnosticsEngine::Warning
,
683 ("call of '%0' with string constant argument containing"
684 " non-ASCII characters"),
686 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
690 DiagnosticsEngine::Warning
,
691 ("call of '%0' with string constant argument containing"
694 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
698 DiagnosticsEngine::Warning
,
699 ("rewrite call of '%0' with empty string constant argument as"
700 " call of 'rtl::OUString::clear'"),
702 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
707 if ((dc
.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
709 && fdecl
->getNumParams() == 1)
711 // u.appendAscii("foo") -> u.append("foo")
713 expr
, 0, fdecl
, "rtl::OUStringBuffer::append", TreatEmpty::Error
,
714 true, "appendAscii", "append");
717 if ((dc
.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
719 && fdecl
->getNumParams() == 2)
721 // u.appendAscii("foo", 3) -> u.append("foo"):
723 expr
, 0, 1, fdecl
, "rtl::OUStringBuffer::append",
727 if (dc
.Function("append").Class("OStringBuffer").Namespace("rtl")
730 switch (fdecl
->getNumParams()) {
732 handleFunArgOstring(expr
, 0, fdecl
);
736 // b.append("foo", 3) -> b.append("foo"):
737 auto file
= getFileNameOfSpellingLoc(
738 compiler
.getSourceManager().getSpellingLoc(
739 compat::getBeginLoc(expr
)));
740 if (loplugin::isSamePathname(
742 SRCDIR
"/sal/qa/OStringBuffer/rtl_OStringBuffer.cxx"))
747 expr
, 0, 1, fdecl
, "rtl::OStringBuffer::append",
756 if (dc
.Function("insert").Class("OStringBuffer").Namespace("rtl")
759 switch (fdecl
->getNumParams()) {
761 handleFunArgOstring(expr
, 1, fdecl
);
765 // b.insert(i, "foo", 3) -> b.insert(i, "foo"):
767 expr
, 1, 2, fdecl
, "rtl::OStringBuffer::insert",
779 bool StringConstant::VisitCXXConstructExpr(CXXConstructExpr
const * expr
) {
780 if (ignoreLocation(expr
)) {
783 auto classdecl
= expr
->getConstructor()->getParent();
784 if (loplugin::DeclCheck(classdecl
)
785 .Class("OUString").Namespace("rtl").GlobalNamespace())
790 switch (expr
->getConstructor()->getNumParams()) {
792 if (!loplugin::TypeCheck(
793 expr
->getConstructor()->getParamDecl(0)->getType())
794 .Typedef("sal_Unicode").GlobalNamespace())
798 kind
= ChangeKind::SingleChar
;
799 pass
= PassThrough::NonEmptyConstantString
;
804 auto arg
= expr
->getArg(0);
805 if (loplugin::TypeCheck(arg
->getType())
806 .Class("OUStringLiteral1_").Namespace("rtl")
809 kind
= ChangeKind::OUStringLiteral1
;
810 pass
= PassThrough::NonEmptyConstantString
;
818 if (!isStringConstant(
819 arg
->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
824 // OSL_THIS_FUNC may be defined as "" or as something other
825 // than a string literal in include/osl/diagnose.h:
826 auto loc
= compat::getBeginLoc(arg
);
827 if (compiler
.getSourceManager().isMacroBodyExpansion(loc
)
828 && (Lexer::getImmediateMacroName(
829 loc
, compiler
.getSourceManager(),
830 compiler
.getLangOpts())
835 if (cont
!= ContentKind::Ascii
) {
837 DiagnosticsEngine::Warning
,
838 ("construction of %0 with string constant argument"
839 " containing non-ASCII characters"),
841 << classdecl
<< expr
->getSourceRange();
845 DiagnosticsEngine::Warning
,
846 ("construction of %0 with string constant argument"
847 " containing embedded NULLs"),
849 << classdecl
<< expr
->getSourceRange();
851 kind
= ChangeKind::Char
;
853 ? PassThrough::EmptyConstantString
854 : PassThrough::NonEmptyConstantString
;
866 std::vector
<char32_t
> utf8Cont
;
867 if (!isStringConstant(
868 expr
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
,
869 &cont
, &emb
, &trm
, &utf8Cont
))
874 if (!compat::EvaluateAsInt(expr
->getArg(1),
875 res
, compiler
.getASTContext()))
881 DiagnosticsEngine::Warning
,
882 ("suspicious 'rtl::OUString' constructor with literal"
883 " of length %0 and non-matching length argument %1"),
885 << n
<< res
.toString(10) << expr
->getSourceRange();
889 if (!compat::EvaluateAsInt(expr
->getArg(2),
890 enc
, compiler
.getASTContext()))
894 auto const encIsAscii
= enc
== 11; // RTL_TEXTENCODING_ASCII_US
895 auto const encIsUtf8
= enc
== 76; // RTL_TEXTENCODING_UTF8
896 if (!compat::EvaluateAsInt(expr
->getArg(3),
897 res
, compiler
.getASTContext())
898 || res
!= 0x333) // OSTRING_TO_OUSTRING_CVTFLAGS
902 if (!encIsAscii
&& cont
== ContentKind::Ascii
) {
904 DiagnosticsEngine::Warning
,
905 ("suspicious 'rtl::OUString' constructor with text"
906 " encoding %0 but plain ASCII content; use"
907 " 'RTL_TEXTENCODING_ASCII_US' instead"),
908 expr
->getArg(2)->getExprLoc())
909 << enc
.toString(10) << expr
->getSourceRange();
913 if (cont
== ContentKind::Arbitrary
) {
915 DiagnosticsEngine::Warning
,
916 ("suspicious 'rtl::OUString' constructor with text"
917 " encoding 'RTL_TEXTENCODING_UTF8' but non-UTF-8"
919 expr
->getArg(0)->getExprLoc())
920 << expr
->getSourceRange();
922 assert(cont
== ContentKind::Utf8
);
923 //TODO: keep original content as much as possible
924 std::ostringstream s
;
925 for (auto const c
: utf8Cont
) {
928 } else if (c
== '"') {
930 } else if (c
== '\a') {
932 } else if (c
== '\b') {
934 } else if (c
== '\f') {
936 } else if (c
== '\n') {
938 } else if (c
== '\r') {
940 } else if (c
== '\t') {
942 } else if (c
== '\v') {
944 } else if (c
<= 0x1F || c
== 0x7F) {
945 s
<< "\\x" << std::oct
<< std::setw(3)
947 << static_cast<std::uint_least32_t>(c
);
948 } else if (c
< 0x7F) {
950 } else if (c
<= 0xFFFF) {
951 s
<< "\\u" << std::hex
<< std::uppercase
952 << std::setw(4) << std::setfill('0')
953 << static_cast<std::uint_least32_t>(c
);
955 assert(c
<= 0x10FFFF);
956 s
<< "\\U" << std::hex
<< std::uppercase
957 << std::setw(8) << std::setfill('0')
958 << static_cast<std::uint_least32_t>(c
);
962 DiagnosticsEngine::Warning
,
963 ("simplify construction of %0 with UTF-8 content as"
964 " OUString(u\"%1\")"),
966 << classdecl
<< s
.str() << expr
->getSourceRange();
971 if (cont
!= ContentKind::Ascii
|| emb
) {
972 // cf. remaining uses of RTL_CONSTASCII_USTRINGPARAM
975 kind
= ChangeKind::Char
;
977 ? PassThrough::EmptyConstantString
978 : PassThrough::NonEmptyConstantString
;
985 if (!calls_
.empty()) {
986 Expr
const * call
= calls_
.top();
987 CallExpr::const_arg_iterator argsBeg
;
988 CallExpr::const_arg_iterator argsEnd
;
989 if (isa
<CallExpr
>(call
)) {
990 argsBeg
= cast
<CallExpr
>(call
)->arg_begin();
991 argsEnd
= cast
<CallExpr
>(call
)->arg_end();
992 } else if (isa
<CXXConstructExpr
>(call
)) {
993 argsBeg
= cast
<CXXConstructExpr
>(call
)->arg_begin();
994 argsEnd
= cast
<CXXConstructExpr
>(call
)->arg_end();
998 for (auto i(argsBeg
); i
!= argsEnd
; ++i
) {
999 Expr
const * e
= (*i
)->IgnoreParenImpCasts();
1000 if (isa
<MaterializeTemporaryExpr
>(e
)) {
1001 e
= cast
<MaterializeTemporaryExpr
>(e
)->GetTemporaryExpr()
1002 ->IgnoreParenImpCasts();
1004 if (isa
<CXXFunctionalCastExpr
>(e
)) {
1005 e
= cast
<CXXFunctionalCastExpr
>(e
)->getSubExpr()
1006 ->IgnoreParenImpCasts();
1008 if (isa
<CXXBindTemporaryExpr
>(e
)) {
1009 e
= cast
<CXXBindTemporaryExpr
>(e
)->getSubExpr()
1010 ->IgnoreParenImpCasts();
1013 if (isa
<CallExpr
>(call
)) {
1014 FunctionDecl
const * fdecl
1015 = cast
<CallExpr
>(call
)->getDirectCallee();
1016 if (fdecl
== nullptr) {
1019 loplugin::DeclCheck
dc(fdecl
);
1020 if (pass
== PassThrough::EmptyConstantString
) {
1021 if ((dc
.Function("equals").Class("OUString")
1022 .Namespace("rtl").GlobalNamespace())
1023 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1024 .GlobalNamespace()))
1027 DiagnosticsEngine::Warning
,
1028 ("rewrite call of '%0' with construction of"
1029 " %1 with empty string constant argument"
1030 " as call of 'rtl::OUString::isEmpty'"),
1031 getMemberLocation(call
))
1032 << fdecl
->getQualifiedNameAsString()
1033 << classdecl
<< call
->getSourceRange();
1036 if (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl")
1040 DiagnosticsEngine::Warning
,
1041 ("rewrite call of '%0' with construction of"
1042 " %1 with empty string constant argument"
1043 " as call of '!rtl::OUString::isEmpty'"),
1044 getMemberLocation(call
))
1045 << fdecl
->getQualifiedNameAsString()
1046 << classdecl
<< call
->getSourceRange();
1049 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
1051 || (dc
.Operator(OO_Plus
).Class("OUString")
1052 .Namespace("rtl").GlobalNamespace()))
1055 DiagnosticsEngine::Warning
,
1056 ("call of '%0' with suspicious construction"
1057 " of %1 with empty string constant"
1059 getMemberLocation(call
))
1060 << fdecl
->getQualifiedNameAsString()
1061 << classdecl
<< call
->getSourceRange();
1064 if (dc
.Operator(OO_Equal
).Class("OUString")
1065 .Namespace("rtl").GlobalNamespace())
1068 DiagnosticsEngine::Warning
,
1069 ("rewrite call of '%0' with construction of"
1070 " %1 with empty string constant argument"
1071 " as call of 'rtl::OUString::clear'"),
1072 getMemberLocation(call
))
1073 << fdecl
->getQualifiedNameAsString()
1074 << classdecl
<< call
->getSourceRange();
1078 assert(pass
== PassThrough::NonEmptyConstantString
);
1079 if (dc
.Function("equals").Class("OUString")
1080 .Namespace("rtl").GlobalNamespace())
1083 DiagnosticsEngine::Warning
,
1084 ("rewrite call of '%0' with construction of"
1085 " %1 with %2 as 'operator =='"),
1086 getMemberLocation(call
))
1087 << fdecl
->getQualifiedNameAsString()
1088 << classdecl
<< describeChangeKind(kind
)
1089 << call
->getSourceRange();
1092 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
1094 || (dc
.Operator(OO_Plus
).Class("OUString")
1095 .Namespace("rtl").GlobalNamespace())
1096 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1098 || (dc
.Operator(OO_ExclaimEqual
)
1099 .Namespace("rtl").GlobalNamespace()))
1101 if (dc
.Operator(OO_Plus
).Namespace("rtl")
1104 auto file
= getFileNameOfSpellingLoc(
1105 compiler
.getSourceManager()
1107 compat::getBeginLoc(expr
)));
1108 if (loplugin::isSamePathname(
1111 "/sal/qa/rtl/strings/test_ostring_concat.cxx"))
1112 || loplugin::isSamePathname(
1115 "/sal/qa/rtl/strings/test_oustring_concat.cxx")))
1120 auto loc
= compat::getBeginLoc(expr
->getArg(0));
1121 while (compiler
.getSourceManager()
1122 .isMacroArgExpansion(loc
))
1124 loc
= compiler
.getSourceManager()
1125 .getImmediateMacroCallerLoc(loc
);
1127 if ((compiler
.getSourceManager()
1128 .isMacroBodyExpansion(loc
))
1129 && (Lexer::getImmediateMacroName(
1130 loc
, compiler
.getSourceManager(),
1131 compiler
.getLangOpts())
1132 == "OSL_THIS_FUNC"))
1136 if (kind
== ChangeKind::SingleChar
) {
1138 DiagnosticsEngine::Warning
,
1139 ("rewrite construction of %0 with %1 in"
1140 " call of '%2' as construction of"
1141 " 'OUStringLiteral1'"),
1142 getMemberLocation(expr
))
1143 << classdecl
<< describeChangeKind(kind
)
1144 << fdecl
->getQualifiedNameAsString()
1145 << expr
->getSourceRange();
1148 DiagnosticsEngine::Warning
,
1149 ("elide construction of %0 with %1 in"
1151 getMemberLocation(expr
))
1152 << classdecl
<< describeChangeKind(kind
)
1153 << fdecl
->getQualifiedNameAsString()
1154 << expr
->getSourceRange();
1159 } else if (isa
<CXXConstructExpr
>(call
)) {
1168 DiagnosticsEngine::Warning
,
1169 "simplify construction of %0 with %1", expr
->getExprLoc())
1170 << classdecl
<< describeChangeKind(kind
)
1171 << expr
->getSourceRange();
1176 auto consDecl
= expr
->getConstructor();
1177 for (unsigned i
= 0; i
!= consDecl
->getNumParams(); ++i
) {
1178 auto t
= consDecl
->getParamDecl(i
)->getType();
1179 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
1180 .LvalueReference().Const().NotSubstTemplateTypeParmType()
1181 .Class("OUString").Namespace("rtl").GlobalNamespace())
1183 auto argExpr
= expr
->getArg(i
);
1184 if (argExpr
&& i
<= consDecl
->getNumParams())
1186 if (!hasOverloads(consDecl
, expr
->getNumArgs()))
1188 handleOUStringCtor(expr
, argExpr
, consDecl
, true);
1192 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
1193 .LvalueReference().Const().NotSubstTemplateTypeParmType()
1194 .Class("OString").Namespace("rtl").GlobalNamespace())
1196 auto argExpr
= expr
->getArg(i
);
1197 if (argExpr
&& i
<= consDecl
->getNumParams())
1199 if (!hasOverloads(consDecl
, expr
->getNumArgs()))
1201 handleOStringCtor(expr
, argExpr
, consDecl
, true);
1210 std::string
StringConstant::describeChangeKind(ChangeKind kind
) {
1212 case ChangeKind::Char
:
1213 return "string constant argument";
1214 case ChangeKind::CharLen
:
1215 return "string constant and matching length arguments";
1216 case ChangeKind::SingleChar
:
1217 return "sal_Unicode argument";
1218 case ChangeKind::OUStringLiteral1
:
1219 return "OUStringLiteral1 argument";
1225 bool StringConstant::isStringConstant(
1226 Expr
const * expr
, unsigned * size
, bool * nonArray
, ContentKind
* content
,
1227 bool * embeddedNuls
, bool * terminatingNul
,
1228 std::vector
<char32_t
> * utf8Content
)
1230 assert(expr
!= nullptr);
1231 assert(size
!= nullptr);
1232 assert(nonArray
!= nullptr);
1233 assert(content
!= nullptr);
1234 assert(embeddedNuls
!= nullptr);
1235 assert(terminatingNul
!= nullptr);
1236 QualType t
= expr
->getType();
1237 // Look inside RTL_CONSTASCII_STRINGPARAM:
1238 if (loplugin::TypeCheck(t
).Pointer().Const().Char()) {
1239 auto e2
= dyn_cast
<UnaryOperator
>(expr
);
1240 if (e2
!= nullptr && e2
->getOpcode() == UO_AddrOf
) {
1241 auto e3
= dyn_cast
<ArraySubscriptExpr
>(
1242 e2
->getSubExpr()->IgnoreParenImpCasts());
1243 if (e3
== nullptr || !isZero(e3
->getIdx()->IgnoreParenImpCasts())) {
1246 expr
= e3
->getBase()->IgnoreParenImpCasts();
1247 t
= expr
->getType();
1250 if (!t
.isConstQualified()) {
1253 DeclRefExpr
const * dre
= dyn_cast
<DeclRefExpr
>(expr
);
1254 if (dre
!= nullptr) {
1255 VarDecl
const * var
= dyn_cast
<VarDecl
>(dre
->getDecl());
1256 if (var
!= nullptr) {
1257 Expr
const * init
= var
->getAnyInitializer();
1258 if (init
!= nullptr) {
1259 expr
= init
->IgnoreParenImpCasts();
1264 if (loplugin::TypeCheck(t
).Pointer().Const().Char()) {
1266 } else if (t
->isConstantArrayType()
1267 && (loplugin::TypeCheck(
1268 t
->getAsArrayTypeUnsafe()->getElementType())
1275 clang::StringLiteral
const * lit
= dyn_cast
<clang::StringLiteral
>(expr
);
1276 if (lit
!= nullptr) {
1277 if (!(lit
->isAscii() || lit
->isUTF8())) {
1280 unsigned n
= lit
->getLength();
1281 ContentKind cont
= ContentKind::Ascii
;
1284 enum class Utf8State
{ Start
, E0
, EB
, F0
, F4
, Trail1
, Trail2
, Trail3
};
1285 Utf8State s
= Utf8State::Start
;
1286 StringRef str
= lit
->getString();
1287 for (unsigned i
= 0; i
!= n
; ++i
) {
1288 auto const c
= static_cast<unsigned char>(str
[i
]);
1293 case Utf8State::Start
:
1295 if (c
>= 0xC2 && c
<= 0xDF) {
1297 s
= Utf8State::Trail1
;
1298 } else if (c
== 0xE0) {
1301 } else if ((c
>= 0xE1 && c
<= 0xEA)
1302 || (c
>= 0xEE && c
<= 0xEF))
1305 s
= Utf8State::Trail2
;
1306 } else if (c
== 0xEB) {
1309 } else if (c
== 0xF0) {
1312 } else if (c
>= 0xF1 && c
<= 0xF3) {
1314 s
= Utf8State::Trail3
;
1315 } else if (c
== 0xF4) {
1319 cont
= ContentKind::Arbitrary
;
1321 } else if (utf8Content
!= nullptr
1322 && cont
!= ContentKind::Arbitrary
)
1324 utf8Content
->push_back(c
);
1328 if (c
>= 0xA0 && c
<= 0xBF) {
1329 val
= (val
<< 6) | (c
& 0x3F);
1330 s
= Utf8State::Trail1
;
1332 cont
= ContentKind::Arbitrary
;
1333 s
= Utf8State::Start
;
1337 if (c
>= 0x80 && c
<= 0x9F) {
1338 val
= (val
<< 6) | (c
& 0x3F);
1339 s
= Utf8State::Trail1
;
1341 cont
= ContentKind::Arbitrary
;
1342 s
= Utf8State::Start
;
1346 if (c
>= 0x90 && c
<= 0xBF) {
1347 val
= (val
<< 6) | (c
& 0x3F);
1348 s
= Utf8State::Trail2
;
1350 cont
= ContentKind::Arbitrary
;
1351 s
= Utf8State::Start
;
1355 if (c
>= 0x80 && c
<= 0x8F) {
1356 val
= (val
<< 6) | (c
& 0x3F);
1357 s
= Utf8State::Trail2
;
1359 cont
= ContentKind::Arbitrary
;
1360 s
= Utf8State::Start
;
1363 case Utf8State::Trail1
:
1364 if (c
>= 0x80 && c
<= 0xBF) {
1365 cont
= ContentKind::Utf8
;
1366 if (utf8Content
!= nullptr)
1368 utf8Content
->push_back((val
<< 6) | (c
& 0x3F));
1372 cont
= ContentKind::Arbitrary
;
1374 s
= Utf8State::Start
;
1376 case Utf8State::Trail2
:
1377 if (c
>= 0x80 && c
<= 0xBF) {
1378 val
= (val
<< 6) | (c
& 0x3F);
1379 s
= Utf8State::Trail1
;
1381 cont
= ContentKind::Arbitrary
;
1382 s
= Utf8State::Start
;
1385 case Utf8State::Trail3
:
1386 if (c
>= 0x80 && c
<= 0xBF) {
1387 val
= (val
<< 6) | (c
& 0x3F);
1388 s
= Utf8State::Trail2
;
1390 cont
= ContentKind::Arbitrary
;
1391 s
= Utf8State::Start
;
1399 *embeddedNuls
= emb
;
1400 *terminatingNul
= true;
1404 if (!expr
->isCXX11ConstantExpr(compiler
.getASTContext(), &v
)) {
1407 switch (v
.getKind()) {
1408 case APValue::LValue
:
1410 Expr
const * e
= v
.getLValueBase().dyn_cast
<Expr
const *>();
1414 if (!v
.getLValueOffset().isZero()) {
1415 return false; //TODO
1417 Expr
const * e2
= e
->IgnoreParenImpCasts();
1419 return isStringConstant(
1420 e2
, size
, nonArray
, content
, embeddedNuls
, terminatingNul
);
1422 //TODO: string literals are represented as recursive LValues???
1424 = compiler
.getASTContext().getAsConstantArrayType(t
)->getSize();
1427 assert(n
.ule(std::numeric_limits
<unsigned>::max()));
1428 *size
= static_cast<unsigned>(n
.getLimitedValue());
1429 *nonArray
= isPtr
|| *nonArray
;
1430 *content
= ContentKind::Ascii
; //TODO
1431 *embeddedNuls
= false; //TODO
1432 *terminatingNul
= true;
1435 case APValue::Array
:
1437 if (v
.hasArrayFiller()) { //TODO: handle final NULL filler?
1440 unsigned n
= v
.getArraySize();
1442 ContentKind cont
= ContentKind::Ascii
;
1444 //TODO: check for ContentType::Utf8
1445 for (unsigned i
= 0; i
!= n
- 1; ++i
) {
1446 APValue
e(v
.getArrayInitializedElt(i
));
1447 if (!e
.isInt()) { //TODO: assert?
1450 APSInt iv
= e
.getInt();
1453 } else if (iv
.uge(0x80)) {
1454 cont
= ContentKind::Arbitrary
;
1457 APValue
e(v
.getArrayInitializedElt(n
- 1));
1458 if (!e
.isInt()) { //TODO: assert?
1461 bool trm
= e
.getInt() == 0;
1462 *size
= trm
? n
- 1 : n
;
1465 *embeddedNuls
= emb
;
1466 *terminatingNul
= trm
;
1470 assert(false); //TODO???
1475 bool StringConstant::isZero(Expr
const * expr
) {
1477 return compat::EvaluateAsInt(expr
, res
, compiler
.getASTContext()) && res
== 0;
1480 void StringConstant::reportChange(
1481 Expr
const * expr
, ChangeKind kind
, std::string
const & original
,
1482 std::string
const & replacement
, PassThrough pass
, bool nonArray
,
1483 char const * rewriteFrom
, char const * rewriteTo
)
1485 assert((rewriteFrom
== nullptr) == (rewriteTo
== nullptr));
1486 if (pass
!= PassThrough::No
&& !calls_
.empty()) {
1487 Expr
const * call
= calls_
.top();
1488 CallExpr::const_arg_iterator argsBeg
;
1489 CallExpr::const_arg_iterator argsEnd
;
1490 if (isa
<CallExpr
>(call
)) {
1491 argsBeg
= cast
<CallExpr
>(call
)->arg_begin();
1492 argsEnd
= cast
<CallExpr
>(call
)->arg_end();
1493 } else if (isa
<CXXConstructExpr
>(call
)) {
1494 argsBeg
= cast
<CXXConstructExpr
>(call
)->arg_begin();
1495 argsEnd
= cast
<CXXConstructExpr
>(call
)->arg_end();
1499 for (auto i(argsBeg
); i
!= argsEnd
; ++i
) {
1500 Expr
const * e
= (*i
)->IgnoreParenImpCasts();
1501 if (isa
<CXXBindTemporaryExpr
>(e
)) {
1502 e
= cast
<CXXBindTemporaryExpr
>(e
)->getSubExpr()
1503 ->IgnoreParenImpCasts();
1506 if (isa
<CallExpr
>(call
)) {
1507 FunctionDecl
const * fdecl
1508 = cast
<CallExpr
>(call
)->getDirectCallee();
1509 if (fdecl
== nullptr) {
1512 loplugin::DeclCheck
dc(fdecl
);
1513 if (pass
== PassThrough::EmptyConstantString
) {
1514 if ((dc
.Function("equals").Class("OUString")
1515 .Namespace("rtl").GlobalNamespace())
1516 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1517 .GlobalNamespace()))
1520 DiagnosticsEngine::Warning
,
1521 ("rewrite call of '%0' with call of %1 with"
1522 " empty string constant argument as call of"
1523 " 'rtl::OUString::isEmpty'"),
1524 getMemberLocation(call
))
1525 << fdecl
->getQualifiedNameAsString() << original
1526 << call
->getSourceRange();
1529 if (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl")
1533 DiagnosticsEngine::Warning
,
1534 ("rewrite call of '%0' with call of %1 with"
1535 " empty string constant argument as call of"
1536 " '!rtl::OUString::isEmpty'"),
1537 getMemberLocation(call
))
1538 << fdecl
->getQualifiedNameAsString() << original
1539 << call
->getSourceRange();
1542 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
1544 || (dc
.Operator(OO_Plus
).Class("OUString")
1545 .Namespace("rtl").GlobalNamespace()))
1548 DiagnosticsEngine::Warning
,
1549 ("call of '%0' with suspicious call of %1 with"
1550 " empty string constant argument"),
1551 getMemberLocation(call
))
1552 << fdecl
->getQualifiedNameAsString() << original
1553 << call
->getSourceRange();
1556 if (dc
.Operator(OO_Equal
).Class("OUString")
1557 .Namespace("rtl").GlobalNamespace())
1560 DiagnosticsEngine::Warning
,
1561 ("rewrite call of '%0' with call of %1 with"
1562 " empty string constant argument as call of"
1563 " rtl::OUString::call"),
1564 getMemberLocation(call
))
1565 << fdecl
->getQualifiedNameAsString() << original
1566 << call
->getSourceRange();
1570 DiagnosticsEngine::Warning
,
1571 "TODO call inside %0", getMemberLocation(expr
))
1572 << fdecl
->getQualifiedNameAsString()
1573 << expr
->getSourceRange();
1576 assert(pass
== PassThrough::NonEmptyConstantString
);
1577 if ((dc
.Function("equals").Class("OUString")
1578 .Namespace("rtl").GlobalNamespace())
1579 || (dc
.Operator(OO_Equal
).Class("OUString")
1580 .Namespace("rtl").GlobalNamespace())
1581 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1583 || (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl")
1584 .GlobalNamespace()))
1587 DiagnosticsEngine::Warning
,
1588 "elide call of %0 with %1 in call of '%2'",
1589 getMemberLocation(expr
))
1590 << original
<< describeChangeKind(kind
)
1591 << fdecl
->getQualifiedNameAsString()
1592 << expr
->getSourceRange();
1596 DiagnosticsEngine::Warning
,
1597 ("rewrite call of %0 with %1 in call of '%2' as"
1598 " (implicit) construction of 'OUString'"),
1599 getMemberLocation(expr
))
1600 << original
<< describeChangeKind(kind
)
1601 << fdecl
->getQualifiedNameAsString()
1602 << expr
->getSourceRange();
1605 } else if (isa
<CXXConstructExpr
>(call
)) {
1606 auto classdecl
= cast
<CXXConstructExpr
>(call
)
1607 ->getConstructor()->getParent();
1608 loplugin::DeclCheck
dc(classdecl
);
1609 if (dc
.Class("OUString").Namespace("rtl").GlobalNamespace()
1610 || (dc
.Class("OUStringBuffer").Namespace("rtl")
1611 .GlobalNamespace()))
1613 //TODO: propagate further out?
1614 if (pass
== PassThrough::EmptyConstantString
) {
1616 DiagnosticsEngine::Warning
,
1617 ("rewrite construction of %0 with call of %1"
1618 " with empty string constant argument as"
1619 " default construction of %0"),
1620 getMemberLocation(call
))
1621 << classdecl
<< original
1622 << call
->getSourceRange();
1624 assert(pass
== PassThrough::NonEmptyConstantString
);
1626 DiagnosticsEngine::Warning
,
1627 ("elide call of %0 with %1 in construction of"
1629 getMemberLocation(expr
))
1630 << original
<< describeChangeKind(kind
)
1631 << classdecl
<< expr
->getSourceRange();
1641 if (rewriter
!= nullptr && !nonArray
&& rewriteFrom
!= nullptr) {
1642 SourceLocation loc
= getMemberLocation(expr
);
1643 while (compiler
.getSourceManager().isMacroArgExpansion(loc
)) {
1644 loc
= compiler
.getSourceManager().getImmediateMacroCallerLoc(loc
);
1646 if (compiler
.getSourceManager().isMacroBodyExpansion(loc
)) {
1647 loc
= compiler
.getSourceManager().getSpellingLoc(loc
);
1649 unsigned n
= Lexer::MeasureTokenLength(
1650 loc
, compiler
.getSourceManager(), compiler
.getLangOpts());
1651 if ((std::string(compiler
.getSourceManager().getCharacterData(loc
), n
)
1653 && replaceText(loc
, n
, rewriteTo
))
1659 DiagnosticsEngine::Warning
,
1660 "rewrite call of '%0' with %1 as call of '%2'%3",
1661 getMemberLocation(expr
))
1662 << original
<< describeChangeKind(kind
) << replacement
1663 << adviseNonArray(nonArray
) << expr
->getSourceRange();
1666 void StringConstant::checkEmpty(
1667 CallExpr
const * expr
, FunctionDecl
const * callee
, TreatEmpty treatEmpty
,
1668 unsigned size
, std::string
* replacement
)
1670 assert(replacement
!= nullptr);
1672 switch (treatEmpty
) {
1673 case TreatEmpty::DefaultCtor
:
1674 *replacement
= "rtl::OUString default constructor";
1676 case TreatEmpty::CheckEmpty
:
1677 *replacement
= "rtl::OUString::isEmpty";
1679 case TreatEmpty::Error
:
1681 DiagnosticsEngine::Warning
,
1682 "call of '%0' with suspicious empty string constant argument",
1683 getMemberLocation(expr
))
1684 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1690 void StringConstant::handleChar(
1691 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
1692 std::string
const & replacement
, TreatEmpty treatEmpty
, bool literal
,
1693 char const * rewriteFrom
, char const * rewriteTo
)
1700 if (!isStringConstant(
1701 expr
->getArg(arg
)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
1706 if (cont
!= ContentKind::Ascii
) {
1708 DiagnosticsEngine::Warning
,
1709 ("call of '%0' with string constant argument containing non-ASCII"
1711 getMemberLocation(expr
))
1712 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1717 DiagnosticsEngine::Warning
,
1718 ("call of '%0' with string constant argument containing embedded"
1720 getMemberLocation(expr
))
1721 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1726 DiagnosticsEngine::Warning
,
1727 ("call of '%0' with string constant argument lacking a terminating"
1729 getMemberLocation(expr
))
1730 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1733 std::string
repl(replacement
);
1734 checkEmpty(expr
, callee
, treatEmpty
, n
, &repl
);
1736 expr
, ChangeKind::Char
, callee
->getQualifiedNameAsString(), repl
,
1739 ? PassThrough::EmptyConstantString
1740 : PassThrough::NonEmptyConstantString
)
1742 nonArray
, rewriteFrom
, rewriteTo
);
1745 void StringConstant::handleCharLen(
1746 CallExpr
const * expr
, unsigned arg1
, unsigned arg2
,
1747 FunctionDecl
const * callee
, std::string
const & replacement
,
1748 TreatEmpty treatEmpty
)
1750 // Especially for f(RTL_CONSTASCII_STRINGPARAM("foo")), where
1751 // RTL_CONSTASCII_STRINGPARAM expands to complicated expressions involving
1752 // (&(X)[0] sub-expressions (and it might or might not be better to handle
1753 // that at the level of non-expanded macros instead, but I have not found
1754 // out how to do that yet anyway):
1760 if (!(isStringConstant(
1761 expr
->getArg(arg1
)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
,
1768 if (compat::EvaluateAsInt(expr
->getArg(arg2
), res
, compiler
.getASTContext())) {
1773 UnaryOperator
const * op
= dyn_cast
<UnaryOperator
>(
1774 expr
->getArg(arg1
)->IgnoreParenImpCasts());
1775 if (op
== nullptr || op
->getOpcode() != UO_AddrOf
) {
1778 ArraySubscriptExpr
const * subs
= dyn_cast
<ArraySubscriptExpr
>(
1779 op
->getSubExpr()->IgnoreParenImpCasts());
1780 if (subs
== nullptr) {
1788 if (!(isStringConstant(
1789 subs
->getBase()->IgnoreParenImpCasts(), &n2
, &nonArray2
,
1790 &cont2
, &emb2
, &trm2
)
1791 && n2
== n
&& cont2
== cont
&& emb2
== emb
&& trm2
== trm
1792 //TODO: same strings
1793 && compat::EvaluateAsInt(subs
->getIdx(), res
, compiler
.getASTContext())
1799 if (cont
!= ContentKind::Ascii
) {
1801 DiagnosticsEngine::Warning
,
1802 ("call of '%0' with string constant argument containing non-ASCII"
1804 getMemberLocation(expr
))
1805 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1810 std::string
repl(replacement
);
1811 checkEmpty(expr
, callee
, treatEmpty
, n
, &repl
);
1813 expr
, ChangeKind::CharLen
, callee
->getQualifiedNameAsString(), repl
,
1814 PassThrough::No
, nonArray
, nullptr, nullptr);
1817 void StringConstant::handleOUStringCtor(
1818 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
1819 bool explicitFunctionalCastNotation
)
1821 handleOUStringCtor(expr
, expr
->getArg(arg
), callee
, explicitFunctionalCastNotation
);
1824 void StringConstant::handleOStringCtor(
1825 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
1826 bool explicitFunctionalCastNotation
)
1828 handleOStringCtor(expr
, expr
->getArg(arg
), callee
, explicitFunctionalCastNotation
);
1831 void StringConstant::handleOUStringCtor(
1832 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
1833 bool explicitFunctionalCastNotation
)
1835 handleStringCtor(expr
, argExpr
, callee
, explicitFunctionalCastNotation
, StringKind::Unicode
);
1838 void StringConstant::handleOStringCtor(
1839 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
1840 bool explicitFunctionalCastNotation
)
1842 handleStringCtor(expr
, argExpr
, callee
, explicitFunctionalCastNotation
, StringKind::Char
);
1845 void StringConstant::handleStringCtor(
1846 Expr
const * expr
, Expr
const * argExpr
, FunctionDecl
const * callee
,
1847 bool explicitFunctionalCastNotation
, StringKind stringKind
)
1849 auto e0
= argExpr
->IgnoreParenImpCasts();
1850 auto e1
= dyn_cast
<CXXFunctionalCastExpr
>(e0
);
1851 if (e1
== nullptr) {
1852 if (explicitFunctionalCastNotation
) {
1856 e0
= e1
->getSubExpr()->IgnoreParenImpCasts();
1858 auto e2
= dyn_cast
<CXXBindTemporaryExpr
>(e0
);
1859 if (e2
== nullptr) {
1862 auto e3
= dyn_cast
<CXXConstructExpr
>(
1863 e2
->getSubExpr()->IgnoreParenImpCasts());
1864 if (e3
== nullptr) {
1867 if (!loplugin::DeclCheck(e3
->getConstructor()).MemberFunction()
1868 .Class(stringKind
== StringKind::Unicode
? "OUString" : "OString").Namespace("rtl").GlobalNamespace())
1872 if (e3
->getNumArgs() == 0) {
1874 DiagnosticsEngine::Warning
,
1875 ("in call of '%0', replace default-constructed 'OUString' with an"
1876 " empty string literal"),
1878 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1881 if (e3
->getNumArgs() == 1
1882 && e3
->getConstructor()->getNumParams() == 1
1883 && (loplugin::TypeCheck(
1884 e3
->getConstructor()->getParamDecl(0)->getType())
1885 .Typedef(stringKind
== StringKind::Unicode
? "sal_Unicode" : "char").GlobalNamespace()))
1887 // It may not be easy to rewrite OUString(c), esp. given there is no
1888 // OUString ctor taking an OUStringLiteral1 arg, so don't warn there:
1889 if (!explicitFunctionalCastNotation
) {
1891 DiagnosticsEngine::Warning
,
1892 ("in call of '%0', replace 'OUString' constructed from a"
1893 " 'sal_Unicode' with an 'OUStringLiteral1'"),
1895 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1899 if (e3
->getNumArgs() != 2) {
1907 if (!isStringConstant(
1908 e3
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
, &cont
, &emb
,
1913 //TODO: cont, emb, trm
1914 if (rewriter
!= nullptr) {
1915 auto loc1
= compat::getBeginLoc(e3
);
1916 auto range
= e3
->getParenOrBraceRange();
1917 if (loc1
.isFileID() && range
.getBegin().isFileID()
1918 && range
.getEnd().isFileID())
1920 auto loc2
= range
.getBegin();
1921 for (bool first
= true;; first
= false) {
1922 unsigned n
= Lexer::MeasureTokenLength(
1923 loc2
, compiler
.getSourceManager(), compiler
.getLangOpts());
1926 compiler
.getSourceManager().getCharacterData(loc2
), n
);
1927 while (s
.startswith("\\\n")) {
1928 s
= s
.drop_front(2);
1930 && (s
.front() == ' ' || s
.front() == '\t'
1931 || s
.front() == '\n' || s
.front() == '\v'
1932 || s
.front() == '\f'))
1934 s
= s
.drop_front(1);
1937 if (!(s
.empty() || s
.startswith("/*") || s
.startswith("//")
1943 loc2
= loc2
.getLocWithOffset(std::max
<unsigned>(n
, 1));
1945 auto loc3
= range
.getEnd();
1947 auto l
= Lexer::GetBeginningOfToken(
1948 loc3
.getLocWithOffset(-1), compiler
.getSourceManager(),
1949 compiler
.getLangOpts());
1950 unsigned n
= Lexer::MeasureTokenLength(
1951 l
, compiler
.getSourceManager(), compiler
.getLangOpts());
1952 StringRef
s(compiler
.getSourceManager().getCharacterData(l
), n
);
1953 while (s
.startswith("\\\n")) {
1954 s
= s
.drop_front(2);
1956 && (s
.front() == ' ' || s
.front() == '\t'
1957 || s
.front() == '\n' || s
.front() == '\v'
1958 || s
.front() == '\f'))
1960 s
= s
.drop_front(1);
1963 if (!(s
.empty() || s
.startswith("/*") || s
.startswith("//")
1970 if (removeText(CharSourceRange(SourceRange(loc1
, loc2
), false))) {
1971 if (removeText(SourceRange(loc3
, range
.getEnd()))) {
1974 report(DiagnosticsEngine::Fatal
, "Corrupt rewrite", loc3
)
1975 << expr
->getSourceRange();
1981 DiagnosticsEngine::Warning
,
1982 ("in call of '%0', replace 'OUString' constructed from a string literal"
1983 " directly with the string literal"),
1985 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1988 void StringConstant::handleFunArgOstring(
1989 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
)
1991 auto argExpr
= expr
->getArg(arg
)->IgnoreParenImpCasts();
1997 if (isStringConstant(argExpr
, &n
, &nonArray
, &cont
, &emb
, &trm
)) {
1998 if (cont
!= ContentKind::Ascii
|| emb
) {
2003 DiagnosticsEngine::Warning
,
2004 ("call of '%0' with string constant argument lacking a"
2005 " terminating NULL"),
2006 getMemberLocation(expr
))
2007 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2011 checkEmpty(expr
, callee
, TreatEmpty::Error
, n
, &repl
);
2014 DiagnosticsEngine::Warning
,
2015 ("in call of '%0' with non-array string constant argument,"
2016 " turn the non-array string constant into an array"),
2017 getMemberLocation(expr
))
2018 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
2020 } else if (auto cexpr
= lookForCXXConstructExpr(argExpr
)) {
2021 auto classdecl
= cexpr
->getConstructor()->getParent();
2022 if (loplugin::DeclCheck(classdecl
).Class("OString").Namespace("rtl")
2025 switch (cexpr
->getConstructor()->getNumParams()) {
2028 DiagnosticsEngine::Warning
,
2029 ("in call of '%0', replace empty %1 constructor with empty"
2031 cexpr
->getLocation())
2032 << callee
->getQualifiedNameAsString() << classdecl
2033 << expr
->getSourceRange();
2036 if (isStringConstant(
2037 cexpr
->getArg(0)->IgnoreParenImpCasts(), &n
, &nonArray
,
2041 if (compat::EvaluateAsInt(cexpr
->getArg(1),
2042 res
, compiler
.getASTContext()))
2044 if (res
== n
&& !emb
&& trm
) {
2046 DiagnosticsEngine::Warning
,
2047 ("in call of '%0', elide explicit %1"
2049 cexpr
->getLocation())
2050 << callee
->getQualifiedNameAsString()
2051 << classdecl
<< adviseNonArray(nonArray
)
2052 << expr
->getSourceRange();
2057 DiagnosticsEngine::Warning
,
2058 ("call of %0 constructor with string constant"
2059 " argument containing embedded NULLs"),
2060 cexpr
->getLocation())
2061 << classdecl
<< cexpr
->getSourceRange();
2066 DiagnosticsEngine::Warning
,
2067 ("call of %0 constructor with string constant"
2068 " argument lacking a terminating NULL"),
2069 cexpr
->getLocation())
2070 << classdecl
<< cexpr
->getSourceRange();
2074 DiagnosticsEngine::Warning
,
2075 "in call of '%0', elide explicit %1 constructor%2",
2076 cexpr
->getLocation())
2077 << callee
->getQualifiedNameAsString() << classdecl
2078 << adviseNonArray(nonArray
)
2079 << expr
->getSourceRange();
2090 loplugin::Plugin::Registration
< StringConstant
> X("stringconstant", true);
2094 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */