//===--- TransRetainReleaseDealloc.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 // //===----------------------------------------------------------------------===// // // removeRetainReleaseDealloc: // // Removes retain/release/autorelease/dealloc messages. // // return [[foo retain] autorelease]; // ----> // return foo; // //===----------------------------------------------------------------------===// #include "Transforms.h" #include "Internals.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ParentMap.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" #include "clang/Sema/SemaDiagnostic.h" #include "llvm/ADT/StringSwitch.h" using namespace clang; using namespace arcmt; using namespace trans; namespace { class RetainReleaseDeallocRemover : public RecursiveASTVisitor { Stmt *Body; MigrationPass &Pass; ExprSet Removables; std::unique_ptr StmtMap; Selector DelegateSel, FinalizeSel; public: RetainReleaseDeallocRemover(MigrationPass &pass) : Body(nullptr), Pass(pass) { DelegateSel = Pass.Ctx.Selectors.getNullarySelector(&Pass.Ctx.Idents.get("delegate")); FinalizeSel = Pass.Ctx.Selectors.getNullarySelector(&Pass.Ctx.Idents.get("finalize")); } void transformBody(Stmt *body, Decl *ParentD) { Body = body; collectRemovables(body, Removables); StmtMap.reset(new ParentMap(body)); TraverseStmt(body); } bool VisitObjCMessageExpr(ObjCMessageExpr *E) { switch (E->getMethodFamily()) { default: if (E->isInstanceMessage() && E->getSelector() == FinalizeSel) break; return true; case OMF_autorelease: if (isRemovable(E)) { if (!isCommonUnusedAutorelease(E)) { // An unused autorelease is badness. If we remove it the receiver // will likely die immediately while previously it was kept alive // by the autorelease pool. This is bad practice in general, leave it // and emit an error to force the user to restructure their code. Pass.TA.reportError( "it is not safe to remove an unused 'autorelease' " "message; its receiver may be destroyed immediately", E->getBeginLoc(), E->getSourceRange()); return true; } } // Pass through. [[fallthrough]]; case OMF_retain: case OMF_release: if (E->getReceiverKind() == ObjCMessageExpr::Instance) if (Expr *rec = E->getInstanceReceiver()) { rec = rec->IgnoreParenImpCasts(); if (rec->getType().getObjCLifetime() == Qualifiers::OCL_ExplicitNone && (E->getMethodFamily() != OMF_retain || isRemovable(E))) { std::string err = "it is not safe to remove '"; err += E->getSelector().getAsString() + "' message on " "an __unsafe_unretained type"; Pass.TA.reportError(err, rec->getBeginLoc()); return true; } if (isGlobalVar(rec) && (E->getMethodFamily() != OMF_retain || isRemovable(E))) { std::string err = "it is not safe to remove '"; err += E->getSelector().getAsString() + "' message on " "a global variable"; Pass.TA.reportError(err, rec->getBeginLoc()); return true; } if (E->getMethodFamily() == OMF_release && isDelegateMessage(rec)) { Pass.TA.reportError( "it is not safe to remove 'retain' " "message on the result of a 'delegate' message; " "the object that was passed to 'setDelegate:' may not be " "properly retained", rec->getBeginLoc()); return true; } } break; case OMF_dealloc: break; } switch (E->getReceiverKind()) { default: return true; case ObjCMessageExpr::SuperInstance: { Transaction Trans(Pass.TA); clearDiagnostics(E->getSelectorLoc(0)); if (tryRemoving(E)) return true; Pass.TA.replace(E->getSourceRange(), "self"); return true; } case ObjCMessageExpr::Instance: break; } Expr *rec = E->getInstanceReceiver(); if (!rec) return true; Transaction Trans(Pass.TA); clearDiagnostics(E->getSelectorLoc(0)); ObjCMessageExpr *Msg = E; Expr *RecContainer = Msg; SourceRange RecRange = rec->getSourceRange(); checkForGCDOrXPC(Msg, RecContainer, rec, RecRange); if (Msg->getMethodFamily() == OMF_release && isRemovable(RecContainer) && isInAtFinally(RecContainer)) { // Change the -release to "receiver = nil" in a finally to avoid a leak // when an exception is thrown. Pass.TA.replace(RecContainer->getSourceRange(), RecRange); std::string str = " = "; str += getNilString(Pass); Pass.TA.insertAfterToken(RecRange.getEnd(), str); return true; } if (hasSideEffects(rec, Pass.Ctx) || !tryRemoving(RecContainer)) Pass.TA.replace(RecContainer->getSourceRange(), RecRange); return true; } private: /// Checks for idioms where an unused -autorelease is common. /// /// Returns true for this idiom which is common in property /// setters: /// /// [backingValue autorelease]; /// backingValue = [newValue retain]; // in general a +1 assign /// /// For these as well: /// /// [[var retain] autorelease]; /// return var; /// bool isCommonUnusedAutorelease(ObjCMessageExpr *E) { return isPlusOneAssignBeforeOrAfterAutorelease(E) || isReturnedAfterAutorelease(E); } bool isReturnedAfterAutorelease(ObjCMessageExpr *E) { Expr *Rec = E->getInstanceReceiver(); if (!Rec) return false; Decl *RefD = getReferencedDecl(Rec); if (!RefD) return false; Stmt *nextStmt = getNextStmt(E); if (!nextStmt) return false; // Check for "return ;". if (ReturnStmt *RetS = dyn_cast(nextStmt)) return RefD == getReferencedDecl(RetS->getRetValue()); return false; } bool isPlusOneAssignBeforeOrAfterAutorelease(ObjCMessageExpr *E) { Expr *Rec = E->getInstanceReceiver(); if (!Rec) return false; Decl *RefD = getReferencedDecl(Rec); if (!RefD) return false; Stmt *prevStmt, *nextStmt; std::tie(prevStmt, nextStmt) = getPreviousAndNextStmt(E); return isPlusOneAssignToVar(prevStmt, RefD) || isPlusOneAssignToVar(nextStmt, RefD); } bool isPlusOneAssignToVar(Stmt *S, Decl *RefD) { if (!S) return false; // Check for "RefD = [+1 retained object];". if (BinaryOperator *Bop = dyn_cast(S)) { return (RefD == getReferencedDecl(Bop->getLHS())) && isPlusOneAssign(Bop); } if (DeclStmt *DS = dyn_cast(S)) { if (DS->isSingleDecl() && DS->getSingleDecl() == RefD) { if (VarDecl *VD = dyn_cast(RefD)) return isPlusOne(VD->getInit()); } return false; } return false; } Stmt *getNextStmt(Expr *E) { return getPreviousAndNextStmt(E).second; } std::pair getPreviousAndNextStmt(Expr *E) { Stmt *prevStmt = nullptr, *nextStmt = nullptr; if (!E) return std::make_pair(prevStmt, nextStmt); Stmt *OuterS = E, *InnerS; do { InnerS = OuterS; OuterS = StmtMap->getParent(InnerS); } while (OuterS && (isa(OuterS) || isa(OuterS) || isa(OuterS))); if (!OuterS) return std::make_pair(prevStmt, nextStmt); Stmt::child_iterator currChildS = OuterS->child_begin(); Stmt::child_iterator childE = OuterS->child_end(); Stmt::child_iterator prevChildS = childE; for (; currChildS != childE; ++currChildS) { if (*currChildS == InnerS) break; prevChildS = currChildS; } if (prevChildS != childE) { prevStmt = *prevChildS; if (auto *E = dyn_cast_or_null(prevStmt)) prevStmt = E->IgnoreImplicit(); } if (currChildS == childE) return std::make_pair(prevStmt, nextStmt); ++currChildS; if (currChildS == childE) return std::make_pair(prevStmt, nextStmt); nextStmt = *currChildS; if (auto *E = dyn_cast_or_null(nextStmt)) nextStmt = E->IgnoreImplicit(); return std::make_pair(prevStmt, nextStmt); } Decl *getReferencedDecl(Expr *E) { if (!E) return nullptr; E = E->IgnoreParenCasts(); if (ObjCMessageExpr *ME = dyn_cast(E)) { switch (ME->getMethodFamily()) { case OMF_copy: case OMF_autorelease: case OMF_release: case OMF_retain: return getReferencedDecl(ME->getInstanceReceiver()); default: return nullptr; } } if (DeclRefExpr *DRE = dyn_cast(E)) return DRE->getDecl(); if (MemberExpr *ME = dyn_cast(E)) return ME->getMemberDecl(); if (ObjCIvarRefExpr *IRE = dyn_cast(E)) return IRE->getDecl(); return nullptr; } /// Check if the retain/release is due to a GCD/XPC macro that are /// defined as: /// /// #define dispatch_retain(object) ({ dispatch_object_t _o = (object); _dispatch_object_validate(_o); (void)[_o retain]; }) /// #define dispatch_release(object) ({ dispatch_object_t _o = (object); _dispatch_object_validate(_o); [_o release]; }) /// #define xpc_retain(object) ({ xpc_object_t _o = (object); _xpc_object_validate(_o); [_o retain]; }) /// #define xpc_release(object) ({ xpc_object_t _o = (object); _xpc_object_validate(_o); [_o release]; }) /// /// and return the top container which is the StmtExpr and the macro argument /// expression. void checkForGCDOrXPC(ObjCMessageExpr *Msg, Expr *&RecContainer, Expr *&Rec, SourceRange &RecRange) { SourceLocation Loc = Msg->getExprLoc(); if (!Loc.isMacroID()) return; SourceManager &SM = Pass.Ctx.getSourceManager(); StringRef MacroName = Lexer::getImmediateMacroName(Loc, SM, Pass.Ctx.getLangOpts()); bool isGCDOrXPC = llvm::StringSwitch(MacroName) .Case("dispatch_retain", true) .Case("dispatch_release", true) .Case("xpc_retain", true) .Case("xpc_release", true) .Default(false); if (!isGCDOrXPC) return; StmtExpr *StmtE = nullptr; Stmt *S = Msg; while (S) { if (StmtExpr *SE = dyn_cast(S)) { StmtE = SE; break; } S = StmtMap->getParent(S); } if (!StmtE) return; Stmt::child_range StmtExprChild = StmtE->children(); if (StmtExprChild.begin() == StmtExprChild.end()) return; auto *CompS = dyn_cast_or_null(*StmtExprChild.begin()); if (!CompS) return; Stmt::child_range CompStmtChild = CompS->children(); if (CompStmtChild.begin() == CompStmtChild.end()) return; auto *DeclS = dyn_cast_or_null(*CompStmtChild.begin()); if (!DeclS) return; if (!DeclS->isSingleDecl()) return; VarDecl *VD = dyn_cast_or_null(DeclS->getSingleDecl()); if (!VD) return; Expr *Init = VD->getInit(); if (!Init) return; RecContainer = StmtE; Rec = Init->IgnoreParenImpCasts(); if (FullExpr *FE = dyn_cast(Rec)) Rec = FE->getSubExpr()->IgnoreParenImpCasts(); RecRange = Rec->getSourceRange(); if (SM.isMacroArgExpansion(RecRange.getBegin())) RecRange.setBegin(SM.getImmediateSpellingLoc(RecRange.getBegin())); if (SM.isMacroArgExpansion(RecRange.getEnd())) RecRange.setEnd(SM.getImmediateSpellingLoc(RecRange.getEnd())); } void clearDiagnostics(SourceLocation loc) const { Pass.TA.clearDiagnostic(diag::err_arc_illegal_explicit_message, diag::err_unavailable, diag::err_unavailable_message, loc); } bool isDelegateMessage(Expr *E) const { if (!E) return false; E = E->IgnoreParenCasts(); // Also look through property-getter sugar. if (PseudoObjectExpr *pseudoOp = dyn_cast(E)) E = pseudoOp->getResultExpr()->IgnoreImplicit(); if (ObjCMessageExpr *ME = dyn_cast(E)) return (ME->isInstanceMessage() && ME->getSelector() == DelegateSel); return false; } bool isInAtFinally(Expr *E) const { assert(E); Stmt *S = E; while (S) { if (isa(S)) return true; S = StmtMap->getParent(S); } return false; } bool isRemovable(Expr *E) const { return Removables.count(E); } bool tryRemoving(Expr *E) const { if (isRemovable(E)) { Pass.TA.removeStmt(E); return true; } Stmt *parent = StmtMap->getParent(E); if (ImplicitCastExpr *castE = dyn_cast_or_null(parent)) return tryRemoving(castE); if (ParenExpr *parenE = dyn_cast_or_null(parent)) return tryRemoving(parenE); if (BinaryOperator * bopE = dyn_cast_or_null(parent)) { if (bopE->getOpcode() == BO_Comma && bopE->getLHS() == E && isRemovable(bopE)) { Pass.TA.replace(bopE->getSourceRange(), bopE->getRHS()->getSourceRange()); return true; } } return false; } }; } // anonymous namespace void trans::removeRetainReleaseDeallocFinalize(MigrationPass &pass) { BodyTransform trans(pass); trans.TraverseDecl(pass.Ctx.getTranslationUnitDecl()); }