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