From: John McCall Date: Thu, 10 Nov 2011 05:35:25 +0000 (+0000) Subject: There's no good reason to track temporaries in ExprWithCleanups, X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=80ee6e878a169e6255d4686a91bb696151ff229f;p=clang There's no good reason to track temporaries in ExprWithCleanups, but it is sometimes useful to track blocks. Do so. Also optimize the storage of these expressions. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@144263 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/AST/Decl.h b/include/clang/AST/Decl.h index 058e304149..2941982a28 100644 --- a/include/clang/AST/Decl.h +++ b/include/clang/AST/Decl.h @@ -1285,12 +1285,6 @@ public: Init = reinterpret_cast(defarg); } - unsigned getNumDefaultArgTemporaries() const; - CXXTemporary *getDefaultArgTemporary(unsigned i); - const CXXTemporary *getDefaultArgTemporary(unsigned i) const { - return const_cast(this)->getDefaultArgTemporary(i); - } - /// \brief Retrieve the source range that covers the entire default /// argument. SourceRange getDefaultArgRange() const; diff --git a/include/clang/AST/ExprCXX.h b/include/clang/AST/ExprCXX.h index 3998ae8a4b..4ea1ef4aa9 100644 --- a/include/clang/AST/ExprCXX.h +++ b/include/clang/AST/ExprCXX.h @@ -2179,43 +2179,58 @@ public: /// Represents an expression --- generally a full-expression --- which /// introduces cleanups to be run at the end of the sub-expression's /// evaluation. The most common source of expression-introduced -/// cleanups is temporary objects in C++, but several other C++ -/// expressions can create cleanups. +/// cleanups is temporary objects in C++, but several other kinds of +/// expressions can create cleanups, including basically every +/// call in ARC that returns an Objective-C pointer. +/// +/// This expression also tracks whether the sub-expression contains a +/// potentially-evaluated block literal. The lifetime of a block +/// literal is the extent of the enclosing scope. class ExprWithCleanups : public Expr { +public: + /// The type of objects that are kept in the cleanup. + /// It's useful to remember the set of blocks; we could also + /// remember the set of temporaries, but there's currently + /// no need. + typedef BlockDecl *CleanupObject; + +private: Stmt *SubExpr; - CXXTemporary **Temps; - unsigned NumTemps; + ExprWithCleanups(EmptyShell, unsigned NumObjects); + ExprWithCleanups(Expr *SubExpr, ArrayRef Objects); - ExprWithCleanups(ASTContext &C, Expr *SubExpr, - CXXTemporary **Temps, unsigned NumTemps); + CleanupObject *getObjectsBuffer() { + return reinterpret_cast(this + 1); + } + const CleanupObject *getObjectsBuffer() const { + return reinterpret_cast(this + 1); + } + friend class ASTStmtReader; public: - ExprWithCleanups(EmptyShell Empty) - : Expr(ExprWithCleanupsClass, Empty), - SubExpr(0), Temps(0), NumTemps(0) {} - - static ExprWithCleanups *Create(ASTContext &C, Expr *SubExpr, - CXXTemporary **Temps, - unsigned NumTemps); + static ExprWithCleanups *Create(ASTContext &C, EmptyShell empty, + unsigned numObjects); - unsigned getNumTemporaries() const { return NumTemps; } - void setNumTemporaries(ASTContext &C, unsigned N); + static ExprWithCleanups *Create(ASTContext &C, Expr *subexpr, + ArrayRef objects); - CXXTemporary *getTemporary(unsigned i) { - assert(i < NumTemps && "Index out of range"); - return Temps[i]; - } - const CXXTemporary *getTemporary(unsigned i) const { - return const_cast(this)->getTemporary(i); + ArrayRef getObjects() const { + return ArrayRef(getObjectsBuffer(), getNumObjects()); } - void setTemporary(unsigned i, CXXTemporary *T) { - assert(i < NumTemps && "Index out of range"); - Temps[i] = T; + + unsigned getNumObjects() const { return ExprWithCleanupsBits.NumObjects; } + + CleanupObject getObject(unsigned i) const { + assert(i < getNumObjects() && "Index out of range"); + return getObjects()[i]; } Expr *getSubExpr() { return cast(SubExpr); } const Expr *getSubExpr() const { return cast(SubExpr); } + + /// setSubExpr - As with any mutator of the AST, be very careful + /// when modifying an existing AST to preserve its invariants. void setSubExpr(Expr *E) { SubExpr = E; } SourceRange getSourceRange() const { diff --git a/include/clang/AST/Stmt.h b/include/clang/AST/Stmt.h index 3dc8a41714..1a1dc67550 100644 --- a/include/clang/AST/Stmt.h +++ b/include/clang/AST/Stmt.h @@ -185,6 +185,15 @@ protected: unsigned NumPreArgs : 1; }; + class ExprWithCleanupsBitfields { + friend class ExprWithCleanups; + friend class ASTStmtReader; // deserialization + + unsigned : NumExprBits; + + unsigned NumObjects : 32 - NumExprBits; + }; + class PseudoObjectExprBitfields { friend class PseudoObjectExpr; friend class ASTStmtReader; // deserialization @@ -214,6 +223,7 @@ protected: DeclRefExprBitfields DeclRefExprBits; CastExprBitfields CastExprBits; CallExprBitfields CallExprBits; + ExprWithCleanupsBitfields ExprWithCleanupsBits; PseudoObjectExprBitfields PseudoObjectExprBits; ObjCIndirectCopyRestoreExprBitfields ObjCIndirectCopyRestoreExprBits; }; diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index 4c87dd6d3c..27730f08f7 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -223,6 +223,11 @@ public: /// requires cleanups to be run at its conclusion. bool ExprNeedsCleanups; + /// ExprCleanupObjects - This is the stack of objects requiring + /// cleanup that are created by the current full expression. The + /// element type here is ExprWithCleanups::Object. + SmallVector ExprCleanupObjects; + /// \brief Stack containing information about each of the nested /// function, block, and method scopes that are currently active. /// @@ -231,10 +236,6 @@ public: /// that's used to parse every top-level function. SmallVector FunctionScopes; - /// ExprTemporaries - This is the stack of temporaries that are created by - /// the current full expression. - SmallVector ExprTemporaries; - typedef LazyVector ExtVectorDeclsType; @@ -547,9 +548,9 @@ public: /// \brief Whether the enclosing context needed a cleanup. bool ParentNeedsCleanups; - /// \brief The number of temporaries that were active when we - /// entered this expression evaluation context. - unsigned NumTemporaries; + /// \brief The number of active cleanup objects when we entered + /// this expression evaluation context. + unsigned NumCleanupObjects; /// \brief The set of declarations referenced within a /// potentially potentially-evaluated context. @@ -565,10 +566,10 @@ public: PotentiallyEmittedDiagnostics *PotentiallyDiagnosed; ExpressionEvaluationContextRecord(ExpressionEvaluationContext Context, - unsigned NumTemporaries, + unsigned NumCleanupObjects, bool ParentNeedsCleanups) : Context(Context), ParentNeedsCleanups(ParentNeedsCleanups), - NumTemporaries(NumTemporaries), + NumCleanupObjects(NumCleanupObjects), PotentiallyReferenced(0), PotentiallyDiagnosed(0) { } void addReferencedDecl(SourceLocation Loc, Decl *Decl) { diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index c766577ab0..095491aafe 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -1412,21 +1412,6 @@ Expr *ParmVarDecl::getDefaultArg() { return Arg; } -unsigned ParmVarDecl::getNumDefaultArgTemporaries() const { - if (const ExprWithCleanups *E = dyn_cast(getInit())) - return E->getNumTemporaries(); - - return 0; -} - -CXXTemporary *ParmVarDecl::getDefaultArgTemporary(unsigned i) { - assert(getNumDefaultArgTemporaries() && - "Default arguments does not have any temporaries!"); - - ExprWithCleanups *E = cast(getInit()); - return E->getTemporary(i); -} - SourceRange ParmVarDecl::getDefaultArgRange() const { if (const Expr *E = getInit()) return E->getSourceRange(); diff --git a/lib/AST/ExprCXX.cpp b/lib/AST/ExprCXX.cpp index ad5ec8b833..99e9002a48 100644 --- a/lib/AST/ExprCXX.cpp +++ b/lib/AST/ExprCXX.cpp @@ -695,35 +695,37 @@ CXXConstructExpr::CXXConstructExpr(ASTContext &C, StmtClass SC, QualType T, } } -ExprWithCleanups::ExprWithCleanups(ASTContext &C, - Expr *subexpr, - CXXTemporary **temps, - unsigned numtemps) +ExprWithCleanups::ExprWithCleanups(Expr *subexpr, + ArrayRef objects) : Expr(ExprWithCleanupsClass, subexpr->getType(), subexpr->getValueKind(), subexpr->getObjectKind(), subexpr->isTypeDependent(), subexpr->isValueDependent(), subexpr->isInstantiationDependent(), subexpr->containsUnexpandedParameterPack()), - SubExpr(subexpr), Temps(0), NumTemps(0) { - if (numtemps) { - setNumTemporaries(C, numtemps); - for (unsigned i = 0; i != numtemps; ++i) - Temps[i] = temps[i]; - } + SubExpr(subexpr) { + ExprWithCleanupsBits.NumObjects = objects.size(); + for (unsigned i = 0, e = objects.size(); i != e; ++i) + getObjectsBuffer()[i] = objects[i]; } -void ExprWithCleanups::setNumTemporaries(ASTContext &C, unsigned N) { - assert(Temps == 0 && "Cannot resize with this"); - NumTemps = N; - Temps = new (C) CXXTemporary*[NumTemps]; +ExprWithCleanups *ExprWithCleanups::Create(ASTContext &C, Expr *subexpr, + ArrayRef objects) { + size_t size = sizeof(ExprWithCleanups) + + objects.size() * sizeof(CleanupObject); + void *buffer = C.Allocate(size, llvm::alignOf()); + return new (buffer) ExprWithCleanups(subexpr, objects); } +ExprWithCleanups::ExprWithCleanups(EmptyShell empty, unsigned numObjects) + : Expr(ExprWithCleanupsClass, empty) { + ExprWithCleanupsBits.NumObjects = numObjects; +} -ExprWithCleanups *ExprWithCleanups::Create(ASTContext &C, - Expr *SubExpr, - CXXTemporary **Temps, - unsigned NumTemps) { - return new (C) ExprWithCleanups(C, SubExpr, Temps, NumTemps); +ExprWithCleanups *ExprWithCleanups::Create(ASTContext &C, EmptyShell empty, + unsigned numObjects) { + size_t size = sizeof(ExprWithCleanups) + numObjects * sizeof(CleanupObject); + void *buffer = C.Allocate(size, llvm::alignOf()); + return new (buffer) ExprWithCleanups(empty, numObjects); } CXXUnresolvedConstructExpr::CXXUnresolvedConstructExpr(TypeSourceInfo *Type, diff --git a/lib/AST/StmtDumper.cpp b/lib/AST/StmtDumper.cpp index 2248343199..bfbacf4f30 100644 --- a/lib/AST/StmtDumper.cpp +++ b/lib/AST/StmtDumper.cpp @@ -590,10 +590,12 @@ void StmtDumper::VisitCXXBindTemporaryExpr(CXXBindTemporaryExpr *Node) { void StmtDumper::VisitExprWithCleanups(ExprWithCleanups *Node) { DumpExpr(Node); ++IndentLevel; - for (unsigned i = 0, e = Node->getNumTemporaries(); i != e; ++i) { + for (unsigned i = 0, e = Node->getNumObjects(); i != e; ++i) { OS << "\n"; Indent(); - DumpCXXTemporary(Node->getTemporary(i)); + OS << "(cleanup "; + DumpDeclRef(Node->getObject(i)); + OS << ")"; } --IndentLevel; } diff --git a/lib/Sema/Sema.cpp b/lib/Sema/Sema.cpp index 7d28026402..d36b67aed8 100644 --- a/lib/Sema/Sema.cpp +++ b/lib/Sema/Sema.cpp @@ -97,7 +97,7 @@ Sema::Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer, CollectStats(false), ExternalSource(0), CodeCompleter(CodeCompleter), CurContext(0), OriginalLexicalContext(0), PackContext(0), MSStructPragmaOn(false), VisContext(0), - ExprNeedsCleanups(0), LateTemplateParser(0), OpaqueParser(0), + ExprNeedsCleanups(false), LateTemplateParser(0), OpaqueParser(0), IdResolver(pp), CXXTypeInfoDecl(0), MSVCGuidDecl(0), GlobalNewDeleteDeclared(false), ObjCShouldCallSuperDealloc(false), diff --git a/lib/Sema/SemaChecking.cpp b/lib/Sema/SemaChecking.cpp index 6fbdb5163c..ad290fa568 100644 --- a/lib/Sema/SemaChecking.cpp +++ b/lib/Sema/SemaChecking.cpp @@ -2676,6 +2676,9 @@ static Expr *EvalAddr(Expr *E, SmallVectorImpl &refVars) { case Stmt::AddrLabelExprClass: return E; // address of label. + case Stmt::ExprWithCleanupsClass: + return EvalAddr(cast(E)->getSubExpr(), refVars); + // For casts, we need to handle conversions from arrays to // pointer values, and pointer-to-pointer conversions. case Stmt::ImplicitCastExprClass: @@ -2752,6 +2755,9 @@ do { return NULL; } + case Stmt::ExprWithCleanupsClass: + return EvalVal(cast(E)->getSubExpr(), refVars); + case Stmt::DeclRefExprClass: { // When we hit a DeclRefExpr we are looking at code that refers to a // variable's name. If it's not a reference variable we check if it has diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index 8419cbebc4..f59fa5bb47 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -7190,8 +7190,7 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body, // deletion in some later function. if (PP.getDiagnostics().hasErrorOccurred() || PP.getDiagnostics().getSuppressAllDiagnostics()) { - ExprTemporaries.clear(); - ExprNeedsCleanups = false; + DiscardCleanupsInEvaluationContext(); } else if (!isa(dcl)) { // Since the body is valid, issue any analysis-based warnings that are // enabled. @@ -7202,7 +7201,7 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body, !CheckConstexprFunctionBody(FD, Body)) FD->setInvalidDecl(); - assert(ExprTemporaries.empty() && "Leftover temporaries in function"); + assert(ExprCleanupObjects.empty() && "Leftover temporaries in function"); assert(!ExprNeedsCleanups && "Unaccounted cleanups in function"); } @@ -7215,8 +7214,7 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body, // been leftover. This ensures that these temporaries won't be picked up for // deletion in some later function. if (getDiagnostics().hasErrorOccurred()) { - ExprTemporaries.clear(); - ExprNeedsCleanups = false; + DiscardCleanupsInEvaluationContext(); } return dcl; diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp index 4ba294cb8a..bc422db8b9 100644 --- a/lib/Sema/SemaExpr.cpp +++ b/lib/Sema/SemaExpr.cpp @@ -3291,12 +3291,18 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc, // be properly destroyed. // FIXME: We should really be rebuilding the default argument with new // bound temporaries; see the comment in PR5810. - for (unsigned i = 0, e = Param->getNumDefaultArgTemporaries(); i != e; ++i) { - CXXTemporary *Temporary = Param->getDefaultArgTemporary(i); - MarkDeclarationReferenced(Param->getDefaultArg()->getLocStart(), - const_cast(Temporary->getDestructor())); - ExprTemporaries.push_back(Temporary); + // We don't need to do that with block decls, though, because + // blocks in default argument expression can never capture anything. + if (isa(Param->getInit())) { + // Set the "needs cleanups" bit regardless of whether there are + // any explicit objects. ExprNeedsCleanups = true; + + // Append all the objects to the cleanup list. Right now, this + // should always be a no-op, because blocks in default argument + // expressions should never be able to capture anything. + assert(!cast(Param->getInit())->getNumObjects() && + "default argument expression has capturing blocks?"); } // We already type-checked the argument, so we know it works. @@ -8828,6 +8834,13 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc, const AnalysisBasedWarnings::Policy &WP = AnalysisWarnings.getDefaultPolicy(); PopFunctionOrBlockScope(&WP, Result->getBlockDecl(), Result); + // If the block isn't obviously global, i.e. it captures anything at + // all, mark this full-expression as needing a cleanup. + if (Result->getBlockDecl()->hasCaptures()) { + ExprCleanupObjects.push_back(Result->getBlockDecl()); + ExprNeedsCleanups = true; + } + return Owned(Result); } @@ -9158,7 +9171,7 @@ void Sema::PushExpressionEvaluationContext(ExpressionEvaluationContext NewContext) { ExprEvalContexts.push_back( ExpressionEvaluationContextRecord(NewContext, - ExprTemporaries.size(), + ExprCleanupObjects.size(), ExprNeedsCleanups)); ExprNeedsCleanups = false; } @@ -9195,8 +9208,8 @@ void Sema::PopExpressionEvaluationContext() { // the expression in that context: they aren't relevant because they // will never be constructed. if (Rec.Context == Unevaluated) { - ExprTemporaries.erase(ExprTemporaries.begin() + Rec.NumTemporaries, - ExprTemporaries.end()); + ExprCleanupObjects.erase(ExprCleanupObjects.begin() + Rec.NumCleanupObjects, + ExprCleanupObjects.end()); ExprNeedsCleanups = Rec.ParentNeedsCleanups; // Otherwise, merge the contexts together. @@ -9209,9 +9222,9 @@ void Sema::PopExpressionEvaluationContext() { } void Sema::DiscardCleanupsInEvaluationContext() { - ExprTemporaries.erase( - ExprTemporaries.begin() + ExprEvalContexts.back().NumTemporaries, - ExprTemporaries.end()); + ExprCleanupObjects.erase( + ExprCleanupObjects.begin() + ExprEvalContexts.back().NumCleanupObjects, + ExprCleanupObjects.end()); ExprNeedsCleanups = false; } @@ -9454,6 +9467,12 @@ namespace { Inherited::VisitMemberExpr(E); } + void VisitCXXBindTemporaryExpr(CXXBindTemporaryExpr *E) { + S.MarkDeclarationReferenced(E->getLocStart(), + const_cast(E->getTemporary()->getDestructor())); + Visit(E->getSubExpr()); + } + void VisitCXXNewExpr(CXXNewExpr *E) { if (E->getConstructor()) S.MarkDeclarationReferenced(E->getLocStart(), E->getConstructor()); diff --git a/lib/Sema/SemaExprCXX.cpp b/lib/Sema/SemaExprCXX.cpp index 7249400965..6c533c4ed4 100644 --- a/lib/Sema/SemaExprCXX.cpp +++ b/lib/Sema/SemaExprCXX.cpp @@ -4122,37 +4122,37 @@ ExprResult Sema::MaybeBindToTemporary(Expr *E) { PDiag(diag::err_access_dtor_temp) << E->getType()); - ExprTemporaries.push_back(Temp); + // We need a cleanup, but we don't need to remember the temporary. ExprNeedsCleanups = true; } return Owned(CXXBindTemporaryExpr::Create(Context, Temp, E)); } +ExprResult +Sema::MaybeCreateExprWithCleanups(ExprResult SubExpr) { + if (SubExpr.isInvalid()) + return ExprError(); + + return Owned(MaybeCreateExprWithCleanups(SubExpr.take())); +} + Expr *Sema::MaybeCreateExprWithCleanups(Expr *SubExpr) { assert(SubExpr && "sub expression can't be null!"); - unsigned FirstTemporary = ExprEvalContexts.back().NumTemporaries; - assert(ExprTemporaries.size() >= FirstTemporary); - assert(ExprNeedsCleanups || ExprTemporaries.size() == FirstTemporary); + unsigned FirstCleanup = ExprEvalContexts.back().NumCleanupObjects; + assert(ExprCleanupObjects.size() >= FirstCleanup); + assert(ExprNeedsCleanups || ExprCleanupObjects.size() == FirstCleanup); if (!ExprNeedsCleanups) return SubExpr; - Expr *E = ExprWithCleanups::Create(Context, SubExpr, - ExprTemporaries.begin() + FirstTemporary, - ExprTemporaries.size() - FirstTemporary); - ExprTemporaries.erase(ExprTemporaries.begin() + FirstTemporary, - ExprTemporaries.end()); - ExprNeedsCleanups = false; + ArrayRef Cleanups + = llvm::makeArrayRef(ExprCleanupObjects.begin() + FirstCleanup, + ExprCleanupObjects.size() - FirstCleanup); - return E; -} - -ExprResult -Sema::MaybeCreateExprWithCleanups(ExprResult SubExpr) { - if (SubExpr.isInvalid()) - return ExprError(); + Expr *E = ExprWithCleanups::Create(Context, SubExpr, Cleanups); + DiscardCleanupsInEvaluationContext(); - return Owned(MaybeCreateExprWithCleanups(SubExpr.take())); + return E; } Stmt *Sema::MaybeCreateStmtWithCleanups(Stmt *SubStmt) { diff --git a/lib/Serialization/ASTReaderStmt.cpp b/lib/Serialization/ASTReaderStmt.cpp index ff306b06c9..cd51b06a5a 100644 --- a/lib/Serialization/ASTReaderStmt.cpp +++ b/lib/Serialization/ASTReaderStmt.cpp @@ -1195,13 +1195,13 @@ void ASTStmtReader::VisitCXXPseudoDestructorExpr(CXXPseudoDestructorExpr *E) { void ASTStmtReader::VisitExprWithCleanups(ExprWithCleanups *E) { VisitExpr(E); - unsigned NumTemps = Record[Idx++]; - if (NumTemps) { - E->setNumTemporaries(Reader.getContext(), NumTemps); - for (unsigned i = 0; i != NumTemps; ++i) - E->setTemporary(i, Reader.ReadCXXTemporary(F, Record, Idx)); - } - E->setSubExpr(Reader.ReadSubExpr()); + + unsigned NumObjects = Record[Idx++]; + assert(NumObjects == E->getNumObjects()); + for (unsigned i = 0; i != NumObjects; ++i) + E->getObjectsBuffer()[i] = ReadDeclAs(Record, Idx); + + E->SubExpr = Reader.ReadSubExpr(); } void @@ -1974,7 +1974,8 @@ Stmt *ASTReader::ReadStmtFromStream(Module &F) { break; case EXPR_EXPR_WITH_CLEANUPS: - S = new (Context) ExprWithCleanups(Empty); + S = ExprWithCleanups::Create(Context, Empty, + Record[ASTStmtReader::NumExprFields]); break; case EXPR_CXX_DEPENDENT_SCOPE_MEMBER: diff --git a/lib/Serialization/ASTWriterStmt.cpp b/lib/Serialization/ASTWriterStmt.cpp index a8c76b5114..e25a20b353 100644 --- a/lib/Serialization/ASTWriterStmt.cpp +++ b/lib/Serialization/ASTWriterStmt.cpp @@ -1180,9 +1180,9 @@ void ASTStmtWriter::VisitCXXPseudoDestructorExpr(CXXPseudoDestructorExpr *E) { void ASTStmtWriter::VisitExprWithCleanups(ExprWithCleanups *E) { VisitExpr(E); - Record.push_back(E->getNumTemporaries()); - for (unsigned i = 0, e = E->getNumTemporaries(); i != e; ++i) - Writer.AddCXXTemporary(E->getTemporary(i), Record); + Record.push_back(E->getNumObjects()); + for (unsigned i = 0, e = E->getNumObjects(); i != e; ++i) + Writer.AddDeclRef(E->getObject(i), Record); Writer.AddStmt(E->getSubExpr()); Code = serialization::EXPR_EXPR_WITH_CLEANUPS;