1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
16 #include <unordered_map>
17 #include <unordered_set>
21 #include "clang/AST/CXXInheritance.h"
22 #include "clang/AST/StmtVisitor.h"
24 // Original idea from mike kaganski.
25 // Look for variables that can have their scoped reduced, which makes the code easier to read.
27 // TODO when dealing with vars that are referenced in multiple child blocks, the check is very primitive
28 // and could be greatly improved.
32 class ReduceVarScope
: public loplugin::FilteringPlugin
<ReduceVarScope
>
35 explicit ReduceVarScope(loplugin::InstantiationData
const& data
)
36 : FilteringPlugin(data
)
40 bool preRun() override
42 if (!compiler
.getLangOpts().CPlusPlus
)
44 // ignore some files with problematic macros
45 std::string
fn(handler
.getMainFileName());
46 loplugin::normalizeDotDotInFilePath(fn
);
47 // some declarations look better all together
48 if (fn
== SRCDIR
"/package/source/manifest/ManifestExport.cxx")
50 // storing pointer to OUString internal data
51 if (fn
== SRCDIR
"/connectivity/source/drivers/odbc/ODatabaseMetaDataResultSet.cxx"
52 || fn
== SRCDIR
"/sc/source/filter/excel/xestyle.cxx"
53 || fn
== SRCDIR
"/sw/source/filter/html/htmlflywriter.cxx"
54 || fn
== SRCDIR
"/unoxml/source/dom/element.cxx"
55 || fn
== SRCDIR
"/unoxml/source/dom/document.cxx"
56 || fn
== SRCDIR
"/sd/source/filter/eppt/pptx-animations.cxx")
58 if (fn
== SRCDIR
"/sal/qa/rtl/strings/nonconstarray.cxx")
63 virtual void run() override
68 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
72 virtual void postRun() override
74 for (auto const& pair
: maVarDeclMap
)
76 auto varDecl
= pair
.first
;
77 auto const& depthInfo
= pair
.second
;
78 if (depthInfo
.maDeclBlockPath
.size() == depthInfo
.maCommonBlockPath
.size())
80 if (maVarDeclToIgnoreSet
.find(varDecl
) != maVarDeclToIgnoreSet
.end())
82 auto it
= maVarUseSourceRangeMap
.find(varDecl
);
83 if (it
== maVarUseSourceRangeMap
.end())
85 report(DiagnosticsEngine::Warning
, "can reduce scope of var", varDecl
->getLocation())
86 << varDecl
->getSourceRange();
87 for (SourceRange
const& useRange
: it
->second
)
88 report(DiagnosticsEngine::Note
, "used here", useRange
.getBegin()) << useRange
;
92 bool VisitUnaryOperator(UnaryOperator
const* expr
)
94 // if we take the address of it
95 UnaryOperator::Opcode op
= expr
->getOpcode();
97 recordIgnore(expr
->getSubExpr());
101 bool VisitDeclRefExpr(const DeclRefExpr
*);
102 bool VisitVarDecl(const VarDecl
*);
103 bool VisitLambdaExpr(const LambdaExpr
*);
105 bool PreTraverseFunctionDecl(FunctionDecl
*);
106 bool PostTraverseFunctionDecl(FunctionDecl
*, bool);
107 bool TraverseFunctionDecl(FunctionDecl
*);
109 bool PreTraverseCompoundStmt(CompoundStmt
*);
110 bool PostTraverseCompoundStmt(CompoundStmt
*, bool);
111 bool TraverseCompoundStmt(CompoundStmt
*);
113 bool PreTraverseWhileStmt(WhileStmt
*);
114 bool PostTraverseWhileStmt(WhileStmt
*, bool);
115 bool TraverseWhileStmt(WhileStmt
*);
117 bool PreTraverseDoStmt(DoStmt
*);
118 bool PostTraverseDoStmt(DoStmt
*, bool);
119 bool TraverseDoStmt(DoStmt
*);
121 bool PreTraverseCXXForRangeStmt(CXXForRangeStmt
*);
122 bool PostTraverseCXXForRangeStmt(CXXForRangeStmt
*, bool);
123 bool TraverseCXXForRangeStmt(CXXForRangeStmt
*);
125 bool PreTraverseForStmt(ForStmt
*);
126 bool PostTraverseForStmt(ForStmt
*, bool);
127 bool TraverseForStmt(ForStmt
*);
129 bool PreTraverseSwitchStmt(SwitchStmt
*);
130 bool PostTraverseSwitchStmt(SwitchStmt
*, bool);
131 bool TraverseSwitchStmt(SwitchStmt
*);
136 unsigned int mnFirstDepth
= 0;
137 unsigned int mnFirstLoopDepth
= 0;
138 std::vector
<unsigned int> maDeclBlockPath
= {};
139 std::vector
<unsigned int> maCommonBlockPath
= {};
141 std::unordered_map
<VarDecl
const*, DepthInfo
> maVarDeclMap
; // varDecl->depth
142 std::unordered_set
<VarDecl
const*> maVarDeclToIgnoreSet
;
143 std::map
<VarDecl
const*, std::vector
<SourceRange
>> maVarUseSourceRangeMap
;
144 std::vector
<unsigned int> maCurrentBlockPath
;
145 unsigned int mnCurrentDepth
= 0;
146 unsigned int mnCurrentLoopDepth
= 0;
147 static unsigned int gnBlockId
;
149 bool isTypeOK(QualType qt
);
150 bool isInitConstant(const VarDecl
* varDecl
);
152 void recordIgnore(Expr
const* expr
)
156 expr
= expr
->IgnoreParenImpCasts();
157 if (auto const e
= dyn_cast
<MemberExpr
>(expr
))
159 if (isa
<FieldDecl
>(e
->getMemberDecl()))
165 if (auto const e
= dyn_cast
<ArraySubscriptExpr
>(expr
))
170 if (auto const e
= dyn_cast
<BinaryOperator
>(expr
))
172 if (e
->getOpcode() == BO_PtrMemD
)
180 auto const dre
= dyn_cast
<DeclRefExpr
>(expr
);
183 auto const var
= dyn_cast
<VarDecl
>(dre
->getDecl());
186 maVarDeclToIgnoreSet
.insert(var
);
190 unsigned int ReduceVarScope::gnBlockId
= 0;
192 bool ReduceVarScope::PreTraverseFunctionDecl(FunctionDecl
* functionDecl
)
194 // Ignore functions that contains #ifdef-ery, can be quite tricky
195 // to make useful changes when this plugin fires in such functions
196 if (containsPreprocessingConditionalInclusion(functionDecl
->getSourceRange()))
201 bool ReduceVarScope::PostTraverseFunctionDecl(FunctionDecl
*, bool) { return true; }
203 bool ReduceVarScope::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
206 if (PreTraverseFunctionDecl(functionDecl
))
208 ret
= FilteringPlugin::TraverseFunctionDecl(functionDecl
);
209 PostTraverseFunctionDecl(functionDecl
, ret
);
214 bool ReduceVarScope::PreTraverseCompoundStmt(CompoundStmt
*)
216 assert(mnCurrentDepth
!= std::numeric_limits
<unsigned int>::max());
219 maCurrentBlockPath
.push_back(gnBlockId
);
223 bool ReduceVarScope::PostTraverseCompoundStmt(CompoundStmt
*, bool)
225 assert(mnCurrentDepth
!= 0);
227 maCurrentBlockPath
.pop_back();
231 bool ReduceVarScope::TraverseCompoundStmt(CompoundStmt
* decl
)
234 if (PreTraverseCompoundStmt(decl
))
236 ret
= FilteringPlugin::TraverseCompoundStmt(decl
);
237 PostTraverseCompoundStmt(decl
, ret
);
242 bool ReduceVarScope::PreTraverseWhileStmt(WhileStmt
*)
244 assert(mnCurrentLoopDepth
!= std::numeric_limits
<unsigned int>::max());
245 ++mnCurrentLoopDepth
;
249 bool ReduceVarScope::PostTraverseWhileStmt(WhileStmt
*, bool)
251 assert(mnCurrentLoopDepth
!= 0);
252 --mnCurrentLoopDepth
;
256 bool ReduceVarScope::TraverseWhileStmt(WhileStmt
* decl
)
259 if (PreTraverseWhileStmt(decl
))
261 ret
= FilteringPlugin::TraverseWhileStmt(decl
);
262 PostTraverseWhileStmt(decl
, ret
);
267 bool ReduceVarScope::PreTraverseDoStmt(DoStmt
*)
269 assert(mnCurrentLoopDepth
!= std::numeric_limits
<unsigned int>::max());
270 ++mnCurrentLoopDepth
;
274 bool ReduceVarScope::PostTraverseDoStmt(DoStmt
*, bool)
276 assert(mnCurrentLoopDepth
!= 0);
277 --mnCurrentLoopDepth
;
281 bool ReduceVarScope::TraverseDoStmt(DoStmt
* decl
)
284 if (PreTraverseDoStmt(decl
))
286 ret
= FilteringPlugin::TraverseDoStmt(decl
);
287 PostTraverseDoStmt(decl
, ret
);
292 bool ReduceVarScope::PreTraverseSwitchStmt(SwitchStmt
*)
294 assert(mnCurrentLoopDepth
!= std::numeric_limits
<unsigned int>::max());
295 ++mnCurrentLoopDepth
;
299 bool ReduceVarScope::PostTraverseSwitchStmt(SwitchStmt
*, bool)
301 assert(mnCurrentLoopDepth
!= 0);
302 --mnCurrentLoopDepth
;
306 // Consider a switch to be a loop, because weird things happen inside it
307 bool ReduceVarScope::TraverseSwitchStmt(SwitchStmt
* decl
)
310 if (PreTraverseSwitchStmt(decl
))
312 ret
= FilteringPlugin::TraverseSwitchStmt(decl
);
313 PostTraverseSwitchStmt(decl
, ret
);
318 bool ReduceVarScope::PreTraverseCXXForRangeStmt(CXXForRangeStmt
*)
320 assert(mnCurrentLoopDepth
!= std::numeric_limits
<unsigned int>::max());
321 ++mnCurrentLoopDepth
;
325 bool ReduceVarScope::PostTraverseCXXForRangeStmt(CXXForRangeStmt
*, bool)
327 assert(mnCurrentLoopDepth
!= 0);
328 --mnCurrentLoopDepth
;
332 bool ReduceVarScope::TraverseCXXForRangeStmt(CXXForRangeStmt
* decl
)
335 if (PreTraverseCXXForRangeStmt(decl
))
337 ret
= FilteringPlugin::TraverseCXXForRangeStmt(decl
);
338 PostTraverseCXXForRangeStmt(decl
, ret
);
343 bool ReduceVarScope::PreTraverseForStmt(ForStmt
* forStmt
)
345 assert(mnCurrentLoopDepth
!= std::numeric_limits
<unsigned int>::max());
346 ++mnCurrentLoopDepth
;
348 auto declStmt
= dyn_cast_or_null
<DeclStmt
>(forStmt
->getInit());
351 if (declStmt
->isSingleDecl())
353 if (auto varDecl
= dyn_cast_or_null
<VarDecl
>(declStmt
->getSingleDecl()))
354 maVarDeclToIgnoreSet
.insert(varDecl
);
358 for (auto const& decl
: declStmt
->getDeclGroup())
359 if (auto varDecl
= dyn_cast_or_null
<VarDecl
>(decl
))
360 maVarDeclToIgnoreSet
.insert(varDecl
);
367 bool ReduceVarScope::PostTraverseForStmt(ForStmt
*, bool)
369 assert(mnCurrentLoopDepth
!= 0);
370 --mnCurrentLoopDepth
;
374 bool ReduceVarScope::TraverseForStmt(ForStmt
* decl
)
377 if (PreTraverseForStmt(decl
))
379 ret
= FilteringPlugin::TraverseForStmt(decl
);
380 PostTraverseForStmt(decl
, ret
);
385 bool ReduceVarScope::VisitVarDecl(const VarDecl
* varDecl
)
387 if (ignoreLocation(varDecl
))
389 if (varDecl
->isExceptionVariable() || isa
<ParmVarDecl
>(varDecl
))
391 // ignore stuff in header files (which should really not be there, but anyhow)
392 if (!compiler
.getSourceManager().isInMainFile(varDecl
->getLocation()))
394 // Ignore macros like FD_ZERO
395 if (compiler
.getSourceManager().isMacroBodyExpansion(compat::getBeginLoc(varDecl
)))
397 if (varDecl
->hasGlobalStorage())
399 if (varDecl
->isConstexpr())
401 if (varDecl
->isInitCapture())
403 if (varDecl
->isCXXForRangeDecl())
405 if (!isTypeOK(varDecl
->getType()))
408 if (varDecl
->hasInit() && !isInitConstant(varDecl
))
411 maVarDeclMap
[varDecl
].mnFirstDepth
= mnCurrentDepth
;
412 maVarDeclMap
[varDecl
].mnFirstLoopDepth
= mnCurrentLoopDepth
;
413 maVarDeclMap
[varDecl
].maDeclBlockPath
= maCurrentBlockPath
;
418 bool ReduceVarScope::isInitConstant(const VarDecl
* varDecl
)
420 // check for string or scalar literals
421 const Expr
* initExpr
= varDecl
->getInit();
422 if (auto e
= dyn_cast
<ExprWithCleanups
>(initExpr
))
423 initExpr
= e
->getSubExpr();
424 if (isa
<clang::StringLiteral
>(initExpr
))
426 if (auto constructExpr
= dyn_cast
<CXXConstructExpr
>(initExpr
))
428 if (constructExpr
->getNumArgs() == 0)
430 return true; // i.e., empty string
434 auto stringLit2
= dyn_cast
<clang::StringLiteral
>(constructExpr
->getArg(0));
440 auto const init
= varDecl
->getInit();
441 if (init
->isValueDependent())
443 return init
->isConstantInitializer(compiler
.getASTContext(), false /*ForRef*/);
446 bool ReduceVarScope::isTypeOK(QualType varType
)
448 // TODO improve this - requires more analysis because it's really easy to
449 // take a pointer to an array
450 if (varType
->isArrayType())
453 if (varType
.isCXX11PODType(compiler
.getASTContext()))
455 if (!varType
->isRecordType())
457 auto recordDecl
= dyn_cast_or_null
<CXXRecordDecl
>(varType
->getAs
<RecordType
>()->getDecl());
458 if (recordDecl
&& recordDecl
->hasTrivialDestructor())
460 auto const tc
= loplugin::TypeCheck(varType
);
461 // Safe types with destructors that don't do anything interesting
462 if (tc
.Class("OString").Namespace("rtl").GlobalNamespace()
463 || tc
.Class("OUString").Namespace("rtl").GlobalNamespace()
464 || tc
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace()
465 || tc
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
466 || tc
.Class("Color").GlobalNamespace() || tc
.Class("Pair").GlobalNamespace()
467 || tc
.Class("Point").GlobalNamespace() || tc
.Class("Size").GlobalNamespace()
468 || tc
.Class("Range").GlobalNamespace() || tc
.Class("Selection").GlobalNamespace()
469 || tc
.Class("Rectangle").Namespace("tools").GlobalNamespace())
474 bool ReduceVarScope::VisitDeclRefExpr(const DeclRefExpr
* declRefExpr
)
476 if (ignoreLocation(declRefExpr
))
478 const Decl
* decl
= declRefExpr
->getDecl();
479 if (!isa
<VarDecl
>(decl
) || isa
<ParmVarDecl
>(decl
))
481 const VarDecl
* varDecl
= dyn_cast
<VarDecl
>(decl
)->getCanonicalDecl();
482 // ignore stuff in header files (which should really not be there, but anyhow)
483 if (!compiler
.getSourceManager().isInMainFile(varDecl
->getLocation()))
486 auto varIt
= maVarDeclMap
.find(varDecl
);
487 if (varIt
== maVarDeclMap
.end())
490 auto& depthInfo
= varIt
->second
;
492 // merge block paths to get common ancestor path
493 if (depthInfo
.maCommonBlockPath
.empty())
494 depthInfo
.maCommonBlockPath
= maCurrentBlockPath
;
497 auto len
= std::min(depthInfo
.maCommonBlockPath
.size(), maCurrentBlockPath
.size());
499 while (i
< len
&& depthInfo
.maCommonBlockPath
[i
] == maCurrentBlockPath
[i
])
501 depthInfo
.maCommonBlockPath
.resize(i
);
502 if (depthInfo
.maCommonBlockPath
== depthInfo
.maDeclBlockPath
)
504 maVarDeclMap
.erase(varIt
);
505 maVarUseSourceRangeMap
.erase(varDecl
);
510 // seen in a loop below initial decl
511 if (mnCurrentLoopDepth
> depthInfo
.mnFirstLoopDepth
)
513 // TODO, we could additionally check if we are reading or writing to the var inside a loop
514 // We only need to exclude vars that are written to, or passed taken-addr-of, or have non-const method called,
515 // or passed as arg to non-const-ref parameter.
516 maVarDeclMap
.erase(varIt
);
517 maVarUseSourceRangeMap
.erase(varDecl
);
521 auto it
= maVarUseSourceRangeMap
.find(varDecl
);
522 if (it
== maVarUseSourceRangeMap
.end())
523 it
= maVarUseSourceRangeMap
.emplace(varDecl
, std::vector
<SourceRange
>()).first
;
524 it
->second
.push_back(declRefExpr
->getSourceRange());
529 bool ReduceVarScope::VisitLambdaExpr(const LambdaExpr
* lambdaExpr
)
531 if (ignoreLocation(lambdaExpr
))
533 for (auto captureIt
= lambdaExpr
->capture_begin(); captureIt
!= lambdaExpr
->capture_end();
536 const LambdaCapture
& capture
= *captureIt
;
537 if (capture
.capturesVariable())
539 auto varDecl
= capture
.getCapturedVar();
540 maVarDeclMap
.erase(varDecl
);
541 maVarUseSourceRangeMap
.erase(varDecl
);
547 loplugin::Plugin::Registration
<ReduceVarScope
> reducevarscope("reducevarscope", false);
550 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */