435 lines
14 KiB
C++
435 lines
14 KiB
C++
|
//===--- TransAutoreleasePool.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
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
//
|
||
|
// 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(nullptr), 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)->getEndLoc(), 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)->getEndLoc()),
|
||
|
scope.CompoundParent->getBeginLoc());
|
||
|
} else {
|
||
|
Pass.TA.replaceStmt(*scope.Begin, "@autoreleasepool {");
|
||
|
Pass.TA.replaceStmt(*scope.End, "}");
|
||
|
Pass.TA.increaseIndentation(scope.getIndentedRange(),
|
||
|
scope.CompoundParent->getBeginLoc());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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(nullptr), CompoundParent(nullptr), 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)->getBeginLoc(), (*rangeE)->getEndLoc());
|
||
|
}
|
||
|
};
|
||
|
|
||
|
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)->getBeginLoc(),
|
||
|
(*scope.End)->getBeginLoc());
|
||
|
}
|
||
|
|
||
|
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() == nullptr ||
|
||
|
isa<DeclRefExpr>(retS->getRetValue()->IgnoreParenCasts())) &&
|
||
|
findLocationAfterSemi(retS->getEndLoc(), 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)->getBeginLoc());
|
||
|
Pass.TA.reportNote("intended @autoreleasepool scope ends here",
|
||
|
(*scope.End)->getBeginLoc());
|
||
|
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 (FullExpr *FE = dyn_cast<FullExpr>(S))
|
||
|
S = FE->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(nullptr) { }
|
||
|
};
|
||
|
|
||
|
std::map<VarDecl *, PoolVarInfo> PoolVars;
|
||
|
};
|
||
|
|
||
|
} // anonymous namespace
|
||
|
|
||
|
void trans::rewriteAutoreleasePool(MigrationPass &pass) {
|
||
|
BodyTransform<AutoreleasePoolRewriter> trans(pass);
|
||
|
trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl());
|
||
|
}
|