1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
9 // versions before 9.0 didn't have getExceptionSpecType
13 // clang before V9 does not have API to report exception spec type
14 #if CLANG_VERSION >= 90000
22 Look for move constructors that can be noexcept.
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;
31 class NoExceptMove
: public loplugin::FilteringPlugin
<NoExceptMove
>
34 explicit NoExceptMove(loplugin::InstantiationData
const& data
)
35 : FilteringPlugin(data
)
39 virtual void run() override
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"))
46 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
49 bool shouldVisitImplicitCode() const { return true; }
51 bool TraverseCXXConstructorDecl(CXXConstructorDecl
*);
52 bool TraverseCXXMethodDecl(CXXMethodDecl
*);
53 bool VisitCallExpr(const CallExpr
*);
54 bool VisitCXXConstructExpr(const CXXConstructExpr
*);
55 bool VisitVarDecl(const VarDecl
*);
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
;
64 bool NoExceptMove::TraverseCXXConstructorDecl(CXXConstructorDecl
* constructorDecl
)
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;
74 m_ConstructorThrows
.push_back(false);
75 m_Exclusions
.emplace_back();
76 m_CannotFix
.push_back(false);
78 bool rv
= RecursiveASTVisitor::TraverseCXXConstructorDecl(constructorDecl
);
81 if (!m_ConstructorThrows
.back())
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();
92 else if (bLookForStuffWeCanFix
&& !m_CannotFix
.back())
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();
107 m_ConstructorThrows
.pop_back();
108 m_Exclusions
.pop_back();
109 m_CannotFix
.pop_back();
114 bool NoExceptMove::TraverseCXXMethodDecl(CXXMethodDecl
* methodDecl
)
116 bool isMove
= methodDecl
->isMoveAssignmentOperator()
117 && methodDecl
->getExceptionSpecType() == EST_None
&& !methodDecl
->isDefaulted()
118 && !methodDecl
->isDeleted() && !ignoreLocation(methodDecl
)
119 && methodDecl
->isThisDeclarationADefinition() && methodDecl
->getBody() != nullptr;
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"))
130 m_ConstructorThrows
.push_back(false);
131 m_Exclusions
.emplace_back();
132 m_CannotFix
.push_back(false);
134 bool rv
= RecursiveASTVisitor::TraverseCXXMethodDecl(methodDecl
);
137 if (!m_ConstructorThrows
.back())
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();
148 else if (bLookForStuffWeCanFix
&& !m_CannotFix
.back())
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();
163 m_ConstructorThrows
.pop_back();
164 m_Exclusions
.pop_back();
165 m_CannotFix
.pop_back();
170 bool NoExceptMove::VisitCallExpr(const CallExpr
* callExpr
)
172 if (ignoreLocation(callExpr
))
174 if (m_ConstructorThrows
.empty())
176 llvm::Optional
<bool> bCallThrows
= IsCallThrows(callExpr
);
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;
188 m_ConstructorThrows
.back() = true;
192 static bool IsCallThrowsSpec(clang::ExceptionSpecificationType est
)
194 return !(est
== EST_DynamicNone
|| est
== EST_NoThrow
|| est
== EST_BasicNoexcept
195 || est
== EST_NoexceptTrue
);
198 bool NoExceptMove::VisitCXXConstructExpr(const CXXConstructExpr
* constructExpr
)
200 if (ignoreLocation(constructExpr
))
202 if (m_ConstructorThrows
.empty())
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
))
210 if (bLookForStuffWeCanFix
)
212 if (est
== EST_None
&& !ignoreLocation(constructorDecl
))
213 m_Exclusions
.back().push_back(constructorDecl
);
215 m_CannotFix
.back() = true;
217 m_ConstructorThrows
.back() = true;
222 bool NoExceptMove::VisitVarDecl(const VarDecl
* varDecl
)
224 if (varDecl
->getLocation().isValid() && ignoreLocation(varDecl
))
226 if (m_ConstructorThrows
.empty())
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())
232 auto cxxRecordDecl
= varDecl
->getType()->getAsCXXRecordDecl();
235 auto destructorDecl
= cxxRecordDecl
->getDestructor();
238 auto est
= destructorDecl
->getExceptionSpecType();
239 if (destructorDecl
->isDefaulted() && est
== EST_None
)
240 ; // ok, non-throwing
241 else if (IsCallThrowsSpec(est
))
243 if (bLookForStuffWeCanFix
)
245 if (est
== EST_None
&& !ignoreLocation(destructorDecl
))
246 m_Exclusions
.back().push_back(destructorDecl
);
248 m_CannotFix
.back() = true;
250 m_ConstructorThrows
.back() = true;
255 llvm::Optional
<bool> NoExceptMove::IsCallThrows(const CallExpr
* callExpr
)
257 const FunctionDecl
* calleeFunctionDecl
= callExpr
->getDirectCallee();
258 if (calleeFunctionDecl
)
260 auto est
= calleeFunctionDecl
->getExceptionSpecType();
261 if (bLookForStuffWeCanFix
)
263 if (est
== EST_None
&& !ignoreLocation(calleeFunctionDecl
))
264 m_Exclusions
.back().push_back(calleeFunctionDecl
);
266 m_CannotFix
.back() = true;
268 // Whitelist 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())
273 auto name
= calleeFunctionDecl
->getName();
274 if (auto cxxMethodDecl
= dyn_cast
<CXXMethodDecl
>(calleeFunctionDecl
))
275 if (loplugin::ContextCheck(cxxMethodDecl
->getParent()->getDeclContext())
281 && (name
== "acquire" || name
== "release"))
283 if (name
== "osl_releasePipe" || name
== "osl_destroySocketAddr")
286 return IsCallThrowsSpec(est
);
289 auto calleeExpr
= callExpr
->getCallee();
290 if (isa
<CXXDependentScopeMemberExpr
>(calleeExpr
) || isa
<UnresolvedLookupExpr
>(calleeExpr
))
292 m_CannotFix
.back() = true;
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();
304 m_CannotFix
.back() = true;
305 return llvm::Optional
<bool>();
308 // whitelist 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")
313 if (calleeType
->isPointerType())
314 calleeType
= calleeType
->getPointeeType();
315 auto funcProto
= calleeType
->getAs
<FunctionProtoType
>();
318 m_CannotFix
.back() = true;
319 return llvm::Optional
<bool>();
322 auto est
= funcProto
->getExceptionSpecType();
323 if (bLookForStuffWeCanFix
)
325 m_CannotFix
.back() = true; // TODO, could improve
327 return IsCallThrowsSpec(est
);
330 loplugin::Plugin::Registration
<NoExceptMove
> noexceptmove("noexceptmove");
333 #endif // CLANG_VERSION
334 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */