Version 6.1.0.2, tag libreoffice-6.1.0.2
[LibreOffice.git] / compilerplugins / clang / dyncastvisibility.cxx
blobafc500f61f619183454be9906cdb7041a1431720
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 #include <algorithm>
11 #include <cassert>
12 #include <cstddef>
13 #include <set>
14 #include <string>
16 #include "plugin.hxx"
18 namespace {
20 using Bases = std::set<CXXRecordDecl const *>;
22 Visibility getTypeVisibility(CXXRecordDecl const * decl) {
23 assert(decl->isThisDeclarationADefinition());
24 if (auto const opt = decl->getExplicitVisibility(
25 NamedDecl::VisibilityForType))
27 return *opt;
29 if (auto const opt = decl->getExplicitVisibility(
30 NamedDecl::VisibilityForValue))
32 return *opt;
34 auto const vis = decl->getVisibility();
35 return vis == DefaultVisibility && decl->isInAnonymousNamespace()
36 ? HiddenVisibility : vis;
39 // Check whether 'decl' is derived from 'base', gathering any 'bases' between
40 // 'decl' and 'base', and whether any of those 'bases' or 'base' are 'hidden'
41 // (i.e., have non-default visibility):
42 bool isDerivedFrom(
43 CXXRecordDecl const * decl, CXXRecordDecl const * base, Bases * bases,
44 bool * hidden)
46 bool derived = false;
47 for (auto const i: decl->bases()) {
48 auto const bd
49 = (cast<CXXRecordDecl>(i.getType()->getAs<RecordType>()->getDecl())
50 ->getDefinition());
51 assert(bd != nullptr);
52 if (bd == base) {
53 *hidden |= getTypeVisibility(base) != DefaultVisibility;
54 derived = true;
56 else if (bd->isDerivedFrom(base)) {
57 if (bases->insert(bd).second) {
58 auto const d = isDerivedFrom(bd, base, bases, hidden);
59 assert(d);
60 *hidden |= getTypeVisibility(bd) != DefaultVisibility;
62 derived = true;
65 return derived;
68 StringRef vis(Visibility v) {
69 switch (v) {
70 case HiddenVisibility:
71 return "hidden";
72 case ProtectedVisibility:
73 return "protected";
74 case DefaultVisibility:
75 return "default";
76 default:
77 llvm_unreachable("unknown visibility");
81 class DynCastVisibility final:
82 public RecursiveASTVisitor<DynCastVisibility>, public loplugin::Plugin
84 public:
85 explicit DynCastVisibility(loplugin::InstantiationData const & data):
86 Plugin(data) {}
88 bool shouldVisitTemplateInstantiations() const { return true; }
90 bool VisitCXXDynamicCastExpr(CXXDynamicCastExpr const * expr) {
91 if (ignoreLocation(expr)) {
92 return true;
94 auto td = expr->getTypeAsWritten();
95 if (auto const t = td->getAs<ReferenceType>()) {
96 td = t->getPointeeType();
98 while (auto const t = td->getAs<clang::PointerType>()) {
99 td = t->getPointeeType();
101 auto const rtd = td->getAs<RecordType>();
102 if (rtd == nullptr) {
103 return true;
105 auto const rdd = cast<CXXRecordDecl>(rtd->getDecl())->getDefinition();
106 assert(rdd != nullptr);
107 if (getTypeVisibility(rdd) != DefaultVisibility) {
108 // Heuristic to find problematic dynamic_cast<T> with hidden type T is: T is defined in
109 // include/M1/ while the compilation unit is in module M2/ with M1 != M2. There are
110 // legitimate cases where T is a hidden type in dynamic_cast<T>, e.g., when both the
111 // type and the cast are in the same library. This heuristic appears to be conservative
112 // enough to produce only a few false positives (which have been addressed with
113 // preceding commits, marking the relevant types in global include files as
114 // SAL_DLLPUBLIC_RTTI after all, to be on the safe side) and aggressive enough to find
115 // at least some interesting cases (though it would still not be aggressive enough to
116 // have found ff570b4b58dbf274d3094d21d974f18b613e9b4b "DocumentSettingsSerializer must
117 // be SAL_DLLPUBLIC_RTTI for dynamic_cast"):
118 auto const file = compiler.getSourceManager().getFilename(
119 compiler.getSourceManager().getSpellingLoc(rdd->getLocation()));
120 if (loplugin::hasPathnamePrefix(file, SRCDIR "/include/")) {
121 std::size_t const n1 = std::strlen(SRCDIR "/include/");
122 std::size_t n2 = file.find('/', n1);
123 #if defined _WIN32
124 n2 = std::min(n2, file.find('\\', n1));
125 #endif
126 auto const seg = n2 >= file.size() ? file.substr(n1) : file.substr(n1, n2 - n1);
127 auto prefix = std::string(SRCDIR "/");
128 prefix += seg;
129 if (!loplugin::hasPathnamePrefix(
130 (compiler.getSourceManager()
131 .getFileEntryForID(compiler.getSourceManager().getMainFileID())
132 ->getName()),
133 prefix))
135 report(
136 DiagnosticsEngine::Warning,
137 "Suspicious dynamic_cast to %0 with %1 type visibility", expr->getExprLoc())
138 << td << vis(getTypeVisibility(rdd)) << expr->getSourceRange();
139 report(DiagnosticsEngine::Note, "class %0 defined here", rdd->getLocation())
140 << td << rdd->getSourceRange();
143 return true;
145 auto ts = expr->getSubExpr()->getType();
146 while (auto const t = ts->getAs<clang::PointerType>()) {
147 ts = t->getPointeeType();
149 auto const rts = ts->getAs<RecordType>();
150 if (rts == nullptr) { // in case it's a dependent type
151 return true;
153 auto const rds = cast<CXXRecordDecl>(rts->getDecl())->getDefinition();
154 assert(rds != nullptr);
155 Bases bs;
156 bool hidden = false;
157 if (!(isDerivedFrom(rdd, rds, &bs, &hidden) && hidden)) {
158 return true;
160 report(
161 DiagnosticsEngine::Warning,
162 ("dynamic_cast from %0 with %1 type visibility to %2 with %3 type"
163 " visibility"),
164 expr->getExprLoc())
165 << ts << vis(getTypeVisibility(rds)) << td
166 << vis(getTypeVisibility(rdd)) << expr->getSourceRange();
167 report(
168 DiagnosticsEngine::Note,
169 "base class %0 with %1 type visibility defined here",
170 rds->getLocation())
171 << ts << vis(getTypeVisibility(rds)) << rds->getSourceRange();
172 for (auto const i: bs) {
173 if (getTypeVisibility(i) != DefaultVisibility) {
174 report(
175 DiagnosticsEngine::Note,
176 ("intermediary class %0 with %1 type visibility defined"
177 " here"),
178 i->getLocation())
179 << i << vis(getTypeVisibility(i)) << i->getSourceRange();
182 report(
183 DiagnosticsEngine::Note,
184 "derived class %0 with %1 type visibility defined here",
185 rdd->getLocation())
186 << td << vis(getTypeVisibility(rdd)) << rdd->getSourceRange();
187 return true;
190 private:
191 void run() override {
192 if (compiler.getLangOpts().CPlusPlus) {
193 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
198 static loplugin::Plugin::Registration<DynCastVisibility> reg(
199 "dyncastvisibility");
203 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */