1 //===--- TransProperties.cpp - Tranformations to ARC mode -----------------===//
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 // rewriteProperties:
11 //
12 // - Adds strong/weak/unsafe_unretained ownership specifier to properties that
13 //   are missing one.
14 // - Migrates properties from (retain) to (strong) and (assign) to
15 //   (unsafe_unretained/weak).
16 // - If a property is synthesized, adds the ownership specifier in the ivar
17 //   backing the property.
18 //
19 //  @interface Foo : NSObject {
20 //      NSObject *x;
21 //  }
22 //  @property (assign) id x;
23 //  @end
24 // ---->
25 //  @interface Foo : NSObject {
26 //      NSObject *__weak x;
27 //  }
28 //  @property (weak) id x;
29 //  @end
30 //
31 //===----------------------------------------------------------------------===//
32 
33 #include "Transforms.h"
34 #include "Internals.h"
35 #include "clang/Sema/SemaDiagnostic.h"
36 #include "clang/Basic/SourceManager.h"
37 #include "clang/Lex/Lexer.h"
38 #include <map>
39 
40 using namespace clang;
41 using namespace arcmt;
42 using namespace trans;
43 
44 namespace {
45 
46 class PropertiesRewriter {
47   MigrationContext &MigrateCtx;
48   MigrationPass &Pass;
49   ObjCImplementationDecl *CurImplD;
50 
51   enum PropActionKind {
52     PropAction_None,
53     PropAction_RetainRemoved,
54     PropAction_AssignRemoved,
55     PropAction_AssignRewritten,
56     PropAction_MaybeAddWeakOrUnsafe
57   };
58 
59   struct PropData {
60     ObjCPropertyDecl *PropD;
61     ObjCIvarDecl *IvarD;
62     ObjCPropertyImplDecl *ImplD;
63 
64     PropData(ObjCPropertyDecl *propD) : PropD(propD), IvarD(0), ImplD(0) { }
65   };
66 
67   typedef SmallVector<PropData, 2> PropsTy;
68   typedef std::map<unsigned, PropsTy> AtPropDeclsTy;
69   AtPropDeclsTy AtProps;
70   llvm::DenseMap<IdentifierInfo *, PropActionKind> ActionOnProp;
71 
72 public:
73   explicit PropertiesRewriter(MigrationContext &MigrateCtx)
74     : MigrateCtx(MigrateCtx), Pass(MigrateCtx.Pass) { }
75 
76   static void collectProperties(ObjCContainerDecl *D, AtPropDeclsTy &AtProps) {
77     for (ObjCInterfaceDecl::prop_iterator
78            propI = D->prop_begin(),
79            propE = D->prop_end(); propI != propE; ++propI) {
80       if (propI->getAtLoc().isInvalid())
81         continue;
82       PropsTy &props = AtProps[propI->getAtLoc().getRawEncoding()];
83       props.push_back(*propI);
84     }
85   }
86 
87   void doTransform(ObjCImplementationDecl *D) {
88     CurImplD = D;
89     ObjCInterfaceDecl *iface = D->getClassInterface();
90     if (!iface)
91       return;
92 
93     collectProperties(iface, AtProps);
94 
95     typedef DeclContext::specific_decl_iterator<ObjCPropertyImplDecl>
96         prop_impl_iterator;
97     for (prop_impl_iterator
98            I = prop_impl_iterator(D->decls_begin()),
99            E = prop_impl_iterator(D->decls_end()); I != E; ++I) {
100       ObjCPropertyImplDecl *implD = *I;
101       if (implD->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize)
102         continue;
103       ObjCPropertyDecl *propD = implD->getPropertyDecl();
104       if (!propD || propD->isInvalidDecl())
105         continue;
106       ObjCIvarDecl *ivarD = implD->getPropertyIvarDecl();
107       if (!ivarD || ivarD->isInvalidDecl())
108         continue;
109       unsigned rawAtLoc = propD->getAtLoc().getRawEncoding();
110       AtPropDeclsTy::iterator findAtLoc = AtProps.find(rawAtLoc);
111       if (findAtLoc == AtProps.end())
112         continue;
113 
114       PropsTy &props = findAtLoc->second;
115       for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
116         if (I->PropD == propD) {
117           I->IvarD = ivarD;
118           I->ImplD = implD;
119           break;
120         }
121       }
122     }
123 
124     for (AtPropDeclsTy::iterator
125            I = AtProps.begin(), E = AtProps.end(); I != E; ++I) {
126       SourceLocation atLoc = SourceLocation::getFromRawEncoding(I->first);
127       PropsTy &props = I->second;
128       if (!getPropertyType(props)->isObjCRetainableType())
129         continue;
130       if (hasIvarWithExplicitARCOwnership(props))
131         continue;
132 
133       Transaction Trans(Pass.TA);
134       rewriteProperty(props, atLoc);
135     }
136 
137     AtPropDeclsTy AtExtProps;
138     // Look through extensions.
139     for (ObjCCategoryDecl *Cat = iface->getCategoryList();
140            Cat; Cat = Cat->getNextClassCategory())
141       if (Cat->IsClassExtension())
142         collectProperties(Cat, AtExtProps);
143 
144     for (AtPropDeclsTy::iterator
145            I = AtExtProps.begin(), E = AtExtProps.end(); I != E; ++I) {
146       SourceLocation atLoc = SourceLocation::getFromRawEncoding(I->first);
147       PropsTy &props = I->second;
148       Transaction Trans(Pass.TA);
149       doActionForExtensionProp(props, atLoc);
150     }
151   }
152 
153 private:
154   void doPropAction(PropActionKind kind,
155                     PropsTy &props, SourceLocation atLoc,
156                     bool markAction = true) {
157     if (markAction)
158       for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I)
159         ActionOnProp[I->PropD->getIdentifier()] = kind;
160 
161     switch (kind) {
162     case PropAction_None:
163       return;
164     case PropAction_RetainRemoved:
165       removeAttribute("retain", atLoc);
166       return;
167     case PropAction_AssignRemoved:
168       return removeAssignForDefaultStrong(props, atLoc);
169     case PropAction_AssignRewritten:
170       return rewriteAssign(props, atLoc);
171     case PropAction_MaybeAddWeakOrUnsafe:
172       return maybeAddWeakOrUnsafeUnretainedAttr(props, atLoc);
173     }
174   }
175 
176   void doActionForExtensionProp(PropsTy &props, SourceLocation atLoc) {
177     llvm::DenseMap<IdentifierInfo *, PropActionKind>::iterator I;
178     I = ActionOnProp.find(props[0].PropD->getIdentifier());
179     if (I == ActionOnProp.end())
180       return;
181 
182     doPropAction(I->second, props, atLoc, false);
183   }
184 
185   void rewriteProperty(PropsTy &props, SourceLocation atLoc) {
186     ObjCPropertyDecl::PropertyAttributeKind propAttrs = getPropertyAttrs(props);
187 
188     if (propAttrs & (ObjCPropertyDecl::OBJC_PR_copy |
189                      ObjCPropertyDecl::OBJC_PR_unsafe_unretained |
190                      ObjCPropertyDecl::OBJC_PR_strong |
191                      ObjCPropertyDecl::OBJC_PR_weak))
192       return;
193 
194     if (propAttrs & ObjCPropertyDecl::OBJC_PR_retain) {
195       // strong is the default.
196       return doPropAction(PropAction_RetainRemoved, props, atLoc);
197     }
198 
199     bool HasIvarAssignedAPlusOneObject = hasIvarAssignedAPlusOneObject(props);
200 
201     if (propAttrs & ObjCPropertyDecl::OBJC_PR_assign) {
202       if (HasIvarAssignedAPlusOneObject ||
203           (Pass.isGCMigration() && !hasGCWeak(props, atLoc))) {
204         return doPropAction(PropAction_AssignRemoved, props, atLoc);
205       }
206       return doPropAction(PropAction_AssignRewritten, props, atLoc);
207     }
208 
209     if (HasIvarAssignedAPlusOneObject ||
210         (Pass.isGCMigration() && !hasGCWeak(props, atLoc)))
211       return; // 'strong' by default.
212 
213     return doPropAction(PropAction_MaybeAddWeakOrUnsafe, props, atLoc);
214   }
215 
216   void removeAssignForDefaultStrong(PropsTy &props,
217                                     SourceLocation atLoc) const {
218     removeAttribute("retain", atLoc);
219     if (!removeAttribute("assign", atLoc))
220       return;
221 
222     for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
223       if (I->ImplD)
224         Pass.TA.clearDiagnostic(diag::err_arc_assign_property_ownership,
225                                 I->ImplD->getLocation());
226     }
227   }
228 
229   void rewriteAssign(PropsTy &props, SourceLocation atLoc) const {
230     bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props),
231                                   /*AllowOnUnknownClass=*/Pass.isGCMigration());
232 
233     bool rewroteAttr = rewriteAttribute("assign",
234                                      canUseWeak ? "weak" : "unsafe_unretained",
235                                          atLoc);
236     if (!rewroteAttr)
237       canUseWeak = false;
238 
239     for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
240       if (isUserDeclared(I->IvarD)) {
241         if (I->IvarD &&
242             I->IvarD->getType().getObjCLifetime() != Qualifiers::OCL_Weak)
243           Pass.TA.insert(I->IvarD->getLocation(),
244                          canUseWeak ? "__weak " : "__unsafe_unretained ");
245       }
246       if (I->ImplD)
247         Pass.TA.clearDiagnostic(diag::err_arc_assign_property_ownership,
248                                 I->ImplD->getLocation());
249     }
250   }
251 
252   void maybeAddWeakOrUnsafeUnretainedAttr(PropsTy &props,
253                                           SourceLocation atLoc) const {
254     bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props),
255                                   /*AllowOnUnknownClass=*/Pass.isGCMigration());
256 
257     bool addedAttr = addAttribute(canUseWeak ? "weak" : "unsafe_unretained",
258                                   atLoc);
259     if (!addedAttr)
260       canUseWeak = false;
261 
262     for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
263       if (isUserDeclared(I->IvarD)) {
264         if (I->IvarD &&
265             I->IvarD->getType().getObjCLifetime() != Qualifiers::OCL_Weak)
266           Pass.TA.insert(I->IvarD->getLocation(),
267                          canUseWeak ? "__weak " : "__unsafe_unretained ");
268       }
269       if (I->ImplD) {
270         Pass.TA.clearDiagnostic(diag::err_arc_assign_property_ownership,
271                                 I->ImplD->getLocation());
272         Pass.TA.clearDiagnostic(
273                            diag::err_arc_objc_property_default_assign_on_object,
274                            I->ImplD->getLocation());
275       }
276     }
277   }
278 
279   bool removeAttribute(StringRef fromAttr, SourceLocation atLoc) const {
280     return MigrateCtx.removePropertyAttribute(fromAttr, atLoc);
281   }
282 
283   bool rewriteAttribute(StringRef fromAttr, StringRef toAttr,
284                         SourceLocation atLoc) const {
285     return MigrateCtx.rewritePropertyAttribute(fromAttr, toAttr, atLoc);
286   }
287 
288   bool addAttribute(StringRef attr, SourceLocation atLoc) const {
289     return MigrateCtx.addPropertyAttribute(attr, atLoc);
290   }
291 
292   class PlusOneAssign : public RecursiveASTVisitor<PlusOneAssign> {
293     ObjCIvarDecl *Ivar;
294   public:
295     PlusOneAssign(ObjCIvarDecl *D) : Ivar(D) {}
296 
297     bool VisitBinAssign(BinaryOperator *E) {
298       Expr *lhs = E->getLHS()->IgnoreParenImpCasts();
299       if (ObjCIvarRefExpr *RE = dyn_cast<ObjCIvarRefExpr>(lhs)) {
300         if (RE->getDecl() != Ivar)
301           return true;
302 
303       if (ObjCMessageExpr *
304             ME = dyn_cast<ObjCMessageExpr>(E->getRHS()->IgnoreParenCasts()))
305         if (ME->getMethodFamily() == OMF_retain)
306           return false;
307 
308       ImplicitCastExpr *implCE = dyn_cast<ImplicitCastExpr>(E->getRHS());
309       while (implCE && implCE->getCastKind() ==  CK_BitCast)
310         implCE = dyn_cast<ImplicitCastExpr>(implCE->getSubExpr());
311 
312       if (implCE && implCE->getCastKind() == CK_ARCConsumeObject)
313         return false;
314       }
315 
316       return true;
317     }
318   };
319 
320   bool hasIvarAssignedAPlusOneObject(PropsTy &props) const {
321     for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
322       PlusOneAssign oneAssign(I->IvarD);
323       bool notFound = oneAssign.TraverseDecl(CurImplD);
324       if (!notFound)
325         return true;
326     }
327 
328     return false;
329   }
330 
331   bool hasIvarWithExplicitARCOwnership(PropsTy &props) const {
332     if (Pass.isGCMigration())
333       return false;
334 
335     for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
336       if (isUserDeclared(I->IvarD)) {
337         if (isa<AttributedType>(I->IvarD->getType()))
338           return true;
339         if (I->IvarD->getType().getLocalQualifiers().getObjCLifetime()
340               != Qualifiers::OCL_Strong)
341           return true;
342       }
343     }
344 
345     return false;
346   }
347 
348   bool hasAllIvarsBacked(PropsTy &props) const {
349     for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I)
350       if (!isUserDeclared(I->IvarD))
351         return false;
352 
353     return true;
354   }
355 
356   // \brief Returns true if all declarations in the @property have GC __weak.
357   bool hasGCWeak(PropsTy &props, SourceLocation atLoc) const {
358     if (!Pass.isGCMigration())
359       return false;
360     if (props.empty())
361       return false;
362     return MigrateCtx.AtPropsWeak.count(atLoc.getRawEncoding());
363   }
364 
365   bool isUserDeclared(ObjCIvarDecl *ivarD) const {
366     return ivarD && !ivarD->getSynthesize();
367   }
368 
369   QualType getPropertyType(PropsTy &props) const {
370     assert(!props.empty());
371     QualType ty = props[0].PropD->getType().getUnqualifiedType();
372 
373 #ifndef NDEBUG
374     for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I)
375       assert(ty == I->PropD->getType().getUnqualifiedType());
376 #endif
377 
378     return ty;
379   }
380 
381   ObjCPropertyDecl::PropertyAttributeKind
382   getPropertyAttrs(PropsTy &props) const {
383     assert(!props.empty());
384     ObjCPropertyDecl::PropertyAttributeKind
385       attrs = props[0].PropD->getPropertyAttributesAsWritten();
386 
387 #ifndef NDEBUG
388     for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I)
389       assert(attrs == I->PropD->getPropertyAttributesAsWritten());
390 #endif
391 
392     return attrs;
393   }
394 };
395 
396 } // anonymous namespace
397 
398 void PropertyRewriteTraverser::traverseObjCImplementation(
399                                            ObjCImplementationContext &ImplCtx) {
400   PropertiesRewriter(ImplCtx.getMigrationContext())
401                                   .doTransform(ImplCtx.getImplementationDecl());
402 }
403