bump product version to 5.0.4.1
[LibreOffice.git] / compilerplugins / clang / stringconstant.cxx
blob12bf173bc4f1167e4171f3cd217351ff843bd809
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 <cassert>
11 #include <limits>
12 #include <stack>
13 #include <string>
15 #include "plugin.hxx"
17 // Define a "string constant" to be a constant expression either of type "array
18 // of N char" where each array element is a non-NUL ASCII character---except
19 // that the last array element may be NUL, or, in some situations, of type char
20 // with a ASCII value (including NUL). Note that the former includes
21 // expressions denoting narrow string literals like "foo", and, with toolchains
22 // that support constexpr, constexpr variables declared like
24 // constexpr char str[] = "bar";
26 // This plugin flags uses of OUString functions with string constant arguments
27 // that can be rewritten more directly, like
29 // OUString::createFromAscii("foo") -> "foo"
31 // and
33 // s.equals(OUString("bar")) -> s == "bar"
35 namespace {
37 bool isPlainChar(QualType type) {
38 return type->isSpecificBuiltinType(BuiltinType::Char_S)
39 || type->isSpecificBuiltinType(BuiltinType::Char_U);
42 SourceLocation getMemberLocation(Expr const * expr) {
43 CallExpr const * e1 = dyn_cast<CallExpr>(expr);
44 MemberExpr const * e2 = e1 == nullptr
45 ? nullptr : dyn_cast<MemberExpr>(e1->getCallee());
46 return e2 == nullptr ? expr->getExprLoc()/*TODO*/ : e2->getMemberLoc();
49 class StringConstant:
50 public RecursiveASTVisitor<StringConstant>, public loplugin::Plugin
52 public:
53 explicit StringConstant(InstantiationData const & data): Plugin(data) {}
55 void run() override;
57 bool TraverseCallExpr(CallExpr * expr);
59 bool TraverseCXXMemberCallExpr(CXXMemberCallExpr * expr);
61 bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr * expr);
63 bool TraverseCXXConstructExpr(CXXConstructExpr * expr);
65 bool VisitCallExpr(CallExpr const * expr);
67 bool VisitCXXConstructExpr(CXXConstructExpr const * expr);
69 private:
70 enum class TreatEmpty { DefaultCtor, CheckEmpty, Error };
72 enum class ChangeKind { Char, CharLen, SingleChar };
74 enum class PassThrough { No, EmptyConstantString, NonEmptyConstantString };
76 std::string describeChangeKind(ChangeKind kind);
78 bool isStringConstant(
79 Expr const * expr, unsigned * size, bool * nonAscii,
80 bool * embeddedNuls, bool * terminatingNul);
82 bool isZero(Expr const * expr);
84 void reportChange(
85 Expr const * expr, ChangeKind kind, std::string const & original,
86 std::string const & replacement, PassThrough pass);
88 void checkEmpty(
89 CallExpr const * expr, std::string const & qname, TreatEmpty treatEmpty,
90 unsigned size, std::string * replacement);
92 void handleChar(
93 CallExpr const * expr, unsigned arg, std::string const & qname,
94 std::string const & replacement, TreatEmpty treatEmpty, bool literal);
96 void handleCharLen(
97 CallExpr const * expr, unsigned arg1, unsigned arg2,
98 std::string const & qname, std::string const & replacement,
99 TreatEmpty treatEmpty);
101 std::stack<Expr const *> calls_;
104 void StringConstant::run() {
105 if (compiler.getLangOpts().CPlusPlus
106 && compiler.getPreprocessor().getIdentifierInfo(
107 "LIBO_INTERNAL_ONLY")->hasMacroDefinition())
108 //TODO: some parts of it are useful for external code, too
110 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
114 bool StringConstant::TraverseCallExpr(CallExpr * expr) {
115 if (!WalkUpFromCallExpr(expr)) {
116 return false;
118 calls_.push(expr);
119 bool res = true;
120 for (auto * e: expr->children()) {
121 if (!TraverseStmt(e)) {
122 res = false;
123 break;
126 calls_.pop();
127 return res;
130 bool StringConstant::TraverseCXXMemberCallExpr(CXXMemberCallExpr * expr) {
131 if (!WalkUpFromCXXMemberCallExpr(expr)) {
132 return false;
134 calls_.push(expr);
135 bool res = true;
136 for (auto * e: expr->children()) {
137 if (!TraverseStmt(e)) {
138 res = false;
139 break;
142 calls_.pop();
143 return res;
146 bool StringConstant::TraverseCXXOperatorCallExpr(CXXOperatorCallExpr * expr)
148 if (!WalkUpFromCXXOperatorCallExpr(expr)) {
149 return false;
151 calls_.push(expr);
152 bool res = true;
153 for (auto * e: expr->children()) {
154 if (!TraverseStmt(e)) {
155 res = false;
156 break;
159 calls_.pop();
160 return res;
163 bool StringConstant::TraverseCXXConstructExpr(CXXConstructExpr * expr) {
164 if (!WalkUpFromCXXConstructExpr(expr)) {
165 return false;
167 calls_.push(expr);
168 bool res = true;
169 for (auto * e: expr->children()) {
170 if (!TraverseStmt(e)) {
171 res = false;
172 break;
175 calls_.pop();
176 return res;
179 bool StringConstant::VisitCallExpr(CallExpr const * expr) {
180 if (ignoreLocation(expr)) {
181 return true;
183 FunctionDecl const * fdecl = expr->getDirectCallee();
184 if (fdecl == nullptr) {
185 return true;
187 std::string qname(fdecl->getQualifiedNameAsString());
188 //TODO: u.compareToAscii("foo") -> u.???("foo")
189 //TODO: u.compareToIgnoreAsciiCaseAscii("foo") -> u.???("foo")
190 if (qname == "rtl::OUString::createFromAscii" && fdecl->getNumParams() == 1)
192 // OUString::createFromAscii("foo") -> OUString("foo")
193 handleChar(
194 expr, 0, qname, "rtl::OUString constructor",
195 TreatEmpty::DefaultCtor, true);
196 return true;
198 if (qname == "rtl::OUString::endsWithAsciiL" && fdecl->getNumParams() == 2)
200 // u.endsWithAsciiL("foo", 3) -> u.endsWith("foo"):
201 handleCharLen(
202 expr, 0, 1, qname, "rtl::OUString::endsWith", TreatEmpty::Error);
203 return true;
205 if (qname == "rtl::OUString::endsWithIgnoreAsciiCaseAsciiL"
206 && fdecl->getNumParams() == 2)
208 // u.endsWithIgnoreAsciiCaseAsciiL("foo", 3) ->
209 // u.endsWithIgnoreAsciiCase("foo"):
210 handleCharLen(
211 expr, 0, 1, qname, "rtl::OUString::endsWithIgnoreAsciiCase",
212 TreatEmpty::Error);
213 return true;
215 if (qname == "rtl::OUString::equalsAscii" && fdecl->getNumParams() == 1) {
216 // u.equalsAscii("foo") -> u == "foo":
217 handleChar(
218 expr, 0, qname, "operator ==", TreatEmpty::CheckEmpty, false);
219 return true;
221 if (qname == "rtl::OUString::equalsAsciiL" && fdecl->getNumParams() == 2) {
222 // u.equalsAsciiL("foo", 3) -> u == "foo":
223 handleCharLen(expr, 0, 1, qname, "operator ==", TreatEmpty::CheckEmpty);
224 return true;
226 if (qname == "rtl::OUString::equalsIgnoreAsciiCaseAscii"
227 && fdecl->getNumParams() == 1)
229 // u.equalsIgnoreAsciiCaseAscii("foo") ->
230 // u.equalsIngoreAsciiCase("foo"):
231 handleChar(
232 expr, 0, qname, "rtl::OUString::equalsIgnoreAsciiCase",
233 TreatEmpty::CheckEmpty, false);
234 return true;
236 if (qname == "rtl::OUString::equalsIgnoreAsciiCaseAsciiL"
237 && fdecl->getNumParams() == 2)
239 // u.equalsIgnoreAsciiCaseAsciiL("foo", 3) ->
240 // u.equalsIngoreAsciiCase("foo"):
241 handleCharLen(
242 expr, 0, 1, qname, "rtl::OUString::equalsIgnoreAsciiCase",
243 TreatEmpty::CheckEmpty);
244 return true;
246 if (qname == "rtl::OUString::indexOfAsciiL" && fdecl->getNumParams() == 3) {
247 assert(expr->getNumArgs() == 3);
248 // u.indexOfAsciiL("foo", 3, i) -> u.indexOf("foo", i):
249 handleCharLen(
250 expr, 0, 1, qname, "rtl::OUString::indexOf", TreatEmpty::Error);
251 return true;
253 if (qname == "rtl::OUString::lastIndexOfAsciiL"
254 && fdecl->getNumParams() == 2)
256 // u.lastIndexOfAsciiL("foo", 3) -> u.lastIndexOf("foo"):
257 handleCharLen(
258 expr, 0, 1, qname, "rtl::OUString::lastIndexOf", TreatEmpty::Error);
259 return true;
261 if (qname == "rtl::OUString::matchAsciiL" && fdecl->getNumParams() == 3) {
262 assert(expr->getNumArgs() == 3);
263 // u.matchAsciiL("foo", 3, i) -> u.match("foo", i):
264 handleCharLen(
265 expr, 0, 1, qname,
266 (isZero(expr->getArg(2))
267 ? std::string("rtl::OUString::startsWith")
268 : std::string("rtl::OUString::match")),
269 TreatEmpty::Error);
270 return true;
272 if (qname == "rtl::OUString::matchIgnoreAsciiCaseAsciiL"
273 && fdecl->getNumParams() == 3)
275 assert(expr->getNumArgs() == 3);
276 // u.matchIgnoreAsciiCaseAsciiL("foo", 3, i) ->
277 // u.matchIgnoreAsciiCase("foo", i):
278 handleCharLen(
279 expr, 0, 1, qname,
280 (isZero(expr->getArg(2))
281 ? std::string("rtl::OUString::startsWithIgnoreAsciiCase")
282 : std::string("rtl::OUString::matchIgnoreAsciiCase")),
283 TreatEmpty::Error);
284 return true;
286 if (qname == "rtl::OUString::reverseCompareToAsciiL"
287 && fdecl->getNumParams() == 2)
289 // u.reverseCompareToAsciiL("foo", 3) -> u.reverseCompareTo("foo"):
290 handleCharLen(
291 expr, 0, 1, qname, "rtl::OUString::reverseCompareTo",
292 TreatEmpty::Error);
293 return true;
295 if (qname == "rtl::OUString::equals" && fdecl->getNumParams() == 1) {
296 unsigned n;
297 bool non;
298 bool emb;
299 bool trm;
300 if (!isStringConstant(
301 expr->getArg(0)->IgnoreParenImpCasts(), &n, &non, &emb, &trm))
303 return true;
305 if (non) {
306 report(
307 DiagnosticsEngine::Warning,
308 ("call of " + qname
309 + (" with string constant argument containging non-ASCII"
310 " characters")),
311 expr->getExprLoc())
312 << expr->getSourceRange();
314 if (emb) {
315 report(
316 DiagnosticsEngine::Warning,
317 ("call of " + qname
318 + " with string constant argument containging embedded NULs"),
319 expr->getExprLoc())
320 << expr->getSourceRange();
322 if (n == 0) {
323 report(
324 DiagnosticsEngine::Warning,
325 ("rewrite call of " + qname
326 + (" with empty string constant argument as call of"
327 " rtl::OUString::isEmpty")),
328 expr->getExprLoc())
329 << expr->getSourceRange();
330 return true;
333 if (qname == "rtl::operator==" && fdecl->getNumParams() == 2) {
334 for (unsigned i = 0; i != 2; ++i) {
335 unsigned n;
336 bool non;
337 bool emb;
338 bool trm;
339 if (!isStringConstant(
340 expr->getArg(i)->IgnoreParenImpCasts(), &n, &non, &emb,
341 &trm))
343 continue;
345 if (non) {
346 report(
347 DiagnosticsEngine::Warning,
348 ("call of " + qname
349 + (" with string constant argument containging non-ASCII"
350 " characters")),
351 expr->getExprLoc())
352 << expr->getSourceRange();
354 if (emb) {
355 report(
356 DiagnosticsEngine::Warning,
357 ("call of " + qname
358 + (" with string constant argument containging embedded"
359 " NULs")),
360 expr->getExprLoc())
361 << expr->getSourceRange();
363 if (n == 0) {
364 report(
365 DiagnosticsEngine::Warning,
366 ("rewrite call of " + qname
367 + (" with empty string constant argument as call of"
368 " rtl::OUString::isEmpty")),
369 expr->getExprLoc())
370 << expr->getSourceRange();
373 return true;
375 if (qname == "rtl::operator!=" && fdecl->getNumParams() == 2) {
376 for (unsigned i = 0; i != 2; ++i) {
377 unsigned n;
378 bool non;
379 bool emb;
380 bool trm;
381 if (!isStringConstant(
382 expr->getArg(i)->IgnoreParenImpCasts(), &n, &non, &emb,
383 &trm))
385 continue;
387 if (non) {
388 report(
389 DiagnosticsEngine::Warning,
390 ("call of " + qname
391 + (" with string constant argument containging non-ASCII"
392 " characters")),
393 expr->getExprLoc())
394 << expr->getSourceRange();
396 if (emb) {
397 report(
398 DiagnosticsEngine::Warning,
399 ("call of " + qname
400 + (" with string constant argument containging embedded"
401 " NULs")),
402 expr->getExprLoc())
403 << expr->getSourceRange();
405 if (n == 0) {
406 report(
407 DiagnosticsEngine::Warning,
408 ("rewrite call of " + qname
409 + (" with empty string constant argument as call of"
410 " !rtl::OUString::isEmpty")),
411 expr->getExprLoc())
412 << expr->getSourceRange();
415 return true;
417 if (qname == "rtl::OUString::operator=" && fdecl->getNumParams() == 1) {
418 unsigned n;
419 bool non;
420 bool emb;
421 bool trm;
422 if (!isStringConstant(
423 expr->getArg(1)->IgnoreParenImpCasts(), &n, &non, &emb, &trm))
425 return true;
427 if (non) {
428 report(
429 DiagnosticsEngine::Warning,
430 ("call of " + qname
431 + (" with string constant argument containging non-ASCII"
432 " characters")),
433 expr->getExprLoc())
434 << expr->getSourceRange();
436 if (emb) {
437 report(
438 DiagnosticsEngine::Warning,
439 ("call of " + qname
440 + " with string constant argument containging embedded NULs"),
441 expr->getExprLoc())
442 << expr->getSourceRange();
444 if (n == 0) {
445 report(
446 DiagnosticsEngine::Warning,
447 ("rewrite call of " + qname
448 + (" with empty string constant argument as call of"
449 " rtl::OUString::clear")),
450 expr->getExprLoc())
451 << expr->getSourceRange();
452 return true;
455 return true;
458 bool StringConstant::VisitCXXConstructExpr(CXXConstructExpr const * expr) {
459 if (ignoreLocation(expr)) {
460 return true;
462 std::string qname(
463 expr->getConstructor()->getParent()->getQualifiedNameAsString());
464 if (qname == "rtl::OUString") {
465 ChangeKind kind;
466 PassThrough pass;
467 switch (expr->getConstructor()->getNumParams()) {
468 case 1:
470 APSInt v;
471 if (!expr->getArg(0)->isIntegerConstantExpr(
472 v, compiler.getASTContext()))
474 return true;
476 if (v == 0 || v.uge(0x80)) {
477 return true;
479 kind = ChangeKind::SingleChar;
480 pass = PassThrough::NonEmptyConstantString;
481 break;
483 case 2:
485 unsigned n;
486 bool non;
487 bool emb;
488 bool trm;
489 if (!isStringConstant(
490 expr->getArg(0)->IgnoreParenImpCasts(), &n, &non, &emb,
491 &trm))
493 return true;
495 if (non) {
496 report(
497 DiagnosticsEngine::Warning,
498 ("construction of " + qname
499 + (" with string constant argument containging"
500 " non-ASCII characters")),
501 expr->getExprLoc())
502 << expr->getSourceRange();
504 if (emb) {
505 report(
506 DiagnosticsEngine::Warning,
507 ("construction of " + qname
508 + (" with string constant argument containging"
509 " embedded NULs")),
510 expr->getExprLoc())
511 << expr->getSourceRange();
513 kind = ChangeKind::Char;
514 pass = n == 0
515 ? PassThrough::EmptyConstantString
516 : PassThrough::NonEmptyConstantString;
517 break;
519 default:
520 return true;
522 if (!calls_.empty()) {
523 Expr const * call = calls_.top();
524 CallExpr::const_arg_iterator argsBeg;
525 CallExpr::const_arg_iterator argsEnd;
526 if (isa<CallExpr>(call)) {
527 argsBeg = cast<CallExpr>(call)->arg_begin();
528 argsEnd = cast<CallExpr>(call)->arg_end();
529 } else if (isa<CXXConstructExpr>(call)) {
530 argsBeg = cast<CXXConstructExpr>(call)->arg_begin();
531 argsEnd = cast<CXXConstructExpr>(call)->arg_end();
532 } else {
533 assert(false);
535 for (auto i(argsBeg); i != argsEnd; ++i) {
536 Expr const * e = (*i)->IgnoreParenImpCasts();
537 if (isa<MaterializeTemporaryExpr>(e)) {
538 e = cast<MaterializeTemporaryExpr>(e)->GetTemporaryExpr()
539 ->IgnoreParenImpCasts();
541 if (isa<CXXFunctionalCastExpr>(e)) {
542 e = cast<CXXFunctionalCastExpr>(e)->getSubExpr()
543 ->IgnoreParenImpCasts();
545 if (isa<CXXBindTemporaryExpr>(e)) {
546 e = cast<CXXBindTemporaryExpr>(e)->getSubExpr()
547 ->IgnoreParenImpCasts();
549 if (e == expr) {
550 if (isa<CallExpr>(call)) {
551 FunctionDecl const * fdecl
552 = cast<CallExpr>(call)->getDirectCallee();
553 if (fdecl == nullptr) {
554 break;
556 std::string callQname(
557 fdecl->getQualifiedNameAsString());
558 if (pass == PassThrough::EmptyConstantString) {
559 if (callQname == "rtl::OUString::equals"
560 || callQname == "rtl::operator==")
562 report(
563 DiagnosticsEngine::Warning,
564 ("rewrite call of " + callQname
565 + " with construction of " + qname
566 + (" with empty string constant argument"
567 " as call of rtl::OUString::isEmpty")),
568 getMemberLocation(call))
569 << call->getSourceRange();
570 return true;
572 if (callQname == "rtl::operator!=") {
573 report(
574 DiagnosticsEngine::Warning,
575 ("rewrite call of " + callQname
576 + " with construction of " + qname
577 + (" with empty string constant argument"
578 " as call of !rtl::OUString::isEmpty")),
579 getMemberLocation(call))
580 << call->getSourceRange();
581 return true;
583 if (callQname == "rtl::operator+"
584 || callQname == "rtl::OUString::operator+=")
586 report(
587 DiagnosticsEngine::Warning,
588 ("call of " + callQname
589 + " with suspicous construction of "
590 + qname
591 + " with empty string constant argument"),
592 getMemberLocation(call))
593 << call->getSourceRange();
594 return true;
596 if (callQname == "rtl::OUString::operator=") {
597 report(
598 DiagnosticsEngine::Warning,
599 ("rewrite call of " + callQname
600 + " with construction of " + qname
601 + (" with empty string constant argument"
602 " as call of rtl::OUString::clear")),
603 getMemberLocation(call))
604 << call->getSourceRange();
605 return true;
607 } else {
608 assert(pass == PassThrough::NonEmptyConstantString);
609 if (callQname == "rtl::OUString::equals") {
610 report(
611 DiagnosticsEngine::Warning,
612 ("rewrite call of " + callQname
613 + " with construction of " + qname
614 + " with " + describeChangeKind(kind)
615 + " as operator =="),
616 getMemberLocation(call))
617 << call->getSourceRange();
618 return true;
620 if (callQname == "rtl::operator+"
621 || callQname == "rtl::OUString::operator="
622 || callQname == "rtl::operator=="
623 || callQname == "rtl::operator!=")
625 if (callQname == "rtl::operator+") {
626 std::string file(
627 compiler.getSourceManager().getFilename(
628 compiler.getSourceManager()
629 .getSpellingLoc(
630 expr->getLocStart())));
631 if (file
632 == (SRCDIR
633 "/sal/qa/rtl/strings/test_ostring_concat.cxx")
634 || (file
635 == (SRCDIR
636 "/sal/qa/rtl/strings/test_oustring_concat.cxx")))
638 return true;
641 report(
642 DiagnosticsEngine::Warning,
643 ("elide construction of " + qname + " with "
644 + describeChangeKind(kind) + " in call of "
645 + callQname),
646 getMemberLocation(expr))
647 << expr->getSourceRange();
648 return true;
651 return true;
652 } else if (isa<CXXConstructExpr>(call)) {
653 } else {
654 assert(false);
659 return true;
661 return true;
664 std::string StringConstant::describeChangeKind(ChangeKind kind) {
665 switch (kind) {
666 case ChangeKind::Char:
667 return "string constant argument";
668 case ChangeKind::CharLen:
669 return "string constant and matching length arguments";
670 case ChangeKind::SingleChar:
671 return "ASCII sal_Unicode argument";
675 bool StringConstant::isStringConstant(
676 Expr const * expr, unsigned * size, bool * nonAscii, bool * embeddedNuls,
677 bool * terminatingNul)
679 assert(expr != nullptr);
680 assert(size != nullptr);
681 assert(nonAscii != nullptr);
682 assert(embeddedNuls != nullptr);
683 assert(terminatingNul != nullptr);
684 QualType t = expr->getType();
685 if (!(t->isConstantArrayType() && t.isConstQualified()
686 && isPlainChar(t->getAsArrayTypeUnsafe()->getElementType())))
688 return false;
690 StringLiteral const * lit = dyn_cast<StringLiteral>(expr);
691 if (lit != nullptr) {
692 if (!lit->isAscii()) {
693 return false;
695 unsigned n = lit->getLength();
696 bool non = false;
697 bool emb = false;
698 StringRef str = lit->getString();
699 for (unsigned i = 0; i != n; ++i) {
700 if (str[i] == '\0') {
701 emb = true;
702 } else if (static_cast<unsigned char>(str[i]) >= 0x80) {
703 non = true;
706 *size = n;
707 *nonAscii = non;
708 *embeddedNuls = emb;
709 *terminatingNul = true;
710 return true;
712 APValue v;
713 if (!expr->isCXX11ConstantExpr(compiler.getASTContext(), &v)) {
714 return false;
716 switch (v.getKind()) {
717 case APValue::LValue:
719 Expr const * e = v.getLValueBase().dyn_cast<Expr const *>();
720 assert(e != nullptr); //TODO???
721 if (!v.getLValueOffset().isZero()) {
722 return false; //TODO
724 Expr const * e2 = e->IgnoreParenImpCasts();
725 if (e2 != e) {
726 return isStringConstant(
727 e2, size, nonAscii, embeddedNuls, terminatingNul);
729 //TODO: string literals are represented as recursive LValues???
730 llvm::APInt n
731 = compiler.getASTContext().getAsConstantArrayType(t)->getSize();
732 assert(n != 0);
733 --n;
734 assert(n.ule(std::numeric_limits<unsigned>::max()));
735 *size = static_cast<unsigned>(n.getLimitedValue());
736 *nonAscii = false; //TODO
737 *embeddedNuls = false; //TODO
738 *terminatingNul = true;
739 return true;
741 case APValue::Array:
743 if (v.hasArrayFiller()) { //TODO: handle final NUL filler?
744 return false;
746 unsigned n = v.getArraySize();
747 assert(n != 0);
748 bool non = false;
749 bool emb = false;
750 for (unsigned i = 0; i != n - 1; ++i) {
751 APValue e(v.getArrayInitializedElt(i));
752 if (!e.isInt()) { //TODO: assert?
753 return false;
755 APSInt iv = e.getInt();
756 if (iv == 0) {
757 emb = true;
758 } else if (iv.uge(0x80)) {
759 non = true;
762 APValue e(v.getArrayInitializedElt(n - 1));
763 if (!e.isInt()) { //TODO: assert?
764 return false;
766 bool trm = e.getInt() == 0;
767 *size = trm ? n - 1 : n;
768 *nonAscii = non;
769 *embeddedNuls = emb;
770 *terminatingNul = trm;
771 return true;
773 default:
774 assert(false); //TODO???
775 return false;
779 bool StringConstant::isZero(Expr const * expr) {
780 APSInt res;
781 return expr->isIntegerConstantExpr(res, compiler.getASTContext())
782 && res == 0;
785 void StringConstant::reportChange(
786 Expr const * expr, ChangeKind kind, std::string const & original,
787 std::string const & replacement, PassThrough pass)
789 if (pass != PassThrough::No && !calls_.empty()) {
790 Expr const * call = calls_.top();
791 CallExpr::const_arg_iterator argsBeg;
792 CallExpr::const_arg_iterator argsEnd;
793 if (isa<CallExpr>(call)) {
794 argsBeg = cast<CallExpr>(call)->arg_begin();
795 argsEnd = cast<CallExpr>(call)->arg_end();
796 } else if (isa<CXXConstructExpr>(call)) {
797 argsBeg = cast<CXXConstructExpr>(call)->arg_begin();
798 argsEnd = cast<CXXConstructExpr>(call)->arg_end();
799 } else {
800 assert(false);
802 for (auto i(argsBeg); i != argsEnd; ++i) {
803 Expr const * e = (*i)->IgnoreParenImpCasts();
804 if (isa<CXXBindTemporaryExpr>(e)) {
805 e = cast<CXXBindTemporaryExpr>(e)->getSubExpr()
806 ->IgnoreParenImpCasts();
808 if (e == expr) {
809 if (isa<CallExpr>(call)) {
810 FunctionDecl const * fdecl
811 = cast<CallExpr>(call)->getDirectCallee();
812 if (fdecl == nullptr) {
813 break;
815 std::string qname(fdecl->getQualifiedNameAsString());
816 if (pass == PassThrough::EmptyConstantString) {
817 if (qname == "rtl::OUString::equals"
818 || qname == "rtl::operator==")
820 report(
821 DiagnosticsEngine::Warning,
822 ("rewrite call of " + qname + " with call of "
823 + original
824 + (" with empty string constant argument as"
825 " call of rtl::OUString::isEmpty")),
826 getMemberLocation(call))
827 << call->getSourceRange();
828 return;
830 if (qname == "rtl::operator!=") {
831 report(
832 DiagnosticsEngine::Warning,
833 ("rewrite call of " + qname + " with call of "
834 + original
835 + (" with empty string constant argument as"
836 " call of !rtl::OUString::isEmpty")),
837 getMemberLocation(call))
838 << call->getSourceRange();
839 return;
841 if (qname == "rtl::operator+"
842 || qname == "rtl::OUString::operator+=")
844 report(
845 DiagnosticsEngine::Warning,
846 ("call of " + qname + " with suspicous call of "
847 + original
848 + " with empty string constant argument"),
849 getMemberLocation(call))
850 << call->getSourceRange();
851 return;
853 if (qname == "rtl::OUString::operator=") {
854 report(
855 DiagnosticsEngine::Warning,
856 ("rewrite call of " + qname + " with call of "
857 + original
858 + (" with empty string constant argument as"
859 " call of rtl::OUString::call")),
860 getMemberLocation(call))
861 << call->getSourceRange();
862 return;
864 } else {
865 assert(pass == PassThrough::NonEmptyConstantString);
866 if (qname == "rtl::OUString::equals"
867 || qname == "rtl::OUString::operator="
868 || qname == "rtl::operator=="
869 || qname == "rtl::operator!=")
871 report(
872 DiagnosticsEngine::Warning,
873 ("elide call of " + original + " with "
874 + describeChangeKind(kind) + " in call of "
875 + qname),
876 getMemberLocation(expr))
877 << expr->getSourceRange();
878 return;
880 if (qname == "rtl::operator+"
881 || qname == "rtl::OUString::operator+=")
883 report(
884 DiagnosticsEngine::Warning,
885 ("rewrite call of " + original + " with "
886 + describeChangeKind(kind) + " in call of "
887 + qname
888 + (" as (implicit) construction of"
889 " rtl::OUString")),
890 getMemberLocation(expr))
891 << expr->getSourceRange();
892 return;
895 report(
896 DiagnosticsEngine::Warning,
897 "TODO call inside " + qname, getMemberLocation(expr))
898 << expr->getSourceRange();
899 return;
900 } else if (isa<CXXConstructExpr>(call)) {
901 std::string qname(
902 cast<CXXConstructExpr>(call)->getConstructor()
903 ->getParent()->getQualifiedNameAsString());
904 if (qname == "rtl::OUString"
905 || qname == "rtl::OUStringBuffer")
907 //TODO: propagate further out?
908 if (pass == PassThrough::EmptyConstantString) {
909 report(
910 DiagnosticsEngine::Warning,
911 ("rewrite construction of " + qname
912 + " with call of " + original
913 + (" with empty string constant argument as"
914 " default construction of ")
915 + qname),
916 getMemberLocation(call))
917 << call->getSourceRange();
918 } else {
919 assert(pass == PassThrough::NonEmptyConstantString);
920 report(
921 DiagnosticsEngine::Warning,
922 ("elide call of " + original + " with "
923 + describeChangeKind(kind)
924 + " in construction of " + qname),
925 getMemberLocation(expr))
926 << expr->getSourceRange();
928 return;
930 } else {
931 assert(false);
936 report(
937 DiagnosticsEngine::Warning,
938 ("rewrite call of " + original + " with " + describeChangeKind(kind)
939 + " as call of " + replacement),
940 getMemberLocation(expr))
941 << expr->getSourceRange();
944 void StringConstant::checkEmpty(
945 CallExpr const * expr, std::string const & qname, TreatEmpty treatEmpty,
946 unsigned size, std::string * replacement)
948 assert(replacement != nullptr);
949 if (size == 0) {
950 switch (treatEmpty) {
951 case TreatEmpty::DefaultCtor:
952 *replacement = "rtl::OUString default constructor";
953 break;
954 case TreatEmpty::CheckEmpty:
955 *replacement = "rtl::OUString::isEmpty";
956 break;
957 case TreatEmpty::Error:
958 report(
959 DiagnosticsEngine::Warning,
960 ("call of " + qname
961 + " with suspicous empty string constant argument"),
962 getMemberLocation(expr))
963 << expr->getSourceRange();
964 break;
969 void StringConstant::handleChar(
970 CallExpr const * expr, unsigned arg, std::string const & qname,
971 std::string const & replacement, TreatEmpty treatEmpty, bool literal)
973 unsigned n;
974 bool non;
975 bool emb;
976 bool trm;
977 if (!isStringConstant(
978 expr->getArg(arg)->IgnoreParenImpCasts(), &n, &non, &emb, &trm))
980 return;
982 if (non) {
983 report(
984 DiagnosticsEngine::Warning,
985 ("call of " + qname
986 + (" with string constant argument containging non-ASCII"
987 " characters")),
988 getMemberLocation(expr))
989 << expr->getSourceRange();
990 return;
992 if (emb) {
993 report(
994 DiagnosticsEngine::Warning,
995 ("call of " + qname
996 + " with string constant argument containging embedded NULs"),
997 getMemberLocation(expr))
998 << expr->getSourceRange();
999 return;
1001 if (!trm) {
1002 report(
1003 DiagnosticsEngine::Warning,
1004 ("call of " + qname
1005 + " with string constant argument lacking a terminating NUL"),
1006 getMemberLocation(expr))
1007 << expr->getSourceRange();
1008 return;
1010 std::string repl(replacement);
1011 checkEmpty(expr, qname, treatEmpty, n, &repl);
1012 reportChange(
1013 expr, ChangeKind::Char, qname, repl,
1014 (literal
1015 ? (n == 0
1016 ? PassThrough::EmptyConstantString
1017 : PassThrough::NonEmptyConstantString)
1018 : PassThrough::No));
1021 void StringConstant::handleCharLen(
1022 CallExpr const * expr, unsigned arg1, unsigned arg2,
1023 std::string const & qname, std::string const & replacement,
1024 TreatEmpty treatEmpty)
1026 // Especially for f(RTL_CONSTASCII_STRINGPARAM("foo")), where
1027 // RTL_CONSTASCII_STRINGPARAM expands to complicated expressions involving
1028 // (&(X)[0] sub-expressions (and it might or might not be better to handle
1029 // that at the level of non-expanded macros instead, but I have not found
1030 // out how to do that yet anyway):
1031 unsigned n;
1032 bool non;
1033 bool emb;
1034 bool trm;
1035 if (!(isStringConstant(
1036 expr->getArg(arg1)->IgnoreParenImpCasts(), &n, &non, &emb, &trm)
1037 && trm))
1039 return;
1041 APSInt res;
1042 if (expr->getArg(arg2)->isIntegerConstantExpr(
1043 res, compiler.getASTContext()))
1045 if (res != n) {
1046 return;
1048 } else {
1049 UnaryOperator const * op = dyn_cast<UnaryOperator>(
1050 expr->getArg(arg1)->IgnoreParenImpCasts());
1051 if (op == nullptr || op->getOpcode() != UO_AddrOf) {
1052 return;
1054 ArraySubscriptExpr const * subs = dyn_cast<ArraySubscriptExpr>(
1055 op->getSubExpr()->IgnoreParenImpCasts());
1056 if (subs == nullptr) {
1057 return;
1059 unsigned n2;
1060 bool non2;
1061 bool emb2;
1062 bool trm2;
1063 if (!(isStringConstant(
1064 subs->getBase()->IgnoreParenImpCasts(), &n2, &non2, &emb2,
1065 &trm2)
1066 && n2 == n && non2 == non && emb2 == emb && trm2 == trm
1067 //TODO: same strings
1068 && subs->getIdx()->isIntegerConstantExpr(
1069 res, compiler.getASTContext())
1070 && res == 0))
1072 return;
1075 if (non) {
1076 report(
1077 DiagnosticsEngine::Warning,
1078 ("call of " + qname
1079 + (" with string constant argument containging non-ASCII"
1080 " characters")),
1081 getMemberLocation(expr))
1082 << expr->getSourceRange();
1084 if (emb) {
1085 return;
1087 std::string repl(replacement);
1088 checkEmpty(expr, qname, treatEmpty, n, &repl);
1089 reportChange(expr, ChangeKind::CharLen, qname, repl, PassThrough::No);
1092 loplugin::Plugin::Registration< StringConstant > X("stringconstant");
1096 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */