Version 6.1.0.2, tag libreoffice-6.1.0.2
[LibreOffice.git] / compilerplugins / clang / stringconstant.cxx
blob0555b20af24a3408c89aee6c40091b9627c5a8e3
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-NUL ASCII character---except
27 // that the last array element may be NUL, or, in some situations, of type char
28 // with a ASCII value (including NUL). 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 RecursiveASTVisitor<StringConstant>, public loplugin::RewritePlugin
107 public:
108 explicit StringConstant(loplugin::InstantiationData const & data):
109 RewritePlugin(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 handleOUStringCtor(
167 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
168 bool explicitFunctionalCastNotation);
170 void handleFunArgOstring(
171 CallExpr const * expr, unsigned arg, FunctionDecl const * callee);
173 std::stack<Expr const *> calls_;
176 void StringConstant::run() {
177 if (compiler.getLangOpts().CPlusPlus
178 && compiler.getPreprocessor().getIdentifierInfo(
179 "LIBO_INTERNAL_ONLY")->hasMacroDefinition())
180 //TODO: some parts of it are useful for external code, too
182 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
186 bool StringConstant::TraverseCallExpr(CallExpr * expr) {
187 if (!WalkUpFromCallExpr(expr)) {
188 return false;
190 calls_.push(expr);
191 bool bRes = true;
192 for (auto * e: expr->children()) {
193 if (!TraverseStmt(e)) {
194 bRes = false;
195 break;
198 calls_.pop();
199 return bRes;
202 bool StringConstant::TraverseCXXMemberCallExpr(CXXMemberCallExpr * expr) {
203 if (!WalkUpFromCXXMemberCallExpr(expr)) {
204 return false;
206 calls_.push(expr);
207 bool bRes = true;
208 for (auto * e: expr->children()) {
209 if (!TraverseStmt(e)) {
210 bRes = false;
211 break;
214 calls_.pop();
215 return bRes;
218 bool StringConstant::TraverseCXXOperatorCallExpr(CXXOperatorCallExpr * expr)
220 if (!WalkUpFromCXXOperatorCallExpr(expr)) {
221 return false;
223 calls_.push(expr);
224 bool bRes = true;
225 for (auto * e: expr->children()) {
226 if (!TraverseStmt(e)) {
227 bRes = false;
228 break;
231 calls_.pop();
232 return bRes;
235 bool StringConstant::TraverseCXXConstructExpr(CXXConstructExpr * expr) {
236 if (!WalkUpFromCXXConstructExpr(expr)) {
237 return false;
239 calls_.push(expr);
240 bool bRes = true;
241 for (auto * e: expr->children()) {
242 if (!TraverseStmt(e)) {
243 bRes = false;
244 break;
247 calls_.pop();
248 return bRes;
251 bool StringConstant::VisitCallExpr(CallExpr const * expr) {
252 if (ignoreLocation(expr)) {
253 return true;
255 FunctionDecl const * fdecl = expr->getDirectCallee();
256 if (fdecl == nullptr) {
257 return true;
259 for (unsigned i = 0; i != fdecl->getNumParams(); ++i) {
260 auto t = fdecl->getParamDecl(i)->getType();
261 if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
262 .LvalueReference().Const().NotSubstTemplateTypeParmType()
263 .Class("OUString").Namespace("rtl").GlobalNamespace())
265 if (!(isLhsOfAssignment(fdecl, i)
266 || hasOverloads(fdecl, expr->getNumArgs())))
268 handleOUStringCtor(expr, i, fdecl, true);
272 loplugin::DeclCheck dc(fdecl);
273 //TODO: u.compareToAscii("foo") -> u.???("foo")
274 //TODO: u.compareToIgnoreAsciiCaseAscii("foo") -> u.???("foo")
275 if ((dc.Function("createFromAscii").Class("OUString").Namespace("rtl")
276 .GlobalNamespace())
277 && fdecl->getNumParams() == 1)
279 // OUString::createFromAscii("foo") -> OUString("foo")
280 handleChar(
281 expr, 0, fdecl, "rtl::OUString constructor",
282 TreatEmpty::DefaultCtor, true);
283 return true;
285 if ((dc.Function("endsWithAsciiL").Class("OUString").Namespace("rtl")
286 .GlobalNamespace())
287 && fdecl->getNumParams() == 2)
289 // u.endsWithAsciiL("foo", 3) -> u.endsWith("foo"):
290 handleCharLen(
291 expr, 0, 1, fdecl, "rtl::OUString::endsWith", TreatEmpty::Error);
292 return true;
294 if ((dc.Function("endsWithIgnoreAsciiCaseAsciiL").Class("OUString")
295 .Namespace("rtl").GlobalNamespace())
296 && fdecl->getNumParams() == 2)
298 // u.endsWithIgnoreAsciiCaseAsciiL("foo", 3) ->
299 // u.endsWithIgnoreAsciiCase("foo"):
300 handleCharLen(
301 expr, 0, 1, fdecl, "rtl::OUString::endsWithIgnoreAsciiCase",
302 TreatEmpty::Error);
303 return true;
305 if ((dc.Function("equalsAscii").Class("OUString").Namespace("rtl")
306 .GlobalNamespace())
307 && fdecl->getNumParams() == 1)
309 // u.equalsAscii("foo") -> u == "foo":
310 handleChar(
311 expr, 0, fdecl, "operator ==", TreatEmpty::CheckEmpty, false);
312 return true;
314 if ((dc.Function("equalsAsciiL").Class("OUString").Namespace("rtl")
315 .GlobalNamespace())
316 && fdecl->getNumParams() == 2)
318 // u.equalsAsciiL("foo", 3) -> u == "foo":
319 handleCharLen(expr, 0, 1, fdecl, "operator ==", TreatEmpty::CheckEmpty);
320 return true;
322 if ((dc.Function("equalsIgnoreAsciiCaseAscii").Class("OUString")
323 .Namespace("rtl").GlobalNamespace())
324 && fdecl->getNumParams() == 1)
326 // u.equalsIgnoreAsciiCaseAscii("foo") ->
327 // u.equalsIngoreAsciiCase("foo"):
328 auto file = compiler.getSourceManager().getFilename(
329 compiler.getSourceManager().getSpellingLoc(expr->getLocStart()));
330 if (loplugin::isSamePathname(
331 file, SRCDIR "/sal/qa/rtl/strings/test_oustring_compare.cxx"))
333 return true;
335 handleChar(
336 expr, 0, fdecl, "rtl::OUString::equalsIgnoreAsciiCase",
337 TreatEmpty::CheckEmpty, false);
338 return true;
340 if ((dc.Function("equalsIgnoreAsciiCaseAsciiL").Class("OUString")
341 .Namespace("rtl").GlobalNamespace())
342 && fdecl->getNumParams() == 2)
344 // u.equalsIgnoreAsciiCaseAsciiL("foo", 3) ->
345 // u.equalsIngoreAsciiCase("foo"):
346 auto file = compiler.getSourceManager().getFilename(
347 compiler.getSourceManager().getSpellingLoc(expr->getLocStart()));
348 if (loplugin::isSamePathname(
349 file, SRCDIR "/sal/qa/rtl/strings/test_oustring_compare.cxx"))
351 return true;
353 handleCharLen(
354 expr, 0, 1, fdecl, "rtl::OUString::equalsIgnoreAsciiCase",
355 TreatEmpty::CheckEmpty);
356 return true;
358 if ((dc.Function("indexOfAsciiL").Class("OUString").Namespace("rtl")
359 .GlobalNamespace())
360 && fdecl->getNumParams() == 3)
362 assert(expr->getNumArgs() == 3);
363 // u.indexOfAsciiL("foo", 3, i) -> u.indexOf("foo", i):
364 handleCharLen(
365 expr, 0, 1, fdecl, "rtl::OUString::indexOf", TreatEmpty::Error);
366 return true;
368 if ((dc.Function("lastIndexOfAsciiL").Class("OUString").Namespace("rtl")
369 .GlobalNamespace())
370 && fdecl->getNumParams() == 2)
372 // u.lastIndexOfAsciiL("foo", 3) -> u.lastIndexOf("foo"):
373 handleCharLen(
374 expr, 0, 1, fdecl, "rtl::OUString::lastIndexOf", TreatEmpty::Error);
375 return true;
377 if ((dc.Function("matchAsciiL").Class("OUString").Namespace("rtl")
378 .GlobalNamespace())
379 && fdecl->getNumParams() == 3)
381 assert(expr->getNumArgs() == 3);
382 // u.matchAsciiL("foo", 3, i) -> u.match("foo", i):
383 handleCharLen(
384 expr, 0, 1, fdecl,
385 (isZero(expr->getArg(2))
386 ? std::string("rtl::OUString::startsWith")
387 : std::string("rtl::OUString::match")),
388 TreatEmpty::Error);
389 return true;
391 if ((dc.Function("matchIgnoreAsciiCaseAsciiL").Class("OUString")
392 .Namespace("rtl").GlobalNamespace())
393 && fdecl->getNumParams() == 3)
395 assert(expr->getNumArgs() == 3);
396 // u.matchIgnoreAsciiCaseAsciiL("foo", 3, i) ->
397 // u.matchIgnoreAsciiCase("foo", i):
398 handleCharLen(
399 expr, 0, 1, fdecl,
400 (isZero(expr->getArg(2))
401 ? std::string("rtl::OUString::startsWithIgnoreAsciiCase")
402 : std::string("rtl::OUString::matchIgnoreAsciiCase")),
403 TreatEmpty::Error);
404 return true;
406 if ((dc.Function("reverseCompareToAsciiL").Class("OUString")
407 .Namespace("rtl").GlobalNamespace())
408 && fdecl->getNumParams() == 2)
410 // u.reverseCompareToAsciiL("foo", 3) -> u.reverseCompareTo("foo"):
411 handleCharLen(
412 expr, 0, 1, fdecl, "rtl::OUString::reverseCompareTo",
413 TreatEmpty::Error);
414 return true;
416 if ((dc.Function("reverseCompareTo").Class("OUString").Namespace("rtl")
417 .GlobalNamespace())
418 && fdecl->getNumParams() == 1)
420 handleOUStringCtor(expr, 0, fdecl, false);
421 return true;
423 if ((dc.Function("equalsIgnoreAsciiCase").Class("OUString").Namespace("rtl")
424 .GlobalNamespace())
425 && fdecl->getNumParams() == 1)
427 handleOUStringCtor(expr, 0, fdecl, false);
428 return true;
430 if ((dc.Function("match").Class("OUString").Namespace("rtl")
431 .GlobalNamespace())
432 && fdecl->getNumParams() == 2)
434 handleOUStringCtor(expr, 0, fdecl, false);
435 return true;
437 if ((dc.Function("matchIgnoreAsciiCase").Class("OUString").Namespace("rtl")
438 .GlobalNamespace())
439 && fdecl->getNumParams() == 2)
441 handleOUStringCtor(expr, 0, fdecl, false);
442 return true;
444 if ((dc.Function("startsWith").Class("OUString").Namespace("rtl")
445 .GlobalNamespace())
446 && fdecl->getNumParams() == 2)
448 handleOUStringCtor(expr, 0, fdecl, false);
449 return true;
451 if ((dc.Function("startsWithIgnoreAsciiCase").Class("OUString")
452 .Namespace("rtl").GlobalNamespace())
453 && fdecl->getNumParams() == 2)
455 handleOUStringCtor(expr, 0, fdecl, false);
456 return true;
458 if ((dc.Function("endsWith").Class("OUString").Namespace("rtl")
459 .GlobalNamespace())
460 && fdecl->getNumParams() == 2)
462 handleOUStringCtor(expr, 0, fdecl, false);
463 return true;
465 if ((dc.Function("endsWithIgnoreAsciiCase").Class("OUString")
466 .Namespace("rtl").GlobalNamespace())
467 && fdecl->getNumParams() == 2)
469 handleOUStringCtor(expr, 0, fdecl, false);
470 return true;
472 if ((dc.Function("indexOf").Class("OUString").Namespace("rtl")
473 .GlobalNamespace())
474 && fdecl->getNumParams() == 2)
476 handleOUStringCtor(expr, 0, fdecl, false);
477 return true;
479 if ((dc.Function("lastIndexOf").Class("OUString").Namespace("rtl")
480 .GlobalNamespace())
481 && fdecl->getNumParams() == 1)
483 handleOUStringCtor(expr, 0, fdecl, false);
484 return true;
486 if ((dc.Function("replaceFirst").Class("OUString").Namespace("rtl")
487 .GlobalNamespace())
488 && fdecl->getNumParams() == 3)
490 handleOUStringCtor(expr, 0, fdecl, false);
491 handleOUStringCtor(expr, 1, fdecl, false);
492 return true;
494 if ((dc.Function("replaceAll").Class("OUString").Namespace("rtl")
495 .GlobalNamespace())
496 && (fdecl->getNumParams() == 2 || fdecl->getNumParams() == 3))
498 handleOUStringCtor(expr, 0, fdecl, false);
499 handleOUStringCtor(expr, 1, fdecl, false);
500 return true;
502 if ((dc.Operator(OO_PlusEqual).Class("OUString").Namespace("rtl")
503 .GlobalNamespace())
504 && fdecl->getNumParams() == 1)
506 handleOUStringCtor(
507 expr, dyn_cast<CXXOperatorCallExpr>(expr) == nullptr ? 0 : 1,
508 fdecl, false);
509 return true;
511 if ((dc.Function("equals").Class("OUString").Namespace("rtl")
512 .GlobalNamespace())
513 && fdecl->getNumParams() == 1)
515 unsigned n;
516 bool nonArray;
517 ContentKind cont;
518 bool emb;
519 bool trm;
520 if (!isStringConstant(
521 expr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
522 &emb, &trm))
524 return true;
526 if (cont != ContentKind::Ascii) {
527 report(
528 DiagnosticsEngine::Warning,
529 ("call of '%0' with string constant argument containing"
530 " non-ASCII characters"),
531 expr->getExprLoc())
532 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
534 if (emb) {
535 report(
536 DiagnosticsEngine::Warning,
537 ("call of '%0' with string constant argument containing"
538 " embedded NULs"),
539 expr->getExprLoc())
540 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
542 if (n == 0) {
543 report(
544 DiagnosticsEngine::Warning,
545 ("rewrite call of '%0' with empty string constant argument as"
546 " call of 'rtl::OUString::isEmpty'"),
547 expr->getExprLoc())
548 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
549 return true;
552 if (dc.Operator(OO_EqualEqual).Namespace("rtl").GlobalNamespace()
553 && fdecl->getNumParams() == 2)
555 for (unsigned i = 0; i != 2; ++i) {
556 unsigned n;
557 bool nonArray;
558 ContentKind cont;
559 bool emb;
560 bool trm;
561 if (!isStringConstant(
562 expr->getArg(i)->IgnoreParenImpCasts(), &n, &nonArray,
563 &cont, &emb, &trm))
565 continue;
567 if (cont != ContentKind::Ascii) {
568 report(
569 DiagnosticsEngine::Warning,
570 ("call of '%0' with string constant argument containing"
571 " non-ASCII characters"),
572 expr->getExprLoc())
573 << fdecl->getQualifiedNameAsString()
574 << expr->getSourceRange();
576 if (emb) {
577 report(
578 DiagnosticsEngine::Warning,
579 ("call of '%0' with string constant argument containing"
580 " embedded NULs"),
581 expr->getExprLoc())
582 << fdecl->getQualifiedNameAsString()
583 << expr->getSourceRange();
585 if (n == 0) {
586 report(
587 DiagnosticsEngine::Warning,
588 ("rewrite call of '%0' with empty string constant argument"
589 " as call of 'rtl::OUString::isEmpty'"),
590 expr->getExprLoc())
591 << fdecl->getQualifiedNameAsString()
592 << expr->getSourceRange();
595 return true;
597 if (dc.Operator(OO_ExclaimEqual).Namespace("rtl").GlobalNamespace()
598 && fdecl->getNumParams() == 2)
600 for (unsigned i = 0; i != 2; ++i) {
601 unsigned n;
602 bool nonArray;
603 ContentKind cont;
604 bool emb;
605 bool trm;
606 if (!isStringConstant(
607 expr->getArg(i)->IgnoreParenImpCasts(), &n, &nonArray,
608 &cont, &emb, &trm))
610 continue;
612 if (cont != ContentKind::Ascii) {
613 report(
614 DiagnosticsEngine::Warning,
615 ("call of '%0' with string constant argument containing"
616 " non-ASCII characters"),
617 expr->getExprLoc())
618 << fdecl->getQualifiedNameAsString()
619 << expr->getSourceRange();
621 if (emb) {
622 report(
623 DiagnosticsEngine::Warning,
624 ("call of '%0' with string constant argument containing"
625 " embedded NULs"),
626 expr->getExprLoc())
627 << fdecl->getQualifiedNameAsString()
628 << expr->getSourceRange();
630 if (n == 0) {
631 report(
632 DiagnosticsEngine::Warning,
633 ("rewrite call of '%0' with empty string constant argument"
634 " as call of '!rtl::OUString::isEmpty'"),
635 expr->getExprLoc())
636 << fdecl->getQualifiedNameAsString()
637 << expr->getSourceRange();
640 return true;
642 if (dc.Operator(OO_Equal).Namespace("rtl").GlobalNamespace()
643 && fdecl->getNumParams() == 1)
645 unsigned n;
646 bool nonArray;
647 ContentKind cont;
648 bool emb;
649 bool trm;
650 if (!isStringConstant(
651 expr->getArg(1)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
652 &emb, &trm))
654 return true;
656 if (cont != ContentKind::Ascii) {
657 report(
658 DiagnosticsEngine::Warning,
659 ("call of '%0' with string constant argument containing"
660 " non-ASCII characters"),
661 expr->getExprLoc())
662 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
664 if (emb) {
665 report(
666 DiagnosticsEngine::Warning,
667 ("call of '%0' with string constant argument containing"
668 " embedded NULs"),
669 expr->getExprLoc())
670 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
672 if (n == 0) {
673 report(
674 DiagnosticsEngine::Warning,
675 ("rewrite call of '%0' with empty string constant argument as"
676 " call of 'rtl::OUString::clear'"),
677 expr->getExprLoc())
678 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
679 return true;
681 return true;
683 if ((dc.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
684 .GlobalNamespace())
685 && fdecl->getNumParams() == 1)
687 // u.appendAscii("foo") -> u.append("foo")
688 handleChar(
689 expr, 0, fdecl, "rtl::OUStringBuffer::append", TreatEmpty::Error,
690 true, "appendAscii", "append");
691 return true;
693 if ((dc.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
694 .GlobalNamespace())
695 && fdecl->getNumParams() == 2)
697 // u.appendAscii("foo", 3) -> u.append("foo"):
698 handleCharLen(
699 expr, 0, 1, fdecl, "rtl::OUStringBuffer::append",
700 TreatEmpty::Error);
701 return true;
703 if (dc.Function("append").Class("OStringBuffer").Namespace("rtl")
704 .GlobalNamespace())
706 switch (fdecl->getNumParams()) {
707 case 1:
708 handleFunArgOstring(expr, 0, fdecl);
709 break;
710 case 2:
712 // b.append("foo", 3) -> b.append("foo"):
713 auto file = compiler.getSourceManager().getFilename(
714 compiler.getSourceManager().getSpellingLoc(
715 expr->getLocStart()));
716 if (loplugin::isSamePathname(
717 file,
718 SRCDIR "/sal/qa/OStringBuffer/rtl_OStringBuffer.cxx"))
720 return true;
722 handleCharLen(
723 expr, 0, 1, fdecl, "rtl::OStringBuffer::append",
724 TreatEmpty::Error);
726 break;
727 default:
728 break;
730 return true;
732 if (dc.Function("insert").Class("OStringBuffer").Namespace("rtl")
733 .GlobalNamespace())
735 switch (fdecl->getNumParams()) {
736 case 2:
737 handleFunArgOstring(expr, 1, fdecl);
738 break;
739 case 3:
741 // b.insert(i, "foo", 3) -> b.insert(i, "foo"):
742 handleCharLen(
743 expr, 1, 2, fdecl, "rtl::OStringBuffer::insert",
744 TreatEmpty::Error);
745 break;
747 default:
748 break;
750 return true;
752 return true;
755 bool StringConstant::VisitCXXConstructExpr(CXXConstructExpr const * expr) {
756 if (ignoreLocation(expr)) {
757 return true;
759 auto classdecl = expr->getConstructor()->getParent();
760 if (loplugin::DeclCheck(classdecl)
761 .Class("OUString").Namespace("rtl").GlobalNamespace())
763 ChangeKind kind;
764 PassThrough pass;
765 bool simplify;
766 switch (expr->getConstructor()->getNumParams()) {
767 case 1:
768 if (!loplugin::TypeCheck(
769 expr->getConstructor()->getParamDecl(0)->getType())
770 .Typedef("sal_Unicode").GlobalNamespace())
772 return true;
774 kind = ChangeKind::SingleChar;
775 pass = PassThrough::NonEmptyConstantString;
776 simplify = false;
777 break;
778 case 2:
780 auto arg = expr->getArg(0);
781 if (loplugin::TypeCheck(arg->getType())
782 .Class("OUStringLiteral1_").Namespace("rtl")
783 .GlobalNamespace())
785 kind = ChangeKind::OUStringLiteral1;
786 pass = PassThrough::NonEmptyConstantString;
787 simplify = false;
788 } else {
789 unsigned n;
790 bool nonArray;
791 ContentKind cont;
792 bool emb;
793 bool trm;
794 if (!isStringConstant(
795 arg->IgnoreParenImpCasts(), &n, &nonArray, &cont,
796 &emb, &trm))
798 return true;
800 // OSL_THIS_FUNC may be defined as "" or as something other
801 // than a string literal in include/osl/diagnose.h:
802 auto loc = arg->getLocStart();
803 if (compiler.getSourceManager().isMacroBodyExpansion(loc)
804 && (Lexer::getImmediateMacroName(
805 loc, compiler.getSourceManager(),
806 compiler.getLangOpts())
807 == "OSL_THIS_FUNC"))
809 return true;
811 if (cont != ContentKind::Ascii) {
812 report(
813 DiagnosticsEngine::Warning,
814 ("construction of %0 with string constant argument"
815 " containing non-ASCII characters"),
816 expr->getExprLoc())
817 << classdecl << expr->getSourceRange();
819 if (emb) {
820 report(
821 DiagnosticsEngine::Warning,
822 ("construction of %0 with string constant argument"
823 " containing embedded NULs"),
824 expr->getExprLoc())
825 << classdecl << expr->getSourceRange();
827 kind = ChangeKind::Char;
828 pass = n == 0
829 ? PassThrough::EmptyConstantString
830 : PassThrough::NonEmptyConstantString;
831 simplify = false;
833 break;
835 case 4:
837 unsigned n;
838 bool nonArray;
839 ContentKind cont;
840 bool emb;
841 bool trm;
842 std::vector<char32_t> utf8Cont;
843 if (!isStringConstant(
844 expr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray,
845 &cont, &emb, &trm, &utf8Cont))
847 return true;
849 APSInt res;
850 if (!expr->getArg(1)->EvaluateAsInt(
851 res, compiler.getASTContext()))
853 return true;
855 if (res != n) {
856 report(
857 DiagnosticsEngine::Warning,
858 ("suspicious 'rtl::OUString' constructor with literal"
859 " of length %0 and non-matching length argument %1"),
860 expr->getExprLoc())
861 << n << res.toString(10) << expr->getSourceRange();
862 return true;
864 APSInt enc;
865 if (!expr->getArg(2)->EvaluateAsInt(
866 enc, compiler.getASTContext()))
868 return true;
870 auto const encIsAscii = enc == 11; // RTL_TEXTENCODING_ASCII_US
871 auto const encIsUtf8 = enc == 76; // RTL_TEXTENCODING_UTF8
872 if (!expr->getArg(3)->EvaluateAsInt(
873 res, compiler.getASTContext())
874 || res != 0x333) // OSTRING_TO_OUSTRING_CVTFLAGS
876 return true;
878 if (!encIsAscii && cont == ContentKind::Ascii) {
879 report(
880 DiagnosticsEngine::Warning,
881 ("suspicious 'rtl::OUString' constructor with text"
882 " encoding %0 but plain ASCII content; use"
883 " 'RTL_TEXTENCODING_ASCII_US' instead"),
884 expr->getArg(2)->getExprLoc())
885 << enc.toString(10) << expr->getSourceRange();
886 return true;
888 if (encIsUtf8) {
889 if (cont == ContentKind::Arbitrary) {
890 report(
891 DiagnosticsEngine::Warning,
892 ("suspicious 'rtl::OUString' constructor with text"
893 " encoding 'RTL_TEXTENCODING_UTF8' but non-UTF-8"
894 " content"),
895 expr->getArg(0)->getExprLoc())
896 << expr->getSourceRange();
897 } else {
898 assert(cont == ContentKind::Utf8);
899 //TODO: keep original content as much as possible
900 std::ostringstream s;
901 for (auto const c: utf8Cont) {
902 if (c == '\\') {
903 s << "\\\\";
904 } else if (c == '"') {
905 s << "\\\"";
906 } else if (c == '\a') {
907 s << "\\a";
908 } else if (c == '\b') {
909 s << "\\b";
910 } else if (c == '\f') {
911 s << "\\f";
912 } else if (c == '\n') {
913 s << "\\n";
914 } else if (c == '\r') {
915 s << "\\r";
916 } else if (c == '\t') {
917 s << "\\r";
918 } else if (c == '\v') {
919 s << "\\v";
920 } else if (c <= 0x1F || c == 0x7F) {
921 s << "\\x" << std::oct << std::setw(3)
922 << std::setfill('0')
923 << static_cast<std::uint_least32_t>(c);
924 } else if (c < 0x7F) {
925 s << char(c);
926 } else if (c <= 0xFFFF) {
927 s << "\\u" << std::hex << std::uppercase
928 << std::setw(4) << std::setfill('0')
929 << static_cast<std::uint_least32_t>(c);
930 } else {
931 assert(c <= 0x10FFFF);
932 s << "\\U" << std::hex << std::uppercase
933 << std::setw(8) << std::setfill('0')
934 << static_cast<std::uint_least32_t>(c);
937 report(
938 DiagnosticsEngine::Warning,
939 ("simplify construction of %0 with UTF-8 content as"
940 " OUString(u\"%1\")"),
941 expr->getExprLoc())
942 << classdecl << s.str() << expr->getSourceRange();
945 return true;
947 if (cont != ContentKind::Ascii || emb) {
948 // cf. remaining uses of RTL_CONSTASCII_USTRINGPARAM
949 return true;
951 kind = ChangeKind::Char;
952 pass = n == 0
953 ? PassThrough::EmptyConstantString
954 : PassThrough::NonEmptyConstantString;
955 simplify = true;
956 break;
958 default:
959 return true;
961 if (!calls_.empty()) {
962 Expr const * call = calls_.top();
963 CallExpr::const_arg_iterator argsBeg;
964 CallExpr::const_arg_iterator argsEnd;
965 if (isa<CallExpr>(call)) {
966 argsBeg = cast<CallExpr>(call)->arg_begin();
967 argsEnd = cast<CallExpr>(call)->arg_end();
968 } else if (isa<CXXConstructExpr>(call)) {
969 argsBeg = cast<CXXConstructExpr>(call)->arg_begin();
970 argsEnd = cast<CXXConstructExpr>(call)->arg_end();
971 } else {
972 assert(false);
974 for (auto i(argsBeg); i != argsEnd; ++i) {
975 Expr const * e = (*i)->IgnoreParenImpCasts();
976 if (isa<MaterializeTemporaryExpr>(e)) {
977 e = cast<MaterializeTemporaryExpr>(e)->GetTemporaryExpr()
978 ->IgnoreParenImpCasts();
980 if (isa<CXXFunctionalCastExpr>(e)) {
981 e = cast<CXXFunctionalCastExpr>(e)->getSubExpr()
982 ->IgnoreParenImpCasts();
984 if (isa<CXXBindTemporaryExpr>(e)) {
985 e = cast<CXXBindTemporaryExpr>(e)->getSubExpr()
986 ->IgnoreParenImpCasts();
988 if (e == expr) {
989 if (isa<CallExpr>(call)) {
990 FunctionDecl const * fdecl
991 = cast<CallExpr>(call)->getDirectCallee();
992 if (fdecl == nullptr) {
993 break;
995 loplugin::DeclCheck dc(fdecl);
996 if (pass == PassThrough::EmptyConstantString) {
997 if ((dc.Function("equals").Class("OUString")
998 .Namespace("rtl").GlobalNamespace())
999 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1000 .GlobalNamespace()))
1002 report(
1003 DiagnosticsEngine::Warning,
1004 ("rewrite call of '%0' with construction of"
1005 " %1 with empty string constant argument"
1006 " as call of 'rtl::OUString::isEmpty'"),
1007 getMemberLocation(call))
1008 << fdecl->getQualifiedNameAsString()
1009 << classdecl << call->getSourceRange();
1010 return true;
1012 if (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
1013 .GlobalNamespace())
1015 report(
1016 DiagnosticsEngine::Warning,
1017 ("rewrite call of '%0' with construction of"
1018 " %1 with empty string constant argument"
1019 " as call of '!rtl::OUString::isEmpty'"),
1020 getMemberLocation(call))
1021 << fdecl->getQualifiedNameAsString()
1022 << classdecl << call->getSourceRange();
1023 return true;
1025 if ((dc.Operator(OO_Plus).Namespace("rtl")
1026 .GlobalNamespace())
1027 || (dc.Operator(OO_Plus).Class("OUString")
1028 .Namespace("rtl").GlobalNamespace()))
1030 report(
1031 DiagnosticsEngine::Warning,
1032 ("call of '%0' with suspicious construction"
1033 " of %1 with empty string constant"
1034 " argument"),
1035 getMemberLocation(call))
1036 << fdecl->getQualifiedNameAsString()
1037 << classdecl << call->getSourceRange();
1038 return true;
1040 if (dc.Operator(OO_Equal).Class("OUString")
1041 .Namespace("rtl").GlobalNamespace())
1043 report(
1044 DiagnosticsEngine::Warning,
1045 ("rewrite call of '%0' with construction of"
1046 " %1 with empty string constant argument"
1047 " as call of 'rtl::OUString::clear'"),
1048 getMemberLocation(call))
1049 << fdecl->getQualifiedNameAsString()
1050 << classdecl << call->getSourceRange();
1051 return true;
1053 } else {
1054 assert(pass == PassThrough::NonEmptyConstantString);
1055 if (dc.Function("equals").Class("OUString")
1056 .Namespace("rtl").GlobalNamespace())
1058 report(
1059 DiagnosticsEngine::Warning,
1060 ("rewrite call of '%0' with construction of"
1061 " %1 with %2 as 'operator =='"),
1062 getMemberLocation(call))
1063 << fdecl->getQualifiedNameAsString()
1064 << classdecl << describeChangeKind(kind)
1065 << call->getSourceRange();
1066 return true;
1068 if ((dc.Operator(OO_Plus).Namespace("rtl")
1069 .GlobalNamespace())
1070 || (dc.Operator(OO_Plus).Class("OUString")
1071 .Namespace("rtl").GlobalNamespace())
1072 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1073 .GlobalNamespace())
1074 || (dc.Operator(OO_ExclaimEqual)
1075 .Namespace("rtl").GlobalNamespace()))
1077 if (dc.Operator(OO_Plus).Namespace("rtl")
1078 .GlobalNamespace())
1080 auto file =
1081 compiler.getSourceManager().getFilename(
1082 compiler.getSourceManager()
1083 .getSpellingLoc(
1084 expr->getLocStart()));
1085 if (loplugin::isSamePathname(
1086 file,
1087 (SRCDIR
1088 "/sal/qa/rtl/strings/test_ostring_concat.cxx"))
1089 || loplugin::isSamePathname(
1090 file,
1091 (SRCDIR
1092 "/sal/qa/rtl/strings/test_oustring_concat.cxx")))
1094 return true;
1097 auto loc = expr->getArg(0)->getLocStart();
1098 while (compiler.getSourceManager()
1099 .isMacroArgExpansion(loc))
1101 loc = compiler.getSourceManager()
1102 .getImmediateMacroCallerLoc(loc);
1104 if ((compiler.getSourceManager()
1105 .isMacroBodyExpansion(loc))
1106 && (Lexer::getImmediateMacroName(
1107 loc, compiler.getSourceManager(),
1108 compiler.getLangOpts())
1109 == "OSL_THIS_FUNC"))
1111 return true;
1113 if (kind == ChangeKind::SingleChar) {
1114 report(
1115 DiagnosticsEngine::Warning,
1116 ("rewrite construction of %0 with %1 in"
1117 " call of '%2' as construction of"
1118 " 'OUStringLiteral1'"),
1119 getMemberLocation(expr))
1120 << classdecl << describeChangeKind(kind)
1121 << fdecl->getQualifiedNameAsString()
1122 << expr->getSourceRange();
1123 } else {
1124 report(
1125 DiagnosticsEngine::Warning,
1126 ("elide construction of %0 with %1 in"
1127 " call of '%2'"),
1128 getMemberLocation(expr))
1129 << classdecl << describeChangeKind(kind)
1130 << fdecl->getQualifiedNameAsString()
1131 << expr->getSourceRange();
1133 return true;
1136 } else if (isa<CXXConstructExpr>(call)) {
1137 } else {
1138 assert(false);
1143 if (simplify) {
1144 report(
1145 DiagnosticsEngine::Warning,
1146 "simplify construction of %0 with %1", expr->getExprLoc())
1147 << classdecl << describeChangeKind(kind)
1148 << expr->getSourceRange();
1150 return true;
1153 auto consDecl = expr->getConstructor();
1154 for (unsigned i = 0; i != consDecl->getNumParams(); ++i) {
1155 auto t = consDecl->getParamDecl(i)->getType();
1156 if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
1157 .LvalueReference().Const().NotSubstTemplateTypeParmType()
1158 .Class("OUString").Namespace("rtl").GlobalNamespace())
1160 auto argExpr = expr->getArg(i);
1161 if (argExpr && i <= consDecl->getNumParams())
1163 if (!hasOverloads(consDecl, expr->getNumArgs()))
1165 handleOUStringCtor(expr, argExpr, consDecl, true);
1171 return true;
1174 std::string StringConstant::describeChangeKind(ChangeKind kind) {
1175 switch (kind) {
1176 case ChangeKind::Char:
1177 return "string constant argument";
1178 case ChangeKind::CharLen:
1179 return "string constant and matching length arguments";
1180 case ChangeKind::SingleChar:
1181 return "sal_Unicode argument";
1182 case ChangeKind::OUStringLiteral1:
1183 return "OUStringLiteral1 argument";
1184 default:
1185 std::abort();
1189 bool StringConstant::isStringConstant(
1190 Expr const * expr, unsigned * size, bool * nonArray, ContentKind * content,
1191 bool * embeddedNuls, bool * terminatingNul,
1192 std::vector<char32_t> * utf8Content)
1194 assert(expr != nullptr);
1195 assert(size != nullptr);
1196 assert(nonArray != nullptr);
1197 assert(content != nullptr);
1198 assert(embeddedNuls != nullptr);
1199 assert(terminatingNul != nullptr);
1200 QualType t = expr->getType();
1201 // Look inside RTL_CONSTASCII_STRINGPARAM:
1202 if (loplugin::TypeCheck(t).Pointer().Const().Char()) {
1203 auto e2 = dyn_cast<UnaryOperator>(expr);
1204 if (e2 != nullptr && e2->getOpcode() == UO_AddrOf) {
1205 auto e3 = dyn_cast<ArraySubscriptExpr>(
1206 e2->getSubExpr()->IgnoreParenImpCasts());
1207 if (e3 == nullptr || !isZero(e3->getIdx()->IgnoreParenImpCasts())) {
1208 return false;
1210 expr = e3->getBase()->IgnoreParenImpCasts();
1211 t = expr->getType();
1214 if (!t.isConstQualified()) {
1215 return false;
1217 DeclRefExpr const * dre = dyn_cast<DeclRefExpr>(expr);
1218 if (dre != nullptr) {
1219 VarDecl const * var = dyn_cast<VarDecl>(dre->getDecl());
1220 if (var != nullptr) {
1221 Expr const * init = var->getAnyInitializer();
1222 if (init != nullptr) {
1223 expr = init->IgnoreParenImpCasts();
1227 bool isPtr;
1228 if (loplugin::TypeCheck(t).Pointer().Const().Char()) {
1229 isPtr = true;
1230 } else if (t->isConstantArrayType()
1231 && (loplugin::TypeCheck(
1232 t->getAsArrayTypeUnsafe()->getElementType())
1233 .Char()))
1235 isPtr = false;
1236 } else {
1237 return false;
1239 clang::StringLiteral const * lit = dyn_cast<clang::StringLiteral>(expr);
1240 if (lit != nullptr) {
1241 if (!(lit->isAscii() || lit->isUTF8())) {
1242 return false;
1244 unsigned n = lit->getLength();
1245 ContentKind cont = ContentKind::Ascii;
1246 bool emb = false;
1247 char32_t val = 0;
1248 enum class Utf8State { Start, E0, EB, F0, F4, Trail1, Trail2, Trail3 };
1249 Utf8State s = Utf8State::Start;
1250 StringRef str = lit->getString();
1251 for (unsigned i = 0; i != n; ++i) {
1252 auto const c = static_cast<unsigned char>(str[i]);
1253 if (c == '\0') {
1254 emb = true;
1256 switch (s) {
1257 case Utf8State::Start:
1258 if (c >= 0x80) {
1259 if (c >= 0xC2 && c <= 0xDF) {
1260 val = c & 0x1F;
1261 s = Utf8State::Trail1;
1262 } else if (c == 0xE0) {
1263 val = c & 0x0F;
1264 s = Utf8State::E0;
1265 } else if ((c >= 0xE1 && c <= 0xEA)
1266 || (c >= 0xEE && c <= 0xEF))
1268 val = c & 0x0F;
1269 s = Utf8State::Trail2;
1270 } else if (c == 0xEB) {
1271 val = c & 0x0F;
1272 s = Utf8State::EB;
1273 } else if (c == 0xF0) {
1274 val = c & 0x03;
1275 s = Utf8State::F0;
1276 } else if (c >= 0xF1 && c <= 0xF3) {
1277 val = c & 0x03;
1278 s = Utf8State::Trail3;
1279 } else if (c == 0xF4) {
1280 val = c & 0x03;
1281 s = Utf8State::F4;
1282 } else {
1283 cont = ContentKind::Arbitrary;
1285 } else if (utf8Content != nullptr
1286 && cont != ContentKind::Arbitrary)
1288 utf8Content->push_back(c);
1290 break;
1291 case Utf8State::E0:
1292 if (c >= 0xA0 && c <= 0xBF) {
1293 val = (val << 6) | (c & 0x3F);
1294 s = Utf8State::Trail1;
1295 } else {
1296 cont = ContentKind::Arbitrary;
1297 s = Utf8State::Start;
1299 break;
1300 case Utf8State::EB:
1301 if (c >= 0x80 && c <= 0x9F) {
1302 val = (val << 6) | (c & 0x3F);
1303 s = Utf8State::Trail1;
1304 } else {
1305 cont = ContentKind::Arbitrary;
1306 s = Utf8State::Start;
1308 break;
1309 case Utf8State::F0:
1310 if (c >= 0x90 && c <= 0xBF) {
1311 val = (val << 6) | (c & 0x3F);
1312 s = Utf8State::Trail2;
1313 } else {
1314 cont = ContentKind::Arbitrary;
1315 s = Utf8State::Start;
1317 break;
1318 case Utf8State::F4:
1319 if (c >= 0x80 && c <= 0x8F) {
1320 val = (val << 6) | (c & 0x3F);
1321 s = Utf8State::Trail2;
1322 } else {
1323 cont = ContentKind::Arbitrary;
1324 s = Utf8State::Start;
1326 break;
1327 case Utf8State::Trail1:
1328 if (c >= 0x80 && c <= 0xBF) {
1329 cont = ContentKind::Utf8;
1330 if (utf8Content != nullptr)
1332 utf8Content->push_back((val << 6) | (c & 0x3F));
1333 val = 0;
1335 } else {
1336 cont = ContentKind::Arbitrary;
1338 s = Utf8State::Start;
1339 break;
1340 case Utf8State::Trail2:
1341 if (c >= 0x80 && c <= 0xBF) {
1342 val = (val << 6) | (c & 0x3F);
1343 s = Utf8State::Trail1;
1344 } else {
1345 cont = ContentKind::Arbitrary;
1346 s = Utf8State::Start;
1348 break;
1349 case Utf8State::Trail3:
1350 if (c >= 0x80 && c <= 0xBF) {
1351 val = (val << 6) | (c & 0x3F);
1352 s = Utf8State::Trail2;
1353 } else {
1354 cont = ContentKind::Arbitrary;
1355 s = Utf8State::Start;
1357 break;
1360 *size = n;
1361 *nonArray = isPtr;
1362 *content = cont;
1363 *embeddedNuls = emb;
1364 *terminatingNul = true;
1365 return true;
1367 APValue v;
1368 if (!expr->isCXX11ConstantExpr(compiler.getASTContext(), &v)) {
1369 return false;
1371 switch (v.getKind()) {
1372 case APValue::LValue:
1374 Expr const * e = v.getLValueBase().dyn_cast<Expr const *>();
1375 if (e == nullptr) {
1376 return false;
1378 if (!v.getLValueOffset().isZero()) {
1379 return false; //TODO
1381 Expr const * e2 = e->IgnoreParenImpCasts();
1382 if (e2 != e) {
1383 return isStringConstant(
1384 e2, size, nonArray, content, embeddedNuls, terminatingNul);
1386 //TODO: string literals are represented as recursive LValues???
1387 llvm::APInt n
1388 = compiler.getASTContext().getAsConstantArrayType(t)->getSize();
1389 assert(n != 0);
1390 --n;
1391 assert(n.ule(std::numeric_limits<unsigned>::max()));
1392 *size = static_cast<unsigned>(n.getLimitedValue());
1393 *nonArray = isPtr || *nonArray;
1394 *content = ContentKind::Ascii; //TODO
1395 *embeddedNuls = false; //TODO
1396 *terminatingNul = true;
1397 return true;
1399 case APValue::Array:
1401 if (v.hasArrayFiller()) { //TODO: handle final NUL filler?
1402 return false;
1404 unsigned n = v.getArraySize();
1405 assert(n != 0);
1406 ContentKind cont = ContentKind::Ascii;
1407 bool emb = false;
1408 //TODO: check for ContentType::Utf8
1409 for (unsigned i = 0; i != n - 1; ++i) {
1410 APValue e(v.getArrayInitializedElt(i));
1411 if (!e.isInt()) { //TODO: assert?
1412 return false;
1414 APSInt iv = e.getInt();
1415 if (iv == 0) {
1416 emb = true;
1417 } else if (iv.uge(0x80)) {
1418 cont = ContentKind::Arbitrary;
1421 APValue e(v.getArrayInitializedElt(n - 1));
1422 if (!e.isInt()) { //TODO: assert?
1423 return false;
1425 bool trm = e.getInt() == 0;
1426 *size = trm ? n - 1 : n;
1427 *nonArray = isPtr;
1428 *content = cont;
1429 *embeddedNuls = emb;
1430 *terminatingNul = trm;
1431 return true;
1433 default:
1434 assert(false); //TODO???
1435 return false;
1439 bool StringConstant::isZero(Expr const * expr) {
1440 APSInt res;
1441 return expr->EvaluateAsInt(res, compiler.getASTContext()) && res == 0;
1444 void StringConstant::reportChange(
1445 Expr const * expr, ChangeKind kind, std::string const & original,
1446 std::string const & replacement, PassThrough pass, bool nonArray,
1447 char const * rewriteFrom, char const * rewriteTo)
1449 assert((rewriteFrom == nullptr) == (rewriteTo == nullptr));
1450 if (pass != PassThrough::No && !calls_.empty()) {
1451 Expr const * call = calls_.top();
1452 CallExpr::const_arg_iterator argsBeg;
1453 CallExpr::const_arg_iterator argsEnd;
1454 if (isa<CallExpr>(call)) {
1455 argsBeg = cast<CallExpr>(call)->arg_begin();
1456 argsEnd = cast<CallExpr>(call)->arg_end();
1457 } else if (isa<CXXConstructExpr>(call)) {
1458 argsBeg = cast<CXXConstructExpr>(call)->arg_begin();
1459 argsEnd = cast<CXXConstructExpr>(call)->arg_end();
1460 } else {
1461 assert(false);
1463 for (auto i(argsBeg); i != argsEnd; ++i) {
1464 Expr const * e = (*i)->IgnoreParenImpCasts();
1465 if (isa<CXXBindTemporaryExpr>(e)) {
1466 e = cast<CXXBindTemporaryExpr>(e)->getSubExpr()
1467 ->IgnoreParenImpCasts();
1469 if (e == expr) {
1470 if (isa<CallExpr>(call)) {
1471 FunctionDecl const * fdecl
1472 = cast<CallExpr>(call)->getDirectCallee();
1473 if (fdecl == nullptr) {
1474 break;
1476 loplugin::DeclCheck dc(fdecl);
1477 if (pass == PassThrough::EmptyConstantString) {
1478 if ((dc.Function("equals").Class("OUString")
1479 .Namespace("rtl").GlobalNamespace())
1480 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1481 .GlobalNamespace()))
1483 report(
1484 DiagnosticsEngine::Warning,
1485 ("rewrite call of '%0' with call of %1 with"
1486 " empty string constant argument as call of"
1487 " 'rtl::OUString::isEmpty'"),
1488 getMemberLocation(call))
1489 << fdecl->getQualifiedNameAsString() << original
1490 << call->getSourceRange();
1491 return;
1493 if (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
1494 .GlobalNamespace())
1496 report(
1497 DiagnosticsEngine::Warning,
1498 ("rewrite call of '%0' with call of %1 with"
1499 " empty string constant argument as call of"
1500 " '!rtl::OUString::isEmpty'"),
1501 getMemberLocation(call))
1502 << fdecl->getQualifiedNameAsString() << original
1503 << call->getSourceRange();
1504 return;
1506 if ((dc.Operator(OO_Plus).Namespace("rtl")
1507 .GlobalNamespace())
1508 || (dc.Operator(OO_Plus).Class("OUString")
1509 .Namespace("rtl").GlobalNamespace()))
1511 report(
1512 DiagnosticsEngine::Warning,
1513 ("call of '%0' with suspicious call of %1 with"
1514 " empty string constant argument"),
1515 getMemberLocation(call))
1516 << fdecl->getQualifiedNameAsString() << original
1517 << call->getSourceRange();
1518 return;
1520 if (dc.Operator(OO_Equal).Class("OUString")
1521 .Namespace("rtl").GlobalNamespace())
1523 report(
1524 DiagnosticsEngine::Warning,
1525 ("rewrite call of '%0' with call of %1 with"
1526 " empty string constant argument as call of"
1527 " rtl::OUString::call"),
1528 getMemberLocation(call))
1529 << fdecl->getQualifiedNameAsString() << original
1530 << call->getSourceRange();
1531 return;
1533 report(
1534 DiagnosticsEngine::Warning,
1535 "TODO call inside %0", getMemberLocation(expr))
1536 << fdecl->getQualifiedNameAsString()
1537 << expr->getSourceRange();
1538 return;
1539 } else {
1540 assert(pass == PassThrough::NonEmptyConstantString);
1541 if ((dc.Function("equals").Class("OUString")
1542 .Namespace("rtl").GlobalNamespace())
1543 || (dc.Operator(OO_Equal).Class("OUString")
1544 .Namespace("rtl").GlobalNamespace())
1545 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1546 .GlobalNamespace())
1547 || (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
1548 .GlobalNamespace()))
1550 report(
1551 DiagnosticsEngine::Warning,
1552 "elide call of %0 with %1 in call of '%2'",
1553 getMemberLocation(expr))
1554 << original << describeChangeKind(kind)
1555 << fdecl->getQualifiedNameAsString()
1556 << expr->getSourceRange();
1557 return;
1559 report(
1560 DiagnosticsEngine::Warning,
1561 ("rewrite call of %0 with %1 in call of '%2' as"
1562 " (implicit) construction of 'OUString'"),
1563 getMemberLocation(expr))
1564 << original << describeChangeKind(kind)
1565 << fdecl->getQualifiedNameAsString()
1566 << expr->getSourceRange();
1567 return;
1569 } else if (isa<CXXConstructExpr>(call)) {
1570 auto classdecl = cast<CXXConstructExpr>(call)
1571 ->getConstructor()->getParent();
1572 loplugin::DeclCheck dc(classdecl);
1573 if (dc.Class("OUString").Namespace("rtl").GlobalNamespace()
1574 || (dc.Class("OUStringBuffer").Namespace("rtl")
1575 .GlobalNamespace()))
1577 //TODO: propagate further out?
1578 if (pass == PassThrough::EmptyConstantString) {
1579 report(
1580 DiagnosticsEngine::Warning,
1581 ("rewrite construction of %0 with call of %1"
1582 " with empty string constant argument as"
1583 " default construction of %0"),
1584 getMemberLocation(call))
1585 << classdecl << original
1586 << call->getSourceRange();
1587 } else {
1588 assert(pass == PassThrough::NonEmptyConstantString);
1589 report(
1590 DiagnosticsEngine::Warning,
1591 ("elide call of %0 with %1 in construction of"
1592 " %2"),
1593 getMemberLocation(expr))
1594 << original << describeChangeKind(kind)
1595 << classdecl << expr->getSourceRange();
1597 return;
1599 } else {
1600 assert(false);
1605 if (rewriter != nullptr && !nonArray && rewriteFrom != nullptr) {
1606 SourceLocation loc = getMemberLocation(expr);
1607 while (compiler.getSourceManager().isMacroArgExpansion(loc)) {
1608 loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
1610 if (compiler.getSourceManager().isMacroBodyExpansion(loc)) {
1611 loc = compiler.getSourceManager().getSpellingLoc(loc);
1613 unsigned n = Lexer::MeasureTokenLength(
1614 loc, compiler.getSourceManager(), compiler.getLangOpts());
1615 if ((std::string(compiler.getSourceManager().getCharacterData(loc), n)
1616 == rewriteFrom)
1617 && replaceText(loc, n, rewriteTo))
1619 return;
1622 report(
1623 DiagnosticsEngine::Warning,
1624 "rewrite call of '%0' with %1 as call of '%2'%3",
1625 getMemberLocation(expr))
1626 << original << describeChangeKind(kind) << replacement
1627 << adviseNonArray(nonArray) << expr->getSourceRange();
1630 void StringConstant::checkEmpty(
1631 CallExpr const * expr, FunctionDecl const * callee, TreatEmpty treatEmpty,
1632 unsigned size, std::string * replacement)
1634 assert(replacement != nullptr);
1635 if (size == 0) {
1636 switch (treatEmpty) {
1637 case TreatEmpty::DefaultCtor:
1638 *replacement = "rtl::OUString default constructor";
1639 break;
1640 case TreatEmpty::CheckEmpty:
1641 *replacement = "rtl::OUString::isEmpty";
1642 break;
1643 case TreatEmpty::Error:
1644 report(
1645 DiagnosticsEngine::Warning,
1646 "call of '%0' with suspicious empty string constant argument",
1647 getMemberLocation(expr))
1648 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1649 break;
1654 void StringConstant::handleChar(
1655 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
1656 std::string const & replacement, TreatEmpty treatEmpty, bool literal,
1657 char const * rewriteFrom, char const * rewriteTo)
1659 unsigned n;
1660 bool nonArray;
1661 ContentKind cont;
1662 bool emb;
1663 bool trm;
1664 if (!isStringConstant(
1665 expr->getArg(arg)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
1666 &emb, &trm))
1668 return;
1670 if (cont != ContentKind::Ascii) {
1671 report(
1672 DiagnosticsEngine::Warning,
1673 ("call of '%0' with string constant argument containing non-ASCII"
1674 " characters"),
1675 getMemberLocation(expr))
1676 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1677 return;
1679 if (emb) {
1680 report(
1681 DiagnosticsEngine::Warning,
1682 ("call of '%0' with string constant argument containing embedded"
1683 " NULs"),
1684 getMemberLocation(expr))
1685 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1686 return;
1688 if (!trm) {
1689 report(
1690 DiagnosticsEngine::Warning,
1691 ("call of '%0' with string constant argument lacking a terminating"
1692 " NUL"),
1693 getMemberLocation(expr))
1694 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1695 return;
1697 std::string repl(replacement);
1698 checkEmpty(expr, callee, treatEmpty, n, &repl);
1699 reportChange(
1700 expr, ChangeKind::Char, callee->getQualifiedNameAsString(), repl,
1701 (literal
1702 ? (n == 0
1703 ? PassThrough::EmptyConstantString
1704 : PassThrough::NonEmptyConstantString)
1705 : PassThrough::No),
1706 nonArray, rewriteFrom, rewriteTo);
1709 void StringConstant::handleCharLen(
1710 CallExpr const * expr, unsigned arg1, unsigned arg2,
1711 FunctionDecl const * callee, std::string const & replacement,
1712 TreatEmpty treatEmpty)
1714 // Especially for f(RTL_CONSTASCII_STRINGPARAM("foo")), where
1715 // RTL_CONSTASCII_STRINGPARAM expands to complicated expressions involving
1716 // (&(X)[0] sub-expressions (and it might or might not be better to handle
1717 // that at the level of non-expanded macros instead, but I have not found
1718 // out how to do that yet anyway):
1719 unsigned n;
1720 bool nonArray;
1721 ContentKind cont;
1722 bool emb;
1723 bool trm;
1724 if (!(isStringConstant(
1725 expr->getArg(arg1)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
1726 &emb, &trm)
1727 && trm))
1729 return;
1731 APSInt res;
1732 if (expr->getArg(arg2)->EvaluateAsInt(res, compiler.getASTContext())) {
1733 if (res != n) {
1734 return;
1736 } else {
1737 UnaryOperator const * op = dyn_cast<UnaryOperator>(
1738 expr->getArg(arg1)->IgnoreParenImpCasts());
1739 if (op == nullptr || op->getOpcode() != UO_AddrOf) {
1740 return;
1742 ArraySubscriptExpr const * subs = dyn_cast<ArraySubscriptExpr>(
1743 op->getSubExpr()->IgnoreParenImpCasts());
1744 if (subs == nullptr) {
1745 return;
1747 unsigned n2;
1748 bool nonArray2;
1749 ContentKind cont2;
1750 bool emb2;
1751 bool trm2;
1752 if (!(isStringConstant(
1753 subs->getBase()->IgnoreParenImpCasts(), &n2, &nonArray2,
1754 &cont2, &emb2, &trm2)
1755 && n2 == n && cont2 == cont && emb2 == emb && trm2 == trm
1756 //TODO: same strings
1757 && subs->getIdx()->EvaluateAsInt(res, compiler.getASTContext())
1758 && res == 0))
1760 return;
1763 if (cont != ContentKind::Ascii) {
1764 report(
1765 DiagnosticsEngine::Warning,
1766 ("call of '%0' with string constant argument containing non-ASCII"
1767 " characters"),
1768 getMemberLocation(expr))
1769 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1771 if (emb) {
1772 return;
1774 std::string repl(replacement);
1775 checkEmpty(expr, callee, treatEmpty, n, &repl);
1776 reportChange(
1777 expr, ChangeKind::CharLen, callee->getQualifiedNameAsString(), repl,
1778 PassThrough::No, nonArray, nullptr, nullptr);
1781 void StringConstant::handleOUStringCtor(
1782 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
1783 bool explicitFunctionalCastNotation)
1785 handleOUStringCtor(expr, expr->getArg(arg), callee, explicitFunctionalCastNotation);
1788 void StringConstant::handleOUStringCtor(
1789 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
1790 bool explicitFunctionalCastNotation)
1792 auto e0 = argExpr->IgnoreParenImpCasts();
1793 auto e1 = dyn_cast<CXXFunctionalCastExpr>(e0);
1794 if (e1 == nullptr) {
1795 if (explicitFunctionalCastNotation) {
1796 return;
1798 } else {
1799 e0 = e1->getSubExpr()->IgnoreParenImpCasts();
1801 auto e2 = dyn_cast<CXXBindTemporaryExpr>(e0);
1802 if (e2 == nullptr) {
1803 return;
1805 auto e3 = dyn_cast<CXXConstructExpr>(
1806 e2->getSubExpr()->IgnoreParenImpCasts());
1807 if (e3 == nullptr) {
1808 return;
1810 if (!loplugin::DeclCheck(e3->getConstructor()).MemberFunction()
1811 .Class("OUString").Namespace("rtl").GlobalNamespace())
1813 return;
1815 if (e3->getNumArgs() == 0) {
1816 report(
1817 DiagnosticsEngine::Warning,
1818 ("in call of '%0', replace default-constructed 'OUString' with an"
1819 " empty string literal"),
1820 e3->getExprLoc())
1821 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1822 return;
1824 if (e3->getNumArgs() == 1
1825 && e3->getConstructor()->getNumParams() == 1
1826 && (loplugin::TypeCheck(
1827 e3->getConstructor()->getParamDecl(0)->getType())
1828 .Typedef("sal_Unicode").GlobalNamespace()))
1830 // It may not be easy to rewrite OUString(c), esp. given there is no
1831 // OUString ctor taking an OUStringLiteral1 arg, so don't warn there:
1832 if (!explicitFunctionalCastNotation) {
1833 report(
1834 DiagnosticsEngine::Warning,
1835 ("in call of '%0', replace 'OUString' constructed from a"
1836 " 'sal_Unicode' with an 'OUStringLiteral1'"),
1837 e3->getExprLoc())
1838 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1840 return;
1842 if (e3->getNumArgs() != 2) {
1843 return;
1845 unsigned n;
1846 bool nonArray;
1847 ContentKind cont;
1848 bool emb;
1849 bool trm;
1850 if (!isStringConstant(
1851 e3->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray, &cont, &emb,
1852 &trm))
1854 return;
1856 //TODO: cont, emb, trm
1857 if (rewriter != nullptr) {
1858 auto loc1 = e3->getLocStart();
1859 auto range = e3->getParenOrBraceRange();
1860 if (loc1.isFileID() && range.getBegin().isFileID()
1861 && range.getEnd().isFileID())
1863 auto loc2 = range.getBegin();
1864 for (bool first = true;; first = false) {
1865 unsigned n = Lexer::MeasureTokenLength(
1866 loc2, compiler.getSourceManager(), compiler.getLangOpts());
1867 if (!first) {
1868 StringRef s(
1869 compiler.getSourceManager().getCharacterData(loc2), n);
1870 while (s.startswith("\\\n")) {
1871 s = s.drop_front(2);
1872 while (!s.empty()
1873 && (s.front() == ' ' || s.front() == '\t'
1874 || s.front() == '\n' || s.front() == '\v'
1875 || s.front() == '\f'))
1877 s = s.drop_front(1);
1880 if (!(s.empty() || s.startswith("/*") || s.startswith("//")
1881 || s == "\\"))
1883 break;
1886 loc2 = loc2.getLocWithOffset(std::max<unsigned>(n, 1));
1888 auto loc3 = range.getEnd();
1889 for (;;) {
1890 auto l = Lexer::GetBeginningOfToken(
1891 loc3.getLocWithOffset(-1), compiler.getSourceManager(),
1892 compiler.getLangOpts());
1893 unsigned n = Lexer::MeasureTokenLength(
1894 l, compiler.getSourceManager(), compiler.getLangOpts());
1895 StringRef s(compiler.getSourceManager().getCharacterData(l), n);
1896 while (s.startswith("\\\n")) {
1897 s = s.drop_front(2);
1898 while (!s.empty()
1899 && (s.front() == ' ' || s.front() == '\t'
1900 || s.front() == '\n' || s.front() == '\v'
1901 || s.front() == '\f'))
1903 s = s.drop_front(1);
1906 if (!(s.empty() || s.startswith("/*") || s.startswith("//")
1907 || s == "\\"))
1909 break;
1911 loc3 = l;
1913 if (removeText(CharSourceRange(SourceRange(loc1, loc2), false))) {
1914 if (removeText(SourceRange(loc3, range.getEnd()))) {
1915 return;
1917 report(DiagnosticsEngine::Fatal, "Corrupt rewrite", loc3)
1918 << expr->getSourceRange();
1919 return;
1923 report(
1924 DiagnosticsEngine::Warning,
1925 ("in call of '%0', replace 'OUString' constructed from a string literal"
1926 " directly with the string literal"),
1927 e3->getExprLoc())
1928 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1931 void StringConstant::handleFunArgOstring(
1932 CallExpr const * expr, unsigned arg, FunctionDecl const * callee)
1934 auto argExpr = expr->getArg(arg)->IgnoreParenImpCasts();
1935 unsigned n;
1936 bool nonArray;
1937 ContentKind cont;
1938 bool emb;
1939 bool trm;
1940 if (isStringConstant(argExpr, &n, &nonArray, &cont, &emb, &trm)) {
1941 if (cont != ContentKind::Ascii || emb) {
1942 return;
1944 if (!trm) {
1945 report(
1946 DiagnosticsEngine::Warning,
1947 ("call of '%0' with string constant argument lacking a"
1948 " terminating NUL"),
1949 getMemberLocation(expr))
1950 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1951 return;
1953 std::string repl;
1954 checkEmpty(expr, callee, TreatEmpty::Error, n, &repl);
1955 if (nonArray) {
1956 report(
1957 DiagnosticsEngine::Warning,
1958 ("in call of '%0' with non-array string constant argument,"
1959 " turn the non-array string constant into an array"),
1960 getMemberLocation(expr))
1961 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1963 } else if (auto cexpr = lookForCXXConstructExpr(argExpr)) {
1964 auto classdecl = cexpr->getConstructor()->getParent();
1965 if (loplugin::DeclCheck(classdecl).Class("OString").Namespace("rtl")
1966 .GlobalNamespace())
1968 switch (cexpr->getConstructor()->getNumParams()) {
1969 case 0:
1970 report(
1971 DiagnosticsEngine::Warning,
1972 ("in call of '%0', replace empty %1 constructor with empty"
1973 " string literal"),
1974 cexpr->getLocation())
1975 << callee->getQualifiedNameAsString() << classdecl
1976 << expr->getSourceRange();
1977 break;
1978 case 2:
1979 if (isStringConstant(
1980 cexpr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray,
1981 &cont, &emb, &trm))
1983 APSInt res;
1984 if (cexpr->getArg(1)->EvaluateAsInt(
1985 res, compiler.getASTContext()))
1987 if (res == n && !emb && trm) {
1988 report(
1989 DiagnosticsEngine::Warning,
1990 ("in call of '%0', elide explicit %1"
1991 " constructor%2"),
1992 cexpr->getLocation())
1993 << callee->getQualifiedNameAsString()
1994 << classdecl << adviseNonArray(nonArray)
1995 << expr->getSourceRange();
1997 } else {
1998 if (emb) {
1999 report(
2000 DiagnosticsEngine::Warning,
2001 ("call of %0 constructor with string constant"
2002 " argument containing embedded NULs"),
2003 cexpr->getLocation())
2004 << classdecl << cexpr->getSourceRange();
2005 return;
2007 if (!trm) {
2008 report(
2009 DiagnosticsEngine::Warning,
2010 ("call of %0 constructor with string constant"
2011 " argument lacking a terminating NUL"),
2012 cexpr->getLocation())
2013 << classdecl << cexpr->getSourceRange();
2014 return;
2016 report(
2017 DiagnosticsEngine::Warning,
2018 "in call of '%0', elide explicit %1 constructor%2",
2019 cexpr->getLocation())
2020 << callee->getQualifiedNameAsString() << classdecl
2021 << adviseNonArray(nonArray)
2022 << expr->getSourceRange();
2025 break;
2026 default:
2027 break;
2033 loplugin::Plugin::Registration< StringConstant > X("stringconstant", true);
2037 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */