//===--- 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 using namespace clang; using namespace arcmt; using namespace trans; namespace { class ReleaseCollector : public RecursiveASTVisitor { Decl *Dcl; SmallVectorImpl &Releases; public: ReleaseCollector(Decl *D, SmallVectorImpl &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(instance)) { if (DE->getDecl() == Dcl) Releases.push_back(E); } return true; } }; } namespace { class AutoreleasePoolRewriter : public RecursiveASTVisitor { 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 VarsToHandle; for (std::map::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::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::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::iterator scpI = info.Scopes.begin(), scpE = info.Scopes.end(); scpI != scpE; ++scpI) { PoolScope &scope = *scpI; for (SmallVectorImpl::iterator relI = scope.Releases.begin(), relE = scope.Releases.end(); relI != relE; ++relI) { clearUnavailableDiags(*relI); Pass.TA.removeStmt(*relI); } } } } bool VisitCompoundStmt(CompoundStmt *S) { SmallVector 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(child)) { if (DclS->isSingleDecl()) { if (VarDecl *VD = dyn_cast(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(child)) { if (DeclRefExpr *dref = dyn_cast(bop->getLHS())) { if (VarDecl *VD = dyn_cast(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 Releases; PoolScope() : PoolVar(nullptr), CompoundParent(nullptr), 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{ 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(*SI)) if ((retS->getRetValue() == nullptr || isa(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(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(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(S); if (!ME) return false; if (ME->getReceiverKind() == ObjCMessageExpr::Instance) { Expr *rec = getEssential(ME->getInstanceReceiver()); if (DeclRefExpr *dref = dyn_cast(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()) return isNSAutoreleasePool(interT->getDecl()); return false; } static Expr *getEssential(Expr *E) { return cast(getEssential((Stmt*)E)); } static Stmt *getEssential(Stmt *S) { if (FullExpr *FE = dyn_cast(S)) S = FE->getSubExpr(); if (Expr *E = dyn_cast(S)) S = E->IgnoreParenCasts(); return S; } Stmt *Body; MigrationPass &Pass; IdentifierInfo *PoolII; Selector DrainSel; struct PoolVarInfo { DeclStmt *Dcl; ExprSet Refs; SmallVector Scopes; PoolVarInfo() : Dcl(nullptr) { } }; std::map PoolVars; }; } // anonymous namespace void trans::rewriteAutoreleasePool(MigrationPass &pass) { BodyTransform trans(pass); trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); }