bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / dyncastvisibility.cxx
blob0f6021101e2b320afc19d1e31d0d87db5f6fd27b
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
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/.
8 */
10 #ifndef LO_CLANG_SHARED_PLUGINS
12 #include <algorithm>
13 #include <cassert>
14 #include <cstddef>
15 #include <set>
16 #include <string>
18 #include "plugin.hxx"
20 namespace {
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))
29 return *opt;
31 if (auto const opt = decl->getExplicitVisibility(
32 NamedDecl::VisibilityForValue))
34 return *opt;
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):
44 bool isDerivedFrom(
45 CXXRecordDecl const * decl, CXXRecordDecl const * base, Bases * bases,
46 bool * hidden)
48 bool derived = false;
49 for (auto const i: decl->bases()) {
50 auto const bd
51 = (cast<CXXRecordDecl>(i.getType()->getAs<RecordType>()->getDecl())
52 ->getDefinition());
53 assert(bd != nullptr);
54 if (bd == base) {
55 *hidden |= getTypeVisibility(base) != DefaultVisibility;
56 derived = true;
58 else if (bd->isDerivedFrom(base)) {
59 if (bases->insert(bd).second) {
60 auto const d = isDerivedFrom(bd, base, bases, hidden);
61 assert(d);
62 *hidden |= getTypeVisibility(bd) != DefaultVisibility;
64 derived = true;
67 return derived;
70 StringRef vis(Visibility v) {
71 switch (v) {
72 case HiddenVisibility:
73 return "hidden";
74 case ProtectedVisibility:
75 return "protected";
76 case DefaultVisibility:
77 return "default";
78 default:
79 llvm_unreachable("unknown visibility");
83 class DynCastVisibility final:
84 public loplugin::FilteringPlugin<DynCastVisibility>
86 public:
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)) {
94 return true;
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) {
105 return true;
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 = getFileNameOfSpellingLoc(
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);
125 #if defined _WIN32
126 n2 = std::min(n2, file.find('\\', n1));
127 #endif
128 auto const seg = n2 >= file.size() ? file.substr(n1) : file.substr(n1, n2 - n1);
129 auto prefix = std::string(SRCDIR "/");
130 prefix += seg;
131 if (!loplugin::hasPathnamePrefix(
132 handler.getMainFileName(), prefix))
134 report(
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();
142 return true;
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
150 return true;
152 auto const rds = cast<CXXRecordDecl>(rts->getDecl())->getDefinition();
153 assert(rds != nullptr);
154 Bases bs;
155 bool hidden = false;
156 if (!(isDerivedFrom(rdd, rds, &bs, &hidden) && hidden)) {
157 return true;
159 report(
160 DiagnosticsEngine::Warning,
161 ("dynamic_cast from %0 with %1 type visibility to %2 with %3 type"
162 " visibility"),
163 expr->getExprLoc())
164 << ts << vis(getTypeVisibility(rds)) << td
165 << vis(getTypeVisibility(rdd)) << expr->getSourceRange();
166 report(
167 DiagnosticsEngine::Note,
168 "base class %0 with %1 type visibility defined here",
169 rds->getLocation())
170 << ts << vis(getTypeVisibility(rds)) << rds->getSourceRange();
171 for (auto const i: bs) {
172 if (getTypeVisibility(i) != DefaultVisibility) {
173 report(
174 DiagnosticsEngine::Note,
175 ("intermediary class %0 with %1 type visibility defined"
176 " here"),
177 i->getLocation())
178 << i << vis(getTypeVisibility(i)) << i->getSourceRange();
181 report(
182 DiagnosticsEngine::Note,
183 "derived class %0 with %1 type visibility defined here",
184 rdd->getLocation())
185 << td << vis(getTypeVisibility(rdd)) << rdd->getSourceRange();
186 return true;
189 virtual bool preRun() override {
190 return compiler.getLangOpts().CPlusPlus;
192 private:
193 void run() override {
194 if (preRun()) {
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: */