Update git submodules
[LibreOffice.git] / compilerplugins / clang / noexceptmove.cxx
blob9be55e870e615f3f97702b254eaafc021f47a2f7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 */
9 // versions before 9.0 didn't have getExceptionSpecType
11 #include "check.hxx"
12 #include "compat.hxx"
13 #include "plugin.hxx"
15 #include "config_clang.h"
17 #include <string>
18 #include <set>
20 /**
21 Look for move constructors that can be noexcept.
24 namespace
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>
32 public:
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"))
44 return;
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*);
56 private:
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;
71 if (isMove)
73 m_ConstructorThrows.push_back(false);
74 m_Exclusions.emplace_back();
75 m_CannotFix.push_back(false);
77 bool rv = RecursiveASTVisitor::TraverseCXXConstructorDecl(constructorDecl);
78 if (isMove)
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();
110 return rv;
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;
119 if (isMove)
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"))
125 isMove = false;
127 if (isMove)
129 m_ConstructorThrows.push_back(false);
130 m_Exclusions.emplace_back();
131 m_CannotFix.push_back(false);
133 bool rv = RecursiveASTVisitor::TraverseCXXMethodDecl(methodDecl);
134 if (isMove)
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();
166 return rv;
169 bool NoExceptMove::VisitCallExpr(const CallExpr* callExpr)
171 if (ignoreLocation(callExpr))
172 return true;
173 if (m_ConstructorThrows.empty())
174 return true;
175 compat::optional<bool> bCallThrows = IsCallThrows(callExpr);
176 if (!bCallThrows)
178 callExpr->dump();
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;
184 return true;
186 if (*bCallThrows)
187 m_ConstructorThrows.back() = true;
188 return 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))
200 return true;
201 if (m_ConstructorThrows.empty())
202 return true;
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);
213 else
214 m_CannotFix.back() = true;
216 m_ConstructorThrows.back() = true;
218 return true;
221 bool NoExceptMove::VisitVarDecl(const VarDecl* varDecl)
223 if (varDecl->getLocation().isValid() && ignoreLocation(varDecl))
224 return true;
225 if (m_ConstructorThrows.empty())
226 return true;
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())
230 return true;
231 auto cxxRecordDecl = varDecl->getType()->getAsCXXRecordDecl();
232 if (!cxxRecordDecl)
233 return true;
234 auto destructorDecl = cxxRecordDecl->getDestructor();
235 if (!destructorDecl)
236 return true;
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);
246 else
247 m_CannotFix.back() = true;
249 m_ConstructorThrows.back() = true;
251 return 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);
264 else
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())
275 .Namespace("uno")
276 .Namespace("star")
277 .Namespace("sun")
278 .Namespace("com")
279 .GlobalNamespace()
280 && (name == "acquire" || name == "release"))
281 return false;
282 if (name == "osl_releasePipe" || name == "osl_destroySocketAddr")
283 return false;
285 return IsCallThrowsSpec(est);
288 auto calleeExpr = callExpr->getCallee();
289 if (isa<CXXDependentScopeMemberExpr>(calleeExpr) || isa<UnresolvedLookupExpr>(calleeExpr)
290 || isa<UnresolvedMemberExpr>(calleeExpr))
292 m_CannotFix.back() = true;
293 return 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();
302 else
304 m_CannotFix.back() = true;
305 return compat::optional<bool>();
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;
313 if (calleeType->isPointerType())
314 calleeType = calleeType->getPointeeType();
315 auto funcProto = calleeType->getAs<FunctionProtoType>();
316 if (!funcProto)
318 m_CannotFix.back() = true;
319 return compat::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 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */