diff options
Diffstat (limited to 'compilerplugins/clang/virtualdown.cxx')
-rw-r--r-- | compilerplugins/clang/virtualdown.cxx | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/compilerplugins/clang/virtualdown.cxx b/compilerplugins/clang/virtualdown.cxx new file mode 100644 index 000000000..946d8bc95 --- /dev/null +++ b/compilerplugins/clang/virtualdown.cxx @@ -0,0 +1,219 @@ +/* -*- 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 <set> +#include "config_clang.h" +#include "plugin.hxx" +#include <fstream> + +/** +Look for virtual methods where we never call the defining virtual method, and only call the overriding virtual +methods, which indicates a places where the virtual-ness is unwarranted, normally a result of premature abstraction. + +The process goes something like this: + $ make check + $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='VirtualDown' check + $ ./compilerplugins/clang/VirtualDown.py + +@TODO for some reason, we get false+ for operator== methods +@TODO some templates confuse it and we get false+ + +*/ + +namespace +{ +struct MyFuncInfo +{ + std::string name; + std::string sourceLocation; +}; +bool operator<(const MyFuncInfo& lhs, const MyFuncInfo& rhs) { return lhs.name < rhs.name; } + +// try to limit the voluminous output a little +static std::set<MyFuncInfo> definitionSet; +static std::set<std::string> callSet; + +class VirtualDown : public loplugin::FilteringPlugin<VirtualDown> +{ +public: + explicit VirtualDown(loplugin::InstantiationData const& data) + : FilteringPlugin(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.name + "\t" + s.sourceLocation + "\n"; + for (const std::string& s : callSet) + output += "call:\t" + s + "\n"; + std::ofstream myfile; + myfile.open(WORKDIR "/loplugin.virtualdown.log", std::ios::app | std::ios::out); + myfile << output; + myfile.close(); + } + bool shouldVisitTemplateInstantiations() const { return true; } + bool shouldVisitImplicitCode() const { return true; } + + bool VisitCXXMethodDecl(CXXMethodDecl const*); + bool VisitCXXMemberCallExpr(CXXMemberCallExpr const*); + bool TraverseFunctionDecl(FunctionDecl*); + bool TraverseCXXMethodDecl(CXXMethodDecl*); + bool TraverseCXXConversionDecl(CXXConversionDecl*); + bool TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl*); + +private: + std::string toString(SourceLocation loc); + std::string niceName(const CXXMethodDecl* functionDecl); + FunctionDecl const* currentFunctionDecl = nullptr; +}; + +bool VirtualDown::VisitCXXMethodDecl(const CXXMethodDecl* methodDecl) +{ + if (ignoreLocation(methodDecl)) + { + return true; + } + if (!methodDecl->isThisDeclarationADefinition() || !methodDecl->isVirtual() + || methodDecl->isDeleted()) + { + return true; + } + methodDecl = methodDecl->getCanonicalDecl(); + // ignore stuff that forms part of the stable URE interface + if (isInUnoIncludeFile(methodDecl)) + { + return true; + } + + std::string aNiceName = niceName(methodDecl); + + if (isa<CXXDestructorDecl>(methodDecl)) + return true; + if (isa<CXXConstructorDecl>(methodDecl)) + return true; + + if (methodDecl->size_overridden_methods() == 0) + definitionSet.insert({ aNiceName, toString(methodDecl->getLocation()) }); + + return true; +} + +bool VirtualDown::VisitCXXMemberCallExpr(CXXMemberCallExpr const* 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 const* calleeFunctionDecl = expr->getDirectCallee(); + if (calleeFunctionDecl == nullptr) + { + Expr const* callee = expr->getCallee()->IgnoreParenImpCasts(); + DeclRefExpr const* dr = dyn_cast<DeclRefExpr>(callee); + if (dr) + { + calleeFunctionDecl = dyn_cast<FunctionDecl>(dr->getDecl()); + if (calleeFunctionDecl) + goto gotfunc; + } + return true; + } + +gotfunc: + + // ignore recursive calls + if (currentFunctionDecl == calleeFunctionDecl) + return true; + + auto cxxMethodDecl = dyn_cast<CXXMethodDecl>(calleeFunctionDecl); + if (!cxxMethodDecl) + return true; + + while (cxxMethodDecl->getTemplateInstantiationPattern()) + cxxMethodDecl = dyn_cast<CXXMethodDecl>(cxxMethodDecl->getTemplateInstantiationPattern()); + while (cxxMethodDecl->getInstantiatedFromMemberFunction()) + cxxMethodDecl = dyn_cast<CXXMethodDecl>(cxxMethodDecl->getInstantiatedFromMemberFunction()); + if (cxxMethodDecl->getLocation().isValid() && !ignoreLocation(cxxMethodDecl)) + callSet.insert(niceName(cxxMethodDecl)); + + return true; +} + +bool VirtualDown::TraverseFunctionDecl(FunctionDecl* f) +{ + auto copy = currentFunctionDecl; + currentFunctionDecl = f; + bool ret = RecursiveASTVisitor::TraverseFunctionDecl(f); + currentFunctionDecl = copy; + return ret; +} +bool VirtualDown::TraverseCXXMethodDecl(CXXMethodDecl* f) +{ + auto copy = currentFunctionDecl; + currentFunctionDecl = f; + bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(f); + currentFunctionDecl = copy; + return ret; +} +bool VirtualDown::TraverseCXXConversionDecl(CXXConversionDecl* f) +{ + auto copy = currentFunctionDecl; + currentFunctionDecl = f; + bool ret = RecursiveASTVisitor::TraverseCXXConversionDecl(f); + currentFunctionDecl = copy; + return ret; +} +bool VirtualDown::TraverseCXXDeductionGuideDecl(CXXDeductionGuideDecl* f) +{ + auto copy = currentFunctionDecl; + currentFunctionDecl = f; + bool ret = RecursiveASTVisitor::TraverseCXXDeductionGuideDecl(f); + currentFunctionDecl = copy; + return ret; +} + +std::string VirtualDown::niceName(const CXXMethodDecl* cxxMethodDecl) +{ + std::string s = cxxMethodDecl->getReturnType().getCanonicalType().getAsString() + " " + + cxxMethodDecl->getQualifiedNameAsString() + "("; + for (const ParmVarDecl* pParmVarDecl : cxxMethodDecl->parameters()) + { + s += pParmVarDecl->getType().getCanonicalType().getAsString(); + s += ","; + } + s += ")"; + if (cxxMethodDecl->isConst()) + { + s += "const"; + } + return s; +} + +std::string VirtualDown::toString(SourceLocation loc) +{ + SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc(loc); + StringRef name = getFilenameOfLocation(expansionLoc); + std::string sourceLocation + = std::string(name.substr(strlen(SRCDIR) + 1)) + ":" + + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc)); + loplugin::normalizeDotDotInFilePath(sourceLocation); + return sourceLocation; +} + +loplugin::Plugin::Registration<VirtualDown> X("virtualdown", false); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |