bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / stringconstant.cxx
blob05cfa03ff711e6a92adc7264a571b896c408c34c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
10 #include <algorithm>
11 #include <cassert>
12 #include <cstdint>
13 #include <cstdlib>
14 #include <iomanip>
15 #include <limits>
16 #include <sstream>
17 #include <stack>
18 #include <string>
19 #include <vector>
20 #include <iostream>
22 #include "check.hxx"
23 #include "plugin.hxx"
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"
39 // and
41 // s.equals(OUString("bar")) -> s == "bar"
43 namespace {
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) {
53 if (parameter != 0) {
54 return false;
56 auto oo = decl->getOverloadedOperator();
57 return oo == OO_Equal
58 || (oo >= OO_PlusEqual && oo <= OO_GreaterGreaterEqual);
61 bool hasOverloads(FunctionDecl const * decl, unsigned arguments) {
62 int n = 0;
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()) {
75 continue;
77 ++n;
78 if (n == 2) {
79 return true;
83 return false;
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) {
100 return nonArray
101 ? ", and turn the non-array string constant into an array" : "";
104 class StringConstant:
105 public loplugin::FilteringRewritePlugin<StringConstant>
107 public:
108 explicit StringConstant(loplugin::InstantiationData const & data):
109 FilteringRewritePlugin(data) {}
111 void run() override;
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);
125 private:
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);
143 void reportChange(
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);
148 void checkEmpty(
149 CallExpr const * expr, FunctionDecl const * callee,
150 TreatEmpty treatEmpty, unsigned size, std::string * replacement);
152 void handleChar(
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);
157 void handleCharLen(
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)) {
201 return false;
203 calls_.push(expr);
204 bool bRes = true;
205 for (auto * e: expr->children()) {
206 if (!TraverseStmt(e)) {
207 bRes = false;
208 break;
211 calls_.pop();
212 return bRes;
215 bool StringConstant::TraverseCXXMemberCallExpr(CXXMemberCallExpr * expr) {
216 if (!WalkUpFromCXXMemberCallExpr(expr)) {
217 return false;
219 calls_.push(expr);
220 bool bRes = true;
221 for (auto * e: expr->children()) {
222 if (!TraverseStmt(e)) {
223 bRes = false;
224 break;
227 calls_.pop();
228 return bRes;
231 bool StringConstant::TraverseCXXOperatorCallExpr(CXXOperatorCallExpr * expr)
233 if (!WalkUpFromCXXOperatorCallExpr(expr)) {
234 return false;
236 calls_.push(expr);
237 bool bRes = true;
238 for (auto * e: expr->children()) {
239 if (!TraverseStmt(e)) {
240 bRes = false;
241 break;
244 calls_.pop();
245 return bRes;
248 bool StringConstant::TraverseCXXConstructExpr(CXXConstructExpr * expr) {
249 if (!WalkUpFromCXXConstructExpr(expr)) {
250 return false;
252 calls_.push(expr);
253 bool bRes = true;
254 for (auto * e: expr->children()) {
255 if (!TraverseStmt(e)) {
256 bRes = false;
257 break;
260 calls_.pop();
261 return bRes;
264 bool StringConstant::VisitCallExpr(CallExpr const * expr) {
265 if (ignoreLocation(expr)) {
266 return true;
268 FunctionDecl const * fdecl = expr->getDirectCallee();
269 if (fdecl == nullptr) {
270 return true;
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")
299 .GlobalNamespace())
300 && fdecl->getNumParams() == 1)
302 // OUString::createFromAscii("foo") -> OUString("foo")
303 handleChar(
304 expr, 0, fdecl, "rtl::OUString constructor",
305 TreatEmpty::DefaultCtor, true);
306 return true;
308 if ((dc.Function("endsWithAsciiL").Class("OUString").Namespace("rtl")
309 .GlobalNamespace())
310 && fdecl->getNumParams() == 2)
312 // u.endsWithAsciiL("foo", 3) -> u.endsWith("foo"):
313 handleCharLen(
314 expr, 0, 1, fdecl, "rtl::OUString::endsWith", TreatEmpty::Error);
315 return true;
317 if ((dc.Function("endsWithIgnoreAsciiCaseAsciiL").Class("OUString")
318 .Namespace("rtl").GlobalNamespace())
319 && fdecl->getNumParams() == 2)
321 // u.endsWithIgnoreAsciiCaseAsciiL("foo", 3) ->
322 // u.endsWithIgnoreAsciiCase("foo"):
323 handleCharLen(
324 expr, 0, 1, fdecl, "rtl::OUString::endsWithIgnoreAsciiCase",
325 TreatEmpty::Error);
326 return true;
328 if ((dc.Function("equalsAscii").Class("OUString").Namespace("rtl")
329 .GlobalNamespace())
330 && fdecl->getNumParams() == 1)
332 // u.equalsAscii("foo") -> u == "foo":
333 handleChar(
334 expr, 0, fdecl, "operator ==", TreatEmpty::CheckEmpty, false);
335 return true;
337 if ((dc.Function("equalsAsciiL").Class("OUString").Namespace("rtl")
338 .GlobalNamespace())
339 && fdecl->getNumParams() == 2)
341 // u.equalsAsciiL("foo", 3) -> u == "foo":
342 handleCharLen(expr, 0, 1, fdecl, "operator ==", TreatEmpty::CheckEmpty);
343 return true;
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"))
357 return true;
359 handleChar(
360 expr, 0, fdecl, "rtl::OUString::equalsIgnoreAsciiCase",
361 TreatEmpty::CheckEmpty, false);
362 return true;
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"))
375 return true;
377 handleCharLen(
378 expr, 0, 1, fdecl, "rtl::OUString::equalsIgnoreAsciiCase",
379 TreatEmpty::CheckEmpty);
380 return true;
382 if ((dc.Function("indexOfAsciiL").Class("OUString").Namespace("rtl")
383 .GlobalNamespace())
384 && fdecl->getNumParams() == 3)
386 assert(expr->getNumArgs() == 3);
387 // u.indexOfAsciiL("foo", 3, i) -> u.indexOf("foo", i):
388 handleCharLen(
389 expr, 0, 1, fdecl, "rtl::OUString::indexOf", TreatEmpty::Error);
390 return true;
392 if ((dc.Function("lastIndexOfAsciiL").Class("OUString").Namespace("rtl")
393 .GlobalNamespace())
394 && fdecl->getNumParams() == 2)
396 // u.lastIndexOfAsciiL("foo", 3) -> u.lastIndexOf("foo"):
397 handleCharLen(
398 expr, 0, 1, fdecl, "rtl::OUString::lastIndexOf", TreatEmpty::Error);
399 return true;
401 if ((dc.Function("matchAsciiL").Class("OUString").Namespace("rtl")
402 .GlobalNamespace())
403 && fdecl->getNumParams() == 3)
405 assert(expr->getNumArgs() == 3);
406 // u.matchAsciiL("foo", 3, i) -> u.match("foo", i):
407 handleCharLen(
408 expr, 0, 1, fdecl,
409 (isZero(expr->getArg(2))
410 ? std::string("rtl::OUString::startsWith")
411 : std::string("rtl::OUString::match")),
412 TreatEmpty::Error);
413 return true;
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):
422 handleCharLen(
423 expr, 0, 1, fdecl,
424 (isZero(expr->getArg(2))
425 ? std::string("rtl::OUString::startsWithIgnoreAsciiCase")
426 : std::string("rtl::OUString::matchIgnoreAsciiCase")),
427 TreatEmpty::Error);
428 return true;
430 if ((dc.Function("reverseCompareToAsciiL").Class("OUString")
431 .Namespace("rtl").GlobalNamespace())
432 && fdecl->getNumParams() == 2)
434 // u.reverseCompareToAsciiL("foo", 3) -> u.reverseCompareTo("foo"):
435 handleCharLen(
436 expr, 0, 1, fdecl, "rtl::OUString::reverseCompareTo",
437 TreatEmpty::Error);
438 return true;
440 if ((dc.Function("reverseCompareTo").Class("OUString").Namespace("rtl")
441 .GlobalNamespace())
442 && fdecl->getNumParams() == 1)
444 handleOUStringCtor(expr, 0, fdecl, false);
445 return true;
447 if ((dc.Function("equalsIgnoreAsciiCase").Class("OUString").Namespace("rtl")
448 .GlobalNamespace())
449 && fdecl->getNumParams() == 1)
451 handleOUStringCtor(expr, 0, fdecl, false);
452 return true;
454 if ((dc.Function("match").Class("OUString").Namespace("rtl")
455 .GlobalNamespace())
456 && fdecl->getNumParams() == 2)
458 handleOUStringCtor(expr, 0, fdecl, false);
459 return true;
461 if ((dc.Function("matchIgnoreAsciiCase").Class("OUString").Namespace("rtl")
462 .GlobalNamespace())
463 && fdecl->getNumParams() == 2)
465 handleOUStringCtor(expr, 0, fdecl, false);
466 return true;
468 if ((dc.Function("startsWith").Class("OUString").Namespace("rtl")
469 .GlobalNamespace())
470 && fdecl->getNumParams() == 2)
472 handleOUStringCtor(expr, 0, fdecl, false);
473 return true;
475 if ((dc.Function("startsWithIgnoreAsciiCase").Class("OUString")
476 .Namespace("rtl").GlobalNamespace())
477 && fdecl->getNumParams() == 2)
479 handleOUStringCtor(expr, 0, fdecl, false);
480 return true;
482 if ((dc.Function("endsWith").Class("OUString").Namespace("rtl")
483 .GlobalNamespace())
484 && fdecl->getNumParams() == 2)
486 handleOUStringCtor(expr, 0, fdecl, false);
487 return true;
489 if ((dc.Function("endsWithIgnoreAsciiCase").Class("OUString")
490 .Namespace("rtl").GlobalNamespace())
491 && fdecl->getNumParams() == 2)
493 handleOUStringCtor(expr, 0, fdecl, false);
494 return true;
496 if ((dc.Function("indexOf").Class("OUString").Namespace("rtl")
497 .GlobalNamespace())
498 && fdecl->getNumParams() == 2)
500 handleOUStringCtor(expr, 0, fdecl, false);
501 return true;
503 if ((dc.Function("lastIndexOf").Class("OUString").Namespace("rtl")
504 .GlobalNamespace())
505 && fdecl->getNumParams() == 1)
507 handleOUStringCtor(expr, 0, fdecl, false);
508 return true;
510 if ((dc.Function("replaceFirst").Class("OUString").Namespace("rtl")
511 .GlobalNamespace())
512 && fdecl->getNumParams() == 3)
514 handleOUStringCtor(expr, 0, fdecl, false);
515 handleOUStringCtor(expr, 1, fdecl, false);
516 return true;
518 if ((dc.Function("replaceAll").Class("OUString").Namespace("rtl")
519 .GlobalNamespace())
520 && (fdecl->getNumParams() == 2 || fdecl->getNumParams() == 3))
522 handleOUStringCtor(expr, 0, fdecl, false);
523 handleOUStringCtor(expr, 1, fdecl, false);
524 return true;
526 if ((dc.Operator(OO_PlusEqual).Class("OUString").Namespace("rtl")
527 .GlobalNamespace())
528 && fdecl->getNumParams() == 1)
530 handleOUStringCtor(
531 expr, dyn_cast<CXXOperatorCallExpr>(expr) == nullptr ? 0 : 1,
532 fdecl, false);
533 return true;
535 if ((dc.Function("equals").Class("OUString").Namespace("rtl")
536 .GlobalNamespace())
537 && fdecl->getNumParams() == 1)
539 unsigned n;
540 bool nonArray;
541 ContentKind cont;
542 bool emb;
543 bool trm;
544 if (!isStringConstant(
545 expr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
546 &emb, &trm))
548 return true;
550 if (cont != ContentKind::Ascii) {
551 report(
552 DiagnosticsEngine::Warning,
553 ("call of '%0' with string constant argument containing"
554 " non-ASCII characters"),
555 expr->getExprLoc())
556 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
558 if (emb) {
559 report(
560 DiagnosticsEngine::Warning,
561 ("call of '%0' with string constant argument containing"
562 " embedded NULLs"),
563 expr->getExprLoc())
564 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
566 if (n == 0) {
567 report(
568 DiagnosticsEngine::Warning,
569 ("rewrite call of '%0' with empty string constant argument as"
570 " call of 'rtl::OUString::isEmpty'"),
571 expr->getExprLoc())
572 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
573 return true;
576 if (dc.Operator(OO_EqualEqual).Namespace("rtl").GlobalNamespace()
577 && fdecl->getNumParams() == 2)
579 for (unsigned i = 0; i != 2; ++i) {
580 unsigned n;
581 bool nonArray;
582 ContentKind cont;
583 bool emb;
584 bool trm;
585 if (!isStringConstant(
586 expr->getArg(i)->IgnoreParenImpCasts(), &n, &nonArray,
587 &cont, &emb, &trm))
589 continue;
591 if (cont != ContentKind::Ascii) {
592 report(
593 DiagnosticsEngine::Warning,
594 ("call of '%0' with string constant argument containing"
595 " non-ASCII characters"),
596 expr->getExprLoc())
597 << fdecl->getQualifiedNameAsString()
598 << expr->getSourceRange();
600 if (emb) {
601 report(
602 DiagnosticsEngine::Warning,
603 ("call of '%0' with string constant argument containing"
604 " embedded NULLs"),
605 expr->getExprLoc())
606 << fdecl->getQualifiedNameAsString()
607 << expr->getSourceRange();
609 if (n == 0) {
610 report(
611 DiagnosticsEngine::Warning,
612 ("rewrite call of '%0' with empty string constant argument"
613 " as call of 'rtl::OUString::isEmpty'"),
614 expr->getExprLoc())
615 << fdecl->getQualifiedNameAsString()
616 << expr->getSourceRange();
619 return true;
621 if (dc.Operator(OO_ExclaimEqual).Namespace("rtl").GlobalNamespace()
622 && fdecl->getNumParams() == 2)
624 for (unsigned i = 0; i != 2; ++i) {
625 unsigned n;
626 bool nonArray;
627 ContentKind cont;
628 bool emb;
629 bool trm;
630 if (!isStringConstant(
631 expr->getArg(i)->IgnoreParenImpCasts(), &n, &nonArray,
632 &cont, &emb, &trm))
634 continue;
636 if (cont != ContentKind::Ascii) {
637 report(
638 DiagnosticsEngine::Warning,
639 ("call of '%0' with string constant argument containing"
640 " non-ASCII characters"),
641 expr->getExprLoc())
642 << fdecl->getQualifiedNameAsString()
643 << expr->getSourceRange();
645 if (emb) {
646 report(
647 DiagnosticsEngine::Warning,
648 ("call of '%0' with string constant argument containing"
649 " embedded NULLs"),
650 expr->getExprLoc())
651 << fdecl->getQualifiedNameAsString()
652 << expr->getSourceRange();
654 if (n == 0) {
655 report(
656 DiagnosticsEngine::Warning,
657 ("rewrite call of '%0' with empty string constant argument"
658 " as call of '!rtl::OUString::isEmpty'"),
659 expr->getExprLoc())
660 << fdecl->getQualifiedNameAsString()
661 << expr->getSourceRange();
664 return true;
666 if (dc.Operator(OO_Equal).Namespace("rtl").GlobalNamespace()
667 && fdecl->getNumParams() == 1)
669 unsigned n;
670 bool nonArray;
671 ContentKind cont;
672 bool emb;
673 bool trm;
674 if (!isStringConstant(
675 expr->getArg(1)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
676 &emb, &trm))
678 return true;
680 if (cont != ContentKind::Ascii) {
681 report(
682 DiagnosticsEngine::Warning,
683 ("call of '%0' with string constant argument containing"
684 " non-ASCII characters"),
685 expr->getExprLoc())
686 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
688 if (emb) {
689 report(
690 DiagnosticsEngine::Warning,
691 ("call of '%0' with string constant argument containing"
692 " embedded NULLs"),
693 expr->getExprLoc())
694 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
696 if (n == 0) {
697 report(
698 DiagnosticsEngine::Warning,
699 ("rewrite call of '%0' with empty string constant argument as"
700 " call of 'rtl::OUString::clear'"),
701 expr->getExprLoc())
702 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
703 return true;
705 return true;
707 if ((dc.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
708 .GlobalNamespace())
709 && fdecl->getNumParams() == 1)
711 // u.appendAscii("foo") -> u.append("foo")
712 handleChar(
713 expr, 0, fdecl, "rtl::OUStringBuffer::append", TreatEmpty::Error,
714 true, "appendAscii", "append");
715 return true;
717 if ((dc.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
718 .GlobalNamespace())
719 && fdecl->getNumParams() == 2)
721 // u.appendAscii("foo", 3) -> u.append("foo"):
722 handleCharLen(
723 expr, 0, 1, fdecl, "rtl::OUStringBuffer::append",
724 TreatEmpty::Error);
725 return true;
727 if (dc.Function("append").Class("OStringBuffer").Namespace("rtl")
728 .GlobalNamespace())
730 switch (fdecl->getNumParams()) {
731 case 1:
732 handleFunArgOstring(expr, 0, fdecl);
733 break;
734 case 2:
736 // b.append("foo", 3) -> b.append("foo"):
737 auto file = getFileNameOfSpellingLoc(
738 compiler.getSourceManager().getSpellingLoc(
739 compat::getBeginLoc(expr)));
740 if (loplugin::isSamePathname(
741 file,
742 SRCDIR "/sal/qa/OStringBuffer/rtl_OStringBuffer.cxx"))
744 return true;
746 handleCharLen(
747 expr, 0, 1, fdecl, "rtl::OStringBuffer::append",
748 TreatEmpty::Error);
750 break;
751 default:
752 break;
754 return true;
756 if (dc.Function("insert").Class("OStringBuffer").Namespace("rtl")
757 .GlobalNamespace())
759 switch (fdecl->getNumParams()) {
760 case 2:
761 handleFunArgOstring(expr, 1, fdecl);
762 break;
763 case 3:
765 // b.insert(i, "foo", 3) -> b.insert(i, "foo"):
766 handleCharLen(
767 expr, 1, 2, fdecl, "rtl::OStringBuffer::insert",
768 TreatEmpty::Error);
769 break;
771 default:
772 break;
774 return true;
776 return true;
779 bool StringConstant::VisitCXXConstructExpr(CXXConstructExpr const * expr) {
780 if (ignoreLocation(expr)) {
781 return true;
783 auto classdecl = expr->getConstructor()->getParent();
784 if (loplugin::DeclCheck(classdecl)
785 .Class("OUString").Namespace("rtl").GlobalNamespace())
787 ChangeKind kind;
788 PassThrough pass;
789 bool simplify;
790 switch (expr->getConstructor()->getNumParams()) {
791 case 1:
792 if (!loplugin::TypeCheck(
793 expr->getConstructor()->getParamDecl(0)->getType())
794 .Typedef("sal_Unicode").GlobalNamespace())
796 return true;
798 kind = ChangeKind::SingleChar;
799 pass = PassThrough::NonEmptyConstantString;
800 simplify = false;
801 break;
802 case 2:
804 auto arg = expr->getArg(0);
805 if (loplugin::TypeCheck(arg->getType())
806 .Class("OUStringLiteral1_").Namespace("rtl")
807 .GlobalNamespace())
809 kind = ChangeKind::OUStringLiteral1;
810 pass = PassThrough::NonEmptyConstantString;
811 simplify = false;
812 } else {
813 unsigned n;
814 bool nonArray;
815 ContentKind cont;
816 bool emb;
817 bool trm;
818 if (!isStringConstant(
819 arg->IgnoreParenImpCasts(), &n, &nonArray, &cont,
820 &emb, &trm))
822 return true;
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())
831 == "OSL_THIS_FUNC"))
833 return true;
835 if (cont != ContentKind::Ascii) {
836 report(
837 DiagnosticsEngine::Warning,
838 ("construction of %0 with string constant argument"
839 " containing non-ASCII characters"),
840 expr->getExprLoc())
841 << classdecl << expr->getSourceRange();
843 if (emb) {
844 report(
845 DiagnosticsEngine::Warning,
846 ("construction of %0 with string constant argument"
847 " containing embedded NULLs"),
848 expr->getExprLoc())
849 << classdecl << expr->getSourceRange();
851 kind = ChangeKind::Char;
852 pass = n == 0
853 ? PassThrough::EmptyConstantString
854 : PassThrough::NonEmptyConstantString;
855 simplify = false;
857 break;
859 case 4:
861 unsigned n;
862 bool nonArray;
863 ContentKind cont;
864 bool emb;
865 bool trm;
866 std::vector<char32_t> utf8Cont;
867 if (!isStringConstant(
868 expr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray,
869 &cont, &emb, &trm, &utf8Cont))
871 return true;
873 APSInt res;
874 if (!compat::EvaluateAsInt(expr->getArg(1),
875 res, compiler.getASTContext()))
877 return true;
879 if (res != n) {
880 report(
881 DiagnosticsEngine::Warning,
882 ("suspicious 'rtl::OUString' constructor with literal"
883 " of length %0 and non-matching length argument %1"),
884 expr->getExprLoc())
885 << n << res.toString(10) << expr->getSourceRange();
886 return true;
888 APSInt enc;
889 if (!compat::EvaluateAsInt(expr->getArg(2),
890 enc, compiler.getASTContext()))
892 return true;
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
900 return true;
902 if (!encIsAscii && cont == ContentKind::Ascii) {
903 report(
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();
910 return true;
912 if (encIsUtf8) {
913 if (cont == ContentKind::Arbitrary) {
914 report(
915 DiagnosticsEngine::Warning,
916 ("suspicious 'rtl::OUString' constructor with text"
917 " encoding 'RTL_TEXTENCODING_UTF8' but non-UTF-8"
918 " content"),
919 expr->getArg(0)->getExprLoc())
920 << expr->getSourceRange();
921 } else {
922 assert(cont == ContentKind::Utf8);
923 //TODO: keep original content as much as possible
924 std::ostringstream s;
925 for (auto const c: utf8Cont) {
926 if (c == '\\') {
927 s << "\\\\";
928 } else if (c == '"') {
929 s << "\\\"";
930 } else if (c == '\a') {
931 s << "\\a";
932 } else if (c == '\b') {
933 s << "\\b";
934 } else if (c == '\f') {
935 s << "\\f";
936 } else if (c == '\n') {
937 s << "\\n";
938 } else if (c == '\r') {
939 s << "\\r";
940 } else if (c == '\t') {
941 s << "\\r";
942 } else if (c == '\v') {
943 s << "\\v";
944 } else if (c <= 0x1F || c == 0x7F) {
945 s << "\\x" << std::oct << std::setw(3)
946 << std::setfill('0')
947 << static_cast<std::uint_least32_t>(c);
948 } else if (c < 0x7F) {
949 s << char(c);
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);
954 } else {
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);
961 report(
962 DiagnosticsEngine::Warning,
963 ("simplify construction of %0 with UTF-8 content as"
964 " OUString(u\"%1\")"),
965 expr->getExprLoc())
966 << classdecl << s.str() << expr->getSourceRange();
969 return true;
971 if (cont != ContentKind::Ascii || emb) {
972 // cf. remaining uses of RTL_CONSTASCII_USTRINGPARAM
973 return true;
975 kind = ChangeKind::Char;
976 pass = n == 0
977 ? PassThrough::EmptyConstantString
978 : PassThrough::NonEmptyConstantString;
979 simplify = true;
980 break;
982 default:
983 return true;
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();
995 } else {
996 assert(false);
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();
1012 if (e == expr) {
1013 if (isa<CallExpr>(call)) {
1014 FunctionDecl const * fdecl
1015 = cast<CallExpr>(call)->getDirectCallee();
1016 if (fdecl == nullptr) {
1017 break;
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()))
1026 report(
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();
1034 return true;
1036 if (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
1037 .GlobalNamespace())
1039 report(
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();
1047 return true;
1049 if ((dc.Operator(OO_Plus).Namespace("rtl")
1050 .GlobalNamespace())
1051 || (dc.Operator(OO_Plus).Class("OUString")
1052 .Namespace("rtl").GlobalNamespace()))
1054 report(
1055 DiagnosticsEngine::Warning,
1056 ("call of '%0' with suspicious construction"
1057 " of %1 with empty string constant"
1058 " argument"),
1059 getMemberLocation(call))
1060 << fdecl->getQualifiedNameAsString()
1061 << classdecl << call->getSourceRange();
1062 return true;
1064 if (dc.Operator(OO_Equal).Class("OUString")
1065 .Namespace("rtl").GlobalNamespace())
1067 report(
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();
1075 return true;
1077 } else {
1078 assert(pass == PassThrough::NonEmptyConstantString);
1079 if (dc.Function("equals").Class("OUString")
1080 .Namespace("rtl").GlobalNamespace())
1082 report(
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();
1090 return true;
1092 if ((dc.Operator(OO_Plus).Namespace("rtl")
1093 .GlobalNamespace())
1094 || (dc.Operator(OO_Plus).Class("OUString")
1095 .Namespace("rtl").GlobalNamespace())
1096 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1097 .GlobalNamespace())
1098 || (dc.Operator(OO_ExclaimEqual)
1099 .Namespace("rtl").GlobalNamespace()))
1101 if (dc.Operator(OO_Plus).Namespace("rtl")
1102 .GlobalNamespace())
1104 auto file = getFileNameOfSpellingLoc(
1105 compiler.getSourceManager()
1106 .getSpellingLoc(
1107 compat::getBeginLoc(expr)));
1108 if (loplugin::isSamePathname(
1109 file,
1110 (SRCDIR
1111 "/sal/qa/rtl/strings/test_ostring_concat.cxx"))
1112 || loplugin::isSamePathname(
1113 file,
1114 (SRCDIR
1115 "/sal/qa/rtl/strings/test_oustring_concat.cxx")))
1117 return true;
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"))
1134 return true;
1136 if (kind == ChangeKind::SingleChar) {
1137 report(
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();
1146 } else {
1147 report(
1148 DiagnosticsEngine::Warning,
1149 ("elide construction of %0 with %1 in"
1150 " call of '%2'"),
1151 getMemberLocation(expr))
1152 << classdecl << describeChangeKind(kind)
1153 << fdecl->getQualifiedNameAsString()
1154 << expr->getSourceRange();
1156 return true;
1159 } else if (isa<CXXConstructExpr>(call)) {
1160 } else {
1161 assert(false);
1166 if (simplify) {
1167 report(
1168 DiagnosticsEngine::Warning,
1169 "simplify construction of %0 with %1", expr->getExprLoc())
1170 << classdecl << describeChangeKind(kind)
1171 << expr->getSourceRange();
1173 return true;
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);
1207 return true;
1210 std::string StringConstant::describeChangeKind(ChangeKind kind) {
1211 switch (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";
1220 default:
1221 std::abort();
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())) {
1244 return false;
1246 expr = e3->getBase()->IgnoreParenImpCasts();
1247 t = expr->getType();
1250 if (!t.isConstQualified()) {
1251 return false;
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();
1263 bool isPtr;
1264 if (loplugin::TypeCheck(t).Pointer().Const().Char()) {
1265 isPtr = true;
1266 } else if (t->isConstantArrayType()
1267 && (loplugin::TypeCheck(
1268 t->getAsArrayTypeUnsafe()->getElementType())
1269 .Char()))
1271 isPtr = false;
1272 } else {
1273 return false;
1275 clang::StringLiteral const * lit = dyn_cast<clang::StringLiteral>(expr);
1276 if (lit != nullptr) {
1277 if (!(lit->isAscii() || lit->isUTF8())) {
1278 return false;
1280 unsigned n = lit->getLength();
1281 ContentKind cont = ContentKind::Ascii;
1282 bool emb = false;
1283 char32_t val = 0;
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]);
1289 if (c == '\0') {
1290 emb = true;
1292 switch (s) {
1293 case Utf8State::Start:
1294 if (c >= 0x80) {
1295 if (c >= 0xC2 && c <= 0xDF) {
1296 val = c & 0x1F;
1297 s = Utf8State::Trail1;
1298 } else if (c == 0xE0) {
1299 val = c & 0x0F;
1300 s = Utf8State::E0;
1301 } else if ((c >= 0xE1 && c <= 0xEA)
1302 || (c >= 0xEE && c <= 0xEF))
1304 val = c & 0x0F;
1305 s = Utf8State::Trail2;
1306 } else if (c == 0xEB) {
1307 val = c & 0x0F;
1308 s = Utf8State::EB;
1309 } else if (c == 0xF0) {
1310 val = c & 0x03;
1311 s = Utf8State::F0;
1312 } else if (c >= 0xF1 && c <= 0xF3) {
1313 val = c & 0x03;
1314 s = Utf8State::Trail3;
1315 } else if (c == 0xF4) {
1316 val = c & 0x03;
1317 s = Utf8State::F4;
1318 } else {
1319 cont = ContentKind::Arbitrary;
1321 } else if (utf8Content != nullptr
1322 && cont != ContentKind::Arbitrary)
1324 utf8Content->push_back(c);
1326 break;
1327 case Utf8State::E0:
1328 if (c >= 0xA0 && c <= 0xBF) {
1329 val = (val << 6) | (c & 0x3F);
1330 s = Utf8State::Trail1;
1331 } else {
1332 cont = ContentKind::Arbitrary;
1333 s = Utf8State::Start;
1335 break;
1336 case Utf8State::EB:
1337 if (c >= 0x80 && c <= 0x9F) {
1338 val = (val << 6) | (c & 0x3F);
1339 s = Utf8State::Trail1;
1340 } else {
1341 cont = ContentKind::Arbitrary;
1342 s = Utf8State::Start;
1344 break;
1345 case Utf8State::F0:
1346 if (c >= 0x90 && c <= 0xBF) {
1347 val = (val << 6) | (c & 0x3F);
1348 s = Utf8State::Trail2;
1349 } else {
1350 cont = ContentKind::Arbitrary;
1351 s = Utf8State::Start;
1353 break;
1354 case Utf8State::F4:
1355 if (c >= 0x80 && c <= 0x8F) {
1356 val = (val << 6) | (c & 0x3F);
1357 s = Utf8State::Trail2;
1358 } else {
1359 cont = ContentKind::Arbitrary;
1360 s = Utf8State::Start;
1362 break;
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));
1369 val = 0;
1371 } else {
1372 cont = ContentKind::Arbitrary;
1374 s = Utf8State::Start;
1375 break;
1376 case Utf8State::Trail2:
1377 if (c >= 0x80 && c <= 0xBF) {
1378 val = (val << 6) | (c & 0x3F);
1379 s = Utf8State::Trail1;
1380 } else {
1381 cont = ContentKind::Arbitrary;
1382 s = Utf8State::Start;
1384 break;
1385 case Utf8State::Trail3:
1386 if (c >= 0x80 && c <= 0xBF) {
1387 val = (val << 6) | (c & 0x3F);
1388 s = Utf8State::Trail2;
1389 } else {
1390 cont = ContentKind::Arbitrary;
1391 s = Utf8State::Start;
1393 break;
1396 *size = n;
1397 *nonArray = isPtr;
1398 *content = cont;
1399 *embeddedNuls = emb;
1400 *terminatingNul = true;
1401 return true;
1403 APValue v;
1404 if (!expr->isCXX11ConstantExpr(compiler.getASTContext(), &v)) {
1405 return false;
1407 switch (v.getKind()) {
1408 case APValue::LValue:
1410 Expr const * e = v.getLValueBase().dyn_cast<Expr const *>();
1411 if (e == nullptr) {
1412 return false;
1414 if (!v.getLValueOffset().isZero()) {
1415 return false; //TODO
1417 Expr const * e2 = e->IgnoreParenImpCasts();
1418 if (e2 != e) {
1419 return isStringConstant(
1420 e2, size, nonArray, content, embeddedNuls, terminatingNul);
1422 //TODO: string literals are represented as recursive LValues???
1423 llvm::APInt n
1424 = compiler.getASTContext().getAsConstantArrayType(t)->getSize();
1425 assert(n != 0);
1426 --n;
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;
1433 return true;
1435 case APValue::Array:
1437 if (v.hasArrayFiller()) { //TODO: handle final NULL filler?
1438 return false;
1440 unsigned n = v.getArraySize();
1441 assert(n != 0);
1442 ContentKind cont = ContentKind::Ascii;
1443 bool emb = false;
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?
1448 return false;
1450 APSInt iv = e.getInt();
1451 if (iv == 0) {
1452 emb = true;
1453 } else if (iv.uge(0x80)) {
1454 cont = ContentKind::Arbitrary;
1457 APValue e(v.getArrayInitializedElt(n - 1));
1458 if (!e.isInt()) { //TODO: assert?
1459 return false;
1461 bool trm = e.getInt() == 0;
1462 *size = trm ? n - 1 : n;
1463 *nonArray = isPtr;
1464 *content = cont;
1465 *embeddedNuls = emb;
1466 *terminatingNul = trm;
1467 return true;
1469 default:
1470 assert(false); //TODO???
1471 return false;
1475 bool StringConstant::isZero(Expr const * expr) {
1476 APSInt res;
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();
1496 } else {
1497 assert(false);
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();
1505 if (e == expr) {
1506 if (isa<CallExpr>(call)) {
1507 FunctionDecl const * fdecl
1508 = cast<CallExpr>(call)->getDirectCallee();
1509 if (fdecl == nullptr) {
1510 break;
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()))
1519 report(
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();
1527 return;
1529 if (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
1530 .GlobalNamespace())
1532 report(
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();
1540 return;
1542 if ((dc.Operator(OO_Plus).Namespace("rtl")
1543 .GlobalNamespace())
1544 || (dc.Operator(OO_Plus).Class("OUString")
1545 .Namespace("rtl").GlobalNamespace()))
1547 report(
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();
1554 return;
1556 if (dc.Operator(OO_Equal).Class("OUString")
1557 .Namespace("rtl").GlobalNamespace())
1559 report(
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();
1567 return;
1569 report(
1570 DiagnosticsEngine::Warning,
1571 "TODO call inside %0", getMemberLocation(expr))
1572 << fdecl->getQualifiedNameAsString()
1573 << expr->getSourceRange();
1574 return;
1575 } else {
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")
1582 .GlobalNamespace())
1583 || (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
1584 .GlobalNamespace()))
1586 report(
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();
1593 return;
1595 report(
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();
1603 return;
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) {
1615 report(
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();
1623 } else {
1624 assert(pass == PassThrough::NonEmptyConstantString);
1625 report(
1626 DiagnosticsEngine::Warning,
1627 ("elide call of %0 with %1 in construction of"
1628 " %2"),
1629 getMemberLocation(expr))
1630 << original << describeChangeKind(kind)
1631 << classdecl << expr->getSourceRange();
1633 return;
1635 } else {
1636 assert(false);
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)
1652 == rewriteFrom)
1653 && replaceText(loc, n, rewriteTo))
1655 return;
1658 report(
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);
1671 if (size == 0) {
1672 switch (treatEmpty) {
1673 case TreatEmpty::DefaultCtor:
1674 *replacement = "rtl::OUString default constructor";
1675 break;
1676 case TreatEmpty::CheckEmpty:
1677 *replacement = "rtl::OUString::isEmpty";
1678 break;
1679 case TreatEmpty::Error:
1680 report(
1681 DiagnosticsEngine::Warning,
1682 "call of '%0' with suspicious empty string constant argument",
1683 getMemberLocation(expr))
1684 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1685 break;
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)
1695 unsigned n;
1696 bool nonArray;
1697 ContentKind cont;
1698 bool emb;
1699 bool trm;
1700 if (!isStringConstant(
1701 expr->getArg(arg)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
1702 &emb, &trm))
1704 return;
1706 if (cont != ContentKind::Ascii) {
1707 report(
1708 DiagnosticsEngine::Warning,
1709 ("call of '%0' with string constant argument containing non-ASCII"
1710 " characters"),
1711 getMemberLocation(expr))
1712 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1713 return;
1715 if (emb) {
1716 report(
1717 DiagnosticsEngine::Warning,
1718 ("call of '%0' with string constant argument containing embedded"
1719 " NULLs"),
1720 getMemberLocation(expr))
1721 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1722 return;
1724 if (!trm) {
1725 report(
1726 DiagnosticsEngine::Warning,
1727 ("call of '%0' with string constant argument lacking a terminating"
1728 " NULL"),
1729 getMemberLocation(expr))
1730 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1731 return;
1733 std::string repl(replacement);
1734 checkEmpty(expr, callee, treatEmpty, n, &repl);
1735 reportChange(
1736 expr, ChangeKind::Char, callee->getQualifiedNameAsString(), repl,
1737 (literal
1738 ? (n == 0
1739 ? PassThrough::EmptyConstantString
1740 : PassThrough::NonEmptyConstantString)
1741 : PassThrough::No),
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):
1755 unsigned n;
1756 bool nonArray;
1757 ContentKind cont;
1758 bool emb;
1759 bool trm;
1760 if (!(isStringConstant(
1761 expr->getArg(arg1)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
1762 &emb, &trm)
1763 && trm))
1765 return;
1767 APSInt res;
1768 if (compat::EvaluateAsInt(expr->getArg(arg2), res, compiler.getASTContext())) {
1769 if (res != n) {
1770 return;
1772 } else {
1773 UnaryOperator const * op = dyn_cast<UnaryOperator>(
1774 expr->getArg(arg1)->IgnoreParenImpCasts());
1775 if (op == nullptr || op->getOpcode() != UO_AddrOf) {
1776 return;
1778 ArraySubscriptExpr const * subs = dyn_cast<ArraySubscriptExpr>(
1779 op->getSubExpr()->IgnoreParenImpCasts());
1780 if (subs == nullptr) {
1781 return;
1783 unsigned n2;
1784 bool nonArray2;
1785 ContentKind cont2;
1786 bool emb2;
1787 bool trm2;
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())
1794 && res == 0))
1796 return;
1799 if (cont != ContentKind::Ascii) {
1800 report(
1801 DiagnosticsEngine::Warning,
1802 ("call of '%0' with string constant argument containing non-ASCII"
1803 " characters"),
1804 getMemberLocation(expr))
1805 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1807 if (emb) {
1808 return;
1810 std::string repl(replacement);
1811 checkEmpty(expr, callee, treatEmpty, n, &repl);
1812 reportChange(
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) {
1853 return;
1855 } else {
1856 e0 = e1->getSubExpr()->IgnoreParenImpCasts();
1858 auto e2 = dyn_cast<CXXBindTemporaryExpr>(e0);
1859 if (e2 == nullptr) {
1860 return;
1862 auto e3 = dyn_cast<CXXConstructExpr>(
1863 e2->getSubExpr()->IgnoreParenImpCasts());
1864 if (e3 == nullptr) {
1865 return;
1867 if (!loplugin::DeclCheck(e3->getConstructor()).MemberFunction()
1868 .Class(stringKind == StringKind::Unicode ? "OUString" : "OString").Namespace("rtl").GlobalNamespace())
1870 return;
1872 if (e3->getNumArgs() == 0) {
1873 report(
1874 DiagnosticsEngine::Warning,
1875 ("in call of '%0', replace default-constructed 'OUString' with an"
1876 " empty string literal"),
1877 e3->getExprLoc())
1878 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1879 return;
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) {
1890 report(
1891 DiagnosticsEngine::Warning,
1892 ("in call of '%0', replace 'OUString' constructed from a"
1893 " 'sal_Unicode' with an 'OUStringLiteral1'"),
1894 e3->getExprLoc())
1895 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1897 return;
1899 if (e3->getNumArgs() != 2) {
1900 return;
1902 unsigned n;
1903 bool nonArray;
1904 ContentKind cont;
1905 bool emb;
1906 bool trm;
1907 if (!isStringConstant(
1908 e3->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray, &cont, &emb,
1909 &trm))
1911 return;
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());
1924 if (!first) {
1925 StringRef s(
1926 compiler.getSourceManager().getCharacterData(loc2), n);
1927 while (s.startswith("\\\n")) {
1928 s = s.drop_front(2);
1929 while (!s.empty()
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("//")
1938 || s == "\\"))
1940 break;
1943 loc2 = loc2.getLocWithOffset(std::max<unsigned>(n, 1));
1945 auto loc3 = range.getEnd();
1946 for (;;) {
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);
1955 while (!s.empty()
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("//")
1964 || s == "\\"))
1966 break;
1968 loc3 = l;
1970 if (removeText(CharSourceRange(SourceRange(loc1, loc2), false))) {
1971 if (removeText(SourceRange(loc3, range.getEnd()))) {
1972 return;
1974 report(DiagnosticsEngine::Fatal, "Corrupt rewrite", loc3)
1975 << expr->getSourceRange();
1976 return;
1980 report(
1981 DiagnosticsEngine::Warning,
1982 ("in call of '%0', replace 'OUString' constructed from a string literal"
1983 " directly with the string literal"),
1984 e3->getExprLoc())
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();
1992 unsigned n;
1993 bool nonArray;
1994 ContentKind cont;
1995 bool emb;
1996 bool trm;
1997 if (isStringConstant(argExpr, &n, &nonArray, &cont, &emb, &trm)) {
1998 if (cont != ContentKind::Ascii || emb) {
1999 return;
2001 if (!trm) {
2002 report(
2003 DiagnosticsEngine::Warning,
2004 ("call of '%0' with string constant argument lacking a"
2005 " terminating NULL"),
2006 getMemberLocation(expr))
2007 << callee->getQualifiedNameAsString() << expr->getSourceRange();
2008 return;
2010 std::string repl;
2011 checkEmpty(expr, callee, TreatEmpty::Error, n, &repl);
2012 if (nonArray) {
2013 report(
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")
2023 .GlobalNamespace())
2025 switch (cexpr->getConstructor()->getNumParams()) {
2026 case 0:
2027 report(
2028 DiagnosticsEngine::Warning,
2029 ("in call of '%0', replace empty %1 constructor with empty"
2030 " string literal"),
2031 cexpr->getLocation())
2032 << callee->getQualifiedNameAsString() << classdecl
2033 << expr->getSourceRange();
2034 break;
2035 case 2:
2036 if (isStringConstant(
2037 cexpr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray,
2038 &cont, &emb, &trm))
2040 APSInt res;
2041 if (compat::EvaluateAsInt(cexpr->getArg(1),
2042 res, compiler.getASTContext()))
2044 if (res == n && !emb && trm) {
2045 report(
2046 DiagnosticsEngine::Warning,
2047 ("in call of '%0', elide explicit %1"
2048 " constructor%2"),
2049 cexpr->getLocation())
2050 << callee->getQualifiedNameAsString()
2051 << classdecl << adviseNonArray(nonArray)
2052 << expr->getSourceRange();
2054 } else {
2055 if (emb) {
2056 report(
2057 DiagnosticsEngine::Warning,
2058 ("call of %0 constructor with string constant"
2059 " argument containing embedded NULLs"),
2060 cexpr->getLocation())
2061 << classdecl << cexpr->getSourceRange();
2062 return;
2064 if (!trm) {
2065 report(
2066 DiagnosticsEngine::Warning,
2067 ("call of %0 constructor with string constant"
2068 " argument lacking a terminating NULL"),
2069 cexpr->getLocation())
2070 << classdecl << cexpr->getSourceRange();
2071 return;
2073 report(
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();
2082 break;
2083 default:
2084 break;
2090 loplugin::Plugin::Registration< StringConstant > X("stringconstant", true);
2094 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */