1//===--- TransProperties.cpp - Transformations to ARC mode ----------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// rewriteProperties:
10//
11// - Adds strong/weak/unsafe_unretained ownership specifier to properties that
12//   are missing one.
13// - Migrates properties from (retain) to (strong) and (assign) to
14//   (unsafe_unretained/weak).
15// - If a property is synthesized, adds the ownership specifier in the ivar
16//   backing the property.
17//
18//  @interface Foo : NSObject {
19//      NSObject *x;
20//  }
21//  @property (assign) id x;
22//  @end
23// ---->
24//  @interface Foo : NSObject {
25//      NSObject *__weak x;
26//  }
27//  @property (weak) id x;
28//  @end
29//
30//===----------------------------------------------------------------------===//
31
32#include "Transforms.h"
33#include "Internals.h"
34#include "clang/Basic/SourceManager.h"
35#include "clang/Lex/Lexer.h"
36#include "clang/Sema/SemaDiagnostic.h"
37#include <map>
38
39using namespace clang;
40using namespace arcmt;
41using namespace trans;
42
43namespace {
44
45class PropertiesRewriter {
46  MigrationContext &MigrateCtx;
47  MigrationPass &Pass;
48  ObjCImplementationDecl *CurImplD = nullptr;
49
50  enum PropActionKind {
51    PropAction_None,
52    PropAction_RetainReplacedWithStrong,
53    PropAction_AssignRemoved,
54    PropAction_AssignRewritten,
55    PropAction_MaybeAddWeakOrUnsafe
56  };
57
58  struct PropData {
59    ObjCPropertyDecl *PropD;
60    ObjCIvarDecl *IvarD;
61    ObjCPropertyImplDecl *ImplD;
62
63    PropData(ObjCPropertyDecl *propD)
64      : PropD(propD), IvarD(nullptr), ImplD(nullptr) {}
65  };
66
67  typedef SmallVector<PropData, 2> PropsTy;
68  typedef std::map<SourceLocation, 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 = nullptr) {
78    for (auto *Prop : D->instance_properties()) {
79      SourceLocation Loc = Prop->getAtLoc();
80      if (Loc.isInvalid())
81        continue;
82      if (PrevAtProps)
83        if (PrevAtProps->find(Loc) != PrevAtProps->end())
84          continue;
85      PropsTy &props = AtProps[Loc];
86      props.push_back(Prop);
87    }
88  }
89
90  void doTransform(ObjCImplementationDecl *D) {
91    CurImplD = D;
92    ObjCInterfaceDecl *iface = D->getClassInterface();
93    if (!iface)
94      return;
95
96    collectProperties(iface, AtProps);
97
98    // Look through extensions.
99    for (auto *Ext : iface->visible_extensions())
100      collectProperties(Ext, AtProps);
101
102    typedef DeclContext::specific_decl_iterator<ObjCPropertyImplDecl>
103        prop_impl_iterator;
104    for (prop_impl_iterator
105           I = prop_impl_iterator(D->decls_begin()),
106           E = prop_impl_iterator(D->decls_end()); I != E; ++I) {
107      ObjCPropertyImplDecl *implD = *I;
108      if (implD->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize)
109        continue;
110      ObjCPropertyDecl *propD = implD->getPropertyDecl();
111      if (!propD || propD->isInvalidDecl())
112        continue;
113      ObjCIvarDecl *ivarD = implD->getPropertyIvarDecl();
114      if (!ivarD || ivarD->isInvalidDecl())
115        continue;
116      AtPropDeclsTy::iterator findAtLoc = AtProps.find(propD->getAtLoc());
117      if (findAtLoc == AtProps.end())
118        continue;
119
120      PropsTy &props = findAtLoc->second;
121      for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
122        if (I->PropD == propD) {
123          I->IvarD = ivarD;
124          I->ImplD = implD;
125          break;
126        }
127      }
128    }
129
130    for (AtPropDeclsTy::iterator
131           I = AtProps.begin(), E = AtProps.end(); I != E; ++I) {
132      SourceLocation atLoc = I->first;
133      PropsTy &props = I->second;
134      if (!getPropertyType(props)->isObjCRetainableType())
135        continue;
136      if (hasIvarWithExplicitARCOwnership(props))
137        continue;
138
139      Transaction Trans(Pass.TA);
140      rewriteProperty(props, atLoc);
141    }
142  }
143
144private:
145  void doPropAction(PropActionKind kind,
146                    PropsTy &props, SourceLocation atLoc,
147                    bool markAction = true) {
148    if (markAction)
149      for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I)
150        ActionOnProp[I->PropD->getIdentifier()] = kind;
151
152    switch (kind) {
153    case PropAction_None:
154      return;
155    case PropAction_RetainReplacedWithStrong: {
156      StringRef toAttr = "strong";
157      MigrateCtx.rewritePropertyAttribute("retain", toAttr, atLoc);
158      return;
159    }
160    case PropAction_AssignRemoved:
161      return removeAssignForDefaultStrong(props, atLoc);
162    case PropAction_AssignRewritten:
163      return rewriteAssign(props, atLoc);
164    case PropAction_MaybeAddWeakOrUnsafe:
165      return maybeAddWeakOrUnsafeUnretainedAttr(props, atLoc);
166    }
167  }
168
169  void rewriteProperty(PropsTy &props, SourceLocation atLoc) {
170    ObjCPropertyAttribute::Kind propAttrs = getPropertyAttrs(props);
171
172    if (propAttrs &
173        (ObjCPropertyAttribute::kind_copy |
174         ObjCPropertyAttribute::kind_unsafe_unretained |
175         ObjCPropertyAttribute::kind_strong | ObjCPropertyAttribute::kind_weak))
176      return;
177
178    if (propAttrs & ObjCPropertyAttribute::kind_retain) {
179      // strong is the default.
180      return doPropAction(PropAction_RetainReplacedWithStrong, props, atLoc);
181    }
182
183    bool HasIvarAssignedAPlusOneObject = hasIvarAssignedAPlusOneObject(props);
184
185    if (propAttrs & ObjCPropertyAttribute::kind_assign) {
186      if (HasIvarAssignedAPlusOneObject)
187        return doPropAction(PropAction_AssignRemoved, props, atLoc);
188      return doPropAction(PropAction_AssignRewritten, props, atLoc);
189    }
190
191    if (HasIvarAssignedAPlusOneObject ||
192        (Pass.isGCMigration() && !hasGCWeak(props, atLoc)))
193      return; // 'strong' by default.
194
195    return doPropAction(PropAction_MaybeAddWeakOrUnsafe, props, atLoc);
196  }
197
198  void removeAssignForDefaultStrong(PropsTy &props,
199                                    SourceLocation atLoc) const {
200    removeAttribute("retain", atLoc);
201    if (!removeAttribute("assign", atLoc))
202      return;
203
204    for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
205      if (I->ImplD)
206        Pass.TA.clearDiagnostic(diag::err_arc_strong_property_ownership,
207                                diag::err_arc_assign_property_ownership,
208                                diag::err_arc_inconsistent_property_ownership,
209                                I->IvarD->getLocation());
210    }
211  }
212
213  void rewriteAssign(PropsTy &props, SourceLocation atLoc) const {
214    bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props),
215                                  /*AllowOnUnknownClass=*/Pass.isGCMigration());
216    const char *toWhich =
217      (Pass.isGCMigration() && !hasGCWeak(props, atLoc)) ? "strong" :
218      (canUseWeak ? "weak" : "unsafe_unretained");
219
220    bool rewroteAttr = rewriteAttribute("assign", toWhich, atLoc);
221    if (!rewroteAttr)
222      canUseWeak = false;
223
224    for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
225      if (isUserDeclared(I->IvarD)) {
226        if (I->IvarD &&
227            I->IvarD->getType().getObjCLifetime() != Qualifiers::OCL_Weak) {
228          const char *toWhich =
229            (Pass.isGCMigration() && !hasGCWeak(props, atLoc)) ? "__strong " :
230              (canUseWeak ? "__weak " : "__unsafe_unretained ");
231          Pass.TA.insert(I->IvarD->getLocation(), toWhich);
232        }
233      }
234      if (I->ImplD)
235        Pass.TA.clearDiagnostic(diag::err_arc_strong_property_ownership,
236                                diag::err_arc_assign_property_ownership,
237                                diag::err_arc_inconsistent_property_ownership,
238                                I->IvarD->getLocation());
239    }
240  }
241
242  void maybeAddWeakOrUnsafeUnretainedAttr(PropsTy &props,
243                                          SourceLocation atLoc) const {
244    bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props),
245                                  /*AllowOnUnknownClass=*/Pass.isGCMigration());
246
247    bool addedAttr = addAttribute(canUseWeak ? "weak" : "unsafe_unretained",
248                                  atLoc);
249    if (!addedAttr)
250      canUseWeak = false;
251
252    for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
253      if (isUserDeclared(I->IvarD)) {
254        if (I->IvarD &&
255            I->IvarD->getType().getObjCLifetime() != Qualifiers::OCL_Weak)
256          Pass.TA.insert(I->IvarD->getLocation(),
257                         canUseWeak ? "__weak " : "__unsafe_unretained ");
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        Pass.TA.clearDiagnostic(
265                           diag::err_arc_objc_property_default_assign_on_object,
266                           I->ImplD->getLocation());
267      }
268    }
269  }
270
271  bool removeAttribute(StringRef fromAttr, SourceLocation atLoc) const {
272    return MigrateCtx.removePropertyAttribute(fromAttr, atLoc);
273  }
274
275  bool rewriteAttribute(StringRef fromAttr, StringRef toAttr,
276                        SourceLocation atLoc) const {
277    return MigrateCtx.rewritePropertyAttribute(fromAttr, toAttr, atLoc);
278  }
279
280  bool addAttribute(StringRef attr, SourceLocation atLoc) const {
281    return MigrateCtx.addPropertyAttribute(attr, atLoc);
282  }
283
284  class PlusOneAssign : public RecursiveASTVisitor<PlusOneAssign> {
285    ObjCIvarDecl *Ivar;
286  public:
287    PlusOneAssign(ObjCIvarDecl *D) : Ivar(D) {}
288
289    bool VisitBinaryOperator(BinaryOperator *E) {
290      if (E->getOpcode() != BO_Assign)
291        return true;
292
293      Expr *lhs = E->getLHS()->IgnoreParenImpCasts();
294      if (ObjCIvarRefExpr *RE = dyn_cast<ObjCIvarRefExpr>(lhs)) {
295        if (RE->getDecl() != Ivar)
296          return true;
297
298        if (isPlusOneAssign(E))
299          return false;
300      }
301
302      return true;
303    }
304  };
305
306  bool hasIvarAssignedAPlusOneObject(PropsTy &props) const {
307    for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
308      PlusOneAssign oneAssign(I->IvarD);
309      bool notFound = oneAssign.TraverseDecl(CurImplD);
310      if (!notFound)
311        return true;
312    }
313
314    return false;
315  }
316
317  bool hasIvarWithExplicitARCOwnership(PropsTy &props) const {
318    if (Pass.isGCMigration())
319      return false;
320
321    for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
322      if (isUserDeclared(I->IvarD)) {
323        if (isa<AttributedType>(I->IvarD->getType()))
324          return true;
325        if (I->IvarD->getType().getLocalQualifiers().getObjCLifetime()
326              != Qualifiers::OCL_Strong)
327          return true;
328      }
329    }
330
331    return false;
332  }
333
334  // Returns true if all declarations in the @property have GC __weak.
335  bool hasGCWeak(PropsTy &props, SourceLocation atLoc) const {
336    if (!Pass.isGCMigration())
337      return false;
338    if (props.empty())
339      return false;
340    return MigrateCtx.AtPropsWeak.count(atLoc);
341  }
342
343  bool isUserDeclared(ObjCIvarDecl *ivarD) const {
344    return ivarD && !ivarD->getSynthesize();
345  }
346
347  QualType getPropertyType(PropsTy &props) const {
348    assert(!props.empty());
349    QualType ty = props[0].PropD->getType().getUnqualifiedType();
350
351#ifndef NDEBUG
352    for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I)
353      assert(ty == I->PropD->getType().getUnqualifiedType());
354#endif
355
356    return ty;
357  }
358
359  ObjCPropertyAttribute::Kind getPropertyAttrs(PropsTy &props) const {
360    assert(!props.empty());
361    ObjCPropertyAttribute::Kind attrs =
362        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