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