Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / compilerplugins / clang / reducevarscope.cxx
blob76eece2131bb547eddb9e6119f27c0eca33d45a0
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
10 #include <cassert>
11 #include <string>
12 #include <iostream>
13 #include <map>
14 #include <set>
15 #include <vector>
16 #include <unordered_map>
17 #include <unordered_set>
19 #include "plugin.hxx"
20 #include "check.hxx"
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.
31 namespace
33 class ReduceVarScope : public loplugin::FilteringPlugin<ReduceVarScope>
35 public:
36 explicit ReduceVarScope(loplugin::InstantiationData const& data)
37 : FilteringPlugin(data)
41 bool preRun() override
43 if (!compiler.getLangOpts().CPlusPlus)
44 return false;
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")
50 return false;
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")
58 return false;
59 if (fn == SRCDIR "/sal/qa/rtl/strings/nonconstarray.cxx")
60 return false;
61 return true;
64 virtual void run() override
66 if (!preRun())
67 return;
69 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
70 postRun();
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())
80 continue;
81 if (maVarDeclToIgnoreSet.find(varDecl) != maVarDeclToIgnoreSet.end())
82 continue;
83 auto it = maVarUseSourceRangeMap.find(varDecl);
84 if (it == maVarUseSourceRangeMap.end())
85 continue;
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();
97 if (op == UO_AddrOf)
98 recordIgnore(expr->getSubExpr());
99 return true;
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*);
134 private:
135 struct DepthInfo
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)
155 for (;;)
157 expr = expr->IgnoreParenImpCasts();
158 if (auto const e = dyn_cast<MemberExpr>(expr))
160 if (isa<FieldDecl>(e->getMemberDecl()))
162 expr = e->getBase();
163 continue;
166 if (auto const e = dyn_cast<ArraySubscriptExpr>(expr))
168 expr = e->getBase();
169 continue;
171 if (auto const e = dyn_cast<BinaryOperator>(expr))
173 if (e->getOpcode() == BO_PtrMemD)
175 expr = e->getLHS();
176 continue;
179 break;
181 auto const dre = dyn_cast<DeclRefExpr>(expr);
182 if (dre == nullptr)
183 return;
184 auto const var = dyn_cast<VarDecl>(dre->getDecl());
185 if (var == nullptr)
186 return;
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()))
198 return false;
199 return true;
202 bool ReduceVarScope::PostTraverseFunctionDecl(FunctionDecl*, bool) { return true; }
204 bool ReduceVarScope::TraverseFunctionDecl(FunctionDecl* functionDecl)
206 bool ret = true;
207 if (PreTraverseFunctionDecl(functionDecl))
209 ret = FilteringPlugin::TraverseFunctionDecl(functionDecl);
210 PostTraverseFunctionDecl(functionDecl, ret);
212 return ret;
215 bool ReduceVarScope::PreTraverseCompoundStmt(CompoundStmt*)
217 assert(mnCurrentDepth != std::numeric_limits<unsigned int>::max());
218 ++mnCurrentDepth;
219 ++gnBlockId;
220 maCurrentBlockPath.push_back(gnBlockId);
221 return true;
224 bool ReduceVarScope::PostTraverseCompoundStmt(CompoundStmt*, bool)
226 assert(mnCurrentDepth != 0);
227 --mnCurrentDepth;
228 maCurrentBlockPath.pop_back();
229 return true;
232 bool ReduceVarScope::TraverseCompoundStmt(CompoundStmt* decl)
234 bool ret = true;
235 if (PreTraverseCompoundStmt(decl))
237 ret = FilteringPlugin::TraverseCompoundStmt(decl);
238 PostTraverseCompoundStmt(decl, ret);
240 return ret;
243 bool ReduceVarScope::PreTraverseWhileStmt(WhileStmt*)
245 assert(mnCurrentLoopDepth != std::numeric_limits<unsigned int>::max());
246 ++mnCurrentLoopDepth;
247 return true;
250 bool ReduceVarScope::PostTraverseWhileStmt(WhileStmt*, bool)
252 assert(mnCurrentLoopDepth != 0);
253 --mnCurrentLoopDepth;
254 return true;
257 bool ReduceVarScope::TraverseWhileStmt(WhileStmt* decl)
259 bool ret = true;
260 if (PreTraverseWhileStmt(decl))
262 ret = FilteringPlugin::TraverseWhileStmt(decl);
263 PostTraverseWhileStmt(decl, ret);
265 return ret;
268 bool ReduceVarScope::PreTraverseDoStmt(DoStmt*)
270 assert(mnCurrentLoopDepth != std::numeric_limits<unsigned int>::max());
271 ++mnCurrentLoopDepth;
272 return true;
275 bool ReduceVarScope::PostTraverseDoStmt(DoStmt*, bool)
277 assert(mnCurrentLoopDepth != 0);
278 --mnCurrentLoopDepth;
279 return true;
282 bool ReduceVarScope::TraverseDoStmt(DoStmt* decl)
284 bool ret = true;
285 if (PreTraverseDoStmt(decl))
287 ret = FilteringPlugin::TraverseDoStmt(decl);
288 PostTraverseDoStmt(decl, ret);
290 return ret;
293 bool ReduceVarScope::PreTraverseSwitchStmt(SwitchStmt*)
295 assert(mnCurrentLoopDepth != std::numeric_limits<unsigned int>::max());
296 ++mnCurrentLoopDepth;
297 return true;
300 bool ReduceVarScope::PostTraverseSwitchStmt(SwitchStmt*, bool)
302 assert(mnCurrentLoopDepth != 0);
303 --mnCurrentLoopDepth;
304 return true;
307 // Consider a switch to be a loop, because weird things happen inside it
308 bool ReduceVarScope::TraverseSwitchStmt(SwitchStmt* decl)
310 bool ret = true;
311 if (PreTraverseSwitchStmt(decl))
313 ret = FilteringPlugin::TraverseSwitchStmt(decl);
314 PostTraverseSwitchStmt(decl, ret);
316 return ret;
319 bool ReduceVarScope::PreTraverseCXXForRangeStmt(CXXForRangeStmt*)
321 assert(mnCurrentLoopDepth != std::numeric_limits<unsigned int>::max());
322 ++mnCurrentLoopDepth;
323 return true;
326 bool ReduceVarScope::PostTraverseCXXForRangeStmt(CXXForRangeStmt*, bool)
328 assert(mnCurrentLoopDepth != 0);
329 --mnCurrentLoopDepth;
330 return true;
333 bool ReduceVarScope::TraverseCXXForRangeStmt(CXXForRangeStmt* decl)
335 bool ret = true;
336 if (PreTraverseCXXForRangeStmt(decl))
338 ret = FilteringPlugin::TraverseCXXForRangeStmt(decl);
339 PostTraverseCXXForRangeStmt(decl, ret);
341 return 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());
350 if (declStmt)
352 if (declStmt->isSingleDecl())
354 if (auto varDecl = dyn_cast_or_null<VarDecl>(declStmt->getSingleDecl()))
355 maVarDeclToIgnoreSet.insert(varDecl);
357 else
359 for (auto const& decl : declStmt->getDeclGroup())
360 if (auto varDecl = dyn_cast_or_null<VarDecl>(decl))
361 maVarDeclToIgnoreSet.insert(varDecl);
365 return true;
368 bool ReduceVarScope::PostTraverseForStmt(ForStmt*, bool)
370 assert(mnCurrentLoopDepth != 0);
371 --mnCurrentLoopDepth;
372 return true;
375 bool ReduceVarScope::TraverseForStmt(ForStmt* decl)
377 bool ret = true;
378 if (PreTraverseForStmt(decl))
380 ret = FilteringPlugin::TraverseForStmt(decl);
381 PostTraverseForStmt(decl, ret);
383 return ret;
386 bool ReduceVarScope::VisitVarDecl(const VarDecl* varDecl)
388 if (ignoreLocation(varDecl))
389 return true;
390 if (varDecl->isExceptionVariable() || isa<ParmVarDecl>(varDecl))
391 return true;
392 // ignore stuff in header files (which should really not be there, but anyhow)
393 if (!compiler.getSourceManager().isInMainFile(varDecl->getLocation()))
394 return true;
395 // Ignore macros like FD_ZERO
396 if (compiler.getSourceManager().isMacroBodyExpansion(varDecl->getBeginLoc()))
397 return true;
398 if (varDecl->hasGlobalStorage())
399 return true;
400 if (varDecl->isConstexpr())
401 return true;
402 if (varDecl->isInitCapture())
403 return true;
404 if (varDecl->isCXXForRangeDecl())
405 return true;
406 if (!isTypeOK(varDecl->getType()))
407 return true;
409 if (varDecl->hasInit() && !isInitConstant(varDecl))
410 return true;
412 maVarDeclMap[varDecl].mnFirstDepth = mnCurrentDepth;
413 maVarDeclMap[varDecl].mnFirstLoopDepth = mnCurrentLoopDepth;
414 maVarDeclMap[varDecl].maDeclBlockPath = maCurrentBlockPath;
416 return true;
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))
426 return true;
427 if (auto constructExpr = dyn_cast<CXXConstructExpr>(initExpr))
429 if (constructExpr->getNumArgs() == 0)
431 return true; // i.e., empty string
433 else
435 auto stringLit2 = dyn_cast<clang::StringLiteral>(constructExpr->getArg(0));
436 if (stringLit2)
437 return true;
441 auto const init = varDecl->getInit();
442 if (init->isValueDependent())
443 return false;
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())
452 return false;
454 if (varType.isCXX11PODType(compiler.getASTContext()))
455 return true;
456 if (!varType->isRecordType())
457 return false;
458 auto recordDecl = dyn_cast_or_null<CXXRecordDecl>(varType->getAs<RecordType>()->getDecl());
459 if (recordDecl && recordDecl->hasTrivialDestructor())
460 return true;
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())
471 return true;
472 return false;
475 bool ReduceVarScope::VisitDeclRefExpr(const DeclRefExpr* declRefExpr)
477 if (ignoreLocation(declRefExpr))
478 return true;
479 const Decl* decl = declRefExpr->getDecl();
480 if (!isa<VarDecl>(decl) || isa<ParmVarDecl>(decl))
481 return true;
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()))
485 return true;
487 auto varIt = maVarDeclMap.find(varDecl);
488 if (varIt == maVarDeclMap.end())
489 return true;
491 auto& depthInfo = varIt->second;
493 // merge block paths to get common ancestor path
494 if (depthInfo.maCommonBlockPath.empty())
495 depthInfo.maCommonBlockPath = maCurrentBlockPath;
496 else
498 auto len = std::min(depthInfo.maCommonBlockPath.size(), maCurrentBlockPath.size());
499 unsigned int i = 0;
500 while (i < len && depthInfo.maCommonBlockPath[i] == maCurrentBlockPath[i])
501 ++i;
502 depthInfo.maCommonBlockPath.resize(i);
503 if (depthInfo.maCommonBlockPath == depthInfo.maDeclBlockPath)
505 maVarDeclMap.erase(varIt);
506 maVarUseSourceRangeMap.erase(varDecl);
507 return true;
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);
519 return true;
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());
527 return true;
530 bool ReduceVarScope::VisitLambdaExpr(const LambdaExpr* lambdaExpr)
532 if (ignoreLocation(lambdaExpr))
533 return true;
534 for (auto captureIt = lambdaExpr->capture_begin(); captureIt != lambdaExpr->capture_end();
535 ++captureIt)
537 const LambdaCapture& capture = *captureIt;
538 if (capture.capturesVariable())
540 auto varDecl = cast<VarDecl>(capture.getCapturedVar());
541 maVarDeclMap.erase(varDecl);
542 maVarUseSourceRangeMap.erase(varDecl);
545 return true;
548 loplugin::Plugin::Registration<ReduceVarScope> reducevarscope("reducevarscope", false);
551 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */