/* -*- 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/. */ #ifndef LO_CLANG_SHARED_PLUGINS #include #include #include "clang/Sema/SemaDiagnostic.h" #include "check.hxx" #include "plugin.hxx" namespace { bool derivesFromTestFixture(CXXRecordDecl const* decl) { static auto const pred = [](CXXBaseSpecifier const& spec) { if (auto const t = spec.getType()->getAs()) { // (may be a template parameter) return derivesFromTestFixture(dyn_cast(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); } bool isInjectedFunction(FunctionDecl const* decl) { for (auto d = decl->redecls_begin(); d != decl->redecls_end(); ++d) { auto const c = d->getLexicalDeclContext(); if (!(c->isFunctionOrMethod() || c->isRecord())) { return false; } } return true; } class External : public loplugin::FilteringPlugin { 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(decl)) { return true; } if (!decl->isThisDeclarationADefinition()) { return true; } if (isa(decl->getDeclContext())) { return true; } if (!compiler.getLangOpts().CPlusPlus) { return true; } if (auto const d = dyn_cast(decl)) { if (d->getDescribedClassTemplate() != nullptr) { return true; } if (auto const attr = d->getAttr()) { 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(decl)) { return true; } if (decl->getTemplatedKind() != FunctionDecl::TK_NonTemplate) { return true; } if (!decl->isThisDeclarationADefinition()) { return true; } if (decl->isMain() || decl->isMSVCRTEntryPoint()) { return true; } if (loplugin::hasCLanguageLinkageType(decl) && loplugin::DeclCheck(decl).Function("_DllMainCRTStartup").GlobalNamespace()) { return true; } // If the function definition is explicit marked SAL_DLLPUBLIC_EXPORT or similar, 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): if (auto const attr = decl->getAttr()) { if (attr->getVisibility() == VisibilityAttr::Default) { return true; } } else if (decl->hasAttr()) { return true; } auto const canon = decl->getCanonicalDecl(); if (loplugin::hasCLanguageLinkageType(canon) && (canon->hasAttr() || canon->hasAttr())) { 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; } if (isInjectedFunction(decl)) { return true; } return handleDeclaration(decl); } bool VisitVarDecl(VarDecl const* decl) { if (decl->isStaticDataMember()) { return true; } if (isa(decl)) { return true; } if (!decl->isThisDeclarationADefinition()) { return true; } if (loplugin::DeclCheck(decl).Var("_pRawDllMain").GlobalNamespace()) { 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(decl->getDeclContext())) { return true; } return handleDeclaration(decl); } bool VisitFunctionTemplateDecl(FunctionTemplateDecl const* decl) { if (!decl->isThisDeclarationADefinition()) { return true; } if (isa(decl->getDeclContext())) { return true; } if (isInjectedFunction(decl->getTemplatedDecl())) { return true; } return handleDeclaration(decl); } bool VisitVarTemplateDecl(VarTemplateDecl const* decl) { if (!decl->isThisDeclarationADefinition()) { return true; } return handleDeclaration(decl); } private: template 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() < ModuleLinkage) { 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())) { if (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; } } else if (compiler.getSourceManager().isMacroArgExpansion(decl->getLocation())) { if (Lexer::getImmediateMacroName(decl->getLocation(), compiler.getSourceManager(), compiler.getLangOpts()) == "DEFINE_GUID") { // Windows, guiddef.h: return true; } } TypedefNameDecl const* typedefed = nullptr; if (auto const d = dyn_cast(decl)) { typedefed = d->getTypedefNameForAnonDecl(); } bool canStatic; if (auto const d = dyn_cast(decl)) { canStatic = d->isUnion() && d->isAnonymousStructOrUnion(); } else { canStatic = isa(decl) || isa(decl) || isa(decl) || isa(decl); } // In general, moving functions into an unnamed namespace can: break ADL like in // // struct S1 { int f() { return 1; } }; // int f(S1 s) { return s.f(); } // namespace N { // struct S2: S1 { int f() { return 0; } }; // int f(S2 s) { return s.f(); } // [*] // } // int main() { return f(N::S2()); } // // changing from returning 0 to returning 1 when [*] is moved into an unnamed namespace; can // conflict with function declarations in the moved function like in // // int f(int) { return 0; } // namespace { int f(int) { return 1; } } // int g() { // [*] // int f(int); // return f(0); // } // int main() { return g(); } // // changing from returning 0 to returning 1 when [*] is moved into an unnamed namespace; and // can conflict with overload resolution in general like in // // int f(int) { return 0; } // namespace { int f(...) { return 1; } } // int g() { return f(0); } // [*] // int main() { return g(); } // // changing from returning 0 to returning 1 when [*] is moved into an unnamed namespace: auto const canUnnamed = compiler.getLangOpts().CPlusPlus && !(isa(decl) || isa(decl)); 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->redecls_begin(); d != decl->redecls_end(); ++d) { if (*d == decl) { continue; } report(DiagnosticsEngine::Note, "another 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(decl)) { reportSpecializations(d->specializations()); } return true; } }; loplugin::Plugin::Registration external("external"); } // namespace #endif // LO_CLANG_SHARED_PLUGINS /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */