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 
PropData__anon708686a90111::PropertiesRewriter::PropData64     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 
73 public:
PropertiesRewriter(MigrationContext & MigrateCtx)74   explicit PropertiesRewriter(MigrationContext &MigrateCtx)
75     : MigrateCtx(MigrateCtx), Pass(MigrateCtx.Pass) { }
76 
collectProperties(ObjCContainerDecl * D,AtPropDeclsTy & AtProps,AtPropDeclsTy * PrevAtProps=nullptr)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 
doTransform(ObjCImplementationDecl * D)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 
146 private:
doPropAction(PropActionKind kind,PropsTy & props,SourceLocation atLoc,bool markAction=true)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 
rewriteProperty(PropsTy & props,SourceLocation atLoc)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 
removeAssignForDefaultStrong(PropsTy & props,SourceLocation atLoc) const200   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 
rewriteAssign(PropsTy & props,SourceLocation atLoc) const215   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 
maybeAddWeakOrUnsafeUnretainedAttr(PropsTy & props,SourceLocation atLoc) const244   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 
removeAttribute(StringRef fromAttr,SourceLocation atLoc) const273   bool removeAttribute(StringRef fromAttr, SourceLocation atLoc) const {
274     return MigrateCtx.removePropertyAttribute(fromAttr, atLoc);
275   }
276 
rewriteAttribute(StringRef fromAttr,StringRef toAttr,SourceLocation atLoc) const277   bool rewriteAttribute(StringRef fromAttr, StringRef toAttr,
278                         SourceLocation atLoc) const {
279     return MigrateCtx.rewritePropertyAttribute(fromAttr, toAttr, atLoc);
280   }
281 
addAttribute(StringRef attr,SourceLocation atLoc) const282   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:
PlusOneAssign(ObjCIvarDecl * D)289     PlusOneAssign(ObjCIvarDecl *D) : Ivar(D) {}
290 
VisitBinAssign(BinaryOperator * E)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 
hasIvarAssignedAPlusOneObject(PropsTy & props) const305   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 
hasIvarWithExplicitARCOwnership(PropsTy & props) const316   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   // Returns true if all declarations in the @property have GC __weak.
hasGCWeak(PropsTy & props,SourceLocation atLoc) const334   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 
isUserDeclared(ObjCIvarDecl * ivarD) const342   bool isUserDeclared(ObjCIvarDecl *ivarD) const {
343     return ivarD && !ivarD->getSynthesize();
344   }
345 
getPropertyType(PropsTy & props) const346   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
getPropertyAttrs(PropsTy & props) const359   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 
traverseObjCImplementation(ObjCImplementationContext & ImplCtx)375 void PropertyRewriteTraverser::traverseObjCImplementation(
376                                            ObjCImplementationContext &ImplCtx) {
377   PropertiesRewriter(ImplCtx.getMigrationContext())
378                                   .doTransform(ImplCtx.getImplementationDecl());
379 }
380