Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / compilerplugins / clang / stringconstant.cxx
blobcf1eb6afedbf6f267efe39070a5b1dea95ba042e
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 (auto const d = dyn_cast_or_null<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 VisitCXXConstructExpr(CXXConstructExpr const * expr);
196 bool VisitReturnStmt(ReturnStmt const * stmt);
198 private:
199 enum class ContentKind { Ascii, Utf8, Arbitrary };
201 enum class TreatEmpty { DefaultCtor, CheckEmpty, Error };
203 enum class ChangeKind { Char, CharLen, SingleChar, OUStringChar };
205 enum class PassThrough { No, EmptyConstantString, NonEmptyConstantString };
207 std::string describeChangeKind(ChangeKind kind);
209 bool isStringConstant(
210 Expr const * expr, unsigned * size, bool * nonArray,
211 ContentKind * content, bool * embeddedNuls, bool * terminatingNul,
212 std::vector<char32_t> * utf8Content = nullptr);
214 bool isZero(Expr const * expr);
216 void reportChange(
217 Expr const * expr, ChangeKind kind, std::string const & original,
218 std::string const & replacement, PassThrough pass, bool nonArray,
219 char const * rewriteFrom, char const * rewriteTo);
221 void checkEmpty(
222 CallExpr const * expr, FunctionDecl const * callee,
223 TreatEmpty treatEmpty, unsigned size, std::string * replacement);
225 void handleChar(
226 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
227 std::string const & replacement, TreatEmpty treatEmpty, bool literal,
228 char const * rewriteFrom = nullptr, char const * rewriteTo = nullptr);
230 void handleCharLen(
231 CallExpr const * expr, unsigned arg1, unsigned arg2,
232 FunctionDecl const * callee, std::string const & replacement,
233 TreatEmpty treatEmpty);
235 void handleOUStringCtor(
236 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
237 bool explicitFunctionalCastNotation);
239 void handleOStringCtor(
240 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
241 bool explicitFunctionalCastNotation);
243 void handleOUStringCtor(
244 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
245 bool explicitFunctionalCastNotation);
247 void handleOStringCtor(
248 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
249 bool explicitFunctionalCastNotation);
251 enum class StringKind { Unicode, Char };
252 void handleStringCtor(
253 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
254 bool explicitFunctionalCastNotation, StringKind stringKind);
256 void handleFunArgOstring(
257 CallExpr const * expr, unsigned arg, FunctionDecl const * callee);
259 std::stack<QualType> returnTypes_;
260 std::stack<Expr const *> calls_;
263 void StringConstant::run() {
264 if (compiler.getLangOpts().CPlusPlus
265 && compiler.getPreprocessor().getIdentifierInfo(
266 "LIBO_INTERNAL_ONLY")->hasMacroDefinition())
267 //TODO: some parts of it are useful for external code, too
269 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
273 bool StringConstant::TraverseCallExpr(CallExpr * expr) {
274 if (!WalkUpFromCallExpr(expr)) {
275 return false;
277 calls_.push(expr);
278 bool bRes = true;
279 for (auto * e: expr->children()) {
280 if (!TraverseStmt(e)) {
281 bRes = false;
282 break;
285 calls_.pop();
286 return bRes;
289 bool StringConstant::TraverseCXXMemberCallExpr(CXXMemberCallExpr * expr) {
290 if (!WalkUpFromCXXMemberCallExpr(expr)) {
291 return false;
293 calls_.push(expr);
294 bool bRes = true;
295 for (auto * e: expr->children()) {
296 if (!TraverseStmt(e)) {
297 bRes = false;
298 break;
301 calls_.pop();
302 return bRes;
305 bool StringConstant::TraverseCXXOperatorCallExpr(CXXOperatorCallExpr * expr)
307 if (!WalkUpFromCXXOperatorCallExpr(expr)) {
308 return false;
310 calls_.push(expr);
311 bool bRes = true;
312 for (auto * e: expr->children()) {
313 if (!TraverseStmt(e)) {
314 bRes = false;
315 break;
318 calls_.pop();
319 return bRes;
322 bool StringConstant::TraverseCXXConstructExpr(CXXConstructExpr * expr) {
323 if (!WalkUpFromCXXConstructExpr(expr)) {
324 return false;
326 calls_.push(expr);
327 bool bRes = true;
328 for (auto * e: expr->children()) {
329 if (!TraverseStmt(e)) {
330 bRes = false;
331 break;
334 calls_.pop();
335 return bRes;
338 bool StringConstant::VisitCallExpr(CallExpr const * expr) {
339 if (ignoreLocation(expr)) {
340 return true;
342 FunctionDecl const * fdecl = expr->getDirectCallee();
343 if (fdecl == nullptr) {
344 return true;
346 for (unsigned i = 0; i != fdecl->getNumParams(); ++i) {
347 auto t = fdecl->getParamDecl(i)->getType();
348 if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
349 .LvalueReference().Const().NotSubstTemplateTypeParmType()
350 .Class("OUString").Namespace("rtl").GlobalNamespace())
352 if (!(isLhsOfAssignment(fdecl, i)
353 || hasOverloads(fdecl, expr->getNumArgs())))
355 handleOUStringCtor(expr, i, fdecl, true);
358 if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
359 .LvalueReference().Const().NotSubstTemplateTypeParmType()
360 .Class("OString").Namespace("rtl").GlobalNamespace())
362 if (!(isLhsOfAssignment(fdecl, i)
363 || hasOverloads(fdecl, expr->getNumArgs())))
365 handleOStringCtor(expr, i, fdecl, true);
369 loplugin::DeclCheck dc(fdecl);
370 //TODO: u.compareToAscii("foo") -> u.???("foo")
371 //TODO: u.compareToIgnoreAsciiCaseAscii("foo") -> u.???("foo")
372 if ((dc.Function("createFromAscii").Class("OUString").Namespace("rtl")
373 .GlobalNamespace())
374 && fdecl->getNumParams() == 1)
376 // OUString::createFromAscii("foo") -> OUString("foo")
377 handleChar(
378 expr, 0, fdecl, "rtl::OUString constructor",
379 TreatEmpty::DefaultCtor, true);
380 return true;
382 if ((dc.Function("endsWithAsciiL").Class("OUString").Namespace("rtl")
383 .GlobalNamespace())
384 && fdecl->getNumParams() == 2)
386 // u.endsWithAsciiL("foo", 3) -> u.endsWith("foo"):
387 handleCharLen(
388 expr, 0, 1, fdecl, "rtl::OUString::endsWith", TreatEmpty::Error);
389 return true;
391 if ((dc.Function("endsWithIgnoreAsciiCaseAsciiL").Class("OUString")
392 .Namespace("rtl").GlobalNamespace())
393 && fdecl->getNumParams() == 2)
395 // u.endsWithIgnoreAsciiCaseAsciiL("foo", 3) ->
396 // u.endsWithIgnoreAsciiCase("foo"):
397 handleCharLen(
398 expr, 0, 1, fdecl, "rtl::OUString::endsWithIgnoreAsciiCase",
399 TreatEmpty::Error);
400 return true;
402 if ((dc.Function("equalsAscii").Class("OUString").Namespace("rtl")
403 .GlobalNamespace())
404 && fdecl->getNumParams() == 1)
406 // u.equalsAscii("foo") -> u == "foo":
407 handleChar(
408 expr, 0, fdecl, "operator ==", TreatEmpty::CheckEmpty, false);
409 return true;
411 if ((dc.Function("equalsAsciiL").Class("OUString").Namespace("rtl")
412 .GlobalNamespace())
413 && fdecl->getNumParams() == 2)
415 // u.equalsAsciiL("foo", 3) -> u == "foo":
416 handleCharLen(expr, 0, 1, fdecl, "operator ==", TreatEmpty::CheckEmpty);
417 return true;
419 if ((dc.Function("equalsIgnoreAsciiCaseAscii").Class("OUString")
420 .Namespace("rtl").GlobalNamespace())
421 && fdecl->getNumParams() == 1)
423 // u.equalsIgnoreAsciiCaseAscii("foo") ->
424 // u.equalsIngoreAsciiCase("foo"):
426 auto file = getFilenameOfLocation(
427 compiler.getSourceManager().getSpellingLoc(expr->getBeginLoc()));
428 if (loplugin::isSamePathname(
429 file, SRCDIR "/sal/qa/rtl/strings/test_oustring_compare.cxx"))
431 return true;
433 handleChar(
434 expr, 0, fdecl, "rtl::OUString::equalsIgnoreAsciiCase",
435 TreatEmpty::CheckEmpty, false);
436 return true;
438 if ((dc.Function("equalsIgnoreAsciiCaseAsciiL").Class("OUString")
439 .Namespace("rtl").GlobalNamespace())
440 && fdecl->getNumParams() == 2)
442 // u.equalsIgnoreAsciiCaseAsciiL("foo", 3) ->
443 // u.equalsIngoreAsciiCase("foo"):
444 auto file = getFilenameOfLocation(
445 compiler.getSourceManager().getSpellingLoc(expr->getBeginLoc()));
446 if (loplugin::isSamePathname(
447 file, SRCDIR "/sal/qa/rtl/strings/test_oustring_compare.cxx"))
449 return true;
451 handleCharLen(
452 expr, 0, 1, fdecl, "rtl::OUString::equalsIgnoreAsciiCase",
453 TreatEmpty::CheckEmpty);
454 return true;
456 if ((dc.Function("indexOfAsciiL").Class("OUString").Namespace("rtl")
457 .GlobalNamespace())
458 && fdecl->getNumParams() == 3)
460 assert(expr->getNumArgs() == 3);
461 // u.indexOfAsciiL("foo", 3, i) -> u.indexOf("foo", i):
462 handleCharLen(
463 expr, 0, 1, fdecl, "rtl::OUString::indexOf", TreatEmpty::Error);
464 return true;
466 if ((dc.Function("lastIndexOfAsciiL").Class("OUString").Namespace("rtl")
467 .GlobalNamespace())
468 && fdecl->getNumParams() == 2)
470 // u.lastIndexOfAsciiL("foo", 3) -> u.lastIndexOf("foo"):
471 handleCharLen(
472 expr, 0, 1, fdecl, "rtl::OUString::lastIndexOf", TreatEmpty::Error);
473 return true;
475 if ((dc.Function("matchAsciiL").Class("OUString").Namespace("rtl")
476 .GlobalNamespace())
477 && fdecl->getNumParams() == 3)
479 assert(expr->getNumArgs() == 3);
480 // u.matchAsciiL("foo", 3, i) -> u.match("foo", i):
481 handleCharLen(
482 expr, 0, 1, fdecl,
483 (isZero(expr->getArg(2))
484 ? std::string("rtl::OUString::startsWith")
485 : std::string("rtl::OUString::match")),
486 TreatEmpty::Error);
487 return true;
489 if ((dc.Function("matchIgnoreAsciiCaseAsciiL").Class("OUString")
490 .Namespace("rtl").GlobalNamespace())
491 && fdecl->getNumParams() == 3)
493 assert(expr->getNumArgs() == 3);
494 // u.matchIgnoreAsciiCaseAsciiL("foo", 3, i) ->
495 // u.matchIgnoreAsciiCase("foo", i):
496 handleCharLen(
497 expr, 0, 1, fdecl,
498 (isZero(expr->getArg(2))
499 ? std::string("rtl::OUString::startsWithIgnoreAsciiCase")
500 : std::string("rtl::OUString::matchIgnoreAsciiCase")),
501 TreatEmpty::Error);
502 return true;
504 if ((dc.Function("reverseCompareToAsciiL").Class("OUString")
505 .Namespace("rtl").GlobalNamespace())
506 && fdecl->getNumParams() == 2)
508 // u.reverseCompareToAsciiL("foo", 3) -> u.reverseCompareTo("foo"):
509 handleCharLen(
510 expr, 0, 1, fdecl, "rtl::OUString::reverseCompareTo",
511 TreatEmpty::Error);
512 return true;
514 if ((dc.Function("reverseCompareTo").Class("OUString").Namespace("rtl")
515 .GlobalNamespace())
516 && fdecl->getNumParams() == 1)
518 handleOUStringCtor(expr, 0, fdecl, false);
519 return true;
521 if ((dc.Function("equalsIgnoreAsciiCase").Class("OUString").Namespace("rtl")
522 .GlobalNamespace())
523 && fdecl->getNumParams() == 1)
525 handleOUStringCtor(expr, 0, fdecl, false);
526 return true;
528 if ((dc.Function("match").Class("OUString").Namespace("rtl")
529 .GlobalNamespace())
530 && fdecl->getNumParams() == 2)
532 handleOUStringCtor(expr, 0, fdecl, false);
533 return true;
535 if ((dc.Function("matchIgnoreAsciiCase").Class("OUString").Namespace("rtl")
536 .GlobalNamespace())
537 && fdecl->getNumParams() == 2)
539 handleOUStringCtor(expr, 0, fdecl, false);
540 return true;
542 if ((dc.Function("startsWith").Class("OUString").Namespace("rtl")
543 .GlobalNamespace())
544 && fdecl->getNumParams() == 2)
546 handleOUStringCtor(expr, 0, fdecl, false);
547 return true;
549 if ((dc.Function("startsWithIgnoreAsciiCase").Class("OUString")
550 .Namespace("rtl").GlobalNamespace())
551 && fdecl->getNumParams() == 2)
553 handleOUStringCtor(expr, 0, fdecl, false);
554 return true;
556 if ((dc.Function("endsWith").Class("OUString").Namespace("rtl")
557 .GlobalNamespace())
558 && fdecl->getNumParams() == 2)
560 handleOUStringCtor(expr, 0, fdecl, false);
561 return true;
563 if ((dc.Function("endsWithIgnoreAsciiCase").Class("OUString")
564 .Namespace("rtl").GlobalNamespace())
565 && fdecl->getNumParams() == 2)
567 handleOUStringCtor(expr, 0, fdecl, false);
568 return true;
570 if ((dc.Function("indexOf").Class("OUString").Namespace("rtl")
571 .GlobalNamespace())
572 && fdecl->getNumParams() == 2)
574 handleOUStringCtor(expr, 0, fdecl, false);
575 return true;
577 if ((dc.Function("lastIndexOf").Class("OUString").Namespace("rtl")
578 .GlobalNamespace())
579 && fdecl->getNumParams() == 1)
581 handleOUStringCtor(expr, 0, fdecl, false);
582 return true;
584 if ((dc.Function("replaceFirst").Class("OUString").Namespace("rtl")
585 .GlobalNamespace())
586 && fdecl->getNumParams() == 3)
588 handleOUStringCtor(expr, 0, fdecl, false);
589 handleOUStringCtor(expr, 1, fdecl, false);
590 return true;
592 if ((dc.Function("replaceAll").Class("OUString").Namespace("rtl")
593 .GlobalNamespace())
594 && (fdecl->getNumParams() == 2 || fdecl->getNumParams() == 3))
596 handleOUStringCtor(expr, 0, fdecl, false);
597 handleOUStringCtor(expr, 1, fdecl, false);
598 return true;
600 if ((dc.Operator(OO_PlusEqual).Class("OUString").Namespace("rtl")
601 .GlobalNamespace())
602 && fdecl->getNumParams() == 1)
604 handleOUStringCtor(
605 expr, dyn_cast<CXXOperatorCallExpr>(expr) == nullptr ? 0 : 1,
606 fdecl, false);
607 return true;
609 if ((dc.Function("equals").Class("OUString").Namespace("rtl")
610 .GlobalNamespace())
611 && fdecl->getNumParams() == 1)
613 unsigned n;
614 bool nonArray;
615 ContentKind cont;
616 bool emb;
617 bool trm;
618 if (!isStringConstant(
619 expr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
620 &emb, &trm))
622 return true;
624 if (cont != ContentKind::Ascii) {
625 report(
626 DiagnosticsEngine::Warning,
627 ("call of '%0' with string constant argument containing"
628 " non-ASCII characters"),
629 expr->getExprLoc())
630 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
632 if (emb) {
633 report(
634 DiagnosticsEngine::Warning,
635 ("call of '%0' with string constant argument containing"
636 " embedded NULLs"),
637 expr->getExprLoc())
638 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
640 if (n == 0) {
641 report(
642 DiagnosticsEngine::Warning,
643 ("rewrite call of '%0' with empty string constant argument as"
644 " call of 'rtl::OUString::isEmpty'"),
645 expr->getExprLoc())
646 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
647 return true;
650 if (dc.Operator(OO_EqualEqual).Namespace("rtl").GlobalNamespace()
651 && fdecl->getNumParams() == 2)
653 for (unsigned i = 0; i != 2; ++i) {
654 unsigned n;
655 bool nonArray;
656 ContentKind cont;
657 bool emb;
658 bool trm;
659 if (!isStringConstant(
660 expr->getArg(i)->IgnoreParenImpCasts(), &n, &nonArray,
661 &cont, &emb, &trm))
663 continue;
665 if (cont != ContentKind::Ascii) {
666 report(
667 DiagnosticsEngine::Warning,
668 ("call of '%0' with string constant argument containing"
669 " non-ASCII characters"),
670 expr->getExprLoc())
671 << fdecl->getQualifiedNameAsString()
672 << expr->getSourceRange();
674 if (emb) {
675 report(
676 DiagnosticsEngine::Warning,
677 ("call of '%0' with string constant argument containing"
678 " embedded NULLs"),
679 expr->getExprLoc())
680 << fdecl->getQualifiedNameAsString()
681 << expr->getSourceRange();
683 if (n == 0) {
684 report(
685 DiagnosticsEngine::Warning,
686 ("rewrite call of '%0' with empty string constant argument"
687 " as call of 'rtl::OUString::isEmpty'"),
688 expr->getExprLoc())
689 << fdecl->getQualifiedNameAsString()
690 << expr->getSourceRange();
693 return true;
695 if (dc.Operator(OO_ExclaimEqual).Namespace("rtl").GlobalNamespace()
696 && fdecl->getNumParams() == 2)
698 for (unsigned i = 0; i != 2; ++i) {
699 unsigned n;
700 bool nonArray;
701 ContentKind cont;
702 bool emb;
703 bool trm;
704 if (!isStringConstant(
705 expr->getArg(i)->IgnoreParenImpCasts(), &n, &nonArray,
706 &cont, &emb, &trm))
708 continue;
710 if (cont != ContentKind::Ascii) {
711 report(
712 DiagnosticsEngine::Warning,
713 ("call of '%0' with string constant argument containing"
714 " non-ASCII characters"),
715 expr->getExprLoc())
716 << fdecl->getQualifiedNameAsString()
717 << expr->getSourceRange();
719 if (emb) {
720 report(
721 DiagnosticsEngine::Warning,
722 ("call of '%0' with string constant argument containing"
723 " embedded NULLs"),
724 expr->getExprLoc())
725 << fdecl->getQualifiedNameAsString()
726 << expr->getSourceRange();
728 if (n == 0) {
729 report(
730 DiagnosticsEngine::Warning,
731 ("rewrite call of '%0' with empty string constant argument"
732 " as call of '!rtl::OUString::isEmpty'"),
733 expr->getExprLoc())
734 << fdecl->getQualifiedNameAsString()
735 << expr->getSourceRange();
738 return true;
740 if (dc.Operator(OO_Equal).Namespace("rtl").GlobalNamespace()
741 && fdecl->getNumParams() == 1)
743 unsigned n;
744 bool nonArray;
745 ContentKind cont;
746 bool emb;
747 bool trm;
748 if (!isStringConstant(
749 expr->getArg(1)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
750 &emb, &trm))
752 return true;
754 if (cont != ContentKind::Ascii) {
755 report(
756 DiagnosticsEngine::Warning,
757 ("call of '%0' with string constant argument containing"
758 " non-ASCII characters"),
759 expr->getExprLoc())
760 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
762 if (emb) {
763 report(
764 DiagnosticsEngine::Warning,
765 ("call of '%0' with string constant argument containing"
766 " embedded NULLs"),
767 expr->getExprLoc())
768 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
770 if (n == 0) {
771 report(
772 DiagnosticsEngine::Warning,
773 ("rewrite call of '%0' with empty string constant argument as"
774 " call of 'rtl::OUString::clear'"),
775 expr->getExprLoc())
776 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
777 return true;
779 return true;
781 if (dc.Function("append").Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
782 && fdecl->getNumParams() == 1)
784 handleChar(expr, 0, fdecl, "", TreatEmpty::Error, false);
785 return true;
787 if ((dc.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
788 .GlobalNamespace())
789 && fdecl->getNumParams() == 1)
791 // u.appendAscii("foo") -> u.append("foo")
792 handleChar(
793 expr, 0, fdecl, "rtl::OUStringBuffer::append", TreatEmpty::Error,
794 true, "appendAscii", "append");
795 return true;
797 if ((dc.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
798 .GlobalNamespace())
799 && fdecl->getNumParams() == 2)
801 // u.appendAscii("foo", 3) -> u.append("foo"):
802 handleCharLen(
803 expr, 0, 1, fdecl, "rtl::OUStringBuffer::append",
804 TreatEmpty::Error);
805 return true;
807 if (dc.Function("append").Class("OStringBuffer").Namespace("rtl")
808 .GlobalNamespace())
810 switch (fdecl->getNumParams()) {
811 case 1:
812 handleFunArgOstring(expr, 0, fdecl);
813 break;
814 case 2:
816 // b.append("foo", 3) -> b.append("foo"):
817 auto file = getFilenameOfLocation(
818 compiler.getSourceManager().getSpellingLoc(
819 expr->getBeginLoc()));
820 if (loplugin::isSamePathname(
821 file,
822 SRCDIR "/sal/qa/OStringBuffer/rtl_OStringBuffer.cxx"))
824 return true;
826 handleCharLen(
827 expr, 0, 1, fdecl, "rtl::OStringBuffer::append",
828 TreatEmpty::Error);
830 break;
831 default:
832 break;
834 return true;
836 if (dc.Function("insert").Class("OStringBuffer").Namespace("rtl")
837 .GlobalNamespace())
839 switch (fdecl->getNumParams()) {
840 case 2:
841 handleFunArgOstring(expr, 1, fdecl);
842 break;
843 case 3:
845 // b.insert(i, "foo", 3) -> b.insert(i, "foo"):
846 handleCharLen(
847 expr, 1, 2, fdecl, "rtl::OStringBuffer::insert",
848 TreatEmpty::Error);
849 break;
851 default:
852 break;
854 return true;
856 return true;
859 bool StringConstant::VisitCXXConstructExpr(CXXConstructExpr const * expr) {
860 if (ignoreLocation(expr)) {
861 return true;
863 auto classdecl = expr->getConstructor()->getParent();
864 if (loplugin::DeclCheck(classdecl)
865 .Class("OUString").Namespace("rtl").GlobalNamespace())
867 ChangeKind kind;
868 PassThrough pass;
869 bool simplify;
870 switch (expr->getConstructor()->getNumParams()) {
871 case 1:
872 if (!loplugin::TypeCheck(
873 expr->getConstructor()->getParamDecl(0)->getType())
874 .Typedef("sal_Unicode").GlobalNamespace())
876 return true;
878 kind = ChangeKind::SingleChar;
879 pass = PassThrough::NonEmptyConstantString;
880 simplify = false;
881 break;
882 case 2:
884 auto arg = expr->getArg(0);
885 if (loplugin::TypeCheck(arg->getType())
886 .Class("OUStringChar_").Namespace("rtl")
887 .GlobalNamespace())
889 kind = ChangeKind::OUStringChar;
890 pass = PassThrough::NonEmptyConstantString;
891 simplify = false;
892 } else {
893 unsigned n;
894 bool nonArray;
895 ContentKind cont;
896 bool emb;
897 bool trm;
898 if (!isStringConstant(
899 arg->IgnoreParenImpCasts(), &n, &nonArray, &cont,
900 &emb, &trm))
902 return true;
904 if (cont != ContentKind::Ascii) {
905 report(
906 DiagnosticsEngine::Warning,
907 ("construction of %0 with string constant argument"
908 " containing non-ASCII characters"),
909 expr->getExprLoc())
910 << classdecl << expr->getSourceRange();
912 if (emb) {
913 report(
914 DiagnosticsEngine::Warning,
915 ("construction of %0 with string constant argument"
916 " containing embedded NULLs"),
917 expr->getExprLoc())
918 << classdecl << expr->getSourceRange();
920 kind = ChangeKind::Char;
921 pass = n == 0
922 ? PassThrough::EmptyConstantString
923 : PassThrough::NonEmptyConstantString;
924 simplify = false;
926 break;
928 case 4:
930 unsigned n;
931 bool nonArray;
932 ContentKind cont;
933 bool emb;
934 bool trm;
935 std::vector<char32_t> utf8Cont;
936 if (!isStringConstant(
937 expr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray,
938 &cont, &emb, &trm, &utf8Cont))
940 return true;
942 APSInt res;
943 if (!compat::EvaluateAsInt(expr->getArg(1),
944 res, compiler.getASTContext()))
946 return true;
948 if (res != n) {
949 report(
950 DiagnosticsEngine::Warning,
951 ("suspicious 'rtl::OUString' constructor with literal"
952 " of length %0 and non-matching length argument %1"),
953 expr->getExprLoc())
954 << n << compat::toString(res, 10) << expr->getSourceRange();
955 return true;
957 APSInt enc;
958 if (!compat::EvaluateAsInt(expr->getArg(2),
959 enc, compiler.getASTContext()))
961 return true;
963 auto const encIsAscii = enc == 11; // RTL_TEXTENCODING_ASCII_US
964 auto const encIsUtf8 = enc == 76; // RTL_TEXTENCODING_UTF8
965 if (!compat::EvaluateAsInt(expr->getArg(3),
966 res, compiler.getASTContext())
967 || res != 0x333) // OSTRING_TO_OUSTRING_CVTFLAGS
969 return true;
971 if (!encIsAscii && cont == ContentKind::Ascii) {
972 report(
973 DiagnosticsEngine::Warning,
974 ("suspicious 'rtl::OUString' constructor with text"
975 " encoding %0 but plain ASCII content; use"
976 " 'RTL_TEXTENCODING_ASCII_US' instead"),
977 expr->getArg(2)->getExprLoc())
978 << compat::toString(enc, 10) << expr->getSourceRange();
979 return true;
981 if (encIsUtf8) {
982 if (cont == ContentKind::Arbitrary) {
983 report(
984 DiagnosticsEngine::Warning,
985 ("suspicious 'rtl::OUString' constructor with text"
986 " encoding 'RTL_TEXTENCODING_UTF8' but non-UTF-8"
987 " content"),
988 expr->getArg(0)->getExprLoc())
989 << expr->getSourceRange();
990 } else {
991 assert(cont == ContentKind::Utf8);
992 //TODO: keep original content as much as possible
993 std::ostringstream s;
994 for (auto const c: utf8Cont) {
995 if (c == '\\') {
996 s << "\\\\";
997 } else if (c == '"') {
998 s << "\\\"";
999 } else if (c == '\a') {
1000 s << "\\a";
1001 } else if (c == '\b') {
1002 s << "\\b";
1003 } else if (c == '\f') {
1004 s << "\\f";
1005 } else if (c == '\n') {
1006 s << "\\n";
1007 } else if (c == '\r') {
1008 s << "\\r";
1009 } else if (c == '\t') {
1010 s << "\\r";
1011 } else if (c == '\v') {
1012 s << "\\v";
1013 } else if (c <= 0x1F || c == 0x7F) {
1014 s << "\\x" << std::oct << std::setw(3)
1015 << std::setfill('0')
1016 << static_cast<std::uint_least32_t>(c);
1017 } else if (c < 0x7F) {
1018 s << char(c);
1019 } else if (c <= 0xFFFF) {
1020 s << "\\u" << std::hex << std::uppercase
1021 << std::setw(4) << std::setfill('0')
1022 << static_cast<std::uint_least32_t>(c);
1023 } else {
1024 assert(c <= 0x10FFFF);
1025 s << "\\U" << std::hex << std::uppercase
1026 << std::setw(8) << std::setfill('0')
1027 << static_cast<std::uint_least32_t>(c);
1030 report(
1031 DiagnosticsEngine::Warning,
1032 ("simplify construction of %0 with UTF-8 content as"
1033 " OUString(u\"%1\")"),
1034 expr->getExprLoc())
1035 << classdecl << s.str() << expr->getSourceRange();
1038 return true;
1040 if (cont != ContentKind::Ascii || emb) {
1041 // cf. remaining uses of RTL_CONSTASCII_USTRINGPARAM
1042 return true;
1044 kind = ChangeKind::Char;
1045 pass = n == 0
1046 ? PassThrough::EmptyConstantString
1047 : PassThrough::NonEmptyConstantString;
1048 simplify = true;
1049 break;
1051 default:
1052 return true;
1054 if (!calls_.empty()) {
1055 Expr const * call = calls_.top();
1056 CallExpr::const_arg_iterator argsBeg;
1057 CallExpr::const_arg_iterator argsEnd;
1058 if (isa<CallExpr>(call)) {
1059 argsBeg = cast<CallExpr>(call)->arg_begin();
1060 argsEnd = cast<CallExpr>(call)->arg_end();
1061 } else if (isa<CXXConstructExpr>(call)) {
1062 argsBeg = cast<CXXConstructExpr>(call)->arg_begin();
1063 argsEnd = cast<CXXConstructExpr>(call)->arg_end();
1064 } else {
1065 assert(false);
1067 for (auto i(argsBeg); i != argsEnd; ++i) {
1068 Expr const * e = (*i)->IgnoreParenImpCasts();
1069 if (isa<MaterializeTemporaryExpr>(e)) {
1070 e = cast<MaterializeTemporaryExpr>(e)->getSubExpr()
1071 ->IgnoreParenImpCasts();
1073 if (isa<CXXFunctionalCastExpr>(e)) {
1074 e = cast<CXXFunctionalCastExpr>(e)->getSubExpr()
1075 ->IgnoreParenImpCasts();
1077 if (isa<CXXBindTemporaryExpr>(e)) {
1078 e = cast<CXXBindTemporaryExpr>(e)->getSubExpr()
1079 ->IgnoreParenImpCasts();
1081 if (e == expr) {
1082 if (isa<CallExpr>(call)) {
1083 FunctionDecl const * fdecl
1084 = cast<CallExpr>(call)->getDirectCallee();
1085 if (fdecl == nullptr) {
1086 break;
1088 loplugin::DeclCheck dc(fdecl);
1089 if (pass == PassThrough::EmptyConstantString) {
1090 if ((dc.Function("equals").Class("OUString")
1091 .Namespace("rtl").GlobalNamespace())
1092 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1093 .GlobalNamespace()))
1095 report(
1096 DiagnosticsEngine::Warning,
1097 ("rewrite call of '%0' with construction of"
1098 " %1 with empty string constant argument"
1099 " as call of 'rtl::OUString::isEmpty'"),
1100 getMemberLocation(call))
1101 << fdecl->getQualifiedNameAsString()
1102 << classdecl << call->getSourceRange();
1103 return true;
1105 if (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
1106 .GlobalNamespace())
1108 report(
1109 DiagnosticsEngine::Warning,
1110 ("rewrite call of '%0' with construction of"
1111 " %1 with empty string constant argument"
1112 " as call of '!rtl::OUString::isEmpty'"),
1113 getMemberLocation(call))
1114 << fdecl->getQualifiedNameAsString()
1115 << classdecl << call->getSourceRange();
1116 return true;
1118 if ((dc.Operator(OO_Plus).Namespace("rtl")
1119 .GlobalNamespace())
1120 || (dc.Operator(OO_Plus).Class("OUString")
1121 .Namespace("rtl").GlobalNamespace()))
1123 report(
1124 DiagnosticsEngine::Warning,
1125 ("call of '%0' with suspicious construction"
1126 " of %1 with empty string constant"
1127 " argument"),
1128 getMemberLocation(call))
1129 << fdecl->getQualifiedNameAsString()
1130 << classdecl << call->getSourceRange();
1131 return true;
1133 if (dc.Operator(OO_Equal).Class("OUString")
1134 .Namespace("rtl").GlobalNamespace())
1136 report(
1137 DiagnosticsEngine::Warning,
1138 ("rewrite call of '%0' with construction of"
1139 " %1 with empty string constant argument"
1140 " as call of 'rtl::OUString::clear'"),
1141 getMemberLocation(call))
1142 << fdecl->getQualifiedNameAsString()
1143 << classdecl << call->getSourceRange();
1144 return true;
1146 } else {
1147 assert(pass == PassThrough::NonEmptyConstantString);
1148 if (dc.Function("equals").Class("OUString")
1149 .Namespace("rtl").GlobalNamespace())
1151 report(
1152 DiagnosticsEngine::Warning,
1153 ("rewrite call of '%0' with construction of"
1154 " %1 with %2 as 'operator =='"),
1155 getMemberLocation(call))
1156 << fdecl->getQualifiedNameAsString()
1157 << classdecl << describeChangeKind(kind)
1158 << 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())
1165 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1166 .GlobalNamespace())
1167 || (dc.Operator(OO_ExclaimEqual)
1168 .Namespace("rtl").GlobalNamespace()))
1170 if (dc.Operator(OO_Plus).Namespace("rtl")
1171 .GlobalNamespace())
1173 auto file = getFilenameOfLocation(
1174 compiler.getSourceManager()
1175 .getSpellingLoc(
1176 expr->getBeginLoc()));
1177 if (loplugin::isSamePathname(
1178 file,
1179 (SRCDIR
1180 "/sal/qa/rtl/strings/test_ostring_concat.cxx"))
1181 || loplugin::isSamePathname(
1182 file,
1183 (SRCDIR
1184 "/sal/qa/rtl/strings/test_oustring_concat.cxx")))
1186 return true;
1189 auto loc = expr->getArg(0)->getBeginLoc();
1190 while (compiler.getSourceManager()
1191 .isMacroArgExpansion(loc))
1193 loc = compiler.getSourceManager()
1194 .getImmediateMacroCallerLoc(loc);
1196 if (kind == ChangeKind::SingleChar) {
1197 report(
1198 DiagnosticsEngine::Warning,
1199 ("rewrite construction of %0 with %1 in"
1200 " call of '%2' as construction of"
1201 " 'OUStringChar'"),
1202 getMemberLocation(expr))
1203 << classdecl << describeChangeKind(kind)
1204 << fdecl->getQualifiedNameAsString()
1205 << expr->getSourceRange();
1206 } else {
1207 report(
1208 DiagnosticsEngine::Warning,
1209 ("elide construction of %0 with %1 in"
1210 " call of '%2'"),
1211 getMemberLocation(expr))
1212 << classdecl << describeChangeKind(kind)
1213 << fdecl->getQualifiedNameAsString()
1214 << expr->getSourceRange();
1216 return true;
1219 } else if (isa<CXXConstructExpr>(call)) {
1220 } else {
1221 assert(false);
1226 if (simplify) {
1227 report(
1228 DiagnosticsEngine::Warning,
1229 "simplify construction of %0 with %1", expr->getExprLoc())
1230 << classdecl << describeChangeKind(kind)
1231 << expr->getSourceRange();
1233 return true;
1236 auto consDecl = expr->getConstructor();
1237 for (unsigned i = 0; i != consDecl->getNumParams(); ++i) {
1238 auto t = consDecl->getParamDecl(i)->getType();
1239 if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
1240 .LvalueReference().Const().NotSubstTemplateTypeParmType()
1241 .Class("OUString").Namespace("rtl").GlobalNamespace())
1243 auto argExpr = expr->getArg(i);
1244 if (argExpr && i <= consDecl->getNumParams())
1246 if (!hasOverloads(consDecl, expr->getNumArgs()))
1248 handleOUStringCtor(expr, argExpr, consDecl, true);
1252 if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
1253 .LvalueReference().Const().NotSubstTemplateTypeParmType()
1254 .Class("OString").Namespace("rtl").GlobalNamespace())
1256 auto argExpr = expr->getArg(i);
1257 if (argExpr && i <= consDecl->getNumParams())
1259 if (!hasOverloads(consDecl, expr->getNumArgs()))
1261 handleOStringCtor(expr, argExpr, consDecl, true);
1267 return true;
1270 bool StringConstant::VisitReturnStmt(ReturnStmt const * stmt) {
1271 if (ignoreLocation(stmt)) {
1272 return true;
1274 auto const e1 = stmt->getRetValue();
1275 if (e1 == nullptr) {
1276 return true;
1278 auto const tc1 = loplugin::TypeCheck(e1->getType().getTypePtr());
1279 if (!(tc1.Class("OString").Namespace("rtl").GlobalNamespace()
1280 || tc1.Class("OUString").Namespace("rtl").GlobalNamespace()))
1282 return true;
1284 assert(!returnTypes_.empty());
1285 auto const tc2 = loplugin::TypeCheck(returnTypes_.top().getTypePtr());
1286 if (!(tc2.Class("OString").Namespace("rtl").GlobalNamespace()
1287 || tc2.Class("OUString").Namespace("rtl").GlobalNamespace()))
1289 return true;
1291 auto const e2 = dyn_cast<CXXFunctionalCastExpr>(e1->IgnoreImplicit());
1292 if (e2 == nullptr) {
1293 return true;
1295 auto const e3 = dyn_cast<CXXBindTemporaryExpr>(e2->getSubExpr());
1296 if (e3 == nullptr) {
1297 return true;
1299 auto const e4 = dyn_cast<CXXConstructExpr>(e3->getSubExpr());
1300 if (e4 == nullptr) {
1301 return true;
1303 if (e4->getNumArgs() != 2) {
1304 return true;
1306 auto const t = e4->getArg(0)->getType();
1307 if (!(t.isConstQualified() && t->isConstantArrayType())) {
1308 return true;
1310 auto const e5 = e4->getArg(1);
1311 if (!(isa<CXXDefaultArgExpr>(e5)
1312 && (loplugin::TypeCheck(e5->getType()).Struct("Dummy").Namespace("libreoffice_internal")
1313 .Namespace("rtl").GlobalNamespace())))
1315 return true;
1317 report(DiagnosticsEngine::Warning, "elide constructor call", e1->getBeginLoc())
1318 << e1->getSourceRange();
1319 return true;
1322 std::string StringConstant::describeChangeKind(ChangeKind kind) {
1323 switch (kind) {
1324 case ChangeKind::Char:
1325 return "string constant argument";
1326 case ChangeKind::CharLen:
1327 return "string constant and matching length arguments";
1328 case ChangeKind::SingleChar:
1329 return "sal_Unicode argument";
1330 case ChangeKind::OUStringChar:
1331 return "OUStringChar argument";
1333 llvm_unreachable("unknown change kind");
1336 bool StringConstant::isStringConstant(
1337 Expr const * expr, unsigned * size, bool * nonArray, ContentKind * content,
1338 bool * embeddedNuls, bool * terminatingNul,
1339 std::vector<char32_t> * utf8Content)
1341 assert(expr != nullptr);
1342 assert(size != nullptr);
1343 assert(nonArray != nullptr);
1344 assert(content != nullptr);
1345 assert(embeddedNuls != nullptr);
1346 assert(terminatingNul != nullptr);
1347 QualType t = expr->getType();
1348 // Look inside RTL_CONSTASCII_STRINGPARAM:
1349 if (loplugin::TypeCheck(t).Pointer().Const().Char()) {
1350 auto e2 = dyn_cast<UnaryOperator>(expr);
1351 if (e2 != nullptr && e2->getOpcode() == UO_AddrOf) {
1352 auto e3 = dyn_cast<ArraySubscriptExpr>(
1353 e2->getSubExpr()->IgnoreParenImpCasts());
1354 if (e3 == nullptr || !isZero(e3->getIdx()->IgnoreParenImpCasts())) {
1355 return false;
1357 expr = e3->getBase()->IgnoreParenImpCasts();
1358 t = expr->getType();
1361 if (!t.isConstQualified()) {
1362 return false;
1364 DeclRefExpr const * dre = dyn_cast<DeclRefExpr>(expr);
1365 if (dre != nullptr) {
1366 VarDecl const * var = dyn_cast<VarDecl>(dre->getDecl());
1367 if (var != nullptr) {
1368 Expr const * init = var->getAnyInitializer();
1369 if (init != nullptr) {
1370 expr = init->IgnoreParenImpCasts();
1374 bool isPtr;
1375 if (loplugin::TypeCheck(t).Pointer().Const().Char()) {
1376 isPtr = true;
1377 } else if (t->isConstantArrayType()
1378 && (loplugin::TypeCheck(
1379 t->getAsArrayTypeUnsafe()->getElementType())
1380 .Char()))
1382 isPtr = false;
1383 } else {
1384 return false;
1386 clang::StringLiteral const * lit = dyn_cast<clang::StringLiteral>(expr);
1387 if (lit != nullptr) {
1388 if (!(compat::isOrdinary(lit) || lit->isUTF8())) {
1389 return false;
1391 unsigned n = lit->getLength();
1392 ContentKind cont = ContentKind::Ascii;
1393 bool emb = false;
1394 char32_t val = 0;
1395 enum class Utf8State { Start, E0, EB, F0, F4, Trail1, Trail2, Trail3 };
1396 Utf8State s = Utf8State::Start;
1397 StringRef str = lit->getString();
1398 for (unsigned i = 0; i != n; ++i) {
1399 auto const c = static_cast<unsigned char>(str[i]);
1400 if (c == '\0') {
1401 emb = true;
1403 switch (s) {
1404 case Utf8State::Start:
1405 if (c >= 0x80) {
1406 if (c >= 0xC2 && c <= 0xDF) {
1407 val = c & 0x1F;
1408 s = Utf8State::Trail1;
1409 } else if (c == 0xE0) {
1410 val = c & 0x0F;
1411 s = Utf8State::E0;
1412 } else if ((c >= 0xE1 && c <= 0xEA)
1413 || (c >= 0xEE && c <= 0xEF))
1415 val = c & 0x0F;
1416 s = Utf8State::Trail2;
1417 } else if (c == 0xEB) {
1418 val = c & 0x0F;
1419 s = Utf8State::EB;
1420 } else if (c == 0xF0) {
1421 val = c & 0x03;
1422 s = Utf8State::F0;
1423 } else if (c >= 0xF1 && c <= 0xF3) {
1424 val = c & 0x03;
1425 s = Utf8State::Trail3;
1426 } else if (c == 0xF4) {
1427 val = c & 0x03;
1428 s = Utf8State::F4;
1429 } else {
1430 cont = ContentKind::Arbitrary;
1432 } else if (utf8Content != nullptr
1433 && cont != ContentKind::Arbitrary)
1435 utf8Content->push_back(c);
1437 break;
1438 case Utf8State::E0:
1439 if (c >= 0xA0 && c <= 0xBF) {
1440 val = (val << 6) | (c & 0x3F);
1441 s = Utf8State::Trail1;
1442 } else {
1443 cont = ContentKind::Arbitrary;
1444 s = Utf8State::Start;
1446 break;
1447 case Utf8State::EB:
1448 if (c >= 0x80 && c <= 0x9F) {
1449 val = (val << 6) | (c & 0x3F);
1450 s = Utf8State::Trail1;
1451 } else {
1452 cont = ContentKind::Arbitrary;
1453 s = Utf8State::Start;
1455 break;
1456 case Utf8State::F0:
1457 if (c >= 0x90 && c <= 0xBF) {
1458 val = (val << 6) | (c & 0x3F);
1459 s = Utf8State::Trail2;
1460 } else {
1461 cont = ContentKind::Arbitrary;
1462 s = Utf8State::Start;
1464 break;
1465 case Utf8State::F4:
1466 if (c >= 0x80 && c <= 0x8F) {
1467 val = (val << 6) | (c & 0x3F);
1468 s = Utf8State::Trail2;
1469 } else {
1470 cont = ContentKind::Arbitrary;
1471 s = Utf8State::Start;
1473 break;
1474 case Utf8State::Trail1:
1475 if (c >= 0x80 && c <= 0xBF) {
1476 cont = ContentKind::Utf8;
1477 if (utf8Content != nullptr)
1479 utf8Content->push_back((val << 6) | (c & 0x3F));
1480 val = 0;
1482 } else {
1483 cont = ContentKind::Arbitrary;
1485 s = Utf8State::Start;
1486 break;
1487 case Utf8State::Trail2:
1488 if (c >= 0x80 && c <= 0xBF) {
1489 val = (val << 6) | (c & 0x3F);
1490 s = Utf8State::Trail1;
1491 } else {
1492 cont = ContentKind::Arbitrary;
1493 s = Utf8State::Start;
1495 break;
1496 case Utf8State::Trail3:
1497 if (c >= 0x80 && c <= 0xBF) {
1498 val = (val << 6) | (c & 0x3F);
1499 s = Utf8State::Trail2;
1500 } else {
1501 cont = ContentKind::Arbitrary;
1502 s = Utf8State::Start;
1504 break;
1507 *size = n;
1508 *nonArray = isPtr;
1509 *content = cont;
1510 *embeddedNuls = emb;
1511 *terminatingNul = true;
1512 return true;
1514 APValue v;
1515 if (!expr->isCXX11ConstantExpr(compiler.getASTContext(), &v)) {
1516 return false;
1518 switch (v.getKind()) {
1519 case APValue::LValue:
1521 Expr const * e = v.getLValueBase().dyn_cast<Expr const *>();
1522 if (e == nullptr) {
1523 return false;
1525 if (!v.getLValueOffset().isZero()) {
1526 return false; //TODO
1528 Expr const * e2 = e->IgnoreParenImpCasts();
1529 if (e2 != e) {
1530 return isStringConstant(
1531 e2, size, nonArray, content, embeddedNuls, terminatingNul);
1533 //TODO: string literals are represented as recursive LValues???
1534 llvm::APInt n
1535 = compiler.getASTContext().getAsConstantArrayType(t)->getSize();
1536 assert(n != 0);
1537 --n;
1538 assert(n.ule(std::numeric_limits<unsigned>::max()));
1539 *size = static_cast<unsigned>(n.getLimitedValue());
1540 *nonArray = isPtr || *nonArray;
1541 *content = ContentKind::Ascii; //TODO
1542 *embeddedNuls = false; //TODO
1543 *terminatingNul = true;
1544 return true;
1546 case APValue::Array:
1548 if (v.hasArrayFiller()) { //TODO: handle final NULL filler?
1549 return false;
1551 unsigned n = v.getArraySize();
1552 assert(n != 0);
1553 ContentKind cont = ContentKind::Ascii;
1554 bool emb = false;
1555 //TODO: check for ContentType::Utf8
1556 for (unsigned i = 0; i != n - 1; ++i) {
1557 APValue e(v.getArrayInitializedElt(i));
1558 if (!e.isInt()) { //TODO: assert?
1559 return false;
1561 APSInt iv = e.getInt();
1562 if (iv == 0) {
1563 emb = true;
1564 } else if (iv.uge(0x80)) {
1565 cont = ContentKind::Arbitrary;
1568 APValue e(v.getArrayInitializedElt(n - 1));
1569 if (!e.isInt()) { //TODO: assert?
1570 return false;
1572 bool trm = e.getInt() == 0;
1573 *size = trm ? n - 1 : n;
1574 *nonArray = isPtr;
1575 *content = cont;
1576 *embeddedNuls = emb;
1577 *terminatingNul = trm;
1578 return true;
1580 default:
1581 assert(false); //TODO???
1582 return false;
1586 bool StringConstant::isZero(Expr const * expr) {
1587 APSInt res;
1588 return compat::EvaluateAsInt(expr, res, compiler.getASTContext()) && res == 0;
1591 void StringConstant::reportChange(
1592 Expr const * expr, ChangeKind kind, std::string const & original,
1593 std::string const & replacement, PassThrough pass, bool nonArray,
1594 char const * rewriteFrom, char const * rewriteTo)
1596 assert((rewriteFrom == nullptr) == (rewriteTo == nullptr));
1597 if (pass != PassThrough::No && !calls_.empty()) {
1598 Expr const * call = calls_.top();
1599 CallExpr::const_arg_iterator argsBeg;
1600 CallExpr::const_arg_iterator argsEnd;
1601 if (isa<CallExpr>(call)) {
1602 argsBeg = cast<CallExpr>(call)->arg_begin();
1603 argsEnd = cast<CallExpr>(call)->arg_end();
1604 } else if (isa<CXXConstructExpr>(call)) {
1605 argsBeg = cast<CXXConstructExpr>(call)->arg_begin();
1606 argsEnd = cast<CXXConstructExpr>(call)->arg_end();
1607 } else {
1608 assert(false);
1610 for (auto i(argsBeg); i != argsEnd; ++i) {
1611 Expr const * e = (*i)->IgnoreParenImpCasts();
1612 if (isa<CXXBindTemporaryExpr>(e)) {
1613 e = cast<CXXBindTemporaryExpr>(e)->getSubExpr()
1614 ->IgnoreParenImpCasts();
1616 if (e == expr) {
1617 if (isa<CallExpr>(call)) {
1618 FunctionDecl const * fdecl
1619 = cast<CallExpr>(call)->getDirectCallee();
1620 if (fdecl == nullptr) {
1621 break;
1623 loplugin::DeclCheck dc(fdecl);
1624 if (pass == PassThrough::EmptyConstantString) {
1625 if ((dc.Function("equals").Class("OUString")
1626 .Namespace("rtl").GlobalNamespace())
1627 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1628 .GlobalNamespace()))
1630 report(
1631 DiagnosticsEngine::Warning,
1632 ("rewrite call of '%0' with call of %1 with"
1633 " empty string constant argument as call of"
1634 " 'rtl::OUString::isEmpty'"),
1635 getMemberLocation(call))
1636 << fdecl->getQualifiedNameAsString() << original
1637 << call->getSourceRange();
1638 return;
1640 if (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
1641 .GlobalNamespace())
1643 report(
1644 DiagnosticsEngine::Warning,
1645 ("rewrite call of '%0' with call of %1 with"
1646 " empty string constant argument as call of"
1647 " '!rtl::OUString::isEmpty'"),
1648 getMemberLocation(call))
1649 << fdecl->getQualifiedNameAsString() << original
1650 << call->getSourceRange();
1651 return;
1653 if ((dc.Operator(OO_Plus).Namespace("rtl")
1654 .GlobalNamespace())
1655 || (dc.Operator(OO_Plus).Class("OUString")
1656 .Namespace("rtl").GlobalNamespace()))
1658 report(
1659 DiagnosticsEngine::Warning,
1660 ("call of '%0' with suspicious call of %1 with"
1661 " empty string constant argument"),
1662 getMemberLocation(call))
1663 << fdecl->getQualifiedNameAsString() << original
1664 << call->getSourceRange();
1665 return;
1667 if (dc.Operator(OO_Equal).Class("OUString")
1668 .Namespace("rtl").GlobalNamespace())
1670 report(
1671 DiagnosticsEngine::Warning,
1672 ("rewrite call of '%0' with call of %1 with"
1673 " empty string constant argument as call of"
1674 " rtl::OUString::call"),
1675 getMemberLocation(call))
1676 << fdecl->getQualifiedNameAsString() << original
1677 << call->getSourceRange();
1678 return;
1680 report(
1681 DiagnosticsEngine::Warning,
1682 "TODO call inside %0", getMemberLocation(expr))
1683 << fdecl->getQualifiedNameAsString()
1684 << expr->getSourceRange();
1685 return;
1686 } else {
1687 assert(pass == PassThrough::NonEmptyConstantString);
1688 if ((dc.Function("equals").Class("OUString")
1689 .Namespace("rtl").GlobalNamespace())
1690 || (dc.Operator(OO_Equal).Class("OUString")
1691 .Namespace("rtl").GlobalNamespace())
1692 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1693 .GlobalNamespace())
1694 || (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
1695 .GlobalNamespace()))
1697 report(
1698 DiagnosticsEngine::Warning,
1699 "elide call of %0 with %1 in call of '%2'",
1700 getMemberLocation(expr))
1701 << original << describeChangeKind(kind)
1702 << fdecl->getQualifiedNameAsString()
1703 << expr->getSourceRange();
1704 return;
1706 report(
1707 DiagnosticsEngine::Warning,
1708 ("rewrite call of %0 with %1 in call of '%2' as"
1709 " (implicit) construction of 'OUString'"),
1710 getMemberLocation(expr))
1711 << original << describeChangeKind(kind)
1712 << fdecl->getQualifiedNameAsString()
1713 << expr->getSourceRange();
1714 return;
1716 } else if (isa<CXXConstructExpr>(call)) {
1717 auto classdecl = cast<CXXConstructExpr>(call)
1718 ->getConstructor()->getParent();
1719 loplugin::DeclCheck dc(classdecl);
1720 if (dc.Class("OUString").Namespace("rtl").GlobalNamespace()
1721 || (dc.Class("OUStringBuffer").Namespace("rtl")
1722 .GlobalNamespace()))
1724 //TODO: propagate further out?
1725 if (pass == PassThrough::EmptyConstantString) {
1726 report(
1727 DiagnosticsEngine::Warning,
1728 ("rewrite construction of %0 with call of %1"
1729 " with empty string constant argument as"
1730 " default construction of %0"),
1731 getMemberLocation(call))
1732 << classdecl << original
1733 << call->getSourceRange();
1734 } else {
1735 assert(pass == PassThrough::NonEmptyConstantString);
1736 report(
1737 DiagnosticsEngine::Warning,
1738 ("elide call of %0 with %1 in construction of"
1739 " %2"),
1740 getMemberLocation(expr))
1741 << original << describeChangeKind(kind)
1742 << classdecl << expr->getSourceRange();
1744 return;
1746 } else {
1747 assert(false);
1752 if (rewriter != nullptr && !nonArray && rewriteFrom != nullptr) {
1753 SourceLocation loc = getMemberLocation(expr);
1754 while (compiler.getSourceManager().isMacroArgExpansion(loc)) {
1755 loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
1757 if (compiler.getSourceManager().isMacroBodyExpansion(loc)) {
1758 loc = compiler.getSourceManager().getSpellingLoc(loc);
1760 unsigned n = Lexer::MeasureTokenLength(
1761 loc, compiler.getSourceManager(), compiler.getLangOpts());
1762 if ((std::string(compiler.getSourceManager().getCharacterData(loc), n)
1763 == rewriteFrom)
1764 && replaceText(loc, n, rewriteTo))
1766 return;
1769 report(
1770 DiagnosticsEngine::Warning,
1771 "rewrite call of '%0' with %1 as call of '%2'%3",
1772 getMemberLocation(expr))
1773 << original << describeChangeKind(kind) << replacement
1774 << adviseNonArray(nonArray) << expr->getSourceRange();
1777 void StringConstant::checkEmpty(
1778 CallExpr const * expr, FunctionDecl const * callee, TreatEmpty treatEmpty,
1779 unsigned size, std::string * replacement)
1781 assert(replacement != nullptr);
1782 if (size == 0) {
1783 switch (treatEmpty) {
1784 case TreatEmpty::DefaultCtor:
1785 *replacement = "rtl::OUString default constructor";
1786 break;
1787 case TreatEmpty::CheckEmpty:
1788 *replacement = "rtl::OUString::isEmpty";
1789 break;
1790 case TreatEmpty::Error:
1791 report(
1792 DiagnosticsEngine::Warning,
1793 "call of '%0' with suspicious empty string constant argument",
1794 getMemberLocation(expr))
1795 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1796 break;
1801 void StringConstant::handleChar(
1802 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
1803 std::string const & replacement, TreatEmpty treatEmpty, bool literal,
1804 char const * rewriteFrom, char const * rewriteTo)
1806 unsigned n;
1807 bool nonArray;
1808 ContentKind cont;
1809 bool emb;
1810 bool trm;
1811 if (!isStringConstant(
1812 expr->getArg(arg)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
1813 &emb, &trm))
1815 return;
1817 if (cont != ContentKind::Ascii) {
1818 report(
1819 DiagnosticsEngine::Warning,
1820 ("call of '%0' with string constant argument containing non-ASCII"
1821 " characters"),
1822 getMemberLocation(expr))
1823 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1824 return;
1826 if (emb) {
1827 report(
1828 DiagnosticsEngine::Warning,
1829 ("call of '%0' with string constant argument containing embedded"
1830 " NULLs"),
1831 getMemberLocation(expr))
1832 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1833 return;
1835 if (!trm) {
1836 report(
1837 DiagnosticsEngine::Warning,
1838 ("call of '%0' with string constant argument lacking a terminating"
1839 " NULL"),
1840 getMemberLocation(expr))
1841 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1842 return;
1844 std::string repl(replacement);
1845 checkEmpty(expr, callee, treatEmpty, n, &repl);
1846 if (!repl.empty()) {
1847 reportChange(
1848 expr, ChangeKind::Char, callee->getQualifiedNameAsString(), repl,
1849 (literal
1850 ? (n == 0
1851 ? PassThrough::EmptyConstantString
1852 : PassThrough::NonEmptyConstantString)
1853 : PassThrough::No),
1854 nonArray, rewriteFrom, rewriteTo);
1858 void StringConstant::handleCharLen(
1859 CallExpr const * expr, unsigned arg1, unsigned arg2,
1860 FunctionDecl const * callee, std::string const & replacement,
1861 TreatEmpty treatEmpty)
1863 // Especially for f(RTL_CONSTASCII_STRINGPARAM("foo")), where
1864 // RTL_CONSTASCII_STRINGPARAM expands to complicated expressions involving
1865 // (&(X)[0] sub-expressions (and it might or might not be better to handle
1866 // that at the level of non-expanded macros instead, but I have not found
1867 // out how to do that yet anyway):
1868 unsigned n;
1869 bool nonArray;
1870 ContentKind cont;
1871 bool emb;
1872 bool trm;
1873 if (!(isStringConstant(
1874 expr->getArg(arg1)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
1875 &emb, &trm)
1876 && trm))
1878 return;
1880 APSInt res;
1881 if (compat::EvaluateAsInt(expr->getArg(arg2), res, compiler.getASTContext())) {
1882 if (res != n) {
1883 return;
1885 } else {
1886 UnaryOperator const * op = dyn_cast<UnaryOperator>(
1887 expr->getArg(arg1)->IgnoreParenImpCasts());
1888 if (op == nullptr || op->getOpcode() != UO_AddrOf) {
1889 return;
1891 ArraySubscriptExpr const * subs = dyn_cast<ArraySubscriptExpr>(
1892 op->getSubExpr()->IgnoreParenImpCasts());
1893 if (subs == nullptr) {
1894 return;
1896 unsigned n2;
1897 bool nonArray2;
1898 ContentKind cont2;
1899 bool emb2;
1900 bool trm2;
1901 if (!(isStringConstant(
1902 subs->getBase()->IgnoreParenImpCasts(), &n2, &nonArray2,
1903 &cont2, &emb2, &trm2)
1904 && n2 == n && cont2 == cont && emb2 == emb && trm2 == trm
1905 //TODO: same strings
1906 && compat::EvaluateAsInt(subs->getIdx(), res, compiler.getASTContext())
1907 && res == 0))
1909 return;
1912 if (cont != ContentKind::Ascii) {
1913 report(
1914 DiagnosticsEngine::Warning,
1915 ("call of '%0' with string constant argument containing non-ASCII"
1916 " characters"),
1917 getMemberLocation(expr))
1918 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1920 if (emb) {
1921 return;
1923 std::string repl(replacement);
1924 checkEmpty(expr, callee, treatEmpty, n, &repl);
1925 reportChange(
1926 expr, ChangeKind::CharLen, callee->getQualifiedNameAsString(), repl,
1927 PassThrough::No, nonArray, nullptr, nullptr);
1930 void StringConstant::handleOUStringCtor(
1931 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
1932 bool explicitFunctionalCastNotation)
1934 handleOUStringCtor(expr, expr->getArg(arg), callee, explicitFunctionalCastNotation);
1937 void StringConstant::handleOStringCtor(
1938 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
1939 bool explicitFunctionalCastNotation)
1941 handleOStringCtor(expr, expr->getArg(arg), callee, explicitFunctionalCastNotation);
1944 void StringConstant::handleOUStringCtor(
1945 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
1946 bool explicitFunctionalCastNotation)
1948 handleStringCtor(expr, argExpr, callee, explicitFunctionalCastNotation, StringKind::Unicode);
1951 void StringConstant::handleOStringCtor(
1952 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
1953 bool explicitFunctionalCastNotation)
1955 handleStringCtor(expr, argExpr, callee, explicitFunctionalCastNotation, StringKind::Char);
1958 void StringConstant::handleStringCtor(
1959 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
1960 bool explicitFunctionalCastNotation, StringKind stringKind)
1962 auto e0 = argExpr->IgnoreParenImpCasts();
1963 if (auto const e1 = dyn_cast<CXXMemberCallExpr>(e0)) {
1964 if (auto const e2 = dyn_cast<CXXConversionDecl>(e1->getMethodDecl())) {
1965 if (loplugin::TypeCheck(e2->getConversionType()).ClassOrStruct("basic_string_view")
1966 .StdNamespace())
1968 e0 = e1->getImplicitObjectArgument()->IgnoreParenImpCasts();
1972 auto e1 = dyn_cast<CXXFunctionalCastExpr>(e0);
1973 if (e1 == nullptr) {
1974 if (explicitFunctionalCastNotation) {
1975 return;
1977 } else {
1978 e0 = e1->getSubExpr()->IgnoreParenImpCasts();
1980 auto e2 = dyn_cast<CXXBindTemporaryExpr>(e0);
1981 if (e2 == nullptr) {
1982 return;
1984 auto e3 = dyn_cast<CXXConstructExpr>(
1985 e2->getSubExpr()->IgnoreParenImpCasts());
1986 if (e3 == nullptr) {
1987 return;
1989 if (!loplugin::DeclCheck(e3->getConstructor()).MemberFunction()
1990 .Class(stringKind == StringKind::Unicode ? "OUString" : "OString").Namespace("rtl").GlobalNamespace())
1992 return;
1994 if (e3->getNumArgs() == 0) {
1995 report(
1996 DiagnosticsEngine::Warning,
1997 ("in call of '%0', replace default-constructed 'OUString' with an"
1998 " empty string literal"),
1999 e3->getExprLoc())
2000 << callee->getQualifiedNameAsString() << expr->getSourceRange();
2001 return;
2003 if (e3->getNumArgs() == 1
2004 && e3->getConstructor()->getNumParams() == 1
2005 && (loplugin::TypeCheck(
2006 e3->getConstructor()->getParamDecl(0)->getType())
2007 .Typedef(stringKind == StringKind::Unicode ? "sal_Unicode" : "char").GlobalNamespace()))
2009 // It may not be easy to rewrite OUString(c), esp. given there is no
2010 // OUString ctor taking an OUStringChar arg, so don't warn there:
2011 if (!explicitFunctionalCastNotation) {
2012 report(
2013 DiagnosticsEngine::Warning,
2014 ("in call of '%0', replace 'OUString' constructed from a"
2015 " 'sal_Unicode' with an 'OUStringChar'"),
2016 e3->getExprLoc())
2017 << callee->getQualifiedNameAsString() << expr->getSourceRange();
2019 return;
2021 if (e3->getNumArgs() != 2) {
2022 return;
2024 unsigned n;
2025 bool nonArray;
2026 ContentKind cont;
2027 bool emb;
2028 bool trm;
2029 if (!isStringConstant(
2030 e3->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray, &cont, &emb,
2031 &trm))
2033 return;
2035 //TODO: cont, emb, trm
2036 if (rewriter != nullptr) {
2037 auto loc1 = e3->getBeginLoc();
2038 auto range = e3->getParenOrBraceRange();
2039 if (loc1.isFileID() && range.getBegin().isFileID()
2040 && range.getEnd().isFileID())
2042 auto loc2 = range.getBegin();
2043 for (bool first = true;; first = false) {
2044 unsigned n = Lexer::MeasureTokenLength(
2045 loc2, compiler.getSourceManager(), compiler.getLangOpts());
2046 if (!first) {
2047 StringRef s(
2048 compiler.getSourceManager().getCharacterData(loc2), n);
2049 while (s.startswith("\\\n")) {
2050 s = s.drop_front(2);
2051 while (!s.empty()
2052 && (s.front() == ' ' || s.front() == '\t'
2053 || s.front() == '\n' || s.front() == '\v'
2054 || s.front() == '\f'))
2056 s = s.drop_front(1);
2059 if (!(s.empty() || s.startswith("/*") || s.startswith("//")
2060 || s == "\\"))
2062 break;
2065 loc2 = loc2.getLocWithOffset(std::max<unsigned>(n, 1));
2067 auto loc3 = range.getEnd();
2068 for (;;) {
2069 auto l = Lexer::GetBeginningOfToken(
2070 loc3.getLocWithOffset(-1), compiler.getSourceManager(),
2071 compiler.getLangOpts());
2072 unsigned n = Lexer::MeasureTokenLength(
2073 l, compiler.getSourceManager(), compiler.getLangOpts());
2074 StringRef s(compiler.getSourceManager().getCharacterData(l), n);
2075 while (s.startswith("\\\n")) {
2076 s = s.drop_front(2);
2077 while (!s.empty()
2078 && (s.front() == ' ' || s.front() == '\t'
2079 || s.front() == '\n' || s.front() == '\v'
2080 || s.front() == '\f'))
2082 s = s.drop_front(1);
2085 if (!(s.empty() || s.startswith("/*") || s.startswith("//")
2086 || s == "\\"))
2088 break;
2090 loc3 = l;
2092 if (removeText(CharSourceRange(SourceRange(loc1, loc2), false))) {
2093 if (removeText(SourceRange(loc3, range.getEnd()))) {
2094 return;
2096 report(DiagnosticsEngine::Fatal, "Corrupt rewrite", loc3)
2097 << expr->getSourceRange();
2098 return;
2102 report(
2103 DiagnosticsEngine::Warning,
2104 ("in call of '%0', replace 'OUString' constructed from a string literal"
2105 " directly with the string literal"),
2106 e3->getExprLoc())
2107 << callee->getQualifiedNameAsString() << expr->getSourceRange();
2110 void StringConstant::handleFunArgOstring(
2111 CallExpr const * expr, unsigned arg, FunctionDecl const * callee)
2113 auto argExpr = expr->getArg(arg)->IgnoreParenImpCasts();
2114 unsigned n;
2115 bool nonArray;
2116 ContentKind cont;
2117 bool emb;
2118 bool trm;
2119 if (isStringConstant(argExpr, &n, &nonArray, &cont, &emb, &trm)) {
2120 if (cont != ContentKind::Ascii || emb) {
2121 return;
2123 if (!trm) {
2124 report(
2125 DiagnosticsEngine::Warning,
2126 ("call of '%0' with string constant argument lacking a"
2127 " terminating NULL"),
2128 getMemberLocation(expr))
2129 << callee->getQualifiedNameAsString() << expr->getSourceRange();
2130 return;
2132 std::string repl;
2133 checkEmpty(expr, callee, TreatEmpty::Error, n, &repl);
2134 if (nonArray) {
2135 report(
2136 DiagnosticsEngine::Warning,
2137 ("in call of '%0' with non-array string constant argument,"
2138 " turn the non-array string constant into an array"),
2139 getMemberLocation(expr))
2140 << callee->getQualifiedNameAsString() << expr->getSourceRange();
2142 } else if (auto cexpr = lookForCXXConstructExpr(argExpr)) {
2143 auto classdecl = cexpr->getConstructor()->getParent();
2144 if (loplugin::DeclCheck(classdecl).Class("OString").Namespace("rtl")
2145 .GlobalNamespace())
2147 switch (cexpr->getConstructor()->getNumParams()) {
2148 case 0:
2149 report(
2150 DiagnosticsEngine::Warning,
2151 ("in call of '%0', replace empty %1 constructor with empty"
2152 " string literal"),
2153 cexpr->getLocation())
2154 << callee->getQualifiedNameAsString() << classdecl
2155 << expr->getSourceRange();
2156 break;
2157 case 2:
2158 if (isStringConstant(
2159 cexpr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray,
2160 &cont, &emb, &trm))
2162 APSInt res;
2163 if (compat::EvaluateAsInt(cexpr->getArg(1),
2164 res, compiler.getASTContext()))
2166 if (res == n && !emb && trm) {
2167 report(
2168 DiagnosticsEngine::Warning,
2169 ("in call of '%0', elide explicit %1"
2170 " constructor%2"),
2171 cexpr->getLocation())
2172 << callee->getQualifiedNameAsString()
2173 << classdecl << adviseNonArray(nonArray)
2174 << expr->getSourceRange();
2176 } else {
2177 if (emb) {
2178 report(
2179 DiagnosticsEngine::Warning,
2180 ("call of %0 constructor with string constant"
2181 " argument containing embedded NULLs"),
2182 cexpr->getLocation())
2183 << classdecl << cexpr->getSourceRange();
2184 return;
2186 if (!trm) {
2187 report(
2188 DiagnosticsEngine::Warning,
2189 ("call of %0 constructor with string constant"
2190 " argument lacking a terminating NULL"),
2191 cexpr->getLocation())
2192 << classdecl << cexpr->getSourceRange();
2193 return;
2195 report(
2196 DiagnosticsEngine::Warning,
2197 "in call of '%0', elide explicit %1 constructor%2",
2198 cexpr->getLocation())
2199 << callee->getQualifiedNameAsString() << classdecl
2200 << adviseNonArray(nonArray)
2201 << expr->getSourceRange();
2204 break;
2205 default:
2206 break;
2212 loplugin::Plugin::Registration< StringConstant > X("stringconstant", true);
2216 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */