1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 // versions before 9.0 didn't have getExceptionSpecType
10 
11 #include "plugin.hxx"
12 
13 // clang before V9 does not have API to report exception spec type
14 #if CLANG_VERSION >= 90000
15 
16 #include "check.hxx"
17 
18 #include <string>
19 #include <set>
20 
21 /**
22   Look for move constructors that can be noexcept.
23 */
24 
25 namespace
26 {
27 /// Look for the stuff that can be marked noexcept, but only if we also mark some of the callees noexcept.
28 /// Off by default so as not too annoy people.
29 constexpr bool bLookForStuffWeCanFix = false;
30 
31 class NoExceptMove : public loplugin::FilteringPlugin<NoExceptMove>
32 {
33 public:
NoExceptMove(loplugin::InstantiationData const & data)34     explicit NoExceptMove(loplugin::InstantiationData const& data)
35         : FilteringPlugin(data)
36     {
37     }
38 
run()39     virtual void run() override
40     {
41         StringRef fn(handler.getMainFileName());
42         // ONDXPagePtr::operator= calls ONDXPage::ReleaseRef which cannot be noexcept
43         if (loplugin::isSamePathname(fn,
44                                      SRCDIR "/connectivity/source/drivers/dbase/dindexnode.cxx"))
45             return;
46         TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
47     }
48 
shouldVisitImplicitCode() const49     bool shouldVisitImplicitCode() const { return true; }
50 
51     bool TraverseCXXConstructorDecl(CXXConstructorDecl*);
52     bool TraverseCXXMethodDecl(CXXMethodDecl*);
53     bool VisitCallExpr(const CallExpr*);
54     bool VisitCXXConstructExpr(const CXXConstructExpr*);
55     bool VisitVarDecl(const VarDecl*);
56 
57 private:
58     llvm::Optional<bool> IsCallThrows(const CallExpr* callExpr);
59     std::vector<bool> m_ConstructorThrows;
60     std::vector<std::vector<const Decl*>> m_Exclusions;
61     std::vector<bool> m_CannotFix;
62 };
63 
TraverseCXXConstructorDecl(CXXConstructorDecl * constructorDecl)64 bool NoExceptMove::TraverseCXXConstructorDecl(CXXConstructorDecl* constructorDecl)
65 {
66     const bool isMove = constructorDecl->isMoveConstructor()
67                         && constructorDecl->getExceptionSpecType() == EST_None
68                         && !constructorDecl->isDefaulted() && !constructorDecl->isDeleted()
69                         && !ignoreLocation(constructorDecl)
70                         && constructorDecl->isThisDeclarationADefinition()
71                         && constructorDecl->getBody() != nullptr;
72     if (isMove)
73     {
74         m_ConstructorThrows.push_back(false);
75         m_Exclusions.emplace_back();
76         m_CannotFix.push_back(false);
77     }
78     bool rv = RecursiveASTVisitor::TraverseCXXConstructorDecl(constructorDecl);
79     if (isMove)
80     {
81         if (!m_ConstructorThrows.back())
82         {
83             report(DiagnosticsEngine::Warning, "move constructor can be noexcept",
84                    constructorDecl->getSourceRange().getBegin())
85                 << constructorDecl->getSourceRange();
86             auto canonicalDecl = constructorDecl->getCanonicalDecl();
87             if (canonicalDecl != constructorDecl)
88                 report(DiagnosticsEngine::Note, "declaration here",
89                        canonicalDecl->getSourceRange().getBegin())
90                     << canonicalDecl->getSourceRange();
91         }
92         else if (bLookForStuffWeCanFix && !m_CannotFix.back())
93         {
94             report(DiagnosticsEngine::Warning, "move constructor can be noexcept",
95                    constructorDecl->getSourceRange().getBegin())
96                 << constructorDecl->getSourceRange();
97             auto canonicalDecl = constructorDecl->getCanonicalDecl();
98             if (canonicalDecl != constructorDecl)
99                 report(DiagnosticsEngine::Note, "declaration here",
100                        canonicalDecl->getSourceRange().getBegin())
101                     << canonicalDecl->getSourceRange();
102             for (const Decl* callDecl : m_Exclusions.back())
103                 report(DiagnosticsEngine::Warning, "but need to fix this to be noexcept",
104                        callDecl->getSourceRange().getBegin())
105                     << callDecl->getSourceRange();
106         }
107         m_ConstructorThrows.pop_back();
108         m_Exclusions.pop_back();
109         m_CannotFix.pop_back();
110     }
111     return rv;
112 }
113 
TraverseCXXMethodDecl(CXXMethodDecl * methodDecl)114 bool NoExceptMove::TraverseCXXMethodDecl(CXXMethodDecl* methodDecl)
115 {
116     bool isMove = methodDecl->isMoveAssignmentOperator()
117                   && methodDecl->getExceptionSpecType() == EST_None && !methodDecl->isDefaulted()
118                   && !methodDecl->isDeleted() && !ignoreLocation(methodDecl)
119                   && methodDecl->isThisDeclarationADefinition() && methodDecl->getBody() != nullptr;
120     if (isMove)
121     {
122         StringRef fn = getFilenameOfLocation(
123             compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(methodDecl)));
124         // SfxObjectShellLock::operator= calls SotObject::OwnerLock which in turn calls stuff which cannot be noexcept
125         if (loplugin::isSamePathname(fn, SRCDIR "/include/sfx2/objsh.hxx"))
126             isMove = false;
127     }
128     if (isMove)
129     {
130         m_ConstructorThrows.push_back(false);
131         m_Exclusions.emplace_back();
132         m_CannotFix.push_back(false);
133     }
134     bool rv = RecursiveASTVisitor::TraverseCXXMethodDecl(methodDecl);
135     if (isMove)
136     {
137         if (!m_ConstructorThrows.back())
138         {
139             report(DiagnosticsEngine::Warning, "move operator= can be noexcept",
140                    methodDecl->getSourceRange().getBegin())
141                 << methodDecl->getSourceRange();
142             auto canonicalDecl = methodDecl->getCanonicalDecl();
143             if (canonicalDecl != methodDecl)
144                 report(DiagnosticsEngine::Note, "declaration here",
145                        canonicalDecl->getSourceRange().getBegin())
146                     << canonicalDecl->getSourceRange();
147         }
148         else if (bLookForStuffWeCanFix && !m_CannotFix.back())
149         {
150             report(DiagnosticsEngine::Warning, "move operator= can be noexcept",
151                    methodDecl->getSourceRange().getBegin())
152                 << methodDecl->getSourceRange();
153             auto canonicalDecl = methodDecl->getCanonicalDecl();
154             if (canonicalDecl != methodDecl)
155                 report(DiagnosticsEngine::Note, "declaration here",
156                        canonicalDecl->getSourceRange().getBegin())
157                     << canonicalDecl->getSourceRange();
158             for (const Decl* callDecl : m_Exclusions.back())
159                 report(DiagnosticsEngine::Warning, "but need to fix this to be noexcept",
160                        callDecl->getSourceRange().getBegin())
161                     << callDecl->getSourceRange();
162         }
163         m_ConstructorThrows.pop_back();
164         m_Exclusions.pop_back();
165         m_CannotFix.pop_back();
166     }
167     return rv;
168 }
169 
VisitCallExpr(const CallExpr * callExpr)170 bool NoExceptMove::VisitCallExpr(const CallExpr* callExpr)
171 {
172     if (ignoreLocation(callExpr))
173         return true;
174     if (m_ConstructorThrows.empty())
175         return true;
176     llvm::Optional<bool> bCallThrows = IsCallThrows(callExpr);
177     if (!bCallThrows)
178     {
179         callExpr->dump();
180         if (callExpr->getCalleeDecl())
181             callExpr->getCalleeDecl()->dump();
182         report(DiagnosticsEngine::Warning, "what's up doc?", callExpr->getSourceRange().getBegin())
183             << callExpr->getSourceRange();
184         m_ConstructorThrows.back() = true;
185         return true;
186     }
187     if (*bCallThrows)
188         m_ConstructorThrows.back() = true;
189     return true;
190 }
191 
IsCallThrowsSpec(clang::ExceptionSpecificationType est)192 static bool IsCallThrowsSpec(clang::ExceptionSpecificationType est)
193 {
194     return !(est == EST_DynamicNone || est == EST_NoThrow || est == EST_BasicNoexcept
195              || est == EST_NoexceptTrue);
196 }
197 
VisitCXXConstructExpr(const CXXConstructExpr * constructExpr)198 bool NoExceptMove::VisitCXXConstructExpr(const CXXConstructExpr* constructExpr)
199 {
200     if (ignoreLocation(constructExpr))
201         return true;
202     if (m_ConstructorThrows.empty())
203         return true;
204     auto constructorDecl = constructExpr->getConstructor();
205     auto est = constructorDecl->getExceptionSpecType();
206     if (constructorDecl->isDefaulted() && est == EST_None)
207         ; // ok, non-throwing
208     else if (IsCallThrowsSpec(est))
209     {
210         if (bLookForStuffWeCanFix)
211         {
212             if (est == EST_None && !ignoreLocation(constructorDecl))
213                 m_Exclusions.back().push_back(constructorDecl);
214             else
215                 m_CannotFix.back() = true;
216         }
217         m_ConstructorThrows.back() = true;
218     }
219     return true;
220 }
221 
VisitVarDecl(const VarDecl * varDecl)222 bool NoExceptMove::VisitVarDecl(const VarDecl* varDecl)
223 {
224     if (varDecl->getLocation().isValid() && ignoreLocation(varDecl))
225         return true;
226     if (m_ConstructorThrows.empty())
227         return true;
228     // The clang AST does not show me implicit calls to destructors at the end of a block,
229     // so assume any local var decls of class type will call their destructor.
230     if (!varDecl->getType()->isRecordType())
231         return true;
232     auto cxxRecordDecl = varDecl->getType()->getAsCXXRecordDecl();
233     if (!cxxRecordDecl)
234         return true;
235     auto destructorDecl = cxxRecordDecl->getDestructor();
236     if (!destructorDecl)
237         return true;
238     auto est = destructorDecl->getExceptionSpecType();
239     if (destructorDecl->isDefaulted() && est == EST_None)
240         ; // ok, non-throwing
241     else if (IsCallThrowsSpec(est))
242     {
243         if (bLookForStuffWeCanFix)
244         {
245             if (est == EST_None && !ignoreLocation(destructorDecl))
246                 m_Exclusions.back().push_back(destructorDecl);
247             else
248                 m_CannotFix.back() = true;
249         }
250         m_ConstructorThrows.back() = true;
251     }
252     return true;
253 }
254 
IsCallThrows(const CallExpr * callExpr)255 llvm::Optional<bool> NoExceptMove::IsCallThrows(const CallExpr* callExpr)
256 {
257     const FunctionDecl* calleeFunctionDecl = callExpr->getDirectCallee();
258     if (calleeFunctionDecl)
259     {
260         auto est = calleeFunctionDecl->getExceptionSpecType();
261         if (bLookForStuffWeCanFix)
262         {
263             if (est == EST_None && !ignoreLocation(calleeFunctionDecl))
264                 m_Exclusions.back().push_back(calleeFunctionDecl);
265             else
266                 m_CannotFix.back() = true;
267         }
268         // Allowlist of functions that could be noexcept, but we can't change them because of backwards-compatibility reasons
269         // css::uno::XInterface::acquire
270         // css::uno::XInterface::release
271         if (calleeFunctionDecl->getIdentifier())
272         {
273             auto name = calleeFunctionDecl->getName();
274             if (auto cxxMethodDecl = dyn_cast<CXXMethodDecl>(calleeFunctionDecl))
275                 if (loplugin::ContextCheck(cxxMethodDecl->getParent()->getDeclContext())
276                         .Namespace("uno")
277                         .Namespace("star")
278                         .Namespace("sun")
279                         .Namespace("com")
280                         .GlobalNamespace()
281                     && (name == "acquire" || name == "release"))
282                     return false;
283             if (name == "osl_releasePipe" || name == "osl_destroySocketAddr")
284                 return false;
285         }
286         return IsCallThrowsSpec(est);
287     }
288 
289     auto calleeExpr = callExpr->getCallee();
290     if (isa<CXXDependentScopeMemberExpr>(calleeExpr) || isa<UnresolvedLookupExpr>(calleeExpr))
291     {
292         m_CannotFix.back() = true;
293         return true;
294     }
295 
296     // check for call via function-pointer
297     clang::QualType calleeType;
298     if (auto fieldDecl = dyn_cast_or_null<FieldDecl>(callExpr->getCalleeDecl()))
299         calleeType = fieldDecl->getType();
300     else if (auto varDecl = dyn_cast_or_null<VarDecl>(callExpr->getCalleeDecl()))
301         calleeType = varDecl->getType();
302     else
303     {
304         m_CannotFix.back() = true;
305         return llvm::Optional<bool>();
306     }
307 
308     // allowlist of functions that could be noexcept, but we can't change them because of backwards-compatibility reasons
309     if (auto typedefType = calleeType->getAs<TypedefType>())
310         if (typedefType->getDecl()->getName() == "uno_ReleaseMappingFunc")
311             return false;
312 
313     if (calleeType->isPointerType())
314         calleeType = calleeType->getPointeeType();
315     auto funcProto = calleeType->getAs<FunctionProtoType>();
316     if (!funcProto)
317     {
318         m_CannotFix.back() = true;
319         return llvm::Optional<bool>();
320     }
321 
322     auto est = funcProto->getExceptionSpecType();
323     if (bLookForStuffWeCanFix)
324     {
325         m_CannotFix.back() = true; // TODO, could improve
326     }
327     return IsCallThrowsSpec(est);
328 }
329 
330 loplugin::Plugin::Registration<NoExceptMove> noexceptmove("noexceptmove");
331 }
332 
333 #endif // CLANG_VERSION
334 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
335