bump product version to 6.4.0.3
[LibreOffice.git] / compilerplugins / clang / noexceptmove.cxx
blobb2679024e97bc8330eac82b2c11df3894e35fee0
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 "plugin.hxx"
13 // clang before V9 does not have API to report exception spec type
14 #if CLANG_VERSION >= 90000
16 #include "check.hxx"
18 #include <string>
19 #include <set>
21 /**
22 Look for move constructors that can be noexcept.
25 namespace
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>
33 public:
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"))
45 return;
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*);
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;
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;
72 if (isMove)
74 m_ConstructorThrows.push_back(false);
75 m_Exclusions.emplace_back();
76 m_CannotFix.push_back(false);
78 bool rv = RecursiveASTVisitor::TraverseCXXConstructorDecl(constructorDecl);
79 if (isMove)
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();
111 return rv;
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;
120 if (isMove)
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;
128 if (isMove)
130 m_ConstructorThrows.push_back(false);
131 m_Exclusions.emplace_back();
132 m_CannotFix.push_back(false);
134 bool rv = RecursiveASTVisitor::TraverseCXXMethodDecl(methodDecl);
135 if (isMove)
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();
167 return rv;
170 bool NoExceptMove::VisitCallExpr(const CallExpr* callExpr)
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)
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;
187 if (*bCallThrows)
188 m_ConstructorThrows.back() = true;
189 return 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))
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))
210 if (bLookForStuffWeCanFix)
212 if (est == EST_None && !ignoreLocation(constructorDecl))
213 m_Exclusions.back().push_back(constructorDecl);
214 else
215 m_CannotFix.back() = true;
217 m_ConstructorThrows.back() = true;
219 return true;
222 bool NoExceptMove::VisitVarDecl(const VarDecl* varDecl)
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))
243 if (bLookForStuffWeCanFix)
245 if (est == EST_None && !ignoreLocation(destructorDecl))
246 m_Exclusions.back().push_back(destructorDecl);
247 else
248 m_CannotFix.back() = true;
250 m_ConstructorThrows.back() = true;
252 return 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);
265 else
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())
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;
286 return IsCallThrowsSpec(est);
289 auto calleeExpr = callExpr->getCallee();
290 if (isa<CXXDependentScopeMemberExpr>(calleeExpr) || isa<UnresolvedLookupExpr>(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 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")
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 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: */