//
//===----------------------------------------------------------------------===//
//
-// changeIvarsOfAssignProperties:
+// rewriteProperties:
//
-// If a property is synthesized with 'assign' attribute and the user didn't
-// set a lifetime attribute, change the property to 'weak' or add
-// __unsafe_unretained if the ARC runtime is not available.
+// - 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;
#include "clang/Sema/SemaDiagnostic.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/Lexer.h"
+#include <map>
using namespace clang;
using namespace arcmt;
namespace {
-class AssignPropertiesTrans {
+class PropertiesRewriter {
MigrationPass &Pass;
+
struct PropData {
ObjCPropertyDecl *PropD;
ObjCIvarDecl *IvarD;
- bool ShouldChangeToWeak;
- SourceLocation ArcPropAssignErrorLoc;
+ ObjCPropertyImplDecl *ImplD;
+
+ PropData(ObjCPropertyDecl *propD) : PropD(propD), IvarD(0), ImplD(0) { }
};
- typedef llvm::SmallVector<PropData, 2> PropsTy;
- typedef llvm::DenseMap<unsigned, PropsTy> PropsMapTy;
- PropsMapTy PropsMap;
+ typedef llvm::SmallVector<PropData, 2> PropsTy;
+ typedef std::map<unsigned, PropsTy> AtPropDeclsTy;
+ AtPropDeclsTy AtProps;
public:
- AssignPropertiesTrans(MigrationPass &pass) : Pass(pass) { }
+ PropertiesRewriter(MigrationPass &pass) : Pass(pass) { }
void doTransform(ObjCImplementationDecl *D) {
- SourceManager &SM = Pass.Ctx.getSourceManager();
+ ObjCInterfaceDecl *iface = D->getClassInterface();
+ if (!iface)
+ return;
- ObjCInterfaceDecl *IFace = D->getClassInterface();
for (ObjCInterfaceDecl::prop_iterator
- I = IFace->prop_begin(), E = IFace->prop_end(); I != E; ++I) {
- ObjCPropertyDecl *propD = *I;
- unsigned loc = SM.getInstantiationLoc(propD->getAtLoc()).getRawEncoding();
- PropsTy &props = PropsMap[loc];
- props.push_back(PropData());
- props.back().PropD = propD;
- props.back().IvarD = 0;
- props.back().ShouldChangeToWeak = false;
+ propI = iface->prop_begin(),
+ propE = iface->prop_end(); propI != propE; ++propI) {
+ if (propI->getAtLoc().isInvalid())
+ continue;
+ PropsTy &props = AtProps[propI->getAtLoc().getRawEncoding()];
+ props.push_back(*propI);
}
typedef DeclContext::specific_decl_iterator<ObjCPropertyImplDecl>
for (prop_impl_iterator
I = prop_impl_iterator(D->decls_begin()),
E = prop_impl_iterator(D->decls_end()); I != E; ++I) {
- VisitObjCPropertyImplDecl(*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 (PropsMapTy::iterator
- I = PropsMap.begin(), E = PropsMap.end(); I != E; ++I) {
+ for (AtPropDeclsTy::iterator
+ I = AtProps.begin(), E = AtProps.end(); I != E; ++I) {
SourceLocation atLoc = SourceLocation::getFromRawEncoding(I->first);
PropsTy &props = I->second;
- if (shouldApplyWeakToAllProp(props)) {
- if (changeAssignToWeak(atLoc)) {
- // Couldn't add the 'weak' property attribute,
- // try adding __unsafe_unretained.
- applyUnsafeUnretained(props);
- } else {
- for (PropsTy::iterator
- PI = props.begin(), PE = props.end(); PI != PE; ++PI) {
- applyWeak(*PI);
- }
- }
- } else {
- // We should not add 'weak' attribute since not all properties need it.
- // So just add __unsafe_unretained to the ivars.
- applyUnsafeUnretained(props);
- }
+ QualType ty = getPropertyType(props);
+ if (!ty->isObjCRetainableType())
+ continue;
+ if (hasIvarWithExplicitOwnership(props))
+ continue;
+
+ Transaction Trans(Pass.TA);
+ rewriteProperty(props, atLoc);
}
}
- bool shouldApplyWeakToAllProp(PropsTy &props) {
- for (PropsTy::iterator
- PI = props.begin(), PE = props.end(); PI != PE; ++PI) {
- if (!PI->ShouldChangeToWeak)
- return false;
+private:
+ void rewriteProperty(PropsTy &props, SourceLocation atLoc) const {
+ 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) {
+ rewriteAttribute("retain", "strong", atLoc);
+ return;
}
- return true;
+
+ if (propAttrs & ObjCPropertyDecl::OBJC_PR_assign)
+ return rewriteAssign(props, atLoc);
+
+ return maybeAddWeakOrUnsafeUnretainedAttr(props, atLoc);
}
- void applyWeak(PropData &prop) {
- assert(canApplyWeak(Pass.Ctx, prop.IvarD->getType()));
+ void rewriteAssign(PropsTy &props, SourceLocation atLoc) const {
+ bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props));
+
+ bool rewroteAttr = rewriteAttribute("assign",
+ canUseWeak ? "weak" : "unsafe_unretained",
+ atLoc);
+ if (!rewroteAttr)
+ canUseWeak = false;
- Transaction Trans(Pass.TA);
- Pass.TA.insert(prop.IvarD->getLocation(), "__weak ");
- Pass.TA.clearDiagnostic(diag::err_arc_assign_property_ownership,
- prop.ArcPropAssignErrorLoc);
+ 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 applyUnsafeUnretained(PropsTy &props) {
- for (PropsTy::iterator
- PI = props.begin(), PE = props.end(); PI != PE; ++PI) {
- if (PI->ShouldChangeToWeak) {
- Transaction Trans(Pass.TA);
- Pass.TA.insert(PI->IvarD->getLocation(), "__unsafe_unretained ");
+ void maybeAddWeakOrUnsafeUnretainedAttr(PropsTy &props,
+ SourceLocation atLoc) const {
+ ObjCPropertyDecl::PropertyAttributeKind propAttrs = getPropertyAttrs(props);
+ if ((propAttrs & ObjCPropertyDecl::OBJC_PR_readonly) &&
+ hasNoBackingIvars(props))
+ return;
+
+ bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(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,
- PI->ArcPropAssignErrorLoc);
+ I->ImplD->getLocation());
+ Pass.TA.clearDiagnostic(
+ diag::err_arc_objc_property_default_assign_on_object,
+ I->ImplD->getLocation());
}
}
}
- bool VisitObjCPropertyImplDecl(ObjCPropertyImplDecl *D) {
+ bool rewriteAttribute(llvm::StringRef fromAttr, llvm::StringRef toAttr,
+ SourceLocation atLoc) const {
+ if (atLoc.isMacroID())
+ return false;
+
SourceManager &SM = Pass.Ctx.getSourceManager();
- if (D->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize)
- return true;
- ObjCPropertyDecl *propD = D->getPropertyDecl();
- if (!propD || propD->isInvalidDecl())
- return true;
- ObjCIvarDecl *ivarD = D->getPropertyIvarDecl();
- if (!ivarD || ivarD->isInvalidDecl())
- return true;
- if (!(propD->getPropertyAttributes() & ObjCPropertyDecl::OBJC_PR_assign))
- return true;
- if (isa<AttributedType>(ivarD->getType().getTypePtr()))
- return true;
- if (ivarD->getType().getLocalQualifiers().getObjCLifetime()
- != Qualifiers::OCL_Strong)
- return true;
- if (!Pass.TA.hasDiagnostic(
- diag::err_arc_assign_property_ownership, D->getLocation()))
- return true;
+ // Break down the source location.
+ std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(atLoc);
+
+ // Try to load the file buffer.
+ bool invalidTemp = false;
+ llvm::StringRef file = SM.getBufferData(locInfo.first, &invalidTemp);
+ if (invalidTemp)
+ return false;
- // There is a "error: existing ivar for assign property must be
- // __unsafe_unretained"; fix it.
+ const char *tokenBegin = file.data() + locInfo.second;
- if (!canApplyWeak(Pass.Ctx, ivarD->getType())) {
- // We will just add __unsafe_unretained to the ivar.
- Transaction Trans(Pass.TA);
- Pass.TA.insert(ivarD->getLocation(), "__unsafe_unretained ");
- Pass.TA.clearDiagnostic(
- diag::err_arc_assign_property_ownership, D->getLocation());
- } else {
- // Mark that we want the ivar to become weak.
- unsigned loc = SM.getInstantiationLoc(propD->getAtLoc()).getRawEncoding();
- PropsTy &props = PropsMap[loc];
- for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) {
- if (I->PropD == propD) {
- I->IvarD = ivarD;
- I->ShouldChangeToWeak = true;
- I->ArcPropAssignErrorLoc = D->getLocation();
- }
+ // 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 (llvm::StringRef(tok.getRawIdentifierData(), tok.getLength())
+ != "property")
+ return false;
+ lexer.LexFromRawLexer(tok);
+ if (tok.isNot(tok::l_paren)) return false;
+
+ lexer.LexFromRawLexer(tok);
+ if (tok.is(tok::r_paren))
+ return false;
+
+ while (1) {
+ if (tok.isNot(tok::raw_identifier)) return false;
+ llvm::StringRef ident(tok.getRawIdentifierData(), tok.getLength());
+ if (ident == fromAttr) {
+ Pass.TA.replaceText(tok.getLocation(), fromAttr, toAttr);
+ return true;
}
+
+ do {
+ lexer.LexFromRawLexer(tok);
+ } while (tok.isNot(tok::comma) && tok.isNot(tok::r_paren));
+ if (tok.is(tok::r_paren))
+ break;
+ lexer.LexFromRawLexer(tok);
}
- return true;
+ return false;
}
-private:
- bool changeAssignToWeak(SourceLocation atLoc) {
+ bool addAttribute(llvm::StringRef attr, SourceLocation atLoc) const {
+ if (atLoc.isMacroID())
+ return false;
+
SourceManager &SM = Pass.Ctx.getSourceManager();
// Break down the source location.
bool invalidTemp = false;
llvm::StringRef file = SM.getBufferData(locInfo.first, &invalidTemp);
if (invalidTemp)
- return true;
+ return false;
const char *tokenBegin = file.data() + locInfo.second;
file.begin(), tokenBegin, file.end());
Token tok;
lexer.LexFromRawLexer(tok);
- if (tok.isNot(tok::at)) return true;
+ if (tok.isNot(tok::at)) return false;
lexer.LexFromRawLexer(tok);
- if (tok.isNot(tok::raw_identifier)) return true;
+ if (tok.isNot(tok::raw_identifier)) return false;
if (llvm::StringRef(tok.getRawIdentifierData(), tok.getLength())
!= "property")
- return true;
+ return false;
lexer.LexFromRawLexer(tok);
- if (tok.isNot(tok::l_paren)) return true;
-
- SourceLocation LParen = tok.getLocation();
- SourceLocation assignLoc;
- bool isEmpty = false;
+ 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)) {
- isEmpty = true;
- } else {
- while (1) {
- if (tok.isNot(tok::raw_identifier)) return true;
- llvm::StringRef ident(tok.getRawIdentifierData(), tok.getLength());
- if (ident == "assign")
- assignLoc = tok.getLocation();
-
- do {
- lexer.LexFromRawLexer(tok);
- } while (tok.isNot(tok::comma) && tok.isNot(tok::r_paren));
- if (tok.is(tok::r_paren))
- break;
- lexer.LexFromRawLexer(tok);
+ 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;
+ }
+
+ bool hasIvarWithExplicitOwnership(PropsTy &props) const {
+ 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;
}
}
- Transaction Trans(Pass.TA);
- if (assignLoc.isValid())
- Pass.TA.replaceText(assignLoc, "assign", "weak");
- else
- Pass.TA.insertAfterToken(LParen, isEmpty ? "weak" : "weak, ");
- return false;
+ return false;
+ }
+
+ bool hasNoBackingIvars(PropsTy &props) const {
+ for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I)
+ if (isUserDeclared(I->IvarD))
+ return false;
+
+ return true;
+ }
+
+ bool isUserDeclared(ObjCIvarDecl *ivarD) const {
+ return ivarD && !ivarD->getSynthesize();
+ }
+
+ QualType getPropertyType(PropsTy &props) const {
+ assert(!props.empty());
+ QualType ty = props[0].PropD->getType();
+
+#ifndef NDEBUG
+ for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I)
+ assert(ty == I->PropD->getType());
+#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;
}
};
-class PropertiesChecker : public RecursiveASTVisitor<PropertiesChecker> {
+class ImplementationChecker :
+ public RecursiveASTVisitor<ImplementationChecker> {
MigrationPass &Pass;
public:
- PropertiesChecker(MigrationPass &pass) : Pass(pass) { }
+ ImplementationChecker(MigrationPass &pass) : Pass(pass) { }
bool TraverseObjCImplementationDecl(ObjCImplementationDecl *D) {
- AssignPropertiesTrans(Pass).doTransform(D);
+ PropertiesRewriter(Pass).doTransform(D);
return true;
}
};
} // anonymous namespace
-void trans::changeIvarsOfAssignProperties(MigrationPass &pass) {
- PropertiesChecker(pass).TraverseDecl(pass.Ctx.getTranslationUnitDecl());
+void trans::rewriteProperties(MigrationPass &pass) {
+ ImplementationChecker(pass).TraverseDecl(pass.Ctx.getTranslationUnitDecl());
}