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
22 using Bases
= std::set
<CXXRecordDecl
const *>;
24 Visibility
getTypeVisibility(CXXRecordDecl
const * decl
) {
25 assert(decl
->isThisDeclarationADefinition());
26 if (auto const opt
= decl
->getExplicitVisibility(
27 NamedDecl::VisibilityForType
))
31 if (auto const opt
= decl
->getExplicitVisibility(
32 NamedDecl::VisibilityForValue
))
36 auto const vis
= decl
->getVisibility();
37 return vis
== DefaultVisibility
&& decl
->isInAnonymousNamespace()
38 ? HiddenVisibility
: vis
;
41 // Check whether 'decl' is derived from 'base', gathering any 'bases' between
42 // 'decl' and 'base', and whether any of those 'bases' or 'base' are 'hidden'
43 // (i.e., have non-default visibility):
45 CXXRecordDecl
const * decl
, CXXRecordDecl
const * base
, Bases
* bases
,
49 for (auto const i
: decl
->bases()) {
51 = (cast
<CXXRecordDecl
>(i
.getType()->getAs
<RecordType
>()->getDecl())
53 assert(bd
!= nullptr);
55 *hidden
|= getTypeVisibility(base
) != DefaultVisibility
;
58 else if (bd
->isDerivedFrom(base
)) {
59 if (bases
->insert(bd
).second
) {
60 auto const d
= isDerivedFrom(bd
, base
, bases
, hidden
);
63 *hidden
|= getTypeVisibility(bd
) != DefaultVisibility
;
71 StringRef
vis(Visibility v
) {
73 case HiddenVisibility
:
75 case ProtectedVisibility
:
77 case DefaultVisibility
:
80 llvm_unreachable("unknown visibility");
83 class DynCastVisibility final
:
84 public loplugin::FilteringPlugin
<DynCastVisibility
>
87 explicit DynCastVisibility(loplugin::InstantiationData
const & data
):
88 FilteringPlugin(data
) {}
90 bool shouldVisitTemplateInstantiations() const { return true; }
92 bool VisitCXXDynamicCastExpr(CXXDynamicCastExpr
const * expr
) {
93 if (ignoreLocation(expr
)) {
96 auto td
= expr
->getTypeAsWritten();
97 if (auto const t
= td
->getAs
<ReferenceType
>()) {
98 td
= t
->getPointeeType();
100 while (auto const t
= td
->getAs
<clang::PointerType
>()) {
101 td
= t
->getPointeeType();
103 auto const rtd
= td
->getAs
<RecordType
>();
104 if (rtd
== nullptr) {
107 auto const rdd
= cast
<CXXRecordDecl
>(rtd
->getDecl())->getDefinition();
108 assert(rdd
!= nullptr);
109 if (getTypeVisibility(rdd
) != DefaultVisibility
) {
110 // Heuristic to find problematic dynamic_cast<T> with hidden type T is: T is defined in
111 // include/M1/ while the compilation unit is in module M2/ with M1 != M2. There are
112 // legitimate cases where T is a hidden type in dynamic_cast<T>, e.g., when both the
113 // type and the cast are in the same library. This heuristic appears to be conservative
114 // enough to produce only a few false positives (which have been addressed with
115 // preceding commits, marking the relevant types in global include files as
116 // SAL_DLLPUBLIC_RTTI after all, to be on the safe side) and aggressive enough to find
117 // at least some interesting cases (though it would still not be aggressive enough to
118 // have found ff570b4b58dbf274d3094d21d974f18b613e9b4b "DocumentSettingsSerializer must
119 // be SAL_DLLPUBLIC_RTTI for dynamic_cast"):
120 auto const file
= getFilenameOfLocation(
121 compiler
.getSourceManager().getSpellingLoc(rdd
->getLocation()));
122 if (loplugin::hasPathnamePrefix(file
, SRCDIR
"/include/")) {
123 std::size_t const n1
= std::strlen(SRCDIR
"/include/");
124 std::size_t n2
= file
.find('/', n1
);
126 n2
= std::min(n2
, file
.find('\\', n1
));
128 auto const seg
= n2
>= file
.size() ? file
.substr(n1
) : file
.substr(n1
, n2
- n1
);
129 auto prefix
= std::string(SRCDIR
"/");
131 if (!loplugin::hasPathnamePrefix(
132 handler
.getMainFileName(), prefix
))
135 DiagnosticsEngine::Warning
,
136 "Suspicious dynamic_cast to %0 with %1 type visibility", expr
->getExprLoc())
137 << td
<< vis(getTypeVisibility(rdd
)) << expr
->getSourceRange();
138 report(DiagnosticsEngine::Note
, "class %0 defined here", rdd
->getLocation())
139 << td
<< rdd
->getSourceRange();
144 auto ts
= expr
->getSubExpr()->getType();
145 while (auto const t
= ts
->getAs
<clang::PointerType
>()) {
146 ts
= t
->getPointeeType();
148 auto const rts
= ts
->getAs
<RecordType
>();
149 if (rts
== nullptr) { // in case it's a dependent type
152 auto const rds
= cast
<CXXRecordDecl
>(rts
->getDecl())->getDefinition();
153 assert(rds
!= nullptr);
156 if (!(isDerivedFrom(rdd
, rds
, &bs
, &hidden
) && hidden
)) {
160 DiagnosticsEngine::Warning
,
161 ("dynamic_cast from %0 with %1 type visibility to %2 with %3 type"
164 << ts
<< vis(getTypeVisibility(rds
)) << td
165 << vis(getTypeVisibility(rdd
)) << expr
->getSourceRange();
167 DiagnosticsEngine::Note
,
168 "base class %0 with %1 type visibility defined here",
170 << ts
<< vis(getTypeVisibility(rds
)) << rds
->getSourceRange();
171 for (auto const i
: bs
) {
172 if (getTypeVisibility(i
) != DefaultVisibility
) {
174 DiagnosticsEngine::Note
,
175 ("intermediary class %0 with %1 type visibility defined"
178 << i
<< vis(getTypeVisibility(i
)) << i
->getSourceRange();
182 DiagnosticsEngine::Note
,
183 "derived class %0 with %1 type visibility defined here",
185 << td
<< vis(getTypeVisibility(rdd
)) << rdd
->getSourceRange();
189 virtual bool preRun() override
{
190 return compiler
.getLangOpts().CPlusPlus
;
193 void run() override
{
195 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
200 static loplugin::Plugin::Registration
<DynCastVisibility
> dyncastvisibility(
201 "dyncastvisibility");
205 #endif // LO_CLANG_SHARED_PLUGINS
207 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */