//===--- TransProperties.cpp - Transformations to ARC mode ----------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // rewriteProperties: // // - Adds strong/weak/unsafe_unretained ownership specifier to properties that // are missing one. // - Migrates properties from (retain) to (strong) and (assign) to // (unsafe_unretained/weak). // - If a property is synthesized, adds the ownership specifier in the ivar // backing the property. // // @interface Foo : NSObject { // NSObject *x; // } // @property (assign) id x; // @end // ----> // @interface Foo : NSObject { // NSObject *__weak x; // } // @property (weak) id x; // @end // //===----------------------------------------------------------------------===// #include "Transforms.h" #include "Internals.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" #include "clang/Sema/SemaDiagnostic.h" #include using namespace clang; using namespace arcmt; using namespace trans; namespace { class PropertiesRewriter { MigrationContext &MigrateCtx; MigrationPass &Pass; ObjCImplementationDecl *CurImplD; enum PropActionKind { PropAction_None, PropAction_RetainReplacedWithStrong, PropAction_AssignRemoved, PropAction_AssignRewritten, PropAction_MaybeAddWeakOrUnsafe }; struct PropData { ObjCPropertyDecl *PropD; ObjCIvarDecl *IvarD; ObjCPropertyImplDecl *ImplD; PropData(ObjCPropertyDecl *propD) : PropD(propD), IvarD(nullptr), ImplD(nullptr) {} }; typedef SmallVector PropsTy; typedef std::map AtPropDeclsTy; AtPropDeclsTy AtProps; llvm::DenseMap ActionOnProp; public: explicit PropertiesRewriter(MigrationContext &MigrateCtx) : MigrateCtx(MigrateCtx), Pass(MigrateCtx.Pass) { } static void collectProperties(ObjCContainerDecl *D, AtPropDeclsTy &AtProps, AtPropDeclsTy *PrevAtProps = nullptr) { for (auto *Prop : D->properties()) { if (Prop->getAtLoc().isInvalid()) continue; unsigned RawLoc = Prop->getAtLoc().getRawEncoding(); if (PrevAtProps) if (PrevAtProps->find(RawLoc) != PrevAtProps->end()) continue; PropsTy &props = AtProps[RawLoc]; props.push_back(Prop); } } void doTransform(ObjCImplementationDecl *D) { CurImplD = D; ObjCInterfaceDecl *iface = D->getClassInterface(); if (!iface) return; collectProperties(iface, AtProps); // Look through extensions. for (auto *Ext : iface->visible_extensions()) collectProperties(Ext, AtProps); typedef DeclContext::specific_decl_iterator prop_impl_iterator; for (prop_impl_iterator I = prop_impl_iterator(D->decls_begin()), E = prop_impl_iterator(D->decls_end()); I != E; ++I) { ObjCPropertyImplDecl *implD = *I; if (implD->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize) continue; ObjCPropertyDecl *propD = implD->getPropertyDecl(); if (!propD || propD->isInvalidDecl()) continue; ObjCIvarDecl *ivarD = implD->getPropertyIvarDecl(); if (!ivarD || ivarD->isInvalidDecl()) continue; unsigned rawAtLoc = propD->getAtLoc().getRawEncoding(); AtPropDeclsTy::iterator findAtLoc = AtProps.find(rawAtLoc); if (findAtLoc == AtProps.end()) continue; PropsTy &props = findAtLoc->second; for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { if (I->PropD == propD) { I->IvarD = ivarD; I->ImplD = implD; break; } } } for (AtPropDeclsTy::iterator I = AtProps.begin(), E = AtProps.end(); I != E; ++I) { SourceLocation atLoc = SourceLocation::getFromRawEncoding(I->first); PropsTy &props = I->second; if (!getPropertyType(props)->isObjCRetainableType()) continue; if (hasIvarWithExplicitARCOwnership(props)) continue; Transaction Trans(Pass.TA); rewriteProperty(props, atLoc); } } private: void doPropAction(PropActionKind kind, PropsTy &props, SourceLocation atLoc, bool markAction = true) { if (markAction) for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) ActionOnProp[I->PropD->getIdentifier()] = kind; switch (kind) { case PropAction_None: return; case PropAction_RetainReplacedWithStrong: { StringRef toAttr = "strong"; MigrateCtx.rewritePropertyAttribute("retain", toAttr, atLoc); return; } case PropAction_AssignRemoved: return removeAssignForDefaultStrong(props, atLoc); case PropAction_AssignRewritten: return rewriteAssign(props, atLoc); case PropAction_MaybeAddWeakOrUnsafe: return maybeAddWeakOrUnsafeUnretainedAttr(props, atLoc); } } void rewriteProperty(PropsTy &props, SourceLocation atLoc) { ObjCPropertyDecl::PropertyAttributeKind propAttrs = getPropertyAttrs(props); if (propAttrs & (ObjCPropertyDecl::OBJC_PR_copy | ObjCPropertyDecl::OBJC_PR_unsafe_unretained | ObjCPropertyDecl::OBJC_PR_strong | ObjCPropertyDecl::OBJC_PR_weak)) return; if (propAttrs & ObjCPropertyDecl::OBJC_PR_retain) { // strong is the default. return doPropAction(PropAction_RetainReplacedWithStrong, props, atLoc); } bool HasIvarAssignedAPlusOneObject = hasIvarAssignedAPlusOneObject(props); if (propAttrs & ObjCPropertyDecl::OBJC_PR_assign) { if (HasIvarAssignedAPlusOneObject) return doPropAction(PropAction_AssignRemoved, props, atLoc); return doPropAction(PropAction_AssignRewritten, props, atLoc); } if (HasIvarAssignedAPlusOneObject || (Pass.isGCMigration() && !hasGCWeak(props, atLoc))) return; // 'strong' by default. return doPropAction(PropAction_MaybeAddWeakOrUnsafe, props, atLoc); } void removeAssignForDefaultStrong(PropsTy &props, SourceLocation atLoc) const { removeAttribute("retain", atLoc); if (!removeAttribute("assign", atLoc)) return; for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { if (I->ImplD) Pass.TA.clearDiagnostic(diag::err_arc_strong_property_ownership, diag::err_arc_assign_property_ownership, diag::err_arc_inconsistent_property_ownership, I->IvarD->getLocation()); } } void rewriteAssign(PropsTy &props, SourceLocation atLoc) const { bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props), /*AllowOnUnknownClass=*/Pass.isGCMigration()); const char *toWhich = (Pass.isGCMigration() && !hasGCWeak(props, atLoc)) ? "strong" : (canUseWeak ? "weak" : "unsafe_unretained"); bool rewroteAttr = rewriteAttribute("assign", toWhich, atLoc); if (!rewroteAttr) canUseWeak = false; for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { if (isUserDeclared(I->IvarD)) { if (I->IvarD && I->IvarD->getType().getObjCLifetime() != Qualifiers::OCL_Weak) { const char *toWhich = (Pass.isGCMigration() && !hasGCWeak(props, atLoc)) ? "__strong " : (canUseWeak ? "__weak " : "__unsafe_unretained "); Pass.TA.insert(I->IvarD->getLocation(), toWhich); } } if (I->ImplD) Pass.TA.clearDiagnostic(diag::err_arc_strong_property_ownership, diag::err_arc_assign_property_ownership, diag::err_arc_inconsistent_property_ownership, I->IvarD->getLocation()); } } void maybeAddWeakOrUnsafeUnretainedAttr(PropsTy &props, SourceLocation atLoc) const { bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props), /*AllowOnUnknownClass=*/Pass.isGCMigration()); bool addedAttr = addAttribute(canUseWeak ? "weak" : "unsafe_unretained", atLoc); if (!addedAttr) canUseWeak = false; for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { if (isUserDeclared(I->IvarD)) { if (I->IvarD && I->IvarD->getType().getObjCLifetime() != Qualifiers::OCL_Weak) Pass.TA.insert(I->IvarD->getLocation(), canUseWeak ? "__weak " : "__unsafe_unretained "); } if (I->ImplD) { Pass.TA.clearDiagnostic(diag::err_arc_strong_property_ownership, diag::err_arc_assign_property_ownership, diag::err_arc_inconsistent_property_ownership, I->IvarD->getLocation()); Pass.TA.clearDiagnostic( diag::err_arc_objc_property_default_assign_on_object, I->ImplD->getLocation()); } } } bool removeAttribute(StringRef fromAttr, SourceLocation atLoc) const { return MigrateCtx.removePropertyAttribute(fromAttr, atLoc); } bool rewriteAttribute(StringRef fromAttr, StringRef toAttr, SourceLocation atLoc) const { return MigrateCtx.rewritePropertyAttribute(fromAttr, toAttr, atLoc); } bool addAttribute(StringRef attr, SourceLocation atLoc) const { return MigrateCtx.addPropertyAttribute(attr, atLoc); } class PlusOneAssign : public RecursiveASTVisitor { ObjCIvarDecl *Ivar; public: PlusOneAssign(ObjCIvarDecl *D) : Ivar(D) {} bool VisitBinAssign(BinaryOperator *E) { Expr *lhs = E->getLHS()->IgnoreParenImpCasts(); if (ObjCIvarRefExpr *RE = dyn_cast(lhs)) { if (RE->getDecl() != Ivar) return true; if (isPlusOneAssign(E)) return false; } return true; } }; bool hasIvarAssignedAPlusOneObject(PropsTy &props) const { for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { PlusOneAssign oneAssign(I->IvarD); bool notFound = oneAssign.TraverseDecl(CurImplD); if (!notFound) return true; } return false; } bool hasIvarWithExplicitARCOwnership(PropsTy &props) const { if (Pass.isGCMigration()) return false; for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { if (isUserDeclared(I->IvarD)) { if (isa(I->IvarD->getType())) return true; if (I->IvarD->getType().getLocalQualifiers().getObjCLifetime() != Qualifiers::OCL_Strong) return true; } } return false; } // \brief Returns true if all declarations in the @property have GC __weak. bool hasGCWeak(PropsTy &props, SourceLocation atLoc) const { if (!Pass.isGCMigration()) return false; if (props.empty()) return false; return MigrateCtx.AtPropsWeak.count(atLoc.getRawEncoding()); } bool isUserDeclared(ObjCIvarDecl *ivarD) const { return ivarD && !ivarD->getSynthesize(); } QualType getPropertyType(PropsTy &props) const { assert(!props.empty()); QualType ty = props[0].PropD->getType().getUnqualifiedType(); #ifndef NDEBUG for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) assert(ty == I->PropD->getType().getUnqualifiedType()); #endif return ty; } ObjCPropertyDecl::PropertyAttributeKind getPropertyAttrs(PropsTy &props) const { assert(!props.empty()); ObjCPropertyDecl::PropertyAttributeKind attrs = props[0].PropD->getPropertyAttributesAsWritten(); #ifndef NDEBUG for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) assert(attrs == I->PropD->getPropertyAttributesAsWritten()); #endif return attrs; } }; } // anonymous namespace void PropertyRewriteTraverser::traverseObjCImplementation( ObjCImplementationContext &ImplCtx) { PropertiesRewriter(ImplCtx.getMigrationContext()) .doTransform(ImplCtx.getImplementationDecl()); }