office-gobmx/compilerplugins/clang/dyncastvisibility.cxx
Stephan Bergmann 89714f9e3e Fix indentation
...accidentally broken by 16e3b84d2e "Improve
loplugin:dyncastvisibility to check for non-inline key functions"

Change-Id: Ie840803b4223fae76bcd72973b6a072eaa5e1d40
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176598
Reviewed-by: Stephan Bergmann <stephan.bergmann@allotropia.de>
Tested-by: Jenkins
2024-11-14 17:13:32 +01:00

259 lines
9.8 KiB
C++

/* -*- 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 <algorithm>
#include <cassert>
#include <cstddef>
#include <set>
#include <string>
#include "config_clang.h"
#include "plugin.hxx"
namespace {
using Bases = std::set<CXXRecordDecl const *>;
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<CXXRecordDecl>(i.getType()->getAs<RecordType>()->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<DynCastVisibility>
{
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<ReferenceType>()) {
td = t->getPointeeType();
}
while (auto const t = td->getAs<clang::PointerType>()) {
td = t->getPointeeType();
}
auto const rtd = td->getAs<RecordType>();
if (rtd == nullptr) {
return true;
}
auto const rdd = cast<CXXRecordDecl>(rtd->getDecl())->getDefinition();
if (rdd == nullptr) {
return true;
}
if (getTypeVisibility(rdd) != DefaultVisibility) {
// Heuristic to find problematic dynamic_cast<T> 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<T>, 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<clang::PointerType>()) {
ts = t->getPointeeType();
}
auto const rts = ts->getAs<RecordType>();
if (rts == nullptr) { // in case it's a dependent type
return true;
}
auto const rds = cast<CXXRecordDecl>(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<CXXRecordDecl>(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 const *>(decl) : static_cast<Decl const *>(md);
}
};
static loplugin::Plugin::Registration<DynCastVisibility> dyncastvisibility(
"dyncastvisibility");
}
#endif // LO_CLANG_SHARED_PLUGINS
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */