1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #ifndef LO_CLANG_SHARED_PLUGINS
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.
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).
40 bool isStringType(QualType type
)
42 loplugin::TypeCheck
const c(type
);
43 return c
.Class("OString").Namespace("rtl").GlobalNamespace()
44 || c
.Class("OUString").Namespace("rtl").GlobalNamespace();
47 class ElideStringVar
: public loplugin::FilteringPlugin
<ElideStringVar
>
50 explicit ElideStringVar(loplugin::InstantiationData
const& data
)
51 : FilteringPlugin(data
)
55 bool preRun() override
{ return compiler
.getLangOpts().CPlusPlus
; }
57 void postRun() override
59 for (auto const& var
: vars_
)
61 if (!var
.second
.singleUse
|| *var
.second
.singleUse
== nullptr)
65 if (containsPreprocessingConditionalInclusion(
66 SourceRange(var
.first
->getBeginLoc(), (*var
.second
.singleUse
)->getEndLoc())))
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.
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();
86 bool VisitVarDecl(VarDecl
const* decl
)
88 if (ignoreLocation(decl
))
92 if (!decl
->isThisDeclarationADefinition())
96 if (isa
<ParmVarDecl
>(decl
))
100 if (decl
->getStorageDuration() != SD_Automatic
)
104 if (!isStringType(decl
->getType()))
108 if (!decl
->hasInit())
112 auto const e1
= dyn_cast
<CXXConstructExpr
>(decl
->getInit()->IgnoreParenImpCasts());
117 if (!isStringType(e1
->getType()))
121 switch (e1
->getNumArgs())
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())
134 if (!e2
->isValueDependent() && e2
->isIntegerConstantExpr(compiler
.getASTContext()))
142 auto const e2
= e1
->getArg(0);
143 auto const t
= e2
->getType();
144 if (!(t
.isConstQualified() && t
->isConstantArrayType()))
148 if (isa
<AbstractConditionalOperator
>(e2
->IgnoreParenImpCasts()))
152 auto const e3
= e1
->getArg(1);
153 if (!(isa
<CXXDefaultArgExpr
>(e3
)
154 && loplugin::TypeCheck(e3
->getType())
156 .Namespace("libreoffice_internal")
167 auto const ok
= vars_
.emplace(decl
, Data(getInnermostLoop()));
173 bool VisitDeclRefExpr(DeclRefExpr
const* expr
)
175 if (ignoreLocation(expr
))
179 auto const var
= dyn_cast
<VarDecl
>(expr
->getDecl());
184 auto const i
= vars_
.find(var
);
185 if (i
== vars_
.end())
190 = i
->second
.singleUse
|| i
->second
.innermostLoop
!= getInnermostLoop() ? nullptr : expr
;
194 bool VisitMemberExpr(MemberExpr
const* expr
)
196 if (ignoreLocation(expr
))
200 auto const e
= dyn_cast
<DeclRefExpr
>(expr
->getBase()->IgnoreParenImpCasts());
205 auto const var
= dyn_cast
<VarDecl
>(e
->getDecl());
210 auto const i
= vars_
.find(var
);
211 if (i
== vars_
.end())
215 i
->second
.singleUse
= nullptr;
219 bool VisitUnaryOperator(UnaryOperator
const* expr
)
221 if (ignoreLocation(expr
))
225 if (expr
->getOpcode() != UO_AddrOf
)
229 auto const e
= dyn_cast
<DeclRefExpr
>(expr
->getSubExpr()->IgnoreParenImpCasts());
234 auto const var
= dyn_cast
<VarDecl
>(e
->getDecl());
239 auto const i
= vars_
.find(var
);
240 if (i
== vars_
.end())
244 i
->second
.singleUse
= nullptr;
248 bool VisitCallExpr(CallExpr
const* expr
)
250 if (ignoreLocation(expr
))
254 auto const fun
= expr
->getDirectCallee();
259 unsigned const n
= std::min(fun
->getNumParams(), expr
->getNumArgs());
260 for (unsigned i
= 0; i
!= n
; ++i
)
262 if (!loplugin::TypeCheck(fun
->getParamDecl(i
)->getType())
268 auto const e
= dyn_cast
<DeclRefExpr
>(expr
->getArg(i
)->IgnoreParenImpCasts());
273 auto const var
= dyn_cast
<VarDecl
>(e
->getDecl());
278 auto const j
= vars_
.find(var
);
279 if (j
== vars_
.end())
283 j
->second
.singleUse
= nullptr;
288 bool VisitCXXConstructExpr(CXXConstructExpr
const* expr
)
290 if (ignoreLocation(expr
))
294 auto const ctor
= expr
->getConstructor();
295 unsigned const n
= std::min(ctor
->getNumParams(), expr
->getNumArgs());
296 for (unsigned i
= 0; i
!= n
; ++i
)
298 if (!loplugin::TypeCheck(ctor
->getParamDecl(i
)->getType())
304 auto const e
= dyn_cast
<DeclRefExpr
>(expr
->getArg(i
)->IgnoreParenImpCasts());
309 auto const var
= dyn_cast
<VarDecl
>(e
->getDecl());
314 auto const j
= vars_
.find(var
);
315 if (j
== vars_
.end())
319 j
->second
.singleUse
= nullptr;
324 bool TraverseWhileStmt(WhileStmt
* stmt
)
327 if (PreTraverseWhileStmt(stmt
))
329 ret
= FilteringPlugin::TraverseWhileStmt(stmt
);
330 PostTraverseWhileStmt(stmt
, ret
);
335 bool PreTraverseWhileStmt(WhileStmt
* stmt
)
337 innermostLoop_
.push(stmt
);
341 bool PostTraverseWhileStmt(WhileStmt
* stmt
, bool)
343 assert(!innermostLoop_
.empty());
344 assert(innermostLoop_
.top() == stmt
);
346 innermostLoop_
.pop();
350 bool TraverseDoStmt(DoStmt
* stmt
)
353 if (PreTraverseDoStmt(stmt
))
355 ret
= FilteringPlugin::TraverseDoStmt(stmt
);
356 PostTraverseDoStmt(stmt
, ret
);
361 bool PreTraverseDoStmt(DoStmt
* stmt
)
363 innermostLoop_
.push(stmt
);
367 bool PostTraverseDoStmt(DoStmt
* stmt
, bool)
369 assert(!innermostLoop_
.empty());
370 assert(innermostLoop_
.top() == stmt
);
372 innermostLoop_
.pop();
376 bool TraverseForStmt(ForStmt
* stmt
)
379 if (PreTraverseForStmt(stmt
))
381 ret
= FilteringPlugin::TraverseForStmt(stmt
);
382 PostTraverseForStmt(stmt
, ret
);
387 bool PreTraverseForStmt(ForStmt
* stmt
)
389 innermostLoop_
.push(stmt
);
393 bool PostTraverseForStmt(ForStmt
* stmt
, bool)
395 assert(!innermostLoop_
.empty());
396 assert(innermostLoop_
.top() == stmt
);
398 innermostLoop_
.pop();
402 bool TraverseCXXForRangeStmt(CXXForRangeStmt
* stmt
)
405 if (PreTraverseCXXForRangeStmt(stmt
))
407 ret
= FilteringPlugin::TraverseCXXForRangeStmt(stmt
);
408 PostTraverseCXXForRangeStmt(stmt
, ret
);
413 bool PreTraverseCXXForRangeStmt(CXXForRangeStmt
* stmt
)
415 innermostLoop_
.push(stmt
);
419 bool PostTraverseCXXForRangeStmt(CXXForRangeStmt
* stmt
, bool)
421 assert(!innermostLoop_
.empty());
422 assert(innermostLoop_
.top() == stmt
);
424 innermostLoop_
.pop();
431 if (preRun() && TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl()))
437 Stmt
const* getInnermostLoop() const
439 return innermostLoop_
.empty() ? nullptr : innermostLoop_
.top();
444 Data(Stmt
const* theInnermostLoop
)
445 : innermostLoop(theInnermostLoop
)
448 Stmt
const* innermostLoop
;
449 compat::optional
<Expr
const*> singleUse
;
452 std::stack
<Stmt
const*> innermostLoop_
;
453 std::map
<VarDecl
const*, Data
> vars_
;
456 loplugin::Plugin::Registration
<ElideStringVar
> elidestringvar("elidestringvar");
461 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */