xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp (revision d99bd55a5e092774214ba31fc5a871bfc31e711c)
1 //==- CheckObjCDealloc.cpp - Check ObjC -dealloc implementation --*- C++ -*-==//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 //  This file defines a CheckObjCDealloc, a checker that
11 //  analyzes an Objective-C class's implementation to determine if it
12 //  correctly implements -dealloc.
13 //
14 //===----------------------------------------------------------------------===//
15 
16 #include "clang/StaticAnalyzer/Checkers/LocalCheckers.h"
17 #include "clang/StaticAnalyzer/BugReporter/PathDiagnostic.h"
18 #include "clang/StaticAnalyzer/BugReporter/BugReporter.h"
19 #include "clang/AST/ExprObjC.h"
20 #include "clang/AST/Expr.h"
21 #include "clang/AST/DeclObjC.h"
22 #include "clang/Basic/LangOptions.h"
23 #include "llvm/Support/raw_ostream.h"
24 
25 using namespace clang;
26 using namespace ento;
27 
28 static bool scan_dealloc(Stmt* S, Selector Dealloc) {
29 
30   if (ObjCMessageExpr* ME = dyn_cast<ObjCMessageExpr>(S))
31     if (ME->getSelector() == Dealloc) {
32       switch (ME->getReceiverKind()) {
33       case ObjCMessageExpr::Instance: return false;
34       case ObjCMessageExpr::SuperInstance: return true;
35       case ObjCMessageExpr::Class: break;
36       case ObjCMessageExpr::SuperClass: break;
37       }
38     }
39 
40   // Recurse to children.
41 
42   for (Stmt::child_iterator I = S->child_begin(), E= S->child_end(); I!=E; ++I)
43     if (*I && scan_dealloc(*I, Dealloc))
44       return true;
45 
46   return false;
47 }
48 
49 static bool scan_ivar_release(Stmt* S, ObjCIvarDecl* ID,
50                               const ObjCPropertyDecl* PD,
51                               Selector Release,
52                               IdentifierInfo* SelfII,
53                               ASTContext& Ctx) {
54 
55   // [mMyIvar release]
56   if (ObjCMessageExpr* ME = dyn_cast<ObjCMessageExpr>(S))
57     if (ME->getSelector() == Release)
58       if (ME->getInstanceReceiver())
59         if (Expr* Receiver = ME->getInstanceReceiver()->IgnoreParenCasts())
60           if (ObjCIvarRefExpr* E = dyn_cast<ObjCIvarRefExpr>(Receiver))
61             if (E->getDecl() == ID)
62               return true;
63 
64   // [self setMyIvar:nil];
65   if (ObjCMessageExpr* ME = dyn_cast<ObjCMessageExpr>(S))
66     if (ME->getInstanceReceiver())
67       if (Expr* Receiver = ME->getInstanceReceiver()->IgnoreParenCasts())
68         if (DeclRefExpr* E = dyn_cast<DeclRefExpr>(Receiver))
69           if (E->getDecl()->getIdentifier() == SelfII)
70             if (ME->getMethodDecl() == PD->getSetterMethodDecl() &&
71                 ME->getNumArgs() == 1 &&
72                 ME->getArg(0)->isNullPointerConstant(Ctx,
73                                               Expr::NPC_ValueDependentIsNull))
74               return true;
75 
76   // self.myIvar = nil;
77   if (BinaryOperator* BO = dyn_cast<BinaryOperator>(S))
78     if (BO->isAssignmentOp())
79       if (ObjCPropertyRefExpr* PRE =
80            dyn_cast<ObjCPropertyRefExpr>(BO->getLHS()->IgnoreParenCasts()))
81         if (PRE->isExplicitProperty() && PRE->getExplicitProperty() == PD)
82             if (BO->getRHS()->isNullPointerConstant(Ctx,
83                                             Expr::NPC_ValueDependentIsNull)) {
84               // This is only a 'release' if the property kind is not
85               // 'assign'.
86               return PD->getSetterKind() != ObjCPropertyDecl::Assign;;
87             }
88 
89   // Recurse to children.
90   for (Stmt::child_iterator I = S->child_begin(), E= S->child_end(); I!=E; ++I)
91     if (*I && scan_ivar_release(*I, ID, PD, Release, SelfII, Ctx))
92       return true;
93 
94   return false;
95 }
96 
97 void ento::CheckObjCDealloc(const ObjCImplementationDecl* D,
98                           const LangOptions& LOpts, BugReporter& BR) {
99 
100   assert (LOpts.getGCMode() != LangOptions::GCOnly);
101 
102   ASTContext& Ctx = BR.getContext();
103   const ObjCInterfaceDecl* ID = D->getClassInterface();
104 
105   // Does the class contain any ivars that are pointers (or id<...>)?
106   // If not, skip the check entirely.
107   // NOTE: This is motivated by PR 2517:
108   //        http://llvm.org/bugs/show_bug.cgi?id=2517
109 
110   bool containsPointerIvar = false;
111 
112   for (ObjCInterfaceDecl::ivar_iterator I=ID->ivar_begin(), E=ID->ivar_end();
113        I!=E; ++I) {
114 
115     ObjCIvarDecl* ID = *I;
116     QualType T = ID->getType();
117 
118     if (!T->isObjCObjectPointerType() ||
119         ID->getAttr<IBOutletAttr>() || // Skip IBOutlets.
120         ID->getAttr<IBOutletCollectionAttr>()) // Skip IBOutletCollections.
121       continue;
122 
123     containsPointerIvar = true;
124     break;
125   }
126 
127   if (!containsPointerIvar)
128     return;
129 
130   // Determine if the class subclasses NSObject.
131   IdentifierInfo* NSObjectII = &Ctx.Idents.get("NSObject");
132   IdentifierInfo* SenTestCaseII = &Ctx.Idents.get("SenTestCase");
133 
134 
135   for ( ; ID ; ID = ID->getSuperClass()) {
136     IdentifierInfo *II = ID->getIdentifier();
137 
138     if (II == NSObjectII)
139       break;
140 
141     // FIXME: For now, ignore classes that subclass SenTestCase, as these don't
142     // need to implement -dealloc.  They implement tear down in another way,
143     // which we should try and catch later.
144     //  http://llvm.org/bugs/show_bug.cgi?id=3187
145     if (II == SenTestCaseII)
146       return;
147   }
148 
149   if (!ID)
150     return;
151 
152   // Get the "dealloc" selector.
153   IdentifierInfo* II = &Ctx.Idents.get("dealloc");
154   Selector S = Ctx.Selectors.getSelector(0, &II);
155   ObjCMethodDecl* MD = 0;
156 
157   // Scan the instance methods for "dealloc".
158   for (ObjCImplementationDecl::instmeth_iterator I = D->instmeth_begin(),
159        E = D->instmeth_end(); I!=E; ++I) {
160 
161     if ((*I)->getSelector() == S) {
162       MD = *I;
163       break;
164     }
165   }
166 
167   if (!MD) { // No dealloc found.
168 
169     const char* name = LOpts.getGCMode() == LangOptions::NonGC
170                        ? "missing -dealloc"
171                        : "missing -dealloc (Hybrid MM, non-GC)";
172 
173     std::string buf;
174     llvm::raw_string_ostream os(buf);
175     os << "Objective-C class '" << D << "' lacks a 'dealloc' instance method";
176 
177     BR.EmitBasicReport(name, os.str(), D->getLocStart());
178     return;
179   }
180 
181   // dealloc found.  Scan for missing [super dealloc].
182   if (MD->getBody() && !scan_dealloc(MD->getBody(), S)) {
183 
184     const char* name = LOpts.getGCMode() == LangOptions::NonGC
185                        ? "missing [super dealloc]"
186                        : "missing [super dealloc] (Hybrid MM, non-GC)";
187 
188     std::string buf;
189     llvm::raw_string_ostream os(buf);
190     os << "The 'dealloc' instance method in Objective-C class '" << D
191        << "' does not send a 'dealloc' message to its super class"
192            " (missing [super dealloc])";
193 
194     BR.EmitBasicReport(name, os.str(), D->getLocStart());
195     return;
196   }
197 
198   // Get the "release" selector.
199   IdentifierInfo* RII = &Ctx.Idents.get("release");
200   Selector RS = Ctx.Selectors.getSelector(0, &RII);
201 
202   // Get the "self" identifier
203   IdentifierInfo* SelfII = &Ctx.Idents.get("self");
204 
205   // Scan for missing and extra releases of ivars used by implementations
206   // of synthesized properties
207   for (ObjCImplementationDecl::propimpl_iterator I = D->propimpl_begin(),
208        E = D->propimpl_end(); I!=E; ++I) {
209 
210     // We can only check the synthesized properties
211     if ((*I)->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize)
212       continue;
213 
214     ObjCIvarDecl* ID = (*I)->getPropertyIvarDecl();
215     if (!ID)
216       continue;
217 
218     QualType T = ID->getType();
219     if (!T->isObjCObjectPointerType()) // Skip non-pointer ivars
220       continue;
221 
222     const ObjCPropertyDecl* PD = (*I)->getPropertyDecl();
223     if (!PD)
224       continue;
225 
226     // ivars cannot be set via read-only properties, so we'll skip them
227     if (PD->isReadOnly())
228        continue;
229 
230     // ivar must be released if and only if the kind of setter was not 'assign'
231     bool requiresRelease = PD->getSetterKind() != ObjCPropertyDecl::Assign;
232     if (scan_ivar_release(MD->getBody(), ID, PD, RS, SelfII, Ctx)
233        != requiresRelease) {
234       const char *name;
235       const char* category = "Memory (Core Foundation/Objective-C)";
236 
237       std::string buf;
238       llvm::raw_string_ostream os(buf);
239 
240       if (requiresRelease) {
241         name = LOpts.getGCMode() == LangOptions::NonGC
242                ? "missing ivar release (leak)"
243                : "missing ivar release (Hybrid MM, non-GC)";
244 
245         os << "The '" << ID
246            << "' instance variable was retained by a synthesized property but "
247               "wasn't released in 'dealloc'";
248       } else {
249         name = LOpts.getGCMode() == LangOptions::NonGC
250                ? "extra ivar release (use-after-release)"
251                : "extra ivar release (Hybrid MM, non-GC)";
252 
253         os << "The '" << ID
254            << "' instance variable was not retained by a synthesized property "
255               "but was released in 'dealloc'";
256       }
257 
258       BR.EmitBasicReport(name, category, os.str(), (*I)->getLocation());
259     }
260   }
261 }
262 
263