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