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