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 
10 #include <cassert>
11 #include <string>
12 #include <iostream>
13 #include <fstream>
14 #include <set>
15 
16 #include <clang/AST/CXXInheritance.h>
17 #include "compat.hxx"
18 #include "plugin.hxx"
19 #include "check.hxx"
20 
21 /**
22  Look for calls to the ref-counting methods acquire()/release(), which should only be called by classes like rtl::Reference.
23 */
24 
25 namespace {
26 
27 class ManualRefCount:
28     public RecursiveASTVisitor<ManualRefCount>, public loplugin::Plugin
29 {
30 public:
ManualRefCount(InstantiationData const & data)31     explicit ManualRefCount(InstantiationData const & data): Plugin(data) {}
32 
run()33     virtual void run() override
34     {
35         StringRef fn( compiler.getSourceManager().getFileEntryForID(
36                           compiler.getSourceManager().getMainFileID())->getName() );
37 
38        // old code, no point in updating
39         if (loplugin::isSamePathname(fn, SRCDIR "/store/source/store.cxx"))
40              return;
41 
42 // TODO -----------------------------
43         if (loplugin::isSamePathname(fn, SRCDIR "/registry/source/registry.cxx"))
44              return;
45         if (loplugin::isSamePathname(fn, SRCDIR "/registry/source/regimpl.cxx"))
46              return;
47         if (loplugin::isSamePathname(fn, SRCDIR "/registry/source/reflread.cxx"))
48              return;
49         // TODO MenuAttributes::CreateAttribute
50         if (loplugin::isSamePathname(fn, SRCDIR "/framework/source/fwe/xml/menuconfiguration.cxx"))
51              return;
52         if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/uibase/app/apphdl.cxx"))
53              return;
54         if (loplugin::isSamePathname(fn, SRCDIR "/dbaccess/source/core/dataaccess/ModelImpl.cxx"))
55              return;
56         // need a better replacement for vcl::EventPoster
57         if (loplugin::isSamePathname(fn, SRCDIR "/svtools/source/misc/acceleratorexecute.cxx"))
58              return;
59         // PostUserEvent stuff
60         if (loplugin::isSamePathname(fn, SRCDIR "/toolkit/source/awt/vclxwindow.cxx"))
61              return;
62         // playing games with pointers passed into combobox entries
63         if (loplugin::isSamePathname(fn, SRCDIR "/cui/source/customize/cfgutil.cxx"))
64              return;
65         if (loplugin::isSamePathname(fn, SRCDIR "/cui/source/customize/cfg.cxx"))
66              return;
67 // END TODO -----------------------------
68 
69         // can't fix these without breaking stable ABI
70         if (fn.startswith(SRCDIR "/sal/"))
71              return;
72         if (fn.startswith(SRCDIR "/salhelper/"))
73              return;
74         if (fn.startswith(SRCDIR "/cppu/"))
75              return;
76         if (fn.startswith(SRCDIR "/cppuhelper/"))
77              return;
78         if (fn.startswith(SRCDIR "/bridges/"))
79              return;
80 
81         // lots of magic here
82         if (fn.startswith(SRCDIR "/stoc/"))
83              return;
84         if (fn.startswith(SRCDIR "/testtools/"))
85              return;
86 
87         // mutex games
88         if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/app/scheduler.cxx"))
89              return;
90         // opengl games
91         if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/app/svdata.cxx"))
92              return;
93 
94         // passing the pointer through PostUserEvent
95         if (loplugin::isSamePathname(fn, SRCDIR "/avmedia/source/gstreamer/gstplayer.cxx"))
96              return;
97         if (loplugin::isSamePathname(fn, SRCDIR "/svx/source/form/fmscriptingenv.cxx"))
98              return;
99 
100         // thread games
101         if (loplugin::isSamePathname(fn, SRCDIR "/io/source/stm/opump.cxx"))
102              return;
103 
104         // ??? no idea what this code is up to
105         if (loplugin::isSamePathname(fn, SRCDIR "/extensions/source/scanner/scanunx.cxx"))
106              return;
107         if (loplugin::isSamePathname(fn, SRCDIR "/stoc/source/invocation_adapterfactory/iafactory.cxx"))
108              return;
109         if (loplugin::isSamePathname(fn, SRCDIR "/fpicker/source/office/asyncfilepicker.cxx"))
110              return;
111         if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/FormComponent.cxx"))
112              return;
113         if (loplugin::isSamePathname(fn, SRCDIR "/ucb/source/ucp/file/bc.cxx"))
114              return;
115         if (loplugin::isSamePathname(fn, SRCDIR "/ucb/source/ucp/file/filprp.cxx"))
116              return;
117         // calling release() ?
118         if (loplugin::isSamePathname(fn, SRCDIR "/toolkit/source/helper/accessibilityclient.cxx"))
119              return;
120         if (loplugin::isSamePathname(fn, SRCDIR "/svtools/source/misc/svtaccessiblefactory.cxx"))
121              return;
122 
123         // implementing css::uno::XInterface
124         if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/animations/motionpathtag.cxx"))
125              return;
126         // UNO factory methods
127         if (fn.startswith(SRCDIR "/comphelper/"))
128              return;
129         if (loplugin::isSamePathname(fn, SRCDIR "/linguistic/source/convdiclist.cxx"))
130              return;
131         if (loplugin::isSamePathname(fn, SRCDIR "/linguistic/source/dlistimp.cxx"))
132              return;
133         if (loplugin::isSamePathname(fn, SRCDIR "/linguistic/source/gciterator.cxx"))
134              return;
135         if (loplugin::isSamePathname(fn, SRCDIR "/linguistic/source/lngsvcmgr.cxx"))
136              return;
137         if (loplugin::isSamePathname(fn, SRCDIR "/linguistic/source/lngopt.cxx"))
138              return;
139         if (loplugin::isSamePathname(fn, SRCDIR "/vcl/unx/generic/gdi/gcach_xpeer.cxx"))
140              return;
141         if (loplugin::isSamePathname(fn, SRCDIR "/dbaccess/source/ui/dlg/dbwizsetup.cxx"))
142              return;
143         if (loplugin::isSamePathname(fn, SRCDIR "/dbaccess/source/ui/dlg/dbwizsetup.cxx"))
144              return;
145         if (loplugin::isSamePathname(fn, SRCDIR "/lingucomponent/source/hyphenator/hyphen/hyphenimp.cxx"))
146              return;
147         if (loplugin::isSamePathname(fn, SRCDIR "/lingucomponent/source/spellcheck/spell/sspellimp.cxx"))
148              return;
149         if (loplugin::isSamePathname(fn, SRCDIR "/lingucomponent/source/thesaurus/libnth/nthesimp.cxx"))
150              return;
151 
152 
153         // some kind of complicated listener nonsense
154         if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/framework/tools/FrameworkHelper.cxx"))
155              return;
156         // more listener nonsense
157         if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/uibase/uno/unomailmerge.cxx"))
158              return;
159         // playing games with it's listener list
160         if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/unoobj/cellsuno.cxx"))
161              return;
162         if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/unoobj/chart2uno.cxx"))
163              return;
164         if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/unoobj/dapiuno.cxx"))
165              return;
166         if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/unoobj/datauno.cxx"))
167              return;
168         if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/unoobj/linkuno.cxx"))
169              return;
170         // PostUserEvent
171         if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/vba/vbaeventshelper.cxx"))
172              return;
173         // thread holding itself
174         if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/EventThread.cxx"))
175              return;
176 
177 
178         TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
179     }
180 
shouldVisitTemplateInstantiations() const181     bool shouldVisitTemplateInstantiations () const { return true; }
182 
183     bool VisitCXXMemberCallExpr(const CXXMemberCallExpr *);
184     bool TraverseCXXRecordDecl(CXXRecordDecl *);
185     bool TraverseCXXMethodDecl(CXXMethodDecl *);
186     bool TraverseFunctionDecl(FunctionDecl *);
187     bool TraverseCXXConstructorDecl(CXXConstructorDecl *);
188     bool TraverseCXXDestructorDecl(CXXDestructorDecl *);
189     bool TraverseCXXConversionDecl(CXXConversionDecl *);
190     bool TraverseClassTemplateSpecializationDecl(ClassTemplateSpecializationDecl *);
191     bool TraverseLinkageSpecDecl(LinkageSpecDecl *);
192 private:
193     bool ignoreCallerClass(CXXRecordDecl*);
194 };
195 
TraverseCXXMethodDecl(CXXMethodDecl * cxxMethodDecl)196 bool ManualRefCount::TraverseCXXMethodDecl(CXXMethodDecl* cxxMethodDecl)
197 {
198     if (ignoreCallerClass(cxxMethodDecl->getParent()))
199         return true;
200     // disambiguating forwarding methods for XInterface subclasses
201     if (cxxMethodDecl->getIdentifier() && (cxxMethodDecl->getName() == "acquire" || cxxMethodDecl->getName() == "release"))
202         return true;
203     return RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
204 }
205 
TraverseFunctionDecl(FunctionDecl * functionDecl)206 bool ManualRefCount::TraverseFunctionDecl(FunctionDecl* functionDecl)
207 {
208     auto tc = loplugin::DeclCheck(functionDecl);
209     if (tc.Function("make_shared_from_UNO").Namespace("comphelper").GlobalNamespace())
210         return true;
211     return RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
212 }
213 
TraverseCXXConstructorDecl(CXXConstructorDecl * cxxMethodDecl)214 bool ManualRefCount::TraverseCXXConstructorDecl(CXXConstructorDecl* cxxMethodDecl)
215 {
216     if (ignoreCallerClass(cxxMethodDecl->getParent()))
217         return true;
218     return RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
219 }
220 
TraverseCXXDestructorDecl(CXXDestructorDecl *)221 bool ManualRefCount::TraverseCXXDestructorDecl(CXXDestructorDecl*)
222 {
223     // just ignore destructors, tons of places like to call acquire() on themselves in their destructor
224     // supposedly to prevent recursively calling the destructor
225     return true;
226 }
TraverseCXXConversionDecl(CXXConversionDecl * cxxMethodDecl)227 bool ManualRefCount::TraverseCXXConversionDecl(CXXConversionDecl* cxxMethodDecl)
228 {
229     if (ignoreCallerClass(cxxMethodDecl->getParent()))
230         return true;
231     return RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
232 }
TraverseCXXRecordDecl(CXXRecordDecl * cxxRecordDecl)233 bool ManualRefCount::TraverseCXXRecordDecl(CXXRecordDecl* cxxRecordDecl)
234 {
235     if (ignoreCallerClass(cxxRecordDecl))
236         return true;
237     return RecursiveASTVisitor::TraverseCXXRecordDecl(cxxRecordDecl);
238 }
239 
TraverseClassTemplateSpecializationDecl(ClassTemplateSpecializationDecl * templateDecl)240 bool ManualRefCount::TraverseClassTemplateSpecializationDecl(ClassTemplateSpecializationDecl* templateDecl)
241 {
242     if (ignoreCallerClass(templateDecl))
243         return true;
244     return RecursiveASTVisitor::TraverseClassTemplateSpecializationDecl(templateDecl);
245 }
246 
TraverseLinkageSpecDecl(LinkageSpecDecl *)247 bool ManualRefCount::TraverseLinkageSpecDecl(LinkageSpecDecl *)
248 {
249     // ignore methods inside "extern ""C""" blocks, these are normally UNO constructors, which
250     // are required to raise the reference count before returning
251     return true;
252 }
253 
ignoreCallerClass(CXXRecordDecl * cxxRecordDecl)254 bool ManualRefCount::ignoreCallerClass(CXXRecordDecl* cxxRecordDecl)
255 {
256     auto tc = loplugin::TypeCheck(cxxRecordDecl);
257     return
258            tc.Class("Reference").Namespace("rtl").GlobalNamespace()
259         || tc.Class("cow_wrapper").Namespace("o3tl").GlobalNamespace()
260         || tc.Class("Reference").Namespace("uno").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace()
261         || tc.Class("ShareGuard").Namespace("framework").GlobalNamespace()
262         || tc.Class("ControlModelLock").Namespace("frm").GlobalNamespace()
263         || tc.Struct("ReleaseFunc").Namespace("detail").Namespace("comphelper").GlobalNamespace()
264         // TODO no idea what this is up to
265         || tc.Class("SfxModelSubComponent").GlobalNamespace()
266         || tc.Class("OSubComponent").Namespace("mysqlc").Namespace("connectivity").GlobalNamespace()
267         || tc.Class("OSubComponent").Namespace("connectivity").GlobalNamespace()
268         // TODO do we really need this?
269         || tc.Class("ShareableMutex").Namespace("framework").GlobalNamespace()
270         || tc.Class("ObservableThread").GlobalNamespace()
271         ;
272 }
273 
VisitCXXMemberCallExpr(const CXXMemberCallExpr * cxxMemberCallExpr)274 bool ManualRefCount::VisitCXXMemberCallExpr(const CXXMemberCallExpr* cxxMemberCallExpr)
275 {
276     if (ignoreLocation(cxxMemberCallExpr))
277         return true;
278     if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(cxxMemberCallExpr->getLocStart())))
279         return true;
280 
281     // first, use some heuristics to find the right kind of acquire()/release() calls
282     CXXMethodDecl const * calleeMethodDecl = cxxMemberCallExpr->getMethodDecl();
283     if (!calleeMethodDecl || !calleeMethodDecl->getIdentifier())
284         return true;
285     if (calleeMethodDecl->getName() != "acquire" && calleeMethodDecl->getName() != "release")
286         return true;
287     if (calleeMethodDecl->getNumParams() != 0)
288         return true;
289     // std::unique_ptr::release() and similar methods
290     if (calleeMethodDecl->getName() == "release" && loplugin::TypeCheck(calleeMethodDecl->getReturnType()).Pointer())
291         return true;
292 
293     // these are OK
294     auto calleeRecordTC = loplugin::TypeCheck(calleeMethodDecl->getParent());
295     if (calleeRecordTC.Struct("ResourceHolder").Namespace("store").GlobalNamespace())
296         return true;
297     if (calleeRecordTC.Class("Module").Namespace("osl").GlobalNamespace())
298         return true;
299     if (calleeRecordTC.Class("Mutex").Namespace("osl").GlobalNamespace())
300         return true;
301     if (calleeRecordTC.Class("multi_type_vector").Namespace("mdds").GlobalNamespace())
302         return true;
303 
304 //    while (calleeMethodDecl->size_overridden_methods() > 0)
305 //        calleeMethodDecl = *calleeMethodDecl->begin_overridden_methods();
306 //    auto tc2 = loplugin::TypeCheck(calleeMethodDecl->getParent());
307 //    if (tc2.Class("XInterface").Namespace("uno").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace())
308 //        return true;
309 
310 std::cout << calleeMethodDecl->getParent()->getQualifiedNameAsString() << std::endl;
311     report(
312         DiagnosticsEngine::Warning, "call to acquire/release",
313         cxxMemberCallExpr->getLocStart())
314         << cxxMemberCallExpr->getSourceRange();
315     return true;
316 }
317 
318 
319 loplugin::Plugin::Registration< ManualRefCount > X("manualrefcount", true);
320 
321 }
322 
323 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
324