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