150
|
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
|
|
63 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:
|
|
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 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
|
|
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:
|
|
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
|
|
170 void rewriteProperty(PropsTy &props, SourceLocation atLoc) {
|
173
|
171 ObjCPropertyAttribute::Kind propAttrs = getPropertyAttrs(props);
|
150
|
172
|
173
|
173 if (propAttrs &
|
|
174 (ObjCPropertyAttribute::kind_copy |
|
|
175 ObjCPropertyAttribute::kind_unsafe_unretained |
|
|
176 ObjCPropertyAttribute::kind_strong | ObjCPropertyAttribute::kind_weak))
|
150
|
177 return;
|
|
178
|
173
|
179 if (propAttrs & ObjCPropertyAttribute::kind_retain) {
|
150
|
180 // strong is the default.
|
|
181 return doPropAction(PropAction_RetainReplacedWithStrong, props, atLoc);
|
|
182 }
|
|
183
|
|
184 bool HasIvarAssignedAPlusOneObject = hasIvarAssignedAPlusOneObject(props);
|
|
185
|
173
|
186 if (propAttrs & ObjCPropertyAttribute::kind_assign) {
|
150
|
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
|
|
199 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
|
|
214 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
|
|
243 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
|
|
272 bool removeAttribute(StringRef fromAttr, SourceLocation atLoc) const {
|
|
273 return MigrateCtx.removePropertyAttribute(fromAttr, atLoc);
|
|
274 }
|
|
275
|
|
276 bool rewriteAttribute(StringRef fromAttr, StringRef toAttr,
|
|
277 SourceLocation atLoc) const {
|
|
278 return MigrateCtx.rewritePropertyAttribute(fromAttr, toAttr, atLoc);
|
|
279 }
|
|
280
|
|
281 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:
|
|
288 PlusOneAssign(ObjCIvarDecl *D) : Ivar(D) {}
|
|
289
|
|
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
|
|
304 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
|
|
315 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.
|
|
333 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
|
|
341 bool isUserDeclared(ObjCIvarDecl *ivarD) const {
|
|
342 return ivarD && !ivarD->getSynthesize();
|
|
343 }
|
|
344
|
|
345 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
|
173
|
357 ObjCPropertyAttribute::Kind getPropertyAttrs(PropsTy &props) const {
|
150
|
358 assert(!props.empty());
|
173
|
359 ObjCPropertyAttribute::Kind attrs =
|
|
360 props[0].PropD->getPropertyAttributesAsWritten();
|
150
|
361
|
|
362 #ifndef NDEBUG
|
|
363 for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I)
|
|
364 assert(attrs == I->PropD->getPropertyAttributesAsWritten());
|
|
365 #endif
|
|
366
|
|
367 return attrs;
|
|
368 }
|
|
369 };
|
|
370
|
|
371 } // anonymous namespace
|
|
372
|
|
373 void PropertyRewriteTraverser::traverseObjCImplementation(
|
|
374 ObjCImplementationContext &ImplCtx) {
|
|
375 PropertiesRewriter(ImplCtx.getMigrationContext())
|
|
376 .doTransform(ImplCtx.getImplementationDecl());
|
|
377 }
|