bump product version to 6.4.0.3
[LibreOffice.git] / compilerplugins / clang / refcounting.cxx
blobc002a14999772305b851cb976dd1e84a94a8e48d
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 */
10 #include <string>
11 #include <iostream>
13 #include "check.hxx"
14 #include "plugin.hxx"
15 #include "clang/AST/CXXInheritance.h"
17 /**
19 If you have:
21 class Foo : public css::foo::XBaa {
24 Then XBaa has acquire and release methods inherited from XInterface.
25 These are hard lifecycle controls.
27 If you see another class:
29 class Baz {
30 Foo aFooMember;
33 this is a bug =) since aFooMember assumes heap allocated lifecycle and
34 not delete on last 'release'.
38 namespace loplugin {
40 class RefCounting:
41 public loplugin::FilteringPlugin<RefCounting>
43 public:
44 explicit RefCounting(loplugin::InstantiationData const & data): FilteringPlugin(data)
47 virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
49 bool VisitFieldDecl(const FieldDecl *);
50 bool VisitVarDecl(const VarDecl *);
51 bool VisitFunctionDecl(const FunctionDecl *);
53 // Creation of temporaries with one argument are represented by
54 // CXXFunctionalCastExpr, while any other number of arguments are
55 // represented by CXXTemporaryObjectExpr:
56 bool VisitCXXTemporaryObjectExpr(CXXTemporaryObjectExpr const * expr)
57 { return visitTemporaryObjectExpr(expr); }
58 bool VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr const * expr)
59 { return visitTemporaryObjectExpr(expr); }
61 bool WalkUpFromObjCIvarDecl(ObjCIvarDecl * decl) {
62 // Don't recurse into WalkUpFromFieldDecl, as VisitFieldDecl calls
63 // FieldDecl::getParent, which triggers an assertion at least with
64 // current trunk towards Clang 3.7 when the FieldDecl is actually an
65 // ObjCIvarDecl.
66 return VisitObjCIvarDecl(decl);
68 private:
69 void checkUnoReference(QualType qt, const Decl* decl,
70 const RecordDecl* parent, const std::string& rDeclName);
72 bool visitTemporaryObjectExpr(Expr const * expr);
75 bool containsXInterfaceSubclass(const clang::Type* pType0);
77 bool containsXInterfaceSubclass(const QualType& qType) {
78 return containsXInterfaceSubclass(qType.getTypePtr());
81 bool containsXInterfaceSubclass(const clang::Type* pType0) {
82 if (!pType0)
83 return false;
84 const clang::Type* pType = pType0->getUnqualifiedDesugaredType();
85 if (!pType)
86 return false;
87 const CXXRecordDecl* pRecordDecl = pType->getAsCXXRecordDecl();
88 if (pRecordDecl) {
89 pRecordDecl = pRecordDecl->getCanonicalDecl();
90 // these classes override acquire/release and forwards to its parent
91 if (isDerivedFrom(pRecordDecl, [](Decl const * decl) -> bool { return bool(loplugin::DeclCheck(decl).Class("ListenerMultiplexerBase").GlobalNamespace()); })) { // module UnoTools
92 return false;
94 if (isDerivedFrom(pRecordDecl, [](Decl const * decl) -> bool { return bool(loplugin::DeclCheck(decl).Class("GridEventForwarder").Namespace("toolkit").GlobalNamespace()); })) { // module toolkit
95 return false;
97 if (isDerivedFrom(pRecordDecl, [](Decl const * decl) -> bool { return bool(loplugin::DeclCheck(decl).Class("OWeakSubObject").GlobalNamespace()); })) { // module svx
98 return false;
100 if (isDerivedFrom(pRecordDecl, [](Decl const * decl) -> bool { return bool(loplugin::DeclCheck(decl).Class("OSbaWeakSubObject").Namespace("dbaui").GlobalNamespace()); })) { // module dbaccess
101 return false;
103 // FIXME This class has private operator new, and I cannot figure out how it can be dynamically instantiated
104 if (isDerivedFrom(pRecordDecl, [](Decl const * decl) -> bool { return bool(loplugin::DeclCheck(decl).Class("XPropertyList").GlobalNamespace()); })) { // module svx
105 return false;
107 // tdf#114596
108 if (isDerivedFrom(pRecordDecl, [](Decl const * decl) -> bool { return bool(loplugin::DeclCheck(decl).Class("OBookmarkContainer").Namespace("dbaccess").GlobalNamespace()); })) { // module dbaccess
109 return false;
112 // tdf#114596
113 if (isDerivedFrom(pRecordDecl, [](Decl const * decl) -> bool { return bool(loplugin::DeclCheck(decl).Class("OCollection").Namespace("dbaccess").GlobalNamespace()); })) { // module dbaccess
114 return false;
117 if (pRecordDecl) {
118 const ClassTemplateSpecializationDecl* pTemplate = dyn_cast<ClassTemplateSpecializationDecl>(pRecordDecl);
119 if (pTemplate) {
120 // Probably good templates:
121 loplugin::DeclCheck dc(pTemplate);
122 if ((dc.Struct("FindUnoInstanceHint").AnonymousNamespace()
123 .GlobalNamespace())
124 || (dc.Class("OMultiInstanceAutoRegistration").Namespace("abp")
125 .GlobalNamespace())
126 || (dc.Class("Reference").Namespace("uno").Namespace("star")
127 .Namespace("sun").Namespace("com").GlobalNamespace())
128 || (dc.Class("WeakReference").Namespace("uno").Namespace("star")
129 .Namespace("sun").Namespace("com").GlobalNamespace())
130 || (dc.Class("Sequence").Namespace("uno").Namespace("star")
131 .Namespace("sun").Namespace("com").GlobalNamespace())
132 || (dc.Class("WeakCppRef").Namespace("accessibility")
133 .GlobalNamespace())
134 || (dc.Class("OAutoRegistration").Namespace("dba")
135 .GlobalNamespace())
136 || (dc.Class("OMultiInstanceAutoRegistration").Namespace("dbp")
137 .GlobalNamespace())
138 || (dc.Class("OMultiInstanceAutoRegistration")
139 .Namespace("dbaui").GlobalNamespace())
140 || (dc.Class("OMultiInstanceAutoRegistration")
141 .Namespace("dbaxml").GlobalNamespace())
142 || (dc.Struct("ReferenceEqual").Namespace("io_acceptor")
143 .GlobalNamespace())
144 || (dc.Struct("ReferenceHash").Namespace("io_acceptor")
145 .GlobalNamespace())
146 || (dc.Class("OAutoRegistration").Namespace("comphelper")
147 .GlobalNamespace())
148 || dc.Class("WeakBag").Namespace("comphelper").GlobalNamespace()
149 || (dc.Struct("class_").Namespace("service_decl")
150 .Namespace("comphelper").GlobalNamespace())
151 || (dc.Struct("vba_service_class_").Namespace("service_decl")
152 .Namespace("comphelper").GlobalNamespace())
153 || (dc.Struct("inheritingClass_").Namespace("service_decl")
154 .Namespace("comphelper").GlobalNamespace())
155 || (dc.Class("OAutoRegistration").Namespace("module")
156 .Namespace("comphelper").GlobalNamespace())
157 || (dc.Class("mem_fun1_t").Namespace("comphelper")
158 .GlobalNamespace())
159 || (dc.Class("OSimpleListenerContainer").Namespace("comphelper")
160 .GlobalNamespace())
161 || (dc.Class("OAutoRegistration").Namespace("dbmm")
162 .GlobalNamespace())
163 || (dc.Class("OAutoRegistration").Namespace("pcr")
164 .GlobalNamespace())
165 || (dc.Class("ComponentMethodGuard").Namespace("logging")
166 .GlobalNamespace())
167 || (dc.Class("OAutoRegistration").Namespace("logging")
168 .GlobalNamespace())
169 || dc.Class("Reference").Namespace("rtl").GlobalNamespace()
170 || (dc.Class("OAutoRegistration").Namespace("sdbtools")
171 .GlobalNamespace())
172 || (dc.Struct("ReferenceEqual").Namespace("stoc_connector")
173 .GlobalNamespace())
174 || (dc.Struct("ReferenceHash").Namespace("stoc_connector")
175 .GlobalNamespace())
176 || dc.Class("mem_fun_t").StdNamespace()
177 || dc.Class("mem_fun1_t").StdNamespace()
178 || dc.Class("SwIterator").GlobalNamespace()
179 || (dc.Class("SharedUNOComponent").Namespace("utl")
180 .GlobalNamespace())
181 || (dc.Class("OAutoRegistration").Namespace("utl")
182 .GlobalNamespace())
183 || (dc.Class("DeleteUnoReferenceOnDeinit").Namespace("vcl")
184 .GlobalNamespace())
185 || (dc.Struct("OInterfaceCompare").Namespace("xmloff")
186 .GlobalNamespace()))
188 return false;
192 if (pType->isPointerType()) {
193 // ignore
194 return false;
195 } else if (pType->isArrayType()) {
196 const clang::ArrayType* pArrayType = dyn_cast<clang::ArrayType>(pType);
197 QualType elementType = pArrayType->getElementType();
198 return containsXInterfaceSubclass(elementType);
199 } else {
200 return isDerivedFrom(pRecordDecl, [](Decl const * decl) -> bool { return bool(loplugin::DeclCheck(decl).Class("XInterface").Namespace("uno").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace()); });
204 bool containsSvRefBaseSubclass(const clang::Type* pType0) {
205 if (!pType0)
206 return false;
207 const clang::Type* pType = pType0->getUnqualifiedDesugaredType();
208 if (!pType)
209 return false;
210 const CXXRecordDecl* pRecordDecl = pType->getAsCXXRecordDecl();
211 if (pRecordDecl) {
212 pRecordDecl = pRecordDecl->getCanonicalDecl();
214 if (pRecordDecl) {
215 const ClassTemplateSpecializationDecl* pTemplate = dyn_cast<ClassTemplateSpecializationDecl>(pRecordDecl);
216 if (pTemplate) {
217 if (loplugin::DeclCheck(pTemplate).Class("SvRef")
218 .Namespace("tools").GlobalNamespace())
220 return false;
222 for(unsigned i=0; i<pTemplate->getTemplateArgs().size(); ++i) {
223 const TemplateArgument& rArg = pTemplate->getTemplateArgs()[i];
224 if (rArg.getKind() == TemplateArgument::ArgKind::Type &&
225 containsSvRefBaseSubclass(rArg.getAsType().getTypePtr()))
227 return true;
232 if (pType->isPointerType()) {
233 // ignore
234 return false;
235 } else if (pType->isArrayType()) {
236 const clang::ArrayType* pArrayType = dyn_cast<clang::ArrayType>(pType);
237 QualType elementType = pArrayType->getElementType();
238 return containsSvRefBaseSubclass(elementType.getTypePtr());
239 } else {
240 return isDerivedFrom(pRecordDecl, [](Decl const * decl) -> bool { return bool(loplugin::DeclCheck(decl).Class("SvRefBase").Namespace("tools").GlobalNamespace()); });
244 bool containsSalhelperReferenceObjectSubclass(const clang::Type* pType0) {
245 if (!pType0)
246 return false;
247 const clang::Type* pType = pType0->getUnqualifiedDesugaredType();
248 if (!pType)
249 return false;
250 const CXXRecordDecl* pRecordDecl = pType->getAsCXXRecordDecl();
251 if (pRecordDecl) {
252 pRecordDecl = pRecordDecl->getCanonicalDecl();
254 if (pRecordDecl) {
255 const ClassTemplateSpecializationDecl* pTemplate = dyn_cast<ClassTemplateSpecializationDecl>(pRecordDecl);
256 if (pTemplate) {
257 auto const dc = loplugin::DeclCheck(pTemplate);
258 if (dc.Class("Reference").Namespace("rtl").GlobalNamespace()
259 || (dc.Class("OStoreHandle").Namespace("store")
260 .GlobalNamespace()))
262 return false;
264 for(unsigned i=0; i<pTemplate->getTemplateArgs().size(); ++i) {
265 const TemplateArgument& rArg = pTemplate->getTemplateArgs()[i];
266 if (rArg.getKind() == TemplateArgument::ArgKind::Type &&
267 containsSalhelperReferenceObjectSubclass(rArg.getAsType().getTypePtr()))
269 return true;
274 if (pType->isPointerType()) {
275 // ignore
276 return false;
277 } else if (pType->isArrayType()) {
278 const clang::ArrayType* pArrayType = dyn_cast<clang::ArrayType>(pType);
279 QualType elementType = pArrayType->getElementType();
280 return containsSalhelperReferenceObjectSubclass(elementType.getTypePtr());
281 } else {
282 return isDerivedFrom(pRecordDecl, [](Decl const * decl) -> bool { return bool(loplugin::DeclCheck(decl).Class("SimpleReferenceObject").Namespace("salhelper").GlobalNamespace()); });
286 static bool containsStaticTypeMethod(const CXXRecordDecl* x)
288 for (auto it = x->method_begin(); it != x->method_end(); ++it) {
289 auto i = *it;
290 if ( !i->isStatic() )
291 continue;
292 auto ident = i->getIdentifier();
293 if ( ident && ident->isStr("static_type") ) {
294 return true;
297 return false;
300 void RefCounting::checkUnoReference(QualType qt, const Decl* decl, const RecordDecl* parent, const std::string& rDeclName)
302 if (loplugin::TypeCheck(qt).Class("Reference").Namespace("uno").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace()) {
303 const CXXRecordDecl* pRecordDecl = qt->getAsCXXRecordDecl();
304 const ClassTemplateSpecializationDecl* pTemplate = dyn_cast<ClassTemplateSpecializationDecl>(pRecordDecl);
305 const TemplateArgument& rArg = pTemplate->getTemplateArgs()[0];
306 const CXXRecordDecl* templateParam = rArg.getAsType()->getAsCXXRecordDecl()->getDefinition();
307 if (templateParam && !containsStaticTypeMethod(templateParam)) {
308 report(
309 DiagnosticsEngine::Warning,
310 ("uno::Reference %0 with template parameter that does not"
311 " contain ::static_type() %1%select{|, parent is %3,}2 should"
312 " probably be using rtl::Reference instead"),
313 decl->getLocation())
314 << rDeclName << qt << (parent != nullptr)
315 << (parent != nullptr
316 ? parent->getQualifiedNameAsString() : std::string())
317 << decl->getSourceRange();
322 bool RefCounting::visitTemporaryObjectExpr(Expr const * expr) {
323 if (ignoreLocation(expr)) {
324 return true;
326 auto t = expr->getType();
327 if (containsSvRefBaseSubclass(t.getTypePtr())) {
328 report(
329 DiagnosticsEngine::Warning,
330 ("Temporary object of SvRefBase subclass %0 being directly stack"
331 " managed, should be managed via tools::SvRef"),
332 compat::getBeginLoc(expr))
333 << t.getUnqualifiedType() << expr->getSourceRange();
334 } else if (containsSalhelperReferenceObjectSubclass(t.getTypePtr())) {
335 report(
336 DiagnosticsEngine::Warning,
337 ("Temporary object of salhelper::SimpleReferenceObject subclass %0"
338 " being directly stack managed, should be managed via"
339 " rtl::Reference"),
340 compat::getBeginLoc(expr))
341 << t.getUnqualifiedType() << expr->getSourceRange();
342 } else if (containsXInterfaceSubclass(t)) {
343 report(
344 DiagnosticsEngine::Warning,
345 ("Temporary object of css::uno::XInterface subclass %0 being"
346 " directly stack managed, should be managed via"
347 " css::uno::Reference"),
348 compat::getBeginLoc(expr))
349 << t.getUnqualifiedType() << expr->getSourceRange();
351 return true;
354 bool RefCounting::VisitFieldDecl(const FieldDecl * fieldDecl) {
355 if (ignoreLocation(fieldDecl)) {
356 return true;
358 if (fieldDecl->isBitField()) {
359 return true;
362 // check for dodgy code managing ref-counted stuff with shared_ptr or unique_ptr or similar stuff
363 QualType firstTemplateParamType;
364 if (auto recordType = fieldDecl->getType()->getUnqualifiedDesugaredType()->getAs<RecordType>()) {
365 auto const tc = loplugin::TypeCheck(fieldDecl->getType());
366 if (tc.Class("unique_ptr").StdNamespace()
367 || tc.Class("shared_ptr").StdNamespace()
368 || tc.Class("intrusive_ptr").Namespace("boost").GlobalNamespace())
370 auto templateDecl = dyn_cast<ClassTemplateSpecializationDecl>(recordType->getDecl());
371 if (templateDecl && templateDecl->getTemplateArgs().size() > 0)
372 firstTemplateParamType = templateDecl->getTemplateArgs()[0].getAsType();
376 if (containsSvRefBaseSubclass(fieldDecl->getType().getTypePtr())) {
377 report(
378 DiagnosticsEngine::Warning,
379 "SvRefBase subclass %0 being directly heap managed, should be managed via tools::SvRef, "
380 ", parent is %1",
381 fieldDecl->getLocation())
382 << fieldDecl->getType()
383 << fieldDecl->getParent()
384 << fieldDecl->getSourceRange();
387 if (!firstTemplateParamType.isNull() && containsSvRefBaseSubclass(firstTemplateParamType.getTypePtr()))
389 report(
390 DiagnosticsEngine::Warning,
391 "SvRefBase subclass %0 being managed via smart pointer, should be managed via tools::SvRef, "
392 "parent is %1",
393 fieldDecl->getLocation())
394 << firstTemplateParamType
395 << fieldDecl->getParent()
396 << fieldDecl->getSourceRange();
399 if (containsSalhelperReferenceObjectSubclass(fieldDecl->getType().getTypePtr())) {
400 report(
401 DiagnosticsEngine::Warning,
402 "salhelper::SimpleReferenceObject subclass %0 being directly heap managed, should be managed via rtl::Reference, "
403 "parent is %1",
404 fieldDecl->getLocation())
405 << fieldDecl->getType()
406 << fieldDecl->getParent()
407 << fieldDecl->getSourceRange();
410 if (!firstTemplateParamType.isNull() && containsSalhelperReferenceObjectSubclass(firstTemplateParamType.getTypePtr()))
412 report(
413 DiagnosticsEngine::Warning,
414 "salhelper::SimpleReferenceObject subclass %0 being managed via smart pointer, should be managed via rtl::Reference, "
415 "parent is %1",
416 fieldDecl->getLocation())
417 << firstTemplateParamType
418 << fieldDecl->getParent()
419 << fieldDecl->getSourceRange();
422 auto const dc = loplugin::DeclCheck(fieldDecl->getParent());
423 if ( (dc.Class("BaseReference").Namespace("uno").Namespace("star")
424 .Namespace("sun").Namespace("com").GlobalNamespace())
425 || (dc.Union("element_alias").Namespace("detail").Namespace("cppu")
426 .GlobalNamespace())
427 // this is playing some kind of game to avoid circular references
428 || (dc.Class("ResultSetDataSupplier").Namespace("ucbhelper")
429 .GlobalNamespace()))
431 return true;
434 if (containsXInterfaceSubclass(fieldDecl->getType())) {
435 report(
436 DiagnosticsEngine::Warning,
437 "XInterface subclass %0 being directly heap managed, should be managed via uno::Reference, "
438 "parent is %1",
439 fieldDecl->getLocation())
440 << fieldDecl->getType()
441 << fieldDecl->getParent()
442 << fieldDecl->getSourceRange();
445 // Not in general (dbaccess::DocumentEvents, dbaccess/source/core/dataaccess/databasedocument.hxx):
446 #if 0
447 if (!firstTemplateParamType.isNull() && containsXInterfaceSubclass(firstTemplateParamType))
449 report(
450 DiagnosticsEngine::Warning,
451 "XInterface subclass %0 being managed via smart pointer, should be managed via uno::Reference, "
452 "parent is %1",
453 fieldDecl->getLocation())
454 << firstTemplateParamType
455 << fieldDecl->getParent()
456 << fieldDecl->getSourceRange();
458 #endif
460 checkUnoReference(
461 fieldDecl->getType(), fieldDecl,
462 fieldDecl->getParent(), "field");
464 return true;
468 bool RefCounting::VisitVarDecl(const VarDecl * varDecl) {
469 if (ignoreLocation(varDecl)) {
470 return true;
472 if (!isa<ParmVarDecl>(varDecl)) {
473 if (containsSvRefBaseSubclass(varDecl->getType().getTypePtr())) {
474 report(
475 DiagnosticsEngine::Warning,
476 "SvRefBase subclass being directly stack managed, should be managed via tools::SvRef, "
477 + varDecl->getType().getAsString(),
478 varDecl->getLocation())
479 << varDecl->getSourceRange();
481 if (containsSalhelperReferenceObjectSubclass(varDecl->getType().getTypePtr())) {
482 StringRef name { getFilenameOfLocation(
483 compiler.getSourceManager().getSpellingLoc(varDecl->getLocation())) };
484 // this is playing games that it believes is safe
485 if (loplugin::isSamePathname(name, SRCDIR "/stoc/source/security/permissions.cxx"))
486 return true;
487 report(
488 DiagnosticsEngine::Warning,
489 "salhelper::SimpleReferenceObject subclass being directly stack managed, should be managed via rtl::Reference, "
490 + varDecl->getType().getAsString(),
491 varDecl->getLocation())
492 << varDecl->getSourceRange();
494 if (containsXInterfaceSubclass(varDecl->getType())) {
495 report(
496 DiagnosticsEngine::Warning,
497 "XInterface subclass being directly stack managed, should be managed via uno::Reference, "
498 + varDecl->getType().getAsString(),
499 varDecl->getLocation())
500 << varDecl->getSourceRange();
503 checkUnoReference(varDecl->getType(), varDecl, nullptr, "var");
504 return true;
507 bool RefCounting::VisitFunctionDecl(const FunctionDecl * functionDecl) {
508 if (ignoreLocation(functionDecl)) {
509 return true;
511 // only consider base declarations, not overridden ones, or we warn on methods that
512 // are overriding stuff from external libraries
513 const CXXMethodDecl * methodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
514 if (methodDecl && methodDecl->size_overridden_methods() > 0) {
515 return true;
517 checkUnoReference(functionDecl->getReturnType(), functionDecl, nullptr, "return");
518 return true;
521 loplugin::Plugin::Registration< RefCounting > X("refcounting");
525 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */