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