1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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 */
9
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>
21
22 #include "check.hxx"
23 #include "compat.hxx"
24 #include "plugin.hxx"
25
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
32 //
33 // constexpr char str[] = "bar";
34 //
35 // This plugin flags uses of OUString functions with string constant arguments
36 // that can be rewritten more directly, like
37 //
38 // OUString::createFromAscii("foo") -> "foo"
39 //
40 // and
41 //
42 // s.equals(OUString("bar")) -> s == "bar"
43
44 namespace {
45
getMemberLocation(Expr const * expr)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();
51 }
52
isLhsOfAssignment(FunctionDecl const * decl,unsigned parameter)53 bool isLhsOfAssignment(FunctionDecl const * decl, unsigned parameter) {
54 if (parameter != 0) {
55 return false;
56 }
57 auto oo = decl->getOverloadedOperator();
58 return oo == OO_Equal
59 || (oo >= OO_PlusEqual && oo <= OO_GreaterGreaterEqual);
60 }
61
hasOverloads(FunctionDecl const * decl,unsigned arguments)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();
67 }
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)
73 {
74 auto consDecl = dyn_cast<CXXConstructorDecl>(f);
75 if (consDecl && consDecl->isCopyOrMoveConstructor()) {
76 continue;
77 }
78 ++n;
79 if (n == 2) {
80 return true;
81 }
82 }
83 }
84 return false;
85 }
86
lookForCXXConstructExpr(Expr const * expr)87 CXXConstructExpr const * lookForCXXConstructExpr(Expr const * expr) {
88 if (auto e = dyn_cast<MaterializeTemporaryExpr>(expr)) {
89 expr = compat::getSubExpr(e);
90 }
91 if (auto e = dyn_cast<CXXFunctionalCastExpr>(expr)) {
92 expr = e->getSubExpr();
93 }
94 if (auto e = dyn_cast<CXXBindTemporaryExpr>(expr)) {
95 expr = e->getSubExpr();
96 }
97 return dyn_cast<CXXConstructExpr>(expr);
98 }
99
adviseNonArray(bool nonArray)100 char const * adviseNonArray(bool nonArray) {
101 return nonArray
102 ? ", and turn the non-array string constant into an array" : "";
103 }
104
105 class StringConstant:
106 public loplugin::FilteringRewritePlugin<StringConstant>
107 {
108 public:
StringConstant(loplugin::InstantiationData const & data)109 explicit StringConstant(loplugin::InstantiationData const & data):
110 FilteringRewritePlugin(data) {}
111
112 void run() override;
113
TraverseFunctionDecl(FunctionDecl * decl)114 bool TraverseFunctionDecl(FunctionDecl * decl) {
115 returnTypes_.push(compat::getDeclaredReturnType(decl));
116 auto const ret = RecursiveASTVisitor::TraverseFunctionDecl(decl);
117 assert(!returnTypes_.empty());
118 assert(returnTypes_.top() == compat::getDeclaredReturnType(decl));
119 returnTypes_.pop();
120 return ret;
121 }
122
TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl * decl)123 bool TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl * decl) {
124 returnTypes_.push(compat::getDeclaredReturnType(decl));
125 auto const ret = RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(
126 decl);
127 assert(!returnTypes_.empty());
128 assert(returnTypes_.top() == compat::getDeclaredReturnType(decl));
129 returnTypes_.pop();
130 return ret;
131 }
132
TraverseCXXMethodDecl(CXXMethodDecl * decl)133 bool TraverseCXXMethodDecl(CXXMethodDecl * decl) {
134 returnTypes_.push(compat::getDeclaredReturnType(decl));
135 auto const ret = RecursiveASTVisitor::TraverseCXXMethodDecl(decl);
136 assert(!returnTypes_.empty());
137 assert(returnTypes_.top() == compat::getDeclaredReturnType(decl));
138 returnTypes_.pop();
139 return ret;
140 }
141
TraverseCXXConstructorDecl(CXXConstructorDecl * decl)142 bool TraverseCXXConstructorDecl(CXXConstructorDecl * decl) {
143 returnTypes_.push(compat::getDeclaredReturnType(decl));
144 auto const ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(decl);
145 assert(!returnTypes_.empty());
146 assert(returnTypes_.top() == compat::getDeclaredReturnType(decl));
147 returnTypes_.pop();
148 return ret;
149 }
150
TraverseCXXDestructorDecl(CXXDestructorDecl * decl)151 bool TraverseCXXDestructorDecl(CXXDestructorDecl * decl) {
152 returnTypes_.push(compat::getDeclaredReturnType(decl));
153 auto const ret = RecursiveASTVisitor::TraverseCXXDestructorDecl(decl);
154 assert(!returnTypes_.empty());
155 assert(returnTypes_.top() == compat::getDeclaredReturnType(decl));
156 returnTypes_.pop();
157 return ret;
158 }
159
TraverseCXXConversionDecl(CXXConversionDecl * decl)160 bool TraverseCXXConversionDecl(CXXConversionDecl * decl) {
161 returnTypes_.push(compat::getDeclaredReturnType(decl));
162 auto const ret = RecursiveASTVisitor::TraverseCXXConversionDecl(decl);
163 assert(!returnTypes_.empty());
164 assert(returnTypes_.top() == compat::getDeclaredReturnType(decl));
165 returnTypes_.pop();
166 return ret;
167 }
168
TraverseObjCMethodDecl(ObjCMethodDecl * decl)169 bool TraverseObjCMethodDecl(ObjCMethodDecl * decl) {
170 returnTypes_.push(decl->getReturnType());
171 auto const ret = RecursiveASTVisitor::TraverseObjCMethodDecl(decl);
172 assert(!returnTypes_.empty());
173 assert(returnTypes_.top() == decl->getReturnType());
174 returnTypes_.pop();
175 return ret;
176 }
177
178 bool TraverseCallExpr(CallExpr * expr);
179
180 bool TraverseCXXMemberCallExpr(CXXMemberCallExpr * expr);
181
182 bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr * expr);
183
184 bool TraverseCXXConstructExpr(CXXConstructExpr * expr);
185
186 bool VisitCallExpr(CallExpr const * expr);
187
188 bool VisitCXXConstructExpr(CXXConstructExpr const * expr);
189
190 bool VisitReturnStmt(ReturnStmt const * stmt);
191
192 private:
193 enum class ContentKind { Ascii, Utf8, Arbitrary };
194
195 enum class TreatEmpty { DefaultCtor, CheckEmpty, Error };
196
197 enum class ChangeKind { Char, CharLen, SingleChar, OUStringChar };
198
199 enum class PassThrough { No, EmptyConstantString, NonEmptyConstantString };
200
201 std::string describeChangeKind(ChangeKind kind);
202
203 bool isStringConstant(
204 Expr const * expr, unsigned * size, bool * nonArray,
205 ContentKind * content, bool * embeddedNuls, bool * terminatingNul,
206 std::vector<char32_t> * utf8Content = nullptr);
207
208 bool isZero(Expr const * expr);
209
210 void reportChange(
211 Expr const * expr, ChangeKind kind, std::string const & original,
212 std::string const & replacement, PassThrough pass, bool nonArray,
213 char const * rewriteFrom, char const * rewriteTo);
214
215 void checkEmpty(
216 CallExpr const * expr, FunctionDecl const * callee,
217 TreatEmpty treatEmpty, unsigned size, std::string * replacement);
218
219 void handleChar(
220 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
221 std::string const & replacement, TreatEmpty treatEmpty, bool literal,
222 char const * rewriteFrom = nullptr, char const * rewriteTo = nullptr);
223
224 void handleCharLen(
225 CallExpr const * expr, unsigned arg1, unsigned arg2,
226 FunctionDecl const * callee, std::string const & replacement,
227 TreatEmpty treatEmpty);
228
229 void handleOUStringCtor(
230 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
231 bool explicitFunctionalCastNotation);
232
233 void handleOStringCtor(
234 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
235 bool explicitFunctionalCastNotation);
236
237 void handleOUStringCtor(
238 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
239 bool explicitFunctionalCastNotation);
240
241 void handleOStringCtor(
242 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
243 bool explicitFunctionalCastNotation);
244
245 enum class StringKind { Unicode, Char };
246 void handleStringCtor(
247 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
248 bool explicitFunctionalCastNotation, StringKind stringKind);
249
250 void handleFunArgOstring(
251 CallExpr const * expr, unsigned arg, FunctionDecl const * callee);
252
253 std::stack<QualType> returnTypes_;
254 std::stack<Expr const *> calls_;
255 };
256
run()257 void StringConstant::run() {
258 if (compiler.getLangOpts().CPlusPlus
259 && compiler.getPreprocessor().getIdentifierInfo(
260 "LIBO_INTERNAL_ONLY")->hasMacroDefinition())
261 //TODO: some parts of it are useful for external code, too
262 {
263 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
264 }
265 }
266
TraverseCallExpr(CallExpr * expr)267 bool StringConstant::TraverseCallExpr(CallExpr * expr) {
268 if (!WalkUpFromCallExpr(expr)) {
269 return false;
270 }
271 calls_.push(expr);
272 bool bRes = true;
273 for (auto * e: expr->children()) {
274 if (!TraverseStmt(e)) {
275 bRes = false;
276 break;
277 }
278 }
279 calls_.pop();
280 return bRes;
281 }
282
TraverseCXXMemberCallExpr(CXXMemberCallExpr * expr)283 bool StringConstant::TraverseCXXMemberCallExpr(CXXMemberCallExpr * expr) {
284 if (!WalkUpFromCXXMemberCallExpr(expr)) {
285 return false;
286 }
287 calls_.push(expr);
288 bool bRes = true;
289 for (auto * e: expr->children()) {
290 if (!TraverseStmt(e)) {
291 bRes = false;
292 break;
293 }
294 }
295 calls_.pop();
296 return bRes;
297 }
298
TraverseCXXOperatorCallExpr(CXXOperatorCallExpr * expr)299 bool StringConstant::TraverseCXXOperatorCallExpr(CXXOperatorCallExpr * expr)
300 {
301 if (!WalkUpFromCXXOperatorCallExpr(expr)) {
302 return false;
303 }
304 calls_.push(expr);
305 bool bRes = true;
306 for (auto * e: expr->children()) {
307 if (!TraverseStmt(e)) {
308 bRes = false;
309 break;
310 }
311 }
312 calls_.pop();
313 return bRes;
314 }
315
TraverseCXXConstructExpr(CXXConstructExpr * expr)316 bool StringConstant::TraverseCXXConstructExpr(CXXConstructExpr * expr) {
317 if (!WalkUpFromCXXConstructExpr(expr)) {
318 return false;
319 }
320 calls_.push(expr);
321 bool bRes = true;
322 for (auto * e: expr->children()) {
323 if (!TraverseStmt(e)) {
324 bRes = false;
325 break;
326 }
327 }
328 calls_.pop();
329 return bRes;
330 }
331
VisitCallExpr(CallExpr const * expr)332 bool StringConstant::VisitCallExpr(CallExpr const * expr) {
333 if (ignoreLocation(expr)) {
334 return true;
335 }
336 FunctionDecl const * fdecl = expr->getDirectCallee();
337 if (fdecl == nullptr) {
338 return true;
339 }
340 for (unsigned i = 0; i != fdecl->getNumParams(); ++i) {
341 auto t = fdecl->getParamDecl(i)->getType();
342 if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
343 .LvalueReference().Const().NotSubstTemplateTypeParmType()
344 .Class("OUString").Namespace("rtl").GlobalNamespace())
345 {
346 if (!(isLhsOfAssignment(fdecl, i)
347 || hasOverloads(fdecl, expr->getNumArgs())))
348 {
349 handleOUStringCtor(expr, i, fdecl, true);
350 }
351 }
352 if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
353 .LvalueReference().Const().NotSubstTemplateTypeParmType()
354 .Class("OString").Namespace("rtl").GlobalNamespace())
355 {
356 if (!(isLhsOfAssignment(fdecl, i)
357 || hasOverloads(fdecl, expr->getNumArgs())))
358 {
359 handleOStringCtor(expr, i, fdecl, true);
360 }
361 }
362 }
363 loplugin::DeclCheck dc(fdecl);
364 //TODO: u.compareToAscii("foo") -> u.???("foo")
365 //TODO: u.compareToIgnoreAsciiCaseAscii("foo") -> u.???("foo")
366 if ((dc.Function("createFromAscii").Class("OUString").Namespace("rtl")
367 .GlobalNamespace())
368 && fdecl->getNumParams() == 1)
369 {
370 // OUString::createFromAscii("foo") -> OUString("foo")
371 handleChar(
372 expr, 0, fdecl, "rtl::OUString constructor",
373 TreatEmpty::DefaultCtor, true);
374 return true;
375 }
376 if ((dc.Function("endsWithAsciiL").Class("OUString").Namespace("rtl")
377 .GlobalNamespace())
378 && fdecl->getNumParams() == 2)
379 {
380 // u.endsWithAsciiL("foo", 3) -> u.endsWith("foo"):
381 handleCharLen(
382 expr, 0, 1, fdecl, "rtl::OUString::endsWith", TreatEmpty::Error);
383 return true;
384 }
385 if ((dc.Function("endsWithIgnoreAsciiCaseAsciiL").Class("OUString")
386 .Namespace("rtl").GlobalNamespace())
387 && fdecl->getNumParams() == 2)
388 {
389 // u.endsWithIgnoreAsciiCaseAsciiL("foo", 3) ->
390 // u.endsWithIgnoreAsciiCase("foo"):
391 handleCharLen(
392 expr, 0, 1, fdecl, "rtl::OUString::endsWithIgnoreAsciiCase",
393 TreatEmpty::Error);
394 return true;
395 }
396 if ((dc.Function("equalsAscii").Class("OUString").Namespace("rtl")
397 .GlobalNamespace())
398 && fdecl->getNumParams() == 1)
399 {
400 // u.equalsAscii("foo") -> u == "foo":
401 handleChar(
402 expr, 0, fdecl, "operator ==", TreatEmpty::CheckEmpty, false);
403 return true;
404 }
405 if ((dc.Function("equalsAsciiL").Class("OUString").Namespace("rtl")
406 .GlobalNamespace())
407 && fdecl->getNumParams() == 2)
408 {
409 // u.equalsAsciiL("foo", 3) -> u == "foo":
410 handleCharLen(expr, 0, 1, fdecl, "operator ==", TreatEmpty::CheckEmpty);
411 return true;
412 }
413 if ((dc.Function("equalsIgnoreAsciiCaseAscii").Class("OUString")
414 .Namespace("rtl").GlobalNamespace())
415 && fdecl->getNumParams() == 1)
416 {
417 // u.equalsIgnoreAsciiCaseAscii("foo") ->
418 // u.equalsIngoreAsciiCase("foo"):
419
420 auto file = getFilenameOfLocation(
421 compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr)));
422 if (loplugin::isSamePathname(
423 file, SRCDIR "/sal/qa/rtl/strings/test_oustring_compare.cxx"))
424 {
425 return true;
426 }
427 handleChar(
428 expr, 0, fdecl, "rtl::OUString::equalsIgnoreAsciiCase",
429 TreatEmpty::CheckEmpty, false);
430 return true;
431 }
432 if ((dc.Function("equalsIgnoreAsciiCaseAsciiL").Class("OUString")
433 .Namespace("rtl").GlobalNamespace())
434 && fdecl->getNumParams() == 2)
435 {
436 // u.equalsIgnoreAsciiCaseAsciiL("foo", 3) ->
437 // u.equalsIngoreAsciiCase("foo"):
438 auto file = getFilenameOfLocation(
439 compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr)));
440 if (loplugin::isSamePathname(
441 file, SRCDIR "/sal/qa/rtl/strings/test_oustring_compare.cxx"))
442 {
443 return true;
444 }
445 handleCharLen(
446 expr, 0, 1, fdecl, "rtl::OUString::equalsIgnoreAsciiCase",
447 TreatEmpty::CheckEmpty);
448 return true;
449 }
450 if ((dc.Function("indexOfAsciiL").Class("OUString").Namespace("rtl")
451 .GlobalNamespace())
452 && fdecl->getNumParams() == 3)
453 {
454 assert(expr->getNumArgs() == 3);
455 // u.indexOfAsciiL("foo", 3, i) -> u.indexOf("foo", i):
456 handleCharLen(
457 expr, 0, 1, fdecl, "rtl::OUString::indexOf", TreatEmpty::Error);
458 return true;
459 }
460 if ((dc.Function("lastIndexOfAsciiL").Class("OUString").Namespace("rtl")
461 .GlobalNamespace())
462 && fdecl->getNumParams() == 2)
463 {
464 // u.lastIndexOfAsciiL("foo", 3) -> u.lastIndexOf("foo"):
465 handleCharLen(
466 expr, 0, 1, fdecl, "rtl::OUString::lastIndexOf", TreatEmpty::Error);
467 return true;
468 }
469 if ((dc.Function("matchAsciiL").Class("OUString").Namespace("rtl")
470 .GlobalNamespace())
471 && fdecl->getNumParams() == 3)
472 {
473 assert(expr->getNumArgs() == 3);
474 // u.matchAsciiL("foo", 3, i) -> u.match("foo", i):
475 handleCharLen(
476 expr, 0, 1, fdecl,
477 (isZero(expr->getArg(2))
478 ? std::string("rtl::OUString::startsWith")
479 : std::string("rtl::OUString::match")),
480 TreatEmpty::Error);
481 return true;
482 }
483 if ((dc.Function("matchIgnoreAsciiCaseAsciiL").Class("OUString")
484 .Namespace("rtl").GlobalNamespace())
485 && fdecl->getNumParams() == 3)
486 {
487 assert(expr->getNumArgs() == 3);
488 // u.matchIgnoreAsciiCaseAsciiL("foo", 3, i) ->
489 // u.matchIgnoreAsciiCase("foo", i):
490 handleCharLen(
491 expr, 0, 1, fdecl,
492 (isZero(expr->getArg(2))
493 ? std::string("rtl::OUString::startsWithIgnoreAsciiCase")
494 : std::string("rtl::OUString::matchIgnoreAsciiCase")),
495 TreatEmpty::Error);
496 return true;
497 }
498 if ((dc.Function("reverseCompareToAsciiL").Class("OUString")
499 .Namespace("rtl").GlobalNamespace())
500 && fdecl->getNumParams() == 2)
501 {
502 // u.reverseCompareToAsciiL("foo", 3) -> u.reverseCompareTo("foo"):
503 handleCharLen(
504 expr, 0, 1, fdecl, "rtl::OUString::reverseCompareTo",
505 TreatEmpty::Error);
506 return true;
507 }
508 if ((dc.Function("reverseCompareTo").Class("OUString").Namespace("rtl")
509 .GlobalNamespace())
510 && fdecl->getNumParams() == 1)
511 {
512 handleOUStringCtor(expr, 0, fdecl, false);
513 return true;
514 }
515 if ((dc.Function("equalsIgnoreAsciiCase").Class("OUString").Namespace("rtl")
516 .GlobalNamespace())
517 && fdecl->getNumParams() == 1)
518 {
519 handleOUStringCtor(expr, 0, fdecl, false);
520 return true;
521 }
522 if ((dc.Function("match").Class("OUString").Namespace("rtl")
523 .GlobalNamespace())
524 && fdecl->getNumParams() == 2)
525 {
526 handleOUStringCtor(expr, 0, fdecl, false);
527 return true;
528 }
529 if ((dc.Function("matchIgnoreAsciiCase").Class("OUString").Namespace("rtl")
530 .GlobalNamespace())
531 && fdecl->getNumParams() == 2)
532 {
533 handleOUStringCtor(expr, 0, fdecl, false);
534 return true;
535 }
536 if ((dc.Function("startsWith").Class("OUString").Namespace("rtl")
537 .GlobalNamespace())
538 && fdecl->getNumParams() == 2)
539 {
540 handleOUStringCtor(expr, 0, fdecl, false);
541 return true;
542 }
543 if ((dc.Function("startsWithIgnoreAsciiCase").Class("OUString")
544 .Namespace("rtl").GlobalNamespace())
545 && fdecl->getNumParams() == 2)
546 {
547 handleOUStringCtor(expr, 0, fdecl, false);
548 return true;
549 }
550 if ((dc.Function("endsWith").Class("OUString").Namespace("rtl")
551 .GlobalNamespace())
552 && fdecl->getNumParams() == 2)
553 {
554 handleOUStringCtor(expr, 0, fdecl, false);
555 return true;
556 }
557 if ((dc.Function("endsWithIgnoreAsciiCase").Class("OUString")
558 .Namespace("rtl").GlobalNamespace())
559 && fdecl->getNumParams() == 2)
560 {
561 handleOUStringCtor(expr, 0, fdecl, false);
562 return true;
563 }
564 if ((dc.Function("indexOf").Class("OUString").Namespace("rtl")
565 .GlobalNamespace())
566 && fdecl->getNumParams() == 2)
567 {
568 handleOUStringCtor(expr, 0, fdecl, false);
569 return true;
570 }
571 if ((dc.Function("lastIndexOf").Class("OUString").Namespace("rtl")
572 .GlobalNamespace())
573 && fdecl->getNumParams() == 1)
574 {
575 handleOUStringCtor(expr, 0, fdecl, false);
576 return true;
577 }
578 if ((dc.Function("replaceFirst").Class("OUString").Namespace("rtl")
579 .GlobalNamespace())
580 && fdecl->getNumParams() == 3)
581 {
582 handleOUStringCtor(expr, 0, fdecl, false);
583 handleOUStringCtor(expr, 1, fdecl, false);
584 return true;
585 }
586 if ((dc.Function("replaceAll").Class("OUString").Namespace("rtl")
587 .GlobalNamespace())
588 && (fdecl->getNumParams() == 2 || fdecl->getNumParams() == 3))
589 {
590 handleOUStringCtor(expr, 0, fdecl, false);
591 handleOUStringCtor(expr, 1, fdecl, false);
592 return true;
593 }
594 if ((dc.Operator(OO_PlusEqual).Class("OUString").Namespace("rtl")
595 .GlobalNamespace())
596 && fdecl->getNumParams() == 1)
597 {
598 handleOUStringCtor(
599 expr, dyn_cast<CXXOperatorCallExpr>(expr) == nullptr ? 0 : 1,
600 fdecl, false);
601 return true;
602 }
603 if ((dc.Function("equals").Class("OUString").Namespace("rtl")
604 .GlobalNamespace())
605 && fdecl->getNumParams() == 1)
606 {
607 unsigned n;
608 bool nonArray;
609 ContentKind cont;
610 bool emb;
611 bool trm;
612 if (!isStringConstant(
613 expr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
614 &emb, &trm))
615 {
616 return true;
617 }
618 if (cont != ContentKind::Ascii) {
619 report(
620 DiagnosticsEngine::Warning,
621 ("call of '%0' with string constant argument containing"
622 " non-ASCII characters"),
623 expr->getExprLoc())
624 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
625 }
626 if (emb) {
627 report(
628 DiagnosticsEngine::Warning,
629 ("call of '%0' with string constant argument containing"
630 " embedded NULLs"),
631 expr->getExprLoc())
632 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
633 }
634 if (n == 0) {
635 report(
636 DiagnosticsEngine::Warning,
637 ("rewrite call of '%0' with empty string constant argument as"
638 " call of 'rtl::OUString::isEmpty'"),
639 expr->getExprLoc())
640 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
641 return true;
642 }
643 }
644 if (dc.Operator(OO_EqualEqual).Namespace("rtl").GlobalNamespace()
645 && fdecl->getNumParams() == 2)
646 {
647 for (unsigned i = 0; i != 2; ++i) {
648 unsigned n;
649 bool nonArray;
650 ContentKind cont;
651 bool emb;
652 bool trm;
653 if (!isStringConstant(
654 expr->getArg(i)->IgnoreParenImpCasts(), &n, &nonArray,
655 &cont, &emb, &trm))
656 {
657 continue;
658 }
659 if (cont != ContentKind::Ascii) {
660 report(
661 DiagnosticsEngine::Warning,
662 ("call of '%0' with string constant argument containing"
663 " non-ASCII characters"),
664 expr->getExprLoc())
665 << fdecl->getQualifiedNameAsString()
666 << expr->getSourceRange();
667 }
668 if (emb) {
669 report(
670 DiagnosticsEngine::Warning,
671 ("call of '%0' with string constant argument containing"
672 " embedded NULLs"),
673 expr->getExprLoc())
674 << fdecl->getQualifiedNameAsString()
675 << expr->getSourceRange();
676 }
677 if (n == 0) {
678 report(
679 DiagnosticsEngine::Warning,
680 ("rewrite call of '%0' with empty string constant argument"
681 " as call of 'rtl::OUString::isEmpty'"),
682 expr->getExprLoc())
683 << fdecl->getQualifiedNameAsString()
684 << expr->getSourceRange();
685 }
686 }
687 return true;
688 }
689 if (dc.Operator(OO_ExclaimEqual).Namespace("rtl").GlobalNamespace()
690 && fdecl->getNumParams() == 2)
691 {
692 for (unsigned i = 0; i != 2; ++i) {
693 unsigned n;
694 bool nonArray;
695 ContentKind cont;
696 bool emb;
697 bool trm;
698 if (!isStringConstant(
699 expr->getArg(i)->IgnoreParenImpCasts(), &n, &nonArray,
700 &cont, &emb, &trm))
701 {
702 continue;
703 }
704 if (cont != ContentKind::Ascii) {
705 report(
706 DiagnosticsEngine::Warning,
707 ("call of '%0' with string constant argument containing"
708 " non-ASCII characters"),
709 expr->getExprLoc())
710 << fdecl->getQualifiedNameAsString()
711 << expr->getSourceRange();
712 }
713 if (emb) {
714 report(
715 DiagnosticsEngine::Warning,
716 ("call of '%0' with string constant argument containing"
717 " embedded NULLs"),
718 expr->getExprLoc())
719 << fdecl->getQualifiedNameAsString()
720 << expr->getSourceRange();
721 }
722 if (n == 0) {
723 report(
724 DiagnosticsEngine::Warning,
725 ("rewrite call of '%0' with empty string constant argument"
726 " as call of '!rtl::OUString::isEmpty'"),
727 expr->getExprLoc())
728 << fdecl->getQualifiedNameAsString()
729 << expr->getSourceRange();
730 }
731 }
732 return true;
733 }
734 if (dc.Operator(OO_Equal).Namespace("rtl").GlobalNamespace()
735 && fdecl->getNumParams() == 1)
736 {
737 unsigned n;
738 bool nonArray;
739 ContentKind cont;
740 bool emb;
741 bool trm;
742 if (!isStringConstant(
743 expr->getArg(1)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
744 &emb, &trm))
745 {
746 return true;
747 }
748 if (cont != ContentKind::Ascii) {
749 report(
750 DiagnosticsEngine::Warning,
751 ("call of '%0' with string constant argument containing"
752 " non-ASCII characters"),
753 expr->getExprLoc())
754 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
755 }
756 if (emb) {
757 report(
758 DiagnosticsEngine::Warning,
759 ("call of '%0' with string constant argument containing"
760 " embedded NULLs"),
761 expr->getExprLoc())
762 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
763 }
764 if (n == 0) {
765 report(
766 DiagnosticsEngine::Warning,
767 ("rewrite call of '%0' with empty string constant argument as"
768 " call of 'rtl::OUString::clear'"),
769 expr->getExprLoc())
770 << fdecl->getQualifiedNameAsString() << expr->getSourceRange();
771 return true;
772 }
773 return true;
774 }
775 if (dc.Function("append").Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
776 && fdecl->getNumParams() == 1)
777 {
778 handleChar(expr, 0, fdecl, "", TreatEmpty::Error, false);
779 return true;
780 }
781 if ((dc.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
782 .GlobalNamespace())
783 && fdecl->getNumParams() == 1)
784 {
785 // u.appendAscii("foo") -> u.append("foo")
786 handleChar(
787 expr, 0, fdecl, "rtl::OUStringBuffer::append", TreatEmpty::Error,
788 true, "appendAscii", "append");
789 return true;
790 }
791 if ((dc.Function("appendAscii").Class("OUStringBuffer").Namespace("rtl")
792 .GlobalNamespace())
793 && fdecl->getNumParams() == 2)
794 {
795 // u.appendAscii("foo", 3) -> u.append("foo"):
796 handleCharLen(
797 expr, 0, 1, fdecl, "rtl::OUStringBuffer::append",
798 TreatEmpty::Error);
799 return true;
800 }
801 if (dc.Function("append").Class("OStringBuffer").Namespace("rtl")
802 .GlobalNamespace())
803 {
804 switch (fdecl->getNumParams()) {
805 case 1:
806 handleFunArgOstring(expr, 0, fdecl);
807 break;
808 case 2:
809 {
810 // b.append("foo", 3) -> b.append("foo"):
811 auto file = getFilenameOfLocation(
812 compiler.getSourceManager().getSpellingLoc(
813 compat::getBeginLoc(expr)));
814 if (loplugin::isSamePathname(
815 file,
816 SRCDIR "/sal/qa/OStringBuffer/rtl_OStringBuffer.cxx"))
817 {
818 return true;
819 }
820 handleCharLen(
821 expr, 0, 1, fdecl, "rtl::OStringBuffer::append",
822 TreatEmpty::Error);
823 }
824 break;
825 default:
826 break;
827 }
828 return true;
829 }
830 if (dc.Function("insert").Class("OStringBuffer").Namespace("rtl")
831 .GlobalNamespace())
832 {
833 switch (fdecl->getNumParams()) {
834 case 2:
835 handleFunArgOstring(expr, 1, fdecl);
836 break;
837 case 3:
838 {
839 // b.insert(i, "foo", 3) -> b.insert(i, "foo"):
840 handleCharLen(
841 expr, 1, 2, fdecl, "rtl::OStringBuffer::insert",
842 TreatEmpty::Error);
843 break;
844 }
845 default:
846 break;
847 }
848 return true;
849 }
850 return true;
851 }
852
VisitCXXConstructExpr(CXXConstructExpr const * expr)853 bool StringConstant::VisitCXXConstructExpr(CXXConstructExpr const * expr) {
854 if (ignoreLocation(expr)) {
855 return true;
856 }
857 auto classdecl = expr->getConstructor()->getParent();
858 if (loplugin::DeclCheck(classdecl)
859 .Class("OUString").Namespace("rtl").GlobalNamespace())
860 {
861 ChangeKind kind;
862 PassThrough pass;
863 bool simplify;
864 switch (expr->getConstructor()->getNumParams()) {
865 case 1:
866 if (!loplugin::TypeCheck(
867 expr->getConstructor()->getParamDecl(0)->getType())
868 .Typedef("sal_Unicode").GlobalNamespace())
869 {
870 return true;
871 }
872 kind = ChangeKind::SingleChar;
873 pass = PassThrough::NonEmptyConstantString;
874 simplify = false;
875 break;
876 case 2:
877 {
878 auto arg = expr->getArg(0);
879 if (loplugin::TypeCheck(arg->getType())
880 .Class("OUStringChar_").Namespace("rtl")
881 .GlobalNamespace())
882 {
883 kind = ChangeKind::OUStringChar;
884 pass = PassThrough::NonEmptyConstantString;
885 simplify = false;
886 } else {
887 unsigned n;
888 bool nonArray;
889 ContentKind cont;
890 bool emb;
891 bool trm;
892 if (!isStringConstant(
893 arg->IgnoreParenImpCasts(), &n, &nonArray, &cont,
894 &emb, &trm))
895 {
896 return true;
897 }
898 if (cont != ContentKind::Ascii) {
899 report(
900 DiagnosticsEngine::Warning,
901 ("construction of %0 with string constant argument"
902 " containing non-ASCII characters"),
903 expr->getExprLoc())
904 << classdecl << expr->getSourceRange();
905 }
906 if (emb) {
907 report(
908 DiagnosticsEngine::Warning,
909 ("construction of %0 with string constant argument"
910 " containing embedded NULLs"),
911 expr->getExprLoc())
912 << classdecl << expr->getSourceRange();
913 }
914 kind = ChangeKind::Char;
915 pass = n == 0
916 ? PassThrough::EmptyConstantString
917 : PassThrough::NonEmptyConstantString;
918 simplify = false;
919 }
920 break;
921 }
922 case 4:
923 {
924 unsigned n;
925 bool nonArray;
926 ContentKind cont;
927 bool emb;
928 bool trm;
929 std::vector<char32_t> utf8Cont;
930 if (!isStringConstant(
931 expr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray,
932 &cont, &emb, &trm, &utf8Cont))
933 {
934 return true;
935 }
936 APSInt res;
937 if (!compat::EvaluateAsInt(expr->getArg(1),
938 res, compiler.getASTContext()))
939 {
940 return true;
941 }
942 if (res != n) {
943 report(
944 DiagnosticsEngine::Warning,
945 ("suspicious 'rtl::OUString' constructor with literal"
946 " of length %0 and non-matching length argument %1"),
947 expr->getExprLoc())
948 << n << res.toString(10) << expr->getSourceRange();
949 return true;
950 }
951 APSInt enc;
952 if (!compat::EvaluateAsInt(expr->getArg(2),
953 enc, compiler.getASTContext()))
954 {
955 return true;
956 }
957 auto const encIsAscii = enc == 11; // RTL_TEXTENCODING_ASCII_US
958 auto const encIsUtf8 = enc == 76; // RTL_TEXTENCODING_UTF8
959 if (!compat::EvaluateAsInt(expr->getArg(3),
960 res, compiler.getASTContext())
961 || res != 0x333) // OSTRING_TO_OUSTRING_CVTFLAGS
962 {
963 return true;
964 }
965 if (!encIsAscii && cont == ContentKind::Ascii) {
966 report(
967 DiagnosticsEngine::Warning,
968 ("suspicious 'rtl::OUString' constructor with text"
969 " encoding %0 but plain ASCII content; use"
970 " 'RTL_TEXTENCODING_ASCII_US' instead"),
971 expr->getArg(2)->getExprLoc())
972 << enc.toString(10) << expr->getSourceRange();
973 return true;
974 }
975 if (encIsUtf8) {
976 if (cont == ContentKind::Arbitrary) {
977 report(
978 DiagnosticsEngine::Warning,
979 ("suspicious 'rtl::OUString' constructor with text"
980 " encoding 'RTL_TEXTENCODING_UTF8' but non-UTF-8"
981 " content"),
982 expr->getArg(0)->getExprLoc())
983 << expr->getSourceRange();
984 } else {
985 assert(cont == ContentKind::Utf8);
986 //TODO: keep original content as much as possible
987 std::ostringstream s;
988 for (auto const c: utf8Cont) {
989 if (c == '\\') {
990 s << "\\\\";
991 } else if (c == '"') {
992 s << "\\\"";
993 } else if (c == '\a') {
994 s << "\\a";
995 } else if (c == '\b') {
996 s << "\\b";
997 } else if (c == '\f') {
998 s << "\\f";
999 } else if (c == '\n') {
1000 s << "\\n";
1001 } else if (c == '\r') {
1002 s << "\\r";
1003 } else if (c == '\t') {
1004 s << "\\r";
1005 } else if (c == '\v') {
1006 s << "\\v";
1007 } else if (c <= 0x1F || c == 0x7F) {
1008 s << "\\x" << std::oct << std::setw(3)
1009 << std::setfill('0')
1010 << static_cast<std::uint_least32_t>(c);
1011 } else if (c < 0x7F) {
1012 s << char(c);
1013 } else if (c <= 0xFFFF) {
1014 s << "\\u" << std::hex << std::uppercase
1015 << std::setw(4) << std::setfill('0')
1016 << static_cast<std::uint_least32_t>(c);
1017 } else {
1018 assert(c <= 0x10FFFF);
1019 s << "\\U" << std::hex << std::uppercase
1020 << std::setw(8) << std::setfill('0')
1021 << static_cast<std::uint_least32_t>(c);
1022 }
1023 }
1024 report(
1025 DiagnosticsEngine::Warning,
1026 ("simplify construction of %0 with UTF-8 content as"
1027 " OUString(u\"%1\")"),
1028 expr->getExprLoc())
1029 << classdecl << s.str() << expr->getSourceRange();
1030
1031 }
1032 return true;
1033 }
1034 if (cont != ContentKind::Ascii || emb) {
1035 // cf. remaining uses of RTL_CONSTASCII_USTRINGPARAM
1036 return true;
1037 }
1038 kind = ChangeKind::Char;
1039 pass = n == 0
1040 ? PassThrough::EmptyConstantString
1041 : PassThrough::NonEmptyConstantString;
1042 simplify = true;
1043 break;
1044 }
1045 default:
1046 return true;
1047 }
1048 if (!calls_.empty()) {
1049 Expr const * call = calls_.top();
1050 CallExpr::const_arg_iterator argsBeg;
1051 CallExpr::const_arg_iterator argsEnd;
1052 if (isa<CallExpr>(call)) {
1053 argsBeg = cast<CallExpr>(call)->arg_begin();
1054 argsEnd = cast<CallExpr>(call)->arg_end();
1055 } else if (isa<CXXConstructExpr>(call)) {
1056 argsBeg = cast<CXXConstructExpr>(call)->arg_begin();
1057 argsEnd = cast<CXXConstructExpr>(call)->arg_end();
1058 } else {
1059 assert(false);
1060 }
1061 for (auto i(argsBeg); i != argsEnd; ++i) {
1062 Expr const * e = (*i)->IgnoreParenImpCasts();
1063 if (isa<MaterializeTemporaryExpr>(e)) {
1064 e = compat::getSubExpr(cast<MaterializeTemporaryExpr>(e))
1065 ->IgnoreParenImpCasts();
1066 }
1067 if (isa<CXXFunctionalCastExpr>(e)) {
1068 e = cast<CXXFunctionalCastExpr>(e)->getSubExpr()
1069 ->IgnoreParenImpCasts();
1070 }
1071 if (isa<CXXBindTemporaryExpr>(e)) {
1072 e = cast<CXXBindTemporaryExpr>(e)->getSubExpr()
1073 ->IgnoreParenImpCasts();
1074 }
1075 if (e == expr) {
1076 if (isa<CallExpr>(call)) {
1077 FunctionDecl const * fdecl
1078 = cast<CallExpr>(call)->getDirectCallee();
1079 if (fdecl == nullptr) {
1080 break;
1081 }
1082 loplugin::DeclCheck dc(fdecl);
1083 if (pass == PassThrough::EmptyConstantString) {
1084 if ((dc.Function("equals").Class("OUString")
1085 .Namespace("rtl").GlobalNamespace())
1086 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1087 .GlobalNamespace()))
1088 {
1089 report(
1090 DiagnosticsEngine::Warning,
1091 ("rewrite call of '%0' with construction of"
1092 " %1 with empty string constant argument"
1093 " as call of 'rtl::OUString::isEmpty'"),
1094 getMemberLocation(call))
1095 << fdecl->getQualifiedNameAsString()
1096 << classdecl << call->getSourceRange();
1097 return true;
1098 }
1099 if (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
1100 .GlobalNamespace())
1101 {
1102 report(
1103 DiagnosticsEngine::Warning,
1104 ("rewrite call of '%0' with construction of"
1105 " %1 with empty string constant argument"
1106 " as call of '!rtl::OUString::isEmpty'"),
1107 getMemberLocation(call))
1108 << fdecl->getQualifiedNameAsString()
1109 << classdecl << call->getSourceRange();
1110 return true;
1111 }
1112 if ((dc.Operator(OO_Plus).Namespace("rtl")
1113 .GlobalNamespace())
1114 || (dc.Operator(OO_Plus).Class("OUString")
1115 .Namespace("rtl").GlobalNamespace()))
1116 {
1117 report(
1118 DiagnosticsEngine::Warning,
1119 ("call of '%0' with suspicious construction"
1120 " of %1 with empty string constant"
1121 " argument"),
1122 getMemberLocation(call))
1123 << fdecl->getQualifiedNameAsString()
1124 << classdecl << call->getSourceRange();
1125 return true;
1126 }
1127 if (dc.Operator(OO_Equal).Class("OUString")
1128 .Namespace("rtl").GlobalNamespace())
1129 {
1130 report(
1131 DiagnosticsEngine::Warning,
1132 ("rewrite call of '%0' with construction of"
1133 " %1 with empty string constant argument"
1134 " as call of 'rtl::OUString::clear'"),
1135 getMemberLocation(call))
1136 << fdecl->getQualifiedNameAsString()
1137 << classdecl << call->getSourceRange();
1138 return true;
1139 }
1140 } else {
1141 assert(pass == PassThrough::NonEmptyConstantString);
1142 if (dc.Function("equals").Class("OUString")
1143 .Namespace("rtl").GlobalNamespace())
1144 {
1145 report(
1146 DiagnosticsEngine::Warning,
1147 ("rewrite call of '%0' with construction of"
1148 " %1 with %2 as 'operator =='"),
1149 getMemberLocation(call))
1150 << fdecl->getQualifiedNameAsString()
1151 << classdecl << describeChangeKind(kind)
1152 << call->getSourceRange();
1153 return true;
1154 }
1155 if ((dc.Operator(OO_Plus).Namespace("rtl")
1156 .GlobalNamespace())
1157 || (dc.Operator(OO_Plus).Class("OUString")
1158 .Namespace("rtl").GlobalNamespace())
1159 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1160 .GlobalNamespace())
1161 || (dc.Operator(OO_ExclaimEqual)
1162 .Namespace("rtl").GlobalNamespace()))
1163 {
1164 if (dc.Operator(OO_Plus).Namespace("rtl")
1165 .GlobalNamespace())
1166 {
1167 auto file = getFilenameOfLocation(
1168 compiler.getSourceManager()
1169 .getSpellingLoc(
1170 compat::getBeginLoc(expr)));
1171 if (loplugin::isSamePathname(
1172 file,
1173 (SRCDIR
1174 "/sal/qa/rtl/strings/test_ostring_concat.cxx"))
1175 || loplugin::isSamePathname(
1176 file,
1177 (SRCDIR
1178 "/sal/qa/rtl/strings/test_oustring_concat.cxx")))
1179 {
1180 return true;
1181 }
1182 }
1183 auto loc = compat::getBeginLoc(expr->getArg(0));
1184 while (compiler.getSourceManager()
1185 .isMacroArgExpansion(loc))
1186 {
1187 loc = compiler.getSourceManager()
1188 .getImmediateMacroCallerLoc(loc);
1189 }
1190 if (kind == ChangeKind::SingleChar) {
1191 report(
1192 DiagnosticsEngine::Warning,
1193 ("rewrite construction of %0 with %1 in"
1194 " call of '%2' as construction of"
1195 " 'OUStringChar'"),
1196 getMemberLocation(expr))
1197 << classdecl << describeChangeKind(kind)
1198 << fdecl->getQualifiedNameAsString()
1199 << expr->getSourceRange();
1200 } else {
1201 report(
1202 DiagnosticsEngine::Warning,
1203 ("elide construction of %0 with %1 in"
1204 " call of '%2'"),
1205 getMemberLocation(expr))
1206 << classdecl << describeChangeKind(kind)
1207 << fdecl->getQualifiedNameAsString()
1208 << expr->getSourceRange();
1209 }
1210 return true;
1211 }
1212 }
1213 } else if (isa<CXXConstructExpr>(call)) {
1214 } else {
1215 assert(false);
1216 }
1217 }
1218 }
1219 }
1220 if (simplify) {
1221 report(
1222 DiagnosticsEngine::Warning,
1223 "simplify construction of %0 with %1", expr->getExprLoc())
1224 << classdecl << describeChangeKind(kind)
1225 << expr->getSourceRange();
1226 }
1227 return true;
1228 }
1229
1230 auto consDecl = expr->getConstructor();
1231 for (unsigned i = 0; i != consDecl->getNumParams(); ++i) {
1232 auto t = consDecl->getParamDecl(i)->getType();
1233 if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
1234 .LvalueReference().Const().NotSubstTemplateTypeParmType()
1235 .Class("OUString").Namespace("rtl").GlobalNamespace())
1236 {
1237 auto argExpr = expr->getArg(i);
1238 if (argExpr && i <= consDecl->getNumParams())
1239 {
1240 if (!hasOverloads(consDecl, expr->getNumArgs()))
1241 {
1242 handleOUStringCtor(expr, argExpr, consDecl, true);
1243 }
1244 }
1245 }
1246 if (loplugin::TypeCheck(t).NotSubstTemplateTypeParmType()
1247 .LvalueReference().Const().NotSubstTemplateTypeParmType()
1248 .Class("OString").Namespace("rtl").GlobalNamespace())
1249 {
1250 auto argExpr = expr->getArg(i);
1251 if (argExpr && i <= consDecl->getNumParams())
1252 {
1253 if (!hasOverloads(consDecl, expr->getNumArgs()))
1254 {
1255 handleOStringCtor(expr, argExpr, consDecl, true);
1256 }
1257 }
1258 }
1259 }
1260
1261 return true;
1262 }
1263
VisitReturnStmt(ReturnStmt const * stmt)1264 bool StringConstant::VisitReturnStmt(ReturnStmt const * stmt) {
1265 if (ignoreLocation(stmt)) {
1266 return true;
1267 }
1268 auto const e1 = stmt->getRetValue();
1269 if (e1 == nullptr) {
1270 return true;
1271 }
1272 auto const tc1 = loplugin::TypeCheck(e1->getType().getTypePtr());
1273 if (!(tc1.Class("OString").Namespace("rtl").GlobalNamespace()
1274 || tc1.Class("OUString").Namespace("rtl").GlobalNamespace()))
1275 {
1276 return true;
1277 }
1278 assert(!returnTypes_.empty());
1279 auto const tc2 = loplugin::TypeCheck(returnTypes_.top().getTypePtr());
1280 if (!(tc2.Class("OString").Namespace("rtl").GlobalNamespace()
1281 || tc2.Class("OUString").Namespace("rtl").GlobalNamespace()))
1282 {
1283 return true;
1284 }
1285 auto const e2 = dyn_cast<CXXFunctionalCastExpr>(e1->IgnoreImplicit());
1286 if (e2 == nullptr) {
1287 return true;
1288 }
1289 auto const e3 = dyn_cast<CXXBindTemporaryExpr>(e2->getSubExpr());
1290 if (e3 == nullptr) {
1291 return true;
1292 }
1293 auto const e4 = dyn_cast<CXXConstructExpr>(e3->getSubExpr());
1294 if (e4 == nullptr) {
1295 return true;
1296 }
1297 if (e4->getNumArgs() != 2) {
1298 return true;
1299 }
1300 auto const t = e4->getArg(0)->getType();
1301 if (!(t.isConstQualified() && t->isConstantArrayType())) {
1302 return true;
1303 }
1304 auto const e5 = e4->getArg(1);
1305 if (!(isa<CXXDefaultArgExpr>(e5)
1306 && (loplugin::TypeCheck(e5->getType()).Struct("Dummy").Namespace("libreoffice_internal")
1307 .Namespace("rtl").GlobalNamespace())))
1308 {
1309 return true;
1310 }
1311 report(DiagnosticsEngine::Warning, "elide constructor call", compat::getBeginLoc(e1))
1312 << e1->getSourceRange();
1313 return true;
1314 }
1315
describeChangeKind(ChangeKind kind)1316 std::string StringConstant::describeChangeKind(ChangeKind kind) {
1317 switch (kind) {
1318 case ChangeKind::Char:
1319 return "string constant argument";
1320 case ChangeKind::CharLen:
1321 return "string constant and matching length arguments";
1322 case ChangeKind::SingleChar:
1323 return "sal_Unicode argument";
1324 case ChangeKind::OUStringChar:
1325 return "OUStringChar argument";
1326 }
1327 llvm_unreachable("unknown change kind");
1328 }
1329
isStringConstant(Expr const * expr,unsigned * size,bool * nonArray,ContentKind * content,bool * embeddedNuls,bool * terminatingNul,std::vector<char32_t> * utf8Content)1330 bool StringConstant::isStringConstant(
1331 Expr const * expr, unsigned * size, bool * nonArray, ContentKind * content,
1332 bool * embeddedNuls, bool * terminatingNul,
1333 std::vector<char32_t> * utf8Content)
1334 {
1335 assert(expr != nullptr);
1336 assert(size != nullptr);
1337 assert(nonArray != nullptr);
1338 assert(content != nullptr);
1339 assert(embeddedNuls != nullptr);
1340 assert(terminatingNul != nullptr);
1341 QualType t = expr->getType();
1342 // Look inside RTL_CONSTASCII_STRINGPARAM:
1343 if (loplugin::TypeCheck(t).Pointer().Const().Char()) {
1344 auto e2 = dyn_cast<UnaryOperator>(expr);
1345 if (e2 != nullptr && e2->getOpcode() == UO_AddrOf) {
1346 auto e3 = dyn_cast<ArraySubscriptExpr>(
1347 e2->getSubExpr()->IgnoreParenImpCasts());
1348 if (e3 == nullptr || !isZero(e3->getIdx()->IgnoreParenImpCasts())) {
1349 return false;
1350 }
1351 expr = e3->getBase()->IgnoreParenImpCasts();
1352 t = expr->getType();
1353 }
1354 }
1355 if (!t.isConstQualified()) {
1356 return false;
1357 }
1358 DeclRefExpr const * dre = dyn_cast<DeclRefExpr>(expr);
1359 if (dre != nullptr) {
1360 VarDecl const * var = dyn_cast<VarDecl>(dre->getDecl());
1361 if (var != nullptr) {
1362 Expr const * init = var->getAnyInitializer();
1363 if (init != nullptr) {
1364 expr = init->IgnoreParenImpCasts();
1365 }
1366 }
1367 }
1368 bool isPtr;
1369 if (loplugin::TypeCheck(t).Pointer().Const().Char()) {
1370 isPtr = true;
1371 } else if (t->isConstantArrayType()
1372 && (loplugin::TypeCheck(
1373 t->getAsArrayTypeUnsafe()->getElementType())
1374 .Char()))
1375 {
1376 isPtr = false;
1377 } else {
1378 return false;
1379 }
1380 clang::StringLiteral const * lit = dyn_cast<clang::StringLiteral>(expr);
1381 if (lit != nullptr) {
1382 if (!(lit->isAscii() || lit->isUTF8())) {
1383 return false;
1384 }
1385 unsigned n = lit->getLength();
1386 ContentKind cont = ContentKind::Ascii;
1387 bool emb = false;
1388 char32_t val = 0;
1389 enum class Utf8State { Start, E0, EB, F0, F4, Trail1, Trail2, Trail3 };
1390 Utf8State s = Utf8State::Start;
1391 StringRef str = lit->getString();
1392 for (unsigned i = 0; i != n; ++i) {
1393 auto const c = static_cast<unsigned char>(str[i]);
1394 if (c == '\0') {
1395 emb = true;
1396 }
1397 switch (s) {
1398 case Utf8State::Start:
1399 if (c >= 0x80) {
1400 if (c >= 0xC2 && c <= 0xDF) {
1401 val = c & 0x1F;
1402 s = Utf8State::Trail1;
1403 } else if (c == 0xE0) {
1404 val = c & 0x0F;
1405 s = Utf8State::E0;
1406 } else if ((c >= 0xE1 && c <= 0xEA)
1407 || (c >= 0xEE && c <= 0xEF))
1408 {
1409 val = c & 0x0F;
1410 s = Utf8State::Trail2;
1411 } else if (c == 0xEB) {
1412 val = c & 0x0F;
1413 s = Utf8State::EB;
1414 } else if (c == 0xF0) {
1415 val = c & 0x03;
1416 s = Utf8State::F0;
1417 } else if (c >= 0xF1 && c <= 0xF3) {
1418 val = c & 0x03;
1419 s = Utf8State::Trail3;
1420 } else if (c == 0xF4) {
1421 val = c & 0x03;
1422 s = Utf8State::F4;
1423 } else {
1424 cont = ContentKind::Arbitrary;
1425 }
1426 } else if (utf8Content != nullptr
1427 && cont != ContentKind::Arbitrary)
1428 {
1429 utf8Content->push_back(c);
1430 }
1431 break;
1432 case Utf8State::E0:
1433 if (c >= 0xA0 && c <= 0xBF) {
1434 val = (val << 6) | (c & 0x3F);
1435 s = Utf8State::Trail1;
1436 } else {
1437 cont = ContentKind::Arbitrary;
1438 s = Utf8State::Start;
1439 }
1440 break;
1441 case Utf8State::EB:
1442 if (c >= 0x80 && c <= 0x9F) {
1443 val = (val << 6) | (c & 0x3F);
1444 s = Utf8State::Trail1;
1445 } else {
1446 cont = ContentKind::Arbitrary;
1447 s = Utf8State::Start;
1448 }
1449 break;
1450 case Utf8State::F0:
1451 if (c >= 0x90 && c <= 0xBF) {
1452 val = (val << 6) | (c & 0x3F);
1453 s = Utf8State::Trail2;
1454 } else {
1455 cont = ContentKind::Arbitrary;
1456 s = Utf8State::Start;
1457 }
1458 break;
1459 case Utf8State::F4:
1460 if (c >= 0x80 && c <= 0x8F) {
1461 val = (val << 6) | (c & 0x3F);
1462 s = Utf8State::Trail2;
1463 } else {
1464 cont = ContentKind::Arbitrary;
1465 s = Utf8State::Start;
1466 }
1467 break;
1468 case Utf8State::Trail1:
1469 if (c >= 0x80 && c <= 0xBF) {
1470 cont = ContentKind::Utf8;
1471 if (utf8Content != nullptr)
1472 {
1473 utf8Content->push_back((val << 6) | (c & 0x3F));
1474 val = 0;
1475 }
1476 } else {
1477 cont = ContentKind::Arbitrary;
1478 }
1479 s = Utf8State::Start;
1480 break;
1481 case Utf8State::Trail2:
1482 if (c >= 0x80 && c <= 0xBF) {
1483 val = (val << 6) | (c & 0x3F);
1484 s = Utf8State::Trail1;
1485 } else {
1486 cont = ContentKind::Arbitrary;
1487 s = Utf8State::Start;
1488 }
1489 break;
1490 case Utf8State::Trail3:
1491 if (c >= 0x80 && c <= 0xBF) {
1492 val = (val << 6) | (c & 0x3F);
1493 s = Utf8State::Trail2;
1494 } else {
1495 cont = ContentKind::Arbitrary;
1496 s = Utf8State::Start;
1497 }
1498 break;
1499 }
1500 }
1501 *size = n;
1502 *nonArray = isPtr;
1503 *content = cont;
1504 *embeddedNuls = emb;
1505 *terminatingNul = true;
1506 return true;
1507 }
1508 APValue v;
1509 if (!expr->isCXX11ConstantExpr(compiler.getASTContext(), &v)) {
1510 return false;
1511 }
1512 switch (v.getKind()) {
1513 case APValue::LValue:
1514 {
1515 Expr const * e = v.getLValueBase().dyn_cast<Expr const *>();
1516 if (e == nullptr) {
1517 return false;
1518 }
1519 if (!v.getLValueOffset().isZero()) {
1520 return false; //TODO
1521 }
1522 Expr const * e2 = e->IgnoreParenImpCasts();
1523 if (e2 != e) {
1524 return isStringConstant(
1525 e2, size, nonArray, content, embeddedNuls, terminatingNul);
1526 }
1527 //TODO: string literals are represented as recursive LValues???
1528 llvm::APInt n
1529 = compiler.getASTContext().getAsConstantArrayType(t)->getSize();
1530 assert(n != 0);
1531 --n;
1532 assert(n.ule(std::numeric_limits<unsigned>::max()));
1533 *size = static_cast<unsigned>(n.getLimitedValue());
1534 *nonArray = isPtr || *nonArray;
1535 *content = ContentKind::Ascii; //TODO
1536 *embeddedNuls = false; //TODO
1537 *terminatingNul = true;
1538 return true;
1539 }
1540 case APValue::Array:
1541 {
1542 if (v.hasArrayFiller()) { //TODO: handle final NULL filler?
1543 return false;
1544 }
1545 unsigned n = v.getArraySize();
1546 assert(n != 0);
1547 ContentKind cont = ContentKind::Ascii;
1548 bool emb = false;
1549 //TODO: check for ContentType::Utf8
1550 for (unsigned i = 0; i != n - 1; ++i) {
1551 APValue e(v.getArrayInitializedElt(i));
1552 if (!e.isInt()) { //TODO: assert?
1553 return false;
1554 }
1555 APSInt iv = e.getInt();
1556 if (iv == 0) {
1557 emb = true;
1558 } else if (iv.uge(0x80)) {
1559 cont = ContentKind::Arbitrary;
1560 }
1561 }
1562 APValue e(v.getArrayInitializedElt(n - 1));
1563 if (!e.isInt()) { //TODO: assert?
1564 return false;
1565 }
1566 bool trm = e.getInt() == 0;
1567 *size = trm ? n - 1 : n;
1568 *nonArray = isPtr;
1569 *content = cont;
1570 *embeddedNuls = emb;
1571 *terminatingNul = trm;
1572 return true;
1573 }
1574 default:
1575 assert(false); //TODO???
1576 return false;
1577 }
1578 }
1579
isZero(Expr const * expr)1580 bool StringConstant::isZero(Expr const * expr) {
1581 APSInt res;
1582 return compat::EvaluateAsInt(expr, res, compiler.getASTContext()) && res == 0;
1583 }
1584
reportChange(Expr const * expr,ChangeKind kind,std::string const & original,std::string const & replacement,PassThrough pass,bool nonArray,char const * rewriteFrom,char const * rewriteTo)1585 void StringConstant::reportChange(
1586 Expr const * expr, ChangeKind kind, std::string const & original,
1587 std::string const & replacement, PassThrough pass, bool nonArray,
1588 char const * rewriteFrom, char const * rewriteTo)
1589 {
1590 assert((rewriteFrom == nullptr) == (rewriteTo == nullptr));
1591 if (pass != PassThrough::No && !calls_.empty()) {
1592 Expr const * call = calls_.top();
1593 CallExpr::const_arg_iterator argsBeg;
1594 CallExpr::const_arg_iterator argsEnd;
1595 if (isa<CallExpr>(call)) {
1596 argsBeg = cast<CallExpr>(call)->arg_begin();
1597 argsEnd = cast<CallExpr>(call)->arg_end();
1598 } else if (isa<CXXConstructExpr>(call)) {
1599 argsBeg = cast<CXXConstructExpr>(call)->arg_begin();
1600 argsEnd = cast<CXXConstructExpr>(call)->arg_end();
1601 } else {
1602 assert(false);
1603 }
1604 for (auto i(argsBeg); i != argsEnd; ++i) {
1605 Expr const * e = (*i)->IgnoreParenImpCasts();
1606 if (isa<CXXBindTemporaryExpr>(e)) {
1607 e = cast<CXXBindTemporaryExpr>(e)->getSubExpr()
1608 ->IgnoreParenImpCasts();
1609 }
1610 if (e == expr) {
1611 if (isa<CallExpr>(call)) {
1612 FunctionDecl const * fdecl
1613 = cast<CallExpr>(call)->getDirectCallee();
1614 if (fdecl == nullptr) {
1615 break;
1616 }
1617 loplugin::DeclCheck dc(fdecl);
1618 if (pass == PassThrough::EmptyConstantString) {
1619 if ((dc.Function("equals").Class("OUString")
1620 .Namespace("rtl").GlobalNamespace())
1621 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1622 .GlobalNamespace()))
1623 {
1624 report(
1625 DiagnosticsEngine::Warning,
1626 ("rewrite call of '%0' with call of %1 with"
1627 " empty string constant argument as call of"
1628 " 'rtl::OUString::isEmpty'"),
1629 getMemberLocation(call))
1630 << fdecl->getQualifiedNameAsString() << original
1631 << call->getSourceRange();
1632 return;
1633 }
1634 if (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
1635 .GlobalNamespace())
1636 {
1637 report(
1638 DiagnosticsEngine::Warning,
1639 ("rewrite call of '%0' with call of %1 with"
1640 " empty string constant argument as call of"
1641 " '!rtl::OUString::isEmpty'"),
1642 getMemberLocation(call))
1643 << fdecl->getQualifiedNameAsString() << original
1644 << call->getSourceRange();
1645 return;
1646 }
1647 if ((dc.Operator(OO_Plus).Namespace("rtl")
1648 .GlobalNamespace())
1649 || (dc.Operator(OO_Plus).Class("OUString")
1650 .Namespace("rtl").GlobalNamespace()))
1651 {
1652 report(
1653 DiagnosticsEngine::Warning,
1654 ("call of '%0' with suspicious call of %1 with"
1655 " empty string constant argument"),
1656 getMemberLocation(call))
1657 << fdecl->getQualifiedNameAsString() << original
1658 << call->getSourceRange();
1659 return;
1660 }
1661 if (dc.Operator(OO_Equal).Class("OUString")
1662 .Namespace("rtl").GlobalNamespace())
1663 {
1664 report(
1665 DiagnosticsEngine::Warning,
1666 ("rewrite call of '%0' with call of %1 with"
1667 " empty string constant argument as call of"
1668 " rtl::OUString::call"),
1669 getMemberLocation(call))
1670 << fdecl->getQualifiedNameAsString() << original
1671 << call->getSourceRange();
1672 return;
1673 }
1674 report(
1675 DiagnosticsEngine::Warning,
1676 "TODO call inside %0", getMemberLocation(expr))
1677 << fdecl->getQualifiedNameAsString()
1678 << expr->getSourceRange();
1679 return;
1680 } else {
1681 assert(pass == PassThrough::NonEmptyConstantString);
1682 if ((dc.Function("equals").Class("OUString")
1683 .Namespace("rtl").GlobalNamespace())
1684 || (dc.Operator(OO_Equal).Class("OUString")
1685 .Namespace("rtl").GlobalNamespace())
1686 || (dc.Operator(OO_EqualEqual).Namespace("rtl")
1687 .GlobalNamespace())
1688 || (dc.Operator(OO_ExclaimEqual).Namespace("rtl")
1689 .GlobalNamespace()))
1690 {
1691 report(
1692 DiagnosticsEngine::Warning,
1693 "elide call of %0 with %1 in call of '%2'",
1694 getMemberLocation(expr))
1695 << original << describeChangeKind(kind)
1696 << fdecl->getQualifiedNameAsString()
1697 << expr->getSourceRange();
1698 return;
1699 }
1700 report(
1701 DiagnosticsEngine::Warning,
1702 ("rewrite call of %0 with %1 in call of '%2' as"
1703 " (implicit) construction of 'OUString'"),
1704 getMemberLocation(expr))
1705 << original << describeChangeKind(kind)
1706 << fdecl->getQualifiedNameAsString()
1707 << expr->getSourceRange();
1708 return;
1709 }
1710 } else if (isa<CXXConstructExpr>(call)) {
1711 auto classdecl = cast<CXXConstructExpr>(call)
1712 ->getConstructor()->getParent();
1713 loplugin::DeclCheck dc(classdecl);
1714 if (dc.Class("OUString").Namespace("rtl").GlobalNamespace()
1715 || (dc.Class("OUStringBuffer").Namespace("rtl")
1716 .GlobalNamespace()))
1717 {
1718 //TODO: propagate further out?
1719 if (pass == PassThrough::EmptyConstantString) {
1720 report(
1721 DiagnosticsEngine::Warning,
1722 ("rewrite construction of %0 with call of %1"
1723 " with empty string constant argument as"
1724 " default construction of %0"),
1725 getMemberLocation(call))
1726 << classdecl << original
1727 << call->getSourceRange();
1728 } else {
1729 assert(pass == PassThrough::NonEmptyConstantString);
1730 report(
1731 DiagnosticsEngine::Warning,
1732 ("elide call of %0 with %1 in construction of"
1733 " %2"),
1734 getMemberLocation(expr))
1735 << original << describeChangeKind(kind)
1736 << classdecl << expr->getSourceRange();
1737 }
1738 return;
1739 }
1740 } else {
1741 assert(false);
1742 }
1743 }
1744 }
1745 }
1746 if (rewriter != nullptr && !nonArray && rewriteFrom != nullptr) {
1747 SourceLocation loc = getMemberLocation(expr);
1748 while (compiler.getSourceManager().isMacroArgExpansion(loc)) {
1749 loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
1750 }
1751 if (compiler.getSourceManager().isMacroBodyExpansion(loc)) {
1752 loc = compiler.getSourceManager().getSpellingLoc(loc);
1753 }
1754 unsigned n = Lexer::MeasureTokenLength(
1755 loc, compiler.getSourceManager(), compiler.getLangOpts());
1756 if ((std::string(compiler.getSourceManager().getCharacterData(loc), n)
1757 == rewriteFrom)
1758 && replaceText(loc, n, rewriteTo))
1759 {
1760 return;
1761 }
1762 }
1763 report(
1764 DiagnosticsEngine::Warning,
1765 "rewrite call of '%0' with %1 as call of '%2'%3",
1766 getMemberLocation(expr))
1767 << original << describeChangeKind(kind) << replacement
1768 << adviseNonArray(nonArray) << expr->getSourceRange();
1769 }
1770
checkEmpty(CallExpr const * expr,FunctionDecl const * callee,TreatEmpty treatEmpty,unsigned size,std::string * replacement)1771 void StringConstant::checkEmpty(
1772 CallExpr const * expr, FunctionDecl const * callee, TreatEmpty treatEmpty,
1773 unsigned size, std::string * replacement)
1774 {
1775 assert(replacement != nullptr);
1776 if (size == 0) {
1777 switch (treatEmpty) {
1778 case TreatEmpty::DefaultCtor:
1779 *replacement = "rtl::OUString default constructor";
1780 break;
1781 case TreatEmpty::CheckEmpty:
1782 *replacement = "rtl::OUString::isEmpty";
1783 break;
1784 case TreatEmpty::Error:
1785 report(
1786 DiagnosticsEngine::Warning,
1787 "call of '%0' with suspicious empty string constant argument",
1788 getMemberLocation(expr))
1789 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1790 break;
1791 }
1792 }
1793 }
1794
handleChar(CallExpr const * expr,unsigned arg,FunctionDecl const * callee,std::string const & replacement,TreatEmpty treatEmpty,bool literal,char const * rewriteFrom,char const * rewriteTo)1795 void StringConstant::handleChar(
1796 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
1797 std::string const & replacement, TreatEmpty treatEmpty, bool literal,
1798 char const * rewriteFrom, char const * rewriteTo)
1799 {
1800 unsigned n;
1801 bool nonArray;
1802 ContentKind cont;
1803 bool emb;
1804 bool trm;
1805 if (!isStringConstant(
1806 expr->getArg(arg)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
1807 &emb, &trm))
1808 {
1809 return;
1810 }
1811 if (cont != ContentKind::Ascii) {
1812 report(
1813 DiagnosticsEngine::Warning,
1814 ("call of '%0' with string constant argument containing non-ASCII"
1815 " characters"),
1816 getMemberLocation(expr))
1817 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1818 return;
1819 }
1820 if (emb) {
1821 report(
1822 DiagnosticsEngine::Warning,
1823 ("call of '%0' with string constant argument containing embedded"
1824 " NULLs"),
1825 getMemberLocation(expr))
1826 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1827 return;
1828 }
1829 if (!trm) {
1830 report(
1831 DiagnosticsEngine::Warning,
1832 ("call of '%0' with string constant argument lacking a terminating"
1833 " NULL"),
1834 getMemberLocation(expr))
1835 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1836 return;
1837 }
1838 std::string repl(replacement);
1839 checkEmpty(expr, callee, treatEmpty, n, &repl);
1840 if (!repl.empty()) {
1841 reportChange(
1842 expr, ChangeKind::Char, callee->getQualifiedNameAsString(), repl,
1843 (literal
1844 ? (n == 0
1845 ? PassThrough::EmptyConstantString
1846 : PassThrough::NonEmptyConstantString)
1847 : PassThrough::No),
1848 nonArray, rewriteFrom, rewriteTo);
1849 }
1850 }
1851
handleCharLen(CallExpr const * expr,unsigned arg1,unsigned arg2,FunctionDecl const * callee,std::string const & replacement,TreatEmpty treatEmpty)1852 void StringConstant::handleCharLen(
1853 CallExpr const * expr, unsigned arg1, unsigned arg2,
1854 FunctionDecl const * callee, std::string const & replacement,
1855 TreatEmpty treatEmpty)
1856 {
1857 // Especially for f(RTL_CONSTASCII_STRINGPARAM("foo")), where
1858 // RTL_CONSTASCII_STRINGPARAM expands to complicated expressions involving
1859 // (&(X)[0] sub-expressions (and it might or might not be better to handle
1860 // that at the level of non-expanded macros instead, but I have not found
1861 // out how to do that yet anyway):
1862 unsigned n;
1863 bool nonArray;
1864 ContentKind cont;
1865 bool emb;
1866 bool trm;
1867 if (!(isStringConstant(
1868 expr->getArg(arg1)->IgnoreParenImpCasts(), &n, &nonArray, &cont,
1869 &emb, &trm)
1870 && trm))
1871 {
1872 return;
1873 }
1874 APSInt res;
1875 if (compat::EvaluateAsInt(expr->getArg(arg2), res, compiler.getASTContext())) {
1876 if (res != n) {
1877 return;
1878 }
1879 } else {
1880 UnaryOperator const * op = dyn_cast<UnaryOperator>(
1881 expr->getArg(arg1)->IgnoreParenImpCasts());
1882 if (op == nullptr || op->getOpcode() != UO_AddrOf) {
1883 return;
1884 }
1885 ArraySubscriptExpr const * subs = dyn_cast<ArraySubscriptExpr>(
1886 op->getSubExpr()->IgnoreParenImpCasts());
1887 if (subs == nullptr) {
1888 return;
1889 }
1890 unsigned n2;
1891 bool nonArray2;
1892 ContentKind cont2;
1893 bool emb2;
1894 bool trm2;
1895 if (!(isStringConstant(
1896 subs->getBase()->IgnoreParenImpCasts(), &n2, &nonArray2,
1897 &cont2, &emb2, &trm2)
1898 && n2 == n && cont2 == cont && emb2 == emb && trm2 == trm
1899 //TODO: same strings
1900 && compat::EvaluateAsInt(subs->getIdx(), res, compiler.getASTContext())
1901 && res == 0))
1902 {
1903 return;
1904 }
1905 }
1906 if (cont != ContentKind::Ascii) {
1907 report(
1908 DiagnosticsEngine::Warning,
1909 ("call of '%0' with string constant argument containing non-ASCII"
1910 " characters"),
1911 getMemberLocation(expr))
1912 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1913 }
1914 if (emb) {
1915 return;
1916 }
1917 std::string repl(replacement);
1918 checkEmpty(expr, callee, treatEmpty, n, &repl);
1919 reportChange(
1920 expr, ChangeKind::CharLen, callee->getQualifiedNameAsString(), repl,
1921 PassThrough::No, nonArray, nullptr, nullptr);
1922 }
1923
handleOUStringCtor(CallExpr const * expr,unsigned arg,FunctionDecl const * callee,bool explicitFunctionalCastNotation)1924 void StringConstant::handleOUStringCtor(
1925 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
1926 bool explicitFunctionalCastNotation)
1927 {
1928 handleOUStringCtor(expr, expr->getArg(arg), callee, explicitFunctionalCastNotation);
1929 }
1930
handleOStringCtor(CallExpr const * expr,unsigned arg,FunctionDecl const * callee,bool explicitFunctionalCastNotation)1931 void StringConstant::handleOStringCtor(
1932 CallExpr const * expr, unsigned arg, FunctionDecl const * callee,
1933 bool explicitFunctionalCastNotation)
1934 {
1935 handleOStringCtor(expr, expr->getArg(arg), callee, explicitFunctionalCastNotation);
1936 }
1937
handleOUStringCtor(Expr const * expr,Expr const * argExpr,FunctionDecl const * callee,bool explicitFunctionalCastNotation)1938 void StringConstant::handleOUStringCtor(
1939 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
1940 bool explicitFunctionalCastNotation)
1941 {
1942 handleStringCtor(expr, argExpr, callee, explicitFunctionalCastNotation, StringKind::Unicode);
1943 }
1944
handleOStringCtor(Expr const * expr,Expr const * argExpr,FunctionDecl const * callee,bool explicitFunctionalCastNotation)1945 void StringConstant::handleOStringCtor(
1946 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
1947 bool explicitFunctionalCastNotation)
1948 {
1949 handleStringCtor(expr, argExpr, callee, explicitFunctionalCastNotation, StringKind::Char);
1950 }
1951
handleStringCtor(Expr const * expr,Expr const * argExpr,FunctionDecl const * callee,bool explicitFunctionalCastNotation,StringKind stringKind)1952 void StringConstant::handleStringCtor(
1953 Expr const * expr, Expr const * argExpr, FunctionDecl const * callee,
1954 bool explicitFunctionalCastNotation, StringKind stringKind)
1955 {
1956 auto e0 = argExpr->IgnoreParenImpCasts();
1957 if (auto const e1 = dyn_cast<CXXMemberCallExpr>(e0)) {
1958 if (auto const e2 = dyn_cast<CXXConversionDecl>(e1->getMethodDecl())) {
1959 if (loplugin::TypeCheck(e2->getConversionType()).ClassOrStruct("basic_string_view")
1960 .StdNamespace())
1961 {
1962 e0 = e1->getImplicitObjectArgument()->IgnoreParenImpCasts();
1963 }
1964 }
1965 }
1966 auto e1 = dyn_cast<CXXFunctionalCastExpr>(e0);
1967 if (e1 == nullptr) {
1968 if (explicitFunctionalCastNotation) {
1969 return;
1970 }
1971 } else {
1972 e0 = e1->getSubExpr()->IgnoreParenImpCasts();
1973 }
1974 auto e2 = dyn_cast<CXXBindTemporaryExpr>(e0);
1975 if (e2 == nullptr) {
1976 return;
1977 }
1978 auto e3 = dyn_cast<CXXConstructExpr>(
1979 e2->getSubExpr()->IgnoreParenImpCasts());
1980 if (e3 == nullptr) {
1981 return;
1982 }
1983 if (!loplugin::DeclCheck(e3->getConstructor()).MemberFunction()
1984 .Class(stringKind == StringKind::Unicode ? "OUString" : "OString").Namespace("rtl").GlobalNamespace())
1985 {
1986 return;
1987 }
1988 if (e3->getNumArgs() == 0) {
1989 report(
1990 DiagnosticsEngine::Warning,
1991 ("in call of '%0', replace default-constructed 'OUString' with an"
1992 " empty string literal"),
1993 e3->getExprLoc())
1994 << callee->getQualifiedNameAsString() << expr->getSourceRange();
1995 return;
1996 }
1997 if (e3->getNumArgs() == 1
1998 && e3->getConstructor()->getNumParams() == 1
1999 && (loplugin::TypeCheck(
2000 e3->getConstructor()->getParamDecl(0)->getType())
2001 .Typedef(stringKind == StringKind::Unicode ? "sal_Unicode" : "char").GlobalNamespace()))
2002 {
2003 // It may not be easy to rewrite OUString(c), esp. given there is no
2004 // OUString ctor taking an OUStringChar arg, so don't warn there:
2005 if (!explicitFunctionalCastNotation) {
2006 report(
2007 DiagnosticsEngine::Warning,
2008 ("in call of '%0', replace 'OUString' constructed from a"
2009 " 'sal_Unicode' with an 'OUStringChar'"),
2010 e3->getExprLoc())
2011 << callee->getQualifiedNameAsString() << expr->getSourceRange();
2012 }
2013 return;
2014 }
2015 if (e3->getNumArgs() != 2) {
2016 return;
2017 }
2018 unsigned n;
2019 bool nonArray;
2020 ContentKind cont;
2021 bool emb;
2022 bool trm;
2023 if (!isStringConstant(
2024 e3->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray, &cont, &emb,
2025 &trm))
2026 {
2027 return;
2028 }
2029 //TODO: cont, emb, trm
2030 if (rewriter != nullptr) {
2031 auto loc1 = compat::getBeginLoc(e3);
2032 auto range = e3->getParenOrBraceRange();
2033 if (loc1.isFileID() && range.getBegin().isFileID()
2034 && range.getEnd().isFileID())
2035 {
2036 auto loc2 = range.getBegin();
2037 for (bool first = true;; first = false) {
2038 unsigned n = Lexer::MeasureTokenLength(
2039 loc2, compiler.getSourceManager(), compiler.getLangOpts());
2040 if (!first) {
2041 StringRef s(
2042 compiler.getSourceManager().getCharacterData(loc2), n);
2043 while (s.startswith("\\\n")) {
2044 s = s.drop_front(2);
2045 while (!s.empty()
2046 && (s.front() == ' ' || s.front() == '\t'
2047 || s.front() == '\n' || s.front() == '\v'
2048 || s.front() == '\f'))
2049 {
2050 s = s.drop_front(1);
2051 }
2052 }
2053 if (!(s.empty() || s.startswith("/*") || s.startswith("//")
2054 || s == "\\"))
2055 {
2056 break;
2057 }
2058 }
2059 loc2 = loc2.getLocWithOffset(std::max<unsigned>(n, 1));
2060 }
2061 auto loc3 = range.getEnd();
2062 for (;;) {
2063 auto l = Lexer::GetBeginningOfToken(
2064 loc3.getLocWithOffset(-1), compiler.getSourceManager(),
2065 compiler.getLangOpts());
2066 unsigned n = Lexer::MeasureTokenLength(
2067 l, compiler.getSourceManager(), compiler.getLangOpts());
2068 StringRef s(compiler.getSourceManager().getCharacterData(l), n);
2069 while (s.startswith("\\\n")) {
2070 s = s.drop_front(2);
2071 while (!s.empty()
2072 && (s.front() == ' ' || s.front() == '\t'
2073 || s.front() == '\n' || s.front() == '\v'
2074 || s.front() == '\f'))
2075 {
2076 s = s.drop_front(1);
2077 }
2078 }
2079 if (!(s.empty() || s.startswith("/*") || s.startswith("//")
2080 || s == "\\"))
2081 {
2082 break;
2083 }
2084 loc3 = l;
2085 }
2086 if (removeText(CharSourceRange(SourceRange(loc1, loc2), false))) {
2087 if (removeText(SourceRange(loc3, range.getEnd()))) {
2088 return;
2089 }
2090 report(DiagnosticsEngine::Fatal, "Corrupt rewrite", loc3)
2091 << expr->getSourceRange();
2092 return;
2093 }
2094 }
2095 }
2096 report(
2097 DiagnosticsEngine::Warning,
2098 ("in call of '%0', replace 'OUString' constructed from a string literal"
2099 " directly with the string literal"),
2100 e3->getExprLoc())
2101 << callee->getQualifiedNameAsString() << expr->getSourceRange();
2102 }
2103
handleFunArgOstring(CallExpr const * expr,unsigned arg,FunctionDecl const * callee)2104 void StringConstant::handleFunArgOstring(
2105 CallExpr const * expr, unsigned arg, FunctionDecl const * callee)
2106 {
2107 auto argExpr = expr->getArg(arg)->IgnoreParenImpCasts();
2108 unsigned n;
2109 bool nonArray;
2110 ContentKind cont;
2111 bool emb;
2112 bool trm;
2113 if (isStringConstant(argExpr, &n, &nonArray, &cont, &emb, &trm)) {
2114 if (cont != ContentKind::Ascii || emb) {
2115 return;
2116 }
2117 if (!trm) {
2118 report(
2119 DiagnosticsEngine::Warning,
2120 ("call of '%0' with string constant argument lacking a"
2121 " terminating NULL"),
2122 getMemberLocation(expr))
2123 << callee->getQualifiedNameAsString() << expr->getSourceRange();
2124 return;
2125 }
2126 std::string repl;
2127 checkEmpty(expr, callee, TreatEmpty::Error, n, &repl);
2128 if (nonArray) {
2129 report(
2130 DiagnosticsEngine::Warning,
2131 ("in call of '%0' with non-array string constant argument,"
2132 " turn the non-array string constant into an array"),
2133 getMemberLocation(expr))
2134 << callee->getQualifiedNameAsString() << expr->getSourceRange();
2135 }
2136 } else if (auto cexpr = lookForCXXConstructExpr(argExpr)) {
2137 auto classdecl = cexpr->getConstructor()->getParent();
2138 if (loplugin::DeclCheck(classdecl).Class("OString").Namespace("rtl")
2139 .GlobalNamespace())
2140 {
2141 switch (cexpr->getConstructor()->getNumParams()) {
2142 case 0:
2143 report(
2144 DiagnosticsEngine::Warning,
2145 ("in call of '%0', replace empty %1 constructor with empty"
2146 " string literal"),
2147 cexpr->getLocation())
2148 << callee->getQualifiedNameAsString() << classdecl
2149 << expr->getSourceRange();
2150 break;
2151 case 2:
2152 if (isStringConstant(
2153 cexpr->getArg(0)->IgnoreParenImpCasts(), &n, &nonArray,
2154 &cont, &emb, &trm))
2155 {
2156 APSInt res;
2157 if (compat::EvaluateAsInt(cexpr->getArg(1),
2158 res, compiler.getASTContext()))
2159 {
2160 if (res == n && !emb && trm) {
2161 report(
2162 DiagnosticsEngine::Warning,
2163 ("in call of '%0', elide explicit %1"
2164 " constructor%2"),
2165 cexpr->getLocation())
2166 << callee->getQualifiedNameAsString()
2167 << classdecl << adviseNonArray(nonArray)
2168 << expr->getSourceRange();
2169 }
2170 } else {
2171 if (emb) {
2172 report(
2173 DiagnosticsEngine::Warning,
2174 ("call of %0 constructor with string constant"
2175 " argument containing embedded NULLs"),
2176 cexpr->getLocation())
2177 << classdecl << cexpr->getSourceRange();
2178 return;
2179 }
2180 if (!trm) {
2181 report(
2182 DiagnosticsEngine::Warning,
2183 ("call of %0 constructor with string constant"
2184 " argument lacking a terminating NULL"),
2185 cexpr->getLocation())
2186 << classdecl << cexpr->getSourceRange();
2187 return;
2188 }
2189 report(
2190 DiagnosticsEngine::Warning,
2191 "in call of '%0', elide explicit %1 constructor%2",
2192 cexpr->getLocation())
2193 << callee->getQualifiedNameAsString() << classdecl
2194 << adviseNonArray(nonArray)
2195 << expr->getSourceRange();
2196 }
2197 }
2198 break;
2199 default:
2200 break;
2201 }
2202 }
2203 }
2204 }
2205
2206 loplugin::Plugin::Registration< StringConstant > X("stringconstant", true);
2207
2208 }
2209
2210 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2211