380 lines
12 KiB
C++
380 lines
12 KiB
C++
|
//===--- TransProperties.cpp - Transformations to ARC mode ----------------===//
|
||
|
//
|
||
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||
|
// See https://llvm.org/LICENSE.txt for license information.
|
||
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
//
|
||
|
// 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 <map>
|
||
|
|
||
|
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<PropData, 2> PropsTy;
|
||
|
typedef std::map<SourceLocation, 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,
|
||
|
AtPropDeclsTy *PrevAtProps = nullptr) {
|
||
|
for (auto *Prop : D->instance_properties()) {
|
||
|
SourceLocation Loc = Prop->getAtLoc();
|
||
|
if (Loc.isInvalid())
|
||
|
continue;
|
||
|
if (PrevAtProps)
|
||
|
if (PrevAtProps->find(Loc) != PrevAtProps->end())
|
||
|
continue;
|
||
|
PropsTy &props = AtProps[Loc];
|
||
|
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<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;
|
||
|
AtPropDeclsTy::iterator findAtLoc = AtProps.find(propD->getAtLoc());
|
||
|
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 = 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) {
|
||
|
ObjCPropertyAttribute::Kind propAttrs = getPropertyAttrs(props);
|
||
|
|
||
|
if (propAttrs &
|
||
|
(ObjCPropertyAttribute::kind_copy |
|
||
|
ObjCPropertyAttribute::kind_unsafe_unretained |
|
||
|
ObjCPropertyAttribute::kind_strong | ObjCPropertyAttribute::kind_weak))
|
||
|
return;
|
||
|
|
||
|
if (propAttrs & ObjCPropertyAttribute::kind_retain) {
|
||
|
// strong is the default.
|
||
|
return doPropAction(PropAction_RetainReplacedWithStrong, props, atLoc);
|
||
|
}
|
||
|
|
||
|
bool HasIvarAssignedAPlusOneObject = hasIvarAssignedAPlusOneObject(props);
|
||
|
|
||
|
if (propAttrs & ObjCPropertyAttribute::kind_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<PlusOneAssign> {
|
||
|
ObjCIvarDecl *Ivar;
|
||
|
public:
|
||
|
PlusOneAssign(ObjCIvarDecl *D) : Ivar(D) {}
|
||
|
|
||
|
bool VisitBinaryOperator(BinaryOperator *E) {
|
||
|
if (E->getOpcode() != BO_Assign)
|
||
|
return true;
|
||
|
|
||
|
Expr *lhs = E->getLHS()->IgnoreParenImpCasts();
|
||
|
if (ObjCIvarRefExpr *RE = dyn_cast<ObjCIvarRefExpr>(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<AttributedType>(I->IvarD->getType()))
|
||
|
return true;
|
||
|
if (I->IvarD->getType().getLocalQualifiers().getObjCLifetime()
|
||
|
!= Qualifiers::OCL_Strong)
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// 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);
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
ObjCPropertyAttribute::Kind getPropertyAttrs(PropsTy &props) const {
|
||
|
assert(!props.empty());
|
||
|
ObjCPropertyAttribute::Kind 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());
|
||
|
}
|