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/.
21 // Define a "string constant" to be a constant expression either of type "array
22 // of N char" where each array element is a non-NUL ASCII character---except
23 // that the last array element may be NUL, or, in some situations, of type char
24 // with a ASCII value (including NUL). Note that the former includes
25 // expressions denoting narrow string literals like "foo", and, with toolchains
26 // that support constexpr, constexpr variables declared like
28 // constexpr char str[] = "bar";
30 // This plugin flags uses of OUString functions with string constant arguments
31 // that can be rewritten more directly, like
33 // OUString::createFromAscii("foo") -> "foo"
37 // s.equals(OUString("bar")) -> s == "bar"
41 SourceLocation
getMemberLocation(Expr
const * expr
) {
42 CallExpr
const * e1
= dyn_cast
<CallExpr
>(expr
);
43 MemberExpr
const * e2
= e1
== nullptr
44 ? nullptr : dyn_cast
<MemberExpr
>(e1
->getCallee());
45 return e2
== nullptr ? expr
->getExprLoc()/*TODO*/ : e2
->getMemberLoc();
48 bool isLhsOfAssignment(FunctionDecl
const * decl
, unsigned parameter
) {
52 auto oo
= decl
->getOverloadedOperator();
54 || (oo
>= OO_PlusEqual
&& oo
<= OO_GreaterGreaterEqual
);
57 bool hasOverloads(FunctionDecl
const * decl
, unsigned arguments
) {
59 auto ctx
= decl
->getDeclContext();
60 if (ctx
->getDeclKind() == Decl::LinkageSpec
) {
61 ctx
= ctx
->getParent();
63 auto res
= ctx
->lookup(decl
->getDeclName());
64 for (auto d
= res
.begin(); d
!= res
.end(); ++d
) {
65 FunctionDecl
const * f
= dyn_cast
<FunctionDecl
>(*d
);
66 if (f
!= nullptr && f
->getMinRequiredArguments() <= arguments
67 && f
->getNumParams() >= arguments
)
79 public RecursiveASTVisitor
<StringConstant
>, public loplugin::RewritePlugin
82 explicit StringConstant(InstantiationData
const & data
): RewritePlugin(data
)
87 bool TraverseCallExpr(CallExpr
* expr
);
89 bool TraverseCXXMemberCallExpr(CXXMemberCallExpr
* expr
);
91 bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr
* expr
);
93 bool TraverseCXXConstructExpr(CXXConstructExpr
* expr
);
95 bool VisitCallExpr(CallExpr
const * expr
);
97 bool VisitCXXConstructExpr(CXXConstructExpr
const * expr
);
100 enum class TreatEmpty
{ DefaultCtor
, CheckEmpty
, Error
};
102 enum class ChangeKind
{ Char
, CharLen
, SingleChar
, OUStringLiteral1
};
104 enum class PassThrough
{ No
, EmptyConstantString
, NonEmptyConstantString
};
106 std::string
describeChangeKind(ChangeKind kind
);
108 bool isStringConstant(
109 Expr
const * expr
, unsigned * size
, bool * nonAscii
,
110 bool * embeddedNuls
, bool * terminatingNul
);
112 bool isZero(Expr
const * expr
);
115 Expr
const * expr
, ChangeKind kind
, std::string
const & original
,
116 std::string
const & replacement
, PassThrough pass
,
117 char const * rewriteFrom
, char const * rewriteTo
);
120 CallExpr
const * expr
, FunctionDecl
const * callee
,
121 TreatEmpty treatEmpty
, unsigned size
, std::string
* replacement
);
124 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
125 std::string
const & replacement
, TreatEmpty treatEmpty
, bool literal
,
126 char const * rewriteFrom
= nullptr, char const * rewriteTo
= nullptr);
129 CallExpr
const * expr
, unsigned arg1
, unsigned arg2
,
130 FunctionDecl
const * callee
, std::string
const & replacement
,
131 TreatEmpty treatEmpty
);
133 void handleOUStringCtor(
134 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
135 bool explicitFunctionalCastNotation
);
137 std::stack
<Expr
const *> calls_
;
140 void StringConstant::run() {
141 if (compiler
.getLangOpts().CPlusPlus
142 && compiler
.getPreprocessor().getIdentifierInfo(
143 "LIBO_INTERNAL_ONLY")->hasMacroDefinition())
144 //TODO: some parts of it are useful for external code, too
146 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
150 bool StringConstant::TraverseCallExpr(CallExpr
* expr
) {
151 if (!WalkUpFromCallExpr(expr
)) {
156 for (auto * e
: expr
->children()) {
157 if (!TraverseStmt(e
)) {
166 bool StringConstant::TraverseCXXMemberCallExpr(CXXMemberCallExpr
* expr
) {
167 if (!WalkUpFromCXXMemberCallExpr(expr
)) {
172 for (auto * e
: expr
->children()) {
173 if (!TraverseStmt(e
)) {
182 bool StringConstant::TraverseCXXOperatorCallExpr(CXXOperatorCallExpr
* expr
)
184 if (!WalkUpFromCXXOperatorCallExpr(expr
)) {
189 for (auto * e
: expr
->children()) {
190 if (!TraverseStmt(e
)) {
199 bool StringConstant::TraverseCXXConstructExpr(CXXConstructExpr
* expr
) {
200 if (!WalkUpFromCXXConstructExpr(expr
)) {
205 for (auto * e
: expr
->children()) {
206 if (!TraverseStmt(e
)) {
215 bool StringConstant::VisitCallExpr(CallExpr
const * expr
) {
216 if (ignoreLocation(expr
)) {
219 FunctionDecl
const * fdecl
= expr
->getDirectCallee();
220 if (fdecl
== nullptr) {
223 for (unsigned i
= 0; i
!= fdecl
->getNumParams(); ++i
) {
224 auto t
= fdecl
->getParamDecl(i
)->getType();
225 if (loplugin::TypeCheck(t
).NotSubstTemplateTypeParmType()
226 .LvalueReference().Const().NotSubstTemplateTypeParmType()
227 .Class("OUString").Namespace("rtl").GlobalNamespace())
229 if (!(isLhsOfAssignment(fdecl
, i
)
230 || hasOverloads(fdecl
, expr
->getNumArgs())))
232 handleOUStringCtor(expr
, i
, fdecl
, true);
236 loplugin::DeclCheck
dc(fdecl
);
237 //TODO: u.compareToAscii("foo") -> u.???("foo")
238 //TODO: u.compareToIgnoreAsciiCaseAscii("foo") -> u.???("foo")
239 if ((dc
.Function("createFromAscii").Class("OUString").Namespace("rtl")
241 && fdecl
->getNumParams() == 1)
243 // OUString::createFromAscii("foo") -> OUString("foo")
245 expr
, 0, fdecl
, "rtl::OUString constructor",
246 TreatEmpty::DefaultCtor
, true);
249 if ((dc
.Function("endsWithAsciiL").Class("OUString").Namespace("rtl")
251 && fdecl
->getNumParams() == 2)
253 // u.endsWithAsciiL("foo", 3) -> u.endsWith("foo"):
255 expr
, 0, 1, fdecl
, "rtl::OUString::endsWith", TreatEmpty::Error
);
258 if ((dc
.Function("endsWithIgnoreAsciiCaseAsciiL").Class("OUString")
259 .Namespace("rtl").GlobalNamespace())
260 && fdecl
->getNumParams() == 2)
262 // u.endsWithIgnoreAsciiCaseAsciiL("foo", 3) ->
263 // u.endsWithIgnoreAsciiCase("foo"):
265 expr
, 0, 1, fdecl
, "rtl::OUString::endsWithIgnoreAsciiCase",
269 if ((dc
.Function("equalsAscii").Class("OUString").Namespace("rtl")
271 && fdecl
->getNumParams() == 1)
273 // u.equalsAscii("foo") -> u == "foo":
275 expr
, 0, fdecl
, "operator ==", TreatEmpty::CheckEmpty
, false);
278 if ((dc
.Function("equalsAsciiL").Class("OUString").Namespace("rtl")
280 && fdecl
->getNumParams() == 2)
282 // u.equalsAsciiL("foo", 3) -> u == "foo":
283 handleCharLen(expr
, 0, 1, fdecl
, "operator ==", TreatEmpty::CheckEmpty
);
286 if ((dc
.Function("equalsIgnoreAsciiCaseAscii").Class("OUString")
287 .Namespace("rtl").GlobalNamespace())
288 && fdecl
->getNumParams() == 1)
290 // u.equalsIgnoreAsciiCaseAscii("foo") ->
291 // u.equalsIngoreAsciiCase("foo"):
293 expr
, 0, fdecl
, "rtl::OUString::equalsIgnoreAsciiCase",
294 TreatEmpty::CheckEmpty
, false);
297 if ((dc
.Function("equalsIgnoreAsciiCaseAsciiL").Class("OUString")
298 .Namespace("rtl").GlobalNamespace())
299 && fdecl
->getNumParams() == 2)
301 // u.equalsIgnoreAsciiCaseAsciiL("foo", 3) ->
302 // u.equalsIngoreAsciiCase("foo"):
304 expr
, 0, 1, fdecl
, "rtl::OUString::equalsIgnoreAsciiCase",
305 TreatEmpty::CheckEmpty
);
308 if ((dc
.Function("indexOfAsciiL").Class("OUString").Namespace("rtl")
310 && fdecl
->getNumParams() == 3)
312 assert(expr
->getNumArgs() == 3);
313 // u.indexOfAsciiL("foo", 3, i) -> u.indexOf("foo", i):
315 expr
, 0, 1, fdecl
, "rtl::OUString::indexOf", TreatEmpty::Error
);
318 if ((dc
.Function("lastIndexOfAsciiL").Class("OUString").Namespace("rtl")
320 && fdecl
->getNumParams() == 2)
322 // u.lastIndexOfAsciiL("foo", 3) -> u.lastIndexOf("foo"):
324 expr
, 0, 1, fdecl
, "rtl::OUString::lastIndexOf", TreatEmpty::Error
);
327 if ((dc
.Function("matchAsciiL").Class("OUString").Namespace("rtl")
329 && fdecl
->getNumParams() == 3)
331 assert(expr
->getNumArgs() == 3);
332 // u.matchAsciiL("foo", 3, i) -> u.match("foo", i):
335 (isZero(expr
->getArg(2))
336 ? std::string("rtl::OUString::startsWith")
337 : std::string("rtl::OUString::match")),
341 if ((dc
.Function("matchIgnoreAsciiCaseAsciiL").Class("OUString")
342 .Namespace("rtl").GlobalNamespace())
343 && fdecl
->getNumParams() == 3)
345 assert(expr
->getNumArgs() == 3);
346 // u.matchIgnoreAsciiCaseAsciiL("foo", 3, i) ->
347 // u.matchIgnoreAsciiCase("foo", i):
350 (isZero(expr
->getArg(2))
351 ? std::string("rtl::OUString::startsWithIgnoreAsciiCase")
352 : std::string("rtl::OUString::matchIgnoreAsciiCase")),
356 if ((dc
.Function("reverseCompareToAsciiL").Class("OUString")
357 .Namespace("rtl").GlobalNamespace())
358 && fdecl
->getNumParams() == 2)
360 // u.reverseCompareToAsciiL("foo", 3) -> u.reverseCompareTo("foo"):
362 expr
, 0, 1, fdecl
, "rtl::OUString::reverseCompareTo",
366 if ((dc
.Function("reverseCompareTo").Class("OUString").Namespace("rtl")
368 && fdecl
->getNumParams() == 1)
370 handleOUStringCtor(expr
, 0, fdecl
, false);
373 if ((dc
.Function("equalsIgnoreAsciiCase").Class("OUString").Namespace("rtl")
375 && fdecl
->getNumParams() == 1)
377 handleOUStringCtor(expr
, 0, fdecl
, false);
380 if ((dc
.Function("match").Class("OUString").Namespace("rtl")
382 && fdecl
->getNumParams() == 2)
384 handleOUStringCtor(expr
, 0, fdecl
, false);
387 if ((dc
.Function("matchIgnoreAsciiCase").Class("OUString").Namespace("rtl")
389 && fdecl
->getNumParams() == 2)
391 handleOUStringCtor(expr
, 0, fdecl
, false);
394 if ((dc
.Function("startsWith").Class("OUString").Namespace("rtl")
396 && fdecl
->getNumParams() == 2)
398 handleOUStringCtor(expr
, 0, fdecl
, false);
401 if ((dc
.Function("startsWithIgnoreAsciiCase").Class("OUString")
402 .Namespace("rtl").GlobalNamespace())
403 && fdecl
->getNumParams() == 2)
405 handleOUStringCtor(expr
, 0, fdecl
, false);
408 if ((dc
.Function("endsWith").Class("OUString").Namespace("rtl")
410 && fdecl
->getNumParams() == 2)
412 handleOUStringCtor(expr
, 0, fdecl
, false);
415 if ((dc
.Function("endsWithIgnoreAsciiCase").Class("OUString")
416 .Namespace("rtl").GlobalNamespace())
417 && fdecl
->getNumParams() == 2)
419 handleOUStringCtor(expr
, 0, fdecl
, false);
422 if ((dc
.Function("indexOf").Class("OUString").Namespace("rtl")
424 && fdecl
->getNumParams() == 2)
426 handleOUStringCtor(expr
, 0, fdecl
, false);
429 if ((dc
.Function("lastIndexOf").Class("OUString").Namespace("rtl")
431 && fdecl
->getNumParams() == 1)
433 handleOUStringCtor(expr
, 0, fdecl
, false);
436 if ((dc
.Function("replaceFirst").Class("OUString").Namespace("rtl")
438 && fdecl
->getNumParams() == 3)
440 handleOUStringCtor(expr
, 0, fdecl
, false);
441 handleOUStringCtor(expr
, 1, fdecl
, false);
444 if ((dc
.Function("replaceAll").Class("OUString").Namespace("rtl")
446 && (fdecl
->getNumParams() == 2 || fdecl
->getNumParams() == 3))
448 handleOUStringCtor(expr
, 0, fdecl
, false);
449 handleOUStringCtor(expr
, 1, fdecl
, false);
452 if ((dc
.Operator(OO_PlusEqual
).Class("OUString").Namespace("rtl")
454 && fdecl
->getNumParams() == 1)
457 expr
, dyn_cast
<CXXOperatorCallExpr
>(expr
) == nullptr ? 0 : 1,
461 if ((dc
.Function("equals").Class("OUString").Namespace("rtl")
463 && fdecl
->getNumParams() == 1)
469 if (!isStringConstant(
470 expr
->getArg(0)->IgnoreParenImpCasts(), &n
, &non
, &emb
, &trm
))
476 DiagnosticsEngine::Warning
,
477 ("call of %0 with string constant argument containging"
478 " non-ASCII characters"),
480 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
484 DiagnosticsEngine::Warning
,
485 ("call of %0 with string constant argument containging embedded"
488 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
492 DiagnosticsEngine::Warning
,
493 ("rewrite call of %0 with empty string constant argument as"
494 " call of rtl::OUString::isEmpty"),
496 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
500 if (dc
.Operator(OO_EqualEqual
).Namespace("rtl").GlobalNamespace()
501 && fdecl
->getNumParams() == 2)
503 for (unsigned i
= 0; i
!= 2; ++i
) {
508 if (!isStringConstant(
509 expr
->getArg(i
)->IgnoreParenImpCasts(), &n
, &non
, &emb
,
516 DiagnosticsEngine::Warning
,
517 ("call of %0 with string constant argument containging"
518 " non-ASCII characters"),
520 << fdecl
->getQualifiedNameAsString()
521 << expr
->getSourceRange();
525 DiagnosticsEngine::Warning
,
526 ("call of %0 with string constant argument containging"
529 << fdecl
->getQualifiedNameAsString()
530 << expr
->getSourceRange();
534 DiagnosticsEngine::Warning
,
535 ("rewrite call of %0 with empty string constant argument as"
536 " call of rtl::OUString::isEmpty"),
538 << fdecl
->getQualifiedNameAsString()
539 << expr
->getSourceRange();
544 if (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl").GlobalNamespace()
545 && fdecl
->getNumParams() == 2)
547 for (unsigned i
= 0; i
!= 2; ++i
) {
552 if (!isStringConstant(
553 expr
->getArg(i
)->IgnoreParenImpCasts(), &n
, &non
, &emb
,
560 DiagnosticsEngine::Warning
,
561 ("call of %0 with string constant argument containging"
562 " non-ASCII characters"),
564 << fdecl
->getQualifiedNameAsString()
565 << expr
->getSourceRange();
569 DiagnosticsEngine::Warning
,
570 ("call of %0 with string constant argument containging"
573 << fdecl
->getQualifiedNameAsString()
574 << expr
->getSourceRange();
578 DiagnosticsEngine::Warning
,
579 ("rewrite call of %0 with empty string constant argument as"
580 " call of !rtl::OUString::isEmpty"),
582 << fdecl
->getQualifiedNameAsString()
583 << expr
->getSourceRange();
588 if (dc
.Operator(OO_Equal
).Namespace("rtl").GlobalNamespace()
589 && fdecl
->getNumParams() == 1)
595 if (!isStringConstant(
596 expr
->getArg(1)->IgnoreParenImpCasts(), &n
, &non
, &emb
, &trm
))
602 DiagnosticsEngine::Warning
,
603 ("call of %0 with string constant argument containging"
604 " non-ASCII characters"),
606 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
610 DiagnosticsEngine::Warning
,
611 ("call of %0 with string constant argument containging embedded"
614 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
618 DiagnosticsEngine::Warning
,
619 ("rewrite call of %0 with empty string constant argument as"
620 " call of rtl::OUString::clear"),
622 << fdecl
->getQualifiedNameAsString() << expr
->getSourceRange();
627 if ((dc
.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
629 && fdecl
->getNumParams() == 1)
631 // u.appendAscii("foo") -> u.append("foo")
633 expr
, 0, fdecl
, "rtl::OUStringBuffer::append", TreatEmpty::Error
,
634 true, "appendAscii", "append");
637 if ((dc
.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
639 && fdecl
->getNumParams() == 2)
641 // u.appendAscii("foo", 3) -> u.append("foo"):
643 expr
, 0, 1, fdecl
, "rtl::OUStringBuffer::append",
650 bool StringConstant::VisitCXXConstructExpr(CXXConstructExpr
const * expr
) {
651 if (ignoreLocation(expr
)) {
654 auto cdecl = expr
->getConstructor()->getParent();
655 if (loplugin::DeclCheck(cdecl)
656 .Class("OUString").Namespace("rtl").GlobalNamespace())
660 switch (expr
->getConstructor()->getNumParams()) {
662 if (!loplugin::TypeCheck(
663 expr
->getConstructor()->getParamDecl(0)->getType())
664 .Typedef("sal_Unicode").GlobalNamespace())
668 kind
= ChangeKind::SingleChar
;
669 pass
= PassThrough::NonEmptyConstantString
;
673 auto arg
= expr
->getArg(0);
674 if (loplugin::TypeCheck(arg
->getType())
675 .Class("OUStringLiteral1_").Namespace("rtl")
678 kind
= ChangeKind::OUStringLiteral1
;
679 pass
= PassThrough::NonEmptyConstantString
;
685 if (!isStringConstant(
686 arg
->IgnoreParenImpCasts(), &n
, &non
, &emb
, &trm
))
692 DiagnosticsEngine::Warning
,
693 ("construction of %0 with string constant argument"
694 " containging non-ASCII characters"),
696 << cdecl << expr
->getSourceRange();
700 DiagnosticsEngine::Warning
,
701 ("construction of %0 with string constant argument"
702 " containging embedded NULs"),
704 << cdecl << expr
->getSourceRange();
706 kind
= ChangeKind::Char
;
708 ? PassThrough::EmptyConstantString
709 : PassThrough::NonEmptyConstantString
;
716 if (!calls_
.empty()) {
717 Expr
const * call
= calls_
.top();
718 CallExpr::const_arg_iterator argsBeg
;
719 CallExpr::const_arg_iterator argsEnd
;
720 if (isa
<CallExpr
>(call
)) {
721 argsBeg
= cast
<CallExpr
>(call
)->arg_begin();
722 argsEnd
= cast
<CallExpr
>(call
)->arg_end();
723 } else if (isa
<CXXConstructExpr
>(call
)) {
724 argsBeg
= cast
<CXXConstructExpr
>(call
)->arg_begin();
725 argsEnd
= cast
<CXXConstructExpr
>(call
)->arg_end();
729 for (auto i(argsBeg
); i
!= argsEnd
; ++i
) {
730 Expr
const * e
= (*i
)->IgnoreParenImpCasts();
731 if (isa
<MaterializeTemporaryExpr
>(e
)) {
732 e
= cast
<MaterializeTemporaryExpr
>(e
)->GetTemporaryExpr()
733 ->IgnoreParenImpCasts();
735 if (isa
<CXXFunctionalCastExpr
>(e
)) {
736 e
= cast
<CXXFunctionalCastExpr
>(e
)->getSubExpr()
737 ->IgnoreParenImpCasts();
739 if (isa
<CXXBindTemporaryExpr
>(e
)) {
740 e
= cast
<CXXBindTemporaryExpr
>(e
)->getSubExpr()
741 ->IgnoreParenImpCasts();
744 if (isa
<CallExpr
>(call
)) {
745 FunctionDecl
const * fdecl
746 = cast
<CallExpr
>(call
)->getDirectCallee();
747 if (fdecl
== nullptr) {
750 loplugin::DeclCheck
dc(fdecl
);
751 if (pass
== PassThrough::EmptyConstantString
) {
752 if ((dc
.Function("equals").Class("OUString")
753 .Namespace("rtl").GlobalNamespace())
754 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
758 DiagnosticsEngine::Warning
,
759 ("rewrite call of %0 with construction of"
760 " %1 with empty string constant argument"
761 " as call of rtl::OUString::isEmpty"),
762 getMemberLocation(call
))
763 << fdecl
->getQualifiedNameAsString()
764 << cdecl << call
->getSourceRange();
767 if (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl")
771 DiagnosticsEngine::Warning
,
772 ("rewrite call of %0 with construction of"
773 " %1 with empty string constant argument"
774 " as call of !rtl::OUString::isEmpty"),
775 getMemberLocation(call
))
776 << fdecl
->getQualifiedNameAsString()
777 << cdecl << call
->getSourceRange();
780 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
782 || (dc
.Operator(OO_Plus
).Class("OUString")
783 .Namespace("rtl").GlobalNamespace()))
786 DiagnosticsEngine::Warning
,
787 ("call of %0 with suspicous construction of"
788 " %1 with empty string constant argument"),
789 getMemberLocation(call
))
790 << fdecl
->getQualifiedNameAsString()
791 << cdecl << call
->getSourceRange();
794 if (dc
.Operator(OO_Equal
).Class("OUString")
795 .Namespace("rtl").GlobalNamespace())
798 DiagnosticsEngine::Warning
,
799 ("rewrite call of %0 with construction of"
800 " %1 with empty string constant argument"
801 " as call of rtl::OUString::clear"),
802 getMemberLocation(call
))
803 << fdecl
->getQualifiedNameAsString()
804 << cdecl << call
->getSourceRange();
808 assert(pass
== PassThrough::NonEmptyConstantString
);
809 if (dc
.Function("equals").Class("OUString")
810 .Namespace("rtl").GlobalNamespace())
813 DiagnosticsEngine::Warning
,
814 (("rewrite call of %0 with construction of"
816 + describeChangeKind(kind
)
817 + " as operator =="),
818 getMemberLocation(call
))
819 << fdecl
->getQualifiedNameAsString()
820 << cdecl << call
->getSourceRange();
823 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
825 || (dc
.Operator(OO_Plus
).Class("OUString")
826 .Namespace("rtl").GlobalNamespace())
827 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
829 || (dc
.Operator(OO_ExclaimEqual
)
830 .Namespace("rtl").GlobalNamespace()))
832 if (dc
.Operator(OO_Plus
).Namespace("rtl")
836 compiler
.getSourceManager().getFilename(
837 compiler
.getSourceManager()
839 expr
->getLocStart())));
842 "/sal/qa/rtl/strings/test_ostring_concat.cxx")
845 "/sal/qa/rtl/strings/test_oustring_concat.cxx")))
850 auto loc
= expr
->getArg(0)->getLocStart();
851 while (compiler
.getSourceManager()
852 .isMacroArgExpansion(loc
))
854 loc
= compiler
.getSourceManager()
855 .getImmediateMacroCallerLoc(loc
);
857 if ((compiler
.getSourceManager()
858 .isMacroBodyExpansion(loc
))
859 && (Lexer::getImmediateMacroName(
860 loc
, compiler
.getSourceManager(),
861 compiler
.getLangOpts())
866 if (kind
== ChangeKind::SingleChar
) {
868 DiagnosticsEngine::Warning
,
869 ("rewrite construction of %0 with "
870 + describeChangeKind(kind
)
871 + (" in call of %1 as construction of"
872 " OUStringLiteral1")),
873 getMemberLocation(expr
))
875 << fdecl
->getQualifiedNameAsString()
876 << expr
->getSourceRange();
879 DiagnosticsEngine::Warning
,
880 ("elide construction of %0 with "
881 + describeChangeKind(kind
)
883 getMemberLocation(expr
))
885 << fdecl
->getQualifiedNameAsString()
886 << expr
->getSourceRange();
892 } else if (isa
<CXXConstructExpr
>(call
)) {
904 std::string
StringConstant::describeChangeKind(ChangeKind kind
) {
906 case ChangeKind::Char
:
907 return "string constant argument";
908 case ChangeKind::CharLen
:
909 return "string constant and matching length arguments";
910 case ChangeKind::SingleChar
:
911 return "sal_Unicode argument";
912 case ChangeKind::OUStringLiteral1
:
913 return "OUStringLiteral1 argument";
919 bool StringConstant::isStringConstant(
920 Expr
const * expr
, unsigned * size
, bool * nonAscii
, bool * embeddedNuls
,
921 bool * terminatingNul
)
923 assert(expr
!= nullptr);
924 assert(size
!= nullptr);
925 assert(nonAscii
!= nullptr);
926 assert(embeddedNuls
!= nullptr);
927 assert(terminatingNul
!= nullptr);
928 QualType t
= expr
->getType();
929 // Look inside RTL_CONSTASCII_STRINGPARAM:
930 if (loplugin::TypeCheck(t
).Pointer().Const().Char()) {
931 auto e2
= dyn_cast
<UnaryOperator
>(expr
);
932 if (e2
== nullptr || e2
->getOpcode() != UO_AddrOf
) {
935 auto e3
= dyn_cast
<ArraySubscriptExpr
>(
936 e2
->getSubExpr()->IgnoreParenImpCasts());
937 if (e3
== nullptr || !isZero(e3
->getIdx()->IgnoreParenImpCasts())) {
940 expr
= e3
->getBase()->IgnoreParenImpCasts();
943 if (!(t
->isConstantArrayType() && t
.isConstQualified()
944 && (loplugin::TypeCheck(t
->getAsArrayTypeUnsafe()->getElementType())
949 DeclRefExpr
const * dre
= dyn_cast
<DeclRefExpr
>(expr
);
950 if (dre
!= nullptr) {
951 VarDecl
const * var
= dyn_cast
<VarDecl
>(dre
->getDecl());
952 if (var
!= nullptr) {
953 Expr
const * init
= var
->getAnyInitializer();
954 if (init
!= nullptr) {
955 expr
= init
->IgnoreParenImpCasts();
959 StringLiteral
const * lit
= dyn_cast
<StringLiteral
>(expr
);
960 if (lit
!= nullptr) {
961 if (!lit
->isAscii()) {
964 unsigned n
= lit
->getLength();
967 StringRef str
= lit
->getString();
968 for (unsigned i
= 0; i
!= n
; ++i
) {
969 if (str
[i
] == '\0') {
971 } else if (static_cast<unsigned char>(str
[i
]) >= 0x80) {
978 *terminatingNul
= true;
982 if (!expr
->isCXX11ConstantExpr(compiler
.getASTContext(), &v
)) {
985 switch (v
.getKind()) {
986 case APValue::LValue
:
988 Expr
const * e
= v
.getLValueBase().dyn_cast
<Expr
const *>();
989 assert(e
!= nullptr); //TODO???
990 if (!v
.getLValueOffset().isZero()) {
993 Expr
const * e2
= e
->IgnoreParenImpCasts();
995 return isStringConstant(
996 e2
, size
, nonAscii
, embeddedNuls
, terminatingNul
);
998 //TODO: string literals are represented as recursive LValues???
1000 = compiler
.getASTContext().getAsConstantArrayType(t
)->getSize();
1003 assert(n
.ule(std::numeric_limits
<unsigned>::max()));
1004 *size
= static_cast<unsigned>(n
.getLimitedValue());
1005 *nonAscii
= false; //TODO
1006 *embeddedNuls
= false; //TODO
1007 *terminatingNul
= true;
1010 case APValue::Array
:
1012 if (v
.hasArrayFiller()) { //TODO: handle final NUL filler?
1015 unsigned n
= v
.getArraySize();
1019 for (unsigned i
= 0; i
!= n
- 1; ++i
) {
1020 APValue
e(v
.getArrayInitializedElt(i
));
1021 if (!e
.isInt()) { //TODO: assert?
1024 APSInt iv
= e
.getInt();
1027 } else if (iv
.uge(0x80)) {
1031 APValue
e(v
.getArrayInitializedElt(n
- 1));
1032 if (!e
.isInt()) { //TODO: assert?
1035 bool trm
= e
.getInt() == 0;
1036 *size
= trm
? n
- 1 : n
;
1038 *embeddedNuls
= emb
;
1039 *terminatingNul
= trm
;
1043 assert(false); //TODO???
1048 bool StringConstant::isZero(Expr
const * expr
) {
1050 return expr
->isIntegerConstantExpr(res
, compiler
.getASTContext())
1054 void StringConstant::reportChange(
1055 Expr
const * expr
, ChangeKind kind
, std::string
const & original
,
1056 std::string
const & replacement
, PassThrough pass
, char const * rewriteFrom
,
1057 char const * rewriteTo
)
1059 assert((rewriteFrom
== nullptr) == (rewriteTo
== nullptr));
1060 if (pass
!= PassThrough::No
&& !calls_
.empty()) {
1061 Expr
const * call
= calls_
.top();
1062 CallExpr::const_arg_iterator argsBeg
;
1063 CallExpr::const_arg_iterator argsEnd
;
1064 if (isa
<CallExpr
>(call
)) {
1065 argsBeg
= cast
<CallExpr
>(call
)->arg_begin();
1066 argsEnd
= cast
<CallExpr
>(call
)->arg_end();
1067 } else if (isa
<CXXConstructExpr
>(call
)) {
1068 argsBeg
= cast
<CXXConstructExpr
>(call
)->arg_begin();
1069 argsEnd
= cast
<CXXConstructExpr
>(call
)->arg_end();
1073 for (auto i(argsBeg
); i
!= argsEnd
; ++i
) {
1074 Expr
const * e
= (*i
)->IgnoreParenImpCasts();
1075 if (isa
<CXXBindTemporaryExpr
>(e
)) {
1076 e
= cast
<CXXBindTemporaryExpr
>(e
)->getSubExpr()
1077 ->IgnoreParenImpCasts();
1080 if (isa
<CallExpr
>(call
)) {
1081 FunctionDecl
const * fdecl
1082 = cast
<CallExpr
>(call
)->getDirectCallee();
1083 if (fdecl
== nullptr) {
1086 loplugin::DeclCheck
dc(fdecl
);
1087 if (pass
== PassThrough::EmptyConstantString
) {
1088 if ((dc
.Function("equals").Class("OUString")
1089 .Namespace("rtl").GlobalNamespace())
1090 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1091 .GlobalNamespace()))
1094 DiagnosticsEngine::Warning
,
1095 ("rewrite call of %0 with call of " + original
1096 + (" with empty string constant argument as"
1097 " call of rtl::OUString::isEmpty")),
1098 getMemberLocation(call
))
1099 << fdecl
->getQualifiedNameAsString()
1100 << call
->getSourceRange();
1103 if (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl")
1107 DiagnosticsEngine::Warning
,
1108 ("rewrite call of %0 with call of " + original
1109 + (" with empty string constant argument as"
1110 " call of !rtl::OUString::isEmpty")),
1111 getMemberLocation(call
))
1112 << fdecl
->getQualifiedNameAsString()
1113 << call
->getSourceRange();
1116 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
1118 || (dc
.Operator(OO_Plus
).Class("OUString")
1119 .Namespace("rtl").GlobalNamespace()))
1122 DiagnosticsEngine::Warning
,
1123 ("call of %0 with suspicous call of " + original
1124 + " with empty string constant argument"),
1125 getMemberLocation(call
))
1126 << fdecl
->getQualifiedNameAsString()
1127 << call
->getSourceRange();
1130 if (dc
.Operator(OO_Equal
).Class("OUString")
1131 .Namespace("rtl").GlobalNamespace())
1134 DiagnosticsEngine::Warning
,
1135 ("rewrite call of %0 with call of " + original
1136 + (" with empty string constant argument as"
1137 " call of rtl::OUString::call")),
1138 getMemberLocation(call
))
1139 << fdecl
->getQualifiedNameAsString()
1140 << call
->getSourceRange();
1144 assert(pass
== PassThrough::NonEmptyConstantString
);
1145 if ((dc
.Function("equals").Class("OUString")
1146 .Namespace("rtl").GlobalNamespace())
1147 || (dc
.Operator(OO_Equal
).Class("OUString")
1148 .Namespace("rtl").GlobalNamespace())
1149 || (dc
.Operator(OO_EqualEqual
).Namespace("rtl")
1151 || (dc
.Operator(OO_ExclaimEqual
).Namespace("rtl")
1152 .GlobalNamespace()))
1155 DiagnosticsEngine::Warning
,
1156 ("elide call of " + original
+ " with "
1157 + describeChangeKind(kind
) + " in call of %0"),
1158 getMemberLocation(expr
))
1159 << fdecl
->getQualifiedNameAsString()
1160 << expr
->getSourceRange();
1163 if ((dc
.Operator(OO_Plus
).Namespace("rtl")
1165 || (dc
.Operator(OO_Plus
).Class("OUString")
1166 .Namespace("rtl").GlobalNamespace()))
1169 DiagnosticsEngine::Warning
,
1170 ("rewrite call of " + original
+ " with "
1171 + describeChangeKind(kind
)
1172 + (" in call of %0 as (implicit) construction"
1173 " of rtl::OUString")),
1174 getMemberLocation(expr
))
1175 << fdecl
->getQualifiedNameAsString()
1176 << expr
->getSourceRange();
1181 DiagnosticsEngine::Warning
,
1182 "TODO call inside %0", getMemberLocation(expr
))
1183 << fdecl
->getQualifiedNameAsString()
1184 << expr
->getSourceRange();
1186 } else if (isa
<CXXConstructExpr
>(call
)) {
1187 auto cdecl = cast
<CXXConstructExpr
>(call
)->getConstructor()
1189 loplugin::DeclCheck
dc(cdecl);
1190 if (dc
.Class("OUString").Namespace("rtl").GlobalNamespace()
1191 || (dc
.Class("OUStringBuffer").Namespace("rtl")
1192 .GlobalNamespace()))
1194 //TODO: propagate further out?
1195 if (pass
== PassThrough::EmptyConstantString
) {
1197 DiagnosticsEngine::Warning
,
1198 ("rewrite construction of %0 with call of "
1200 + (" with empty string constant argument as"
1201 " default construction of %0")),
1202 getMemberLocation(call
))
1203 << cdecl->getQualifiedNameAsString()
1204 << call
->getSourceRange();
1206 assert(pass
== PassThrough::NonEmptyConstantString
);
1208 DiagnosticsEngine::Warning
,
1209 ("elide call of " + original
+ " with "
1210 + describeChangeKind(kind
)
1211 + " in construction of %0"),
1212 getMemberLocation(expr
))
1213 << cdecl->getQualifiedNameAsString()
1214 << expr
->getSourceRange();
1224 if (rewriter
!= nullptr && rewriteFrom
!= nullptr) {
1225 SourceLocation loc
= getMemberLocation(expr
);
1226 while (compiler
.getSourceManager().isMacroArgExpansion(loc
)) {
1227 loc
= compiler
.getSourceManager().getImmediateMacroCallerLoc(loc
);
1229 if (compiler
.getSourceManager().isMacroBodyExpansion(loc
)) {
1230 loc
= compiler
.getSourceManager().getSpellingLoc(loc
);
1232 unsigned n
= Lexer::MeasureTokenLength(
1233 loc
, compiler
.getSourceManager(), compiler
.getLangOpts());
1234 if ((std::string(compiler
.getSourceManager().getCharacterData(loc
), n
)
1236 && replaceText(loc
, n
, rewriteTo
))
1242 DiagnosticsEngine::Warning
,
1243 ("rewrite call of " + original
+ " with " + describeChangeKind(kind
)
1244 + " as call of " + replacement
),
1245 getMemberLocation(expr
))
1246 << expr
->getSourceRange();
1249 void StringConstant::checkEmpty(
1250 CallExpr
const * expr
, FunctionDecl
const * callee
, TreatEmpty treatEmpty
,
1251 unsigned size
, std::string
* replacement
)
1253 assert(replacement
!= nullptr);
1255 switch (treatEmpty
) {
1256 case TreatEmpty::DefaultCtor
:
1257 *replacement
= "rtl::OUString default constructor";
1259 case TreatEmpty::CheckEmpty
:
1260 *replacement
= "rtl::OUString::isEmpty";
1262 case TreatEmpty::Error
:
1264 DiagnosticsEngine::Warning
,
1265 "call of %0 with suspicous empty string constant argument",
1266 getMemberLocation(expr
))
1267 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1273 void StringConstant::handleChar(
1274 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
1275 std::string
const & replacement
, TreatEmpty treatEmpty
, bool literal
,
1276 char const * rewriteFrom
, char const * rewriteTo
)
1282 if (!isStringConstant(
1283 expr
->getArg(arg
)->IgnoreParenImpCasts(), &n
, &non
, &emb
, &trm
))
1289 DiagnosticsEngine::Warning
,
1290 ("call of %0 with string constant argument containging non-ASCII"
1292 getMemberLocation(expr
))
1293 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1298 DiagnosticsEngine::Warning
,
1299 ("call of %0 with string constant argument containging embedded"
1301 getMemberLocation(expr
))
1302 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1307 DiagnosticsEngine::Warning
,
1308 ("call of %0 with string constant argument lacking a terminating"
1310 getMemberLocation(expr
))
1311 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1314 std::string
repl(replacement
);
1315 checkEmpty(expr
, callee
, treatEmpty
, n
, &repl
);
1317 expr
, ChangeKind::Char
, callee
->getQualifiedNameAsString(), repl
,
1320 ? PassThrough::EmptyConstantString
1321 : PassThrough::NonEmptyConstantString
)
1323 rewriteFrom
, rewriteTo
);
1326 void StringConstant::handleCharLen(
1327 CallExpr
const * expr
, unsigned arg1
, unsigned arg2
,
1328 FunctionDecl
const * callee
, std::string
const & replacement
,
1329 TreatEmpty treatEmpty
)
1331 // Especially for f(RTL_CONSTASCII_STRINGPARAM("foo")), where
1332 // RTL_CONSTASCII_STRINGPARAM expands to complicated expressions involving
1333 // (&(X)[0] sub-expressions (and it might or might not be better to handle
1334 // that at the level of non-expanded macros instead, but I have not found
1335 // out how to do that yet anyway):
1340 if (!(isStringConstant(
1341 expr
->getArg(arg1
)->IgnoreParenImpCasts(), &n
, &non
, &emb
, &trm
)
1347 if (expr
->getArg(arg2
)->isIntegerConstantExpr(
1348 res
, compiler
.getASTContext()))
1354 UnaryOperator
const * op
= dyn_cast
<UnaryOperator
>(
1355 expr
->getArg(arg1
)->IgnoreParenImpCasts());
1356 if (op
== nullptr || op
->getOpcode() != UO_AddrOf
) {
1359 ArraySubscriptExpr
const * subs
= dyn_cast
<ArraySubscriptExpr
>(
1360 op
->getSubExpr()->IgnoreParenImpCasts());
1361 if (subs
== nullptr) {
1368 if (!(isStringConstant(
1369 subs
->getBase()->IgnoreParenImpCasts(), &n2
, &non2
, &emb2
,
1371 && n2
== n
&& non2
== non
&& emb2
== emb
&& trm2
== trm
1372 //TODO: same strings
1373 && subs
->getIdx()->isIntegerConstantExpr(
1374 res
, compiler
.getASTContext())
1382 DiagnosticsEngine::Warning
,
1383 ("call of %0 with string constant argument containging non-ASCII"
1385 getMemberLocation(expr
))
1386 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1391 std::string
repl(replacement
);
1392 checkEmpty(expr
, callee
, treatEmpty
, n
, &repl
);
1394 expr
, ChangeKind::CharLen
, callee
->getQualifiedNameAsString(), repl
,
1395 PassThrough::No
, nullptr, nullptr);
1398 void StringConstant::handleOUStringCtor(
1399 CallExpr
const * expr
, unsigned arg
, FunctionDecl
const * callee
,
1400 bool explicitFunctionalCastNotation
)
1402 auto e0
= expr
->getArg(arg
)->IgnoreParenImpCasts();
1403 auto e1
= dyn_cast
<CXXFunctionalCastExpr
>(e0
);
1404 if (e1
== nullptr) {
1405 if (explicitFunctionalCastNotation
) {
1409 e0
= e1
->getSubExpr()->IgnoreParenImpCasts();
1411 auto e2
= dyn_cast
<CXXBindTemporaryExpr
>(e0
);
1412 if (e2
== nullptr) {
1415 auto e3
= dyn_cast
<CXXConstructExpr
>(
1416 e2
->getSubExpr()->IgnoreParenImpCasts());
1417 if (e3
== nullptr) {
1420 if (e3
->getConstructor()->getQualifiedNameAsString()
1421 != "rtl::OUString::OUString")
1425 if (e3
->getNumArgs() == 0) {
1427 DiagnosticsEngine::Warning
,
1428 ("in call of %0, replace default-constructed OUString with an empty"
1431 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1434 if (e3
->getNumArgs() == 1
1435 && e3
->getConstructor()->getNumParams() == 1
1436 && (loplugin::TypeCheck(
1437 e3
->getConstructor()->getParamDecl(0)->getType())
1438 .Typedef("sal_Unicode").GlobalNamespace()))
1440 // It may not be easy to rewrite OUString(c), esp. given there is no
1441 // OUString ctor taking an OUStringLiteral1 arg, so don't warn there:
1442 if (!explicitFunctionalCastNotation
) {
1444 DiagnosticsEngine::Warning
,
1445 ("in call of %0, replace OUString constructed from a"
1446 " sal_Unicode with an OUStringLiteral1"),
1448 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1452 if (e3
->getNumArgs() != 2) {
1459 if (!isStringConstant(
1460 e3
->getArg(0)->IgnoreParenImpCasts(), &n
, &non
, &emb
, &trm
))
1464 //TODO: non, emb, trm
1465 if (rewriter
!= nullptr) {
1466 auto loc1
= e3
->getLocStart();
1467 auto range
= e3
->getParenOrBraceRange();
1468 if (loc1
.isFileID() && range
.getBegin().isFileID()
1469 && range
.getEnd().isFileID())
1471 auto loc2
= range
.getBegin();
1472 for (bool first
= true;; first
= false) {
1473 unsigned n
= Lexer::MeasureTokenLength(
1474 loc2
, compiler
.getSourceManager(), compiler
.getLangOpts());
1477 compiler
.getSourceManager().getCharacterData(loc2
), n
);
1478 while (s
.startswith("\\\n")) {
1479 s
= s
.drop_front(2);
1481 && (s
.front() == ' ' || s
.front() == '\t'
1482 || s
.front() == '\n' || s
.front() == '\v'
1483 || s
.front() == '\f'))
1485 s
= s
.drop_front(1);
1488 if (!(s
.empty() || s
.startswith("/*") || s
.startswith("//")
1494 loc2
= loc2
.getLocWithOffset(std::max
<unsigned>(n
, 1));
1496 auto loc3
= range
.getEnd();
1498 auto l
= Lexer::GetBeginningOfToken(
1499 loc3
.getLocWithOffset(-1), compiler
.getSourceManager(),
1500 compiler
.getLangOpts());
1501 unsigned n
= Lexer::MeasureTokenLength(
1502 l
, compiler
.getSourceManager(), compiler
.getLangOpts());
1503 StringRef
s(compiler
.getSourceManager().getCharacterData(l
), n
);
1504 while (s
.startswith("\\\n")) {
1505 s
= s
.drop_front(2);
1507 && (s
.front() == ' ' || s
.front() == '\t'
1508 || s
.front() == '\n' || s
.front() == '\v'
1509 || s
.front() == '\f'))
1511 s
= s
.drop_front(1);
1514 if (!(s
.empty() || s
.startswith("/*") || s
.startswith("//")
1521 if (removeText(CharSourceRange(SourceRange(loc1
, loc2
), false))) {
1522 if (removeText(SourceRange(loc3
, range
.getEnd()))) {
1525 report(DiagnosticsEngine::Fatal
, "Corrupt rewrite", loc3
)
1526 << expr
->getSourceRange();
1532 DiagnosticsEngine::Warning
,
1533 ("in call of %0, replace OUString constructed from a string literal"
1534 " directly with the string literal"),
1536 << callee
->getQualifiedNameAsString() << expr
->getSourceRange();
1539 loplugin::Plugin::Registration
< StringConstant
> X("stringconstant", true);
1543 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */