diff options
author | Noel Grandin <noel@peralex.com> | 2016-08-11 13:03:04 +0200 |
---|---|---|
committer | Noel Grandin <noel@peralex.com> | 2016-08-11 13:03:54 +0200 |
commit | 045879b5578203c2a6c38a1bce4d9b4fa7cc5cb0 (patch) | |
tree | 3eebc90c94b8814cd511894bb75afcac688a31ff | |
parent | 5037dc9b23a9ef0ba44ed0c371da6af7332a7519 (diff) |
new loplugin unusedenumvalues
Change-Id: I2efcb0332cca23bd53088121b4cbfc702011d0b9
-rw-r--r-- | compilerplugins/clang/unusedenumvalues.cxx | 133 | ||||
-rwxr-xr-x | compilerplugins/clang/unusedenumvalues.py | 93 |
2 files changed, 226 insertions, 0 deletions
diff --git a/compilerplugins/clang/unusedenumvalues.cxx b/compilerplugins/clang/unusedenumvalues.cxx new file mode 100644 index 000000000000..a478dfd5a570 --- /dev/null +++ b/compilerplugins/clang/unusedenumvalues.cxx @@ -0,0 +1,133 @@ +/* -*- 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 "plugin.hxx" +#include "compat.hxx" + +/** +Looks for enums with unused constants. + +Be warned that it produces around 5G of log file. + +The process goes something like this: + $ make check + $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='unusedenumconstants' check + $ ./compilerplugins/clang/unusedenumconstants.py + +Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around +to get it to work :-) + +TODO ignore constants when we are only testing for them, not actually storing them somewhere + +*/ + +namespace { + +struct MyEnumValueInfo +{ + std::string parentClass; + std::string constantName; + std::string sourceLocation; +}; +bool operator < (const MyEnumValueInfo &lhs, const MyEnumValueInfo &rhs) +{ + return std::tie(lhs.parentClass, lhs.constantName) + < std::tie(rhs.parentClass, rhs.constantName); +} + + +// try to limit the voluminous output a little +static std::set<MyEnumValueInfo> touchedSet; +static std::set<MyEnumValueInfo> definitionSet; + + +class UnusedEnumValues: + public RecursiveASTVisitor<UnusedEnumValues>, public loplugin::Plugin +{ +public: + explicit UnusedEnumValues(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 MyEnumValueInfo & s : touchedSet) + output += "touch:\t" + s.parentClass + "\t" + s.constantName + "\n"; + for (const MyEnumValueInfo & s : definitionSet) + { + output += "definition:\t" + s.parentClass + "\t" + s.constantName + "\t" + s.sourceLocation + "\n"; + } + ofstream myfile; + myfile.open( SRCDIR "/loplugin.unusedenumvalues.log", ios::app | ios::out); + myfile << output; + myfile.close(); + } + + bool shouldVisitTemplateInstantiations () const { return true; } + + bool VisitEnumDecl( const EnumDecl* ); + bool VisitDeclRefExpr( const DeclRefExpr* ); +private: + MyEnumValueInfo niceName(const EnumConstantDecl*); +}; + +MyEnumValueInfo UnusedEnumValues::niceName(const EnumConstantDecl* enumDecl) +{ + MyEnumValueInfo aInfo; + const EnumType* enumType = dyn_cast<EnumType>(enumDecl->getType()); + if (enumType && enumType->getDecl()) + { + aInfo.parentClass = enumType->getDecl()->getNameAsString(); + } else { + aInfo.parentClass = "unknown"; + } + aInfo.constantName = enumDecl->getNameAsString(); + + SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( enumDecl->getLocation() ); + StringRef name = compiler.getSourceManager().getFilename(expansionLoc); + aInfo.sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc)); + + return aInfo; +} + +bool UnusedEnumValues::VisitEnumDecl( const EnumDecl* enumDecl ) +{ + enumDecl = enumDecl->getCanonicalDecl(); + + if( ignoreLocation( enumDecl )) + return true; + + for (auto it = enumDecl->enumerator_begin(); it != enumDecl->enumerator_end(); ++it) + definitionSet.insert(niceName(*it)); + return true; +} + +bool UnusedEnumValues::VisitDeclRefExpr( const DeclRefExpr* declRefExpr ) +{ + const Decl* decl = declRefExpr->getDecl(); + if (!isa<EnumConstantDecl>(decl)) { + return true; + } + touchedSet.insert(niceName(dyn_cast<EnumConstantDecl>(decl))); + return true; +} + +loplugin::Plugin::Registration< UnusedEnumValues > X("unusedenumvalues", false); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/compilerplugins/clang/unusedenumvalues.py b/compilerplugins/clang/unusedenumvalues.py new file mode 100755 index 000000000000..1573865eca4e --- /dev/null +++ b/compilerplugins/clang/unusedenumvalues.py @@ -0,0 +1,93 @@ +#!/usr/bin/python + +import sys +import re +import io + +definitionSet = set() +definitionToSourceLocationMap = dict() +touchSet = set() +# things we need to exclude for reasons like : +# - it's a weird template thingy that confuses the plugin +exclusionSet = set([ + ]) + +# 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 ): + return normalizeTypeParamsRegex.sub("type-parameter-?-?", line) + +# The parsing here is designed to avoid grabbing stuff which is mixed in from gbuild. +# I have not yet found a way of suppressing the gbuild output. +with io.open("loplugin.unusedenumvalues.log", "rb", buffering=1024*1024) as txt: + for line in txt: + tokens = line.strip().split("\t") + if tokens[0] == "definition:": + funcInfo = (normalizeTypeParams(tokens[1]), tokens[2]) + definitionSet.add(funcInfo) + definitionToSourceLocationMap[funcInfo] = tokens[3] + elif tokens[0] == "touch:": + callInfo = (normalizeTypeParams(tokens[1]), tokens[2]) + touchSet.add(callInfo) + +# 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 +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) + +untouchedSet = set() +for d in definitionSet: + clazz = d[0] + " " + d[1] + if clazz in exclusionSet: + continue + if d in touchSet: + continue + srcLoc = definitionToSourceLocationMap[d]; + # ignore external source code + if (srcLoc.startswith("external/")): + continue + # ignore build folder + if (srcLoc.startswith("workdir/")): + continue + # ignore our stable/URE/UNO api + if (srcLoc.startswith("include/com/") + or srcLoc.startswith("include/cppu/") + or srcLoc.startswith("include/cppuhelper/") + or srcLoc.startswith("include/osl/") + or srcLoc.startswith("include/rtl/") + or srcLoc.startswith("include/sal/") + or srcLoc.startswith("include/salhelper/") + or srcLoc.startswith("include/systools/") + or srcLoc.startswith("include/typelib/") + or srcLoc.startswith("include/uno/")): + continue + untouchedSet.add((clazz, srcLoc)) + +# 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)] + +# sort results by name and line number +tmp1list = sorted(untouchedSet, key=lambda v: natural_sort_key(v[1])) + +# print out the results +with open("loplugin.unusedenumvalues.report-untouched", "wt") as f: + for t in tmp1list: + f.write( t[1] + "\n" ) + f.write( " " + t[0] + "\n" ) + + + +# add an empty line at the end to make it easier for the unusedFieldsremove plugin to mmap() the output file +print + + |