1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #ifndef LO_CLANG_SHARED_PLUGINS
18 #include "config_clang.h"
24 using Bases
= std::set
<CXXRecordDecl
const *>;
26 Visibility
getTypeVisibility(CXXRecordDecl
const * decl
) {
27 assert(decl
->isThisDeclarationADefinition());
28 if (auto const opt
= decl
->getExplicitVisibility(
29 NamedDecl::VisibilityForType
))
33 if (auto const opt
= decl
->getExplicitVisibility(
34 NamedDecl::VisibilityForValue
))
38 auto const visi
= decl
->getVisibility();
39 return visi
== DefaultVisibility
&& decl
->isInAnonymousNamespace()
40 ? HiddenVisibility
: visi
;
43 // Check whether 'decl' is derived from 'base', gathering any 'bases' between
44 // 'decl' and 'base', and whether any of those 'bases' or 'base' are 'hidden'
45 // (i.e., have non-default visibility):
47 CXXRecordDecl
const * decl
, CXXRecordDecl
const * base
, Bases
* bases
,
51 for (auto const & i
: decl
->bases()) {
53 = (cast
<CXXRecordDecl
>(i
.getType()->getAs
<RecordType
>()->getDecl())
55 assert(bd
!= nullptr);
57 *hidden
|= getTypeVisibility(base
) != DefaultVisibility
;
60 else if (bd
->isDerivedFrom(base
)) {
61 if (bases
->insert(bd
).second
) {
62 auto const d
= isDerivedFrom(bd
, base
, bases
, hidden
);
65 *hidden
|= getTypeVisibility(bd
) != DefaultVisibility
;
73 StringRef
vis(Visibility v
) {
75 case HiddenVisibility
:
77 case ProtectedVisibility
:
79 case DefaultVisibility
:
82 llvm_unreachable("unknown visibility");
85 class DynCastVisibility final
:
86 public loplugin::FilteringPlugin
<DynCastVisibility
>
89 explicit DynCastVisibility(loplugin::InstantiationData
const & data
):
90 FilteringPlugin(data
) {}
92 bool shouldVisitTemplateInstantiations() const { return true; }
94 bool VisitCXXDynamicCastExpr(CXXDynamicCastExpr
const * expr
) {
95 if (ignoreLocation(expr
)) {
98 auto td
= expr
->getTypeAsWritten();
99 if (auto const t
= td
->getAs
<ReferenceType
>()) {
100 td
= t
->getPointeeType();
102 while (auto const t
= td
->getAs
<clang::PointerType
>()) {
103 td
= t
->getPointeeType();
105 auto const rtd
= td
->getAs
<RecordType
>();
106 if (rtd
== nullptr) {
109 auto const rdd
= cast
<CXXRecordDecl
>(rtd
->getDecl())->getDefinition();
110 assert(rdd
!= nullptr);
111 if (getTypeVisibility(rdd
) != DefaultVisibility
) {
112 // Heuristic to find problematic dynamic_cast<T> with hidden type T is: T is defined in
113 // include/M1/ while the compilation unit is in module M2/ with M1 != M2. There are
114 // legitimate cases where T is a hidden type in dynamic_cast<T>, e.g., when both the
115 // type and the cast are in the same library. This heuristic appears to be conservative
116 // enough to produce only a few false positives (which have been addressed with
117 // preceding commits, marking the relevant types in global include files as
118 // SAL_DLLPUBLIC_RTTI after all, to be on the safe side) and aggressive enough to find
119 // at least some interesting cases (though it would still not be aggressive enough to
120 // have found ff570b4b58dbf274d3094d21d974f18b613e9b4b "DocumentSettingsSerializer must
121 // be SAL_DLLPUBLIC_RTTI for dynamic_cast"):
122 auto const file
= getFilenameOfLocation(
123 compiler
.getSourceManager().getSpellingLoc(rdd
->getLocation()));
124 if (loplugin::hasPathnamePrefix(file
, SRCDIR
"/include/")) {
125 std::size_t const n1
= std::strlen(SRCDIR
"/include/");
126 std::size_t n2
= file
.find('/', n1
);
128 n2
= std::min(n2
, file
.find('\\', n1
));
130 auto const seg
= n2
>= file
.size() ? file
.substr(n1
) : file
.substr(n1
, n2
- n1
);
131 auto prefix
= std::string(SRCDIR
"/");
133 if (!loplugin::hasPathnamePrefix(
134 handler
.getMainFileName(), prefix
))
137 DiagnosticsEngine::Warning
,
138 "Suspicious dynamic_cast to %0 with %1 type visibility", expr
->getExprLoc())
139 << td
<< vis(getTypeVisibility(rdd
)) << expr
->getSourceRange();
140 report(DiagnosticsEngine::Note
, "class %0 defined here", rdd
->getLocation())
141 << td
<< rdd
->getSourceRange();
146 auto ts
= expr
->getSubExpr()->getType();
147 while (auto const t
= ts
->getAs
<clang::PointerType
>()) {
148 ts
= t
->getPointeeType();
150 auto const rts
= ts
->getAs
<RecordType
>();
151 if (rts
== nullptr) { // in case it's a dependent type
154 auto const rds
= cast
<CXXRecordDecl
>(rts
->getDecl())->getDefinition();
155 assert(rds
!= nullptr);
158 if (!(isDerivedFrom(rdd
, rds
, &bs
, &hidden
) && hidden
)) {
162 DiagnosticsEngine::Warning
,
163 ("dynamic_cast from %0 with %1 type visibility to %2 with %3 type"
166 << ts
<< vis(getTypeVisibility(rds
)) << td
167 << vis(getTypeVisibility(rdd
)) << expr
->getSourceRange();
169 DiagnosticsEngine::Note
,
170 "base class %0 with %1 type visibility defined here",
172 << ts
<< vis(getTypeVisibility(rds
)) << rds
->getSourceRange();
173 for (auto const i
: bs
) {
174 if (getTypeVisibility(i
) != DefaultVisibility
) {
176 DiagnosticsEngine::Note
,
177 ("intermediary class %0 with %1 type visibility defined"
180 << i
<< vis(getTypeVisibility(i
)) << i
->getSourceRange();
184 DiagnosticsEngine::Note
,
185 "derived class %0 with %1 type visibility defined here",
187 << td
<< vis(getTypeVisibility(rdd
)) << rdd
->getSourceRange();
191 virtual bool preRun() override
{
192 return compiler
.getLangOpts().CPlusPlus
;
195 void run() override
{
197 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
202 static loplugin::Plugin::Registration
<DynCastVisibility
> dyncastvisibility(
203 "dyncastvisibility");
207 #endif // LO_CLANG_SHARED_PLUGINS
209 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */