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