//===--- TransAutoreleasePool.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.
//
//===----------------------------------------------------------------------===//
//
// rewriteAutoreleasePool:
//
// Calls to NSAutoreleasePools will be rewritten as an @autorelease scope.
//
// NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// ...
// [pool release];
// ---->
// @autorelease {
// ...
// }
//
// An NSAutoreleasePool will not be touched if:
// - There is not a corresponding -release/-drain in the same scope
// - Not all references of the NSAutoreleasePool variable can be removed
// - There is a variable that is declared inside the intended @autorelease scope
// which is also used outside it.
//
//===----------------------------------------------------------------------===//
#include "Transforms.h"
#include "Internals.h"
#include "clang/AST/ASTContext.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Sema/SemaDiagnostic.h"
#include <map>
using namespace clang;
using namespace arcmt;
using namespace trans;
namespace {
class ReleaseCollector : public RecursiveASTVisitor<ReleaseCollector> {
Decl *Dcl;
SmallVectorImpl<ObjCMessageExpr *> &Releases;
public:
ReleaseCollector(Decl *D, SmallVectorImpl<ObjCMessageExpr *> &releases)
: Dcl(D), Releases(releases) { }
bool VisitObjCMessageExpr(ObjCMessageExpr *E) {
if (!E->isInstanceMessage())
return true;
if (E->getMethodFamily() != OMF_release)
return true;
Expr *instance = E->getInstanceReceiver()->IgnoreParenCasts();
if (DeclRefExpr *DE = dyn_cast<DeclRefExpr>(instance)) {
if (DE->getDecl() == Dcl)
Releases.push_back(E);
}
return true;
}
};
}
namespace {
class AutoreleasePoolRewriter
: public RecursiveASTVisitor<AutoreleasePoolRewriter> {
public:
AutoreleasePoolRewriter(MigrationPass &pass)
: Body(0), Pass(pass) {
PoolII = &pass.Ctx.Idents.get("NSAutoreleasePool");
DrainSel = pass.Ctx.Selectors.getNullarySelector(
&pass.Ctx.Idents.get("drain"));
}
void transformBody(Stmt *body, Decl *ParentD) {
Body = body;
TraverseStmt(body);
}
~AutoreleasePoolRewriter() {
SmallVector<VarDecl *, 8> VarsToHandle;
for (std::map<VarDecl *, PoolVarInfo>::iterator
I = PoolVars.begin(), E = PoolVars.end(); I != E; ++I) {
VarDecl *var = I->first;
PoolVarInfo &info = I->second;
// Check that we can handle/rewrite all references of the pool.
clearRefsIn(info.Dcl, info.Refs);
for (SmallVectorImpl<PoolScope>::iterator
scpI = info.Scopes.begin(),
scpE = info.Scopes.end(); scpI != scpE; ++scpI) {
PoolScope &scope = *scpI;
clearRefsIn(*scope.Begin, info.Refs);
clearRefsIn(*scope.End, info.Refs);
clearRefsIn(scope.Releases.begin(), scope.Releases.end(), info.Refs);
}
// Even if one reference is not handled we will not do anything about that
// pool variable.
if (info.Refs.empty())
VarsToHandle.push_back(var);
}
for (unsigned i = 0, e = VarsToHandle.size(); i != e; ++i) {
PoolVarInfo &info = PoolVars[VarsToHandle[i]];
Transaction Trans(Pass.TA);
clearUnavailableDiags(info.Dcl);
Pass.TA.removeStmt(info.Dcl);
// Add "@autoreleasepool { }"
for (SmallVectorImpl<PoolScope>::iterator
scpI = info.Scopes.begin(),
scpE = info.Scopes.end(); scpI != scpE; ++scpI) {
PoolScope &scope = *scpI;
clearUnavailableDiags(*scope.Begin);
clearUnavailableDiags(*scope.End);
if (scope.IsFollowedBySimpleReturnStmt) {
// Include the return in the scope.
Pass.TA.replaceStmt(*scope.Begin, "@autoreleasepool {");
Pass.TA.removeStmt(*scope.End);
Stmt::child_iterator retI = scope.End;
++retI;
SourceLocation afterSemi = findLocationAfterSemi((*retI)->getLocEnd(),
Pass.Ctx);
assert(afterSemi.isValid() &&
"Didn't we check before setting IsFollowedBySimpleReturnStmt "
"to true?");
Pass.TA.insertAfterToken(afterSemi, "\n}");
Pass.TA.increaseIndentation(
SourceRange(scope.getIndentedRange().getBegin(),
(*retI)->getLocEnd()),
scope.CompoundParent->getLocStart());
} else {
Pass.TA.replaceStmt(*scope.Begin, "@autoreleasepool {");
Pass.TA.replaceStmt(*scope.End, "}");
Pass.TA.increaseIndentation(scope.getIndentedRange(),
scope.CompoundParent->getLocStart());
}
}
// Remove rest of pool var references.
for (SmallVectorImpl<PoolScope>::iterator
scpI = info.Scopes.begin(),
scpE = info.Scopes.end(); scpI != scpE; ++scpI) {
PoolScope &scope = *scpI;
for (SmallVectorImpl<ObjCMessageExpr *>::iterator
relI = scope.Releases.begin(),
relE = scope.Releases.end(); relI != relE; ++relI) {
clearUnavailableDiags(*relI);
Pass.TA.removeStmt(*relI);
}
}
}
}
bool VisitCompoundStmt(CompoundStmt *S) {
SmallVector<PoolScope, 4> Scopes;
for (Stmt::child_iterator
I = S->body_begin(), E = S->body_end(); I != E; ++I) {
Stmt *child = getEssential(*I);
if (DeclStmt *DclS = dyn_cast<DeclStmt>(child)) {
if (DclS->isSingleDecl()) {
if (VarDecl *VD = dyn_cast<VarDecl>(DclS->getSingleDecl())) {
if (isNSAutoreleasePool(VD->getType())) {
PoolVarInfo &info = PoolVars[VD];
info.Dcl = DclS;
collectRefs(VD, S, info.Refs);
// Does this statement follow the pattern:
// NSAutoreleasePool * pool = [NSAutoreleasePool new];
if (isPoolCreation(VD->getInit())) {
Scopes.push_back(PoolScope());
Scopes.back().PoolVar = VD;
Scopes.back().CompoundParent = S;
Scopes.back().Begin = I;
}
}
}
}
} else if (BinaryOperator *bop = dyn_cast<BinaryOperator>(child)) {
if (DeclRefExpr *dref = dyn_cast<DeclRefExpr>(bop->getLHS())) {
if (VarDecl *VD = dyn_cast<VarDecl>(dref->getDecl())) {
// Does this statement follow the pattern:
// pool = [NSAutoreleasePool new];
if (isNSAutoreleasePool(VD->getType()) &&
isPoolCreation(bop->getRHS())) {
Scopes.push_back(PoolScope());
Scopes.back().PoolVar = VD;
Scopes.back().CompoundParent = S;
Scopes.back().Begin = I;
}
}
}
}
if (Scopes.empty())
continue;
if (isPoolDrain(Scopes.back().PoolVar, child)) {
PoolScope &scope = Scopes.back();
scope.End = I;
handlePoolScope(scope, S);
Scopes.pop_back();
}
}
return true;
}
private:
void clearUnavailableDiags(Stmt *S) {
if (S)
Pass.TA.clearDiagnostic(diag::err_unavailable,
diag::err_unavailable_message,
S->getSourceRange());
}
struct PoolScope {
VarDecl *PoolVar;
CompoundStmt *CompoundParent;
Stmt::child_iterator Begin;
Stmt::child_iterator End;
bool IsFollowedBySimpleReturnStmt;
SmallVector<ObjCMessageExpr *, 4> Releases;
PoolScope() : PoolVar(0), CompoundParent(0), Begin(), End(),
IsFollowedBySimpleReturnStmt(false) { }
SourceRange getIndentedRange() const {
Stmt::child_iterator rangeS = Begin;
++rangeS;
if (rangeS == End)
return SourceRange();
Stmt::child_iterator rangeE = Begin;
for (Stmt::child_iterator I = rangeS; I != End; ++I)
++rangeE;
return SourceRange((*rangeS)->getLocStart(), (*rangeE)->getLocEnd());
}
};
class NameReferenceChecker : public RecursiveASTVisitor<NameReferenceChecker>{
ASTContext &Ctx;
SourceRange ScopeRange;
SourceLocation &referenceLoc, &declarationLoc;
public:
NameReferenceChecker(ASTContext &ctx, PoolScope &scope,
SourceLocation &referenceLoc,
SourceLocation &declarationLoc)
: Ctx(ctx), referenceLoc(referenceLoc),
declarationLoc(declarationLoc) {
ScopeRange = SourceRange((*scope.Begin)->getLocStart(),
(*scope.End)->getLocStart());
}
bool VisitDeclRefExpr(DeclRefExpr *E) {
return checkRef(E->getLocation(), E->getDecl()->getLocation());
}
bool VisitTypedefTypeLoc(TypedefTypeLoc TL) {
return checkRef(TL.getBeginLoc(), TL.getTypedefNameDecl()->getLocation());
}
bool VisitTagTypeLoc(TagTypeLoc TL) {
return checkRef(TL.getBeginLoc(), TL.getDecl()->getLocation());
}
private:
bool checkRef(SourceLocation refLoc, SourceLocation declLoc) {
if (isInScope(declLoc)) {
referenceLoc = refLoc;
declarationLoc = declLoc;
return false;
}
return true;
}
bool isInScope(SourceLocation loc) {
if (loc.isInvalid())
return false;
SourceManager &SM = Ctx.getSourceManager();
if (SM.isBeforeInTranslationUnit(loc, ScopeRange.getBegin()))
return false;
return SM.isBeforeInTranslationUnit(loc, ScopeRange.getEnd());
}
};
void handlePoolScope(PoolScope &scope, CompoundStmt *compoundS) {
// Check that all names declared inside the scope are not used
// outside the scope.
{
bool nameUsedOutsideScope = false;
SourceLocation referenceLoc, declarationLoc;
Stmt::child_iterator SI = scope.End, SE = compoundS->body_end();
++SI;
// Check if the autoreleasepool scope is followed by a simple return
// statement, in which case we will include the return in the scope.
if (SI != SE)
if (ReturnStmt *retS = dyn_cast<ReturnStmt>(*SI))
if ((retS->getRetValue() == 0 ||
isa<DeclRefExpr>(retS->getRetValue()->IgnoreParenCasts())) &&
findLocationAfterSemi(retS->getLocEnd(), Pass.Ctx).isValid()) {
scope.IsFollowedBySimpleReturnStmt = true;
++SI; // the return will be included in scope, don't check it.
}
for (; SI != SE; ++SI) {
nameUsedOutsideScope = !NameReferenceChecker(Pass.Ctx, scope,
referenceLoc,
declarationLoc).TraverseStmt(*SI);
if (nameUsedOutsideScope)
break;
}
// If not all references were cleared it means some variables/typenames/etc
// declared inside the pool scope are used outside of it.
// We won't try to rewrite the pool.
if (nameUsedOutsideScope) {
Pass.TA.reportError("a name is referenced outside the "
"NSAutoreleasePool scope that it was declared in", referenceLoc);
Pass.TA.reportNote("name declared here", declarationLoc);
Pass.TA.reportNote("intended @autoreleasepool scope begins here",
(*scope.Begin)->getLocStart());
Pass.TA.reportNote("intended @autoreleasepool scope ends here",
(*scope.End)->getLocStart());
return;
}
}
// Collect all releases of the pool; they will be removed.
{
ReleaseCollector releaseColl(scope.PoolVar, scope.Releases);
Stmt::child_iterator I = scope.Begin;
++I;
for (; I != scope.End; ++I)
releaseColl.TraverseStmt(*I);
}
PoolVars[scope.PoolVar].Scopes.push_back(scope);
}
bool isPoolCreation(Expr *E) {
if (!E) return false;
E = getEssential(E);
ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(E);
if (!ME) return false;
if (ME->getMethodFamily() == OMF_new &&
ME->getReceiverKind() == ObjCMessageExpr::Class &&
isNSAutoreleasePool(ME->getReceiverInterface()))
return true;
if (ME->getReceiverKind() == ObjCMessageExpr::Instance &&
ME->getMethodFamily() == OMF_init) {
Expr *rec = getEssential(ME->getInstanceReceiver());
if (ObjCMessageExpr *recME = dyn_cast_or_null<ObjCMessageExpr>(rec)) {
if (recME->getMethodFamily() == OMF_alloc &&
recME->getReceiverKind() == ObjCMessageExpr::Class &&
isNSAutoreleasePool(recME->getReceiverInterface()))
return true;
}
}
return false;
}
bool isPoolDrain(VarDecl *poolVar, Stmt *S) {
if (!S) return false;
S = getEssential(S);
ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S);
if (!ME) return false;
if (ME->getReceiverKind() == ObjCMessageExpr::Instance) {
Expr *rec = getEssential(ME->getInstanceReceiver());
if (DeclRefExpr *dref = dyn_cast<DeclRefExpr>(rec))
if (dref->getDecl() == poolVar)
return ME->getMethodFamily() == OMF_release ||
ME->getSelector() == DrainSel;
}
return false;
}
bool isNSAutoreleasePool(ObjCInterfaceDecl *IDecl) {
return IDecl && IDecl->getIdentifier() == PoolII;
}
bool isNSAutoreleasePool(QualType Ty) {
QualType pointee = Ty->getPointeeType();
if (pointee.isNull())
return false;
if (const ObjCInterfaceType *interT = pointee->getAs<ObjCInterfaceType>())
return isNSAutoreleasePool(interT->getDecl());
return false;
}
static Expr *getEssential(Expr *E) {
return cast<Expr>(getEssential((Stmt*)E));
}
static Stmt *getEssential(Stmt *S) {
if (ExprWithCleanups *EWC = dyn_cast<ExprWithCleanups>(S))
S = EWC->getSubExpr();
if (Expr *E = dyn_cast<Expr>(S))
S = E->IgnoreParenCasts();
return S;
}
Stmt *Body;
MigrationPass &Pass;
IdentifierInfo *PoolII;
Selector DrainSel;
struct PoolVarInfo {
DeclStmt *Dcl;
ExprSet Refs;
SmallVector<PoolScope, 2> Scopes;
PoolVarInfo() : Dcl(0) { }
};
std::map<VarDecl *, PoolVarInfo> PoolVars;
};
} // anonymous namespace
void trans::rewriteAutoreleasePool(MigrationPass &pass) {
BodyTransform<AutoreleasePoolRewriter> trans(pass);
trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl());
}