summaryrefslogtreecommitdiff
path: root/compilerplugins
diff options
context:
space:
mode:
authorNoel Grandin <noel@peralex.com>2016-03-08 09:04:18 +0200
committerNoel Grandin <noel@peralex.com>2016-03-08 09:04:47 +0200
commitb4fda7b3f9cf928f45baf6846dd70e97cdb9904a (patch)
tree9158f4d04ed9c144fbe32d4af73939df56b76179 /compilerplugins
parentc0bd26d73d438b6efc14b56231a6e7f05c8e4b92 (diff)
new loplugin:constantparam
finds parameters that are only ever being called with a single value Change-Id: Ibd0c9b6e6dbc1d1b5d5a005eaa19959560a6e50f
Diffstat (limited to 'compilerplugins')
-rw-r--r--compilerplugins/clang/constantparam.cxx215
-rwxr-xr-xcompilerplugins/clang/constantparam.py61
2 files changed, 276 insertions, 0 deletions
diff --git a/compilerplugins/clang/constantparam.cxx b/compilerplugins/clang/constantparam.cxx
new file mode 100644
index 000000000000..6726836d5205
--- /dev/null
+++ b/compilerplugins/clang/constantparam.cxx
@@ -0,0 +1,215 @@
+/* -*- 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 <string>
+#include <set>
+#include <iostream>
+#include <fstream>
+
+#include "plugin.hxx"
+#include "compat.hxx"
+
+/*
+ Find methods with default params, where the callers never specify the default param i.e.
+ might as well remove it.
+
+ The process goes something like this:
+ $ make check
+ $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='unuseddefaultparams' check
+ $ ./compilerplugins/clang/unuseddefaultparams.py unuseddefaultparams.log
+*/
+
+namespace {
+
+struct MyCallSiteInfo
+{
+ std::string returnType;
+ std::string nameAndParams;
+ std::string paramName;
+ std::string callValue;
+ std::string sourceLocation;
+};
+bool operator < (const MyCallSiteInfo &lhs, const MyCallSiteInfo &rhs)
+{
+ if (lhs.sourceLocation < rhs.sourceLocation)
+ return true;
+ else if (lhs.sourceLocation > rhs.sourceLocation)
+ return false;
+ else return lhs.callValue < rhs.callValue;
+}
+
+
+// try to limit the voluminous output a little
+static std::set<MyCallSiteInfo> callSet;
+
+class ConstantParam:
+ public RecursiveASTVisitor<ConstantParam>, public loplugin::Plugin
+{
+public:
+ explicit ConstantParam(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 MyCallSiteInfo & s : callSet)
+ output += s.returnType + "\t" + s.nameAndParams + "\t" + s.sourceLocation + "\t"
+ + s.paramName + "\t" + s.callValue + "\n";
+ ofstream myfile;
+ myfile.open( SRCDIR "/constantparam.log", ios::app | ios::out);
+ myfile << output;
+ myfile.close();
+ }
+
+ bool shouldVisitTemplateInstantiations () const { return true; }
+
+ bool VisitCallExpr(CallExpr * callExpr);
+ bool VisitDeclRefExpr( const DeclRefExpr* declRefExpr );
+private:
+ MyCallSiteInfo niceName(const FunctionDecl* functionDecl, const ParmVarDecl* parmVarDecl, const std::string& callValue);
+};
+
+MyCallSiteInfo ConstantParam::niceName(const FunctionDecl* functionDecl, const ParmVarDecl* parmVarDecl, const std::string& callValue)
+{
+ if (functionDecl->getInstantiatedFromMemberFunction())
+ functionDecl = functionDecl->getInstantiatedFromMemberFunction();
+ else if (functionDecl->getClassScopeSpecializationPattern())
+ functionDecl = functionDecl->getClassScopeSpecializationPattern();
+// workaround clang-3.5 issue
+#if CLANG_VERSION >= 30600
+ else if (functionDecl->getTemplateInstantiationPattern())
+ functionDecl = functionDecl->getTemplateInstantiationPattern();
+#endif
+
+ MyCallSiteInfo aInfo;
+ aInfo.returnType = compat::getReturnType(*functionDecl).getCanonicalType().getAsString();
+
+ if (isa<CXXMethodDecl>(functionDecl)) {
+ const CXXRecordDecl* recordDecl = dyn_cast<CXXMethodDecl>(functionDecl)->getParent();
+ aInfo.nameAndParams += recordDecl->getQualifiedNameAsString();
+ aInfo.nameAndParams += "::";
+ }
+ aInfo.nameAndParams += functionDecl->getNameAsString() + "(";
+ bool bFirst = true;
+ for (const ParmVarDecl *pParmVarDecl : functionDecl->params()) {
+ 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.paramName = parmVarDecl->getName();
+ aInfo.callValue = callValue;
+
+ SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( functionDecl->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 ConstantParam::VisitCallExpr(CallExpr * callExpr) {
+ if (ignoreLocation(callExpr)) {
+ return true;
+ }
+ const FunctionDecl* functionDecl;
+ if (isa<CXXMemberCallExpr>(callExpr)) {
+ functionDecl = dyn_cast<CXXMemberCallExpr>(callExpr)->getMethodDecl();
+ }
+ else {
+ functionDecl = callExpr->getDirectCallee();
+ }
+ if (functionDecl == nullptr) {
+ return true;
+ }
+ functionDecl = functionDecl->getCanonicalDecl();
+ // method overrides don't always specify the same default params (althogh they probably should)
+ // so we need to work our way up to the root method
+ while (isa<CXXMethodDecl>(functionDecl)) {
+ const CXXMethodDecl* methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
+ if (methodDecl->size_overridden_methods()==0)
+ break;
+ functionDecl = *methodDecl->begin_overridden_methods();
+ }
+ // work our way back to the root definition for template methods
+ if (functionDecl->getInstantiatedFromMemberFunction())
+ functionDecl = functionDecl->getInstantiatedFromMemberFunction();
+ else if (functionDecl->getClassScopeSpecializationPattern())
+ functionDecl = functionDecl->getClassScopeSpecializationPattern();
+// workaround clang-3.5 issue
+#if CLANG_VERSION >= 30600
+ else if (functionDecl->getTemplateInstantiationPattern())
+ functionDecl = functionDecl->getTemplateInstantiationPattern();
+#endif
+
+ // ignore stuff that forms part of the stable URE interface
+ if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(
+ functionDecl->getNameInfo().getLoc()))) {
+ return true;
+ }
+ if (functionDecl->getNameInfo().getLoc().isValid() && ignoreLocation(functionDecl)) {
+ return true;
+ }
+
+ for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) {
+ if (i >= functionDecl->getNumParams()) // can happen in template code
+ break;
+ Expr* arg = callExpr->getArg(i);
+ bool found = false;
+ std::string callValue;
+ // ignore this, it seems to trigger an infinite recursion
+ if (isa<UnaryExprOrTypeTraitExpr>(arg->IgnoreParenCasts())) {
+ found = true;
+ callValue = "unknown";
+ }
+ if (!found)
+ {
+ APSInt x1;
+ if (arg->EvaluateAsInt(x1, compiler.getASTContext()))
+ {
+ found = true;
+ callValue = x1.toString(10);
+ }
+ }
+ if (!found)
+ callValue = "unknown";
+ const ParmVarDecl* parmVarDecl = functionDecl->getParamDecl(i);
+ MyCallSiteInfo funcInfo = niceName(functionDecl, parmVarDecl, callValue);
+ callSet.insert(funcInfo);
+ break;
+ }
+ return true;
+}
+
+// this catches places that take the address of a method
+bool ConstantParam::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
+{
+ const Decl* functionDecl = declRefExpr->getDecl();
+ if (!isa<FunctionDecl>(functionDecl)) {
+ return true;
+ }
+// TODO
+// MyCallSiteInfo funcInfo = niceName(dyn_cast<FunctionDecl>(functionDecl));
+// callSet.insert(funcInfo);
+ return true;
+}
+
+loplugin::Plugin::Registration< ConstantParam > X("constantparam", false);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/compilerplugins/clang/constantparam.py b/compilerplugins/clang/constantparam.py
new file mode 100755
index 000000000000..a23be74be50f
--- /dev/null
+++ b/compilerplugins/clang/constantparam.py
@@ -0,0 +1,61 @@
+#!/usr/bin/python
+
+import sys
+import re
+import io
+
+definitionSet = set()
+definitionToSourceLocationMap = dict()
+callParamSet = dict()
+
+# 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(sys.argv[1], "rb", buffering=1024*1024) as txt:
+ for line in txt:
+ idx1 = line.find("\t")
+ idx2 = line.find("\t",idx1+1)
+ idx3 = line.find("\t",idx2+1)
+ idx4 = line.find("\t",idx3+1)
+ returnType = line[:idx1]
+ nameAndParams = line[idx1+1:idx2]
+ sourceLocation = line[idx2+1:idx3]
+ paramName = line[idx3+1:idx4]
+ callValue = line[idx4+1:].strip()
+ callInfo = (normalizeTypeParams(returnType), normalizeTypeParams(nameAndParams), paramName)
+ if callInfo in callParamSet:
+ callParamSet[callInfo].add(callValue)
+ else:
+ callParamSet[callInfo] = set([callValue])
+ definitionToSourceLocationMap[callInfo] = sourceLocation
+
+tmp1set = set()
+for callInfo, callValues in callParamSet.iteritems():
+ if len(callValues) == 1 and "unknown" not in callValues and ("0" in callValues or "1" in callValues or "nullptr" in callValues):
+ v1 = (" ".join(callInfo)) + " " + (",".join(callValues))
+ v2 = definitionToSourceLocationMap[callInfo]
+ tmp1set.add((v1,v2))
+
+# sort results by name and line number
+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)]
+tmp1list = sorted(tmp1set, key=lambda v: natural_sort_key(v[1]))
+
+# print out the results
+with open("unused.constantparams", "wt") as f:
+ for t in tmp1list:
+ f.write(t[1] + "\n")
+ f.write(" " + t[0] + "\n")
+
+