1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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 #ifndef LO_CLANG_SHARED_PLUGINS
11 
12 #include <algorithm>
13 #include <cassert>
14 #include <map>
15 
16 #include "check.hxx"
17 #include "compat.hxx"
18 #include "plugin.hxx"
19 
20 // Find cases where a variable of a OString/OUString type is initialized
21 // with a literal value (incl. as an empty string) and used only once.  Conservatively this only
22 // covers local non-static variables that are not defined outside of the loop (if any) in which they
23 // are used, as other cases may deliberately use the variable for performance (or even correctness,
24 // if addresses are taken and compared) reasons.
25 //
26 // For one, the historically heavy syntax for such uses of string literals
27 // (RTL_CONSTASCII_USTRINGPARAM etc.) probably explains many of these redundant variables, which can
28 // now be considered cargo-cult baggage.  For another, some of those variables are used as arguments
29 // to functions which also have more efficient overloads directly taking string literals.  And for
30 // yet another, some cases with default-initialized variables turned out to be effectively unused
31 // code that could be removed completely (d073cca5f7c04de3e1bcedda334d864e98ac7835 "Clean up dead
32 // code", 91345e7dde6100496a7c9e815b72b2821ae82bc2 "Clean up dead code",
33 // 868b0763ac47f765cb48c277897274a595b831d0 "Upcoming loplugin:elidestringvar: dbaccess" in
34 // dbaccess/source/ui/app/AppController.cxx, bde0aac4ccf7b830b5ef21d5b9e75e62aee6aaf9 "Clean up dead
35 // code", 354aefec42de856b4ab6201ada54a3a3c630b4bd "Upcoming loplugin:elidestringvar: cui" in
36 // cui/source/dialogs/SpellDialog.cxx).
37 
38 namespace
39 {
isStringType(QualType type)40 bool isStringType(QualType type)
41 {
42     loplugin::TypeCheck const c(type);
43     return c.Class("OString").Namespace("rtl").GlobalNamespace()
44            || c.Class("OUString").Namespace("rtl").GlobalNamespace();
45 }
46 
47 class ElideStringVar : public loplugin::FilteringPlugin<ElideStringVar>
48 {
49 public:
ElideStringVar(loplugin::InstantiationData const & data)50     explicit ElideStringVar(loplugin::InstantiationData const& data)
51         : FilteringPlugin(data)
52     {
53     }
54 
preRun()55     bool preRun() override { return compiler.getLangOpts().CPlusPlus; }
56 
postRun()57     void postRun() override
58     {
59         for (auto const& var : vars_)
60         {
61             if (!var.second.singleUse || *var.second.singleUse == nullptr)
62             {
63                 continue;
64             }
65             if (containsPreprocessingConditionalInclusion(SourceRange(
66                     compat::getBeginLoc(var.first), compat::getEndLoc(*var.second.singleUse))))
67             {
68                 // This is not perfect, as additional uses can be hidden in conditional blocks that
69                 // only start after the (would-be) single use (as was the case in
70                 // 3bc5057f9689e024957cfa898a221ee2c4c4afe7 "Upcoming loplugin:elidestringvar:
71                 // testtools" when built with --enable-debug, but where also fixing the hidden
72                 // additional use was trivial).  If this ever becomes a real problem, we can extend
73                 // the above check to cover more of the current function body's remainder.
74                 continue;
75             }
76             report(DiagnosticsEngine::Warning,
77                    "replace single use of literal %0 variable with a literal",
78                    (*var.second.singleUse)->getExprLoc())
79                 << var.first->getType() << (*var.second.singleUse)->getSourceRange();
80             report(DiagnosticsEngine::Note, "literal %0 variable defined here",
81                    var.first->getLocation())
82                 << var.first->getType() << var.first->getSourceRange();
83         }
84     }
85 
VisitVarDecl(VarDecl const * decl)86     bool VisitVarDecl(VarDecl const* decl)
87     {
88         if (ignoreLocation(decl))
89         {
90             return true;
91         }
92         if (!decl->isThisDeclarationADefinition())
93         {
94             return true;
95         }
96         if (isa<ParmVarDecl>(decl))
97         {
98             return true;
99         }
100         if (decl->getStorageDuration() != SD_Automatic)
101         {
102             return true;
103         }
104         if (!isStringType(decl->getType()))
105         {
106             return true;
107         }
108         if (!decl->hasInit())
109         {
110             return true;
111         }
112         auto const e1 = dyn_cast<CXXConstructExpr>(decl->getInit()->IgnoreParenImpCasts());
113         if (e1 == nullptr)
114         {
115             return true;
116         }
117         if (!isStringType(e1->getType()))
118         {
119             return true;
120         }
121         switch (e1->getNumArgs())
122         {
123             case 0:
124                 break;
125             case 1:
126             {
127                 auto const e2 = e1->getArg(0);
128                 loplugin::TypeCheck const c(e2->getType());
129                 if (c.Class("OStringLiteral").Namespace("rtl").GlobalNamespace()
130                     || c.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace())
131                 {
132                     break;
133                 }
134                 if (!e2->isValueDependent() && e2->isIntegerConstantExpr(compiler.getASTContext()))
135                 {
136                     break;
137                 }
138                 return true;
139             }
140             case 2:
141             {
142                 auto const e2 = e1->getArg(0);
143                 auto const t = e2->getType();
144                 if (!(t.isConstQualified() && t->isConstantArrayType()))
145                 {
146                     return true;
147                 }
148                 if (isa<AbstractConditionalOperator>(e2->IgnoreParenImpCasts()))
149                 {
150                     return true;
151                 }
152                 auto const e3 = e1->getArg(1);
153                 if (!(isa<CXXDefaultArgExpr>(e3)
154                       && loplugin::TypeCheck(e3->getType())
155                              .Struct("Dummy")
156                              .Namespace("libreoffice_internal")
157                              .Namespace("rtl")
158                              .GlobalNamespace()))
159                 {
160                     return true;
161                 }
162                 break;
163             }
164             default:
165                 return true;
166         }
167         auto const ok = vars_.emplace(decl, Data(getInnermostLoop()));
168         assert(ok.second);
169         (void)ok;
170         return true;
171     }
172 
VisitDeclRefExpr(DeclRefExpr const * expr)173     bool VisitDeclRefExpr(DeclRefExpr const* expr)
174     {
175         if (ignoreLocation(expr))
176         {
177             return true;
178         }
179         auto const var = dyn_cast<VarDecl>(expr->getDecl());
180         if (var == nullptr)
181         {
182             return true;
183         }
184         auto const i = vars_.find(var);
185         if (i == vars_.end())
186         {
187             return true;
188         }
189         i->second.singleUse
190             = i->second.singleUse || i->second.innermostLoop != getInnermostLoop() ? nullptr : expr;
191         return true;
192     }
193 
VisitMemberExpr(MemberExpr const * expr)194     bool VisitMemberExpr(MemberExpr const* expr)
195     {
196         if (ignoreLocation(expr))
197         {
198             return true;
199         }
200         auto const e = dyn_cast<DeclRefExpr>(expr->getBase()->IgnoreParenImpCasts());
201         if (e == nullptr)
202         {
203             return true;
204         }
205         auto const var = dyn_cast<VarDecl>(e->getDecl());
206         if (var == nullptr)
207         {
208             return true;
209         }
210         auto const i = vars_.find(var);
211         if (i == vars_.end())
212         {
213             return true;
214         }
215         i->second.singleUse = nullptr;
216         return true;
217     }
218 
VisitUnaryOperator(UnaryOperator const * expr)219     bool VisitUnaryOperator(UnaryOperator const* expr)
220     {
221         if (ignoreLocation(expr))
222         {
223             return true;
224         }
225         if (expr->getOpcode() != UO_AddrOf)
226         {
227             return true;
228         }
229         auto const e = dyn_cast<DeclRefExpr>(expr->getSubExpr()->IgnoreParenImpCasts());
230         if (e == nullptr)
231         {
232             return true;
233         }
234         auto const var = dyn_cast<VarDecl>(e->getDecl());
235         if (var == nullptr)
236         {
237             return true;
238         }
239         auto const i = vars_.find(var);
240         if (i == vars_.end())
241         {
242             return true;
243         }
244         i->second.singleUse = nullptr;
245         return true;
246     }
247 
VisitCallExpr(CallExpr const * expr)248     bool VisitCallExpr(CallExpr const* expr)
249     {
250         if (ignoreLocation(expr))
251         {
252             return true;
253         }
254         auto const fun = expr->getDirectCallee();
255         if (fun == nullptr)
256         {
257             return true;
258         }
259         unsigned const n = std::min(fun->getNumParams(), expr->getNumArgs());
260         for (unsigned i = 0; i != n; ++i)
261         {
262             if (!loplugin::TypeCheck(fun->getParamDecl(i)->getType())
263                      .LvalueReference()
264                      .NonConstVolatile())
265             {
266                 continue;
267             }
268             auto const e = dyn_cast<DeclRefExpr>(expr->getArg(i)->IgnoreParenImpCasts());
269             if (e == nullptr)
270             {
271                 continue;
272             }
273             auto const var = dyn_cast<VarDecl>(e->getDecl());
274             if (var == nullptr)
275             {
276                 continue;
277             }
278             auto const j = vars_.find(var);
279             if (j == vars_.end())
280             {
281                 continue;
282             }
283             j->second.singleUse = nullptr;
284         }
285         return true;
286     }
287 
VisitCXXConstructExpr(CXXConstructExpr const * expr)288     bool VisitCXXConstructExpr(CXXConstructExpr const* expr)
289     {
290         if (ignoreLocation(expr))
291         {
292             return true;
293         }
294         auto const ctor = expr->getConstructor();
295         unsigned const n = std::min(ctor->getNumParams(), expr->getNumArgs());
296         for (unsigned i = 0; i != n; ++i)
297         {
298             if (!loplugin::TypeCheck(ctor->getParamDecl(i)->getType())
299                      .LvalueReference()
300                      .NonConstVolatile())
301             {
302                 continue;
303             }
304             auto const e = dyn_cast<DeclRefExpr>(expr->getArg(i)->IgnoreParenImpCasts());
305             if (e == nullptr)
306             {
307                 continue;
308             }
309             auto const var = dyn_cast<VarDecl>(e->getDecl());
310             if (var == nullptr)
311             {
312                 continue;
313             }
314             auto const j = vars_.find(var);
315             if (j == vars_.end())
316             {
317                 continue;
318             }
319             j->second.singleUse = nullptr;
320         }
321         return true;
322     }
323 
TraverseWhileStmt(WhileStmt * stmt)324     bool TraverseWhileStmt(WhileStmt* stmt)
325     {
326         bool ret = true;
327         if (PreTraverseWhileStmt(stmt))
328         {
329             ret = FilteringPlugin::TraverseWhileStmt(stmt);
330             PostTraverseWhileStmt(stmt, ret);
331         }
332         return ret;
333     }
334 
PreTraverseWhileStmt(WhileStmt * stmt)335     bool PreTraverseWhileStmt(WhileStmt* stmt)
336     {
337         innermostLoop_.push(stmt);
338         return true;
339     }
340 
PostTraverseWhileStmt(WhileStmt * stmt,bool)341     bool PostTraverseWhileStmt(WhileStmt* stmt, bool)
342     {
343         assert(!innermostLoop_.empty());
344         assert(innermostLoop_.top() == stmt);
345         (void)stmt;
346         innermostLoop_.pop();
347         return true;
348     }
349 
TraverseDoStmt(DoStmt * stmt)350     bool TraverseDoStmt(DoStmt* stmt)
351     {
352         bool ret = true;
353         if (PreTraverseDoStmt(stmt))
354         {
355             ret = FilteringPlugin::TraverseDoStmt(stmt);
356             PostTraverseDoStmt(stmt, ret);
357         }
358         return ret;
359     }
360 
PreTraverseDoStmt(DoStmt * stmt)361     bool PreTraverseDoStmt(DoStmt* stmt)
362     {
363         innermostLoop_.push(stmt);
364         return true;
365     }
366 
PostTraverseDoStmt(DoStmt * stmt,bool)367     bool PostTraverseDoStmt(DoStmt* stmt, bool)
368     {
369         assert(!innermostLoop_.empty());
370         assert(innermostLoop_.top() == stmt);
371         (void)stmt;
372         innermostLoop_.pop();
373         return true;
374     }
375 
TraverseForStmt(ForStmt * stmt)376     bool TraverseForStmt(ForStmt* stmt)
377     {
378         bool ret = true;
379         if (PreTraverseForStmt(stmt))
380         {
381             ret = FilteringPlugin::TraverseForStmt(stmt);
382             PostTraverseForStmt(stmt, ret);
383         }
384         return ret;
385     }
386 
PreTraverseForStmt(ForStmt * stmt)387     bool PreTraverseForStmt(ForStmt* stmt)
388     {
389         innermostLoop_.push(stmt);
390         return true;
391     }
392 
PostTraverseForStmt(ForStmt * stmt,bool)393     bool PostTraverseForStmt(ForStmt* stmt, bool)
394     {
395         assert(!innermostLoop_.empty());
396         assert(innermostLoop_.top() == stmt);
397         (void)stmt;
398         innermostLoop_.pop();
399         return true;
400     }
401 
TraverseCXXForRangeStmt(CXXForRangeStmt * stmt)402     bool TraverseCXXForRangeStmt(CXXForRangeStmt* stmt)
403     {
404         bool ret = true;
405         if (PreTraverseCXXForRangeStmt(stmt))
406         {
407             ret = FilteringPlugin::TraverseCXXForRangeStmt(stmt);
408             PostTraverseCXXForRangeStmt(stmt, ret);
409         }
410         return ret;
411     }
412 
PreTraverseCXXForRangeStmt(CXXForRangeStmt * stmt)413     bool PreTraverseCXXForRangeStmt(CXXForRangeStmt* stmt)
414     {
415         innermostLoop_.push(stmt);
416         return true;
417     }
418 
PostTraverseCXXForRangeStmt(CXXForRangeStmt * stmt,bool)419     bool PostTraverseCXXForRangeStmt(CXXForRangeStmt* stmt, bool)
420     {
421         assert(!innermostLoop_.empty());
422         assert(innermostLoop_.top() == stmt);
423         (void)stmt;
424         innermostLoop_.pop();
425         return true;
426     }
427 
428 private:
run()429     void run() override
430     {
431         if (preRun() && TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()))
432         {
433             postRun();
434         }
435     }
436 
getInnermostLoop() const437     Stmt const* getInnermostLoop() const
438     {
439         return innermostLoop_.empty() ? nullptr : innermostLoop_.top();
440     }
441 
442     struct Data
443     {
Data__anonc6f293110111::ElideStringVar::Data444         Data(Stmt const* theInnermostLoop)
445             : innermostLoop(theInnermostLoop)
446         {
447         }
448         Stmt const* innermostLoop;
449         llvm::Optional<Expr const*> singleUse;
450     };
451 
452     std::stack<Stmt const*> innermostLoop_;
453     std::map<VarDecl const*, Data> vars_;
454 };
455 
456 loplugin::Plugin::Registration<ElideStringVar> elidestringvar("elidestringvar");
457 }
458 
459 #endif
460 
461 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
462