/* -*- 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 #include #include #include "config_clang.h" #include "plugin.hxx" namespace { using Bases = std::set; Visibility getTypeVisibility(CXXRecordDecl const * decl) { assert(decl->isThisDeclarationADefinition()); if (auto const opt = decl->getExplicitVisibility( NamedDecl::VisibilityForType)) { return *opt; } if (auto const opt = decl->getExplicitVisibility( NamedDecl::VisibilityForValue)) { return *opt; } auto const visi = decl->getVisibility(); return visi == DefaultVisibility && decl->isInAnonymousNamespace() ? HiddenVisibility : visi; } // Check whether 'decl' is derived from 'base', gathering any 'bases' between // 'decl' and 'base', and whether any of those 'bases' or 'base' are 'hidden' // (i.e., have non-default visibility): bool isDerivedFrom( CXXRecordDecl const * decl, CXXRecordDecl const * base, Bases * bases, bool * hidden) { bool derived = false; for (auto const & i: decl->bases()) { auto const bd = (cast(i.getType()->getAs()->getDecl()) ->getDefinition()); assert(bd != nullptr); if (bd == base) { *hidden |= getTypeVisibility(base) != DefaultVisibility; derived = true; } else if (bd->isDerivedFrom(base)) { if (bases->insert(bd).second) { auto const d = isDerivedFrom(bd, base, bases, hidden); assert(d); (void)d; *hidden |= getTypeVisibility(bd) != DefaultVisibility; } derived = true; } } return derived; } StringRef vis(Visibility v) { switch (v) { case HiddenVisibility: return "hidden"; case ProtectedVisibility: return "protected"; case DefaultVisibility: return "default"; } llvm_unreachable("unknown visibility"); } class DynCastVisibility final: public loplugin::FilteringPlugin { public: explicit DynCastVisibility(loplugin::InstantiationData const & data): FilteringPlugin(data) {} bool shouldVisitTemplateInstantiations() const { return true; } bool VisitCXXDynamicCastExpr(CXXDynamicCastExpr const * expr) { if (ignoreLocation(expr)) { return true; } auto td = expr->getTypeAsWritten(); if (auto const t = td->getAs()) { td = t->getPointeeType(); } while (auto const t = td->getAs()) { td = t->getPointeeType(); } auto const rtd = td->getAs(); if (rtd == nullptr) { return true; } auto const rdd = cast(rtd->getDecl())->getDefinition(); if (rdd == nullptr) { return true; } if (getTypeVisibility(rdd) != DefaultVisibility) { // Heuristic to find problematic dynamic_cast with hidden type T is: T is defined in // include/M1/ while the compilation unit is in module M2/ with M1 != M2. There are // legitimate cases where T is a hidden type in dynamic_cast, e.g., when both the // type and the cast are in the same library. This heuristic appears to be conservative // enough to produce only a few false positives (which have been addressed with // preceding commits, marking the relevant types in global include files as // SAL_DLLPUBLIC_RTTI after all, to be on the safe side) and aggressive enough to find // at least some interesting cases (though it would still not be aggressive enough to // have found ff570b4b58dbf274d3094d21d974f18b613e9b4b "DocumentSettingsSerializer must // be SAL_DLLPUBLIC_RTTI for dynamic_cast"): auto const file = getFilenameOfLocation( compiler.getSourceManager().getSpellingLoc(rdd->getLocation())); if (loplugin::hasPathnamePrefix(file, SRCDIR "/include/")) { std::size_t const n1 = std::strlen(SRCDIR "/include/"); std::size_t n2 = file.find('/', n1); #if defined _WIN32 n2 = std::min(n2, file.find('\\', n1)); #endif auto const seg = n2 >= file.size() ? file.substr(n1) : file.substr(n1, n2 - n1); auto prefix = std::string(SRCDIR "/"); prefix += seg; if (!loplugin::hasPathnamePrefix( handler.getMainFileName(), prefix)) { report( DiagnosticsEngine::Warning, "Suspicious dynamic_cast to %0 with %1 type visibility", expr->getExprLoc()) << td << vis(getTypeVisibility(rdd)) << expr->getSourceRange(); report(DiagnosticsEngine::Note, "class %0 defined here", rdd->getLocation()) << td << rdd->getSourceRange(); } } return true; } auto ts = expr->getSubExpr()->getType(); while (auto const t = ts->getAs()) { ts = t->getPointeeType(); } auto const rts = ts->getAs(); if (rts == nullptr) { // in case it's a dependent type return true; } auto const rds = cast(rts->getDecl())->getDefinition(); assert(rds != nullptr); Bases bs; bool hidden = false; if (!isDerivedFrom(rdd, rds, &bs, &hidden)) { return true; } Decl const * missing = nullptr; if (rdd->isEffectivelyFinal()) { missing = missingKeyFunction(rdd); } if (!hidden && missing == nullptr) { return true; } report( DiagnosticsEngine::Warning, ("dynamic_cast from %0 with %1 type visibility to %2 with %3 type" " visibility"), expr->getExprLoc()) << ts << vis(getTypeVisibility(rds)) << td << vis(getTypeVisibility(rdd)) << expr->getSourceRange(); if (hidden) { report( DiagnosticsEngine::Note, "base class %0 with %1 type visibility defined here", rds->getLocation()) << ts << vis(getTypeVisibility(rds)) << rds->getSourceRange(); for (auto const i: bs) { if (getTypeVisibility(i) != DefaultVisibility) { report( DiagnosticsEngine::Note, ("intermediary class %0 with %1 type visibility defined" " here"), i->getLocation()) << i << vis(getTypeVisibility(i)) << i->getSourceRange(); } } report( DiagnosticsEngine::Note, "derived class %0 with %1 type visibility defined here", rdd->getLocation()) << td << vis(getTypeVisibility(rdd)) << rdd->getSourceRange(); } if (missing != nullptr) { if (isa(missing)) { report( DiagnosticsEngine::Note, "derived class %0 does not have a key function (at least on some platforms)", missing->getLocation()) << td << missing->getSourceRange(); } else { report( DiagnosticsEngine::Note, "derived class %0 has a key function (at least on some platforms) that is" " inline", missing->getLocation()) << td << missing->getSourceRange(); } } return true; } virtual bool preRun() override { return compiler.getLangOpts().CPlusPlus; } private: void run() override { if (preRun()) { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); } } Decl const * missingKeyFunction(CXXRecordDecl const * decl) { auto const md = compiler.getASTContext().getCurrentKeyFunction(decl); if (md != nullptr && !md->isInlined()) { return nullptr; } // Ignore classes defined in the main file: auto const def = decl->getDefinition(); assert(def != nullptr); if (compiler.getSourceManager().isInMainFile(def->getLocation())) { return nullptr; } //TODO: Ignore template instantiations, for which any key function would necessarily be // inline, unless there is an explicit extern template instantiation (as there should // arguably be for such cases, cf. comphelper::DocumentEventHolder in // include/comphelper/asyncnotification.hxx, but which might be complicated to check here): auto const tsk = decl->getTemplateSpecializationKind(); if (tsk == TSK_ImplicitInstantiation || tsk == TSK_ExplicitInstantiationDeclaration) { return nullptr; } return md == nullptr ? static_cast(decl) : static_cast(md); } }; static loplugin::Plugin::Registration dyncastvisibility( "dyncastvisibility"); } #endif // LO_CLANG_SHARED_PLUGINS /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */