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.
4  *
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  */
9 #ifndef LO_CLANG_SHARED_PLUGINS
10 
11 #include <algorithm>
12 #include <functional>
13 
14 #include "clang/AST/Comment.h"
15 
16 #include "plugin.hxx"
17 
18 // Remove dynamic exception specifications.  See the mail thread starting at
19 // <https://lists.freedesktop.org/archives/libreoffice/2017-January/076665.html>
20 // "Dynamic Exception Specifications" for details.
21 
22 namespace {
23 
isOverriding(FunctionDecl const * decl)24 bool isOverriding(FunctionDecl const * decl) {
25     if (decl->hasAttr<OverrideAttr>()) {
26         return true;
27     }
28     auto m = dyn_cast<CXXMethodDecl>(decl);
29     return m != nullptr
30         && m->begin_overridden_methods() != m->end_overridden_methods();
31 }
32 
isDtorOrDealloc(FunctionDecl const * decl)33 bool isDtorOrDealloc(FunctionDecl const * decl) {
34     if (isa<CXXDestructorDecl>(decl)) {
35         return true;
36     }
37     switch (decl->getOverloadedOperator()) {
38     case OO_Delete:
39     case OO_Array_Delete:
40         return true;
41     default:
42         return false;
43     }
44 }
45 
46 class DynExcSpec:
47     public loplugin::FilteringRewritePlugin<DynExcSpec>
48 {
49 public:
DynExcSpec(loplugin::InstantiationData const & data)50     explicit DynExcSpec(loplugin::InstantiationData const & data):
51         FilteringRewritePlugin(data) {}
52 
preRun()53     bool preRun() override {
54         return compiler.getLangOpts().CPlusPlus;
55     }
56 
run()57     void run() override {
58         if (preRun()) {
59             TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
60         }
61     }
62 
VisitFunctionDecl(FunctionDecl const * decl)63     bool VisitFunctionDecl(FunctionDecl const * decl) {
64         if (ignoreLocation(decl)) {
65             return true;
66         }
67         auto proto = decl->getType()->getAs<FunctionProtoType>();
68         if (proto == nullptr || proto->getExceptionSpecType() != EST_Dynamic) {
69             return true;
70         }
71         if (decl->isCanonicalDecl() && !isOverriding(decl)
72             && !anyRedeclHasThrowsDocumentation(decl))
73         {
74             report(
75                 DiagnosticsEngine::Warning,
76                 ("function declaration has dynamic exception specification but"
77                  " no corresponding documentation comment"),
78                 decl->getLocation())
79                 << decl->getSourceRange();
80         }
81         if (rewriter != nullptr) {
82             if (!(decl->isDefined() || decl->isPure())) {
83                 return true;
84             }
85             if (auto m = dyn_cast<CXXMethodDecl>(decl)) {
86                 for (auto i = m->begin_overridden_methods();
87                      i != m->end_overridden_methods(); ++i)
88                 {
89                     auto proto2 = (*i)->getType()->getAs<FunctionProtoType>();
90                     assert(proto2 != nullptr);
91                     if (proto2->getExceptionSpecType() == EST_Dynamic) {
92                         return true;
93                     }
94                 }
95             }
96         }
97         bool dtorOrDealloc = isDtorOrDealloc(decl);
98         auto const source = decl->getExceptionSpecSourceRange();
99         if (rewriter != nullptr && source.isValid()) {
100             if (dtorOrDealloc) {
101                 if (replaceText(source, "noexcept(false)")) {
102                     return true;
103                 }
104             } else {
105                 auto beg = source.getBegin();
106                 if (beg.isFileID()) {
107                     for (;;) {
108                         auto prev = Lexer::GetBeginningOfToken(
109                             beg.getLocWithOffset(-1),
110                             compiler.getSourceManager(),
111                             compiler.getLangOpts());
112                         auto n = Lexer::MeasureTokenLength(
113                             prev, compiler.getSourceManager(),
114                             compiler.getLangOpts());
115                         auto s = StringRef(
116                             compiler.getSourceManager().getCharacterData(prev),
117                             n);
118                         while (s.startswith("\\\n")) {
119                             s = s.drop_front(2);
120                             while (!s.empty()
121                                    && (s.front() == ' ' || s.front() == '\t'
122                                        || s.front() == '\n' || s.front() == '\v'
123                                        || s.front() == '\f'))
124                             {
125                                 s = s.drop_front(1);
126                             }
127                         }
128                         if (!s.empty() && s != "\\") {
129                             if (s.startswith("//")) {
130                                 beg = source.getBegin();
131                             }
132                             break;
133                         }
134                         beg = prev;
135                     }
136                 }
137                 if (removeText(SourceRange(beg, source.getEnd()))) {
138                     return true;
139                 }
140             }
141         }
142         report(
143             DiagnosticsEngine::Warning,
144             (dtorOrDealloc
145              ? "replace dynamic exception specification with 'noexcept(false)'"
146              : "remove dynamic exception specification"),
147             source.isValid() ? source.getBegin() : decl->getLocation())
148             << (source.isValid() ? source : decl->getSourceRange());
149         return true;
150     }
151 
152 private:
hasThrowsDocumentation(FunctionDecl const * decl)153     bool hasThrowsDocumentation(FunctionDecl const * decl) {
154         if (auto cmt = compiler.getASTContext().getCommentForDecl(
155             decl, &compiler.getPreprocessor()))
156         {
157             for (auto i = cmt->child_begin(); i != cmt->child_end(); ++i) {
158                 if (auto bcc = dyn_cast<comments::BlockCommandComment>(*i)) {
159                     if (compiler.getASTContext().getCommentCommandTraits()
160                         .getCommandInfo(bcc->getCommandID())->IsThrowsCommand)
161                     {
162                         return true;
163                     }
164                 }
165             }
166         }
167         return false;
168     }
169 
anyRedeclHasThrowsDocumentation(FunctionDecl const * decl)170     bool anyRedeclHasThrowsDocumentation(FunctionDecl const * decl) {
171         return std::any_of(
172             decl->redecls_begin(), decl->redecls_end(),
173             [this](FunctionDecl * d) { return hasThrowsDocumentation(d); });
174             // std::bind(
175             //     &DynExcSpec::hasThrowsDocumentation, this,
176             //     std::placeholders::_1));
177     }
178 };
179 
180 loplugin::Plugin::Registration<DynExcSpec> dynexcspec("dynexcspec");
181 
182 } // namespace
183 
184 #endif // LO_CLANG_SHARED_PLUGINS
185 
186 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
187