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