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
40using namespace clang;
41using namespace arcmt;
42using namespace trans;
43
44namespace {
45
46class 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
72public:
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 (ObjCInterfaceDecl::visible_extensions_iterator
145           ext = iface->visible_extensions_begin(),
146           extEnd = iface->visible_extensions_end();
147         ext != extEnd; ++ext) {
148      collectProperties(*ext, AtExtProps, &AtProps);
149    }
150
151    for (AtPropDeclsTy::iterator
152           I = AtExtProps.begin(), E = AtExtProps.end(); I != E; ++I) {
153      SourceLocation atLoc = SourceLocation::getFromRawEncoding(I->first);
154      PropsTy &props = I->second;
155      Transaction Trans(Pass.TA);
156      doActionForExtensionProp(props, atLoc);
157    }
158  }
159
160private:
161  void doPropAction(PropActionKind kind,
162                    PropsTy &props, SourceLocation atLoc,
163                    bool markAction = true) {
164    if (markAction)
165      for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I)
166        ActionOnProp[I->PropD->getIdentifier()] = kind;
167
168    switch (kind) {
169    case PropAction_None:
170      return;
171    case PropAction_RetainReplacedWithStrong: {
172      StringRef toAttr = "strong";
173      MigrateCtx.rewritePropertyAttribute("retain", toAttr, atLoc);
174      return;
175    }
176    case PropAction_AssignRemoved:
177      return removeAssignForDefaultStrong(props, atLoc);
178    case PropAction_AssignRewritten:
179      return rewriteAssign(props, atLoc);
180    case PropAction_MaybeAddWeakOrUnsafe:
181      return maybeAddWeakOrUnsafeUnretainedAttr(props, atLoc);
182    }
183  }
184
185  void doActionForExtensionProp(PropsTy &props, SourceLocation atLoc) {
186    llvm::DenseMap<IdentifierInfo *, PropActionKind>::iterator I;
187    I = ActionOnProp.find(props[0].PropD->getIdentifier());
188    if (I == ActionOnProp.end())
189      return;
190
191    doPropAction(I->second, props, atLoc, false);
192  }
193
194  void rewriteProperty(PropsTy &props, SourceLocation atLoc) {
195    ObjCPropertyDecl::PropertyAttributeKind propAttrs = getPropertyAttrs(props);
196
197    if (propAttrs & (ObjCPropertyDecl::OBJC_PR_copy |
198                     ObjCPropertyDecl::OBJC_PR_unsafe_unretained |
199                     ObjCPropertyDecl::OBJC_PR_strong |
200                     ObjCPropertyDecl::OBJC_PR_weak))
201      return;
202
203    if (propAttrs & ObjCPropertyDecl::OBJC_PR_retain) {
204      // strong is the default.
205      return doPropAction(PropAction_RetainReplacedWithStrong, props, atLoc);
206    }
207
208    bool HasIvarAssignedAPlusOneObject = hasIvarAssignedAPlusOneObject(props);
209
210    if (propAttrs & ObjCPropertyDecl::OBJC_PR_assign) {
211      if (HasIvarAssignedAPlusOneObject)
212        return doPropAction(PropAction_AssignRemoved, props, atLoc);
213      return doPropAction(PropAction_AssignRewritten, props, atLoc);
214    }
215
216    if (HasIvarAssignedAPlusOneObject ||
217        (Pass.isGCMigration() && !hasGCWeak(props, atLoc)))
218      return; // 'strong' by default.
219
220    return doPropAction(PropAction_MaybeAddWeakOrUnsafe, props, atLoc);
221  }
222
223  void removeAssignForDefaultStrong(PropsTy &props,
224                                    SourceLocation atLoc) const {
225    removeAttribute("retain", atLoc);
226    if (!removeAttribute("assign", atLoc))
227      return;
228
229    for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
230      if (I->ImplD)
231        Pass.TA.clearDiagnostic(diag::err_arc_strong_property_ownership,
232                                diag::err_arc_assign_property_ownership,
233                                diag::err_arc_inconsistent_property_ownership,
234                                I->IvarD->getLocation());
235    }
236  }
237
238  void rewriteAssign(PropsTy &props, SourceLocation atLoc) const {
239    bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props),
240                                  /*AllowOnUnknownClass=*/Pass.isGCMigration());
241    const char *toWhich =
242      (Pass.isGCMigration() && !hasGCWeak(props, atLoc)) ? "strong" :
243      (canUseWeak ? "weak" : "unsafe_unretained");
244
245    bool rewroteAttr = rewriteAttribute("assign", toWhich, atLoc);
246    if (!rewroteAttr)
247      canUseWeak = false;
248
249    for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
250      if (isUserDeclared(I->IvarD)) {
251        if (I->IvarD &&
252            I->IvarD->getType().getObjCLifetime() != Qualifiers::OCL_Weak) {
253          const char *toWhich =
254            (Pass.isGCMigration() && !hasGCWeak(props, atLoc)) ? "__strong " :
255              (canUseWeak ? "__weak " : "__unsafe_unretained ");
256          Pass.TA.insert(I->IvarD->getLocation(), toWhich);
257        }
258      }
259      if (I->ImplD)
260        Pass.TA.clearDiagnostic(diag::err_arc_strong_property_ownership,
261                                diag::err_arc_assign_property_ownership,
262                                diag::err_arc_inconsistent_property_ownership,
263                                I->IvarD->getLocation());
264    }
265  }
266
267  void maybeAddWeakOrUnsafeUnretainedAttr(PropsTy &props,
268                                          SourceLocation atLoc) const {
269    bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props),
270                                  /*AllowOnUnknownClass=*/Pass.isGCMigration());
271
272    bool addedAttr = addAttribute(canUseWeak ? "weak" : "unsafe_unretained",
273                                  atLoc);
274    if (!addedAttr)
275      canUseWeak = false;
276
277    for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
278      if (isUserDeclared(I->IvarD)) {
279        if (I->IvarD &&
280            I->IvarD->getType().getObjCLifetime() != Qualifiers::OCL_Weak)
281          Pass.TA.insert(I->IvarD->getLocation(),
282                         canUseWeak ? "__weak " : "__unsafe_unretained ");
283      }
284      if (I->ImplD) {
285        Pass.TA.clearDiagnostic(diag::err_arc_strong_property_ownership,
286                                diag::err_arc_assign_property_ownership,
287                                diag::err_arc_inconsistent_property_ownership,
288                                I->IvarD->getLocation());
289        Pass.TA.clearDiagnostic(
290                           diag::err_arc_objc_property_default_assign_on_object,
291                           I->ImplD->getLocation());
292      }
293    }
294  }
295
296  bool removeAttribute(StringRef fromAttr, SourceLocation atLoc) const {
297    return MigrateCtx.removePropertyAttribute(fromAttr, atLoc);
298  }
299
300  bool rewriteAttribute(StringRef fromAttr, StringRef toAttr,
301                        SourceLocation atLoc) const {
302    return MigrateCtx.rewritePropertyAttribute(fromAttr, toAttr, atLoc);
303  }
304
305  bool addAttribute(StringRef attr, SourceLocation atLoc) const {
306    return MigrateCtx.addPropertyAttribute(attr, atLoc);
307  }
308
309  class PlusOneAssign : public RecursiveASTVisitor<PlusOneAssign> {
310    ObjCIvarDecl *Ivar;
311  public:
312    PlusOneAssign(ObjCIvarDecl *D) : Ivar(D) {}
313
314    bool VisitBinAssign(BinaryOperator *E) {
315      Expr *lhs = E->getLHS()->IgnoreParenImpCasts();
316      if (ObjCIvarRefExpr *RE = dyn_cast<ObjCIvarRefExpr>(lhs)) {
317        if (RE->getDecl() != Ivar)
318          return true;
319
320        if (isPlusOneAssign(E))
321          return false;
322      }
323
324      return true;
325    }
326  };
327
328  bool hasIvarAssignedAPlusOneObject(PropsTy &props) const {
329    for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
330      PlusOneAssign oneAssign(I->IvarD);
331      bool notFound = oneAssign.TraverseDecl(CurImplD);
332      if (!notFound)
333        return true;
334    }
335
336    return false;
337  }
338
339  bool hasIvarWithExplicitARCOwnership(PropsTy &props) const {
340    if (Pass.isGCMigration())
341      return false;
342
343    for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
344      if (isUserDeclared(I->IvarD)) {
345        if (isa<AttributedType>(I->IvarD->getType()))
346          return true;
347        if (I->IvarD->getType().getLocalQualifiers().getObjCLifetime()
348              != Qualifiers::OCL_Strong)
349          return true;
350      }
351    }
352
353    return false;
354  }
355
356  bool hasAllIvarsBacked(PropsTy &props) const {
357    for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I)
358      if (!isUserDeclared(I->IvarD))
359        return false;
360
361    return true;
362  }
363
364  // \brief Returns true if all declarations in the @property have GC __weak.
365  bool hasGCWeak(PropsTy &props, SourceLocation atLoc) const {
366    if (!Pass.isGCMigration())
367      return false;
368    if (props.empty())
369      return false;
370    return MigrateCtx.AtPropsWeak.count(atLoc.getRawEncoding());
371  }
372
373  bool isUserDeclared(ObjCIvarDecl *ivarD) const {
374    return ivarD && !ivarD->getSynthesize();
375  }
376
377  QualType getPropertyType(PropsTy &props) const {
378    assert(!props.empty());
379    QualType ty = props[0].PropD->getType().getUnqualifiedType();
380
381#ifndef NDEBUG
382    for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I)
383      assert(ty == I->PropD->getType().getUnqualifiedType());
384#endif
385
386    return ty;
387  }
388
389  ObjCPropertyDecl::PropertyAttributeKind
390  getPropertyAttrs(PropsTy &props) const {
391    assert(!props.empty());
392    ObjCPropertyDecl::PropertyAttributeKind
393      attrs = props[0].PropD->getPropertyAttributesAsWritten();
394
395#ifndef NDEBUG
396    for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I)
397      assert(attrs == I->PropD->getPropertyAttributesAsWritten());
398#endif
399
400    return attrs;
401  }
402};
403
404} // anonymous namespace
405
406void PropertyRewriteTraverser::traverseObjCImplementation(
407                                           ObjCImplementationContext &ImplCtx) {
408  PropertiesRewriter(ImplCtx.getMigrationContext())
409                                  .doTransform(ImplCtx.getImplementationDecl());
410}
411