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