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
15 #include "config_clang.h"
21 Look for move constructors that can be noexcept.
26 /// Look for the stuff that can be marked noexcept, but only if we also mark some of the callees noexcept.
27 /// Off by default so as not too annoy people.
28 constexpr bool bLookForStuffWeCanFix
= false;
30 class NoExceptMove
: public loplugin::FilteringPlugin
<NoExceptMove
>
33 explicit NoExceptMove(loplugin::InstantiationData
const& data
)
34 : FilteringPlugin(data
)
38 virtual void run() override
40 StringRef
fn(handler
.getMainFileName());
41 // ONDXPagePtr::operator= calls ONDXPage::ReleaseRef which cannot be noexcept
42 if (loplugin::isSamePathname(fn
,
43 SRCDIR
"/connectivity/source/drivers/dbase/dindexnode.cxx"))
45 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
48 bool shouldVisitImplicitCode() const { return true; }
50 bool TraverseCXXConstructorDecl(CXXConstructorDecl
*);
51 bool TraverseCXXMethodDecl(CXXMethodDecl
*);
52 bool VisitCallExpr(const CallExpr
*);
53 bool VisitCXXConstructExpr(const CXXConstructExpr
*);
54 bool VisitVarDecl(const VarDecl
*);
57 compat::optional
<bool> IsCallThrows(const CallExpr
* callExpr
);
58 std::vector
<bool> m_ConstructorThrows
;
59 std::vector
<std::vector
<const Decl
*>> m_Exclusions
;
60 std::vector
<bool> m_CannotFix
;
63 bool NoExceptMove::TraverseCXXConstructorDecl(CXXConstructorDecl
* constructorDecl
)
65 const bool isMove
= constructorDecl
->isMoveConstructor()
66 && constructorDecl
->getExceptionSpecType() == EST_None
67 && !constructorDecl
->isDefaulted() && !constructorDecl
->isDeleted()
68 && !ignoreLocation(constructorDecl
)
69 && constructorDecl
->isThisDeclarationADefinition()
70 && constructorDecl
->getBody() != nullptr;
73 m_ConstructorThrows
.push_back(false);
74 m_Exclusions
.emplace_back();
75 m_CannotFix
.push_back(false);
77 bool rv
= RecursiveASTVisitor::TraverseCXXConstructorDecl(constructorDecl
);
80 if (!m_ConstructorThrows
.back())
82 report(DiagnosticsEngine::Warning
, "move constructor can be noexcept",
83 constructorDecl
->getSourceRange().getBegin())
84 << constructorDecl
->getSourceRange();
85 auto canonicalDecl
= constructorDecl
->getCanonicalDecl();
86 if (canonicalDecl
!= constructorDecl
)
87 report(DiagnosticsEngine::Note
, "declaration here",
88 canonicalDecl
->getSourceRange().getBegin())
89 << canonicalDecl
->getSourceRange();
91 else if (bLookForStuffWeCanFix
&& !m_CannotFix
.back())
93 report(DiagnosticsEngine::Warning
, "move constructor can be noexcept",
94 constructorDecl
->getSourceRange().getBegin())
95 << constructorDecl
->getSourceRange();
96 auto canonicalDecl
= constructorDecl
->getCanonicalDecl();
97 if (canonicalDecl
!= constructorDecl
)
98 report(DiagnosticsEngine::Note
, "declaration here",
99 canonicalDecl
->getSourceRange().getBegin())
100 << canonicalDecl
->getSourceRange();
101 for (const Decl
* callDecl
: m_Exclusions
.back())
102 report(DiagnosticsEngine::Warning
, "but need to fix this to be noexcept",
103 callDecl
->getSourceRange().getBegin())
104 << callDecl
->getSourceRange();
106 m_ConstructorThrows
.pop_back();
107 m_Exclusions
.pop_back();
108 m_CannotFix
.pop_back();
113 bool NoExceptMove::TraverseCXXMethodDecl(CXXMethodDecl
* methodDecl
)
115 bool isMove
= methodDecl
->isMoveAssignmentOperator()
116 && methodDecl
->getExceptionSpecType() == EST_None
&& !methodDecl
->isDefaulted()
117 && !methodDecl
->isDeleted() && !ignoreLocation(methodDecl
)
118 && methodDecl
->isThisDeclarationADefinition() && methodDecl
->getBody() != nullptr;
121 StringRef fn
= getFilenameOfLocation(
122 compiler
.getSourceManager().getSpellingLoc(methodDecl
->getBeginLoc()));
123 // SfxObjectShellLock::operator= calls SotObject::OwnerLock which in turn calls stuff which cannot be noexcept
124 if (loplugin::isSamePathname(fn
, SRCDIR
"/include/sfx2/objsh.hxx"))
129 m_ConstructorThrows
.push_back(false);
130 m_Exclusions
.emplace_back();
131 m_CannotFix
.push_back(false);
133 bool rv
= RecursiveASTVisitor::TraverseCXXMethodDecl(methodDecl
);
136 if (!m_ConstructorThrows
.back())
138 report(DiagnosticsEngine::Warning
, "move operator= can be noexcept",
139 methodDecl
->getSourceRange().getBegin())
140 << methodDecl
->getSourceRange();
141 auto canonicalDecl
= methodDecl
->getCanonicalDecl();
142 if (canonicalDecl
!= methodDecl
)
143 report(DiagnosticsEngine::Note
, "declaration here",
144 canonicalDecl
->getSourceRange().getBegin())
145 << canonicalDecl
->getSourceRange();
147 else if (bLookForStuffWeCanFix
&& !m_CannotFix
.back())
149 report(DiagnosticsEngine::Warning
, "move operator= can be noexcept",
150 methodDecl
->getSourceRange().getBegin())
151 << methodDecl
->getSourceRange();
152 auto canonicalDecl
= methodDecl
->getCanonicalDecl();
153 if (canonicalDecl
!= methodDecl
)
154 report(DiagnosticsEngine::Note
, "declaration here",
155 canonicalDecl
->getSourceRange().getBegin())
156 << canonicalDecl
->getSourceRange();
157 for (const Decl
* callDecl
: m_Exclusions
.back())
158 report(DiagnosticsEngine::Warning
, "but need to fix this to be noexcept",
159 callDecl
->getSourceRange().getBegin())
160 << callDecl
->getSourceRange();
162 m_ConstructorThrows
.pop_back();
163 m_Exclusions
.pop_back();
164 m_CannotFix
.pop_back();
169 bool NoExceptMove::VisitCallExpr(const CallExpr
* callExpr
)
171 if (ignoreLocation(callExpr
))
173 if (m_ConstructorThrows
.empty())
175 compat::optional
<bool> bCallThrows
= IsCallThrows(callExpr
);
179 if (callExpr
->getCalleeDecl())
180 callExpr
->getCalleeDecl()->dump();
181 report(DiagnosticsEngine::Warning
, "what's up doc?", callExpr
->getSourceRange().getBegin())
182 << callExpr
->getSourceRange();
183 m_ConstructorThrows
.back() = true;
187 m_ConstructorThrows
.back() = true;
191 static bool IsCallThrowsSpec(clang::ExceptionSpecificationType est
)
193 return !(est
== EST_DynamicNone
|| est
== EST_NoThrow
|| est
== EST_BasicNoexcept
194 || est
== EST_NoexceptTrue
);
197 bool NoExceptMove::VisitCXXConstructExpr(const CXXConstructExpr
* constructExpr
)
199 if (ignoreLocation(constructExpr
))
201 if (m_ConstructorThrows
.empty())
203 auto constructorDecl
= constructExpr
->getConstructor();
204 auto est
= constructorDecl
->getExceptionSpecType();
205 if (constructorDecl
->isDefaulted() && est
== EST_None
)
206 ; // ok, non-throwing
207 else if (IsCallThrowsSpec(est
))
209 if (bLookForStuffWeCanFix
)
211 if (est
== EST_None
&& !ignoreLocation(constructorDecl
))
212 m_Exclusions
.back().push_back(constructorDecl
);
214 m_CannotFix
.back() = true;
216 m_ConstructorThrows
.back() = true;
221 bool NoExceptMove::VisitVarDecl(const VarDecl
* varDecl
)
223 if (varDecl
->getLocation().isValid() && ignoreLocation(varDecl
))
225 if (m_ConstructorThrows
.empty())
227 // The clang AST does not show me implicit calls to destructors at the end of a block,
228 // so assume any local var decls of class type will call their destructor.
229 if (!varDecl
->getType()->isRecordType())
231 auto cxxRecordDecl
= varDecl
->getType()->getAsCXXRecordDecl();
234 auto destructorDecl
= cxxRecordDecl
->getDestructor();
237 auto est
= destructorDecl
->getExceptionSpecType();
238 if (destructorDecl
->isDefaulted() && est
== EST_None
)
239 ; // ok, non-throwing
240 else if (IsCallThrowsSpec(est
))
242 if (bLookForStuffWeCanFix
)
244 if (est
== EST_None
&& !ignoreLocation(destructorDecl
))
245 m_Exclusions
.back().push_back(destructorDecl
);
247 m_CannotFix
.back() = true;
249 m_ConstructorThrows
.back() = true;
254 compat::optional
<bool> NoExceptMove::IsCallThrows(const CallExpr
* callExpr
)
256 const FunctionDecl
* calleeFunctionDecl
= callExpr
->getDirectCallee();
257 if (calleeFunctionDecl
)
259 auto est
= calleeFunctionDecl
->getExceptionSpecType();
260 if (bLookForStuffWeCanFix
)
262 if (est
== EST_None
&& !ignoreLocation(calleeFunctionDecl
))
263 m_Exclusions
.back().push_back(calleeFunctionDecl
);
265 m_CannotFix
.back() = true;
267 // Allowlist of functions that could be noexcept, but we can't change them because of backwards-compatibility reasons
268 // css::uno::XInterface::acquire
269 // css::uno::XInterface::release
270 if (calleeFunctionDecl
->getIdentifier())
272 auto name
= calleeFunctionDecl
->getName();
273 if (auto cxxMethodDecl
= dyn_cast
<CXXMethodDecl
>(calleeFunctionDecl
))
274 if (loplugin::ContextCheck(cxxMethodDecl
->getParent()->getDeclContext())
280 && (name
== "acquire" || name
== "release"))
282 if (name
== "osl_releasePipe" || name
== "osl_destroySocketAddr")
285 return IsCallThrowsSpec(est
);
288 auto calleeExpr
= callExpr
->getCallee();
289 if (isa
<CXXDependentScopeMemberExpr
>(calleeExpr
) || isa
<UnresolvedLookupExpr
>(calleeExpr
))
291 m_CannotFix
.back() = true;
295 // check for call via function-pointer
296 clang::QualType calleeType
;
297 if (auto fieldDecl
= dyn_cast_or_null
<FieldDecl
>(callExpr
->getCalleeDecl()))
298 calleeType
= fieldDecl
->getType();
299 else if (auto varDecl
= dyn_cast_or_null
<VarDecl
>(callExpr
->getCalleeDecl()))
300 calleeType
= varDecl
->getType();
303 m_CannotFix
.back() = true;
304 return compat::optional
<bool>();
307 // allowlist of functions that could be noexcept, but we can't change them because of backwards-compatibility reasons
308 if (auto typedefType
= calleeType
->getAs
<TypedefType
>())
309 if (typedefType
->getDecl()->getName() == "uno_ReleaseMappingFunc")
312 if (calleeType
->isPointerType())
313 calleeType
= calleeType
->getPointeeType();
314 auto funcProto
= calleeType
->getAs
<FunctionProtoType
>();
317 m_CannotFix
.back() = true;
318 return compat::optional
<bool>();
321 auto est
= funcProto
->getExceptionSpecType();
322 if (bLookForStuffWeCanFix
)
324 m_CannotFix
.back() = true; // TODO, could improve
326 return IsCallThrowsSpec(est
);
329 loplugin::Plugin::Registration
<NoExceptMove
> noexceptmove("noexceptmove");
332 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */