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(x: Loc) != PrevAtProps->end()) |
84 | continue; |
85 | PropsTy &props = AtProps[Loc]; |
86 | props.push_back(Elt: 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(D: iface, AtProps); |
97 | |
98 | // Look through extensions. |
99 | for (auto *Ext : iface->visible_extensions()) |
100 | collectProperties(D: 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(x: 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(fromAttr: "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(kind: PropAction_RetainReplacedWithStrong, props, atLoc); |
181 | } |
182 | |
183 | bool HasIvarAssignedAPlusOneObject = hasIvarAssignedAPlusOneObject(props); |
184 | |
185 | if (propAttrs & ObjCPropertyAttribute::kind_assign) { |
186 | if (HasIvarAssignedAPlusOneObject) |
187 | return doPropAction(kind: PropAction_AssignRemoved, props, atLoc); |
188 | return doPropAction(kind: PropAction_AssignRewritten, props, atLoc); |
189 | } |
190 | |
191 | if (HasIvarAssignedAPlusOneObject || |
192 | (Pass.isGCMigration() && !hasGCWeak(props, atLoc))) |
193 | return; // 'strong' by default. |
194 | |
195 | return doPropAction(kind: PropAction_MaybeAddWeakOrUnsafe, props, atLoc); |
196 | } |
197 | |
198 | void removeAssignForDefaultStrong(PropsTy &props, |
199 | SourceLocation atLoc) const { |
200 | removeAttribute(fromAttr: "retain" , atLoc); |
201 | if (!removeAttribute(fromAttr: "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(ID1: diag::err_arc_strong_property_ownership, |
207 | ID2: diag::err_arc_assign_property_ownership, |
208 | ID3: diag::err_arc_inconsistent_property_ownership, |
209 | range: I->IvarD->getLocation()); |
210 | } |
211 | } |
212 | |
213 | void rewriteAssign(PropsTy &props, SourceLocation atLoc) const { |
214 | bool canUseWeak = canApplyWeak(Ctx&: Pass.Ctx, type: 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(fromAttr: "assign" , toAttr: 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(ivarD: 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(loc: I->IvarD->getLocation(), text: toWhich); |
232 | } |
233 | } |
234 | if (I->ImplD) |
235 | Pass.TA.clearDiagnostic(ID1: diag::err_arc_strong_property_ownership, |
236 | ID2: diag::err_arc_assign_property_ownership, |
237 | ID3: diag::err_arc_inconsistent_property_ownership, |
238 | range: I->IvarD->getLocation()); |
239 | } |
240 | } |
241 | |
242 | void maybeAddWeakOrUnsafeUnretainedAttr(PropsTy &props, |
243 | SourceLocation atLoc) const { |
244 | bool canUseWeak = canApplyWeak(Ctx&: Pass.Ctx, type: getPropertyType(props), |
245 | /*AllowOnUnknownClass=*/Pass.isGCMigration()); |
246 | |
247 | bool addedAttr = addAttribute(attr: 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(ivarD: I->IvarD)) { |
254 | if (I->IvarD && |
255 | I->IvarD->getType().getObjCLifetime() != Qualifiers::OCL_Weak) |
256 | Pass.TA.insert(loc: I->IvarD->getLocation(), |
257 | text: canUseWeak ? "__weak " : "__unsafe_unretained " ); |
258 | } |
259 | if (I->ImplD) { |
260 | Pass.TA.clearDiagnostic(ID1: diag::err_arc_strong_property_ownership, |
261 | ID2: diag::err_arc_assign_property_ownership, |
262 | ID3: diag::err_arc_inconsistent_property_ownership, |
263 | range: I->IvarD->getLocation()); |
264 | Pass.TA.clearDiagnostic( |
265 | IDs: diag::err_arc_objc_property_default_assign_on_object, |
266 | range: 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>(Val: 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(D: 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(ivarD: I->IvarD)) { |
323 | if (isa<AttributedType>(Val: 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(V: 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(D: ImplCtx.getImplementationDecl()); |
379 | } |
380 | |