| //===--- TransProperties.cpp - Tranformations 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/Sema/SemaDiagnostic.h" |
| #include "clang/Basic/SourceManager.h" |
| #include "clang/Lex/Lexer.h" |
| #include <map> |
| |
| using namespace clang; |
| using namespace arcmt; |
| using namespace trans; |
| |
| namespace { |
| |
| class PropertiesRewriter { |
| MigrationContext &MigrateCtx; |
| MigrationPass &Pass; |
| ObjCImplementationDecl *CurImplD; |
| |
| enum PropActionKind { |
| PropAction_None, |
| PropAction_RetainToStrong, |
| PropAction_RetainRemoved, |
| PropAction_AssignRemoved, |
| PropAction_AssignRewritten, |
| PropAction_MaybeAddStrong, |
| PropAction_MaybeAddWeakOrUnsafe |
| }; |
| |
| struct PropData { |
| ObjCPropertyDecl *PropD; |
| ObjCIvarDecl *IvarD; |
| ObjCPropertyImplDecl *ImplD; |
| |
| PropData(ObjCPropertyDecl *propD) : PropD(propD), IvarD(0), ImplD(0) { } |
| }; |
| |
| typedef SmallVector<PropData, 2> PropsTy; |
| typedef std::map<unsigned, PropsTy> AtPropDeclsTy; |
| AtPropDeclsTy AtProps; |
| llvm::DenseMap<IdentifierInfo *, PropActionKind> ActionOnProp; |
| |
| public: |
| explicit PropertiesRewriter(MigrationContext &MigrateCtx) |
| : MigrateCtx(MigrateCtx), Pass(MigrateCtx.Pass) { } |
| |
| static void collectProperties(ObjCContainerDecl *D, AtPropDeclsTy &AtProps) { |
| for (ObjCInterfaceDecl::prop_iterator |
| propI = D->prop_begin(), |
| propE = D->prop_end(); propI != propE; ++propI) { |
| if (propI->getAtLoc().isInvalid()) |
| continue; |
| PropsTy &props = AtProps[propI->getAtLoc().getRawEncoding()]; |
| props.push_back(*propI); |
| } |
| } |
| |
| void doTransform(ObjCImplementationDecl *D) { |
| CurImplD = D; |
| ObjCInterfaceDecl *iface = D->getClassInterface(); |
| if (!iface) |
| return; |
| |
| collectProperties(iface, AtProps); |
| |
| typedef DeclContext::specific_decl_iterator<ObjCPropertyImplDecl> |
| 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); |
| } |
| |
| AtPropDeclsTy AtExtProps; |
| // Look through extensions. |
| for (ObjCCategoryDecl *Cat = iface->getCategoryList(); |
| Cat; Cat = Cat->getNextClassCategory()) |
| if (Cat->IsClassExtension()) |
| collectProperties(Cat, AtExtProps); |
| |
| for (AtPropDeclsTy::iterator |
| I = AtExtProps.begin(), E = AtExtProps.end(); I != E; ++I) { |
| SourceLocation atLoc = SourceLocation::getFromRawEncoding(I->first); |
| PropsTy &props = I->second; |
| Transaction Trans(Pass.TA); |
| doActionForExtensionProp(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_RetainToStrong: |
| rewriteAttribute("retain", "strong", atLoc); |
| return; |
| case PropAction_RetainRemoved: |
| removeAttribute("retain", atLoc); |
| return; |
| case PropAction_AssignRemoved: |
| return removeAssignForDefaultStrong(props, atLoc); |
| case PropAction_AssignRewritten: |
| return rewriteAssign(props, atLoc); |
| case PropAction_MaybeAddStrong: |
| return maybeAddStrongAttr(props, atLoc); |
| case PropAction_MaybeAddWeakOrUnsafe: |
| return maybeAddWeakOrUnsafeUnretainedAttr(props, atLoc); |
| } |
| } |
| |
| void doActionForExtensionProp(PropsTy &props, SourceLocation atLoc) { |
| llvm::DenseMap<IdentifierInfo *, PropActionKind>::iterator I; |
| I = ActionOnProp.find(props[0].PropD->getIdentifier()); |
| if (I == ActionOnProp.end()) |
| return; |
| |
| doPropAction(I->second, props, atLoc, false); |
| } |
| |
| 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) { |
| if (propAttrs & ObjCPropertyDecl::OBJC_PR_readonly) |
| return doPropAction(PropAction_RetainToStrong, props, atLoc); |
| else |
| // strong is the default. |
| return doPropAction(PropAction_RetainRemoved, props, atLoc); |
| } |
| |
| bool HasIvarAssignedAPlusOneObject = hasIvarAssignedAPlusOneObject(props); |
| |
| if (propAttrs & ObjCPropertyDecl::OBJC_PR_assign) { |
| if (HasIvarAssignedAPlusOneObject || |
| (Pass.isGCMigration() && !hasGCWeak(props, atLoc))) { |
| return doPropAction(PropAction_AssignRemoved, props, atLoc); |
| } |
| return doPropAction(PropAction_AssignRewritten, props, atLoc); |
| } |
| |
| if (HasIvarAssignedAPlusOneObject || |
| (Pass.isGCMigration() && !hasGCWeak(props, atLoc))) |
| return doPropAction(PropAction_MaybeAddStrong, props, atLoc); |
| |
| 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_assign_property_ownership, |
| I->ImplD->getLocation()); |
| } |
| } |
| |
| void rewriteAssign(PropsTy &props, SourceLocation atLoc) const { |
| bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props), |
| /*AllowOnUnknownClass=*/Pass.isGCMigration()); |
| |
| bool rewroteAttr = rewriteAttribute("assign", |
| canUseWeak ? "weak" : "unsafe_unretained", |
| atLoc); |
| if (!rewroteAttr) |
| canUseWeak = false; |
| |
| for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { |
| if (isUserDeclared(I->IvarD)) |
| Pass.TA.insert(I->IvarD->getLocation(), |
| canUseWeak ? "__weak " : "__unsafe_unretained "); |
| if (I->ImplD) |
| Pass.TA.clearDiagnostic(diag::err_arc_assign_property_ownership, |
| I->ImplD->getLocation()); |
| } |
| } |
| |
| void maybeAddWeakOrUnsafeUnretainedAttr(PropsTy &props, |
| SourceLocation atLoc) const { |
| ObjCPropertyDecl::PropertyAttributeKind propAttrs = getPropertyAttrs(props); |
| |
| bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props), |
| /*AllowOnUnknownClass=*/Pass.isGCMigration()); |
| if (!(propAttrs & ObjCPropertyDecl::OBJC_PR_readonly) || |
| !hasAllIvarsBacked(props)) { |
| 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)) |
| Pass.TA.insert(I->IvarD->getLocation(), |
| canUseWeak ? "__weak " : "__unsafe_unretained "); |
| if (I->ImplD) { |
| Pass.TA.clearDiagnostic(diag::err_arc_assign_property_ownership, |
| I->ImplD->getLocation()); |
| Pass.TA.clearDiagnostic( |
| diag::err_arc_objc_property_default_assign_on_object, |
| I->ImplD->getLocation()); |
| } |
| } |
| } |
| |
| void maybeAddStrongAttr(PropsTy &props, SourceLocation atLoc) const { |
| ObjCPropertyDecl::PropertyAttributeKind propAttrs = getPropertyAttrs(props); |
| |
| if (!(propAttrs & ObjCPropertyDecl::OBJC_PR_readonly)) |
| return; // 'strong' by default. |
| |
| if (!hasAllIvarsBacked(props)) { |
| addAttribute("strong", atLoc); |
| } |
| |
| for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { |
| if (I->ImplD) { |
| Pass.TA.clearDiagnostic(diag::err_arc_assign_property_ownership, |
| I->ImplD->getLocation()); |
| Pass.TA.clearDiagnostic( |
| diag::err_arc_objc_property_default_assign_on_object, |
| I->ImplD->getLocation()); |
| } |
| } |
| } |
| |
| bool removeAttribute(StringRef fromAttr, SourceLocation atLoc) const { |
| return rewriteAttribute(fromAttr, StringRef(), atLoc); |
| } |
| |
| bool rewriteAttribute(StringRef fromAttr, StringRef toAttr, |
| SourceLocation atLoc) const { |
| return MigrateCtx.rewritePropertyAttribute(fromAttr, toAttr, atLoc); |
| } |
| |
| bool addAttribute(StringRef attr, SourceLocation atLoc) const { |
| if (atLoc.isMacroID()) |
| return false; |
| |
| SourceManager &SM = Pass.Ctx.getSourceManager(); |
| |
| // Break down the source location. |
| std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(atLoc); |
| |
| // Try to load the file buffer. |
| bool invalidTemp = false; |
| StringRef file = SM.getBufferData(locInfo.first, &invalidTemp); |
| if (invalidTemp) |
| return false; |
| |
| const char *tokenBegin = file.data() + locInfo.second; |
| |
| // Lex from the start of the given location. |
| Lexer lexer(SM.getLocForStartOfFile(locInfo.first), |
| Pass.Ctx.getLangOptions(), |
| file.begin(), tokenBegin, file.end()); |
| Token tok; |
| lexer.LexFromRawLexer(tok); |
| if (tok.isNot(tok::at)) return false; |
| lexer.LexFromRawLexer(tok); |
| if (tok.isNot(tok::raw_identifier)) return false; |
| if (StringRef(tok.getRawIdentifierData(), tok.getLength()) |
| != "property") |
| return false; |
| lexer.LexFromRawLexer(tok); |
| |
| if (tok.isNot(tok::l_paren)) { |
| Pass.TA.insert(tok.getLocation(), std::string("(") + attr.str() + ") "); |
| return true; |
| } |
| |
| lexer.LexFromRawLexer(tok); |
| if (tok.is(tok::r_paren)) { |
| Pass.TA.insert(tok.getLocation(), attr); |
| return true; |
| } |
| |
| if (tok.isNot(tok::raw_identifier)) return false; |
| |
| Pass.TA.insert(tok.getLocation(), std::string(attr) + ", "); |
| return true; |
| } |
| |
| class PlusOneAssign : public RecursiveASTVisitor<PlusOneAssign> { |
| ObjCIvarDecl *Ivar; |
| public: |
| PlusOneAssign(ObjCIvarDecl *D) : Ivar(D) {} |
| |
| bool VisitBinAssign(BinaryOperator *E) { |
| Expr *lhs = E->getLHS()->IgnoreParenImpCasts(); |
| if (ObjCIvarRefExpr *RE = dyn_cast<ObjCIvarRefExpr>(lhs)) { |
| if (RE->getDecl() != Ivar) |
| return true; |
| |
| if (ObjCMessageExpr * |
| ME = dyn_cast<ObjCMessageExpr>(E->getRHS()->IgnoreParenCasts())) |
| if (ME->getMethodFamily() == OMF_retain) |
| return false; |
| |
| ImplicitCastExpr *implCE = dyn_cast<ImplicitCastExpr>(E->getRHS()); |
| while (implCE && implCE->getCastKind() == CK_BitCast) |
| implCE = dyn_cast<ImplicitCastExpr>(implCE->getSubExpr()); |
| |
| if (implCE && implCE->getCastKind() == CK_ARCConsumeObject) |
| 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<AttributedType>(I->IvarD->getType())) |
| return true; |
| if (I->IvarD->getType().getLocalQualifiers().getObjCLifetime() |
| != Qualifiers::OCL_Strong) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool hasAllIvarsBacked(PropsTy &props) const { |
| for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) |
| if (!isUserDeclared(I->IvarD)) |
| return false; |
| |
| return true; |
| } |
| |
| // \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()); |
| } |