[interpreter] Implement handling of try-finally constructs.
This models function local control flow through try-finally constructs using a token dispatch mechanism. All paths through the finally block are assigned a token, at the end of the finally block a switch construct dispatches according to this token. R=oth@chromium.org,rmcilroy@chromium.org BUG=v8:4674 LOG=n Review URL: https://codereview.chromium.org/1613443002 Cr-Commit-Position: refs/heads/master@{#33465}
This commit is contained in:
parent
847ac580f1
commit
e175e39fed
@ -89,9 +89,13 @@ class BytecodeGenerator::ControlScope BASE_EMBEDDED {
|
||||
|
||||
void Break(Statement* stmt) { PerformCommand(CMD_BREAK, stmt); }
|
||||
void Continue(Statement* stmt) { PerformCommand(CMD_CONTINUE, stmt); }
|
||||
void ReturnAccumulator() { PerformCommand(CMD_RETURN, nullptr); }
|
||||
void ReThrowAccumulator() { PerformCommand(CMD_RETHROW, nullptr); }
|
||||
|
||||
class DeferredCommands;
|
||||
|
||||
protected:
|
||||
enum Command { CMD_BREAK, CMD_CONTINUE };
|
||||
enum Command { CMD_BREAK, CMD_CONTINUE, CMD_RETURN, CMD_RETHROW };
|
||||
void PerformCommand(Command command, Statement* statement);
|
||||
virtual bool Execute(Command command, Statement* statement) = 0;
|
||||
|
||||
@ -108,6 +112,112 @@ class BytecodeGenerator::ControlScope BASE_EMBEDDED {
|
||||
};
|
||||
|
||||
|
||||
// Helper class for a try-finally control scope. It can record intercepted
|
||||
// control-flow commands that cause entry into a finally-block, and re-apply
|
||||
// them after again leaving that block. Special tokens are used to identify
|
||||
// paths going through the finally-block to dispatch after leaving the block.
|
||||
class BytecodeGenerator::ControlScope::DeferredCommands final {
|
||||
public:
|
||||
DeferredCommands(BytecodeGenerator* generator, Register token_register,
|
||||
Register result_register)
|
||||
: generator_(generator),
|
||||
deferred_(generator->zone()),
|
||||
token_register_(token_register),
|
||||
result_register_(result_register) {}
|
||||
|
||||
// One recorded control-flow command.
|
||||
struct Entry {
|
||||
Command command; // The command type being applied on this path.
|
||||
Statement* statement; // The target statement for the command or {nullptr}.
|
||||
int token; // A token identifying this particular path.
|
||||
};
|
||||
|
||||
// Records a control-flow command while entering the finally-block. This also
|
||||
// generates a new dispatch token that identifies one particular path. This
|
||||
// expects the result to be in the accumulator.
|
||||
void RecordCommand(Command command, Statement* statement) {
|
||||
int token = static_cast<int>(deferred_.size());
|
||||
deferred_.push_back({command, statement, token});
|
||||
|
||||
builder()->StoreAccumulatorInRegister(result_register_);
|
||||
builder()->LoadLiteral(Smi::FromInt(token));
|
||||
builder()->StoreAccumulatorInRegister(token_register_);
|
||||
}
|
||||
|
||||
// Records the dispatch token to be used to identify the re-throw path when
|
||||
// the finally-block has been entered through the exception handler. This
|
||||
// expects the exception to be in the accumulator.
|
||||
void RecordHandlerReThrowPath() {
|
||||
// The accumulator contains the exception object.
|
||||
RecordCommand(CMD_RETHROW, nullptr);
|
||||
}
|
||||
|
||||
// Records the dispatch token to be used to identify the implicit fall-through
|
||||
// path at the end of a try-block into the corresponding finally-block.
|
||||
void RecordFallThroughPath() {
|
||||
builder()->LoadLiteral(Smi::FromInt(-1));
|
||||
builder()->StoreAccumulatorInRegister(token_register_);
|
||||
}
|
||||
|
||||
// Applies all recorded control-flow commands after the finally-block again.
|
||||
// This generates a dynamic dispatch on the token from the entry point.
|
||||
void ApplyDeferredCommands() {
|
||||
// The fall-through path is covered by the default case, hence +1 here.
|
||||
SwitchBuilder dispatch(builder(), static_cast<int>(deferred_.size() + 1));
|
||||
for (size_t i = 0; i < deferred_.size(); ++i) {
|
||||
Entry& entry = deferred_[i];
|
||||
builder()->LoadLiteral(Smi::FromInt(entry.token));
|
||||
builder()->CompareOperation(Token::EQ_STRICT, token_register_,
|
||||
Strength::WEAK);
|
||||
dispatch.Case(static_cast<int>(i));
|
||||
}
|
||||
dispatch.DefaultAt(static_cast<int>(deferred_.size()));
|
||||
for (size_t i = 0; i < deferred_.size(); ++i) {
|
||||
Entry& entry = deferred_[i];
|
||||
dispatch.SetCaseTarget(static_cast<int>(i));
|
||||
builder()->LoadAccumulatorWithRegister(result_register_);
|
||||
execution_control()->PerformCommand(entry.command, entry.statement);
|
||||
}
|
||||
dispatch.SetCaseTarget(static_cast<int>(deferred_.size()));
|
||||
}
|
||||
|
||||
BytecodeArrayBuilder* builder() { return generator_->builder(); }
|
||||
ControlScope* execution_control() { return generator_->execution_control(); }
|
||||
|
||||
private:
|
||||
BytecodeGenerator* generator_;
|
||||
ZoneVector<Entry> deferred_;
|
||||
Register token_register_;
|
||||
Register result_register_;
|
||||
};
|
||||
|
||||
|
||||
// Scoped class for dealing with control flow reaching the function level.
|
||||
class BytecodeGenerator::ControlScopeForTopLevel final
|
||||
: public BytecodeGenerator::ControlScope {
|
||||
public:
|
||||
explicit ControlScopeForTopLevel(BytecodeGenerator* generator)
|
||||
: ControlScope(generator) {}
|
||||
|
||||
protected:
|
||||
bool Execute(Command command, Statement* statement) override {
|
||||
switch (command) {
|
||||
case CMD_BREAK:
|
||||
case CMD_CONTINUE:
|
||||
break;
|
||||
case CMD_RETURN:
|
||||
generator()->builder()->Return();
|
||||
return true;
|
||||
case CMD_RETHROW:
|
||||
// TODO(mstarzinger): Should be a ReThrow instead.
|
||||
generator()->builder()->Throw();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Scoped class for enabling break inside blocks and switch blocks.
|
||||
class BytecodeGenerator::ControlScopeForBreakable final
|
||||
: public BytecodeGenerator::ControlScope {
|
||||
@ -127,6 +237,8 @@ class BytecodeGenerator::ControlScopeForBreakable final
|
||||
control_builder_->Break();
|
||||
return true;
|
||||
case CMD_CONTINUE:
|
||||
case CMD_RETURN:
|
||||
case CMD_RETHROW:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
@ -160,6 +272,9 @@ class BytecodeGenerator::ControlScopeForIteration final
|
||||
case CMD_CONTINUE:
|
||||
loop_builder_->Continue();
|
||||
return true;
|
||||
case CMD_RETURN:
|
||||
case CMD_RETHROW:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -170,6 +285,66 @@ class BytecodeGenerator::ControlScopeForIteration final
|
||||
};
|
||||
|
||||
|
||||
// Scoped class for enabling 'throw' in try-catch constructs.
|
||||
class BytecodeGenerator::ControlScopeForTryCatch final
|
||||
: public BytecodeGenerator::ControlScope {
|
||||
public:
|
||||
ControlScopeForTryCatch(BytecodeGenerator* generator,
|
||||
TryCatchBuilder* try_catch_builder)
|
||||
: ControlScope(generator), try_catch_builder_(try_catch_builder) {}
|
||||
|
||||
protected:
|
||||
bool Execute(Command command, Statement* statement) override {
|
||||
switch (command) {
|
||||
case CMD_BREAK:
|
||||
case CMD_CONTINUE:
|
||||
case CMD_RETURN:
|
||||
break;
|
||||
case CMD_RETHROW:
|
||||
// TODO(mstarzinger): Test and implement this!
|
||||
USE(try_catch_builder_);
|
||||
UNIMPLEMENTED();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
TryCatchBuilder* try_catch_builder_;
|
||||
};
|
||||
|
||||
|
||||
// Scoped class for enabling control flow through try-finally constructs.
|
||||
class BytecodeGenerator::ControlScopeForTryFinally final
|
||||
: public BytecodeGenerator::ControlScope {
|
||||
public:
|
||||
ControlScopeForTryFinally(BytecodeGenerator* generator,
|
||||
TryFinallyBuilder* try_finally_builder,
|
||||
DeferredCommands* commands)
|
||||
: ControlScope(generator),
|
||||
try_finally_builder_(try_finally_builder),
|
||||
commands_(commands) {}
|
||||
|
||||
protected:
|
||||
bool Execute(Command command, Statement* statement) override {
|
||||
switch (command) {
|
||||
case CMD_BREAK:
|
||||
case CMD_CONTINUE:
|
||||
case CMD_RETURN:
|
||||
case CMD_RETHROW:
|
||||
commands_->RecordCommand(command, statement);
|
||||
try_finally_builder_->LeaveTry();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
TryFinallyBuilder* try_finally_builder_;
|
||||
DeferredCommands* commands_;
|
||||
};
|
||||
|
||||
|
||||
void BytecodeGenerator::ControlScope::PerformCommand(Command command,
|
||||
Statement* statement) {
|
||||
ControlScope* current = this;
|
||||
@ -375,6 +550,9 @@ Handle<BytecodeArray> BytecodeGenerator::MakeBytecode(CompilationInfo* info) {
|
||||
// Initialize the incoming context.
|
||||
ContextScope incoming_context(this, scope(), false);
|
||||
|
||||
// Initialize control scope.
|
||||
ControlScopeForTopLevel control(this);
|
||||
|
||||
builder()->set_parameter_count(info->num_parameters_including_this());
|
||||
builder()->set_locals_count(scope()->num_stack_slots());
|
||||
builder()->set_context_count(scope()->MaxNestedContextChainLength());
|
||||
@ -671,7 +849,7 @@ void BytecodeGenerator::VisitBreakStatement(BreakStatement* stmt) {
|
||||
|
||||
void BytecodeGenerator::VisitReturnStatement(ReturnStatement* stmt) {
|
||||
VisitForAccumulatorValue(stmt->expression());
|
||||
builder()->Return();
|
||||
execution_control()->ReturnAccumulator();
|
||||
}
|
||||
|
||||
|
||||
@ -919,8 +1097,10 @@ void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) {
|
||||
// Evaluate the try-block inside a control scope. This simulates a handler
|
||||
// that is intercepting 'throw' control commands.
|
||||
try_control_builder.BeginTry(context);
|
||||
// TODO(mstarzinger): Control scope is missing!
|
||||
Visit(stmt->try_block());
|
||||
{
|
||||
ControlScopeForTryCatch scope(this, &try_control_builder);
|
||||
Visit(stmt->try_block());
|
||||
}
|
||||
try_control_builder.EndTry();
|
||||
|
||||
// Clear message object as we enter the catch block.
|
||||
@ -938,6 +1118,25 @@ void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) {
|
||||
void BytecodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) {
|
||||
TryFinallyBuilder try_control_builder(builder());
|
||||
|
||||
// We keep a record of all paths that enter the finally-block to be able to
|
||||
// dispatch to the correct continuation point after the statements in the
|
||||
// finally-block have been evaluated.
|
||||
//
|
||||
// The try-finally construct can enter the finally-block in three ways:
|
||||
// 1. By exiting the try-block normally, falling through at the end.
|
||||
// 2. By exiting the try-block with a function-local control flow transfer
|
||||
// (i.e. through break/continue/return statements).
|
||||
// 3. By exiting the try-block with a thrown exception.
|
||||
//
|
||||
// The result register semantics depend on how the block was entered:
|
||||
// - ReturnStatement: It represents the return value being returned.
|
||||
// - ThrowStatement: It represents the exception being thrown.
|
||||
// - BreakStatement/ContinueStatement: Undefined and not used.
|
||||
// - Falling through into finally-block: Undefined and not used.
|
||||
Register token = register_allocator()->NewRegister();
|
||||
Register result = register_allocator()->NewRegister();
|
||||
ControlScope::DeferredCommands commands(this, token, result);
|
||||
|
||||
// Preserve the context in a dedicated register, so that it can be restored
|
||||
// when the handler is entered by the stack-unwinding machinery.
|
||||
// TODO(mstarzinger): Be smarter about register allocation.
|
||||
@ -946,16 +1145,29 @@ void BytecodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) {
|
||||
// Evaluate the try-block inside a control scope. This simulates a handler
|
||||
// that is intercepting all control commands.
|
||||
try_control_builder.BeginTry(context);
|
||||
// TODO(mstarzinger): Control scope is missing!
|
||||
Visit(stmt->try_block());
|
||||
{
|
||||
ControlScopeForTryFinally scope(this, &try_control_builder, &commands);
|
||||
Visit(stmt->try_block());
|
||||
}
|
||||
try_control_builder.EndTry();
|
||||
|
||||
// Record fall-through and exception cases.
|
||||
commands.RecordFallThroughPath();
|
||||
try_control_builder.LeaveTry();
|
||||
try_control_builder.BeginHandler();
|
||||
commands.RecordHandlerReThrowPath();
|
||||
|
||||
try_control_builder.BeginFinally();
|
||||
|
||||
// Clear message object as we enter the finally block.
|
||||
// TODO(mstarzinger): Implement this!
|
||||
|
||||
// Evaluate the finally-block.
|
||||
Visit(stmt->finally_block());
|
||||
try_control_builder.EndFinally();
|
||||
|
||||
// Dynamic dispatch after the finally-block.
|
||||
commands.ApplyDeferredCommands();
|
||||
}
|
||||
|
||||
|
||||
|
@ -32,6 +32,9 @@ class BytecodeGenerator final : public AstVisitor {
|
||||
class ControlScope;
|
||||
class ControlScopeForBreakable;
|
||||
class ControlScopeForIteration;
|
||||
class ControlScopeForTopLevel;
|
||||
class ControlScopeForTryCatch;
|
||||
class ControlScopeForTryFinally;
|
||||
class ExpressionResultScope;
|
||||
class EffectResultScope;
|
||||
class AccumulatorResultScope;
|
||||
|
@ -159,13 +159,31 @@ void TryFinallyBuilder::BeginTry(Register context) {
|
||||
}
|
||||
|
||||
|
||||
void TryFinallyBuilder::LeaveTry() {
|
||||
finalization_sites_.push_back(BytecodeLabel());
|
||||
builder()->Jump(&finalization_sites_.back());
|
||||
}
|
||||
|
||||
|
||||
void TryFinallyBuilder::EndTry() {
|
||||
builder()->MarkTryEnd(handler_id_);
|
||||
}
|
||||
|
||||
|
||||
void TryFinallyBuilder::BeginHandler() {
|
||||
builder()->Bind(&handler_);
|
||||
builder()->MarkHandler(handler_id_, false);
|
||||
}
|
||||
|
||||
|
||||
void TryFinallyBuilder::BeginFinally() {
|
||||
for (size_t i = 0; i < finalization_sites_.size(); i++) {
|
||||
BytecodeLabel& site = finalization_sites_.at(i);
|
||||
builder()->Bind(&site);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TryFinallyBuilder::EndFinally() {
|
||||
// Nothing to be done here.
|
||||
}
|
||||
|
@ -166,15 +166,23 @@ class TryCatchBuilder final : public ControlFlowBuilder {
|
||||
class TryFinallyBuilder final : public ControlFlowBuilder {
|
||||
public:
|
||||
explicit TryFinallyBuilder(BytecodeArrayBuilder* builder)
|
||||
: ControlFlowBuilder(builder), handler_id_(builder->NewHandlerEntry()) {}
|
||||
: ControlFlowBuilder(builder),
|
||||
handler_id_(builder->NewHandlerEntry()),
|
||||
finalization_sites_(builder->zone()) {}
|
||||
|
||||
void BeginTry(Register context);
|
||||
void LeaveTry();
|
||||
void EndTry();
|
||||
void BeginHandler();
|
||||
void BeginFinally();
|
||||
void EndFinally();
|
||||
|
||||
private:
|
||||
int handler_id_;
|
||||
BytecodeLabel handler_;
|
||||
|
||||
// Unbound labels that identify jumps to the finally block in the code.
|
||||
ZoneVector<BytecodeLabel> finalization_sites_;
|
||||
};
|
||||
|
||||
} // namespace interpreter
|
||||
|
@ -4350,92 +4350,128 @@ TEST(TryFinally) {
|
||||
|
||||
ExpectedSnippet<const char*> snippets[] = {
|
||||
{"var a = 1; try { a = 2; } finally { a = 3; }",
|
||||
2 * kPointerSize,
|
||||
4 * kPointerSize,
|
||||
1,
|
||||
14,
|
||||
35,
|
||||
{
|
||||
B(LdaSmi8), U8(1), //
|
||||
B(Star), R(0), //
|
||||
B(LdaSmi8), U8(2), //
|
||||
B(Star), R(0), //
|
||||
B(LdaSmi8), U8(3), //
|
||||
B(Star), R(0), //
|
||||
B(LdaUndefined), //
|
||||
B(Return), //
|
||||
B(LdaSmi8), U8(1), //
|
||||
B(Star), R(0), //
|
||||
B(LdaSmi8), U8(2), //
|
||||
B(Star), R(0), //
|
||||
B(LdaSmi8), U8(-1), //
|
||||
B(Star), R(1), //
|
||||
B(Jump), U8(7), //
|
||||
B(Star), R(2), //
|
||||
B(LdaZero), //
|
||||
B(Star), R(1), //
|
||||
B(LdaSmi8), U8(3), //
|
||||
B(Star), R(0), //
|
||||
B(LdaZero), //
|
||||
B(TestEqualStrict), R(1), //
|
||||
B(JumpIfTrue), U8(4), //
|
||||
B(Jump), U8(5), //
|
||||
B(Ldar), R(2), //
|
||||
B(Throw), //
|
||||
B(LdaUndefined), //
|
||||
B(Return), //
|
||||
},
|
||||
0,
|
||||
{},
|
||||
1,
|
||||
{{4, 8, 8}}},
|
||||
{{4, 8, 14}}},
|
||||
{"var a = 1; try { a = 2; } catch(e) { a = 20 } finally { a = 3; }",
|
||||
7 * kPointerSize,
|
||||
9 * kPointerSize,
|
||||
1,
|
||||
39,
|
||||
60,
|
||||
{
|
||||
B(LdaSmi8), U8(1), //
|
||||
B(Star), R(0), //
|
||||
B(LdaSmi8), U8(2), //
|
||||
B(Star), R(0), //
|
||||
B(Jump), U8(25), //
|
||||
B(Star), R(5), //
|
||||
B(Star), R(7), //
|
||||
B(LdaConstant), U8(0), //
|
||||
B(Star), R(4), //
|
||||
B(Ldar), R(closure), //
|
||||
B(Star), R(6), //
|
||||
B(CallRuntime), U16(Runtime::kPushCatchContext), R(4), U8(3), //
|
||||
B(Ldar), R(closure), //
|
||||
B(Star), R(8), //
|
||||
B(CallRuntime), U16(Runtime::kPushCatchContext), R(6), U8(3), //
|
||||
B(PushContext), R(1), //
|
||||
B(LdaSmi8), U8(20), //
|
||||
B(Star), R(0), //
|
||||
B(PopContext), R(context), //
|
||||
B(LdaSmi8), U8(-1), //
|
||||
B(Star), R(2), //
|
||||
B(Jump), U8(7), //
|
||||
B(Star), R(3), //
|
||||
B(LdaZero), //
|
||||
B(Star), R(2), //
|
||||
B(LdaSmi8), U8(3), //
|
||||
B(Star), R(0), //
|
||||
B(LdaZero), //
|
||||
B(TestEqualStrict), R(2), //
|
||||
B(JumpIfTrue), U8(4), //
|
||||
B(Jump), U8(5), //
|
||||
B(Ldar), R(3), //
|
||||
B(Throw), //
|
||||
B(LdaUndefined), //
|
||||
B(Return), //
|
||||
},
|
||||
1,
|
||||
{"e"},
|
||||
2,
|
||||
{{4, 33, 33}, {4, 8, 10}}},
|
||||
{{4, 33, 39}, {4, 8, 10}}},
|
||||
{"var a; try {"
|
||||
" try { a = 1 } catch(e) { a = 2 }"
|
||||
"} catch(e) { a = 20 } finally { a = 3; }",
|
||||
8 * kPointerSize,
|
||||
10 * kPointerSize,
|
||||
1,
|
||||
60,
|
||||
81,
|
||||
{
|
||||
B(LdaSmi8), U8(1), //
|
||||
B(Star), R(0), //
|
||||
B(Jump), U8(25), //
|
||||
B(Star), R(6), //
|
||||
B(Star), R(8), //
|
||||
B(LdaConstant), U8(0), //
|
||||
B(Star), R(5), //
|
||||
B(Ldar), R(closure), //
|
||||
B(Star), R(7), //
|
||||
B(CallRuntime), U16(Runtime::kPushCatchContext), R(5), U8(3), //
|
||||
B(Ldar), R(closure), //
|
||||
B(Star), R(9), //
|
||||
B(CallRuntime), U16(Runtime::kPushCatchContext), R(7), U8(3), //
|
||||
B(PushContext), R(1), //
|
||||
B(LdaSmi8), U8(2), //
|
||||
B(Star), R(0), //
|
||||
B(PopContext), R(context), //
|
||||
B(Jump), U8(25), //
|
||||
B(Star), R(5), //
|
||||
B(Star), R(7), //
|
||||
B(LdaConstant), U8(0), //
|
||||
B(Star), R(4), //
|
||||
B(Ldar), R(closure), //
|
||||
B(Star), R(6), //
|
||||
B(CallRuntime), U16(Runtime::kPushCatchContext), R(4), U8(3), //
|
||||
B(Ldar), R(closure), //
|
||||
B(Star), R(8), //
|
||||
B(CallRuntime), U16(Runtime::kPushCatchContext), R(6), U8(3), //
|
||||
B(PushContext), R(1), //
|
||||
B(LdaSmi8), U8(20), //
|
||||
B(Star), R(0), //
|
||||
B(PopContext), R(context), //
|
||||
B(LdaSmi8), U8(-1), //
|
||||
B(Star), R(2), //
|
||||
B(Jump), U8(7), //
|
||||
B(Star), R(3), //
|
||||
B(LdaZero), //
|
||||
B(Star), R(2), //
|
||||
B(LdaSmi8), U8(3), //
|
||||
B(Star), R(0), //
|
||||
B(LdaZero), //
|
||||
B(TestEqualStrict), R(2), //
|
||||
B(JumpIfTrue), U8(4), //
|
||||
B(Jump), U8(5), //
|
||||
B(Ldar), R(3), //
|
||||
B(Throw), //
|
||||
B(LdaUndefined), //
|
||||
B(Return), //
|
||||
},
|
||||
1,
|
||||
{"e"},
|
||||
3,
|
||||
{{0, 54, 54}, {0, 29, 31}, {0, 4, 6}}},
|
||||
{{0, 54, 60}, {0, 29, 31}, {0, 4, 6}}},
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < arraysize(snippets); i++) {
|
||||
|
@ -2048,15 +2048,45 @@ TEST(InterpreterTryCatch) {
|
||||
|
||||
TEST(InterpreterTryFinally) {
|
||||
HandleAndZoneScope handles;
|
||||
i::Isolate* isolate = handles.main_isolate();
|
||||
i::Factory* factory = isolate->factory();
|
||||
|
||||
// TODO(rmcilroy): modify tests when we have real try finally support.
|
||||
std::string source(InterpreterTester::SourceForBody(
|
||||
"var a = 1; try { a = a + 1; } finally { a = a + 2; }; return a;"));
|
||||
InterpreterTester tester(handles.main_isolate(), source.c_str());
|
||||
auto callable = tester.GetCallable<>();
|
||||
std::pair<const char*, Handle<Object>> finallies[] = {
|
||||
std::make_pair(
|
||||
"var a = 1; try { a = a + 1; } finally { a = a + 2; }; return a;",
|
||||
factory->NewStringFromStaticChars("R4")),
|
||||
std::make_pair(
|
||||
"var a = 1; try { a = 2; return 23; } finally { a = 3 }; return a;",
|
||||
factory->NewStringFromStaticChars("R23")),
|
||||
std::make_pair(
|
||||
"var a = 1; try { a = 2; throw 23; } finally { a = 3 }; return a;",
|
||||
factory->NewStringFromStaticChars("E23")),
|
||||
std::make_pair(
|
||||
"var a = 1; try { a = 2; throw 23; } finally { return a; };",
|
||||
factory->NewStringFromStaticChars("R2")),
|
||||
std::make_pair(
|
||||
"var a = 1; try { a = 2; throw 23; } finally { throw 42; };",
|
||||
factory->NewStringFromStaticChars("E42")),
|
||||
std::make_pair("var a = 1; for (var i = 10; i < 20; i += 5) {"
|
||||
" try { a = 2; break; } finally { a = 3; }"
|
||||
"} return a + i;",
|
||||
factory->NewStringFromStaticChars("R13")),
|
||||
std::make_pair("var a = 1; for (var i = 10; i < 20; i += 5) {"
|
||||
" try { a = 2; continue; } finally { a = 3; }"
|
||||
"} return a + i;",
|
||||
factory->NewStringFromStaticChars("R23")),
|
||||
};
|
||||
|
||||
Handle<Object> return_val = callable().ToHandleChecked();
|
||||
CHECK_EQ(Smi::cast(*return_val), Smi::FromInt(4));
|
||||
const char* try_wrapper =
|
||||
"(function() { try { return 'R' + f() } catch(e) { return 'E' + e }})()";
|
||||
|
||||
for (size_t i = 0; i < arraysize(finallies); i++) {
|
||||
std::string source(InterpreterTester::SourceForBody(finallies[i].first));
|
||||
InterpreterTester tester(handles.main_isolate(), source.c_str());
|
||||
tester.GetCallable<>();
|
||||
Handle<Object> wrapped = v8::Utils::OpenHandle(*CompileRun(try_wrapper));
|
||||
CHECK(wrapped->SameValue(*finallies[i].second));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user