summaryrefslogtreecommitdiff
path: root/compilerplugins
diff options
context:
space:
mode:
authorNoel Grandin <noel.grandin@collabora.co.uk>2018-09-21 14:26:56 +0200
committerNoel Grandin <noel.grandin@collabora.co.uk>2018-09-21 18:44:29 +0200
commit4c945b22fc42eb7a52864018cbca88358e71fd4d (patch)
tree582dad7232dcd43e00029f5573f9896a93c71ca8 /compilerplugins
parent893f2128ed451120e41dba1ee4c2f512ec6bb20e (diff)
new loplugin:methodcycles
look for closed cycles of methods i.e. unused code that would not otherwise be found by the unusedmethods loplugin Change-Id: I3fb052132d97aabca573bb8e9fc424201b1e2042 Reviewed-on: https://gerrit.libreoffice.org/60875 Tested-by: Jenkins Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
Diffstat (limited to 'compilerplugins')
-rw-r--r--compilerplugins/clang/methodcycles.cxx382
-rwxr-xr-xcompilerplugins/clang/methodcycles.py242
-rw-r--r--compilerplugins/clang/methodcycles.results65
-rw-r--r--compilerplugins/clang/unusedmethods.cxx13
4 files changed, 698 insertions, 4 deletions
diff --git a/compilerplugins/clang/methodcycles.cxx b/compilerplugins/clang/methodcycles.cxx
new file mode 100644
index 000000000000..20a31171c001
--- /dev/null
+++ b/compilerplugins/clang/methodcycles.cxx
@@ -0,0 +1,382 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cassert>
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <set>
+#include <unordered_set>
+#include <map>
+
+#include "clang/AST/Attr.h"
+
+#include "plugin.hxx"
+#include "compat.hxx"
+
+/**
+What we are looking for here are methods that are not reachable from any of the program
+entry points.
+"Entry points" includes main, and various binary API
+
+Mostly that means we end up finding cycles of methods i.e. methods that refer to each
+other, but are not reachable.
+
+It does so, by dumping various call/definition info to a log file.
+Be warned that it produces around 20G of log file.
+
+Then we will post-process the log file with a python script, which takes about
+15min to run on a fast machine.
+
+The process goes something like this:
+ $ make check
+ $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='methodcycles' check
+ $ ./compilerplugins/clang/methodcycles.py
+
+Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around
+to get it to work :-)
+
+*/
+
+namespace
+{
+struct MyFuncInfo
+{
+ std::string returnType;
+ std::string nameAndParams;
+ std::string sourceLocation;
+};
+bool operator<(const MyFuncInfo& lhs, const MyFuncInfo& rhs)
+{
+ return std::tie(lhs.returnType, lhs.nameAndParams)
+ < std::tie(rhs.returnType, rhs.nameAndParams);
+}
+
+// try to limit the voluminous output a little
+static std::multimap<const FunctionDecl*, const FunctionDecl*> callMap;
+static std::set<MyFuncInfo> definitionSet;
+
+class MethodCycles : public RecursiveASTVisitor<MethodCycles>, public loplugin::Plugin
+{
+public:
+ explicit MethodCycles(loplugin::InstantiationData const& data)
+ : Plugin(data)
+ {
+ }
+
+ virtual void run() override
+ {
+ TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
+
+ // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
+ // writing to the same logfile
+
+ std::string output;
+ for (const MyFuncInfo& s : definitionSet)
+ output += "definition:\t" + s.returnType + "\t" + s.nameAndParams + "\t"
+ + s.sourceLocation + "\n";
+ for (const std::pair<const FunctionDecl*, const FunctionDecl*>& pair : callMap)
+ {
+ if (!isLocationMine(pair.first->getLocation())
+ || !isLocationMine(pair.second->getLocation()))
+ continue;
+ auto niceNameFrom = niceName(pair.first);
+ auto niceNameTo = niceName(pair.second);
+ output += "call:\t" + niceNameFrom.returnType + "\t" + niceNameFrom.nameAndParams + "\t"
+ + niceNameTo.returnType + "\t" + niceNameTo.nameAndParams + "\n";
+ }
+ std::ofstream myfile;
+ myfile.open(WORKDIR "/loplugin.methodcycles.log", std::ios::app | std::ios::out);
+ myfile << output;
+ myfile.close();
+ }
+
+ bool shouldVisitTemplateInstantiations() const { return true; }
+ bool shouldVisitImplicitCode() const { return true; }
+
+ bool VisitCallExpr(CallExpr*);
+ bool VisitFunctionDecl(const FunctionDecl* decl);
+ bool VisitDeclRefExpr(const DeclRefExpr*);
+ bool VisitCXXConstructExpr(const CXXConstructExpr*);
+
+ bool TraverseFunctionDecl(FunctionDecl*);
+ bool TraverseCXXMethodDecl(CXXMethodDecl*);
+ bool TraverseCXXConstructorDecl(CXXConstructorDecl*);
+ bool TraverseCXXConversionDecl(CXXConversionDecl*);
+ bool TraverseCXXDestructorDecl(CXXDestructorDecl*);
+#if CLANG_VERSION >= 50000
+ bool TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl*);
+#endif
+private:
+ void logCallToRootMethods(const FunctionDecl* functionDeclFrom,
+ const FunctionDecl* functionDeclTo);
+ void findRoots(const FunctionDecl* functionDecl,
+ std::unordered_set<const FunctionDecl*>& roots);
+ MyFuncInfo niceName(const FunctionDecl* functionDecl);
+ bool isLocationMine(SourceLocation loc);
+ std::string toString(SourceLocation loc);
+ FunctionDecl const* currentFunctionDecl = nullptr;
+};
+
+MyFuncInfo MethodCycles::niceName(const FunctionDecl* functionDecl)
+{
+ if (functionDecl->getInstantiatedFromMemberFunction())
+ functionDecl = functionDecl->getInstantiatedFromMemberFunction();
+ else if (functionDecl->getClassScopeSpecializationPattern())
+ functionDecl = functionDecl->getClassScopeSpecializationPattern();
+ else if (functionDecl->getTemplateInstantiationPattern())
+ functionDecl = functionDecl->getTemplateInstantiationPattern();
+
+ MyFuncInfo aInfo;
+ if (!isa<CXXConstructorDecl>(functionDecl))
+ {
+ aInfo.returnType = functionDecl->getReturnType().getCanonicalType().getAsString();
+ }
+ else
+ {
+ aInfo.returnType = "";
+ }
+
+ if (auto methodDecl = dyn_cast<CXXMethodDecl>(functionDecl))
+ {
+ const CXXRecordDecl* recordDecl = methodDecl->getParent();
+ aInfo.nameAndParams
+ = recordDecl->getQualifiedNameAsString() + "::" + functionDecl->getNameAsString() + "(";
+ }
+ else
+ {
+ aInfo.nameAndParams = functionDecl->getQualifiedNameAsString() + "(";
+ }
+ bool bFirst = true;
+ for (const ParmVarDecl* pParmVarDecl : compat::parameters(*functionDecl))
+ {
+ if (bFirst)
+ bFirst = false;
+ else
+ aInfo.nameAndParams += ",";
+ aInfo.nameAndParams += pParmVarDecl->getType().getCanonicalType().getAsString();
+ }
+ aInfo.nameAndParams += ")";
+ if (isa<CXXMethodDecl>(functionDecl) && dyn_cast<CXXMethodDecl>(functionDecl)->isConst())
+ {
+ aInfo.nameAndParams += " const";
+ }
+
+ aInfo.sourceLocation = toString(functionDecl->getLocation());
+
+ return aInfo;
+}
+
+std::string MethodCycles::toString(SourceLocation loc)
+{
+ SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc(loc);
+ StringRef name = compiler.getSourceManager().getFilename(expansionLoc);
+ std::string sourceLocation
+ = std::string(name.substr(strlen(SRCDIR) + 1)) + ":"
+ + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
+ loplugin::normalizeDotDotInFilePath(sourceLocation);
+ return sourceLocation;
+}
+
+bool MethodCycles::isLocationMine(SourceLocation loc)
+{
+ SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc(loc);
+ if (compiler.getSourceManager().isInSystemHeader(expansionLoc))
+ return false;
+ const char* bufferName = compiler.getSourceManager().getPresumedLoc(expansionLoc).getFilename();
+ if (bufferName == NULL)
+ return false;
+ if (loplugin::hasPathnamePrefix(bufferName, WORKDIR "/")
+ || loplugin::hasPathnamePrefix(bufferName, BUILDDIR "/")
+ || loplugin::hasPathnamePrefix(bufferName, SRCDIR "/"))
+ return true; // ok
+ return false;
+}
+
+void MethodCycles::logCallToRootMethods(const FunctionDecl* functionDeclFrom,
+ const FunctionDecl* functionDeclTo)
+{
+ if (!functionDeclFrom)
+ {
+ // template magic mostly, but also things called from initialisers
+ return;
+ }
+ functionDeclFrom = functionDeclFrom->getCanonicalDecl();
+ functionDeclTo = functionDeclTo->getCanonicalDecl();
+
+ std::unordered_set<const FunctionDecl*> fromRoots;
+ findRoots(functionDeclFrom, fromRoots);
+ std::unordered_set<const FunctionDecl*> toRoots;
+ findRoots(functionDeclTo, toRoots);
+
+ for (auto const& from : fromRoots)
+ for (auto const& to : toRoots)
+ callMap.insert({ from, to });
+}
+
+void MethodCycles::findRoots(const FunctionDecl* functionDecl,
+ std::unordered_set<const FunctionDecl*>& roots)
+{
+ bool bCalledSuperMethod = false;
+ if (auto methodDecl = dyn_cast<CXXMethodDecl>(functionDecl))
+ {
+ // For virtual/overriding methods, we need to pretend we called from/to root method(s),
+ // so that they get marked as used.
+ for (auto it = methodDecl->begin_overridden_methods();
+ it != methodDecl->end_overridden_methods(); ++it)
+ {
+ findRoots(*it, roots);
+ bCalledSuperMethod = true;
+ }
+ }
+ if (!bCalledSuperMethod)
+ {
+ while (functionDecl->getTemplateInstantiationPattern())
+ functionDecl = functionDecl->getTemplateInstantiationPattern();
+ if (functionDecl->getLocation().isValid())
+ roots.insert(functionDecl);
+ }
+}
+
+bool MethodCycles::VisitCallExpr(CallExpr* expr)
+{
+ // Note that I don't ignore ANYTHING here, because I want to get calls to my code that result
+ // from template instantiation deep inside the STL and other external code
+
+ FunctionDecl* calleeFunctionDecl = expr->getDirectCallee();
+ if (calleeFunctionDecl == nullptr)
+ {
+ Expr* callee = expr->getCallee()->IgnoreParenImpCasts();
+ DeclRefExpr* dr = dyn_cast<DeclRefExpr>(callee);
+ if (dr)
+ {
+ calleeFunctionDecl = dyn_cast<FunctionDecl>(dr->getDecl());
+ if (calleeFunctionDecl)
+ goto gotfunc;
+ }
+ return true;
+ }
+
+gotfunc:
+
+ if (currentFunctionDecl != calleeFunctionDecl)
+ // ignore recursive calls
+ logCallToRootMethods(currentFunctionDecl, calleeFunctionDecl);
+
+ return true;
+}
+
+bool MethodCycles::VisitCXXConstructExpr(const CXXConstructExpr* constructExpr)
+{
+ // Note that I don't ignore ANYTHING here, because I want to get calls to my code that result
+ // from template instantiation deep inside the STL and other external code
+
+ const CXXConstructorDecl* constructorDecl = constructExpr->getConstructor();
+ constructorDecl = constructorDecl->getCanonicalDecl();
+
+ if (!constructorDecl->getLocation().isValid())
+ {
+ return true;
+ }
+
+ logCallToRootMethods(currentFunctionDecl, constructorDecl);
+
+ return true;
+}
+
+bool MethodCycles::VisitFunctionDecl(const FunctionDecl* functionDecl)
+{
+ const FunctionDecl* canonicalFunctionDecl = functionDecl->getCanonicalDecl();
+ if (functionDecl->isDeleted())
+ return true;
+ // don't care about compiler-generated functions
+ if (functionDecl->isImplicit())
+ return true;
+ if (!canonicalFunctionDecl->getLocation().isValid())
+ return true;
+ // ignore method overrides, since the call will show up as being directed to the root method
+ const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
+ if (methodDecl
+ && (methodDecl->size_overridden_methods() != 0 || methodDecl->hasAttr<OverrideAttr>()))
+ return true;
+ if (!isLocationMine(canonicalFunctionDecl->getLocation()))
+ return true;
+
+ MyFuncInfo funcInfo = niceName(canonicalFunctionDecl);
+ definitionSet.insert(funcInfo);
+ return true;
+}
+
+bool MethodCycles::VisitDeclRefExpr(const DeclRefExpr* declRefExpr)
+{
+ const FunctionDecl* functionDecl = dyn_cast<FunctionDecl>(declRefExpr->getDecl());
+ if (!functionDecl)
+ {
+ return true;
+ }
+ logCallToRootMethods(currentFunctionDecl, functionDecl->getCanonicalDecl());
+
+ return true;
+}
+
+bool MethodCycles::TraverseFunctionDecl(FunctionDecl* f)
+{
+ auto copy = currentFunctionDecl;
+ currentFunctionDecl = f;
+ bool ret = RecursiveASTVisitor::TraverseFunctionDecl(f);
+ currentFunctionDecl = copy;
+ return ret;
+}
+bool MethodCycles::TraverseCXXMethodDecl(CXXMethodDecl* f)
+{
+ auto copy = currentFunctionDecl;
+ currentFunctionDecl = f;
+ bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(f);
+ currentFunctionDecl = copy;
+ return ret;
+}
+bool MethodCycles::TraverseCXXConversionDecl(CXXConversionDecl* f)
+{
+ auto copy = currentFunctionDecl;
+ currentFunctionDecl = f;
+ bool ret = RecursiveASTVisitor::TraverseCXXConversionDecl(f);
+ currentFunctionDecl = copy;
+ return ret;
+}
+#if CLANG_VERSION >= 50000
+bool MethodCycles::TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl* f)
+{
+ auto copy = currentFunctionDecl;
+ currentFunctionDecl = f;
+ bool ret = RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(f);
+ currentFunctionDecl = copy;
+ return ret;
+}
+#endif
+bool MethodCycles::TraverseCXXConstructorDecl(CXXConstructorDecl* f)
+{
+ auto copy = currentFunctionDecl;
+ currentFunctionDecl = f;
+ bool ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(f);
+ currentFunctionDecl = copy;
+ return ret;
+}
+bool MethodCycles::TraverseCXXDestructorDecl(CXXDestructorDecl* f)
+{
+ auto copy = currentFunctionDecl;
+ currentFunctionDecl = f;
+ bool ret = RecursiveASTVisitor::TraverseCXXDestructorDecl(f);
+ currentFunctionDecl = copy;
+ return ret;
+}
+
+loplugin::Plugin::Registration<MethodCycles> X("methodcycles", false);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/methodcycles.py b/compilerplugins/clang/methodcycles.py
new file mode 100755
index 000000000000..8ff814da8bd5
--- /dev/null
+++ b/compilerplugins/clang/methodcycles.py
@@ -0,0 +1,242 @@
+#!/usr/bin/python
+
+from collections import defaultdict
+import io
+import re
+import subprocess
+import sys
+
+# --------------------------------------------------------------------------------------------
+# globals
+# --------------------------------------------------------------------------------------------
+
+definitionSet = set() # set of method_name
+definitionToSourceLocationMap = dict()
+
+# for the "unused methods" analysis
+callDict = defaultdict(set) # map of from_method_name -> set(method_name)
+
+# clang does not always use exactly the same numbers in the type-parameter vars it generates
+# so I need to substitute them to ensure we can match correctly.
+normalizeTypeParamsRegex = re.compile(r"type-parameter-\d+-\d+")
+def normalizeTypeParams( line ):
+ line = normalizeTypeParamsRegex.sub("type-parameter-?-?", line)
+ # make some of the types a little prettier
+ line = line.replace("std::__debug", "std::")
+ line = line.replace("class ", "")
+ line = line.replace("struct ", "")
+ line = line.replace("_Bool", "bool")
+ return line
+
+# --------------------------------------------------------------------------------------------
+# primary input loop
+# --------------------------------------------------------------------------------------------
+
+cnt = 0
+with io.open("workdir/loplugin.methodcycles.log2", "rb", buffering=1024*1024) as txt:
+ for line in txt:
+ tokens = line.strip().split("\t")
+ if tokens[0] == "definition:":
+ returnType = tokens[1]
+ nameAndParams = tokens[2]
+ sourceLocation = tokens[3]
+ funcInfo = (normalizeTypeParams(returnType) + " " + normalizeTypeParams(nameAndParams)).strip()
+ definitionSet.add(funcInfo)
+ definitionToSourceLocationMap[funcInfo] = sourceLocation
+ elif tokens[0] == "call:":
+ returnTypeFrom = tokens[1]
+ nameAndParamsFrom = tokens[2]
+ returnTypeTo = tokens[3]
+ nameAndParamsTo = tokens[4]
+ caller = (normalizeTypeParams(returnTypeFrom) + " " + normalizeTypeParams(nameAndParamsFrom)).strip()
+ callee = (normalizeTypeParams(returnTypeTo) + " " + normalizeTypeParams(nameAndParamsTo)).strip()
+ callDict[caller].add(callee)
+ else:
+ print( "unknown line: " + line)
+ cnt = cnt + 1
+ #if cnt > 100000: break
+
+# Invert the definitionToSourceLocationMap.
+# If we see more than one method at the same sourceLocation, it's being autogenerated as part of a template
+# and we should just ignore it.
+#sourceLocationToDefinitionMap = {}
+#for k, v in definitionToSourceLocationMap.iteritems():
+# sourceLocationToDefinitionMap[v] = sourceLocationToDefinitionMap.get(v, [])
+# sourceLocationToDefinitionMap[v].append(k)
+#for k, definitions in sourceLocationToDefinitionMap.iteritems():
+# if len(definitions) > 1:
+# for d in definitions:
+# definitionSet.remove(d)
+
+# sort the results using a "natural order" so sequences like [item1,item2,item10] sort nicely
+def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
+ return [int(text) if text.isdigit() else text.lower()
+ for text in re.split(_nsre, s)]
+def sort_set_by_natural_key(s):
+ return sorted(s, key=lambda v: natural_sort_key(v[1]))
+
+
+# --------------------------------------------------------------------------------------------
+# analysis
+# --------------------------------------------------------------------------------------------
+
+# follow caller-callee chains, removing all methods reachable from a root method
+def remove_reachable(startCaller):
+ worklist = list()
+ worklist.append(startCaller)
+ while len(worklist) > 0:
+ caller = worklist.pop()
+ if not caller in callDict:
+ continue
+ calleeSet = callDict[caller]
+ del callDict[caller]
+ if caller in definitionSet:
+ definitionSet.remove(caller)
+ for c in calleeSet:
+ worklist.append(c)
+
+# look for all the external entry points and remove code called from there
+to_be_removed = set()
+to_be_removed.add("int main(int,char **)")
+# random dynload entrypoints that we don't otherwise find
+to_be_removed.add("bool TestImportOLE2(SvStream &)")
+to_be_removed.add("void SbiRuntime::StepREDIMP()")
+to_be_removed.add("_object * (anonymous namespace)::createUnoStructHelper(_object *,_object *,_object *)");
+for caller in definitionSet:
+ if not caller in definitionToSourceLocationMap:
+ to_be_removed.append(caller)
+ continue
+ location = definitionToSourceLocationMap[caller]
+ if "include/com/" in location \
+ or "include/cppu/" in location \
+ or "include/cppuhelper/" in location \
+ or "include/osl/" in location \
+ or "include/rtl/" in location \
+ or "include/sal/" in location \
+ or "include/salhelper/" in location \
+ or "include/systools/" in location \
+ or "include/typelib/" in location \
+ or "include/uno/" in location \
+ or "workdir/UnpackedTarball/" in location \
+ or "workdir/UnoApiHeadersTarget/" in location \
+ or "workdir/CustomTarget/officecfg/" in location \
+ or "workdir/LexTarget/" in location \
+ or "workdir/CustomTarget/i18npool/localedata/" in location \
+ or "workdir/SdiTarget/" in location \
+ or "/qa/" in location \
+ or "include/test/" in location:
+ to_be_removed.add(caller)
+ # TODO calls to destructors are not mentioned in the AST, so we'll just have to assume they get called,
+ # which is not ideal
+ if "::~" in caller:
+ to_be_removed.add(caller)
+ # dyload entry points for VCL builder
+ if "(VclPtr<vcl::Window> & rRet, VclPtr<vcl::Window> & pParent, VclBuilder::stringmap & rMap)" in caller:
+ to_be_removed.add(caller)
+ if "(VclPtr<vcl::Window> &,VclPtr<vcl::Window> &,std::::map<rtl::OString, rtl::OUString, std::less<rtl::OString>, std::allocator<std::pair<const rtl::OString, rtl::OUString> > > &)" in caller:
+ to_be_removed.add(caller)
+# find all the UNO load-by-symbol-name entrypoints
+uno_constructor_entrypoints = set()
+git_grep_process = subprocess.Popen("git grep -h 'constructor=' -- *.component", stdout=subprocess.PIPE, shell=True)
+with git_grep_process.stdout as txt:
+ for line in txt:
+ idx1 = line.find("\"")
+ idx2 = line.find("\"", idx1 + 1)
+ func = line[idx1+1 : idx2]
+ uno_constructor_entrypoints.add(func)
+for caller in callDict:
+ if "(com::sun::star::uno::XComponentContext *,const com::sun::star::uno::Sequence<com::sun::star::uno::Any> &)" in caller:
+ for func in uno_constructor_entrypoints:
+ if func in caller:
+ to_be_removed.add(caller)
+# remove everything reachable from the found entry points
+for caller in to_be_removed:
+ remove_reachable(caller)
+for caller in callDict:
+ callDict[caller] -= to_be_removed
+
+print_tree_recurse_set = set() # protect against cycles
+def print_tree(f, caller, depth):
+ if depth == 0:
+ f.write("\n") # add an empty line before each tree
+ print_tree_recurse_set.clear()
+ # protect against cycles
+ if caller in print_tree_recurse_set:
+ return
+ # when printing out trees, things that are not in the map are things that are reachable,
+ # so we're not interested in them
+ if not caller in callDict:
+ return
+ print_tree_recurse_set.add(caller)
+ f.write(" " * depth + caller + "\n")
+ f.write(" " * depth + definitionToSourceLocationMap[caller] + "\n")
+ calleeSet = callDict[caller]
+ for c in calleeSet:
+ print_tree(f, c, depth+1)
+
+# create a reverse call graph
+inverseCallDict = defaultdict(set) # map of from_method_name -> set(method_name)
+for caller in callDict:
+ for callee in callDict[caller]:
+ inverseCallDict[callee].add(caller)
+
+# find possible roots (ie. entrypoints) by looking for methods that are not called
+def dump_possible_roots():
+ possibleRootList = list()
+ for caller in callDict:
+ if not caller in inverseCallDict and caller in definitionToSourceLocationMap:
+ possibleRootList.append(caller)
+ possibleRootList.sort()
+
+ # print out first 100 trees of caller->callees
+ count = 0
+ with open("compilerplugins/clang/methodcycles.roots", "wt") as f:
+ f.write("callDict size " + str(len(callDict)) + "\n")
+ f.write("possibleRootList size " + str(len(possibleRootList)) + "\n")
+ f.write("\n")
+ for caller in possibleRootList:
+ f.write(caller + "\n")
+ f.write(" " + definitionToSourceLocationMap[caller] + "\n")
+ #print_tree(f, caller, 0)
+ count = count + 1
+ #if count>1000: break
+
+
+# Look for cycles in a directed graph
+# Adapted from:
+# https://codereview.stackexchange.com/questions/86021/check-if-a-directed-graph-contains-a-cycle
+with open("compilerplugins/clang/methodcycles.results", "wt") as f:
+ path = set()
+ visited = set()
+
+ def printPath(path):
+ if len(path) < 2:
+ return
+ # we may have found a cycle, but if the cycle is called from outside the cycle
+ # the code is still in use.
+ for p in path:
+ for caller in inverseCallDict[p]:
+ if not caller in path:
+ return
+ f.write("found cycle\n")
+ for p in path:
+ f.write(" " + p + "\n")
+ f.write(" " + definitionToSourceLocationMap[p] + "\n")
+ f.write("\n")
+
+ def checkCyclic(vertex):
+ if vertex in visited:
+ return
+ visited.add(vertex)
+ path.add(vertex)
+ if vertex in callDict:
+ for neighbour in callDict[vertex]:
+ if neighbour in path:
+ printPath(path)
+ break
+ else:
+ checkCyclic(neighbour)
+ path.remove(vertex)
+
+ for caller in callDict:
+ checkCyclic(caller)
diff --git a/compilerplugins/clang/methodcycles.results b/compilerplugins/clang/methodcycles.results
new file mode 100644
index 000000000000..403178b25a10
--- /dev/null
+++ b/compilerplugins/clang/methodcycles.results
@@ -0,0 +1,65 @@
+found cycle
+ bool connectivity::OSQLParseTreeIterator::impl_getColumnTableRange(const connectivity::OSQLParseNode *,rtl::OUString &) const
+ include/connectivity/sqliterator.hxx:272
+
+ bool connectivity::OSQLParseTreeIterator::getColumnTableRange(const connectivity::OSQLParseNode *,rtl::OUString &) const
+ include/connectivity/sqliterator.hxx:259
+
+found cycle
+ void (anonymous namespace)::traceValue(_typelib_TypeDescriptionReference *,void *)
+ cppu/source/LogBridge/LogBridge.cxx:132
+
+ void uno_ext_getMapping(_uno_Mapping **,_uno_Environment *,_uno_Environment *)
+ cppu/source/UnsafeBridge/UnsafeBridge.cxx:133
+
+ void LogProbe(bool,void *,void *,_typelib_TypeDescriptionReference *,_typelib_MethodParameter *,int,const _typelib_TypeDescription *,void *,void **,_uno_Any **)
+ cppu/source/LogBridge/LogBridge.cxx:192
+
+found cycle
+ rtl::OUString lcl_dbg_out(SwNodes &)
+ sw/source/core/doc/dbgoutsw.cxx:743
+
+ void lcl_dbg_nodes_inner(rtl::OUString &,SwNodes &,unsigned long &)
+ sw/source/core/doc/dbgoutsw.cxx:694
+
+ const char * dbg_out(SwNodes &)
+ sw/inc/dbgoutsw.hxx:67
+
+found cycle
+ void OutputDevice::DrawPixel(const tools::Polygon &,const Color &)
+ include/vcl/outdev.hxx:710
+
+ void OutputDevice::DrawPixel(const tools::Polygon &,const Color *)
+ include/vcl/outdev.hxx:709
+
+found cycle
+ void SbxVariable::Dump(SvStream &,bool)
+ include/basic/sbxvar.hxx:260
+
+ void SbxObject::Dump(SvStream &,bool)
+ include/basic/sbxobj.hxx:81
+
+ void SbRtl_DumpAllObjects(StarBASIC *,SbxArray &,bool)
+ basic/source/inc/rtlproto.hxx:293
+
+found cycle
+ SbxVariable * SbxArray::FindUserData(unsigned int)
+ include/basic/sbx.hxx:138
+
+ SbxVariable * SbxObject::FindUserData(unsigned int)
+ include/basic/sbxobj.hxx:60
+
+found cycle
+ unsigned long slideshow::internal::hash::operator()(const type-parameter-?-? &) const
+ slideshow/source/inc/tools.hxx:83
+
+ unsigned long com::sun::star::uno::hash_value(const Reference<type-parameter-?-?> &)
+ slideshow/source/inc/tools.hxx:93
+
+found cycle
+ void ScDPResultDimension::DumpState(const ScDPResultMember *,ScDocument *,ScAddress &) const
+ sc/inc/dptabres.hxx:583
+
+ void ScDPResultMember::DumpState(const ScDPResultMember *,ScDocument *,ScAddress &) const
+ sc/inc/dptabres.hxx:413
+
diff --git a/compilerplugins/clang/unusedmethods.cxx b/compilerplugins/clang/unusedmethods.cxx
index a84b7e8d237d..5f998712d3ef 100644
--- a/compilerplugins/clang/unusedmethods.cxx
+++ b/compilerplugins/clang/unusedmethods.cxx
@@ -153,14 +153,19 @@ MyFuncInfo UnusedMethods::niceName(const FunctionDecl* functionDecl)
aInfo.returnType = "";
}
- if (const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl)) {
+ if (auto methodDecl = dyn_cast<CXXMethodDecl>(functionDecl)) {
const CXXRecordDecl* recordDecl = methodDecl->getParent();
- aInfo.nameAndParams += recordDecl->getQualifiedNameAsString();
- aInfo.nameAndParams += "::";
+ aInfo.nameAndParams = recordDecl->getQualifiedNameAsString()
+ + "::"
+ + functionDecl->getNameAsString()
+ + "(";
if (methodDecl->isVirtual())
aInfo.virtualness = "virtual";
}
- aInfo.nameAndParams += functionDecl->getNameAsString() + "(";
+ else
+ {
+ aInfo.nameAndParams = functionDecl->getQualifiedNameAsString() + "(";
+ }
bool bFirst = true;
for (const ParmVarDecl *pParmVarDecl : compat::parameters(*functionDecl)) {
if (bFirst)