From e5199d3d78721c962f53a8675d5245e4b839bdc3 Mon Sep 17 00:00:00 2001 From: Stephan Bergmann Date: Tue, 15 Apr 2014 16:53:51 +0200 Subject: Flag unreferrenced functions only declared in the main file, not an include ...which appears to be a good heuristic to identify functions that are either unused or should better be declared just once in an include file. (It also filters out SAL_DLLPUBLIC extern "C" function definitions, which are most likely meant to be referenced dynamically via dlsym.) Change-Id: I7fb78cb836b971791704851535dcfbda2b2f5bc0 --- compilerplugins/clang/compat.hxx | 24 ++++++ compilerplugins/clang/unreffun.cxx | 148 +++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 compilerplugins/clang/unreffun.cxx (limited to 'compilerplugins/clang') diff --git a/compilerplugins/clang/compat.hxx b/compilerplugins/clang/compat.hxx index 3d265722c044..7389f636d0bf 100644 --- a/compilerplugins/clang/compat.hxx +++ b/compilerplugins/clang/compat.hxx @@ -20,7 +20,9 @@ #include "clang/AST/Type.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticIDs.h" +#include "clang/Basic/Linkage.h" #include "clang/Basic/SourceManager.h" +#include "clang/Basic/Visibility.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/raw_ostream.h" @@ -52,6 +54,28 @@ inline bool isExternCContext(clang::DeclContext const & ctxt) { #endif } +#if (__clang_major__ == 3 && __clang_minor__ >= 3) || __clang_major__ > 3 +typedef clang::LinkageInfo LinkageInfo; +#else +typedef clang::NamedDecl::LinkageInfo LinkageInfo; +#endif + +inline clang::Linkage getLinkage(LinkageInfo const & info) { +#if (__clang_major__ == 3 && __clang_minor__ >= 3) || __clang_major__ > 3 + return info.getLinkage(); +#else + return info.linkage(); +#endif +} + +inline clang::Visibility getVisibility(LinkageInfo const & info) { +#if (__clang_major__ == 3 && __clang_minor__ >= 3) || __clang_major__ > 3 + return info.getVisibility(); +#else + return info.visibility(); +#endif +} + inline bool isFirstDecl(clang::FunctionDecl const & decl) { #if (__clang_major__ == 3 && __clang_minor__ >= 4) || __clang_major__ > 3 return decl.isFirstDecl(); diff --git a/compilerplugins/clang/unreffun.cxx b/compilerplugins/clang/unreffun.cxx new file mode 100644 index 000000000000..d49ad2917582 --- /dev/null +++ b/compilerplugins/clang/unreffun.cxx @@ -0,0 +1,148 @@ +/* -*- 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 "clang/AST/Attr.h" +#include "clang/Sema/SemaInternal.h" // warn_unused_function + +#include "compat.hxx" +#include "plugin.hxx" + +namespace { + +// It appears that, given a function declaration, there is no way to determine +// the language linkage of the function's type, only of the function's name +// (via FunctionDecl::isExternC); however, in a case like +// +// extern "C" { static void f(); } +// +// the function's name does not have C language linkage while the function's +// type does (as clarified in C++11 [decl.link]); cf. +// "Language linkage of function type": +bool hasCLanguageLinkageType(FunctionDecl const * decl) { + assert(decl != nullptr); + if (decl->isExternC()) { + return true; + } +#if (__clang_major__ == 3 && __clang_minor__ >= 3) || __clang_major__ > 3 + if (decl->isInExternCContext()) { + return true; + } +#else + if (decl->getCanonicalDecl()->getDeclContext()->isExternCContext()) { + return true; + } +#endif + return false; +} + +class UnrefFun: public RecursiveASTVisitor, public loplugin::Plugin { +public: + explicit UnrefFun(InstantiationData const & data): Plugin(data) {} + + void run() override + { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); } + + bool VisitFunctionDecl(FunctionDecl const * decl); + +private: + bool isInUnoIncludeFile(SourceLocation spellingLocation) const; +}; + +bool UnrefFun::VisitFunctionDecl(FunctionDecl const * decl) { + if (ignoreLocation(decl)) { + return true; + } + + //TODO, filtering out anything template for now: + if (decl->isDependentContext()) { + return true; + } + CXXRecordDecl const * r = dyn_cast(decl->getDeclContext());; + if (r != nullptr && r->getTemplateSpecializationKind() != TSK_Undeclared) { + return true; + } + + FunctionDecl const * canon = decl->getCanonicalDecl(); + //TODO: is that the first? + if (canon->isDeleted() || canon->isReferenced() + || !(canon->isDefined() + ? decl->isThisDeclarationADefinition() + : compat::isFirstDecl(*decl)) + || !compat::isInMainFile( + compiler.getSourceManager(), canon->getLocation()) + || isInUnoIncludeFile( + compiler.getSourceManager().getSpellingLoc( + canon->getNameInfo().getLoc())) + || canon->isMain() + || (compiler.getDiagnostics().getDiagnosticLevel( + diag::warn_unused_function, decl->getLocation()) + < DiagnosticsEngine::Warning)) + { + return true; + } + compat::LinkageInfo info(canon->getLinkageAndVisibility()); + if (compat::getLinkage(info) == ExternalLinkage + && hasCLanguageLinkageType(canon) && canon->isDefined() + && ((decl == canon && compat::getVisibility(info) == DefaultVisibility) + || ((canon->hasAttr() + || canon->hasAttr()) + && compat::getVisibility(info) == HiddenVisibility))) + { + return true; + } + report( + DiagnosticsEngine::Warning, + (canon->isDefined() +#if (__clang_major__ == 3 && __clang_minor__ >= 4) || __clang_major__ > 3 + ? (canon->isExternallyVisible() + ? "Unreferenced externally visible function definition" + : "Unreferenced externally invisible function definition") +#else + ? "Unreferenced function definition" +#endif + : "Unreferenced function declaration"), + decl->getLocation()) + << decl->getSourceRange(); + if (canon->isDefined() && !compat::isFirstDecl(*decl)) { + report( + DiagnosticsEngine::Note, "first declaration is here", + canon->getLocation()) + << canon->getSourceRange(); + } + return true; +} + +bool UnrefFun::isInUnoIncludeFile(SourceLocation spellingLocation) const { + StringRef name { + compiler.getSourceManager().getFilename(spellingLocation) }; + return compat::isInMainFile(compiler.getSourceManager(), spellingLocation) + ? (name == SRCDIR "/cppu/source/cppu/compat.cxx" + || name == SRCDIR "/cppuhelper/source/compat.cxx" + || name == SRCDIR "/sal/osl/all/compat.cxx") + : (name.startswith(SRCDIR "/include/com/") + || name.startswith(SRCDIR "/include/cppu/") + || name.startswith(SRCDIR "/include/cppuhelper/") + || name.startswith(SRCDIR "/include/osl/") + || name.startswith(SRCDIR "/include/rtl/") + || name.startswith(SRCDIR "/include/sal/") + || name.startswith(SRCDIR "/include/salhelper/") + || name.startswith(SRCDIR "/include/systools/") + || name.startswith(SRCDIR "/include/typelib/") + || name.startswith(SRCDIR "/include/uno/")); +} + +loplugin::Plugin::Registration X("unreffun"); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit