xref: /freebsd/contrib/llvm-project/clang/lib/ARCMigrate/TransProperties.cpp (revision 725a9f47324d42037db93c27ceb40d4956872f3e)
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 = 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 
72 public:
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 
144 private:
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 
375 void PropertyRewriteTraverser::traverseObjCImplementation(
376                                            ObjCImplementationContext &ImplCtx) {
377   PropertiesRewriter(ImplCtx.getMigrationContext())
378                                   .doTransform(ImplCtx.getImplementationDecl());
379 }
380