From 4c945b22fc42eb7a52864018cbca88358e71fd4d Mon Sep 17 00:00:00 2001 From: Noel Grandin Date: Fri, 21 Sep 2018 14:26:56 +0200 Subject: 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 --- basic/source/sbx/sbxarray.cxx | 50 ---- basic/source/sbx/sbxcoll.cxx | 13 - basic/source/sbx/sbxobj.cxx | 32 --- compilerplugins/clang/methodcycles.cxx | 382 +++++++++++++++++++++++++++++ compilerplugins/clang/methodcycles.py | 242 ++++++++++++++++++ compilerplugins/clang/methodcycles.results | 65 +++++ compilerplugins/clang/unusedmethods.cxx | 13 +- connectivity/source/parse/sqliterator.cxx | 67 ----- include/basic/sbx.hxx | 2 - include/basic/sbxobj.hxx | 1 - include/connectivity/sqliterator.hxx | 5 - include/vcl/outdev.hxx | 2 - sw/inc/dbgoutsw.hxx | 1 - sw/source/core/doc/dbgoutsw.cxx | 71 ------ vcl/source/outdev/pixel.cxx | 67 ----- 15 files changed, 698 insertions(+), 315 deletions(-) create mode 100644 compilerplugins/clang/methodcycles.cxx create mode 100755 compilerplugins/clang/methodcycles.py create mode 100644 compilerplugins/clang/methodcycles.results diff --git a/basic/source/sbx/sbxarray.cxx b/basic/source/sbx/sbxarray.cxx index 668b4df03bc5..628c7dc918af 100644 --- a/basic/source/sbx/sbxarray.cxx +++ b/basic/source/sbx/sbxarray.cxx @@ -341,56 +341,6 @@ void SbxArray::Merge( SbxArray* p ) } } -// Search of an element via the user data. If the element is -// object, it will also be scanned. - -SbxVariable* SbxArray::FindUserData( sal_uInt32 nData ) -{ - SbxVariable* p = nullptr; - for (auto& rEntry : mVarEntries) - { - if (!rEntry.mpVar.is()) - continue; - - if (rEntry.mpVar->IsVisible() && rEntry.mpVar->GetUserData() == nData) - { - p = rEntry.mpVar.get(); - p->ResetFlag( SbxFlagBits::ExtFound ); - break; // JSM 1995-10-06 - } - - // Did we have an array/object with extended search? - if (rEntry.mpVar->IsSet(SbxFlagBits::ExtSearch)) - { - switch (rEntry.mpVar->GetClass()) - { - case SbxClassType::Object: - { - // Objects are not allowed to scan their parent. - SbxFlagBits nOld = rEntry.mpVar->GetFlags(); - rEntry.mpVar->ResetFlag(SbxFlagBits::GlobalSearch); - p = static_cast(*rEntry.mpVar).FindUserData(nData); - rEntry.mpVar->SetFlags(nOld); - } - break; - case SbxClassType::Array: - // Casting SbxVariable to SbxArray? Really? - p = reinterpret_cast(*rEntry.mpVar).FindUserData(nData); - break; - default: - ; - } - - if (p) - { - p->SetFlag(SbxFlagBits::ExtFound); - break; - } - } - } - return p; -} - // Search of an element by his name and type. If an element is an object, // it will also be scanned.. diff --git a/basic/source/sbx/sbxcoll.cxx b/basic/source/sbx/sbxcoll.cxx index a4175c310318..ab69d3e032a6 100644 --- a/basic/source/sbx/sbxcoll.cxx +++ b/basic/source/sbx/sbxcoll.cxx @@ -87,19 +87,6 @@ void SbxCollection::Initialize() p->SetFlag( SbxFlagBits::DontStore ); } -SbxVariable* SbxCollection::FindUserData( sal_uInt32 nData ) -{ - if( GetParameters() ) - { - SbxObject* pObj = static_cast(GetObject()); - return pObj ? pObj->FindUserData( nData ) : nullptr; - } - else - { - return SbxObject::FindUserData( nData ); - } -} - SbxVariable* SbxCollection::Find( const OUString& rName, SbxClassType t ) { if( GetParameters() ) diff --git a/basic/source/sbx/sbxobj.cxx b/basic/source/sbx/sbxobj.cxx index a9bee2a803ed..d35b2aa0dc16 100644 --- a/basic/source/sbx/sbxobj.cxx +++ b/basic/source/sbx/sbxobj.cxx @@ -175,38 +175,6 @@ bool SbxObject::IsClass( const OUString& rName ) const return aClassName.equalsIgnoreAsciiCase( rName ); } -SbxVariable* SbxObject::FindUserData( sal_uInt32 nData ) -{ - SbxVariable* pRes = pMethods->FindUserData( nData ); - if( !pRes ) - { - pRes = pProps->FindUserData( nData ); - } - if( !pRes ) - { - pRes = pObjs->FindUserData( nData ); - } - // Search in the parents? - if( !pRes && IsSet( SbxFlagBits::GlobalSearch ) ) - { - SbxObject* pCur = this; - while( !pRes && pCur->pParent ) - { - // I myself was already searched! - SbxFlagBits nOwn = pCur->GetFlags(); - pCur->ResetFlag( SbxFlagBits::ExtSearch ); - // I search already global! - SbxFlagBits nPar = pCur->pParent->GetFlags(); - pCur->pParent->ResetFlag( SbxFlagBits::GlobalSearch ); - pRes = pCur->pParent->FindUserData( nData ); - pCur->SetFlags( nOwn ); - pCur->pParent->SetFlags( nPar ); - pCur = pCur->pParent; - } - } - return pRes; -} - SbxVariable* SbxObject::Find( const OUString& rName, SbxClassType t ) { #ifdef DBG_UTIL 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 +#include +#include +#include +#include +#include +#include + +#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 callMap; +static std::set definitionSet; + +class MethodCycles : public RecursiveASTVisitor, 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& 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& 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(functionDecl)) + { + aInfo.returnType = functionDecl->getReturnType().getCanonicalType().getAsString(); + } + else + { + aInfo.returnType = ""; + } + + if (auto methodDecl = dyn_cast(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(functionDecl) && dyn_cast(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 fromRoots; + findRoots(functionDeclFrom, fromRoots); + std::unordered_set 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& roots) +{ + bool bCalledSuperMethod = false; + if (auto methodDecl = dyn_cast(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(callee); + if (dr) + { + calleeFunctionDecl = dyn_cast(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(functionDecl); + if (methodDecl + && (methodDecl->size_overridden_methods() != 0 || methodDecl->hasAttr())) + 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(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 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 & rRet, VclPtr & pParent, VclBuilder::stringmap & rMap)" in caller: + to_be_removed.add(caller) + if "(VclPtr &,VclPtr &,std::::map, std::allocator > > &)" 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 &)" 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 &) + 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(functionDecl)) { + if (auto methodDecl = dyn_cast(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) diff --git a/connectivity/source/parse/sqliterator.cxx b/connectivity/source/parse/sqliterator.cxx index 5ba5c11325be..dcca0fc9db4a 100644 --- a/connectivity/source/parse/sqliterator.cxx +++ b/connectivity/source/parse/sqliterator.cxx @@ -780,73 +780,6 @@ void OSQLParseTreeIterator::getColumnRange( const OSQLParseNode* _pColumnRef, } -bool OSQLParseTreeIterator::getColumnTableRange(const OSQLParseNode* pNode, OUString &rTableRange) const -{ - OUString tmp; - if(impl_getColumnTableRange(pNode, tmp)) - { - rTableRange = tmp; - return true; - } - else - return false; -} - -bool OSQLParseTreeIterator::impl_getColumnTableRange(const OSQLParseNode* pNode, OUString &rTableRange) const -{ - // See if all columns belong to one table - if (SQL_ISRULE(pNode,column_ref)) - { - OUString aColName, aTableRange; - getColumnRange(pNode, aColName, aTableRange); - if (aTableRange.isEmpty()) // None found - { - // Look for the columns in the tables - for (auto const& table : *m_pImpl->m_pTables) - { - if (table.second.is()) - { - try - { - Reference< XNameAccess > xColumns = table.second->getColumns(); - if(xColumns->hasByName(aColName)) - { - Reference< XPropertySet > xColumn; - if (xColumns->getByName(aColName) >>= xColumn) - { - OSL_ENSURE(xColumn.is(),"Column isn't a propertyset!"); - aTableRange = table.first; - break; - } - } - } - catch(Exception&) - { - } - } - } - if (aTableRange.isEmpty()) - return false; - } - - - if (rTableRange.isEmpty()) - rTableRange = aTableRange; - else if (rTableRange != aTableRange) - return false; - } - else - { - for (sal_uInt32 i = 0, ncount = pNode->count(); i < ncount; i++) - { - if (!getColumnTableRange(pNode->getChild(i), rTableRange)) - return false; - } - } - return true; -} - - void OSQLParseTreeIterator::traverseCreateColumns(const OSQLParseNode* pSelectNode) { // aIteratorStatus.Clear(); diff --git a/include/basic/sbx.hxx b/include/basic/sbx.hxx index 4a51b99fd457..26cfa3b4d05d 100644 --- a/include/basic/sbx.hxx +++ b/include/basic/sbx.hxx @@ -135,7 +135,6 @@ public: void Merge( SbxArray* ); OUString GetAlias( sal_uInt16 ); void PutAlias( const OUString&, sal_uInt16 ); - SbxVariable* FindUserData( sal_uInt32 nUserData ); SbxVariable* Find( const OUString&, SbxClassType ); // Additional methods for 32-bit indices @@ -212,7 +211,6 @@ public: SbxCollection(); SbxCollection( const SbxCollection& ); SbxCollection& operator=( const SbxCollection& ); - virtual SbxVariable* FindUserData( sal_uInt32 nUserData ) override; virtual SbxVariable* Find( const OUString&, SbxClassType ) override; virtual void Clear() override; }; diff --git a/include/basic/sbxobj.hxx b/include/basic/sbxobj.hxx index 86aacecff5c9..7ad0953ae500 100644 --- a/include/basic/sbxobj.hxx +++ b/include/basic/sbxobj.hxx @@ -57,7 +57,6 @@ public: SbxProperty* GetDfltProperty(); void SetDfltProperty( const OUString& r ); // Search for an element - virtual SbxVariable* FindUserData( sal_uInt32 nUserData ); virtual SbxVariable* Find( const OUString&, SbxClassType ); SbxVariable* FindQualified( const OUString&, SbxClassType ); // Quick-Call-Interface for Methods diff --git a/include/connectivity/sqliterator.hxx b/include/connectivity/sqliterator.hxx index d10bc71ea81d..f62d6790a63f 100644 --- a/include/connectivity/sqliterator.hxx +++ b/include/connectivity/sqliterator.hxx @@ -255,9 +255,6 @@ namespace connectivity OUString &_rColumnName, OUString& _rTableRange); - // empty if ambiguous - bool getColumnTableRange(const OSQLParseNode* pNode, OUString &rTableRange) const; - // return true when the tableNode is a rule like catalog_name, schema_name or table_name static bool isTableNode(const OSQLParseNode* _pTableNode); @@ -268,8 +265,6 @@ namespace connectivity ::std::vector< TNodePair >& getJoinConditions() const; private: - // helper to implement getColumnTableRange - bool impl_getColumnTableRange(const OSQLParseNode* pNode, OUString &rTableRange) const; /** traverses the list of table names, and fills _rTables */ diff --git a/include/vcl/outdev.hxx b/include/vcl/outdev.hxx index 13ac5a5d05d4..f661bb377636 100644 --- a/include/vcl/outdev.hxx +++ b/include/vcl/outdev.hxx @@ -706,8 +706,6 @@ public: void DrawPixel( const Point& rPt ); void DrawPixel( const Point& rPt, const Color& rColor ); - void DrawPixel( const tools::Polygon& rPts, const Color* pColors ); - void DrawPixel( const tools::Polygon& rPts, const Color& rColor ); Color GetPixel( const Point& rPt ) const; ///@} diff --git a/sw/inc/dbgoutsw.hxx b/sw/inc/dbgoutsw.hxx index bc1d93f6c159..b32f410cc36d 100644 --- a/sw/inc/dbgoutsw.hxx +++ b/sw/inc/dbgoutsw.hxx @@ -64,7 +64,6 @@ SW_DLLPUBLIC const char * dbg_out(const SwpHints &rHints); SW_DLLPUBLIC const char * dbg_out(const SfxPoolItem & rItem); SW_DLLPUBLIC const char * dbg_out(const SfxPoolItem * pItem); SW_DLLPUBLIC const char * dbg_out(const SfxItemSet & rSet); -SW_DLLPUBLIC const char * dbg_out(SwNodes & rNodes); SW_DLLPUBLIC const char * dbg_out(const SwPosition & rPos); SW_DLLPUBLIC const char * dbg_out(const SwPaM & rPam); SW_DLLPUBLIC const char * dbg_out(const SwNodeNum & rNum); diff --git a/sw/source/core/doc/dbgoutsw.cxx b/sw/source/core/doc/dbgoutsw.cxx index a670fc83ded4..9ffbc68866a0 100644 --- a/sw/source/core/doc/dbgoutsw.cxx +++ b/sw/source/core/doc/dbgoutsw.cxx @@ -691,77 +691,6 @@ const char * dbg_out(const SwTextNode * pNode) return nullptr; } -static void lcl_dbg_nodes_inner(OUString & aStr, SwNodes & rNodes, sal_uLong & nIndex) -{ - SwNode * pNode = rNodes[nIndex]; - SwStartNode * pStartNode = dynamic_cast (pNode); - - SwNode * pEndNode = nullptr; - if (pStartNode != nullptr) - pEndNode = pStartNode->EndOfSectionNode(); - - sal_uLong nCount = rNodes.Count(); - sal_uLong nStartIndex = nIndex; - - bool bDone = false; - - OUString aTag; - if (pNode->IsTableNode()) - aTag += "table"; - else if (pNode->IsSectionNode()) - aTag += "section"; - else - aTag += "nodes"; - - aStr += "<"; - aStr += aTag; - aStr += ">"; - - while (! bDone) - { - if (pNode->IsStartNode() && nIndex != nStartIndex) - lcl_dbg_nodes_inner(aStr, rNodes, nIndex); - else - { - aStr += lcl_dbg_out(*pNode); - aStr += "\n"; - - nIndex++; - } - - if (pNode == pEndNode || nIndex >= nCount) - bDone = true; - else - pNode = rNodes[nIndex]; - } - - aStr += "\n"; -} - -static OUString lcl_dbg_out(SwNodes & rNodes) -{ - OUString aStr(""); - - sal_uLong nIndex = 0; - sal_uLong nCount = rNodes.Count(); - - while (nIndex < nCount) - { - lcl_dbg_nodes_inner(aStr, rNodes, nIndex); - } - - aStr += "\n"; - - return aStr; -} - -const char * dbg_out(SwNodes & rNodes) -{ - return dbg_out(lcl_dbg_out(rNodes)); -} - static OUString lcl_dbg_out(const SwUndo & rUndo) { return "[ " + OUString::number(static_cast(rUndo.GetId())) diff --git a/vcl/source/outdev/pixel.cxx b/vcl/source/outdev/pixel.cxx index 270a50bf6710..b00835fe96db 100644 --- a/vcl/source/outdev/pixel.cxx +++ b/vcl/source/outdev/pixel.cxx @@ -110,71 +110,4 @@ void OutputDevice::DrawPixel( const Point& rPt, const Color& rColor ) mpAlphaVDev->DrawPixel( rPt ); } -void OutputDevice::DrawPixel( const tools::Polygon& rPts, const Color* pColors ) -{ - assert(!is_double_buffered_window()); - - if ( !pColors ) - { - DrawPixel( rPts, GetLineColor() ); - } - else - { - SAL_WARN_IF( !pColors, "vcl", "OutputDevice::DrawPixel: No color array specified" ); - - const sal_uInt16 nSize = rPts.GetSize(); - - if ( nSize ) - { - if ( mpMetaFile ) - { - for ( sal_uInt16 i = 0; i < nSize; i++ ) - { - mpMetaFile->AddAction( new MetaPixelAction( rPts[ i ], pColors[ i ] ) ); - } - } - if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) - return; - - if ( mpGraphics || AcquireGraphics() ) - { - if ( mbInitClipRegion ) - InitClipRegion(); - - if ( mbOutputClipped ) - return; - - for ( sal_uInt16 i = 0; i < nSize; i++ ) - { - const Point aPt( ImplLogicToDevicePixel( rPts[ i ] ) ); - mpGraphics->DrawPixel( aPt.X(), aPt.Y(), pColors[ i ], this ); - } - } - } - } - - if( mpAlphaVDev ) - mpAlphaVDev->DrawPixel( rPts, pColors ); -} - -void OutputDevice::DrawPixel( const tools::Polygon& rPts, const Color& rColor ) -{ - assert(!is_double_buffered_window()); - - if( rColor != COL_TRANSPARENT && ! ImplIsRecordLayout() ) - { - const sal_uInt16 nSize = rPts.GetSize(); - std::unique_ptr pColArray(new Color[ nSize ]); - - for( sal_uInt16 i = 0; i < nSize; i++ ) - { - pColArray[ i ] = rColor; - } - DrawPixel( rPts, pColArray.get() ); - } - - if( mpAlphaVDev ) - mpAlphaVDev->DrawPixel( rPts, rColor ); -} - /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit