From 91553bbacd030d6cc49db412873febcf2114d6d8 Mon Sep 17 00:00:00 2001 From: "kmillikin@chromium.org" Date: Fri, 12 Aug 2011 10:52:49 +0000 Subject: [PATCH] Simplify handling of exits from with and catch. Remove the try/finally used for with and catch. Instead of using try/finally to handle break and continue from with or catch, statically track nesting dept and clean up when compiling break or continue. And instead of using try/finally to handle throw to handler in a frame whose pc is inside a with or catch, store the context that the handler should run in in the handler itself. BUG= TEST= Review URL: http://codereview.chromium.org/7618007 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@8922 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/arm/frames-arm.h | 11 ++- src/arm/macro-assembler-arm.cc | 86 ++++++++--------- src/ast.cc | 2 +- src/ast.h | 12 ++- src/frames-inl.h | 9 +- src/frames.h | 3 +- src/full-codegen.cc | 78 +++++++++++---- src/full-codegen.h | 53 ++++++---- src/hydrogen.cc | 5 +- src/ia32/frames-ia32.h | 11 ++- src/ia32/macro-assembler-ia32.cc | 53 ++++++---- src/parser.cc | 88 ++++------------- src/parser.h | 1 - src/prettyprinter.cc | 19 ++-- src/rewriter.cc | 10 +- src/x64/frames-x64.h | 13 +-- src/x64/macro-assembler-x64.cc | 69 ++++++------- test/mjsunit/with-leave.js | 161 ++++++++++++++++++++++++++++++- 18 files changed, 435 insertions(+), 249 deletions(-) diff --git a/src/arm/frames-arm.h b/src/arm/frames-arm.h index 84e108b3d..26bbd82d0 100644 --- a/src/arm/frames-arm.h +++ b/src/arm/frames-arm.h @@ -1,4 +1,4 @@ -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2011 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: @@ -93,10 +93,11 @@ static const int kNumSafepointSavedRegisters = class StackHandlerConstants : public AllStatic { public: - static const int kNextOffset = 0 * kPointerSize; - static const int kStateOffset = 1 * kPointerSize; - static const int kFPOffset = 2 * kPointerSize; - static const int kPCOffset = 3 * kPointerSize; + static const int kNextOffset = 0 * kPointerSize; + static const int kStateOffset = 1 * kPointerSize; + static const int kContextOffset = 2 * kPointerSize; + static const int kFPOffset = 3 * kPointerSize; + static const int kPCOffset = 4 * kPointerSize; static const int kSize = kPCOffset + kPointerSize; }; diff --git a/src/arm/macro-assembler-arm.cc b/src/arm/macro-assembler-arm.cc index c34a57920..88477bb7f 100644 --- a/src/arm/macro-assembler-arm.cc +++ b/src/arm/macro-assembler-arm.cc @@ -1102,7 +1102,13 @@ void MacroAssembler::DebugBreak() { void MacroAssembler::PushTryHandler(CodeLocation try_location, HandlerType type) { // Adjust this code if not the case. - ASSERT(StackHandlerConstants::kSize == 4 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kSize == 5 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kStateOffset == 1 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kContextOffset == 2 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kFPOffset == 3 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kPCOffset == 4 * kPointerSize); + // The pc (return address) is passed in register lr. if (try_location == IN_JAVASCRIPT) { if (type == TRY_CATCH_HANDLER) { @@ -1110,14 +1116,10 @@ void MacroAssembler::PushTryHandler(CodeLocation try_location, } else { mov(r3, Operand(StackHandler::TRY_FINALLY)); } - ASSERT(StackHandlerConstants::kStateOffset == 1 * kPointerSize - && StackHandlerConstants::kFPOffset == 2 * kPointerSize - && StackHandlerConstants::kPCOffset == 3 * kPointerSize); - stm(db_w, sp, r3.bit() | fp.bit() | lr.bit()); + stm(db_w, sp, r3.bit() | cp.bit() | fp.bit() | lr.bit()); // Save the current handler as the next handler. mov(r3, Operand(ExternalReference(Isolate::k_handler_address, isolate()))); ldr(r1, MemOperand(r3)); - ASSERT(StackHandlerConstants::kNextOffset == 0); push(r1); // Link this handler as the new current one. str(sp, MemOperand(r3)); @@ -1127,16 +1129,13 @@ void MacroAssembler::PushTryHandler(CodeLocation try_location, // The frame pointer does not point to a JS frame so we save NULL // for fp. We expect the code throwing an exception to check fp // before dereferencing it to restore the context. - mov(ip, Operand(0, RelocInfo::NONE)); // To save a NULL frame pointer. - mov(r6, Operand(StackHandler::ENTRY)); - ASSERT(StackHandlerConstants::kStateOffset == 1 * kPointerSize - && StackHandlerConstants::kFPOffset == 2 * kPointerSize - && StackHandlerConstants::kPCOffset == 3 * kPointerSize); - stm(db_w, sp, r6.bit() | ip.bit() | lr.bit()); + mov(r5, Operand(StackHandler::ENTRY)); // State. + mov(r6, Operand(Smi::FromInt(0))); // Indicates no context. + mov(r7, Operand(0, RelocInfo::NONE)); // NULL frame pointer. + stm(db_w, sp, r5.bit() | r6.bit() | r7.bit() | lr.bit()); // Save the current handler as the next handler. mov(r7, Operand(ExternalReference(Isolate::k_handler_address, isolate()))); ldr(r6, MemOperand(r7)); - ASSERT(StackHandlerConstants::kNextOffset == 0); push(r6); // Link this handler as the new current one. str(sp, MemOperand(r7)); @@ -1145,7 +1144,7 @@ void MacroAssembler::PushTryHandler(CodeLocation try_location, void MacroAssembler::PopTryHandler() { - ASSERT_EQ(0, StackHandlerConstants::kNextOffset); + STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0); pop(r1); mov(ip, Operand(ExternalReference(Isolate::k_handler_address, isolate()))); add(sp, sp, Operand(StackHandlerConstants::kSize - kPointerSize)); @@ -1154,39 +1153,40 @@ void MacroAssembler::PopTryHandler() { void MacroAssembler::Throw(Register value) { + // Adjust this code if not the case. + STATIC_ASSERT(StackHandlerConstants::kSize == 5 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kStateOffset == 1 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kContextOffset == 2 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kFPOffset == 3 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kPCOffset == 4 * kPointerSize); // r0 is expected to hold the exception. if (!value.is(r0)) { mov(r0, value); } - // Adjust this code if not the case. - STATIC_ASSERT(StackHandlerConstants::kSize == 4 * kPointerSize); - // Drop the sp to the top of the handler. mov(r3, Operand(ExternalReference(Isolate::k_handler_address, isolate()))); ldr(sp, MemOperand(r3)); - // Restore the next handler and frame pointer, discard handler state. - STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0); + // Restore the next handler. pop(r2); str(r2, MemOperand(r3)); - STATIC_ASSERT(StackHandlerConstants::kFPOffset == 2 * kPointerSize); - ldm(ia_w, sp, r3.bit() | fp.bit()); // r3: discarded state. - - // Before returning we restore the context from the frame pointer if - // not NULL. The frame pointer is NULL in the exception handler of a - // JS entry frame. - cmp(fp, Operand(0, RelocInfo::NONE)); - // Set cp to NULL if fp is NULL. - mov(cp, Operand(0, RelocInfo::NONE), LeaveCC, eq); - // Restore cp otherwise. - ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset), ne); + + // Restore context and frame pointer, discard state (r3). + ldm(ia_w, sp, r3.bit() | cp.bit() | fp.bit()); + + // If the handler is a JS frame, restore the context to the frame. + // (r3 == ENTRY) == (fp == 0) == (cp == 0), so we could test any + // of them. + cmp(r3, Operand(StackHandler::ENTRY)); + str(cp, MemOperand(fp, StandardFrameConstants::kContextOffset), ne); + #ifdef DEBUG if (emit_debug_code()) { mov(lr, Operand(pc)); } #endif - STATIC_ASSERT(StackHandlerConstants::kPCOffset == 3 * kPointerSize); pop(pc); } @@ -1194,8 +1194,12 @@ void MacroAssembler::Throw(Register value) { void MacroAssembler::ThrowUncatchable(UncatchableExceptionType type, Register value) { // Adjust this code if not the case. - STATIC_ASSERT(StackHandlerConstants::kSize == 4 * kPointerSize); - + STATIC_ASSERT(StackHandlerConstants::kSize == 5 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kStateOffset == 1 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kContextOffset == 2 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kFPOffset == 3 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kPCOffset == 4 * kPointerSize); // r0 is expected to hold the exception. if (!value.is(r0)) { mov(r0, value); @@ -1220,7 +1224,6 @@ void MacroAssembler::ThrowUncatchable(UncatchableExceptionType type, bind(&done); // Set the top handler address to next handler past the current ENTRY handler. - STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0); pop(r2); str(r2, MemOperand(r3)); @@ -1242,26 +1245,17 @@ void MacroAssembler::ThrowUncatchable(UncatchableExceptionType type, // Stack layout at this point. See also StackHandlerConstants. // sp -> state (ENTRY) + // cp // fp // lr - // Discard handler state (r2 is not used) and restore frame pointer. - STATIC_ASSERT(StackHandlerConstants::kFPOffset == 2 * kPointerSize); - ldm(ia_w, sp, r2.bit() | fp.bit()); // r2: discarded state. - // Before returning we restore the context from the frame pointer if - // not NULL. The frame pointer is NULL in the exception handler of a - // JS entry frame. - cmp(fp, Operand(0, RelocInfo::NONE)); - // Set cp to NULL if fp is NULL. - mov(cp, Operand(0, RelocInfo::NONE), LeaveCC, eq); - // Restore cp otherwise. - ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset), ne); + // Restore context and frame pointer, discard state (r2). + ldm(ia_w, sp, r2.bit() | cp.bit() | fp.bit()); #ifdef DEBUG if (emit_debug_code()) { mov(lr, Operand(pc)); } #endif - STATIC_ASSERT(StackHandlerConstants::kPCOffset == 3 * kPointerSize); pop(pc); } diff --git a/src/ast.cc b/src/ast.cc index 2df62ee96..3e6d856ce 100644 --- a/src/ast.cc +++ b/src/ast.cc @@ -426,7 +426,7 @@ bool ForInStatement::IsInlineable() const { } -bool EnterWithContextStatement::IsInlineable() const { +bool WithStatement::IsInlineable() const { return false; } diff --git a/src/ast.h b/src/ast.h index a735ee130..fccf1aa1c 100644 --- a/src/ast.h +++ b/src/ast.h @@ -60,7 +60,7 @@ namespace internal { V(ContinueStatement) \ V(BreakStatement) \ V(ReturnStatement) \ - V(EnterWithContextStatement) \ + V(WithStatement) \ V(ExitContextStatement) \ V(SwitchStatement) \ V(DoWhileStatement) \ @@ -631,19 +631,21 @@ class ReturnStatement: public Statement { }; -class EnterWithContextStatement: public Statement { +class WithStatement: public Statement { public: - explicit EnterWithContextStatement(Expression* expression) - : expression_(expression) { } + WithStatement(Expression* expression, Statement* statement) + : expression_(expression), statement_(statement) { } - DECLARE_NODE_TYPE(EnterWithContextStatement) + DECLARE_NODE_TYPE(WithStatement) Expression* expression() const { return expression_; } + Statement* statement() const { return statement_; } virtual bool IsInlineable() const; private: Expression* expression_; + Statement* statement_; }; diff --git a/src/frames-inl.h b/src/frames-inl.h index 595180624..7ba79bf1b 100644 --- a/src/frames-inl.h +++ b/src/frames-inl.h @@ -1,4 +1,4 @@ -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2011 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: @@ -67,6 +67,7 @@ inline bool StackHandler::includes(Address address) const { inline void StackHandler::Iterate(ObjectVisitor* v, Code* holder) const { + v->VisitPointer(context_address()); StackFrame::IteratePc(v, pc_address(), holder); } @@ -82,6 +83,12 @@ inline StackHandler::State StackHandler::state() const { } +inline Object** StackHandler::context_address() const { + const int offset = StackHandlerConstants::kContextOffset; + return reinterpret_cast(address() + offset); +} + + inline Address* StackHandler::pc_address() const { const int offset = StackHandlerConstants::kPCOffset; return reinterpret_cast(address() + offset); diff --git a/src/frames.h b/src/frames.h index f542a92d9..4f94ebc7d 100644 --- a/src/frames.h +++ b/src/frames.h @@ -1,4 +1,4 @@ -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2011 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: @@ -114,6 +114,7 @@ class StackHandler BASE_EMBEDDED { // Accessors. inline State state() const; + inline Object** context_address() const; inline Address* pc_address() const; DISALLOW_IMPLICIT_CONSTRUCTORS(StackHandler); diff --git a/src/full-codegen.cc b/src/full-codegen.cc index aabf6ca9a..732a8fe7d 100644 --- a/src/full-codegen.cc +++ b/src/full-codegen.cc @@ -91,8 +91,7 @@ void BreakableStatementChecker::VisitReturnStatement(ReturnStatement* stmt) { } -void BreakableStatementChecker::VisitEnterWithContextStatement( - EnterWithContextStatement* stmt) { +void BreakableStatementChecker::VisitWithStatement(WithStatement* stmt) { Visit(stmt->expression()); } @@ -914,16 +913,24 @@ void FullCodeGenerator::VisitContinueStatement(ContinueStatement* stmt) { SetStatementPosition(stmt); NestedStatement* current = nesting_stack_; int stack_depth = 0; + int context_length = 0; // When continuing, we clobber the unpredictable value in the accumulator // with one that's safe for GC. If we hit an exit from the try block of // try...finally on our way out, we will unconditionally preserve the // accumulator on the stack. ClearAccumulator(); while (!current->IsContinueTarget(stmt->target())) { - stack_depth = current->Exit(stack_depth); - current = current->outer(); + current = current->Exit(&stack_depth, &context_length); } __ Drop(stack_depth); + if (context_length > 0) { + while (context_length > 0) { + LoadContextField(context_register(), Context::PREVIOUS_INDEX); + --context_length; + } + StoreToFrameField(StandardFrameConstants::kContextOffset, + context_register()); + } Iteration* loop = current->AsIteration(); __ jmp(loop->continue_target()); @@ -935,16 +942,24 @@ void FullCodeGenerator::VisitBreakStatement(BreakStatement* stmt) { SetStatementPosition(stmt); NestedStatement* current = nesting_stack_; int stack_depth = 0; + int context_length = 0; // When breaking, we clobber the unpredictable value in the accumulator // with one that's safe for GC. If we hit an exit from the try block of // try...finally on our way out, we will unconditionally preserve the // accumulator on the stack. ClearAccumulator(); while (!current->IsBreakTarget(stmt->target())) { - stack_depth = current->Exit(stack_depth); - current = current->outer(); + current = current->Exit(&stack_depth, &context_length); } __ Drop(stack_depth); + if (context_length > 0) { + while (context_length > 0) { + LoadContextField(context_register(), Context::PREVIOUS_INDEX); + --context_length; + } + StoreToFrameField(StandardFrameConstants::kContextOffset, + context_register()); + } Breakable* target = current->AsBreakable(); __ jmp(target->break_target()); @@ -960,9 +975,9 @@ void FullCodeGenerator::VisitReturnStatement(ReturnStatement* stmt) { // Exit all nested statements. NestedStatement* current = nesting_stack_; int stack_depth = 0; + int context_length = 0; while (current != NULL) { - stack_depth = current->Exit(stack_depth); - current = current->outer(); + current = current->Exit(&stack_depth, &context_length); } __ Drop(stack_depth); @@ -970,9 +985,8 @@ void FullCodeGenerator::VisitReturnStatement(ReturnStatement* stmt) { } -void FullCodeGenerator::VisitEnterWithContextStatement( - EnterWithContextStatement* stmt) { - Comment cmnt(masm_, "[ EnterWithContextStatement"); +void FullCodeGenerator::VisitWithStatement(WithStatement* stmt) { + Comment cmnt(masm_, "[ WithStatement"); SetStatementPosition(stmt); VisitForStackValue(stmt->expression()); @@ -980,6 +994,15 @@ void FullCodeGenerator::VisitEnterWithContextStatement( __ CallRuntime(Runtime::kPushWithContext, 2); decrement_stack_height(); StoreToFrameField(StandardFrameConstants::kContextOffset, context_register()); + + { WithOrCatch body(this); + Visit(stmt->statement()); + } + + // Pop context. + LoadContextField(context_register(), Context::PREVIOUS_INDEX); + // Update local stack frame context field. + StoreToFrameField(StandardFrameConstants::kContextOffset, context_register()); } @@ -1138,7 +1161,9 @@ void FullCodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) { Scope* saved_scope = scope(); scope_ = stmt->scope(); ASSERT(scope_->declarations()->is_empty()); - Visit(stmt->catch_block()); + { WithOrCatch body(this); + Visit(stmt->catch_block()); + } scope_ = saved_scope; __ jmp(&done); @@ -1184,8 +1209,8 @@ void FullCodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) { Label try_handler_setup; const int original_stack_height = stack_height(); const int finally_block_stack_height = original_stack_height + 2; - const int try_block_stack_height = original_stack_height + 4; - STATIC_ASSERT(StackHandlerConstants::kSize / kPointerSize == 4); + const int try_block_stack_height = original_stack_height + 5; + STATIC_ASSERT(StackHandlerConstants::kSize == 5 * kPointerSize); // Setup the try-handler chain. Use a call to // Jump to try-handler setup and try-block code. Use call to put try-handler @@ -1314,20 +1339,33 @@ void FullCodeGenerator::VisitThrow(Throw* expr) { } -int FullCodeGenerator::TryFinally::Exit(int stack_depth) { +FullCodeGenerator::NestedStatement* FullCodeGenerator::TryFinally::Exit( + int* stack_depth, + int* context_length) { // The macros used here must preserve the result register. - __ Drop(stack_depth); + __ Drop(*stack_depth); __ PopTryHandler(); + *stack_depth = 0; + + Register context = FullCodeGenerator::context_register(); + while (*context_length > 0) { + codegen_->LoadContextField(context, Context::PREVIOUS_INDEX); + --(*context_length); + } + __ Call(finally_entry_); - return 0; + return previous_; } -int FullCodeGenerator::TryCatch::Exit(int stack_depth) { +FullCodeGenerator::NestedStatement* FullCodeGenerator::TryCatch::Exit( + int* stack_depth, + int* context_length) { // The macros used here must preserve the result register. - __ Drop(stack_depth); + __ Drop(*stack_depth); __ PopTryHandler(); - return 0; + *stack_depth = 0; + return previous_; } diff --git a/src/full-codegen.h b/src/full-codegen.h index 9bd6e5e4d..2fc055366 100644 --- a/src/full-codegen.h +++ b/src/full-codegen.h @@ -140,25 +140,19 @@ class FullCodeGenerator: public AstVisitor { virtual bool IsContinueTarget(Statement* target) { return false; } virtual bool IsBreakTarget(Statement* target) { return false; } - // Generate code to leave the nested statement. This includes - // cleaning up any stack elements in use and restoring the - // stack to the expectations of the surrounding statements. - // Takes a number of stack elements currently on top of the - // nested statement's stack, and returns a number of stack - // elements left on top of the surrounding statement's stack. - // The generated code must preserve the result register (which - // contains the value in case of a return). - virtual int Exit(int stack_depth) { - // Default implementation for the case where there is - // nothing to clean up. - return stack_depth; + // Notify the statement that we are exiting it via break, continue, or + // return and give it a chance to generate cleanup code. Return the + // next outer statement in the nesting stack. We accumulate in + // *stack_depth the amount to drop the stack and in *context_length the + // number of context chain links to unwind as we traverse the nesting + // stack from an exit to its target. + virtual NestedStatement* Exit(int* stack_depth, int* context_length) { + return previous_; } - NestedStatement* outer() { return previous_; } protected: MacroAssembler* masm() { return codegen_->masm(); } - private: FullCodeGenerator* codegen_; NestedStatement* previous_; DISALLOW_COPY_AND_ASSIGN(NestedStatement); @@ -207,7 +201,7 @@ class FullCodeGenerator: public AstVisitor { virtual ~TryCatch() {} virtual TryCatch* AsTryCatch() { return this; } Label* catch_entry() { return catch_entry_; } - virtual int Exit(int stack_depth); + virtual NestedStatement* Exit(int* stack_depth, int* context_length); private: Label* catch_entry_; DISALLOW_COPY_AND_ASSIGN(TryCatch); @@ -221,7 +215,7 @@ class FullCodeGenerator: public AstVisitor { virtual ~TryFinally() {} virtual TryFinally* AsTryFinally() { return this; } Label* finally_entry() { return finally_entry_; } - virtual int Exit(int stack_depth); + virtual NestedStatement* Exit(int* stack_depth, int* context_length); private: Label* finally_entry_; DISALLOW_COPY_AND_ASSIGN(TryFinally); @@ -235,8 +229,9 @@ class FullCodeGenerator: public AstVisitor { explicit Finally(FullCodeGenerator* codegen) : NestedStatement(codegen) { } virtual ~Finally() {} virtual Finally* AsFinally() { return this; } - virtual int Exit(int stack_depth) { - return stack_depth + kFinallyStackElementCount; + virtual NestedStatement* Exit(int* stack_depth, int* context_length) { + *stack_depth += kFinallyStackElementCount; + return previous_; } private: // Number of extra stack slots occupied during a finally block. @@ -254,14 +249,32 @@ class FullCodeGenerator: public AstVisitor { : Iteration(codegen, statement) { } virtual ~ForIn() {} virtual ForIn* AsForIn() { return this; } - virtual int Exit(int stack_depth) { - return stack_depth + kForInStackElementCount; + virtual NestedStatement* Exit(int* stack_depth, int* context_length) { + *stack_depth += kForInStackElementCount; + return previous_; } private: static const int kForInStackElementCount = 5; DISALLOW_COPY_AND_ASSIGN(ForIn); }; + + // A WithOrCatch represents being inside the body of a with or catch + // statement. Exiting the body needs to remove a link from the context + // chain. + class WithOrCatch : public NestedStatement { + public: + explicit WithOrCatch(FullCodeGenerator* codegen) + : NestedStatement(codegen) { + } + virtual ~WithOrCatch() {} + + virtual NestedStatement* Exit(int* stack_depth, int* context_length) { + ++(*context_length); + return previous_; + } + }; + // The forward bailout stack keeps track of the expressions that can // bail out to just before the control flow is split in a child // node. The stack elements are linked together through the parent diff --git a/src/hydrogen.cc b/src/hydrogen.cc index faf124000..ca0aebbbb 100644 --- a/src/hydrogen.cc +++ b/src/hydrogen.cc @@ -2634,12 +2634,11 @@ void HGraphBuilder::VisitReturnStatement(ReturnStatement* stmt) { } -void HGraphBuilder::VisitEnterWithContextStatement( - EnterWithContextStatement* stmt) { +void HGraphBuilder::VisitWithStatement(WithStatement* stmt) { ASSERT(!HasStackOverflow()); ASSERT(current_block() != NULL); ASSERT(current_block()->HasPredecessor()); - return Bailout("EnterWithContextStatement"); + return Bailout("WithStatement"); } diff --git a/src/ia32/frames-ia32.h b/src/ia32/frames-ia32.h index bc65ddfad..2f1b2a96d 100644 --- a/src/ia32/frames-ia32.h +++ b/src/ia32/frames-ia32.h @@ -1,4 +1,4 @@ -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2011 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: @@ -58,10 +58,11 @@ static const int kNumSafepointRegisters = 8; class StackHandlerConstants : public AllStatic { public: - static const int kNextOffset = 0 * kPointerSize; - static const int kFPOffset = 1 * kPointerSize; - static const int kStateOffset = 2 * kPointerSize; - static const int kPCOffset = 3 * kPointerSize; + static const int kNextOffset = 0 * kPointerSize; + static const int kContextOffset = 1 * kPointerSize; + static const int kFPOffset = 2 * kPointerSize; + static const int kStateOffset = 3 * kPointerSize; + static const int kPCOffset = 4 * kPointerSize; static const int kSize = kPCOffset + kPointerSize; }; diff --git a/src/ia32/macro-assembler-ia32.cc b/src/ia32/macro-assembler-ia32.cc index acb670b70..04e6cde4e 100644 --- a/src/ia32/macro-assembler-ia32.cc +++ b/src/ia32/macro-assembler-ia32.cc @@ -542,7 +542,12 @@ void MacroAssembler::LeaveApiExitFrame() { void MacroAssembler::PushTryHandler(CodeLocation try_location, HandlerType type) { // Adjust this code if not the case. - ASSERT(StackHandlerConstants::kSize == 4 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kSize == 5 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0); + STATIC_ASSERT(StackHandlerConstants::kContextOffset == 1 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kFPOffset == 2 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kStateOffset == 3 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kPCOffset == 4 * kPointerSize); // The pc (return address) is already on TOS. if (try_location == IN_JAVASCRIPT) { if (type == TRY_CATCH_HANDLER) { @@ -551,6 +556,7 @@ void MacroAssembler::PushTryHandler(CodeLocation try_location, push(Immediate(StackHandler::TRY_FINALLY)); } push(ebp); + push(esi); } else { ASSERT(try_location == IN_JS_ENTRY); // The frame pointer does not point to a JS frame so we save NULL @@ -558,6 +564,7 @@ void MacroAssembler::PushTryHandler(CodeLocation try_location, // before dereferencing it to restore the context. push(Immediate(StackHandler::ENTRY)); push(Immediate(0)); // NULL frame pointer. + push(Immediate(Smi::FromInt(0))); // No context. } // Save the current handler as the next handler. push(Operand::StaticVariable(ExternalReference(Isolate::k_handler_address, @@ -570,7 +577,7 @@ void MacroAssembler::PushTryHandler(CodeLocation try_location, void MacroAssembler::PopTryHandler() { - ASSERT_EQ(0, StackHandlerConstants::kNextOffset); + STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0); pop(Operand::StaticVariable(ExternalReference(Isolate::k_handler_address, isolate()))); add(Operand(esp), Immediate(StackHandlerConstants::kSize - kPointerSize)); @@ -579,8 +586,12 @@ void MacroAssembler::PopTryHandler() { void MacroAssembler::Throw(Register value) { // Adjust this code if not the case. - STATIC_ASSERT(StackHandlerConstants::kSize == 4 * kPointerSize); - + STATIC_ASSERT(StackHandlerConstants::kSize == 5 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0); + STATIC_ASSERT(StackHandlerConstants::kContextOffset == 1 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kFPOffset == 2 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kStateOffset == 3 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kPCOffset == 4 * kPointerSize); // eax must hold the exception. if (!value.is(eax)) { mov(eax, value); @@ -591,24 +602,21 @@ void MacroAssembler::Throw(Register value) { isolate()); mov(esp, Operand::StaticVariable(handler_address)); - // Restore next handler and frame pointer, discard handler state. - STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0); + // Restore next handler, context, and frame pointer; discard handler state. pop(Operand::StaticVariable(handler_address)); - STATIC_ASSERT(StackHandlerConstants::kFPOffset == 1 * kPointerSize); - pop(ebp); - pop(edx); // Remove state. + pop(esi); // Context. + pop(ebp); // Frame pointer. + pop(edx); // State. - // Before returning we restore the context from the frame pointer if - // not NULL. The frame pointer is NULL in the exception handler of - // a JS entry frame. - Set(esi, Immediate(0)); // Tentatively set context pointer to NULL. + // If the handler is a JS frame, restore the context to the frame. + // (edx == ENTRY) == (ebp == 0) == (esi == 0), so we could test any + // of them. Label skip; - cmp(ebp, 0); + cmp(Operand(edx), Immediate(StackHandler::ENTRY)); j(equal, &skip, Label::kNear); - mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset)); + mov(Operand(ebp, StandardFrameConstants::kContextOffset), esi); bind(&skip); - STATIC_ASSERT(StackHandlerConstants::kPCOffset == 3 * kPointerSize); ret(0); } @@ -616,7 +624,12 @@ void MacroAssembler::Throw(Register value) { void MacroAssembler::ThrowUncatchable(UncatchableExceptionType type, Register value) { // Adjust this code if not the case. - STATIC_ASSERT(StackHandlerConstants::kSize == 4 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kSize == 5 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0); + STATIC_ASSERT(StackHandlerConstants::kContextOffset == 1 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kFPOffset == 2 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kStateOffset == 3 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kPCOffset == 4 * kPointerSize); // eax must hold the exception. if (!value.is(eax)) { @@ -642,7 +655,6 @@ void MacroAssembler::ThrowUncatchable(UncatchableExceptionType type, bind(&done); // Set the top handler address to next handler past the current ENTRY handler. - STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0); pop(Operand::StaticVariable(handler_address)); if (type == OUT_OF_MEMORY) { @@ -660,15 +672,14 @@ void MacroAssembler::ThrowUncatchable(UncatchableExceptionType type, mov(Operand::StaticVariable(pending_exception), eax); } - // Clear the context pointer. + // Discard the context saved in the handler and clear the context pointer. + pop(edx); Set(esi, Immediate(0)); // Restore fp from handler and discard handler state. - STATIC_ASSERT(StackHandlerConstants::kFPOffset == 1 * kPointerSize); pop(ebp); pop(edx); // State. - STATIC_ASSERT(StackHandlerConstants::kPCOffset == 3 * kPointerSize); ret(0); } diff --git a/src/parser.cc b/src/parser.cc index 1167f43df..c60ee6792 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -2012,41 +2012,6 @@ Statement* Parser::ParseReturnStatement(bool* ok) { } -Block* Parser::WithHelper(Expression* obj, ZoneStringList* labels, bool* ok) { - // Parse the statement and collect escaping labels. - TargetCollector collector; - Statement* stat; - { Target target(&this->target_stack_, &collector); - with_nesting_level_++; - top_scope_->DeclarationScope()->RecordWithStatement(); - stat = ParseStatement(labels, CHECK_OK); - with_nesting_level_--; - } - // Create resulting block with two statements. - // 1: Evaluate the with expression. - // 2: The try-finally block evaluating the body. - Block* result = new(zone()) Block(isolate(), NULL, 2, false); - - if (result != NULL) { - result->AddStatement(new(zone()) EnterWithContextStatement(obj)); - - // Create body block. - Block* body = new(zone()) Block(isolate(), NULL, 1, false); - body->AddStatement(stat); - - // Create exit block. - Block* exit = new(zone()) Block(isolate(), NULL, 1, false); - exit->AddStatement(new(zone()) ExitContextStatement()); - - // Return a try-finally statement. - TryFinallyStatement* wrapper = new(zone()) TryFinallyStatement(body, exit); - wrapper->set_escaping_targets(collector.targets()); - result->AddStatement(wrapper); - } - return result; -} - - Statement* Parser::ParseWithStatement(ZoneStringList* labels, bool* ok) { // WithStatement :: // 'with' '(' Expression ')' Statement @@ -2063,7 +2028,11 @@ Statement* Parser::ParseWithStatement(ZoneStringList* labels, bool* ok) { Expression* expr = ParseExpression(true, CHECK_OK); Expect(Token::RPAREN, CHECK_OK); - return WithHelper(expr, labels, CHECK_OK); + ++with_nesting_level_; + top_scope_->DeclarationScope()->RecordWithStatement(); + Statement* stmt = ParseStatement(labels, CHECK_OK); + --with_nesting_level_; + return new(zone()) WithStatement(expr, stmt); } @@ -2198,39 +2167,22 @@ TryStatement* Parser::ParseTryStatement(bool* ok) { Expect(Token::RPAREN, CHECK_OK); if (peek() == Token::LBRACE) { - // Rewrite the catch body B to a single statement block - // { try B finally { PopContext }}. - Block* inner_body; - // We need to collect escapes from the body for both the inner - // try/finally used to pop the catch context and any possible outer - // try/finally. - TargetCollector inner_collector; - { Target target(&this->target_stack_, &catch_collector); - { Target target(&this->target_stack_, &inner_collector); - catch_scope = NewScope(top_scope_, Scope::CATCH_SCOPE, inside_with()); - if (top_scope_->is_strict_mode()) { - catch_scope->EnableStrictMode(); - } - catch_variable = catch_scope->DeclareLocal(name, Variable::VAR); - - Scope* saved_scope = top_scope_; - top_scope_ = catch_scope; - inner_body = ParseBlock(NULL, CHECK_OK); - top_scope_ = saved_scope; - } + // Rewrite the catch body { B } to a block: + // { { B } ExitContext; }. + Target target(&this->target_stack_, &catch_collector); + catch_scope = NewScope(top_scope_, Scope::CATCH_SCOPE, inside_with()); + if (top_scope_->is_strict_mode()) { + catch_scope->EnableStrictMode(); } - - // Create exit block. - Block* inner_finally = new(zone()) Block(isolate(), NULL, 1, false); - inner_finally->AddStatement(new(zone()) ExitContextStatement()); - - // Create a try/finally statement. - TryFinallyStatement* inner_try_finally = - new(zone()) TryFinallyStatement(inner_body, inner_finally); - inner_try_finally->set_escaping_targets(inner_collector.targets()); - - catch_block = new(zone()) Block(isolate(), NULL, 1, false); - catch_block->AddStatement(inner_try_finally); + catch_variable = catch_scope->DeclareLocal(name, Variable::VAR); + catch_block = new(zone()) Block(isolate(), NULL, 2, false); + + Scope* saved_scope = top_scope_; + top_scope_ = catch_scope; + Block* catch_body = ParseBlock(NULL, CHECK_OK); + top_scope_ = saved_scope; + catch_block->AddStatement(catch_body); + catch_block->AddStatement(new(zone()) ExitContextStatement()); } else { Expect(Token::LBRACE, CHECK_OK); } diff --git a/src/parser.h b/src/parser.h index dd964cebd..b0c580712 100644 --- a/src/parser.h +++ b/src/parser.h @@ -495,7 +495,6 @@ class Parser { Statement* ParseContinueStatement(bool* ok); Statement* ParseBreakStatement(ZoneStringList* labels, bool* ok); Statement* ParseReturnStatement(bool* ok); - Block* WithHelper(Expression* obj, ZoneStringList* labels, bool* ok); Statement* ParseWithStatement(ZoneStringList* labels, bool* ok); CaseClause* ParseCaseClause(bool* default_seen_ptr, bool* ok); SwitchStatement* ParseSwitchStatement(ZoneStringList* labels, bool* ok); diff --git a/src/prettyprinter.cc b/src/prettyprinter.cc index f18b3203e..b03429341 100644 --- a/src/prettyprinter.cc +++ b/src/prettyprinter.cc @@ -123,11 +123,11 @@ void PrettyPrinter::VisitReturnStatement(ReturnStatement* node) { } -void PrettyPrinter::VisitEnterWithContextStatement( - EnterWithContextStatement* node) { - Print(" ("); +void PrettyPrinter::VisitWithStatement(WithStatement* node) { + Print("with ("); Visit(node->expression()); Print(") "); + Visit(node->statement()); } @@ -798,9 +798,10 @@ void AstPrinter::VisitReturnStatement(ReturnStatement* node) { } -void AstPrinter::VisitEnterWithContextStatement( - EnterWithContextStatement* node) { - PrintIndentedVisit("ENTER WITH CONTEXT", node->expression()); +void AstPrinter::VisitWithStatement(WithStatement* node) { + IndentedScope indent(this, "WITH"); + PrintIndentedVisit("OBJECT", node->expression()); + PrintIndentedVisit("BODY", node->statement()); } @@ -1194,10 +1195,10 @@ void JsonAstBuilder::VisitReturnStatement(ReturnStatement* stmt) { } -void JsonAstBuilder::VisitEnterWithContextStatement( - EnterWithContextStatement* stmt) { - TagScope tag(this, "EnterWithContextStatement"); +void JsonAstBuilder::VisitWithStatement(WithStatement* stmt) { + TagScope tag(this, "WithStatement"); Visit(stmt->expression()); + Visit(stmt->statement()); } diff --git a/src/rewriter.cc b/src/rewriter.cc index e8ca5b9de..ad6ce056b 100644 --- a/src/rewriter.cc +++ b/src/rewriter.cc @@ -197,13 +197,17 @@ void Processor::VisitBreakStatement(BreakStatement* node) { } +void Processor::VisitWithStatement(WithStatement* node) { + bool set_after_body = is_set_; + Visit(node->statement()); + is_set_ = is_set_ && set_after_body; +} + + // Do nothing: void Processor::VisitDeclaration(Declaration* node) {} void Processor::VisitEmptyStatement(EmptyStatement* node) {} void Processor::VisitReturnStatement(ReturnStatement* node) {} -void Processor::VisitEnterWithContextStatement( - EnterWithContextStatement* node) { -} void Processor::VisitExitContextStatement(ExitContextStatement* node) {} void Processor::VisitDebuggerStatement(DebuggerStatement* node) {} diff --git a/src/x64/frames-x64.h b/src/x64/frames-x64.h index b14267c82..7012c76f0 100644 --- a/src/x64/frames-x64.h +++ b/src/x64/frames-x64.h @@ -1,4 +1,4 @@ -// Copyright 2010 the V8 project authors. All rights reserved. +// Copyright 2011 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: @@ -50,12 +50,13 @@ static const int kNumSafepointRegisters = 16; class StackHandlerConstants : public AllStatic { public: - static const int kNextOffset = 0 * kPointerSize; - static const int kFPOffset = 1 * kPointerSize; - static const int kStateOffset = 2 * kPointerSize; - static const int kPCOffset = 3 * kPointerSize; + static const int kNextOffset = 0 * kPointerSize; + static const int kContextOffset = 1 * kPointerSize; + static const int kFPOffset = 2 * kPointerSize; + static const int kStateOffset = 3 * kPointerSize; + static const int kPCOffset = 4 * kPointerSize; - static const int kSize = 4 * kPointerSize; + static const int kSize = kPCOffset + kPointerSize; }; diff --git a/src/x64/macro-assembler-x64.cc b/src/x64/macro-assembler-x64.cc index 2b15553f1..e4a76270a 100644 --- a/src/x64/macro-assembler-x64.cc +++ b/src/x64/macro-assembler-x64.cc @@ -2387,18 +2387,15 @@ Operand MacroAssembler::SafepointRegisterSlot(Register reg) { void MacroAssembler::PushTryHandler(CodeLocation try_location, HandlerType type) { // Adjust this code if not the case. - ASSERT(StackHandlerConstants::kSize == 4 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kSize == 5 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kContextOffset == 1 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kFPOffset == 2 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kStateOffset == 3 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kPCOffset == 4 * kPointerSize); // The pc (return address) is already on TOS. This code pushes state, - // frame pointer and current handler. Check that they are expected - // next on the stack, in that order. - ASSERT_EQ(StackHandlerConstants::kStateOffset, - StackHandlerConstants::kPCOffset - kPointerSize); - ASSERT_EQ(StackHandlerConstants::kFPOffset, - StackHandlerConstants::kStateOffset - kPointerSize); - ASSERT_EQ(StackHandlerConstants::kNextOffset, - StackHandlerConstants::kFPOffset - kPointerSize); - + // frame pointer, context, and current handler. if (try_location == IN_JAVASCRIPT) { if (type == TRY_CATCH_HANDLER) { push(Immediate(StackHandler::TRY_CATCH)); @@ -2406,6 +2403,7 @@ void MacroAssembler::PushTryHandler(CodeLocation try_location, push(Immediate(StackHandler::TRY_FINALLY)); } push(rbp); + push(rsi); } else { ASSERT(try_location == IN_JS_ENTRY); // The frame pointer does not point to a JS frame so we save NULL @@ -2413,6 +2411,7 @@ void MacroAssembler::PushTryHandler(CodeLocation try_location, // before dereferencing it to restore the context. push(Immediate(StackHandler::ENTRY)); push(Immediate(0)); // NULL frame pointer. + Push(Smi::FromInt(0)); // No context. } // Save the current handler. Operand handler_operand = @@ -2435,12 +2434,13 @@ void MacroAssembler::PopTryHandler() { void MacroAssembler::Throw(Register value) { - // Check that stack should contain next handler, frame pointer, state and - // return address in that order. - STATIC_ASSERT(StackHandlerConstants::kFPOffset + kPointerSize == - StackHandlerConstants::kStateOffset); - STATIC_ASSERT(StackHandlerConstants::kStateOffset + kPointerSize == - StackHandlerConstants::kPCOffset); + // Adjust this code if not the case. + STATIC_ASSERT(StackHandlerConstants::kSize == 5 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kContextOffset == 1 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kFPOffset == 2 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kStateOffset == 3 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kPCOffset == 4 * kPointerSize); // Keep thrown value in rax. if (!value.is(rax)) { movq(rax, value); @@ -2451,23 +2451,32 @@ void MacroAssembler::Throw(Register value) { movq(rsp, handler_operand); // get next in chain pop(handler_operand); - pop(rbp); // pop frame pointer - pop(rdx); // remove state + pop(rsi); // Context. + pop(rbp); // Frame pointer. + pop(rdx); // State. - // Before returning we restore the context from the frame pointer if not NULL. - // The frame pointer is NULL in the exception handler of a JS entry frame. - Set(rsi, 0); // Tentatively set context pointer to NULL + // If the handler is a JS frame, restore the context to the frame. + // (rdx == ENTRY) == (rbp == 0) == (rsi == 0), so we could test any + // of them. Label skip; - cmpq(rbp, Immediate(0)); + cmpq(rdx, Immediate(StackHandler::ENTRY)); j(equal, &skip, Label::kNear); - movq(rsi, Operand(rbp, StandardFrameConstants::kContextOffset)); + movq(Operand(rbp, StandardFrameConstants::kContextOffset), rsi); bind(&skip); + ret(0); } void MacroAssembler::ThrowUncatchable(UncatchableExceptionType type, Register value) { + // Adjust this code if not the case. + STATIC_ASSERT(StackHandlerConstants::kSize == 5 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kContextOffset == 1 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kFPOffset == 2 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kStateOffset == 3 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kPCOffset == 4 * kPointerSize); // Keep thrown value in rax. if (!value.is(rax)) { movq(rax, value); @@ -2507,19 +2516,13 @@ void MacroAssembler::ThrowUncatchable(UncatchableExceptionType type, Store(pending_exception, rax); } - // Clear the context pointer. + // Discard the context saved in the handler and clear the context pointer. + pop(rdx); Set(rsi, 0); - // Restore registers from handler. - STATIC_ASSERT(StackHandlerConstants::kNextOffset + kPointerSize == - StackHandlerConstants::kFPOffset); - pop(rbp); // FP - STATIC_ASSERT(StackHandlerConstants::kFPOffset + kPointerSize == - StackHandlerConstants::kStateOffset); - pop(rdx); // State + pop(rbp); // Restore frame pointer. + pop(rdx); // Discard state. - STATIC_ASSERT(StackHandlerConstants::kStateOffset + kPointerSize == - StackHandlerConstants::kPCOffset); ret(0); } diff --git a/test/mjsunit/with-leave.js b/test/mjsunit/with-leave.js index ded62caf5..7369faa50 100644 --- a/test/mjsunit/with-leave.js +++ b/test/mjsunit/with-leave.js @@ -1,4 +1,4 @@ -// Copyright 2008 the V8 project authors. All rights reserved. +// Copyright 2011 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: @@ -59,3 +59,162 @@ try { } assertTrue(caught); + +// We want to test the context chain shape. In each of the tests cases +// below, the outer with is to force a runtime lookup of the identifier 'x' +// to actually verify that the inner context has been discarded. A static +// lookup of 'x' might accidentally succeed. +with ({x: 'outer'}) { + label: { + with ({x: 'inner'}) { + break label; + } + } + assertEquals('outer', x); +} + + +with ({x: 'outer'}) { + label: { + with ({x: 'middle'}) { + with ({x: 'inner'}) { + break label; + } + } + } + assertEquals('outer', x); +} + + +with ({x: 'outer'}) { + for (var i = 0; i < 10; ++i) { + with ({x: 'inner' + i}) { + continue; + } + } + assertEquals('outer', x); +} + + +with ({x: 'outer'}) { + label: for (var i = 0; i < 10; ++i) { + with ({x: 'middle' + i}) { + for (var j = 0; j < 10; ++j) { + with ({x: 'inner' + j}) { + continue label; + } + } + } + } + assertEquals('outer', x); +} + + +with ({x: 'outer'}) { + try { + with ({x: 'inner'}) { + throw 0; + } + } catch (e) { + assertEquals('outer', x); + } +} + + +with ({x: 'outer'}) { + try { + with ({x: 'middle'}) { + with ({x: 'inner'}) { + throw 0; + } + } + } catch (e) { + assertEquals('outer', x); + } +} + + +try { + with ({x: 'outer'}) { + try { + with ({x: 'inner'}) { + throw 0; + } + } finally { + assertEquals('outer', x); + } + } +} catch (e) { + if (e instanceof MjsUnitAssertionError) throw e; +} + + +try { + with ({x: 'outer'}) { + try { + with ({x: 'middle'}) { + with ({x: 'inner'}) { + throw 0; + } + } + } finally { + assertEquals('outer', x); + } + } +} catch (e) { + if (e instanceof MjsUnitAssertionError) throw e; +} + + +// Verify that the context is correctly set in the stack frame after exiting +// from with. +function f() {} + +with ({x: 'outer'}) { + label: { + with ({x: 'inner'}) { + break label; + } + } + f(); // The context could be restored from the stack after the call. + assertEquals('outer', x); +} + + +with ({x: 'outer'}) { + for (var i = 0; i < 10; ++i) { + with ({x: 'inner' + i}) { + continue; + } + } + f(); + assertEquals('outer', x); +} + + +with ({x: 'outer'}) { + try { + with ({x: 'inner'}) { + throw 0; + } + } catch (e) { + f(); + assertEquals('outer', x); + } +} + + +try { + with ({x: 'outer'}) { + try { + with ({x: 'inner'}) { + throw 0; + } + } finally { + f(); + assertEquals('outer', x); + } + } +} catch (e) { + if (e instanceof MjsUnitAssertionError) throw e; +} -- 2.34.1