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 "config_clang.h"
22 #include "clang/AST/CXXInheritance.h"
23 #include "clang/AST/StmtVisitor.h"
25 // Original idea from mike kaganski.
26 // Look for variables that can have their scoped reduced, which makes the code easier to read.
28 // TODO when dealing with vars that are referenced in multiple child blocks, the check is very primitive
29 // and could be greatly improved.
33 class ReduceVarScope
: public loplugin::FilteringPlugin
<ReduceVarScope
>
36 explicit ReduceVarScope(loplugin::InstantiationData
const& data
)
37 : FilteringPlugin(data
)
41 bool preRun() override
43 if (!compiler
.getLangOpts().CPlusPlus
)
45 // ignore some files with problematic macros
46 std::string
fn(handler
.getMainFileName());
47 loplugin::normalizeDotDotInFilePath(fn
);
48 // some declarations look better all together
49 if (fn
== SRCDIR
"/package/source/manifest/ManifestExport.cxx")
51 // storing pointer to OUString internal data
52 if (fn
== SRCDIR
"/connectivity/source/drivers/odbc/ODatabaseMetaDataResultSet.cxx"
53 || fn
== SRCDIR
"/sc/source/filter/excel/xestyle.cxx"
54 || fn
== SRCDIR
"/sw/source/filter/html/htmlflywriter.cxx"
55 || fn
== SRCDIR
"/unoxml/source/dom/element.cxx"
56 || fn
== SRCDIR
"/unoxml/source/dom/document.cxx"
57 || fn
== SRCDIR
"/sd/source/filter/eppt/pptx-animations.cxx")
59 if (fn
== SRCDIR
"/sal/qa/rtl/strings/nonconstarray.cxx")
64 virtual void run() override
69 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
73 virtual void postRun() override
75 for (auto const& pair
: maVarDeclMap
)
77 auto varDecl
= pair
.first
;
78 auto const& depthInfo
= pair
.second
;
79 if (depthInfo
.maDeclBlockPath
.size() == depthInfo
.maCommonBlockPath
.size())
81 if (maVarDeclToIgnoreSet
.find(varDecl
) != maVarDeclToIgnoreSet
.end())
83 auto it
= maVarUseSourceRangeMap
.find(varDecl
);
84 if (it
== maVarUseSourceRangeMap
.end())
86 report(DiagnosticsEngine::Warning
, "can reduce scope of var", varDecl
->getLocation())
87 << varDecl
->getSourceRange();
88 for (SourceRange
const& useRange
: it
->second
)
89 report(DiagnosticsEngine::Note
, "used here", useRange
.getBegin()) << useRange
;
93 bool VisitUnaryOperator(UnaryOperator
const* expr
)
95 // if we take the address of it
96 UnaryOperator::Opcode op
= expr
->getOpcode();
98 recordIgnore(expr
->getSubExpr());
102 bool VisitDeclRefExpr(const DeclRefExpr
*);
103 bool VisitVarDecl(const VarDecl
*);
104 bool VisitLambdaExpr(const LambdaExpr
*);
106 bool PreTraverseFunctionDecl(FunctionDecl
*);
107 bool PostTraverseFunctionDecl(FunctionDecl
*, bool);
108 bool TraverseFunctionDecl(FunctionDecl
*);
110 bool PreTraverseCompoundStmt(CompoundStmt
*);
111 bool PostTraverseCompoundStmt(CompoundStmt
*, bool);
112 bool TraverseCompoundStmt(CompoundStmt
*);
114 bool PreTraverseWhileStmt(WhileStmt
*);
115 bool PostTraverseWhileStmt(WhileStmt
*, bool);
116 bool TraverseWhileStmt(WhileStmt
*);
118 bool PreTraverseDoStmt(DoStmt
*);
119 bool PostTraverseDoStmt(DoStmt
*, bool);
120 bool TraverseDoStmt(DoStmt
*);
122 bool PreTraverseCXXForRangeStmt(CXXForRangeStmt
*);
123 bool PostTraverseCXXForRangeStmt(CXXForRangeStmt
*, bool);
124 bool TraverseCXXForRangeStmt(CXXForRangeStmt
*);
126 bool PreTraverseForStmt(ForStmt
*);
127 bool PostTraverseForStmt(ForStmt
*, bool);
128 bool TraverseForStmt(ForStmt
*);
130 bool PreTraverseSwitchStmt(SwitchStmt
*);
131 bool PostTraverseSwitchStmt(SwitchStmt
*, bool);
132 bool TraverseSwitchStmt(SwitchStmt
*);
137 unsigned int mnFirstDepth
= 0;
138 unsigned int mnFirstLoopDepth
= 0;
139 std::vector
<unsigned int> maDeclBlockPath
= {};
140 std::vector
<unsigned int> maCommonBlockPath
= {};
142 std::unordered_map
<VarDecl
const*, DepthInfo
> maVarDeclMap
; // varDecl->depth
143 std::unordered_set
<VarDecl
const*> maVarDeclToIgnoreSet
;
144 std::map
<VarDecl
const*, std::vector
<SourceRange
>> maVarUseSourceRangeMap
;
145 std::vector
<unsigned int> maCurrentBlockPath
;
146 unsigned int mnCurrentDepth
= 0;
147 unsigned int mnCurrentLoopDepth
= 0;
148 static unsigned int gnBlockId
;
150 bool isTypeOK(QualType qt
);
151 bool isInitConstant(const VarDecl
* varDecl
);
153 void recordIgnore(Expr
const* expr
)
157 expr
= expr
->IgnoreParenImpCasts();
158 if (auto const e
= dyn_cast
<MemberExpr
>(expr
))
160 if (isa
<FieldDecl
>(e
->getMemberDecl()))
166 if (auto const e
= dyn_cast
<ArraySubscriptExpr
>(expr
))
171 if (auto const e
= dyn_cast
<BinaryOperator
>(expr
))
173 if (e
->getOpcode() == BO_PtrMemD
)
181 auto const dre
= dyn_cast
<DeclRefExpr
>(expr
);
184 auto const var
= dyn_cast
<VarDecl
>(dre
->getDecl());
187 maVarDeclToIgnoreSet
.insert(var
);
191 unsigned int ReduceVarScope::gnBlockId
= 0;
193 bool ReduceVarScope::PreTraverseFunctionDecl(FunctionDecl
* functionDecl
)
195 // Ignore functions that contains #ifdef-ery, can be quite tricky
196 // to make useful changes when this plugin fires in such functions
197 if (containsPreprocessingConditionalInclusion(functionDecl
->getSourceRange()))
202 bool ReduceVarScope::PostTraverseFunctionDecl(FunctionDecl
*, bool) { return true; }
204 bool ReduceVarScope::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
207 if (PreTraverseFunctionDecl(functionDecl
))
209 ret
= FilteringPlugin::TraverseFunctionDecl(functionDecl
);
210 PostTraverseFunctionDecl(functionDecl
, ret
);
215 bool ReduceVarScope::PreTraverseCompoundStmt(CompoundStmt
*)
217 assert(mnCurrentDepth
!= std::numeric_limits
<unsigned int>::max());
220 maCurrentBlockPath
.push_back(gnBlockId
);
224 bool ReduceVarScope::PostTraverseCompoundStmt(CompoundStmt
*, bool)
226 assert(mnCurrentDepth
!= 0);
228 maCurrentBlockPath
.pop_back();
232 bool ReduceVarScope::TraverseCompoundStmt(CompoundStmt
* decl
)
235 if (PreTraverseCompoundStmt(decl
))
237 ret
= FilteringPlugin::TraverseCompoundStmt(decl
);
238 PostTraverseCompoundStmt(decl
, ret
);
243 bool ReduceVarScope::PreTraverseWhileStmt(WhileStmt
*)
245 assert(mnCurrentLoopDepth
!= std::numeric_limits
<unsigned int>::max());
246 ++mnCurrentLoopDepth
;
250 bool ReduceVarScope::PostTraverseWhileStmt(WhileStmt
*, bool)
252 assert(mnCurrentLoopDepth
!= 0);
253 --mnCurrentLoopDepth
;
257 bool ReduceVarScope::TraverseWhileStmt(WhileStmt
* decl
)
260 if (PreTraverseWhileStmt(decl
))
262 ret
= FilteringPlugin::TraverseWhileStmt(decl
);
263 PostTraverseWhileStmt(decl
, ret
);
268 bool ReduceVarScope::PreTraverseDoStmt(DoStmt
*)
270 assert(mnCurrentLoopDepth
!= std::numeric_limits
<unsigned int>::max());
271 ++mnCurrentLoopDepth
;
275 bool ReduceVarScope::PostTraverseDoStmt(DoStmt
*, bool)
277 assert(mnCurrentLoopDepth
!= 0);
278 --mnCurrentLoopDepth
;
282 bool ReduceVarScope::TraverseDoStmt(DoStmt
* decl
)
285 if (PreTraverseDoStmt(decl
))
287 ret
= FilteringPlugin::TraverseDoStmt(decl
);
288 PostTraverseDoStmt(decl
, ret
);
293 bool ReduceVarScope::PreTraverseSwitchStmt(SwitchStmt
*)
295 assert(mnCurrentLoopDepth
!= std::numeric_limits
<unsigned int>::max());
296 ++mnCurrentLoopDepth
;
300 bool ReduceVarScope::PostTraverseSwitchStmt(SwitchStmt
*, bool)
302 assert(mnCurrentLoopDepth
!= 0);
303 --mnCurrentLoopDepth
;
307 // Consider a switch to be a loop, because weird things happen inside it
308 bool ReduceVarScope::TraverseSwitchStmt(SwitchStmt
* decl
)
311 if (PreTraverseSwitchStmt(decl
))
313 ret
= FilteringPlugin::TraverseSwitchStmt(decl
);
314 PostTraverseSwitchStmt(decl
, ret
);
319 bool ReduceVarScope::PreTraverseCXXForRangeStmt(CXXForRangeStmt
*)
321 assert(mnCurrentLoopDepth
!= std::numeric_limits
<unsigned int>::max());
322 ++mnCurrentLoopDepth
;
326 bool ReduceVarScope::PostTraverseCXXForRangeStmt(CXXForRangeStmt
*, bool)
328 assert(mnCurrentLoopDepth
!= 0);
329 --mnCurrentLoopDepth
;
333 bool ReduceVarScope::TraverseCXXForRangeStmt(CXXForRangeStmt
* decl
)
336 if (PreTraverseCXXForRangeStmt(decl
))
338 ret
= FilteringPlugin::TraverseCXXForRangeStmt(decl
);
339 PostTraverseCXXForRangeStmt(decl
, ret
);
344 bool ReduceVarScope::PreTraverseForStmt(ForStmt
* forStmt
)
346 assert(mnCurrentLoopDepth
!= std::numeric_limits
<unsigned int>::max());
347 ++mnCurrentLoopDepth
;
349 auto declStmt
= dyn_cast_or_null
<DeclStmt
>(forStmt
->getInit());
352 if (declStmt
->isSingleDecl())
354 if (auto varDecl
= dyn_cast_or_null
<VarDecl
>(declStmt
->getSingleDecl()))
355 maVarDeclToIgnoreSet
.insert(varDecl
);
359 for (auto const& decl
: declStmt
->getDeclGroup())
360 if (auto varDecl
= dyn_cast_or_null
<VarDecl
>(decl
))
361 maVarDeclToIgnoreSet
.insert(varDecl
);
368 bool ReduceVarScope::PostTraverseForStmt(ForStmt
*, bool)
370 assert(mnCurrentLoopDepth
!= 0);
371 --mnCurrentLoopDepth
;
375 bool ReduceVarScope::TraverseForStmt(ForStmt
* decl
)
378 if (PreTraverseForStmt(decl
))
380 ret
= FilteringPlugin::TraverseForStmt(decl
);
381 PostTraverseForStmt(decl
, ret
);
386 bool ReduceVarScope::VisitVarDecl(const VarDecl
* varDecl
)
388 if (ignoreLocation(varDecl
))
390 if (varDecl
->isExceptionVariable() || isa
<ParmVarDecl
>(varDecl
))
392 // ignore stuff in header files (which should really not be there, but anyhow)
393 if (!compiler
.getSourceManager().isInMainFile(varDecl
->getLocation()))
395 // Ignore macros like FD_ZERO
396 if (compiler
.getSourceManager().isMacroBodyExpansion(varDecl
->getBeginLoc()))
398 if (varDecl
->hasGlobalStorage())
400 if (varDecl
->isConstexpr())
402 if (varDecl
->isInitCapture())
404 if (varDecl
->isCXXForRangeDecl())
406 if (!isTypeOK(varDecl
->getType()))
409 if (varDecl
->hasInit() && !isInitConstant(varDecl
))
412 maVarDeclMap
[varDecl
].mnFirstDepth
= mnCurrentDepth
;
413 maVarDeclMap
[varDecl
].mnFirstLoopDepth
= mnCurrentLoopDepth
;
414 maVarDeclMap
[varDecl
].maDeclBlockPath
= maCurrentBlockPath
;
419 bool ReduceVarScope::isInitConstant(const VarDecl
* varDecl
)
421 // check for string or scalar literals
422 const Expr
* initExpr
= varDecl
->getInit();
423 if (auto e
= dyn_cast
<ExprWithCleanups
>(initExpr
))
424 initExpr
= e
->getSubExpr();
425 if (isa
<clang::StringLiteral
>(initExpr
))
427 if (auto constructExpr
= dyn_cast
<CXXConstructExpr
>(initExpr
))
429 if (constructExpr
->getNumArgs() == 0)
431 return true; // i.e., empty string
435 auto stringLit2
= dyn_cast
<clang::StringLiteral
>(constructExpr
->getArg(0));
441 auto const init
= varDecl
->getInit();
442 if (init
->isValueDependent())
444 return init
->isConstantInitializer(compiler
.getASTContext(), false /*ForRef*/);
447 bool ReduceVarScope::isTypeOK(QualType varType
)
449 // TODO improve this - requires more analysis because it's really easy to
450 // take a pointer to an array
451 if (varType
->isArrayType())
454 if (varType
.isCXX11PODType(compiler
.getASTContext()))
456 if (!varType
->isRecordType())
458 auto recordDecl
= dyn_cast_or_null
<CXXRecordDecl
>(varType
->getAs
<RecordType
>()->getDecl());
459 if (recordDecl
&& recordDecl
->hasTrivialDestructor())
461 auto const tc
= loplugin::TypeCheck(varType
);
462 // Safe types with destructors that don't do anything interesting
463 if (tc
.Class("OString").Namespace("rtl").GlobalNamespace()
464 || tc
.Class("OUString").Namespace("rtl").GlobalNamespace()
465 || tc
.Class("OStringBuffer").Namespace("rtl").GlobalNamespace()
466 || tc
.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
467 || tc
.Class("Color").GlobalNamespace() || tc
.Class("Pair").GlobalNamespace()
468 || tc
.Class("Point").GlobalNamespace() || tc
.Class("Size").GlobalNamespace()
469 || tc
.Class("Range").GlobalNamespace() || tc
.Class("Selection").GlobalNamespace()
470 || tc
.Class("Rectangle").Namespace("tools").GlobalNamespace())
475 bool ReduceVarScope::VisitDeclRefExpr(const DeclRefExpr
* declRefExpr
)
477 if (ignoreLocation(declRefExpr
))
479 const Decl
* decl
= declRefExpr
->getDecl();
480 if (!isa
<VarDecl
>(decl
) || isa
<ParmVarDecl
>(decl
))
482 const VarDecl
* varDecl
= dyn_cast
<VarDecl
>(decl
)->getCanonicalDecl();
483 // ignore stuff in header files (which should really not be there, but anyhow)
484 if (!compiler
.getSourceManager().isInMainFile(varDecl
->getLocation()))
487 auto varIt
= maVarDeclMap
.find(varDecl
);
488 if (varIt
== maVarDeclMap
.end())
491 auto& depthInfo
= varIt
->second
;
493 // merge block paths to get common ancestor path
494 if (depthInfo
.maCommonBlockPath
.empty())
495 depthInfo
.maCommonBlockPath
= maCurrentBlockPath
;
498 auto len
= std::min(depthInfo
.maCommonBlockPath
.size(), maCurrentBlockPath
.size());
500 while (i
< len
&& depthInfo
.maCommonBlockPath
[i
] == maCurrentBlockPath
[i
])
502 depthInfo
.maCommonBlockPath
.resize(i
);
503 if (depthInfo
.maCommonBlockPath
== depthInfo
.maDeclBlockPath
)
505 maVarDeclMap
.erase(varIt
);
506 maVarUseSourceRangeMap
.erase(varDecl
);
511 // seen in a loop below initial decl
512 if (mnCurrentLoopDepth
> depthInfo
.mnFirstLoopDepth
)
514 // TODO, we could additionally check if we are reading or writing to the var inside a loop
515 // We only need to exclude vars that are written to, or passed taken-addr-of, or have non-const method called,
516 // or passed as arg to non-const-ref parameter.
517 maVarDeclMap
.erase(varIt
);
518 maVarUseSourceRangeMap
.erase(varDecl
);
522 auto it
= maVarUseSourceRangeMap
.find(varDecl
);
523 if (it
== maVarUseSourceRangeMap
.end())
524 it
= maVarUseSourceRangeMap
.emplace(varDecl
, std::vector
<SourceRange
>()).first
;
525 it
->second
.push_back(declRefExpr
->getSourceRange());
530 bool ReduceVarScope::VisitLambdaExpr(const LambdaExpr
* lambdaExpr
)
532 if (ignoreLocation(lambdaExpr
))
534 for (auto captureIt
= lambdaExpr
->capture_begin(); captureIt
!= lambdaExpr
->capture_end();
537 const LambdaCapture
& capture
= *captureIt
;
538 if (capture
.capturesVariable())
540 auto varDecl
= cast
<VarDecl
>(capture
.getCapturedVar());
541 maVarDeclMap
.erase(varDecl
);
542 maVarUseSourceRangeMap
.erase(varDecl
);
548 loplugin::Plugin::Registration
<ReduceVarScope
> reducevarscope("reducevarscope", false);
551 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */