nss: upgrade to release 3.73
[LibreOffice.git] / compilerplugins / clang / reducevarscope.cxx
blobc293fd432e6a26a7076478590dccc14bd26a2961
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 "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.
30 namespace
32 class ReduceVarScope : public loplugin::FilteringPlugin<ReduceVarScope>
34 public:
35 explicit ReduceVarScope(loplugin::InstantiationData const& data)
36 : FilteringPlugin(data)
40 bool preRun() override
42 if (!compiler.getLangOpts().CPlusPlus)
43 return false;
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")
49 return false;
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")
57 return false;
58 if (fn == SRCDIR "/sal/qa/rtl/strings/nonconstarray.cxx")
59 return false;
60 return true;
63 virtual void run() override
65 if (!preRun())
66 return;
68 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
69 postRun();
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())
79 continue;
80 if (maVarDeclToIgnoreSet.find(varDecl) != maVarDeclToIgnoreSet.end())
81 continue;
82 auto it = maVarUseSourceRangeMap.find(varDecl);
83 if (it == maVarUseSourceRangeMap.end())
84 continue;
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();
96 if (op == UO_AddrOf)
97 recordIgnore(expr->getSubExpr());
98 return true;
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*);
133 private:
134 struct DepthInfo
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)
154 for (;;)
156 expr = expr->IgnoreParenImpCasts();
157 if (auto const e = dyn_cast<MemberExpr>(expr))
159 if (isa<FieldDecl>(e->getMemberDecl()))
161 expr = e->getBase();
162 continue;
165 if (auto const e = dyn_cast<ArraySubscriptExpr>(expr))
167 expr = e->getBase();
168 continue;
170 if (auto const e = dyn_cast<BinaryOperator>(expr))
172 if (e->getOpcode() == BO_PtrMemD)
174 expr = e->getLHS();
175 continue;
178 break;
180 auto const dre = dyn_cast<DeclRefExpr>(expr);
181 if (dre == nullptr)
182 return;
183 auto const var = dyn_cast<VarDecl>(dre->getDecl());
184 if (var == nullptr)
185 return;
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()))
197 return false;
198 return true;
201 bool ReduceVarScope::PostTraverseFunctionDecl(FunctionDecl*, bool) { return true; }
203 bool ReduceVarScope::TraverseFunctionDecl(FunctionDecl* functionDecl)
205 bool ret = true;
206 if (PreTraverseFunctionDecl(functionDecl))
208 ret = FilteringPlugin::TraverseFunctionDecl(functionDecl);
209 PostTraverseFunctionDecl(functionDecl, ret);
211 return ret;
214 bool ReduceVarScope::PreTraverseCompoundStmt(CompoundStmt*)
216 assert(mnCurrentDepth != std::numeric_limits<unsigned int>::max());
217 ++mnCurrentDepth;
218 ++gnBlockId;
219 maCurrentBlockPath.push_back(gnBlockId);
220 return true;
223 bool ReduceVarScope::PostTraverseCompoundStmt(CompoundStmt*, bool)
225 assert(mnCurrentDepth != 0);
226 --mnCurrentDepth;
227 maCurrentBlockPath.pop_back();
228 return true;
231 bool ReduceVarScope::TraverseCompoundStmt(CompoundStmt* decl)
233 bool ret = true;
234 if (PreTraverseCompoundStmt(decl))
236 ret = FilteringPlugin::TraverseCompoundStmt(decl);
237 PostTraverseCompoundStmt(decl, ret);
239 return ret;
242 bool ReduceVarScope::PreTraverseWhileStmt(WhileStmt*)
244 assert(mnCurrentLoopDepth != std::numeric_limits<unsigned int>::max());
245 ++mnCurrentLoopDepth;
246 return true;
249 bool ReduceVarScope::PostTraverseWhileStmt(WhileStmt*, bool)
251 assert(mnCurrentLoopDepth != 0);
252 --mnCurrentLoopDepth;
253 return true;
256 bool ReduceVarScope::TraverseWhileStmt(WhileStmt* decl)
258 bool ret = true;
259 if (PreTraverseWhileStmt(decl))
261 ret = FilteringPlugin::TraverseWhileStmt(decl);
262 PostTraverseWhileStmt(decl, ret);
264 return ret;
267 bool ReduceVarScope::PreTraverseDoStmt(DoStmt*)
269 assert(mnCurrentLoopDepth != std::numeric_limits<unsigned int>::max());
270 ++mnCurrentLoopDepth;
271 return true;
274 bool ReduceVarScope::PostTraverseDoStmt(DoStmt*, bool)
276 assert(mnCurrentLoopDepth != 0);
277 --mnCurrentLoopDepth;
278 return true;
281 bool ReduceVarScope::TraverseDoStmt(DoStmt* decl)
283 bool ret = true;
284 if (PreTraverseDoStmt(decl))
286 ret = FilteringPlugin::TraverseDoStmt(decl);
287 PostTraverseDoStmt(decl, ret);
289 return ret;
292 bool ReduceVarScope::PreTraverseSwitchStmt(SwitchStmt*)
294 assert(mnCurrentLoopDepth != std::numeric_limits<unsigned int>::max());
295 ++mnCurrentLoopDepth;
296 return true;
299 bool ReduceVarScope::PostTraverseSwitchStmt(SwitchStmt*, bool)
301 assert(mnCurrentLoopDepth != 0);
302 --mnCurrentLoopDepth;
303 return true;
306 // Consider a switch to be a loop, because weird things happen inside it
307 bool ReduceVarScope::TraverseSwitchStmt(SwitchStmt* decl)
309 bool ret = true;
310 if (PreTraverseSwitchStmt(decl))
312 ret = FilteringPlugin::TraverseSwitchStmt(decl);
313 PostTraverseSwitchStmt(decl, ret);
315 return ret;
318 bool ReduceVarScope::PreTraverseCXXForRangeStmt(CXXForRangeStmt*)
320 assert(mnCurrentLoopDepth != std::numeric_limits<unsigned int>::max());
321 ++mnCurrentLoopDepth;
322 return true;
325 bool ReduceVarScope::PostTraverseCXXForRangeStmt(CXXForRangeStmt*, bool)
327 assert(mnCurrentLoopDepth != 0);
328 --mnCurrentLoopDepth;
329 return true;
332 bool ReduceVarScope::TraverseCXXForRangeStmt(CXXForRangeStmt* decl)
334 bool ret = true;
335 if (PreTraverseCXXForRangeStmt(decl))
337 ret = FilteringPlugin::TraverseCXXForRangeStmt(decl);
338 PostTraverseCXXForRangeStmt(decl, ret);
340 return 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());
349 if (declStmt)
351 if (declStmt->isSingleDecl())
353 if (auto varDecl = dyn_cast_or_null<VarDecl>(declStmt->getSingleDecl()))
354 maVarDeclToIgnoreSet.insert(varDecl);
356 else
358 for (auto const& decl : declStmt->getDeclGroup())
359 if (auto varDecl = dyn_cast_or_null<VarDecl>(decl))
360 maVarDeclToIgnoreSet.insert(varDecl);
364 return true;
367 bool ReduceVarScope::PostTraverseForStmt(ForStmt*, bool)
369 assert(mnCurrentLoopDepth != 0);
370 --mnCurrentLoopDepth;
371 return true;
374 bool ReduceVarScope::TraverseForStmt(ForStmt* decl)
376 bool ret = true;
377 if (PreTraverseForStmt(decl))
379 ret = FilteringPlugin::TraverseForStmt(decl);
380 PostTraverseForStmt(decl, ret);
382 return ret;
385 bool ReduceVarScope::VisitVarDecl(const VarDecl* varDecl)
387 if (ignoreLocation(varDecl))
388 return true;
389 if (varDecl->isExceptionVariable() || isa<ParmVarDecl>(varDecl))
390 return true;
391 // ignore stuff in header files (which should really not be there, but anyhow)
392 if (!compiler.getSourceManager().isInMainFile(varDecl->getLocation()))
393 return true;
394 // Ignore macros like FD_ZERO
395 if (compiler.getSourceManager().isMacroBodyExpansion(compat::getBeginLoc(varDecl)))
396 return true;
397 if (varDecl->hasGlobalStorage())
398 return true;
399 if (varDecl->isConstexpr())
400 return true;
401 if (varDecl->isInitCapture())
402 return true;
403 if (varDecl->isCXXForRangeDecl())
404 return true;
405 if (!isTypeOK(varDecl->getType()))
406 return true;
408 if (varDecl->hasInit() && !isInitConstant(varDecl))
409 return true;
411 maVarDeclMap[varDecl].mnFirstDepth = mnCurrentDepth;
412 maVarDeclMap[varDecl].mnFirstLoopDepth = mnCurrentLoopDepth;
413 maVarDeclMap[varDecl].maDeclBlockPath = maCurrentBlockPath;
415 return true;
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))
425 return true;
426 if (auto constructExpr = dyn_cast<CXXConstructExpr>(initExpr))
428 if (constructExpr->getNumArgs() == 0)
430 return true; // i.e., empty string
432 else
434 auto stringLit2 = dyn_cast<clang::StringLiteral>(constructExpr->getArg(0));
435 if (stringLit2)
436 return true;
440 auto const init = varDecl->getInit();
441 if (init->isValueDependent())
442 return false;
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())
451 return false;
453 if (varType.isCXX11PODType(compiler.getASTContext()))
454 return true;
455 if (!varType->isRecordType())
456 return false;
457 auto recordDecl = dyn_cast_or_null<CXXRecordDecl>(varType->getAs<RecordType>()->getDecl());
458 if (recordDecl && recordDecl->hasTrivialDestructor())
459 return true;
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())
470 return true;
471 return false;
474 bool ReduceVarScope::VisitDeclRefExpr(const DeclRefExpr* declRefExpr)
476 if (ignoreLocation(declRefExpr))
477 return true;
478 const Decl* decl = declRefExpr->getDecl();
479 if (!isa<VarDecl>(decl) || isa<ParmVarDecl>(decl))
480 return true;
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()))
484 return true;
486 auto varIt = maVarDeclMap.find(varDecl);
487 if (varIt == maVarDeclMap.end())
488 return true;
490 auto& depthInfo = varIt->second;
492 // merge block paths to get common ancestor path
493 if (depthInfo.maCommonBlockPath.empty())
494 depthInfo.maCommonBlockPath = maCurrentBlockPath;
495 else
497 auto len = std::min(depthInfo.maCommonBlockPath.size(), maCurrentBlockPath.size());
498 unsigned int i = 0;
499 while (i < len && depthInfo.maCommonBlockPath[i] == maCurrentBlockPath[i])
500 ++i;
501 depthInfo.maCommonBlockPath.resize(i);
502 if (depthInfo.maCommonBlockPath == depthInfo.maDeclBlockPath)
504 maVarDeclMap.erase(varIt);
505 maVarUseSourceRangeMap.erase(varDecl);
506 return true;
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);
518 return true;
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());
526 return true;
529 bool ReduceVarScope::VisitLambdaExpr(const LambdaExpr* lambdaExpr)
531 if (ignoreLocation(lambdaExpr))
532 return true;
533 for (auto captureIt = lambdaExpr->capture_begin(); captureIt != lambdaExpr->capture_end();
534 ++captureIt)
536 const LambdaCapture& capture = *captureIt;
537 if (capture.capturesVariable())
539 auto varDecl = capture.getCapturedVar();
540 maVarDeclMap.erase(varDecl);
541 maVarUseSourceRangeMap.erase(varDecl);
544 return true;
547 loplugin::Plugin::Registration<ReduceVarScope> reducevarscope("reducevarscope", false);
550 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */