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
21 // Find cases where a variable of a OString/OUString type is initialized
22 // with a literal value (incl. as an empty string) and used only once. Conservatively this only
23 // covers local non-static variables that are not defined outside of the loop (if any) in which they
24 // are used, as other cases may deliberately use the variable for performance (or even correctness,
25 // if addresses are taken and compared) reasons.
27 // For one, the historically heavy syntax for such uses of string literals
28 // (RTL_CONSTASCII_USTRINGPARAM etc.) probably explains many of these redundant variables, which can
29 // now be considered cargo-cult baggage. For another, some of those variables are used as arguments
30 // to functions which also have more efficient overloads directly taking string literals. And for
31 // yet another, some cases with default-initialized variables turned out to be effectively unused
32 // code that could be removed completely (d073cca5f7c04de3e1bcedda334d864e98ac7835 "Clean up dead
33 // code", 91345e7dde6100496a7c9e815b72b2821ae82bc2 "Clean up dead code",
34 // 868b0763ac47f765cb48c277897274a595b831d0 "Upcoming loplugin:elidestringvar: dbaccess" in
35 // dbaccess/source/ui/app/AppController.cxx, bde0aac4ccf7b830b5ef21d5b9e75e62aee6aaf9 "Clean up dead
36 // code", 354aefec42de856b4ab6201ada54a3a3c630b4bd "Upcoming loplugin:elidestringvar: cui" in
37 // cui/source/dialogs/SpellDialog.cxx).
41 bool isStringType(QualType type
)
43 loplugin::TypeCheck
const c(type
);
44 return c
.Class("OString").Namespace("rtl").GlobalNamespace()
45 || c
.Class("OUString").Namespace("rtl").GlobalNamespace();
48 class ElideStringVar
: public loplugin::FilteringPlugin
<ElideStringVar
>
51 explicit ElideStringVar(loplugin::InstantiationData
const& data
)
52 : FilteringPlugin(data
)
56 bool preRun() override
{ return compiler
.getLangOpts().CPlusPlus
; }
58 void postRun() override
60 for (auto const& var
: vars_
)
62 if (!var
.second
.singleUse
|| *var
.second
.singleUse
== nullptr)
66 if (containsPreprocessingConditionalInclusion(
67 SourceRange(var
.first
->getBeginLoc(), (*var
.second
.singleUse
)->getEndLoc())))
69 // This is not perfect, as additional uses can be hidden in conditional blocks that
70 // only start after the (would-be) single use (as was the case in
71 // 3bc5057f9689e024957cfa898a221ee2c4c4afe7 "Upcoming loplugin:elidestringvar:
72 // testtools" when built with --enable-debug, but where also fixing the hidden
73 // additional use was trivial). If this ever becomes a real problem, we can extend
74 // the above check to cover more of the current function body's remainder.
77 report(DiagnosticsEngine::Warning
,
78 "replace single use of literal %0 variable with a literal",
79 (*var
.second
.singleUse
)->getExprLoc())
80 << var
.first
->getType() << (*var
.second
.singleUse
)->getSourceRange();
81 report(DiagnosticsEngine::Note
, "literal %0 variable defined here",
82 var
.first
->getLocation())
83 << var
.first
->getType() << var
.first
->getSourceRange();
87 bool VisitVarDecl(VarDecl
const* decl
)
89 if (ignoreLocation(decl
))
93 if (!decl
->isThisDeclarationADefinition())
97 if (isa
<ParmVarDecl
>(decl
))
101 if (decl
->getStorageDuration() != SD_Automatic
)
105 if (!isStringType(decl
->getType()))
109 if (!decl
->hasInit())
113 auto const e1
= dyn_cast
<CXXConstructExpr
>(decl
->getInit()->IgnoreParenImpCasts());
118 if (!isStringType(e1
->getType()))
122 switch (e1
->getNumArgs())
128 auto const e2
= e1
->getArg(0);
129 loplugin::TypeCheck
const c(e2
->getType());
130 if (c
.Class("OStringLiteral").Namespace("rtl").GlobalNamespace()
131 || c
.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace())
135 if (!e2
->isValueDependent() && e2
->isIntegerConstantExpr(compiler
.getASTContext()))
143 auto const e2
= e1
->getArg(0);
144 auto const t
= e2
->getType();
145 if (!(t
.isConstQualified() && t
->isConstantArrayType()))
149 if (isa
<AbstractConditionalOperator
>(e2
->IgnoreParenImpCasts()))
153 auto const e3
= e1
->getArg(1);
154 if (!(isa
<CXXDefaultArgExpr
>(e3
)
155 && loplugin::TypeCheck(e3
->getType())
157 .Namespace("libreoffice_internal")
168 auto const ok
= vars_
.emplace(decl
, Data(getInnermostLoop()));
174 bool VisitDeclRefExpr(DeclRefExpr
const* expr
)
176 if (ignoreLocation(expr
))
180 auto const var
= dyn_cast
<VarDecl
>(expr
->getDecl());
185 auto const i
= vars_
.find(var
);
186 if (i
== vars_
.end())
191 = i
->second
.singleUse
|| i
->second
.innermostLoop
!= getInnermostLoop() ? nullptr : expr
;
195 bool VisitMemberExpr(MemberExpr
const* expr
)
197 if (ignoreLocation(expr
))
201 auto const e
= dyn_cast
<DeclRefExpr
>(expr
->getBase()->IgnoreParenImpCasts());
206 auto const var
= dyn_cast
<VarDecl
>(e
->getDecl());
211 auto const i
= vars_
.find(var
);
212 if (i
== vars_
.end())
216 i
->second
.singleUse
= nullptr;
220 bool VisitUnaryOperator(UnaryOperator
const* expr
)
222 if (ignoreLocation(expr
))
226 if (expr
->getOpcode() != UO_AddrOf
)
230 auto const e
= dyn_cast
<DeclRefExpr
>(expr
->getSubExpr()->IgnoreParenImpCasts());
235 auto const var
= dyn_cast
<VarDecl
>(e
->getDecl());
240 auto const i
= vars_
.find(var
);
241 if (i
== vars_
.end())
245 i
->second
.singleUse
= nullptr;
249 bool VisitCallExpr(CallExpr
const* expr
)
251 if (ignoreLocation(expr
))
255 auto const fun
= expr
->getDirectCallee();
260 unsigned const n
= std::min(fun
->getNumParams(), expr
->getNumArgs());
261 for (unsigned i
= 0; i
!= n
; ++i
)
263 if (!loplugin::TypeCheck(fun
->getParamDecl(i
)->getType())
269 auto const e
= dyn_cast
<DeclRefExpr
>(expr
->getArg(i
)->IgnoreParenImpCasts());
274 auto const var
= dyn_cast
<VarDecl
>(e
->getDecl());
279 auto const j
= vars_
.find(var
);
280 if (j
== vars_
.end())
284 j
->second
.singleUse
= nullptr;
289 bool VisitCXXConstructExpr(CXXConstructExpr
const* expr
)
291 if (ignoreLocation(expr
))
295 auto const ctor
= expr
->getConstructor();
296 unsigned const n
= std::min(ctor
->getNumParams(), expr
->getNumArgs());
297 for (unsigned i
= 0; i
!= n
; ++i
)
299 if (!loplugin::TypeCheck(ctor
->getParamDecl(i
)->getType())
305 auto const e
= dyn_cast
<DeclRefExpr
>(expr
->getArg(i
)->IgnoreParenImpCasts());
310 auto const var
= dyn_cast
<VarDecl
>(e
->getDecl());
315 auto const j
= vars_
.find(var
);
316 if (j
== vars_
.end())
320 j
->second
.singleUse
= nullptr;
325 bool TraverseWhileStmt(WhileStmt
* stmt
)
328 if (PreTraverseWhileStmt(stmt
))
330 ret
= FilteringPlugin::TraverseWhileStmt(stmt
);
331 PostTraverseWhileStmt(stmt
, ret
);
336 bool PreTraverseWhileStmt(WhileStmt
* stmt
)
338 innermostLoop_
.push(stmt
);
342 bool PostTraverseWhileStmt(WhileStmt
* stmt
, bool)
344 assert(!innermostLoop_
.empty());
345 assert(innermostLoop_
.top() == stmt
);
347 innermostLoop_
.pop();
351 bool TraverseDoStmt(DoStmt
* stmt
)
354 if (PreTraverseDoStmt(stmt
))
356 ret
= FilteringPlugin::TraverseDoStmt(stmt
);
357 PostTraverseDoStmt(stmt
, ret
);
362 bool PreTraverseDoStmt(DoStmt
* stmt
)
364 innermostLoop_
.push(stmt
);
368 bool PostTraverseDoStmt(DoStmt
* stmt
, bool)
370 assert(!innermostLoop_
.empty());
371 assert(innermostLoop_
.top() == stmt
);
373 innermostLoop_
.pop();
377 bool TraverseForStmt(ForStmt
* stmt
)
380 if (PreTraverseForStmt(stmt
))
382 ret
= FilteringPlugin::TraverseForStmt(stmt
);
383 PostTraverseForStmt(stmt
, ret
);
388 bool PreTraverseForStmt(ForStmt
* stmt
)
390 innermostLoop_
.push(stmt
);
394 bool PostTraverseForStmt(ForStmt
* stmt
, bool)
396 assert(!innermostLoop_
.empty());
397 assert(innermostLoop_
.top() == stmt
);
399 innermostLoop_
.pop();
403 bool TraverseCXXForRangeStmt(CXXForRangeStmt
* stmt
)
406 if (PreTraverseCXXForRangeStmt(stmt
))
408 ret
= FilteringPlugin::TraverseCXXForRangeStmt(stmt
);
409 PostTraverseCXXForRangeStmt(stmt
, ret
);
414 bool PreTraverseCXXForRangeStmt(CXXForRangeStmt
* stmt
)
416 innermostLoop_
.push(stmt
);
420 bool PostTraverseCXXForRangeStmt(CXXForRangeStmt
* stmt
, bool)
422 assert(!innermostLoop_
.empty());
423 assert(innermostLoop_
.top() == stmt
);
425 innermostLoop_
.pop();
432 if (preRun() && TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl()))
438 Stmt
const* getInnermostLoop() const
440 return innermostLoop_
.empty() ? nullptr : innermostLoop_
.top();
445 Data(Stmt
const* theInnermostLoop
)
446 : innermostLoop(theInnermostLoop
)
449 Stmt
const* innermostLoop
;
450 compat::optional
<Expr
const*> singleUse
;
453 std::stack
<Stmt
const*> innermostLoop_
;
454 std::map
<VarDecl
const*, Data
> vars_
;
457 loplugin::Plugin::Registration
<ElideStringVar
> elidestringvar("elidestringvar");
462 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */