diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /compilerplugins/clang/virtualdead.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream/4%7.4.7.tar.xz libreoffice-upstream/4%7.4.7.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compilerplugins/clang/virtualdead.cxx')
-rw-r--r-- | compilerplugins/clang/virtualdead.cxx | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/compilerplugins/clang/virtualdead.cxx b/compilerplugins/clang/virtualdead.cxx new file mode 100644 index 000000000..1a63363aa --- /dev/null +++ b/compilerplugins/clang/virtualdead.cxx @@ -0,0 +1,283 @@ +/* -*- 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 "plugin.hxx" +#include "check.hxx" +#include "compat.hxx" + +#include <cassert> +#include <string> +#include <iostream> +#include <set> +#include <fstream> + +/** +Look for virtual methods where all of the overrides either +(a) do nothing +(b) all return the same value + +The process goes something like this: + $ make check + $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='VirtualDead' check + $ ./compilerplugins/clang/VirtualDead.py + $ for dir in *; do make FORCE_COMPILE=all UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='removevirtuals' $dir; done + +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 name; + std::string sourceLocation; + std::string returnValue; +}; +bool operator<(const MyFuncInfo& lhs, const MyFuncInfo& rhs) +{ + return std::tie(lhs.name, lhs.returnValue) < std::tie(rhs.name, rhs.returnValue); +} +struct MyParamInfo +{ + std::string funcName; + std::string paramBitField; +}; +bool operator<(const MyParamInfo& lhs, const MyParamInfo& rhs) +{ + return std::tie(lhs.funcName, lhs.paramBitField) < std::tie(rhs.funcName, rhs.paramBitField); +} + +// try to limit the voluminous output a little +static std::set<MyFuncInfo> definitionSet; +static std::set<MyParamInfo> paramUsedSet; + +class VirtualDead : public RecursiveASTVisitor<VirtualDead>, public loplugin::Plugin +{ +public: + explicit VirtualDead(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 += "virtual:\t" + s.name + "\t" + s.sourceLocation + "\t" + s.returnValue + "\n"; + for (const MyParamInfo& s : paramUsedSet) + output += "param:\t" + s.funcName + "\t" + s.paramBitField + "\n"; + std::ofstream myfile; + myfile.open(WORKDIR "/loplugin.virtualdead.log", std::ios::app | std::ios::out); + myfile << output; + myfile.close(); + } + bool shouldVisitTemplateInstantiations() const { return true; } + bool shouldVisitImplicitCode() const { return true; } + + bool VisitCXXMethodDecl(const CXXMethodDecl* decl); + +private: + std::string getCallValue(const Expr* arg); + std::string toString(SourceLocation loc); + void markSuperclassMethods(const CXXMethodDecl* methodDecl, const std::string& returnValue, + const std::string& paramBitField); +}; + +std::string niceName(const CXXMethodDecl* cxxMethodDecl) +{ + while (cxxMethodDecl->getTemplateInstantiationPattern()) + cxxMethodDecl = dyn_cast<CXXMethodDecl>(cxxMethodDecl->getTemplateInstantiationPattern()); + while (cxxMethodDecl->getInstantiatedFromMemberFunction()) + cxxMethodDecl = dyn_cast<CXXMethodDecl>(cxxMethodDecl->getInstantiatedFromMemberFunction()); + 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; +} + +bool VirtualDead::VisitCXXMethodDecl(const CXXMethodDecl* methodDecl) +{ + if (!methodDecl->isVirtual() || methodDecl->isDeleted()) + return true; + if (isa<CXXDestructorDecl>(methodDecl)) + return true; + // ignore stuff that forms part of the stable URE interface + if (isInUnoIncludeFile(methodDecl->getCanonicalDecl())) + return true; + + if (!methodDecl->isThisDeclarationADefinition()) + return true; + + std::string returnValue; + + auto body = methodDecl->getBody(); + if (body) + { + auto compoundStmt = dyn_cast<CompoundStmt>(body); + if (!compoundStmt) + returnValue = "empty"; + else if (compoundStmt->size() == 0) + returnValue = "empty"; + else + { + if (auto returnStmt = dyn_cast<ReturnStmt>(*compoundStmt->body_begin())) + { + if (!returnStmt->getRetValue()) + returnValue = "empty"; + else + returnValue = getCallValue(returnStmt->getRetValue()); + } + else + returnValue = "unknown-stmt"; + } + } + else + returnValue = "empty"; + + std::string paramBitfield; + for (auto it = methodDecl->param_begin(); it != methodDecl->param_end(); ++it) + { + auto param = *it; + paramBitfield += param->getName().empty() ? "0" : "1"; + } + + markSuperclassMethods(methodDecl, returnValue, paramBitfield); + + return true; +} + +void VirtualDead::markSuperclassMethods(const CXXMethodDecl* methodDecl, + const std::string& returnValue, + std::string const& paramBitField) +{ + if (methodDecl->size_overridden_methods() == 0) + { + std::string aNiceName = niceName(methodDecl); + definitionSet.insert( + { aNiceName, toString(methodDecl->getCanonicalDecl()->getLocation()), returnValue }); + paramUsedSet.insert({ aNiceName, paramBitField }); + return; + } + + for (auto iter = methodDecl->begin_overridden_methods(); + iter != methodDecl->end_overridden_methods(); ++iter) + { + const CXXMethodDecl* overriddenMethod = *iter; + markSuperclassMethods(overriddenMethod, returnValue, paramBitField); + } +} + +std::string VirtualDead::getCallValue(const Expr* arg) +{ + arg = arg->IgnoreParenCasts(); + if (isa<CXXDefaultArgExpr>(arg)) + { + arg = dyn_cast<CXXDefaultArgExpr>(arg)->getExpr(); + } + arg = arg->IgnoreParenCasts(); + // ignore this, it seems to trigger an infinite recursion + if (isa<UnaryExprOrTypeTraitExpr>(arg)) + return "unknown1"; + if (arg->isValueDependent()) + return "unknown2"; + APSInt x1; + if (compat::EvaluateAsInt(arg, x1, compiler.getASTContext())) + { + return compat::toString(x1, 10); + } + if (isa<CXXNullPtrLiteralExpr>(arg)) + { + return "0"; + } + if (isa<MaterializeTemporaryExpr>(arg)) + { + const CXXBindTemporaryExpr* strippedArg + = dyn_cast_or_null<CXXBindTemporaryExpr>(arg->IgnoreParenCasts()); + if (strippedArg) + { + auto temp = dyn_cast<CXXTemporaryObjectExpr>(strippedArg->getSubExpr()); + if (temp->getNumArgs() == 0) + { + if (loplugin::TypeCheck(temp->getType()) + .Class("OUString") + .Namespace("rtl") + .GlobalNamespace()) + { + return "\"\""; + } + if (loplugin::TypeCheck(temp->getType()) + .Class("OString") + .Namespace("rtl") + .GlobalNamespace()) + { + return "\"\""; + } + return "defaultConstruct"; + } + } + } + + // Get the expression contents. + // This helps us find params which are always initialised with something like "OUString()". + SourceManager& SM = compiler.getSourceManager(); + SourceLocation startLoc = arg->getBeginLoc(); + SourceLocation endLoc = arg->getEndLoc(); + const char* p1 = SM.getCharacterData(startLoc); + const char* p2 = SM.getCharacterData(endLoc); + if (!p1 || !p2 || (p2 - p1) < 0 || (p2 - p1) > 40) + { + return "unknown3"; + } + unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts()); + std::string s(p1, p2 - p1 + n); + // strip linefeed and tab characters so they don't interfere with the parsing of the log file + std::replace(s.begin(), s.end(), '\r', ' '); + std::replace(s.begin(), s.end(), '\n', ' '); + std::replace(s.begin(), s.end(), '\t', ' '); + + // now normalize the value. For some params, like OUString, we can pass it as OUString() or "" and they are the same thing + if (s == "OUString()") + s = "\"\""; + else if (s == "OString()") + s = "\"\""; + + if (s[0] == '"' || s[0] == '\'') + return s; + return "unknown4"; +} + +std::string VirtualDead::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<VirtualDead> X("virtualdead", false); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |