xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp (revision ea02bba5a10b3514bddcf6cbbbf94aab766ee2ea)
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 "ClangSACheckers.h"
17 #include "clang/AST/Attr.h"
18 #include "clang/AST/DeclObjC.h"
19 #include "clang/AST/Expr.h"
20 #include "clang/AST/ExprObjC.h"
21 #include "clang/Basic/LangOptions.h"
22 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
23 #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
24 #include "clang/StaticAnalyzer/Core/Checker.h"
25 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
26 #include "llvm/Support/raw_ostream.h"
27 
28 using namespace clang;
29 using namespace ento;
30 
31 // FIXME: This was taken from IvarInvalidationChecker.cpp
32 static const Expr *peel(const Expr *E) {
33   E = E->IgnoreParenCasts();
34   if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(E))
35     E = POE->getSyntacticForm()->IgnoreParenCasts();
36   if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(E))
37     E = OVE->getSourceExpr()->IgnoreParenCasts();
38   return E;
39 }
40 
41 static bool scan_ivar_release(Stmt *S, const ObjCIvarDecl *ID,
42                               const ObjCPropertyDecl *PD,
43                               Selector Release,
44                               IdentifierInfo* SelfII,
45                               ASTContext &Ctx) {
46 
47   // [mMyIvar release]
48   if (ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S))
49     if (ME->getSelector() == Release)
50       if (ME->getInstanceReceiver())
51         if (const Expr *Receiver = peel(ME->getInstanceReceiver()))
52           if (auto *E = dyn_cast<ObjCIvarRefExpr>(Receiver))
53             if (E->getDecl() == ID)
54               return true;
55 
56   // [self setMyIvar:nil];
57   if (ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S))
58     if (ME->getInstanceReceiver())
59       if (const Expr *Receiver = peel(ME->getInstanceReceiver()))
60         if (auto *E = dyn_cast<DeclRefExpr>(Receiver))
61           if (E->getDecl()->getIdentifier() == SelfII)
62             if (ME->getMethodDecl() == PD->getSetterMethodDecl() &&
63                 ME->getNumArgs() == 1 &&
64                 peel(ME->getArg(0))->isNullPointerConstant(Ctx,
65                                               Expr::NPC_ValueDependentIsNull))
66               return true;
67 
68   // self.myIvar = nil;
69   if (BinaryOperator* BO = dyn_cast<BinaryOperator>(S))
70     if (BO->isAssignmentOp())
71       if (auto *PRE = dyn_cast<ObjCPropertyRefExpr>(peel(BO->getLHS())))
72         if (PRE->isExplicitProperty() && PRE->getExplicitProperty() == PD)
73             if (peel(BO->getRHS())->isNullPointerConstant(Ctx,
74                                             Expr::NPC_ValueDependentIsNull)) {
75               // This is only a 'release' if the property kind is not
76               // 'assign'.
77               return PD->getSetterKind() != ObjCPropertyDecl::Assign;
78             }
79 
80   // Recurse to children.
81   for (Stmt *SubStmt : S->children())
82     if (SubStmt && scan_ivar_release(SubStmt, ID, PD, Release, SelfII, Ctx))
83       return true;
84 
85   return false;
86 }
87 
88 static bool isSynthesizedRetainableProperty(const ObjCPropertyImplDecl *I,
89                                             const ObjCIvarDecl **ID,
90                                             const ObjCPropertyDecl **PD) {
91 
92   if (I->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize)
93     return false;
94 
95   (*ID) = I->getPropertyIvarDecl();
96   if (!(*ID))
97     return false;
98 
99   QualType T = (*ID)->getType();
100   if (!T->isObjCRetainableType())
101     return false;
102 
103   (*PD) = I->getPropertyDecl();
104   // Shouldn't be able to synthesize a property that doesn't exist.
105   assert(*PD);
106 
107   return true;
108 }
109 
110 static bool synthesizedPropertyRequiresRelease(const ObjCPropertyDecl *PD) {
111   // A synthesized property must be released if and only if the kind of setter
112   // was neither 'assign' or 'weak'.
113   ObjCPropertyDecl::SetterKind SK = PD->getSetterKind();
114   return (SK != ObjCPropertyDecl::Assign && SK != ObjCPropertyDecl::Weak);
115 }
116 
117 static void checkObjCDealloc(const CheckerBase *Checker,
118                              const ObjCImplementationDecl *D,
119                              const LangOptions &LOpts, BugReporter &BR) {
120 
121   assert(LOpts.getGC() != LangOptions::GCOnly);
122   assert(!LOpts.ObjCAutoRefCount);
123 
124   ASTContext &Ctx = BR.getContext();
125   const ObjCInterfaceDecl *ID = D->getClassInterface();
126 
127   // Does the class contain any synthesized properties that are retainable?
128   // If not, skip the check entirely.
129   bool containsRetainedSynthesizedProperty = false;
130   for (const auto *I : D->property_impls()) {
131     const ObjCIvarDecl *ID = nullptr;
132     const ObjCPropertyDecl *PD = nullptr;
133     if (!isSynthesizedRetainableProperty(I, &ID, &PD))
134       continue;
135 
136     if (synthesizedPropertyRequiresRelease(PD)) {
137       containsRetainedSynthesizedProperty = true;
138       break;
139     }
140   }
141 
142   if (!containsRetainedSynthesizedProperty)
143     return;
144 
145   // Determine if the class subclasses NSObject.
146   IdentifierInfo* NSObjectII = &Ctx.Idents.get("NSObject");
147   IdentifierInfo* SenTestCaseII = &Ctx.Idents.get("SenTestCase");
148 
149   for ( ; ID ; ID = ID->getSuperClass()) {
150     IdentifierInfo *II = ID->getIdentifier();
151 
152     if (II == NSObjectII)
153       break;
154 
155     // FIXME: For now, ignore classes that subclass SenTestCase, as these don't
156     // need to implement -dealloc.  They implement tear down in another way,
157     // which we should try and catch later.
158     //  http://llvm.org/bugs/show_bug.cgi?id=3187
159     if (II == SenTestCaseII)
160       return;
161   }
162 
163   if (!ID)
164     return;
165 
166   // Get the "dealloc" selector.
167   IdentifierInfo* II = &Ctx.Idents.get("dealloc");
168   Selector S = Ctx.Selectors.getSelector(0, &II);
169   const ObjCMethodDecl *MD = nullptr;
170 
171   // Scan the instance methods for "dealloc".
172   for (const auto *I : D->instance_methods()) {
173     if (I->getSelector() == S) {
174       MD = I;
175       break;
176     }
177   }
178 
179   if (!MD) { // No dealloc found.
180 
181     const char* name = LOpts.getGC() == LangOptions::NonGC
182                        ? "missing -dealloc"
183                        : "missing -dealloc (Hybrid MM, non-GC)";
184 
185     std::string buf;
186     llvm::raw_string_ostream os(buf);
187     os << "Objective-C class '" << *D << "' lacks a 'dealloc' instance method";
188 
189     PathDiagnosticLocation DLoc =
190         PathDiagnosticLocation::createBegin(D, BR.getSourceManager());
191 
192     BR.EmitBasicReport(D, Checker, name, categories::CoreFoundationObjectiveC,
193                        os.str(), DLoc);
194     return;
195   }
196 
197   // Get the "release" selector.
198   IdentifierInfo* RII = &Ctx.Idents.get("release");
199   Selector RS = Ctx.Selectors.getSelector(0, &RII);
200 
201   // Get the "self" identifier
202   IdentifierInfo* SelfII = &Ctx.Idents.get("self");
203 
204   // Scan for missing and extra releases of ivars used by implementations
205   // of synthesized properties
206   for (const auto *I : D->property_impls()) {
207     const ObjCIvarDecl *ID = nullptr;
208     const ObjCPropertyDecl *PD = nullptr;
209     if (!isSynthesizedRetainableProperty(I, &ID, &PD))
210       continue;
211 
212     bool requiresRelease = synthesizedPropertyRequiresRelease(PD);
213     if (scan_ivar_release(MD->getBody(), ID, PD, RS, SelfII, Ctx)
214        != requiresRelease) {
215       const char *name = nullptr;
216       std::string buf;
217       llvm::raw_string_ostream os(buf);
218 
219       if (requiresRelease) {
220         name = LOpts.getGC() == LangOptions::NonGC
221                ? "missing ivar release (leak)"
222                : "missing ivar release (Hybrid MM, non-GC)";
223 
224         os << "The '" << *ID << "' instance variable in '" << *D
225            << "' was retained by a synthesized property "
226               "but was not released in 'dealloc'";
227       } else {
228         // It is common for the ivars for read-only assign properties to
229         // always be stored retained, so don't warn for a release in
230         // dealloc for the ivar backing these properties.
231         if (PD->isReadOnly())
232           continue;
233 
234         name = LOpts.getGC() == LangOptions::NonGC
235                ? "extra ivar release (use-after-release)"
236                : "extra ivar release (Hybrid MM, non-GC)";
237 
238         os << "The '" << *ID << "' instance variable in '" << *D
239            << "' was not retained by a synthesized property "
240               "but was released in 'dealloc'";
241       }
242 
243       // If @synthesize statement is missing, fall back to @property statement.
244       const Decl *SPDecl = I->getLocation().isValid()
245                                ? static_cast<const Decl *>(I)
246                                : static_cast<const Decl *>(PD);
247       PathDiagnosticLocation SPLoc =
248           PathDiagnosticLocation::createBegin(SPDecl, BR.getSourceManager());
249 
250       BR.EmitBasicReport(MD, Checker, name,
251                          categories::CoreFoundationObjectiveC, os.str(), SPLoc);
252     }
253   }
254 }
255 
256 //===----------------------------------------------------------------------===//
257 // ObjCDeallocChecker
258 //===----------------------------------------------------------------------===//
259 
260 namespace {
261 class ObjCDeallocChecker : public Checker<
262                                       check::ASTDecl<ObjCImplementationDecl> > {
263 public:
264   void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& mgr,
265                     BugReporter &BR) const {
266     if (mgr.getLangOpts().getGC() == LangOptions::GCOnly ||
267         mgr.getLangOpts().ObjCAutoRefCount)
268       return;
269     checkObjCDealloc(this, cast<ObjCImplementationDecl>(D), mgr.getLangOpts(),
270                      BR);
271   }
272 };
273 }
274 
275 void ento::registerObjCDeallocChecker(CheckerManager &mgr) {
276   mgr.registerChecker<ObjCDeallocChecker>();
277 }
278