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__anonc13f06cb0111::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     ObjCPropertyAttribute::Kind propAttrs = getPropertyAttrs(props);
172 
173     if (propAttrs &
174         (ObjCPropertyAttribute::kind_copy |
175          ObjCPropertyAttribute::kind_unsafe_unretained |
176          ObjCPropertyAttribute::kind_strong | ObjCPropertyAttribute::kind_weak))
177       return;
178 
179     if (propAttrs & ObjCPropertyAttribute::kind_retain) {
180       // strong is the default.
181       return doPropAction(PropAction_RetainReplacedWithStrong, props, atLoc);
182     }
183 
184     bool HasIvarAssignedAPlusOneObject = hasIvarAssignedAPlusOneObject(props);
185 
186     if (propAttrs & ObjCPropertyAttribute::kind_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 
VisitBinaryOperator(BinaryOperator * E)290     bool VisitBinaryOperator(BinaryOperator *E) {
291       if (E->getOpcode() != BO_Assign)
292         return true;
293 
294       Expr *lhs = E->getLHS()->IgnoreParenImpCasts();
295       if (ObjCIvarRefExpr *RE = dyn_cast<ObjCIvarRefExpr>(lhs)) {
296         if (RE->getDecl() != Ivar)
297           return true;
298 
299         if (isPlusOneAssign(E))
300           return false;
301       }
302 
303       return true;
304     }
305   };
306 
hasIvarAssignedAPlusOneObject(PropsTy & props) const307   bool hasIvarAssignedAPlusOneObject(PropsTy &props) const {
308     for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
309       PlusOneAssign oneAssign(I->IvarD);
310       bool notFound = oneAssign.TraverseDecl(CurImplD);
311       if (!notFound)
312         return true;
313     }
314 
315     return false;
316   }
317 
hasIvarWithExplicitARCOwnership(PropsTy & props) const318   bool hasIvarWithExplicitARCOwnership(PropsTy &props) const {
319     if (Pass.isGCMigration())
320       return false;
321 
322     for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
323       if (isUserDeclared(I->IvarD)) {
324         if (isa<AttributedType>(I->IvarD->getType()))
325           return true;
326         if (I->IvarD->getType().getLocalQualifiers().getObjCLifetime()
327               != Qualifiers::OCL_Strong)
328           return true;
329       }
330     }
331 
332     return false;
333   }
334 
335   // Returns true if all declarations in the @property have GC __weak.
hasGCWeak(PropsTy & props,SourceLocation atLoc) const336   bool hasGCWeak(PropsTy &props, SourceLocation atLoc) const {
337     if (!Pass.isGCMigration())
338       return false;
339     if (props.empty())
340       return false;
341     return MigrateCtx.AtPropsWeak.count(atLoc.getRawEncoding());
342   }
343 
isUserDeclared(ObjCIvarDecl * ivarD) const344   bool isUserDeclared(ObjCIvarDecl *ivarD) const {
345     return ivarD && !ivarD->getSynthesize();
346   }
347 
getPropertyType(PropsTy & props) const348   QualType getPropertyType(PropsTy &props) const {
349     assert(!props.empty());
350     QualType ty = props[0].PropD->getType().getUnqualifiedType();
351 
352 #ifndef NDEBUG
353     for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I)
354       assert(ty == I->PropD->getType().getUnqualifiedType());
355 #endif
356 
357     return ty;
358   }
359 
getPropertyAttrs(PropsTy & props) const360   ObjCPropertyAttribute::Kind getPropertyAttrs(PropsTy &props) const {
361     assert(!props.empty());
362     ObjCPropertyAttribute::Kind attrs =
363         props[0].PropD->getPropertyAttributesAsWritten();
364 
365 #ifndef NDEBUG
366     for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I)
367       assert(attrs == I->PropD->getPropertyAttributesAsWritten());
368 #endif
369 
370     return attrs;
371   }
372 };
373 
374 } // anonymous namespace
375 
traverseObjCImplementation(ObjCImplementationContext & ImplCtx)376 void PropertyRewriteTraverser::traverseObjCImplementation(
377                                            ObjCImplementationContext &ImplCtx) {
378   PropertiesRewriter(ImplCtx.getMigrationContext())
379                                   .doTransform(ImplCtx.getImplementationDecl());
380 }
381