summaryrefslogtreecommitdiff
path: root/compilerplugins/clang/external.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'compilerplugins/clang/external.cxx')
-rw-r--r--compilerplugins/clang/external.cxx323
1 files changed, 323 insertions, 0 deletions
diff --git a/compilerplugins/clang/external.cxx b/compilerplugins/clang/external.cxx
new file mode 100644
index 000000000000..bb4bcbf36d08
--- /dev/null
+++ b/compilerplugins/clang/external.cxx
@@ -0,0 +1,323 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <algorithm>
+#include <cassert>
+
+#include "clang/Sema/SemaDiagnostic.h"
+
+#include "check.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. <http://clang-developers.
+// 42468.n3.nabble.com/Language-linkage-of-function-type-tt4037248.html>
+// "Language linkage of function type":
+bool hasCLanguageLinkageType(FunctionDecl const* decl)
+{
+ assert(decl != nullptr);
+ if (decl->isExternC())
+ {
+ return true;
+ }
+ if (decl->isInExternCContext())
+ {
+ return true;
+ }
+ return false;
+}
+
+bool derivesFromTestFixture(CXXRecordDecl const* decl)
+{
+ static auto const pred = [](CXXBaseSpecifier const& spec) {
+ if (auto const t = spec.getType()->getAs<RecordType>())
+ { // (may be a template parameter)
+ return derivesFromTestFixture(dyn_cast<CXXRecordDecl>(t->getDecl()));
+ }
+ return false;
+ };
+ return loplugin::DeclCheck(decl).Class("TestFixture").Namespace("CppUnit").GlobalNamespace()
+ || std::any_of(decl->bases_begin(), decl->bases_end(), pred)
+ || std::any_of(decl->vbases_begin(), decl->vbases_end(), pred);
+}
+
+class External : public loplugin::FilteringPlugin<External>
+{
+public:
+ explicit External(loplugin::InstantiationData const& data)
+ : FilteringPlugin(data)
+ {
+ }
+
+ void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+ bool VisitTagDecl(TagDecl const* decl)
+ {
+ /*TODO:*/
+ return true; // in general, moving classes or enumerations into an unnamed namespace can break ADL
+ if (isa<ClassTemplateSpecializationDecl>(decl))
+ {
+ return true;
+ }
+ if (!decl->isThisDeclarationADefinition())
+ {
+ return true;
+ }
+ if (isa<CXXRecordDecl>(decl->getDeclContext()))
+ {
+ return true;
+ }
+ if (!compiler.getLangOpts().CPlusPlus)
+ {
+ return true;
+ }
+ if (auto const d = dyn_cast<CXXRecordDecl>(decl))
+ {
+ if (d->getDescribedClassTemplate() != nullptr)
+ {
+ return true;
+ }
+ if (auto const attr = d->getAttr<VisibilityAttr>())
+ {
+ if (attr->getVisibility() == VisibilityAttr::Default)
+ {
+ // If the class definition has explicit default visibility, then assume that it
+ // needs to be present (e.g., a backwards-compatibility stub like in
+ // cppuhelper/source/compat.cxx):
+ return true;
+ }
+ }
+ if (derivesFromTestFixture(d))
+ {
+ // The names of CppUnit tests (that can be specified with CPPUNIT_TEST_NAME) are
+ // tied to the fully-qualified names of classes derived from CppUnit::TestFixture,
+ // so avoid unnamed namespaces in those classes' names:
+ return true;
+ }
+ }
+ return handleDeclaration(decl);
+ }
+
+ bool VisitFunctionDecl(FunctionDecl const* decl)
+ {
+ if (isa<CXXMethodDecl>(decl))
+ {
+ return true;
+ }
+ if (decl->getTemplatedKind() != FunctionDecl::TK_NonTemplate)
+ {
+ return true;
+ }
+ if (!decl->isThisDeclarationADefinition())
+ {
+ return true;
+ }
+ if (decl->isMain())
+ {
+ return true;
+ }
+ if (auto const attr = decl->getAttr<VisibilityAttr>())
+ {
+ if (attr->getVisibility() == VisibilityAttr::Default)
+ {
+ // If the function definition has explicit default visibility, then assume that it
+ // needs to be present (e.g., only called via dlopen, or a backwards-compatibility
+ // stub like in sal/osl/all/compat.cxx):
+ return true;
+ }
+ }
+ auto const canon = decl->getCanonicalDecl();
+ if (hasCLanguageLinkageType(canon)
+ && (canon->hasAttr<ConstructorAttr>() || canon->hasAttr<DestructorAttr>()))
+ {
+ return true;
+ }
+ if (compiler.getDiagnostics().getDiagnosticLevel(diag::warn_unused_function,
+ decl->getLocation())
+ < DiagnosticsEngine::Warning)
+ {
+ // Don't warn about e.g.
+ //
+ // G_DEFINE_TYPE (GLOAction, g_lo_action, G_TYPE_OBJECT);
+ //
+ // in vcl/unx/gtk/gloactiongroup.cxx (which expands to non-static g_lo_action_get_type
+ // function definition), which is already wrapped in
+ //
+ // #pragma GCC diagnostic ignored "-Wunused-function"
+ return true;
+ }
+ return handleDeclaration(decl);
+ }
+
+ bool VisitVarDecl(VarDecl const* decl)
+ {
+ if (decl->isStaticDataMember())
+ {
+ return true;
+ }
+ if (isa<VarTemplateSpecializationDecl>(decl))
+ {
+ return true;
+ }
+ if (!decl->isThisDeclarationADefinition())
+ {
+ return true;
+ }
+ return handleDeclaration(decl);
+ }
+
+ bool VisitClassTemplateDecl(ClassTemplateDecl const* decl)
+ {
+ /*TODO:*/
+ return true; // in general, moving classes or enumerations into an unnamed namespace can break ADL
+ if (!decl->isThisDeclarationADefinition())
+ {
+ return true;
+ }
+ if (isa<CXXRecordDecl>(decl->getDeclContext()))
+ {
+ return true;
+ }
+ return handleDeclaration(decl);
+ }
+
+ bool VisitFunctionTemplateDecl(FunctionTemplateDecl const* decl)
+ {
+ if (!decl->isThisDeclarationADefinition())
+ {
+ return true;
+ }
+ if (isa<CXXRecordDecl>(decl->getDeclContext()))
+ {
+ return true;
+ }
+ return handleDeclaration(decl);
+ }
+
+ bool VisitVarTemplateDecl(VarTemplateDecl const* decl)
+ {
+ if (!decl->isThisDeclarationADefinition())
+ {
+ return true;
+ }
+ return handleDeclaration(decl);
+ }
+
+private:
+ template <typename T> void reportSpecializations(T specializations)
+ {
+ for (auto const d : specializations)
+ {
+ auto const k = d->getTemplateSpecializationKind();
+ if (isTemplateExplicitInstantiationOrSpecialization(k))
+ {
+ report(DiagnosticsEngine::Note,
+ "explicit %select{instantiation|specialization}0 is here", d->getLocation())
+ << (k == TSK_ExplicitSpecialization) << d->getSourceRange();
+ }
+ }
+ }
+
+ bool handleDeclaration(NamedDecl const* decl)
+ {
+ if (ignoreLocation(decl))
+ {
+ return true;
+ }
+ if (decl->getLinkageInternal() <
+#if CLANG_VERSION >= 40000
+ ModuleLinkage
+#else
+ ExternalLinkage
+#endif
+ )
+ {
+ return true;
+ }
+ //TODO: in some cases getLinkageInternal() appears to report ExternalLinkage instead of
+ // UniqueExternalLinkage:
+ if (decl->isInAnonymousNamespace())
+ {
+ return true;
+ }
+ for (Decl const* d = decl; d != nullptr; d = d->getPreviousDecl())
+ {
+ if (!compiler.getSourceManager().isInMainFile(d->getLocation()))
+ {
+ return true;
+ }
+ }
+ if (compiler.getSourceManager().isMacroBodyExpansion(decl->getLocation())
+ && (Lexer::getImmediateMacroName(decl->getLocation(), compiler.getSourceManager(),
+ compiler.getLangOpts())
+ == "MDDS_MTV_DEFINE_ELEMENT_CALLBACKS"))
+ {
+ // Even wrapping in an unnamed namespace or sneaking "static" into the macro wouldn't
+ // help, as then some of the functions it defines would be flagged as unused:
+ return true;
+ }
+ TypedefNameDecl const* typedefed = nullptr;
+ if (auto const d = dyn_cast<TagDecl>(decl))
+ {
+ typedefed = d->getTypedefNameForAnonDecl();
+ }
+ bool canStatic;
+ if (auto const d = dyn_cast<CXXRecordDecl>(decl))
+ {
+ canStatic = d->isUnion() && d->isAnonymousStructOrUnion();
+ }
+ else
+ {
+ canStatic = isa<FunctionDecl>(decl) || isa<VarDecl>(decl)
+ || isa<FunctionTemplateDecl>(decl) || isa<VarTemplateDecl>(decl);
+ }
+ auto const canUnnamed = compiler.getLangOpts().CPlusPlus
+ && !(isa<FunctionDecl>(decl) || isa<FunctionTemplateDecl>(decl));
+ // in general, moving functions into an unnamed namespace can break ADL
+ assert(canStatic || canUnnamed);
+ report(
+ DiagnosticsEngine::Warning,
+ ("externally available%select{| typedef'ed}0 entity %1 is not previously declared in an"
+ " included file (if it is only used in this translation unit,"
+ " %select{|make it static}2%select{| or }3%select{|put it in an unnamed namespace}4;"
+ " otherwise, provide a declaration of it in an included file)"),
+ decl->getLocation())
+ << (typedefed != nullptr) << (typedefed == nullptr ? decl : typedefed) << canStatic
+ << (canStatic && canUnnamed) << canUnnamed << decl->getSourceRange();
+ for (auto d = decl->getPreviousDecl(); d != nullptr; d = d->getPreviousDecl())
+ {
+ report(DiagnosticsEngine::Note, "previous declaration is here", d->getLocation())
+ << d->getSourceRange();
+ }
+ //TODO: Class template specializations can be in the enclosing namespace, so no need to
+ // list them here (as they won't need to be put into the unnamed namespace too, unlike for
+ // specializations of function and variable templates); and explicit function template
+ // specializations cannot have storage-class specifiers, so as we only suggest to make
+ // function templates static (but not to move them into an unnamed namespace), no need to
+ // list function template specializations here, either:
+ if (auto const d = dyn_cast<VarTemplateDecl>(decl))
+ {
+ reportSpecializations(d->specializations());
+ }
+ return true;
+ }
+};
+
+loplugin::Plugin::Registration<External> X("external");
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */