/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/ARCMigrate/TransProperties.cpp
Line | Count | Source (jump to first uncovered line) |
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 | 195 | : 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 | 88 | : MigrateCtx(MigrateCtx), Pass(MigrateCtx.Pass) { } |
75 | | |
76 | | static void collectProperties(ObjCContainerDecl *D, AtPropDeclsTy &AtProps, |
77 | 90 | AtPropDeclsTy *PrevAtProps = nullptr) { |
78 | 195 | for (auto *Prop : D->instance_properties()) { |
79 | 195 | SourceLocation Loc = Prop->getAtLoc(); |
80 | 195 | if (Loc.isInvalid()) |
81 | 0 | continue; |
82 | 195 | if (PrevAtProps) |
83 | 0 | if (PrevAtProps->find(Loc) != PrevAtProps->end()) |
84 | 0 | continue; |
85 | 195 | PropsTy &props = AtProps[Loc]; |
86 | 195 | props.push_back(Prop); |
87 | 195 | } |
88 | 90 | } |
89 | | |
90 | 88 | void doTransform(ObjCImplementationDecl *D) { |
91 | 88 | CurImplD = D; |
92 | 88 | ObjCInterfaceDecl *iface = D->getClassInterface(); |
93 | 88 | if (!iface) |
94 | 0 | return; |
95 | | |
96 | 88 | collectProperties(iface, AtProps); |
97 | | |
98 | | // Look through extensions. |
99 | 88 | for (auto *Ext : iface->visible_extensions()) |
100 | 2 | collectProperties(Ext, AtProps); |
101 | | |
102 | 88 | typedef DeclContext::specific_decl_iterator<ObjCPropertyImplDecl> |
103 | 88 | prop_impl_iterator; |
104 | 88 | for (prop_impl_iterator |
105 | 88 | I = prop_impl_iterator(D->decls_begin()), |
106 | 275 | E = prop_impl_iterator(D->decls_end()); I != E; ++I187 ) { |
107 | 187 | ObjCPropertyImplDecl *implD = *I; |
108 | 187 | if (implD->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize) |
109 | 2 | continue; |
110 | 185 | ObjCPropertyDecl *propD = implD->getPropertyDecl(); |
111 | 185 | if (!propD || propD->isInvalidDecl()) |
112 | 51 | continue; |
113 | 134 | ObjCIvarDecl *ivarD = implD->getPropertyIvarDecl(); |
114 | 134 | if (!ivarD || ivarD->isInvalidDecl()) |
115 | 0 | continue; |
116 | 134 | AtPropDeclsTy::iterator findAtLoc = AtProps.find(propD->getAtLoc()); |
117 | 134 | if (findAtLoc == AtProps.end()) |
118 | 0 | continue; |
119 | | |
120 | 134 | PropsTy &props = findAtLoc->second; |
121 | 136 | for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I2 ) { |
122 | 136 | if (I->PropD == propD) { |
123 | 134 | I->IvarD = ivarD; |
124 | 134 | I->ImplD = implD; |
125 | 134 | break; |
126 | 134 | } |
127 | 136 | } |
128 | 134 | } |
129 | | |
130 | 88 | for (AtPropDeclsTy::iterator |
131 | 272 | I = AtProps.begin(), E = AtProps.end(); I != E; ++I184 ) { |
132 | 184 | SourceLocation atLoc = I->first; |
133 | 184 | PropsTy &props = I->second; |
134 | 184 | if (!getPropertyType(props)->isObjCRetainableType()) |
135 | 0 | continue; |
136 | 184 | if (hasIvarWithExplicitARCOwnership(props)) |
137 | 0 | continue; |
138 | | |
139 | 184 | Transaction Trans(Pass.TA); |
140 | 184 | rewriteProperty(props, atLoc); |
141 | 184 | } |
142 | 88 | } |
143 | | |
144 | | private: |
145 | | void doPropAction(PropActionKind kind, |
146 | | PropsTy &props, SourceLocation atLoc, |
147 | 155 | bool markAction = true) { |
148 | 155 | if (markAction) |
149 | 321 | for (PropsTy::iterator I = props.begin(), E = props.end(); 155 I != E; ++I166 ) |
150 | 166 | ActionOnProp[I->PropD->getIdentifier()] = kind; |
151 | | |
152 | 155 | switch (kind) { |
153 | 0 | case PropAction_None: |
154 | 0 | return; |
155 | 50 | case PropAction_RetainReplacedWithStrong: { |
156 | 50 | StringRef toAttr = "strong"; |
157 | 50 | MigrateCtx.rewritePropertyAttribute("retain", toAttr, atLoc); |
158 | 50 | return; |
159 | 0 | } |
160 | 1 | case PropAction_AssignRemoved: |
161 | 1 | return removeAssignForDefaultStrong(props, atLoc); |
162 | 82 | case PropAction_AssignRewritten: |
163 | 82 | return rewriteAssign(props, atLoc); |
164 | 22 | case PropAction_MaybeAddWeakOrUnsafe: |
165 | 22 | return maybeAddWeakOrUnsafeUnretainedAttr(props, atLoc); |
166 | 155 | } |
167 | 155 | } |
168 | | |
169 | 184 | void rewriteProperty(PropsTy &props, SourceLocation atLoc) { |
170 | 184 | ObjCPropertyAttribute::Kind propAttrs = getPropertyAttrs(props); |
171 | | |
172 | 184 | if (propAttrs & |
173 | 184 | (ObjCPropertyAttribute::kind_copy | |
174 | 184 | ObjCPropertyAttribute::kind_unsafe_unretained | |
175 | 184 | ObjCPropertyAttribute::kind_strong | ObjCPropertyAttribute::kind_weak)) |
176 | 7 | return; |
177 | | |
178 | 177 | if (propAttrs & ObjCPropertyAttribute::kind_retain) { |
179 | | // strong is the default. |
180 | 50 | return doPropAction(PropAction_RetainReplacedWithStrong, props, atLoc); |
181 | 50 | } |
182 | | |
183 | 127 | bool HasIvarAssignedAPlusOneObject = hasIvarAssignedAPlusOneObject(props); |
184 | | |
185 | 127 | if (propAttrs & ObjCPropertyAttribute::kind_assign) { |
186 | 83 | if (HasIvarAssignedAPlusOneObject) |
187 | 1 | return doPropAction(PropAction_AssignRemoved, props, atLoc); |
188 | 82 | return doPropAction(PropAction_AssignRewritten, props, atLoc); |
189 | 83 | } |
190 | | |
191 | 44 | if (HasIvarAssignedAPlusOneObject || |
192 | 44 | (38 Pass.isGCMigration()38 && !hasGCWeak(props, atLoc)24 )) |
193 | 22 | return; // 'strong' by default. |
194 | | |
195 | 22 | return doPropAction(PropAction_MaybeAddWeakOrUnsafe, props, atLoc); |
196 | 44 | } |
197 | | |
198 | | void removeAssignForDefaultStrong(PropsTy &props, |
199 | 1 | SourceLocation atLoc) const { |
200 | 1 | removeAttribute("retain", atLoc); |
201 | 1 | if (!removeAttribute("assign", atLoc)) |
202 | 0 | return; |
203 | | |
204 | 2 | for (PropsTy::iterator I = props.begin(), E = props.end(); 1 I != E; ++I1 ) { |
205 | 1 | if (I->ImplD) |
206 | 1 | Pass.TA.clearDiagnostic(diag::err_arc_strong_property_ownership, |
207 | 1 | diag::err_arc_assign_property_ownership, |
208 | 1 | diag::err_arc_inconsistent_property_ownership, |
209 | 1 | I->IvarD->getLocation()); |
210 | 1 | } |
211 | 1 | } |
212 | | |
213 | 82 | void rewriteAssign(PropsTy &props, SourceLocation atLoc) const { |
214 | 82 | bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props), |
215 | 82 | /*AllowOnUnknownClass=*/Pass.isGCMigration()); |
216 | 82 | const char *toWhich = |
217 | 82 | (Pass.isGCMigration() && !hasGCWeak(props, atLoc)68 ) ? "strong"43 : |
218 | 82 | (39 canUseWeak39 ? "weak"28 : "unsafe_unretained"11 ); |
219 | | |
220 | 82 | bool rewroteAttr = rewriteAttribute("assign", toWhich, atLoc); |
221 | 82 | if (!rewroteAttr) |
222 | 0 | canUseWeak = false; |
223 | | |
224 | 173 | for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I91 ) { |
225 | 91 | if (isUserDeclared(I->IvarD)) { |
226 | 19 | if (I->IvarD && |
227 | 19 | I->IvarD->getType().getObjCLifetime() != Qualifiers::OCL_Weak) { |
228 | 19 | const char *toWhich = |
229 | 19 | (Pass.isGCMigration() && !hasGCWeak(props, atLoc)9 ) ? "__strong "9 : |
230 | 19 | (10 canUseWeak10 ? "__weak "2 : "__unsafe_unretained "8 ); |
231 | 19 | Pass.TA.insert(I->IvarD->getLocation(), toWhich); |
232 | 19 | } |
233 | 19 | } |
234 | 91 | if (I->ImplD) |
235 | 40 | Pass.TA.clearDiagnostic(diag::err_arc_strong_property_ownership, |
236 | 40 | diag::err_arc_assign_property_ownership, |
237 | 40 | diag::err_arc_inconsistent_property_ownership, |
238 | 40 | I->IvarD->getLocation()); |
239 | 91 | } |
240 | 82 | } |
241 | | |
242 | | void maybeAddWeakOrUnsafeUnretainedAttr(PropsTy &props, |
243 | 22 | SourceLocation atLoc) const { |
244 | 22 | bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props), |
245 | 22 | /*AllowOnUnknownClass=*/Pass.isGCMigration()); |
246 | | |
247 | 22 | bool addedAttr = addAttribute(canUseWeak ? "weak"18 : "unsafe_unretained"4 , |
248 | 22 | atLoc); |
249 | 22 | if (!addedAttr) |
250 | 0 | canUseWeak = false; |
251 | | |
252 | 46 | for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I24 ) { |
253 | 24 | if (isUserDeclared(I->IvarD)) { |
254 | 18 | if (I->IvarD && |
255 | 18 | I->IvarD->getType().getObjCLifetime() != Qualifiers::OCL_Weak) |
256 | 10 | Pass.TA.insert(I->IvarD->getLocation(), |
257 | 10 | canUseWeak ? "__weak "6 : "__unsafe_unretained "4 ); |
258 | 18 | } |
259 | 24 | if (I->ImplD) { |
260 | 22 | Pass.TA.clearDiagnostic(diag::err_arc_strong_property_ownership, |
261 | 22 | diag::err_arc_assign_property_ownership, |
262 | 22 | diag::err_arc_inconsistent_property_ownership, |
263 | 22 | I->IvarD->getLocation()); |
264 | 22 | Pass.TA.clearDiagnostic( |
265 | 22 | diag::err_arc_objc_property_default_assign_on_object, |
266 | 22 | I->ImplD->getLocation()); |
267 | 22 | } |
268 | 24 | } |
269 | 22 | } |
270 | | |
271 | 2 | bool removeAttribute(StringRef fromAttr, SourceLocation atLoc) const { |
272 | 2 | return MigrateCtx.removePropertyAttribute(fromAttr, atLoc); |
273 | 2 | } |
274 | | |
275 | | bool rewriteAttribute(StringRef fromAttr, StringRef toAttr, |
276 | 82 | SourceLocation atLoc) const { |
277 | 82 | return MigrateCtx.rewritePropertyAttribute(fromAttr, toAttr, atLoc); |
278 | 82 | } |
279 | | |
280 | 22 | bool addAttribute(StringRef attr, SourceLocation atLoc) const { |
281 | 22 | return MigrateCtx.addPropertyAttribute(attr, atLoc); |
282 | 22 | } |
283 | | |
284 | | class PlusOneAssign : public RecursiveASTVisitor<PlusOneAssign> { |
285 | | ObjCIvarDecl *Ivar; |
286 | | public: |
287 | 138 | PlusOneAssign(ObjCIvarDecl *D) : Ivar(D) {} |
288 | | |
289 | 81 | bool VisitBinaryOperator(BinaryOperator *E) { |
290 | 81 | if (E->getOpcode() != BO_Assign) |
291 | 0 | return true; |
292 | | |
293 | 81 | Expr *lhs = E->getLHS()->IgnoreParenImpCasts(); |
294 | 81 | if (ObjCIvarRefExpr *RE = dyn_cast<ObjCIvarRefExpr>(lhs)) { |
295 | 79 | if (RE->getDecl() != Ivar) |
296 | 72 | return true; |
297 | | |
298 | 7 | if (isPlusOneAssign(E)) |
299 | 7 | return false; |
300 | 7 | } |
301 | | |
302 | 2 | return true; |
303 | 81 | } |
304 | | }; |
305 | | |
306 | 127 | bool hasIvarAssignedAPlusOneObject(PropsTy &props) const { |
307 | 258 | for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I131 ) { |
308 | 138 | PlusOneAssign oneAssign(I->IvarD); |
309 | 138 | bool notFound = oneAssign.TraverseDecl(CurImplD); |
310 | 138 | if (!notFound) |
311 | 7 | return true; |
312 | 138 | } |
313 | | |
314 | 120 | return false; |
315 | 127 | } |
316 | | |
317 | 184 | bool hasIvarWithExplicitARCOwnership(PropsTy &props) const { |
318 | 184 | if (Pass.isGCMigration()) |
319 | 101 | return false; |
320 | | |
321 | 168 | for (PropsTy::iterator I = props.begin(), E = props.end(); 83 I != E; ++I85 ) { |
322 | 85 | if (isUserDeclared(I->IvarD)) { |
323 | 28 | if (isa<AttributedType>(I->IvarD->getType())) |
324 | 0 | return true; |
325 | 28 | if (I->IvarD->getType().getLocalQualifiers().getObjCLifetime() |
326 | 28 | != Qualifiers::OCL_Strong) |
327 | 0 | return true; |
328 | 28 | } |
329 | 85 | } |
330 | | |
331 | 83 | return false; |
332 | 83 | } |
333 | | |
334 | | // Returns true if all declarations in the @property have GC __weak. |
335 | 101 | bool hasGCWeak(PropsTy &props, SourceLocation atLoc) const { |
336 | 101 | if (!Pass.isGCMigration()) |
337 | 0 | return false; |
338 | 101 | if (props.empty()) |
339 | 0 | return false; |
340 | 101 | return MigrateCtx.AtPropsWeak.count(atLoc); |
341 | 101 | } |
342 | | |
343 | 200 | bool isUserDeclared(ObjCIvarDecl *ivarD) const { |
344 | 200 | return ivarD && !ivarD->getSynthesize()137 ; |
345 | 200 | } |
346 | | |
347 | 288 | QualType getPropertyType(PropsTy &props) const { |
348 | 288 | assert(!props.empty()); |
349 | 288 | QualType ty = props[0].PropD->getType().getUnqualifiedType(); |
350 | | |
351 | 288 | #ifndef NDEBUG |
352 | 598 | for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I310 ) |
353 | 310 | assert(ty == I->PropD->getType().getUnqualifiedType()); |
354 | 288 | #endif |
355 | | |
356 | 288 | return ty; |
357 | 288 | } |
358 | | |
359 | 184 | ObjCPropertyAttribute::Kind getPropertyAttrs(PropsTy &props) const { |
360 | 184 | assert(!props.empty()); |
361 | 184 | ObjCPropertyAttribute::Kind attrs = |
362 | 184 | props[0].PropD->getPropertyAttributesAsWritten(); |
363 | | |
364 | 184 | #ifndef NDEBUG |
365 | 379 | for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I195 ) |
366 | 195 | assert(attrs == I->PropD->getPropertyAttributesAsWritten()); |
367 | 184 | #endif |
368 | | |
369 | 184 | return attrs; |
370 | 184 | } |
371 | | }; |
372 | | |
373 | | } // anonymous namespace |
374 | | |
375 | | void PropertyRewriteTraverser::traverseObjCImplementation( |
376 | 88 | ObjCImplementationContext &ImplCtx) { |
377 | 88 | PropertiesRewriter(ImplCtx.getMigrationContext()) |
378 | 88 | .doTransform(ImplCtx.getImplementationDecl()); |
379 | 88 | } |