null-deref seen in testing
[LibreOffice.git] / compilerplugins / clang / stringconstant.cxx
blob344125dd4df0e0cb59dd6b388384123ac5028140
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 "compat.hxx"
24 #include "plugin.hxx"
26 // Define a "string constant" to be a constant expression either of type "array
27 // of N char" where each array element is a non-NULL ASCII character---except
28 // that the last array element may be NULL, or, in some situations, of type char
29 // with an ASCII value (including NULL). Note that the former includes
30 // expressions denoting narrow string literals like "foo", and, with toolchains
31 // that support constexpr, constexpr variables declared like
33 // constexpr char str[] = "bar";
35 // This plugin flags uses of OUString functions with string constant arguments
36 // that can be rewritten more directly, like
38 // OUString::createFromAscii("foo") -> "foo"
40 // and
42 // s.equals(OUString("bar")) -> s == "bar"
44 namespace {
46 SourceLocation getMemberLocation(Expr const * expr) {
47 CallExpr const * e1 = dyn_cast<CallExpr>(expr);
48 MemberExpr const * e2 = e1 == nullptr
49 ? nullptr : dyn_cast<MemberExpr>(e1->getCallee());
50 return e2 == nullptr ? expr->getExprLoc()/*TODO*/ : e2->getMemberLoc();
53 bool isLhsOfAssignment(FunctionDecl const * decl, unsigned parameter) {
54 if (parameter != 0) {
55 return false;
57 auto oo = decl->getOverloadedOperator();
58 return oo == OO_Equal
59 || (oo >= OO_PlusEqual && oo <= OO_GreaterGreaterEqual);
62 bool hasOverloads(FunctionDecl const * decl, unsigned arguments) {
63 int n = 0;
64 auto ctx = decl->getDeclContext();
65 if (ctx->getDeclKind() == Decl::LinkageSpec) {
66 ctx = ctx->getParent();
68 auto res = ctx->lookup(decl->getDeclName());
69 for (auto d = res.begin(); d != res.end(); ++d) {
70 FunctionDecl const * f = dyn_cast<FunctionDecl>(*d);
71 if (f != nullptr && f->getMinRequiredArguments() <= arguments
72 && f->getNumParams() >= arguments)
74 auto consDecl = dyn_cast<CXXConstructorDecl>(f);
75 if (consDecl && consDecl->isCopyOrMoveConstructor()) {
76 continue;
78 ++n;
79 if (n == 2) {
80 return true;
84 return false;
87 CXXConstructExpr const * lookForCXXConstructExpr(Expr const * expr) {
88 if (auto e = dyn_cast<MaterializeTemporaryExpr>(expr)) {
89 expr = e->getSubExpr();
91 if (auto e = dyn_cast<CXXFunctionalCastExpr>(expr)) {
92 expr = e->getSubExpr();
94 if (auto e = dyn_cast<CXXBindTemporaryExpr>(expr)) {
95 expr = e->getSubExpr();
97 if (auto const e = dyn_cast<CXXMemberCallExpr>(expr)) {
98 // Look through OString::operator std::string_view:
99 if (isa_and_nonnull<CXXConversionDecl>(e->getCalleeDecl())) {
100 return lookForCXXConstructExpr(e->getImplicitObjectArgument()->IgnoreParenImpCasts());
103 return dyn_cast<CXXConstructExpr>(expr);
106 char const * adviseNonArray(bool nonArray) {
107 return nonArray
108 ? ", and turn the non-array string constant into an array" : "";
111 class StringConstant:
112 public loplugin::FilteringRewritePlugin<StringConstant>
114 public:
115 explicit StringConstant(loplugin::InstantiationData const & data):
116 FilteringRewritePlugin(data) {}
118 void run() override;
120 bool TraverseFunctionDecl(FunctionDecl * decl) {
121 returnTypes_.push(decl->getDeclaredReturnType());
122 auto const ret = RecursiveASTVisitor::TraverseFunctionDecl(decl);
123 assert(!returnTypes_.empty());
124 assert(returnTypes_.top() == decl->getDeclaredReturnType());
125 returnTypes_.pop();
126 return ret;
129 bool TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl * decl) {
130 returnTypes_.push(decl->getDeclaredReturnType());
131 auto const ret = RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(
132 decl);
133 assert(!returnTypes_.empty());
134 assert(returnTypes_.top() == decl->getDeclaredReturnType());
135 returnTypes_.pop();
136 return ret;
139 bool TraverseCXXMethodDecl(CXXMethodDecl * decl) {
140 returnTypes_.push(decl->getDeclaredReturnType());
141 auto const ret = RecursiveASTVisitor::TraverseCXXMethodDecl(decl);
142 assert(!returnTypes_.empty());
143 assert(returnTypes_.top() == decl->getDeclaredReturnType());
144 returnTypes_.pop();
145 return ret;
148 bool TraverseCXXConstructorDecl(CXXConstructorDecl * decl) {
149 returnTypes_.push(decl->getDeclaredReturnType());
150 auto const ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(decl);
151 assert(!returnTypes_.empty());
152 assert(returnTypes_.top() == decl->getDeclaredReturnType());
153 returnTypes_.pop();
154 return ret;
157 bool TraverseCXXDestructorDecl(CXXDestructorDecl * decl) {
158 returnTypes_.push(decl->getDeclaredReturnType());
159 auto const ret = RecursiveASTVisitor::TraverseCXXDestructorDecl(decl);
160 assert(!returnTypes_.empty());
161 assert(returnTypes_.top() == decl->getDeclaredReturnType());
162 returnTypes_.pop();
163 return ret;
166 bool TraverseCXXConversionDecl(CXXConversionDecl * decl) {
167 returnTypes_.push(decl->getDeclaredReturnType());
168 auto const ret = RecursiveASTVisitor::TraverseCXXConversionDecl(decl);
169 assert(!returnTypes_.empty());
170 assert(returnTypes_.top() == decl->getDeclaredReturnType());
171 returnTypes_.pop();
172 return ret;
175 bool TraverseObjCMethodDecl(ObjCMethodDecl * decl) {
176 returnTypes_.push(decl->getReturnType());
177 auto const ret = RecursiveASTVisitor::TraverseObjCMethodDecl(decl);
178 assert(!returnTypes_.empty());
179 assert(returnTypes_.top() == decl->getReturnType());
180 returnTypes_.pop();
181 return ret;
184 bool TraverseCallExpr(CallExpr * expr);
186 bool TraverseCXXMemberCallExpr(CXXMemberCallExpr * expr);
188 bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr * expr);
190 bool TraverseCXXConstructExpr(CXXConstructExpr * expr);
192 bool VisitCallExpr(CallExpr const * expr);
194 bool VisitCXXMemberCallExpr(CXXMemberCallExpr const * expr);
196 bool VisitCXXConstructExpr(CXXConstructExpr const * expr);
198 bool VisitReturnStmt(ReturnStmt const * stmt);
200 private:
201 enum class ContentKind { Ascii, Utf8, Arbitrary };
203 enum class TreatEmpty { DefaultCtor, CheckEmpty, Error };
205 enum class ChangeKind { Char, CharLen, SingleChar, OUStringChar };
207 enum class PassThrough { No, EmptyConstantString, NonEmptyConstantString };
209 std::string describeChangeKind(ChangeKind kind);
211 bool isStringConstant(
212 Expr const * expr, unsigned * size, bool * nonArray,
213 ContentKind * content, bool * embeddedNuls, bool * terminatingNul,
214 std::vector<char32_t> * utf8Content = nullptr);
216 bool isZero(Expr const * expr);
218 void reportChange(
219 Expr const * expr, ChangeKind kind, std::string const & original,
220 std::string const & replacement, PassThrough pass, bool nonArray,
221 char const * rewriteFrom, char const * rewriteTo);
223 void checkEmpty(
224 CallExpr const * expr, FunctionDecl const * callee,
225 TreatEmpty treatEmpty, unsigned size, std::string * replacement);
227 void handleChar(
228 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
229 std::string const & replacement, TreatEmpty treatEmpty, bool literal,
230 char const * rewriteFrom = nullptr, char const * rewriteTo = nullptr);
232 void handleCharLen(
233 CallExpr const * expr, unsigned arg1, unsigned arg2,
234 FunctionDecl const * callee, std::string const & replacement,
235 TreatEmpty treatEmpty);
237 void handleOUStringCtor(
238 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
239 bool explicitFunctionalCastNotation);
241 void handleOStringCtor(
242 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
243 bool explicitFunctionalCastNotation);
245 void handleOUStringCtor(
246 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
247 bool explicitFunctionalCastNotation);
249 void handleOStringCtor(
250 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
251 bool explicitFunctionalCastNotation);
253 enum class StringKind { Unicode, Char };
254 void handleStringCtor(
255 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
256 bool explicitFunctionalCastNotation, StringKind stringKind);
258 void handleFunArgOstring(
259 CallExpr const * expr, unsigned arg, FunctionDecl const * callee);
261 std::stack<QualType> returnTypes_;
262 std::stack<Expr const *> calls_;
265 void StringConstant::run() {
266 if (compiler.getLangOpts().CPlusPlus
267 && compiler.getPreprocessor().getIdentifierInfo(
268 "LIBO_INTERNAL_ONLY")->hasMacroDefinition())
269 //TODO: some parts of it are useful for external code, too
271 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
275 bool StringConstant::TraverseCallExpr(CallExpr * expr) {
276 if (!WalkUpFromCallExpr(expr)) {
277 return false;
279 calls_.push(expr);
280 bool bRes = true;
281 for (auto * e: expr->children()) {
282 if (!TraverseStmt(e)) {
283 bRes = false;
284 break;
287 calls_.pop();
288 return bRes;
291 bool StringConstant::TraverseCXXMemberCallExpr(CXXMemberCallExpr * expr) {
292 if (!WalkUpFromCXXMemberCallExpr(expr)) {
293 return false;
295 calls_.push(expr);
296 bool bRes = true;
297 for (auto * e: expr->children()) {
298 if (!TraverseStmt(e)) {
299 bRes = false;
300 break;
303 calls_.pop();
304 return bRes;
307 bool StringConstant::TraverseCXXOperatorCallExpr(CXXOperatorCallExpr * expr)
309 if (!WalkUpFromCXXOperatorCallExpr(expr)) {
310 return false;
312 calls_.push(expr);
313 bool bRes = true;
314 for (auto * e: expr->children()) {
315 if (!TraverseStmt(e)) {
316 bRes = false;
317 break;
320 calls_.pop();
321 return bRes;
324 bool StringConstant::TraverseCXXConstructExpr(CXXConstructExpr * expr) {
325 if (!WalkUpFromCXXConstructExpr(expr)) {
326 return false;
328 calls_.push(expr);
329 bool bRes = true;
330 for (auto * e: expr->children()) {
331 if (!TraverseStmt(e)) {
332 bRes = false;
333 break;
336 calls_.pop();
337 return bRes;
340 bool StringConstant::VisitCallExpr(CallExpr const * expr) {
341 if (ignoreLocation(expr)) {
342 return true;
344 FunctionDecl const * fdecl = expr->getDirectCallee();
345 if (fdecl == nullptr) {
346 return true;
348 for (unsigned i = 0; i != fdecl->getNumParams(); ++i) {
349 auto t = fdecl->getParamDecl(i)->getType();
350 if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
351 .LvalueReference().Const().NotSubstTemplateTypeParmType()
352 .Class("OUString").Namespace("rtl").GlobalNamespace())
354 if (!(isLhsOfAssignment(fdecl, i)
355 || hasOverloads(fdecl, expr->getNumArgs())))
357 handleOUStringCtor(expr, i, fdecl, true);
360 if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
361 .LvalueReference().Const().NotSubstTemplateTypeParmType()
362 .Class("OString").Namespace("rtl").GlobalNamespace())
364 if (!(isLhsOfAssignment(fdecl, i)
365 || hasOverloads(fdecl, expr->getNumArgs())))
367 handleOStringCtor(expr, i, fdecl, true);
371 loplugin::DeclCheck dc(fdecl);
372 //TODO: u.compareToAscii("foo") -> u.???("foo")
373 //TODO: u.compareToIgnoreAsciiCaseAscii("foo") -> u.???("foo")
374 if ((dc.Function("createFromAscii").Class("OUString").Namespace("rtl")
375 .GlobalNamespace())
376 && fdecl->getNumParams() == 1)
378 // OUString::createFromAscii("foo") -> OUString("foo")
379 handleChar(
380 expr, 0, fdecl, "rtl::OUString constructor",
381 TreatEmpty::DefaultCtor, true);
382 return true;
384 if ((dc.Function("endsWithAsciiL").Class("OUString").Namespace("rtl")
385 .GlobalNamespace())
386 && fdecl->getNumParams() == 2)
388 // u.endsWithAsciiL("foo", 3) -> u.endsWith("foo"):
389 handleCharLen(
390 expr, 0, 1, fdecl, "rtl::OUString::endsWith", TreatEmpty::Error);
391 return true;
393 if ((dc.Function("endsWithIgnoreAsciiCaseAsciiL").Class("OUString")
394 .Namespace("rtl").GlobalNamespace())
395 && fdecl->getNumParams() == 2)
397 // u.endsWithIgnoreAsciiCaseAsciiL("foo", 3) ->
398 // u.endsWithIgnoreAsciiCase("foo"):
399 handleCharLen(
400 expr, 0, 1, fdecl, "rtl::OUString::endsWithIgnoreAsciiCase",
401 TreatEmpty::Error);
402 return true;
404 if ((dc.Function("equalsAscii").Class("OUString").Namespace("rtl")
405 .GlobalNamespace())
406 && fdecl->getNumParams() == 1)
408 // u.equalsAscii("foo") -> u == "foo":
409 handleChar(
410 expr, 0, fdecl, "operator ==", TreatEmpty::CheckEmpty, false);
411 return true;
413 if ((dc.Function("equalsAsciiL").Class("OUString").Namespace("rtl")
414 .GlobalNamespace())
415 && fdecl->getNumParams() == 2)
417 // u.equalsAsciiL("foo", 3) -> u == "foo":
418 handleCharLen(expr, 0, 1, fdecl, "operator ==", TreatEmpty::CheckEmpty);
419 return true;
421 if ((dc.Function("equalsIgnoreAsciiCaseAscii").Class("OUString")
422 .Namespace("rtl").GlobalNamespace())
423 && fdecl->getNumParams() == 1)
425 // u.equalsIgnoreAsciiCaseAscii("foo") ->
426 // u.equalsIngoreAsciiCase("foo"):
428 auto file = getFilenameOfLocation(
429 compiler.getSourceManager().getSpellingLoc(expr->getBeginLoc()));
430 if (loplugin::isSamePathname(
431 file, SRCDIR "/sal/qa/rtl/strings/test_oustring_compare.cxx"))
433 return true;
435 handleChar(
436 expr, 0, fdecl, "rtl::OUString::equalsIgnoreAsciiCase",
437 TreatEmpty::CheckEmpty, false);
438 return true;
440 if ((dc.Function("equalsIgnoreAsciiCaseAsciiL").Class("OUString")
441 .Namespace("rtl").GlobalNamespace())
442 && fdecl->getNumParams() == 2)
444 // u.equalsIgnoreAsciiCaseAsciiL("foo", 3) ->
445 // u.equalsIngoreAsciiCase("foo"):
446 auto file = getFilenameOfLocation(
447 compiler.getSourceManager().getSpellingLoc(expr->getBeginLoc()));
448 if (loplugin::isSamePathname(
449 file, SRCDIR "/sal/qa/rtl/strings/test_oustring_compare.cxx"))
451 return true;
453 handleCharLen(
454 expr, 0, 1, fdecl, "rtl::OUString::equalsIgnoreAsciiCase",
455 TreatEmpty::CheckEmpty);
456 return true;
458 if ((dc.Function("indexOfAsciiL").Class("OUString").Namespace("rtl")
459 .GlobalNamespace())
460 && fdecl->getNumParams() == 3)
462 assert(expr->getNumArgs() == 3);
463 // u.indexOfAsciiL("foo", 3, i) -> u.indexOf("foo", i):
464 handleCharLen(
465 expr, 0, 1, fdecl, "rtl::OUString::indexOf", TreatEmpty::Error);
466 return true;
468 if ((dc.Function("lastIndexOfAsciiL").Class("OUString").Namespace("rtl")
469 .GlobalNamespace())
470 && fdecl->getNumParams() == 2)
472 // u.lastIndexOfAsciiL("foo", 3) -> u.lastIndexOf("foo"):
473 handleCharLen(
474 expr, 0, 1, fdecl, "rtl::OUString::lastIndexOf", TreatEmpty::Error);
475 return true;
477 if ((dc.Function("matchAsciiL").Class("OUString").Namespace("rtl")
478 .GlobalNamespace())
479 && fdecl->getNumParams() == 3)
481 assert(expr->getNumArgs() == 3);
482 // u.matchAsciiL("foo", 3, i) -> u.match("foo", i):
483 handleCharLen(
484 expr, 0, 1, fdecl,
485 (isZero(expr->getArg(2))
486 ? std::string("rtl::OUString::startsWith")
487 : std::string("rtl::OUString::match")),
488 TreatEmpty::Error);
489 return true;
491 if ((dc.Function("matchIgnoreAsciiCaseAsciiL").Class("OUString")
492 .Namespace("rtl").GlobalNamespace())
493 && fdecl->getNumParams() == 3)
495 assert(expr->getNumArgs() == 3);
496 // u.matchIgnoreAsciiCaseAsciiL("foo", 3, i) ->
497 // u.matchIgnoreAsciiCase("foo", i):
498 handleCharLen(
499 expr, 0, 1, fdecl,
500 (isZero(expr->getArg(2))
501 ? std::string("rtl::OUString::startsWithIgnoreAsciiCase")
502 : std::string("rtl::OUString::matchIgnoreAsciiCase")),
503 TreatEmpty::Error);
504 return true;
506 if ((dc.Function("reverseCompareToAsciiL").Class("OUString")
507 .Namespace("rtl").GlobalNamespace())
508 && fdecl->getNumParams() == 2)
510 // u.reverseCompareToAsciiL("foo", 3) -> u.reverseCompareTo("foo"):
511 handleCharLen(
512 expr, 0, 1, fdecl, "rtl::OUString::reverseCompareTo",
513 TreatEmpty::Error);
514 return true;
516 if ((dc.Function("reverseCompareTo").Class("OUString").Namespace("rtl")
517 .GlobalNamespace())
518 && fdecl->getNumParams() == 1)
520 handleOUStringCtor(expr, 0, fdecl, false);
521 return true;
523 if ((dc.Function("equalsIgnoreAsciiCase").Class("OUString").Namespace("rtl")
524 .GlobalNamespace())
525 && fdecl->getNumParams() == 1)
527 handleOUStringCtor(expr, 0, fdecl, false);
528 return true;
530 if ((dc.Function("match").Class("OUString").Namespace("rtl")
531 .GlobalNamespace())
532 && fdecl->getNumParams() == 2)
534 handleOUStringCtor(expr, 0, fdecl, false);
535 return true;
537 if ((dc.Function("matchIgnoreAsciiCase").Class("OUString").Namespace("rtl")
538 .GlobalNamespace())
539 && fdecl->getNumParams() == 2)
541 handleOUStringCtor(expr, 0, fdecl, false);
542 return true;
544 if ((dc.Function("startsWith").Class("OUString").Namespace("rtl")
545 .GlobalNamespace())
546 && fdecl->getNumParams() == 2)
548 handleOUStringCtor(expr, 0, fdecl, false);
549 return true;
551 if ((dc.Function("startsWithIgnoreAsciiCase").Class("OUString")
552 .Namespace("rtl").GlobalNamespace())
553 && fdecl->getNumParams() == 2)
555 handleOUStringCtor(expr, 0, fdecl, false);
556 return true;
558 if ((dc.Function("endsWith").Class("OUString").Namespace("rtl")
559 .GlobalNamespace())
560 && fdecl->getNumParams() == 2)
562 handleOUStringCtor(expr, 0, fdecl, false);
563 return true;
565 if ((dc.Function("endsWithIgnoreAsciiCase").Class("OUString")
566 .Namespace("rtl").GlobalNamespace())
567 && fdecl->getNumParams() == 2)
569 handleOUStringCtor(expr, 0, fdecl, false);
570 return true;
572 if ((dc.Function("indexOf").Class("OUString").Namespace("rtl")
573 .GlobalNamespace())
574 && fdecl->getNumParams() == 2)
576 handleOUStringCtor(expr, 0, fdecl, false);
577 return true;
579 if ((dc.Function("lastIndexOf").Class("OUString").Namespace("rtl")
580 .GlobalNamespace())
581 && fdecl->getNumParams() == 1)
583 handleOUStringCtor(expr, 0, fdecl, false);
584 return true;
586 if ((dc.Function("replaceFirst").Class("OUString").Namespace("rtl")
587 .GlobalNamespace())
588 && fdecl->getNumParams() == 3)
590 handleOUStringCtor(expr, 0, fdecl, false);
591 handleOUStringCtor(expr, 1, fdecl, false);
592 return true;
594 if ((dc.Function("replaceAll").Class("OUString").Namespace("rtl")
595 .GlobalNamespace())
596 && (fdecl->getNumParams() == 2 || fdecl->getNumParams() == 3))
598 handleOUStringCtor(expr, 0, fdecl, false);
599 handleOUStringCtor(expr, 1, fdecl, false);
600 return true;
602 if ((dc.Operator(OO_PlusEqual).Class("OUString").Namespace("rtl")
603 .GlobalNamespace())
604 && fdecl->getNumParams() == 1)
606 handleOUStringCtor(
607 expr, dyn_cast<CXXOperatorCallExpr>(expr) == nullptr ? 0 : 1,
608 fdecl, false);
609 return true;
611 if ((dc.Function("equals").Class("OUString").Namespace("rtl")
612 .GlobalNamespace())
613 && fdecl->getNumParams() == 1)
615 unsigned n;
616 bool nonArray;
617 ContentKind cont;
618 bool emb;
619 bool trm;
620 if (!isStringConstant(
621 expr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
622 &emb, &trm))
624 return true;
626 if (cont != ContentKind::Ascii) {
627 report(
628 DiagnosticsEngine::Warning,
629 ("call of '%0' with string constant argument containing"
630 " non-ASCII characters"),
631 expr->getExprLoc())
632 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
634 if (emb) {
635 report(
636 DiagnosticsEngine::Warning,
637 ("call of '%0' with string constant argument containing"
638 " embedded NULLs"),
639 expr->getExprLoc())
640 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
642 if (n == 0) {
643 report(
644 DiagnosticsEngine::Warning,
645 ("rewrite call of '%0' with empty string constant argument as"
646 " call of 'rtl::OUString::isEmpty'"),
647 expr->getExprLoc())
648 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
649 return true;
652 if (dc.Operator(OO_EqualEqual).Namespace("rtl").GlobalNamespace()
653 && fdecl->getNumParams() == 2)
655 for (unsigned i = 0; i != 2; ++i) {
656 unsigned n;
657 bool nonArray;
658 ContentKind cont;
659 bool emb;
660 bool trm;
661 if (!isStringConstant(
662 expr->getArg(i)->IgnoreParenImpCasts(), &n, &nonArray,
663 &cont, &emb, &trm))
665 continue;
667 if (cont != ContentKind::Ascii) {
668 report(
669 DiagnosticsEngine::Warning,
670 ("call of '%0' with string constant argument containing"
671 " non-ASCII characters"),
672 expr->getExprLoc())
673 << fdecl->getQualifiedNameAsString()
674 << expr->getSourceRange();
676 if (emb) {
677 report(
678 DiagnosticsEngine::Warning,
679 ("call of '%0' with string constant argument containing"
680 " embedded NULLs"),
681 expr->getExprLoc())
682 << fdecl->getQualifiedNameAsString()
683 << expr->getSourceRange();
685 if (n == 0) {
686 report(
687 DiagnosticsEngine::Warning,
688 ("rewrite call of '%0' with empty string constant argument"
689 " as call of 'rtl::OUString::isEmpty'"),
690 expr->getExprLoc())
691 << fdecl->getQualifiedNameAsString()
692 << expr->getSourceRange();
695 return true;
697 if (dc.Operator(OO_ExclaimEqual).Namespace("rtl").GlobalNamespace()
698 && fdecl->getNumParams() == 2)
700 for (unsigned i = 0; i != 2; ++i) {
701 unsigned n;
702 bool nonArray;
703 ContentKind cont;
704 bool emb;
705 bool trm;
706 if (!isStringConstant(
707 expr->getArg(i)->IgnoreParenImpCasts(), &n, &nonArray,
708 &cont, &emb, &trm))
710 continue;
712 if (cont != ContentKind::Ascii) {
713 report(
714 DiagnosticsEngine::Warning,
715 ("call of '%0' with string constant argument containing"
716 " non-ASCII characters"),
717 expr->getExprLoc())
718 << fdecl->getQualifiedNameAsString()
719 << expr->getSourceRange();
721 if (emb) {
722 report(
723 DiagnosticsEngine::Warning,
724 ("call of '%0' with string constant argument containing"
725 " embedded NULLs"),
726 expr->getExprLoc())
727 << fdecl->getQualifiedNameAsString()
728 << expr->getSourceRange();
730 if (n == 0) {
731 report(
732 DiagnosticsEngine::Warning,
733 ("rewrite call of '%0' with empty string constant argument"
734 " as call of '!rtl::OUString::isEmpty'"),
735 expr->getExprLoc())
736 << fdecl->getQualifiedNameAsString()
737 << expr->getSourceRange();
740 return true;
742 if (dc.Operator(OO_Equal).Namespace("rtl").GlobalNamespace()
743 && fdecl->getNumParams() == 1)
745 unsigned n;
746 bool nonArray;
747 ContentKind cont;
748 bool emb;
749 bool trm;
750 if (!isStringConstant(
751 expr->getArg(1)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
752 &emb, &trm))
754 return true;
756 if (cont != ContentKind::Ascii) {
757 report(
758 DiagnosticsEngine::Warning,
759 ("call of '%0' with string constant argument containing"
760 " non-ASCII characters"),
761 expr->getExprLoc())
762 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
764 if (emb) {
765 report(
766 DiagnosticsEngine::Warning,
767 ("call of '%0' with string constant argument containing"
768 " embedded NULLs"),
769 expr->getExprLoc())
770 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
772 if (n == 0) {
773 report(
774 DiagnosticsEngine::Warning,
775 ("rewrite call of '%0' with empty string constant argument as"
776 " call of 'rtl::OUString::clear'"),
777 expr->getExprLoc())
778 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
779 return true;
781 return true;
783 if (dc.Function("append").Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
784 && fdecl->getNumParams() == 1)
786 handleChar(expr, 0, fdecl, "", TreatEmpty::Error, false);
787 return true;
789 if ((dc.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
790 .GlobalNamespace())
791 && fdecl->getNumParams() == 1)
793 // u.appendAscii("foo") -> u.append("foo")
794 handleChar(
795 expr, 0, fdecl, "rtl::OUStringBuffer::append", TreatEmpty::Error,
796 true, "appendAscii", "append");
797 return true;
799 if ((dc.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
800 .GlobalNamespace())
801 && fdecl->getNumParams() == 2)
803 // u.appendAscii("foo", 3) -> u.append("foo"):
804 handleCharLen(
805 expr, 0, 1, fdecl, "rtl::OUStringBuffer::append",
806 TreatEmpty::Error);
807 return true;
809 if (dc.Function("append").Class("OStringBuffer").Namespace("rtl")
810 .GlobalNamespace())
812 switch (fdecl->getNumParams()) {
813 case 1:
814 handleFunArgOstring(expr, 0, fdecl);
815 break;
816 case 2:
818 // b.append("foo", 3) -> b.append("foo"):
819 auto file = getFilenameOfLocation(
820 compiler.getSourceManager().getSpellingLoc(
821 expr->getBeginLoc()));
822 if (loplugin::isSamePathname(
823 file,
824 SRCDIR "/sal/qa/OStringBuffer/rtl_OStringBuffer.cxx"))
826 return true;
828 handleCharLen(
829 expr, 0, 1, fdecl, "rtl::OStringBuffer::append",
830 TreatEmpty::Error);
832 break;
833 default:
834 break;
836 return true;
838 if (dc.Function("insert").Class("OStringBuffer").Namespace("rtl")
839 .GlobalNamespace())
841 switch (fdecl->getNumParams()) {
842 case 2:
843 handleFunArgOstring(expr, 1, fdecl);
844 break;
845 case 3:
847 // b.insert(i, "foo", 3) -> b.insert(i, "foo"):
848 handleCharLen(
849 expr, 1, 2, fdecl, "rtl::OStringBuffer::insert",
850 TreatEmpty::Error);
851 break;
853 default:
854 break;
856 return true;
858 return true;
861 bool StringConstant::VisitCXXMemberCallExpr(CXXMemberCallExpr const * expr) {
862 if (ignoreLocation(expr)) {
863 return true;
865 FunctionDecl const * fdecl = expr->getDirectCallee();
866 if (fdecl == nullptr) {
867 return true;
869 auto const c = loplugin::DeclCheck(fdecl).Function("getStr");
870 if ((c.Class("OString").Namespace("rtl").GlobalNamespace()
871 || c.Class("OUString").Namespace("rtl").GlobalNamespace())
872 && fdecl->getNumParams() == 0)
874 auto const e1 = expr->getImplicitObjectArgument()->IgnoreImplicit()->IgnoreParens();
875 if (auto const e2 = dyn_cast<CXXTemporaryObjectExpr>(e1)) {
876 if (e2->getNumArgs() != 0) {
877 return true;
879 report(
880 DiagnosticsEngine::Warning,
881 "in call of '%0', replace default-constructed %1 directly with an empty %select{ordinary|UTF-16}2 string literal",
882 expr->getExprLoc())
883 << fdecl->getQualifiedNameAsString() << e2->getType() << bool(loplugin::TypeCheck(e2->getType()).Class("OUString")) << expr->getSourceRange();
884 return true;
886 if (auto const e2 = dyn_cast<CXXFunctionalCastExpr>(e1)) {
887 auto const e3 = dyn_cast<clang::StringLiteral>(e2->getSubExprAsWritten()->IgnoreParens());
888 if (e3 == nullptr) {
889 return true;
891 report(
892 DiagnosticsEngine::Warning,
893 "in call of '%0', replace %1 constructed from a string literal directly with %select{the|a UTF-16}2 string literal",
894 expr->getExprLoc())
895 << fdecl->getQualifiedNameAsString() << e2->getType() << (loplugin::TypeCheck(e2->getType()).Class("OUString") && !e3->isUTF16()) << expr->getSourceRange();
896 return true;
899 return true;
902 bool StringConstant::VisitCXXConstructExpr(CXXConstructExpr const * expr) {
903 if (ignoreLocation(expr)) {
904 return true;
906 auto classdecl = expr->getConstructor()->getParent();
907 if (loplugin::DeclCheck(classdecl)
908 .Class("OUString").Namespace("rtl").GlobalNamespace())
910 ChangeKind kind;
911 PassThrough pass;
912 bool simplify;
913 switch (expr->getConstructor()->getNumParams()) {
914 case 1:
915 if (!loplugin::TypeCheck(
916 expr->getConstructor()->getParamDecl(0)->getType())
917 .Typedef("sal_Unicode").GlobalNamespace())
919 return true;
921 kind = ChangeKind::SingleChar;
922 pass = PassThrough::NonEmptyConstantString;
923 simplify = false;
924 break;
925 case 2:
927 auto arg = expr->getArg(0);
928 if (loplugin::TypeCheck(arg->getType())
929 .Class("OUStringChar_").Namespace("rtl")
930 .GlobalNamespace())
932 kind = ChangeKind::OUStringChar;
933 pass = PassThrough::NonEmptyConstantString;
934 simplify = false;
935 } else {
936 unsigned n;
937 bool nonArray;
938 ContentKind cont;
939 bool emb;
940 bool trm;
941 if (!isStringConstant(
942 arg->IgnoreParenImpCasts(), &n, &nonArray, &cont,
943 &emb, &trm))
945 return true;
947 if (cont != ContentKind::Ascii) {
948 report(
949 DiagnosticsEngine::Warning,
950 ("construction of %0 with string constant argument"
951 " containing non-ASCII characters"),
952 expr->getExprLoc())
953 << classdecl << expr->getSourceRange();
955 if (emb) {
956 report(
957 DiagnosticsEngine::Warning,
958 ("construction of %0 with string constant argument"
959 " containing embedded NULLs"),
960 expr->getExprLoc())
961 << classdecl << expr->getSourceRange();
963 kind = ChangeKind::Char;
964 pass = n == 0
965 ? PassThrough::EmptyConstantString
966 : PassThrough::NonEmptyConstantString;
967 simplify = false;
969 break;
971 case 4:
973 unsigned n;
974 bool nonArray;
975 ContentKind cont;
976 bool emb;
977 bool trm;
978 std::vector<char32_t> utf8Cont;
979 if (!isStringConstant(
980 expr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray,
981 &cont, &emb, &trm, &utf8Cont))
983 return true;
985 APSInt res;
986 if (!compat::EvaluateAsInt(expr->getArg(1),
987 res, compiler.getASTContext()))
989 return true;
991 if (res != n) {
992 report(
993 DiagnosticsEngine::Warning,
994 ("suspicious 'rtl::OUString' constructor with literal"
995 " of length %0 and non-matching length argument %1"),
996 expr->getExprLoc())
997 << n << compat::toString(res, 10) << expr->getSourceRange();
998 return true;
1000 APSInt enc;
1001 if (!compat::EvaluateAsInt(expr->getArg(2),
1002 enc, compiler.getASTContext()))
1004 return true;
1006 auto const encIsAscii = enc == 11; // RTL_TEXTENCODING_ASCII_US
1007 auto const encIsUtf8 = enc == 76; // RTL_TEXTENCODING_UTF8
1008 if (!compat::EvaluateAsInt(expr->getArg(3),
1009 res, compiler.getASTContext())
1010 || res != 0x333) // OSTRING_TO_OUSTRING_CVTFLAGS
1012 return true;
1014 if (!encIsAscii && cont == ContentKind::Ascii) {
1015 report(
1016 DiagnosticsEngine::Warning,
1017 ("suspicious 'rtl::OUString' constructor with text"
1018 " encoding %0 but plain ASCII content; use"
1019 " 'RTL_TEXTENCODING_ASCII_US' instead"),
1020 expr->getArg(2)->getExprLoc())
1021 << compat::toString(enc, 10) << expr->getSourceRange();
1022 return true;
1024 if (encIsUtf8) {
1025 if (cont == ContentKind::Arbitrary) {
1026 report(
1027 DiagnosticsEngine::Warning,
1028 ("suspicious 'rtl::OUString' constructor with text"
1029 " encoding 'RTL_TEXTENCODING_UTF8' but non-UTF-8"
1030 " content"),
1031 expr->getArg(0)->getExprLoc())
1032 << expr->getSourceRange();
1033 } else {
1034 assert(cont == ContentKind::Utf8);
1035 //TODO: keep original content as much as possible
1036 std::ostringstream s;
1037 for (auto const c: utf8Cont) {
1038 if (c == '\\') {
1039 s << "\\\\";
1040 } else if (c == '"') {
1041 s << "\\\"";
1042 } else if (c == '\a') {
1043 s << "\\a";
1044 } else if (c == '\b') {
1045 s << "\\b";
1046 } else if (c == '\f') {
1047 s << "\\f";
1048 } else if (c == '\n') {
1049 s << "\\n";
1050 } else if (c == '\r') {
1051 s << "\\r";
1052 } else if (c == '\t') {
1053 s << "\\r";
1054 } else if (c == '\v') {
1055 s << "\\v";
1056 } else if (c <= 0x1F || c == 0x7F) {
1057 s << "\\x" << std::oct << std::setw(3)
1058 << std::setfill('0')
1059 << static_cast<std::uint_least32_t>(c);
1060 } else if (c < 0x7F) {
1061 s << char(c);
1062 } else if (c <= 0xFFFF) {
1063 s << "\\u" << std::hex << std::uppercase
1064 << std::setw(4) << std::setfill('0')
1065 << static_cast<std::uint_least32_t>(c);
1066 } else {
1067 assert(c <= 0x10FFFF);
1068 s << "\\U" << std::hex << std::uppercase
1069 << std::setw(8) << std::setfill('0')
1070 << static_cast<std::uint_least32_t>(c);
1073 report(
1074 DiagnosticsEngine::Warning,
1075 ("simplify construction of %0 with UTF-8 content as"
1076 " OUString(u\"%1\")"),
1077 expr->getExprLoc())
1078 << classdecl << s.str() << expr->getSourceRange();
1081 return true;
1083 if (cont != ContentKind::Ascii || emb) {
1084 // cf. remaining uses of RTL_CONSTASCII_USTRINGPARAM
1085 return true;
1087 kind = ChangeKind::Char;
1088 pass = n == 0
1089 ? PassThrough::EmptyConstantString
1090 : PassThrough::NonEmptyConstantString;
1091 simplify = true;
1092 break;
1094 default:
1095 return true;
1097 if (!calls_.empty()) {
1098 Expr const * call = calls_.top();
1099 CallExpr::const_arg_iterator argsBeg;
1100 CallExpr::const_arg_iterator argsEnd;
1101 if (isa<CallExpr>(call)) {
1102 argsBeg = cast<CallExpr>(call)->arg_begin();
1103 argsEnd = cast<CallExpr>(call)->arg_end();
1104 } else if (isa<CXXConstructExpr>(call)) {
1105 argsBeg = cast<CXXConstructExpr>(call)->arg_begin();
1106 argsEnd = cast<CXXConstructExpr>(call)->arg_end();
1107 } else {
1108 assert(false);
1110 for (auto i(argsBeg); i != argsEnd; ++i) {
1111 Expr const * e = (*i)->IgnoreParenImpCasts();
1112 if (isa<MaterializeTemporaryExpr>(e)) {
1113 e = cast<MaterializeTemporaryExpr>(e)->getSubExpr()
1114 ->IgnoreParenImpCasts();
1116 if (isa<CXXFunctionalCastExpr>(e)) {
1117 e = cast<CXXFunctionalCastExpr>(e)->getSubExpr()
1118 ->IgnoreParenImpCasts();
1120 if (isa<CXXBindTemporaryExpr>(e)) {
1121 e = cast<CXXBindTemporaryExpr>(e)->getSubExpr()
1122 ->IgnoreParenImpCasts();
1124 if (e == expr) {
1125 if (isa<CallExpr>(call)) {
1126 FunctionDecl const * fdecl
1127 = cast<CallExpr>(call)->getDirectCallee();
1128 if (fdecl == nullptr) {
1129 break;
1131 loplugin::DeclCheck dc(fdecl);
1132 if (pass == PassThrough::EmptyConstantString) {
1133 if ((dc.Function("equals").Class("OUString")
1134 .Namespace("rtl").GlobalNamespace())
1135 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1136 .GlobalNamespace()))
1138 report(
1139 DiagnosticsEngine::Warning,
1140 ("rewrite call of '%0' with construction of"
1141 " %1 with empty string constant argument"
1142 " as call of 'rtl::OUString::isEmpty'"),
1143 getMemberLocation(call))
1144 << fdecl->getQualifiedNameAsString()
1145 << classdecl << call->getSourceRange();
1146 return true;
1148 if (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
1149 .GlobalNamespace())
1151 report(
1152 DiagnosticsEngine::Warning,
1153 ("rewrite call of '%0' with construction of"
1154 " %1 with empty string constant argument"
1155 " as call of '!rtl::OUString::isEmpty'"),
1156 getMemberLocation(call))
1157 << fdecl->getQualifiedNameAsString()
1158 << classdecl << call->getSourceRange();
1159 return true;
1161 if ((dc.Operator(OO_Plus).Namespace("rtl")
1162 .GlobalNamespace())
1163 || (dc.Operator(OO_Plus).Class("OUString")
1164 .Namespace("rtl").GlobalNamespace()))
1166 report(
1167 DiagnosticsEngine::Warning,
1168 ("call of '%0' with suspicious construction"
1169 " of %1 with empty string constant"
1170 " argument"),
1171 getMemberLocation(call))
1172 << fdecl->getQualifiedNameAsString()
1173 << classdecl << call->getSourceRange();
1174 return true;
1176 if (dc.Operator(OO_Equal).Class("OUString")
1177 .Namespace("rtl").GlobalNamespace())
1179 report(
1180 DiagnosticsEngine::Warning,
1181 ("rewrite call of '%0' with construction of"
1182 " %1 with empty string constant argument"
1183 " as call of 'rtl::OUString::clear'"),
1184 getMemberLocation(call))
1185 << fdecl->getQualifiedNameAsString()
1186 << classdecl << call->getSourceRange();
1187 return true;
1189 } else {
1190 assert(pass == PassThrough::NonEmptyConstantString);
1191 if (dc.Function("equals").Class("OUString")
1192 .Namespace("rtl").GlobalNamespace())
1194 report(
1195 DiagnosticsEngine::Warning,
1196 ("rewrite call of '%0' with construction of"
1197 " %1 with %2 as 'operator =='"),
1198 getMemberLocation(call))
1199 << fdecl->getQualifiedNameAsString()
1200 << classdecl << describeChangeKind(kind)
1201 << call->getSourceRange();
1202 return true;
1204 if ((dc.Operator(OO_Plus).Namespace("rtl")
1205 .GlobalNamespace())
1206 || (dc.Operator(OO_Plus).Class("OUString")
1207 .Namespace("rtl").GlobalNamespace())
1208 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1209 .GlobalNamespace())
1210 || (dc.Operator(OO_ExclaimEqual)
1211 .Namespace("rtl").GlobalNamespace()))
1213 if (dc.Operator(OO_Plus).Namespace("rtl")
1214 .GlobalNamespace())
1216 auto file = getFilenameOfLocation(
1217 compiler.getSourceManager()
1218 .getSpellingLoc(
1219 expr->getBeginLoc()));
1220 if (loplugin::isSamePathname(
1221 file,
1222 (SRCDIR
1223 "/sal/qa/rtl/strings/test_ostring_concat.cxx"))
1224 || loplugin::isSamePathname(
1225 file,
1226 (SRCDIR
1227 "/sal/qa/rtl/strings/test_oustring_concat.cxx")))
1229 return true;
1232 auto loc = expr->getArg(0)->getBeginLoc();
1233 while (compiler.getSourceManager()
1234 .isMacroArgExpansion(loc))
1236 loc = compiler.getSourceManager()
1237 .getImmediateMacroCallerLoc(loc);
1239 if (kind == ChangeKind::SingleChar) {
1240 report(
1241 DiagnosticsEngine::Warning,
1242 ("rewrite construction of %0 with %1 in"
1243 " call of '%2' as construction of"
1244 " 'OUStringChar'"),
1245 getMemberLocation(expr))
1246 << classdecl << describeChangeKind(kind)
1247 << fdecl->getQualifiedNameAsString()
1248 << expr->getSourceRange();
1249 } else {
1250 report(
1251 DiagnosticsEngine::Warning,
1252 ("elide construction of %0 with %1 in"
1253 " call of '%2'"),
1254 getMemberLocation(expr))
1255 << classdecl << describeChangeKind(kind)
1256 << fdecl->getQualifiedNameAsString()
1257 << expr->getSourceRange();
1259 return true;
1262 } else if (isa<CXXConstructExpr>(call)) {
1263 } else {
1264 assert(false);
1269 if (simplify) {
1270 report(
1271 DiagnosticsEngine::Warning,
1272 "simplify construction of %0 with %1", expr->getExprLoc())
1273 << classdecl << describeChangeKind(kind)
1274 << expr->getSourceRange();
1276 return true;
1279 auto consDecl = expr->getConstructor();
1280 for (unsigned i = 0; i != consDecl->getNumParams(); ++i) {
1281 auto t = consDecl->getParamDecl(i)->getType();
1282 if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
1283 .LvalueReference().Const().NotSubstTemplateTypeParmType()
1284 .Class("OUString").Namespace("rtl").GlobalNamespace())
1286 auto argExpr = expr->getArg(i);
1287 if (argExpr && i <= consDecl->getNumParams())
1289 if (!hasOverloads(consDecl, expr->getNumArgs()))
1291 handleOUStringCtor(expr, argExpr, consDecl, true);
1295 if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
1296 .LvalueReference().Const().NotSubstTemplateTypeParmType()
1297 .Class("OString").Namespace("rtl").GlobalNamespace())
1299 auto argExpr = expr->getArg(i);
1300 if (argExpr && i <= consDecl->getNumParams())
1302 if (!hasOverloads(consDecl, expr->getNumArgs()))
1304 handleOStringCtor(expr, argExpr, consDecl, true);
1310 return true;
1313 bool StringConstant::VisitReturnStmt(ReturnStmt const * stmt) {
1314 if (ignoreLocation(stmt)) {
1315 return true;
1317 auto const e1 = stmt->getRetValue();
1318 if (e1 == nullptr) {
1319 return true;
1321 auto const tc1 = loplugin::TypeCheck(e1->getType().getTypePtr());
1322 if (!(tc1.Class("OString").Namespace("rtl").GlobalNamespace()
1323 || tc1.Class("OUString").Namespace("rtl").GlobalNamespace()))
1325 return true;
1327 assert(!returnTypes_.empty());
1328 auto const tc2 = loplugin::TypeCheck(returnTypes_.top().getTypePtr());
1329 if (!(tc2.Class("OString").Namespace("rtl").GlobalNamespace()
1330 || tc2.Class("OUString").Namespace("rtl").GlobalNamespace()))
1332 return true;
1334 auto const e2 = dyn_cast<CXXFunctionalCastExpr>(e1->IgnoreImplicit());
1335 if (e2 == nullptr) {
1336 return true;
1338 auto const e3 = dyn_cast<CXXBindTemporaryExpr>(e2->getSubExpr());
1339 if (e3 == nullptr) {
1340 return true;
1342 auto const e4 = dyn_cast<CXXConstructExpr>(e3->getSubExpr());
1343 if (e4 == nullptr) {
1344 return true;
1346 if (e4->getNumArgs() != 2) {
1347 return true;
1349 auto const t = e4->getArg(0)->getType();
1350 if (!(t.isConstQualified() && t->isConstantArrayType())) {
1351 return true;
1353 auto const e5 = e4->getArg(1);
1354 if (!(isa<CXXDefaultArgExpr>(e5)
1355 && (loplugin::TypeCheck(e5->getType()).Struct("Dummy").Namespace("libreoffice_internal")
1356 .Namespace("rtl").GlobalNamespace())))
1358 return true;
1360 report(DiagnosticsEngine::Warning, "elide constructor call", e1->getBeginLoc())
1361 << e1->getSourceRange();
1362 return true;
1365 std::string StringConstant::describeChangeKind(ChangeKind kind) {
1366 switch (kind) {
1367 case ChangeKind::Char:
1368 return "string constant argument";
1369 case ChangeKind::CharLen:
1370 return "string constant and matching length arguments";
1371 case ChangeKind::SingleChar:
1372 return "sal_Unicode argument";
1373 case ChangeKind::OUStringChar:
1374 return "OUStringChar argument";
1376 llvm_unreachable("unknown change kind");
1379 bool StringConstant::isStringConstant(
1380 Expr const * expr, unsigned * size, bool * nonArray, ContentKind * content,
1381 bool * embeddedNuls, bool * terminatingNul,
1382 std::vector<char32_t> * utf8Content)
1384 assert(expr != nullptr);
1385 assert(size != nullptr);
1386 assert(nonArray != nullptr);
1387 assert(content != nullptr);
1388 assert(embeddedNuls != nullptr);
1389 assert(terminatingNul != nullptr);
1390 QualType t = expr->getType();
1391 // Look inside RTL_CONSTASCII_STRINGPARAM:
1392 if (loplugin::TypeCheck(t).Pointer().Const().Char()) {
1393 auto e2 = dyn_cast<UnaryOperator>(expr);
1394 if (e2 != nullptr && e2->getOpcode() == UO_AddrOf) {
1395 auto e3 = dyn_cast<ArraySubscriptExpr>(
1396 e2->getSubExpr()->IgnoreParenImpCasts());
1397 if (e3 == nullptr || !isZero(e3->getIdx()->IgnoreParenImpCasts())) {
1398 return false;
1400 expr = e3->getBase()->IgnoreParenImpCasts();
1401 t = expr->getType();
1404 if (!t.isConstQualified()) {
1405 return false;
1407 DeclRefExpr const * dre = dyn_cast<DeclRefExpr>(expr);
1408 if (dre != nullptr) {
1409 VarDecl const * var = dyn_cast<VarDecl>(dre->getDecl());
1410 if (var != nullptr) {
1411 Expr const * init = var->getAnyInitializer();
1412 if (init != nullptr) {
1413 expr = init->IgnoreParenImpCasts();
1417 bool isPtr;
1418 if (loplugin::TypeCheck(t).Pointer().Const().Char()) {
1419 isPtr = true;
1420 } else if (t->isConstantArrayType()
1421 && (loplugin::TypeCheck(
1422 t->getAsArrayTypeUnsafe()->getElementType())
1423 .Char()))
1425 isPtr = false;
1426 } else {
1427 return false;
1429 clang::StringLiteral const * lit = dyn_cast<clang::StringLiteral>(expr);
1430 if (lit != nullptr) {
1431 if (!(compat::isOrdinary(lit) || lit->isUTF8())) {
1432 return false;
1434 unsigned n = lit->getLength();
1435 ContentKind cont = ContentKind::Ascii;
1436 bool emb = false;
1437 char32_t val = 0;
1438 enum class Utf8State { Start, E0, EB, F0, F4, Trail1, Trail2, Trail3 };
1439 Utf8State s = Utf8State::Start;
1440 StringRef str = lit->getString();
1441 for (unsigned i = 0; i != n; ++i) {
1442 auto const c = static_cast<unsigned char>(str[i]);
1443 if (c == '\0') {
1444 emb = true;
1446 switch (s) {
1447 case Utf8State::Start:
1448 if (c >= 0x80) {
1449 if (c >= 0xC2 && c <= 0xDF) {
1450 val = c & 0x1F;
1451 s = Utf8State::Trail1;
1452 } else if (c == 0xE0) {
1453 val = c & 0x0F;
1454 s = Utf8State::E0;
1455 } else if ((c >= 0xE1 && c <= 0xEA)
1456 || (c >= 0xEE && c <= 0xEF))
1458 val = c & 0x0F;
1459 s = Utf8State::Trail2;
1460 } else if (c == 0xEB) {
1461 val = c & 0x0F;
1462 s = Utf8State::EB;
1463 } else if (c == 0xF0) {
1464 val = c & 0x03;
1465 s = Utf8State::F0;
1466 } else if (c >= 0xF1 && c <= 0xF3) {
1467 val = c & 0x03;
1468 s = Utf8State::Trail3;
1469 } else if (c == 0xF4) {
1470 val = c & 0x03;
1471 s = Utf8State::F4;
1472 } else {
1473 cont = ContentKind::Arbitrary;
1475 } else if (utf8Content != nullptr
1476 && cont != ContentKind::Arbitrary)
1478 utf8Content->push_back(c);
1480 break;
1481 case Utf8State::E0:
1482 if (c >= 0xA0 && c <= 0xBF) {
1483 val = (val << 6) | (c & 0x3F);
1484 s = Utf8State::Trail1;
1485 } else {
1486 cont = ContentKind::Arbitrary;
1487 s = Utf8State::Start;
1489 break;
1490 case Utf8State::EB:
1491 if (c >= 0x80 && c <= 0x9F) {
1492 val = (val << 6) | (c & 0x3F);
1493 s = Utf8State::Trail1;
1494 } else {
1495 cont = ContentKind::Arbitrary;
1496 s = Utf8State::Start;
1498 break;
1499 case Utf8State::F0:
1500 if (c >= 0x90 && c <= 0xBF) {
1501 val = (val << 6) | (c & 0x3F);
1502 s = Utf8State::Trail2;
1503 } else {
1504 cont = ContentKind::Arbitrary;
1505 s = Utf8State::Start;
1507 break;
1508 case Utf8State::F4:
1509 if (c >= 0x80 && c <= 0x8F) {
1510 val = (val << 6) | (c & 0x3F);
1511 s = Utf8State::Trail2;
1512 } else {
1513 cont = ContentKind::Arbitrary;
1514 s = Utf8State::Start;
1516 break;
1517 case Utf8State::Trail1:
1518 if (c >= 0x80 && c <= 0xBF) {
1519 cont = ContentKind::Utf8;
1520 if (utf8Content != nullptr)
1522 utf8Content->push_back((val << 6) | (c & 0x3F));
1523 val = 0;
1525 } else {
1526 cont = ContentKind::Arbitrary;
1528 s = Utf8State::Start;
1529 break;
1530 case Utf8State::Trail2:
1531 if (c >= 0x80 && c <= 0xBF) {
1532 val = (val << 6) | (c & 0x3F);
1533 s = Utf8State::Trail1;
1534 } else {
1535 cont = ContentKind::Arbitrary;
1536 s = Utf8State::Start;
1538 break;
1539 case Utf8State::Trail3:
1540 if (c >= 0x80 && c <= 0xBF) {
1541 val = (val << 6) | (c & 0x3F);
1542 s = Utf8State::Trail2;
1543 } else {
1544 cont = ContentKind::Arbitrary;
1545 s = Utf8State::Start;
1547 break;
1550 *size = n;
1551 *nonArray = isPtr;
1552 *content = cont;
1553 *embeddedNuls = emb;
1554 *terminatingNul = true;
1555 return true;
1557 APValue v;
1558 if (!expr->isCXX11ConstantExpr(compiler.getASTContext(), &v)) {
1559 return false;
1561 switch (v.getKind()) {
1562 case APValue::LValue:
1564 Expr const * e = v.getLValueBase().dyn_cast<Expr const *>();
1565 if (e == nullptr) {
1566 return false;
1568 if (!v.getLValueOffset().isZero()) {
1569 return false; //TODO
1571 Expr const * e2 = e->IgnoreParenImpCasts();
1572 if (e2 != e) {
1573 return isStringConstant(
1574 e2, size, nonArray, content, embeddedNuls, terminatingNul);
1576 //TODO: string literals are represented as recursive LValues???
1577 llvm::APInt n
1578 = compiler.getASTContext().getAsConstantArrayType(t)->getSize();
1579 assert(n != 0);
1580 --n;
1581 assert(n.ule(std::numeric_limits<unsigned>::max()));
1582 *size = static_cast<unsigned>(n.getLimitedValue());
1583 *nonArray = isPtr || *nonArray;
1584 *content = ContentKind::Ascii; //TODO
1585 *embeddedNuls = false; //TODO
1586 *terminatingNul = true;
1587 return true;
1589 case APValue::Array:
1591 if (v.hasArrayFiller()) { //TODO: handle final NULL filler?
1592 return false;
1594 unsigned n = v.getArraySize();
1595 assert(n != 0);
1596 ContentKind cont = ContentKind::Ascii;
1597 bool emb = false;
1598 //TODO: check for ContentType::Utf8
1599 for (unsigned i = 0; i != n - 1; ++i) {
1600 APValue e(v.getArrayInitializedElt(i));
1601 if (!e.isInt()) { //TODO: assert?
1602 return false;
1604 APSInt iv = e.getInt();
1605 if (iv == 0) {
1606 emb = true;
1607 } else if (iv.uge(0x80)) {
1608 cont = ContentKind::Arbitrary;
1611 APValue e(v.getArrayInitializedElt(n - 1));
1612 if (!e.isInt()) { //TODO: assert?
1613 return false;
1615 bool trm = e.getInt() == 0;
1616 *size = trm ? n - 1 : n;
1617 *nonArray = isPtr;
1618 *content = cont;
1619 *embeddedNuls = emb;
1620 *terminatingNul = trm;
1621 return true;
1623 default:
1624 assert(false); //TODO???
1625 return false;
1629 bool StringConstant::isZero(Expr const * expr) {
1630 APSInt res;
1631 return compat::EvaluateAsInt(expr, res, compiler.getASTContext()) && res == 0;
1634 void StringConstant::reportChange(
1635 Expr const * expr, ChangeKind kind, std::string const & original,
1636 std::string const & replacement, PassThrough pass, bool nonArray,
1637 char const * rewriteFrom, char const * rewriteTo)
1639 assert((rewriteFrom == nullptr) == (rewriteTo == nullptr));
1640 if (pass != PassThrough::No && !calls_.empty()) {
1641 Expr const * call = calls_.top();
1642 CallExpr::const_arg_iterator argsBeg;
1643 CallExpr::const_arg_iterator argsEnd;
1644 if (isa<CallExpr>(call)) {
1645 argsBeg = cast<CallExpr>(call)->arg_begin();
1646 argsEnd = cast<CallExpr>(call)->arg_end();
1647 } else if (isa<CXXConstructExpr>(call)) {
1648 argsBeg = cast<CXXConstructExpr>(call)->arg_begin();
1649 argsEnd = cast<CXXConstructExpr>(call)->arg_end();
1650 } else {
1651 assert(false);
1653 for (auto i(argsBeg); i != argsEnd; ++i) {
1654 Expr const * e = (*i)->IgnoreParenImpCasts();
1655 if (isa<CXXBindTemporaryExpr>(e)) {
1656 e = cast<CXXBindTemporaryExpr>(e)->getSubExpr()
1657 ->IgnoreParenImpCasts();
1659 if (e == expr) {
1660 if (isa<CallExpr>(call)) {
1661 FunctionDecl const * fdecl
1662 = cast<CallExpr>(call)->getDirectCallee();
1663 if (fdecl == nullptr) {
1664 break;
1666 loplugin::DeclCheck dc(fdecl);
1667 if (pass == PassThrough::EmptyConstantString) {
1668 if ((dc.Function("equals").Class("OUString")
1669 .Namespace("rtl").GlobalNamespace())
1670 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1671 .GlobalNamespace()))
1673 report(
1674 DiagnosticsEngine::Warning,
1675 ("rewrite call of '%0' with call of %1 with"
1676 " empty string constant argument as call of"
1677 " 'rtl::OUString::isEmpty'"),
1678 getMemberLocation(call))
1679 << fdecl->getQualifiedNameAsString() << original
1680 << call->getSourceRange();
1681 return;
1683 if (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
1684 .GlobalNamespace())
1686 report(
1687 DiagnosticsEngine::Warning,
1688 ("rewrite call of '%0' with call of %1 with"
1689 " empty string constant argument as call of"
1690 " '!rtl::OUString::isEmpty'"),
1691 getMemberLocation(call))
1692 << fdecl->getQualifiedNameAsString() << original
1693 << call->getSourceRange();
1694 return;
1696 if ((dc.Operator(OO_Plus).Namespace("rtl")
1697 .GlobalNamespace())
1698 || (dc.Operator(OO_Plus).Class("OUString")
1699 .Namespace("rtl").GlobalNamespace()))
1701 report(
1702 DiagnosticsEngine::Warning,
1703 ("call of '%0' with suspicious call of %1 with"
1704 " empty string constant argument"),
1705 getMemberLocation(call))
1706 << fdecl->getQualifiedNameAsString() << original
1707 << call->getSourceRange();
1708 return;
1710 if (dc.Operator(OO_Equal).Class("OUString")
1711 .Namespace("rtl").GlobalNamespace())
1713 report(
1714 DiagnosticsEngine::Warning,
1715 ("rewrite call of '%0' with call of %1 with"
1716 " empty string constant argument as call of"
1717 " rtl::OUString::call"),
1718 getMemberLocation(call))
1719 << fdecl->getQualifiedNameAsString() << original
1720 << call->getSourceRange();
1721 return;
1723 report(
1724 DiagnosticsEngine::Warning,
1725 "TODO call inside %0", getMemberLocation(expr))
1726 << fdecl->getQualifiedNameAsString()
1727 << expr->getSourceRange();
1728 return;
1729 } else {
1730 assert(pass == PassThrough::NonEmptyConstantString);
1731 if ((dc.Function("equals").Class("OUString")
1732 .Namespace("rtl").GlobalNamespace())
1733 || (dc.Operator(OO_Equal).Class("OUString")
1734 .Namespace("rtl").GlobalNamespace())
1735 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1736 .GlobalNamespace())
1737 || (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
1738 .GlobalNamespace()))
1740 report(
1741 DiagnosticsEngine::Warning,
1742 "elide call of %0 with %1 in call of '%2'",
1743 getMemberLocation(expr))
1744 << original << describeChangeKind(kind)
1745 << fdecl->getQualifiedNameAsString()
1746 << expr->getSourceRange();
1747 return;
1749 report(
1750 DiagnosticsEngine::Warning,
1751 ("rewrite call of %0 with %1 in call of '%2' as"
1752 " (implicit) construction of 'OUString'"),
1753 getMemberLocation(expr))
1754 << original << describeChangeKind(kind)
1755 << fdecl->getQualifiedNameAsString()
1756 << expr->getSourceRange();
1757 return;
1759 } else if (isa<CXXConstructExpr>(call)) {
1760 auto classdecl = cast<CXXConstructExpr>(call)
1761 ->getConstructor()->getParent();
1762 loplugin::DeclCheck dc(classdecl);
1763 if (dc.Class("OUString").Namespace("rtl").GlobalNamespace()
1764 || (dc.Class("OUStringBuffer").Namespace("rtl")
1765 .GlobalNamespace()))
1767 //TODO: propagate further out?
1768 if (pass == PassThrough::EmptyConstantString) {
1769 report(
1770 DiagnosticsEngine::Warning,
1771 ("rewrite construction of %0 with call of %1"
1772 " with empty string constant argument as"
1773 " default construction of %0"),
1774 getMemberLocation(call))
1775 << classdecl << original
1776 << call->getSourceRange();
1777 } else {
1778 assert(pass == PassThrough::NonEmptyConstantString);
1779 report(
1780 DiagnosticsEngine::Warning,
1781 ("elide call of %0 with %1 in construction of"
1782 " %2"),
1783 getMemberLocation(expr))
1784 << original << describeChangeKind(kind)
1785 << classdecl << expr->getSourceRange();
1787 return;
1789 } else {
1790 assert(false);
1795 if (rewriter != nullptr && !nonArray && rewriteFrom != nullptr) {
1796 SourceLocation loc = getMemberLocation(expr);
1797 while (compiler.getSourceManager().isMacroArgExpansion(loc)) {
1798 loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
1800 if (compiler.getSourceManager().isMacroBodyExpansion(loc)) {
1801 loc = compiler.getSourceManager().getSpellingLoc(loc);
1803 unsigned n = Lexer::MeasureTokenLength(
1804 loc, compiler.getSourceManager(), compiler.getLangOpts());
1805 if ((std::string(compiler.getSourceManager().getCharacterData(loc), n)
1806 == rewriteFrom)
1807 && replaceText(loc, n, rewriteTo))
1809 return;
1812 report(
1813 DiagnosticsEngine::Warning,
1814 "rewrite call of '%0' with %1 as call of '%2'%3",
1815 getMemberLocation(expr))
1816 << original << describeChangeKind(kind) << replacement
1817 << adviseNonArray(nonArray) << expr->getSourceRange();
1820 void StringConstant::checkEmpty(
1821 CallExpr const * expr, FunctionDecl const * callee, TreatEmpty treatEmpty,
1822 unsigned size, std::string * replacement)
1824 assert(replacement != nullptr);
1825 if (size == 0) {
1826 switch (treatEmpty) {
1827 case TreatEmpty::DefaultCtor:
1828 *replacement = "rtl::OUString default constructor";
1829 break;
1830 case TreatEmpty::CheckEmpty:
1831 *replacement = "rtl::OUString::isEmpty";
1832 break;
1833 case TreatEmpty::Error:
1834 report(
1835 DiagnosticsEngine::Warning,
1836 "call of '%0' with suspicious empty string constant argument",
1837 getMemberLocation(expr))
1838 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1839 break;
1844 void StringConstant::handleChar(
1845 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
1846 std::string const & replacement, TreatEmpty treatEmpty, bool literal,
1847 char const * rewriteFrom, char const * rewriteTo)
1849 unsigned n;
1850 bool nonArray;
1851 ContentKind cont;
1852 bool emb;
1853 bool trm;
1854 if (!isStringConstant(
1855 expr->getArg(arg)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
1856 &emb, &trm))
1858 return;
1860 if (cont != ContentKind::Ascii) {
1861 report(
1862 DiagnosticsEngine::Warning,
1863 ("call of '%0' with string constant argument containing non-ASCII"
1864 " characters"),
1865 getMemberLocation(expr))
1866 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1867 return;
1869 if (emb) {
1870 report(
1871 DiagnosticsEngine::Warning,
1872 ("call of '%0' with string constant argument containing embedded"
1873 " NULLs"),
1874 getMemberLocation(expr))
1875 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1876 return;
1878 if (!trm) {
1879 report(
1880 DiagnosticsEngine::Warning,
1881 ("call of '%0' with string constant argument lacking a terminating"
1882 " NULL"),
1883 getMemberLocation(expr))
1884 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1885 return;
1887 std::string repl(replacement);
1888 checkEmpty(expr, callee, treatEmpty, n, &repl);
1889 if (!repl.empty()) {
1890 reportChange(
1891 expr, ChangeKind::Char, callee->getQualifiedNameAsString(), repl,
1892 (literal
1893 ? (n == 0
1894 ? PassThrough::EmptyConstantString
1895 : PassThrough::NonEmptyConstantString)
1896 : PassThrough::No),
1897 nonArray, rewriteFrom, rewriteTo);
1901 void StringConstant::handleCharLen(
1902 CallExpr const * expr, unsigned arg1, unsigned arg2,
1903 FunctionDecl const * callee, std::string const & replacement,
1904 TreatEmpty treatEmpty)
1906 // Especially for f(RTL_CONSTASCII_STRINGPARAM("foo")), where
1907 // RTL_CONSTASCII_STRINGPARAM expands to complicated expressions involving
1908 // (&(X)[0] sub-expressions (and it might or might not be better to handle
1909 // that at the level of non-expanded macros instead, but I have not found
1910 // out how to do that yet anyway):
1911 unsigned n;
1912 bool nonArray;
1913 ContentKind cont;
1914 bool emb;
1915 bool trm;
1916 if (!(isStringConstant(
1917 expr->getArg(arg1)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
1918 &emb, &trm)
1919 && trm))
1921 return;
1923 APSInt res;
1924 if (compat::EvaluateAsInt(expr->getArg(arg2), res, compiler.getASTContext())) {
1925 if (res != n) {
1926 return;
1928 } else {
1929 UnaryOperator const * op = dyn_cast<UnaryOperator>(
1930 expr->getArg(arg1)->IgnoreParenImpCasts());
1931 if (op == nullptr || op->getOpcode() != UO_AddrOf) {
1932 return;
1934 ArraySubscriptExpr const * subs = dyn_cast<ArraySubscriptExpr>(
1935 op->getSubExpr()->IgnoreParenImpCasts());
1936 if (subs == nullptr) {
1937 return;
1939 unsigned n2;
1940 bool nonArray2;
1941 ContentKind cont2;
1942 bool emb2;
1943 bool trm2;
1944 if (!(isStringConstant(
1945 subs->getBase()->IgnoreParenImpCasts(), &n2, &nonArray2,
1946 &cont2, &emb2, &trm2)
1947 && n2 == n && cont2 == cont && emb2 == emb && trm2 == trm
1948 //TODO: same strings
1949 && compat::EvaluateAsInt(subs->getIdx(), res, compiler.getASTContext())
1950 && res == 0))
1952 return;
1955 if (cont != ContentKind::Ascii) {
1956 report(
1957 DiagnosticsEngine::Warning,
1958 ("call of '%0' with string constant argument containing non-ASCII"
1959 " characters"),
1960 getMemberLocation(expr))
1961 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1963 if (emb) {
1964 return;
1966 std::string repl(replacement);
1967 checkEmpty(expr, callee, treatEmpty, n, &repl);
1968 reportChange(
1969 expr, ChangeKind::CharLen, callee->getQualifiedNameAsString(), repl,
1970 PassThrough::No, nonArray, nullptr, nullptr);
1973 void StringConstant::handleOUStringCtor(
1974 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
1975 bool explicitFunctionalCastNotation)
1977 handleOUStringCtor(expr, expr->getArg(arg), callee, explicitFunctionalCastNotation);
1980 void StringConstant::handleOStringCtor(
1981 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
1982 bool explicitFunctionalCastNotation)
1984 handleOStringCtor(expr, expr->getArg(arg), callee, explicitFunctionalCastNotation);
1987 void StringConstant::handleOUStringCtor(
1988 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
1989 bool explicitFunctionalCastNotation)
1991 handleStringCtor(expr, argExpr, callee, explicitFunctionalCastNotation, StringKind::Unicode);
1994 void StringConstant::handleOStringCtor(
1995 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
1996 bool explicitFunctionalCastNotation)
1998 handleStringCtor(expr, argExpr, callee, explicitFunctionalCastNotation, StringKind::Char);
2001 void StringConstant::handleStringCtor(
2002 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
2003 bool explicitFunctionalCastNotation, StringKind stringKind)
2005 auto e0 = argExpr->IgnoreParenImpCasts();
2006 if (auto const e1 = dyn_cast<CXXMemberCallExpr>(e0)) {
2007 if (auto const e2 = dyn_cast<CXXConversionDecl>(e1->getMethodDecl())) {
2008 if (loplugin::TypeCheck(e2->getConversionType()).ClassOrStruct("basic_string_view")
2009 .StdNamespace())
2011 e0 = e1->getImplicitObjectArgument()->IgnoreParenImpCasts();
2015 auto e1 = dyn_cast<CXXFunctionalCastExpr>(e0);
2016 if (e1 == nullptr) {
2017 if (explicitFunctionalCastNotation) {
2018 return;
2020 } else {
2021 e0 = e1->getSubExpr()->IgnoreParenImpCasts();
2023 auto e2 = dyn_cast<CXXBindTemporaryExpr>(e0);
2024 if (e2 == nullptr) {
2025 return;
2027 auto e3 = dyn_cast<CXXConstructExpr>(
2028 e2->getSubExpr()->IgnoreParenImpCasts());
2029 if (e3 == nullptr) {
2030 return;
2032 if (!loplugin::DeclCheck(e3->getConstructor()).MemberFunction()
2033 .Class(stringKind == StringKind::Unicode ? "OUString" : "OString").Namespace("rtl").GlobalNamespace())
2035 return;
2037 if (e3->getNumArgs() == 0) {
2038 report(
2039 DiagnosticsEngine::Warning,
2040 ("in call of '%0', replace default-constructed 'OUString' with an"
2041 " empty string literal"),
2042 e3->getExprLoc())
2043 << callee->getQualifiedNameAsString() << expr->getSourceRange();
2044 return;
2046 if (e3->getNumArgs() == 1
2047 && e3->getConstructor()->getNumParams() == 1
2048 && (loplugin::TypeCheck(
2049 e3->getConstructor()->getParamDecl(0)->getType())
2050 .Typedef(stringKind == StringKind::Unicode ? "sal_Unicode" : "char").GlobalNamespace()))
2052 // It may not be easy to rewrite OUString(c), esp. given there is no
2053 // OUString ctor taking an OUStringChar arg, so don't warn there:
2054 if (!explicitFunctionalCastNotation) {
2055 report(
2056 DiagnosticsEngine::Warning,
2057 ("in call of '%0', replace 'OUString' constructed from a"
2058 " 'sal_Unicode' with an 'OUStringChar'"),
2059 e3->getExprLoc())
2060 << callee->getQualifiedNameAsString() << expr->getSourceRange();
2062 return;
2064 if (e3->getNumArgs() != 2) {
2065 return;
2067 unsigned n;
2068 bool nonArray;
2069 ContentKind cont;
2070 bool emb;
2071 bool trm;
2072 if (!isStringConstant(
2073 e3->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray, &cont, &emb,
2074 &trm))
2076 return;
2078 //TODO: cont, emb, trm
2079 if (rewriter != nullptr) {
2080 auto loc1 = e3->getBeginLoc();
2081 auto range = e3->getParenOrBraceRange();
2082 if (loc1.isFileID() && range.getBegin().isFileID()
2083 && range.getEnd().isFileID())
2085 auto loc2 = range.getBegin();
2086 for (bool first = true;; first = false) {
2087 unsigned n = Lexer::MeasureTokenLength(
2088 loc2, compiler.getSourceManager(), compiler.getLangOpts());
2089 if (!first) {
2090 StringRef s(
2091 compiler.getSourceManager().getCharacterData(loc2), n);
2092 while (compat::starts_with(s, "\\\n")) {
2093 s = s.drop_front(2);
2094 while (!s.empty()
2095 && (s.front() == ' ' || s.front() == '\t'
2096 || s.front() == '\n' || s.front() == '\v'
2097 || s.front() == '\f'))
2099 s = s.drop_front(1);
2102 if (!(s.empty() || compat::starts_with(s, "/*") || compat::starts_with(s, "//")
2103 || s == "\\"))
2105 break;
2108 loc2 = loc2.getLocWithOffset(std::max<unsigned>(n, 1));
2110 auto loc3 = range.getEnd();
2111 for (;;) {
2112 auto l = Lexer::GetBeginningOfToken(
2113 loc3.getLocWithOffset(-1), compiler.getSourceManager(),
2114 compiler.getLangOpts());
2115 unsigned n = Lexer::MeasureTokenLength(
2116 l, compiler.getSourceManager(), compiler.getLangOpts());
2117 StringRef s(compiler.getSourceManager().getCharacterData(l), n);
2118 while (compat::starts_with(s, "\\\n")) {
2119 s = s.drop_front(2);
2120 while (!s.empty()
2121 && (s.front() == ' ' || s.front() == '\t'
2122 || s.front() == '\n' || s.front() == '\v'
2123 || s.front() == '\f'))
2125 s = s.drop_front(1);
2128 if (!(s.empty() || compat::starts_with(s, "/*") || compat::starts_with(s, "//")
2129 || s == "\\"))
2131 break;
2133 loc3 = l;
2135 if (removeText(CharSourceRange(SourceRange(loc1, loc2), false))) {
2136 if (removeText(SourceRange(loc3, range.getEnd()))) {
2137 return;
2139 report(DiagnosticsEngine::Fatal, "Corrupt rewrite", loc3)
2140 << expr->getSourceRange();
2141 return;
2145 report(
2146 DiagnosticsEngine::Warning,
2147 ("in call of '%0', replace 'OUString' constructed from a string literal"
2148 " directly with the string literal"),
2149 e3->getExprLoc())
2150 << callee->getQualifiedNameAsString() << expr->getSourceRange();
2153 void StringConstant::handleFunArgOstring(
2154 CallExpr const * expr, unsigned arg, FunctionDecl const * callee)
2156 auto argExpr = expr->getArg(arg)->IgnoreParenImpCasts();
2157 unsigned n;
2158 bool nonArray;
2159 ContentKind cont;
2160 bool emb;
2161 bool trm;
2162 if (isStringConstant(argExpr, &n, &nonArray, &cont, &emb, &trm)) {
2163 if (cont != ContentKind::Ascii || emb) {
2164 return;
2166 if (!trm) {
2167 report(
2168 DiagnosticsEngine::Warning,
2169 ("call of '%0' with string constant argument lacking a"
2170 " terminating NULL"),
2171 getMemberLocation(expr))
2172 << callee->getQualifiedNameAsString() << expr->getSourceRange();
2173 return;
2175 std::string repl;
2176 checkEmpty(expr, callee, TreatEmpty::Error, n, &repl);
2177 if (nonArray) {
2178 report(
2179 DiagnosticsEngine::Warning,
2180 ("in call of '%0' with non-array string constant argument,"
2181 " turn the non-array string constant into an array"),
2182 getMemberLocation(expr))
2183 << callee->getQualifiedNameAsString() << expr->getSourceRange();
2185 } else if (auto cexpr = lookForCXXConstructExpr(argExpr)) {
2186 auto classdecl = cexpr->getConstructor()->getParent();
2187 if (loplugin::DeclCheck(classdecl).Class("OString").Namespace("rtl")
2188 .GlobalNamespace())
2190 switch (cexpr->getConstructor()->getNumParams()) {
2191 case 0:
2192 report(
2193 DiagnosticsEngine::Warning,
2194 ("in call of '%0', replace empty %1 constructor with empty"
2195 " string literal"),
2196 cexpr->getLocation())
2197 << callee->getQualifiedNameAsString() << classdecl
2198 << expr->getSourceRange();
2199 break;
2200 case 2:
2201 if (isStringConstant(
2202 cexpr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray,
2203 &cont, &emb, &trm))
2205 APSInt res;
2206 if (compat::EvaluateAsInt(cexpr->getArg(1),
2207 res, compiler.getASTContext()))
2209 if (res == n && !emb && trm) {
2210 report(
2211 DiagnosticsEngine::Warning,
2212 ("in call of '%0', elide explicit %1"
2213 " constructor%2"),
2214 cexpr->getLocation())
2215 << callee->getQualifiedNameAsString()
2216 << classdecl << adviseNonArray(nonArray)
2217 << expr->getSourceRange();
2219 } else {
2220 if (emb) {
2221 report(
2222 DiagnosticsEngine::Warning,
2223 ("call of %0 constructor with string constant"
2224 " argument containing embedded NULLs"),
2225 cexpr->getLocation())
2226 << classdecl << cexpr->getSourceRange();
2227 return;
2229 if (!trm) {
2230 report(
2231 DiagnosticsEngine::Warning,
2232 ("call of %0 constructor with string constant"
2233 " argument lacking a terminating NULL"),
2234 cexpr->getLocation())
2235 << classdecl << cexpr->getSourceRange();
2236 return;
2238 report(
2239 DiagnosticsEngine::Warning,
2240 "in call of '%0', elide explicit %1 constructor%2",
2241 cexpr->getLocation())
2242 << callee->getQualifiedNameAsString() << classdecl
2243 << adviseNonArray(nonArray)
2244 << expr->getSourceRange();
2247 break;
2248 default:
2249 break;
2255 loplugin::Plugin::Registration< StringConstant > X("stringconstant", true);
2259 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */