2020-03-19 09:27:22 +00:00
|
|
|
// Copyright 2020 the V8 project authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
// found in the LICENSE file.
|
|
|
|
|
|
|
|
#include <initializer_list>
|
|
|
|
|
|
|
|
#include "src/api/api-inl.h"
|
|
|
|
#include "src/base/macros.h"
|
|
|
|
#include "src/codegen/assembler-inl.h"
|
|
|
|
#include "src/compiler/heap-refs.h"
|
|
|
|
#include "src/debug/debug-evaluate.h"
|
|
|
|
#include "src/debug/debug-interface.h"
|
|
|
|
#include "src/diagnostics/disassembler.h"
|
|
|
|
#include "src/execution/frames-inl.h"
|
|
|
|
#include "src/execution/frames.h"
|
|
|
|
#include "src/objects/js-objects.h"
|
|
|
|
#include "src/objects/property-descriptor.h"
|
|
|
|
#include "src/utils/utils.h"
|
|
|
|
#include "src/utils/vector.h"
|
|
|
|
#include "src/wasm/compilation-environment.h"
|
|
|
|
#include "src/wasm/module-decoder.h"
|
|
|
|
#include "src/wasm/value-type.h"
|
|
|
|
#include "src/wasm/wasm-constants.h"
|
|
|
|
#include "src/wasm/wasm-debug-evaluate.h"
|
|
|
|
#include "src/wasm/wasm-debug.h"
|
|
|
|
#include "src/wasm/wasm-module-builder.h"
|
|
|
|
#include "src/wasm/wasm-module.h"
|
|
|
|
#include "src/wasm/wasm-objects-inl.h"
|
|
|
|
#include "src/wasm/wasm-opcodes.h"
|
|
|
|
#include "src/wasm/wasm-tier.h"
|
|
|
|
#include "test/cctest/cctest.h"
|
|
|
|
#include "test/cctest/compiler/value-helper.h"
|
|
|
|
#include "test/cctest/wasm/wasm-run-utils.h"
|
|
|
|
#include "test/common/wasm/test-signatures.h"
|
|
|
|
#include "test/common/wasm/wasm-macro-gen.h"
|
|
|
|
|
|
|
|
namespace v8 {
|
|
|
|
namespace internal {
|
|
|
|
namespace wasm {
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
template <typename... FunctionArgsT>
|
|
|
|
class TestCode {
|
|
|
|
public:
|
2020-04-16 14:19:09 +00:00
|
|
|
TestCode(WasmRunnerBase* runner, std::initializer_list<byte> code,
|
|
|
|
std::initializer_list<ValueType::Kind> locals = {})
|
|
|
|
: compiler_(&runner->NewFunction<FunctionArgsT...>()),
|
|
|
|
code_(code),
|
|
|
|
locals_(static_cast<uint32_t>(locals.size())) {
|
|
|
|
for (ValueType::Kind T : locals) {
|
[wasm-gc] Change ValueType representation to account for new types
Motivation:
Changes to the typed function references and gc proposals solidified
the notion of heap type, clarified nullable vs. non-nullable reference
types, and introduced rtts, which contain an integer depth field in
addition to a heap type. This required us to overhaul our ValueType
representation, which results in extensive changes.
To keep this CL "small", we do not try to implement the binary encoding
as described in the proposals, but rather devise a simpler one of our
own (see below). Also, we do not try to implement additional
functionality for the new types.
Changes:
- Introduce HeapType. Move heap types from ValueType to HeapType.
- Introduce Nullability for reference types.
- Rework ValueType helper methods.
- Introduce rtts in ValueType with an integer depth field. Include depth
in the ValueType encoding.
- Make the constructor of ValueType private, instead expose static
functions which explicitly state what they create.
- Change every switch statement on ValueType::Kind. Sometimes, we need
nested switches.
- Introduce temporary constants in ValueTypeCode for nullable types,
use them for decoding.
- In WasmGlobalObject, split 'flags' into 'raw_type' and 'is_mutable'.
- Change IsSubtypeOfRef to IsSubtypeOfHeap and implement changes in
subtyping.
- kWasmFuncRef initializers are now non-nullable. Initializers are
only required to be subtypes of the declared global type.
- Change tests and fuzzers as needed.
Bug: v8:7748
Change-Id: If41f783bd4128443b07e94188cea7dd53ab0bfa5
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2247657
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68408}
2020-06-18 11:24:07 +00:00
|
|
|
compiler_->AllocateLocal(ValueType::Primitive(T));
|
2020-04-16 14:19:09 +00:00
|
|
|
}
|
2020-03-19 09:27:22 +00:00
|
|
|
compiler_->Build(code.begin(), code.end());
|
|
|
|
}
|
|
|
|
|
|
|
|
Handle<BreakPoint> BreakOnReturn(WasmRunnerBase* runner) {
|
2020-03-26 17:44:50 +00:00
|
|
|
runner->TierDown();
|
2020-04-16 14:19:09 +00:00
|
|
|
uint32_t return_offset_in_function = locals_ + FindReturn();
|
2020-03-19 09:27:22 +00:00
|
|
|
|
|
|
|
int function_index = compiler_->function_index();
|
|
|
|
int function_offset =
|
|
|
|
runner->builder().GetFunctionAt(function_index)->code.offset();
|
|
|
|
int return_offset_in_module = function_offset + return_offset_in_function;
|
|
|
|
|
|
|
|
Handle<WasmInstanceObject> instance = runner->builder().instance_object();
|
|
|
|
Handle<Script> script(instance->module_object().script(),
|
|
|
|
runner->main_isolate());
|
|
|
|
static int break_index = 0;
|
|
|
|
Handle<BreakPoint> break_point =
|
|
|
|
runner->main_isolate()->factory()->NewBreakPoint(
|
|
|
|
break_index++, runner->main_isolate()->factory()->empty_string());
|
|
|
|
CHECK(WasmScript::SetBreakPoint(script, &return_offset_in_module,
|
|
|
|
break_point));
|
|
|
|
return break_point;
|
|
|
|
}
|
|
|
|
|
|
|
|
MaybeHandle<Object> Run(WasmRunnerBase* runner) {
|
|
|
|
Isolate* isolate = runner->main_isolate();
|
|
|
|
Handle<JSFunction> fun_wrapper =
|
|
|
|
runner->builder().WrapCode(compiler_->function_index());
|
|
|
|
Handle<Object> global(isolate->context().global_object(), isolate);
|
|
|
|
return Execution::Call(isolate, fun_wrapper, global, 0, nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
uint32_t FindReturn() const {
|
|
|
|
for (auto i = code_.begin(); i != code_.end();
|
|
|
|
i += OpcodeLength(&*i, &*code_.end())) {
|
|
|
|
if (*i == kExprReturn) {
|
|
|
|
return static_cast<uint32_t>(std::distance(code_.begin(), i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
UNREACHABLE();
|
|
|
|
}
|
|
|
|
|
|
|
|
WasmFunctionCompiler* compiler_;
|
|
|
|
std::vector<byte> code_;
|
2020-04-16 14:19:09 +00:00
|
|
|
uint32_t locals_;
|
2020-03-19 09:27:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class WasmEvaluatorBuilder {
|
|
|
|
public:
|
|
|
|
explicit WasmEvaluatorBuilder(ExecutionTier execution_tier,
|
|
|
|
uint32_t min_memory = 1,
|
|
|
|
uint32_t max_memory = 1)
|
|
|
|
: zone_(&allocator_, ZONE_NAME), builder_(&zone_) {
|
|
|
|
get_memory_function_index = AddImport<void, uint32_t, uint32_t, uint32_t>(
|
|
|
|
CStrVector("__getMemory"));
|
2020-04-16 14:19:09 +00:00
|
|
|
get_local_function_index =
|
|
|
|
AddImport<void, uint32_t, uint32_t>(CStrVector("__getLocal"));
|
|
|
|
sbrk_function_index = AddImport<uint32_t, uint32_t>(CStrVector("__sbrk"));
|
2020-03-19 09:27:22 +00:00
|
|
|
wasm_format_function =
|
|
|
|
builder_.AddFunction(WasmRunnerBase::CreateSig<uint32_t>(&zone_));
|
|
|
|
wasm_format_function->SetName(CStrVector("wasm_format"));
|
|
|
|
builder_.AddExport(CStrVector("wasm_format"), wasm_format_function);
|
|
|
|
builder_.SetMinMemorySize(min_memory);
|
|
|
|
builder_.SetMaxMemorySize(max_memory);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename ReturnT, typename... ArgTs>
|
|
|
|
uint32_t AddImport(Vector<const char> name) {
|
|
|
|
return builder_.AddImport(
|
2020-04-16 14:19:09 +00:00
|
|
|
name, WasmRunnerBase::CreateSig<ReturnT, ArgTs...>(&zone_),
|
|
|
|
CStrVector("env"));
|
2020-03-19 09:27:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void push_back(std::initializer_list<byte> code) {
|
|
|
|
wasm_format_function->EmitCode(code.begin(),
|
|
|
|
static_cast<uint32_t>(code.size()));
|
|
|
|
}
|
|
|
|
|
2020-04-16 14:19:09 +00:00
|
|
|
void CallSbrk(std::initializer_list<byte> args) {
|
|
|
|
push_back(args);
|
|
|
|
push_back({WASM_CALL_FUNCTION0(sbrk_function_index)});
|
|
|
|
}
|
|
|
|
|
|
|
|
void CallGetLocal(std::initializer_list<byte> args) {
|
|
|
|
push_back(args);
|
|
|
|
push_back({WASM_CALL_FUNCTION0(get_local_function_index)});
|
|
|
|
}
|
|
|
|
|
2020-03-19 09:27:22 +00:00
|
|
|
void CallGetMemory(std::initializer_list<byte> args) {
|
|
|
|
push_back(args);
|
2020-04-08 12:15:23 +00:00
|
|
|
push_back({WASM_CALL_FUNCTION0(get_memory_function_index)});
|
2020-03-19 09:27:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ZoneBuffer bytes() {
|
|
|
|
ZoneBuffer bytes(&zone_);
|
|
|
|
builder_.WriteTo(&bytes);
|
|
|
|
return bytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
v8::internal::AccountingAllocator allocator_;
|
|
|
|
Zone zone_;
|
|
|
|
WasmModuleBuilder builder_;
|
|
|
|
uint32_t get_memory_function_index = 0;
|
2020-04-16 14:19:09 +00:00
|
|
|
uint32_t get_local_function_index = 0;
|
|
|
|
uint32_t sbrk_function_index = 0;
|
2020-03-19 09:27:22 +00:00
|
|
|
WasmFunctionBuilder* wasm_format_function = nullptr;
|
|
|
|
};
|
|
|
|
|
|
|
|
class WasmBreakHandler : public debug::DebugDelegate {
|
|
|
|
public:
|
|
|
|
struct EvaluationResult {
|
|
|
|
Maybe<std::string> result = Nothing<std::string>();
|
|
|
|
Maybe<std::string> error = Nothing<std::string>();
|
|
|
|
};
|
|
|
|
|
|
|
|
WasmBreakHandler(Isolate* isolate, ZoneBuffer evaluator_bytes)
|
|
|
|
: isolate_(isolate),
|
|
|
|
evaluator_bytes_(std::move(evaluator_bytes)),
|
|
|
|
result_(Nothing<EvaluationResult>()) {
|
|
|
|
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this);
|
|
|
|
}
|
|
|
|
|
|
|
|
~WasmBreakHandler() override {
|
|
|
|
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_),
|
|
|
|
nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
const Maybe<EvaluationResult>& result() const { return result_; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
Isolate* isolate_;
|
|
|
|
ZoneBuffer evaluator_bytes_;
|
|
|
|
Maybe<EvaluationResult> result_;
|
|
|
|
|
|
|
|
Maybe<std::string> GetPendingExceptionAsString() {
|
|
|
|
if (!isolate_->has_pending_exception()) return Nothing<std::string>();
|
|
|
|
Handle<Object> exception(isolate_->pending_exception(), isolate_);
|
|
|
|
isolate_->clear_pending_exception();
|
|
|
|
|
|
|
|
Handle<String> exception_string;
|
|
|
|
if (!Object::ToString(isolate_, exception).ToHandle(&exception_string)) {
|
|
|
|
return Just<std::string>("");
|
|
|
|
}
|
|
|
|
return Just<std::string>(exception_string->ToCString().get());
|
|
|
|
}
|
|
|
|
|
|
|
|
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
|
|
|
|
const std::vector<int>&) override {
|
|
|
|
// Check the current position.
|
|
|
|
StackTraceFrameIterator frame_it(isolate_);
|
|
|
|
|
2020-05-07 12:29:48 +00:00
|
|
|
WasmFrame* frame = WasmFrame::cast(frame_it.frame());
|
2020-04-24 14:17:08 +00:00
|
|
|
Handle<WasmInstanceObject> instance{frame->wasm_instance(), isolate_};
|
2020-03-19 09:27:22 +00:00
|
|
|
|
|
|
|
MaybeHandle<String> result_handle = v8::internal::wasm::DebugEvaluate(
|
|
|
|
{evaluator_bytes_.begin(), evaluator_bytes_.size()}, instance,
|
2020-04-16 14:19:09 +00:00
|
|
|
frame_it.frame());
|
2020-03-19 09:27:22 +00:00
|
|
|
|
|
|
|
Maybe<std::string> error_message = GetPendingExceptionAsString();
|
|
|
|
Maybe<std::string> result_message =
|
|
|
|
result_handle.is_null()
|
|
|
|
? Nothing<std::string>()
|
|
|
|
: Just<std::string>(
|
|
|
|
result_handle.ToHandleChecked()->ToCString().get());
|
|
|
|
|
|
|
|
isolate_->clear_pending_exception();
|
|
|
|
result_ = Just<EvaluationResult>({result_message, error_message});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_CompileFailed) {
|
|
|
|
WasmRunner<int> runner(execution_tier);
|
|
|
|
|
|
|
|
TestCode<int> code(&runner, {WASM_RETURN1(WASM_I32V_1(32))});
|
|
|
|
code.BreakOnReturn(&runner);
|
|
|
|
|
|
|
|
WasmEvaluatorBuilder evaluator(execution_tier);
|
2020-04-16 14:19:09 +00:00
|
|
|
// Create a module that doesn't compile by missing the END bytecode.
|
2020-03-19 09:27:22 +00:00
|
|
|
evaluator.push_back({WASM_RETURN1(WASM_I32V_1(33))});
|
|
|
|
|
|
|
|
Isolate* isolate = runner.main_isolate();
|
|
|
|
WasmBreakHandler break_handler(isolate, evaluator.bytes());
|
|
|
|
CHECK(!code.Run(&runner).is_null());
|
|
|
|
|
|
|
|
WasmBreakHandler::EvaluationResult result =
|
|
|
|
break_handler.result().ToChecked();
|
|
|
|
CHECK(result.result.IsNothing());
|
|
|
|
CHECK_NE(result.error.ToChecked().find(
|
|
|
|
"function body must end with \"end\" opcode"),
|
|
|
|
std::string::npos);
|
|
|
|
}
|
|
|
|
|
|
|
|
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_MissingEntrypoint) {
|
|
|
|
WasmRunner<int> runner(execution_tier);
|
|
|
|
|
|
|
|
TestCode<int> code(&runner, {WASM_RETURN1(WASM_I32V_1(32))});
|
|
|
|
code.BreakOnReturn(&runner);
|
|
|
|
|
|
|
|
v8::internal::AccountingAllocator allocator;
|
|
|
|
Zone zone(&allocator, ZONE_NAME);
|
|
|
|
WasmModuleBuilder evaluator(&zone);
|
|
|
|
ZoneBuffer evaluator_bytes(&zone);
|
|
|
|
evaluator.WriteTo(&evaluator_bytes);
|
|
|
|
|
|
|
|
Isolate* isolate = runner.main_isolate();
|
|
|
|
WasmBreakHandler break_handler(isolate, std::move(evaluator_bytes));
|
|
|
|
CHECK(!code.Run(&runner).is_null());
|
|
|
|
|
|
|
|
WasmBreakHandler::EvaluationResult result =
|
|
|
|
break_handler.result().ToChecked();
|
|
|
|
CHECK(result.result.IsNothing());
|
|
|
|
CHECK_NE(result.error.ToChecked().find("Missing export: \"wasm_format\""),
|
|
|
|
std::string::npos);
|
|
|
|
}
|
|
|
|
|
|
|
|
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_ExecuteFailed_SEGV) {
|
|
|
|
WasmRunner<int> runner(execution_tier);
|
|
|
|
runner.builder().AddMemoryElems<int32_t>(64);
|
|
|
|
|
|
|
|
TestCode<int> code(&runner, {WASM_RETURN1(WASM_I32V_1(32))});
|
|
|
|
|
2020-04-16 14:19:09 +00:00
|
|
|
// Use a max memory size of 2 here to verify the precondition for the
|
|
|
|
// GrowMemory test below.
|
|
|
|
WasmEvaluatorBuilder evaluator(execution_tier, 1, 2);
|
2020-03-19 09:27:22 +00:00
|
|
|
code.BreakOnReturn(&runner);
|
|
|
|
|
2020-04-16 14:19:09 +00:00
|
|
|
// Load 1 byte from an address that's too high.
|
2020-03-19 09:27:22 +00:00
|
|
|
evaluator.CallGetMemory(
|
|
|
|
{WASM_I32V_1(32), WASM_I32V_1(1), WASM_I32V_3((1 << 16) + 1)});
|
|
|
|
evaluator.push_back({WASM_RETURN1(WASM_I32V_1(33)), WASM_END});
|
|
|
|
|
|
|
|
Isolate* isolate = runner.main_isolate();
|
|
|
|
WasmBreakHandler break_handler(isolate, evaluator.bytes());
|
|
|
|
CHECK(!code.Run(&runner).is_null());
|
|
|
|
|
|
|
|
WasmBreakHandler::EvaluationResult result =
|
|
|
|
break_handler.result().ToChecked();
|
|
|
|
CHECK(result.result.IsNothing());
|
|
|
|
CHECK_NE(
|
|
|
|
result.error.ToChecked().find("Illegal access to out-of-bounds memory"),
|
|
|
|
std::string::npos);
|
|
|
|
}
|
|
|
|
|
2020-04-16 14:19:09 +00:00
|
|
|
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_GrowMemory) {
|
|
|
|
WasmRunner<int> runner(execution_tier);
|
|
|
|
runner.builder().AddMemoryElems<int32_t>(64);
|
|
|
|
|
|
|
|
TestCode<int> code(
|
|
|
|
&runner,
|
|
|
|
{WASM_STORE_MEM(MachineType::Int32(), WASM_I32V_1(32), WASM_I32V_2('A')),
|
|
|
|
WASM_RETURN1(WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V_1(32)))});
|
|
|
|
code.BreakOnReturn(&runner);
|
|
|
|
|
|
|
|
WasmEvaluatorBuilder evaluator(execution_tier, 1, 2);
|
|
|
|
// Grow the memory.
|
|
|
|
evaluator.CallSbrk({WASM_I32V_1(1)});
|
|
|
|
// Load 1 byte from an address that's too high for the default memory.
|
|
|
|
evaluator.CallGetMemory(
|
|
|
|
{WASM_I32V_1(32), WASM_I32V_1(1), WASM_I32V_3((1 << 16) + 1)});
|
|
|
|
evaluator.push_back({WASM_RETURN1(WASM_I32V_3((1 << 16) + 1)), WASM_END});
|
|
|
|
|
|
|
|
Isolate* isolate = runner.main_isolate();
|
|
|
|
WasmBreakHandler break_handler(isolate, evaluator.bytes());
|
|
|
|
CHECK(!code.Run(&runner).is_null());
|
|
|
|
|
|
|
|
WasmBreakHandler::EvaluationResult result =
|
|
|
|
break_handler.result().ToChecked();
|
|
|
|
CHECK(result.error.IsNothing());
|
|
|
|
CHECK_EQ(result.result.ToChecked(), "A");
|
|
|
|
}
|
|
|
|
|
2020-03-19 09:27:22 +00:00
|
|
|
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_LinearMemory) {
|
|
|
|
WasmRunner<int> runner(execution_tier);
|
|
|
|
runner.builder().AddMemoryElems<int32_t>(64);
|
|
|
|
|
|
|
|
TestCode<int> code(
|
|
|
|
&runner,
|
|
|
|
{WASM_STORE_MEM(MachineType::Int32(), WASM_I32V_1(32), WASM_I32V_2('A')),
|
|
|
|
WASM_RETURN1(WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V_1(32)))});
|
|
|
|
code.BreakOnReturn(&runner);
|
|
|
|
|
|
|
|
WasmEvaluatorBuilder evaluator(execution_tier);
|
|
|
|
// Load 4 bytes from debuggee memory at address 32, and store at the offset 33
|
|
|
|
// of the linear memory.
|
|
|
|
evaluator.CallGetMemory({WASM_I32V_1(32), WASM_I32V_1(4), WASM_I32V_1(33)});
|
|
|
|
evaluator.push_back({WASM_RETURN1(WASM_I32V_1(33)), WASM_END});
|
|
|
|
|
|
|
|
Isolate* isolate = runner.main_isolate();
|
|
|
|
WasmBreakHandler break_handler(isolate, evaluator.bytes());
|
|
|
|
CHECK(!code.Run(&runner).is_null());
|
|
|
|
|
|
|
|
WasmBreakHandler::EvaluationResult result =
|
|
|
|
break_handler.result().ToChecked();
|
|
|
|
CHECK(result.error.IsNothing());
|
|
|
|
CHECK_EQ(result.result.ToChecked(), "A");
|
|
|
|
}
|
|
|
|
|
2020-04-16 14:19:09 +00:00
|
|
|
WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_Locals) {
|
|
|
|
WasmRunner<int> runner(execution_tier);
|
|
|
|
runner.builder().AddMemoryElems<int32_t>(64);
|
|
|
|
|
|
|
|
TestCode<int> code(
|
|
|
|
&runner,
|
|
|
|
{WASM_SET_LOCAL(0, WASM_I32V_2('A')), WASM_RETURN1(WASM_GET_LOCAL(0))},
|
|
|
|
{ValueType::kI32});
|
|
|
|
code.BreakOnReturn(&runner);
|
|
|
|
|
|
|
|
WasmEvaluatorBuilder evaluator(execution_tier);
|
|
|
|
evaluator.CallGetLocal({WASM_I32V_1(0), WASM_I32V_1(33)});
|
|
|
|
evaluator.push_back({WASM_RETURN1(WASM_I32V_1(33)), WASM_END});
|
|
|
|
|
|
|
|
Isolate* isolate = runner.main_isolate();
|
|
|
|
WasmBreakHandler break_handler(isolate, evaluator.bytes());
|
|
|
|
CHECK(!code.Run(&runner).is_null());
|
|
|
|
|
|
|
|
WasmBreakHandler::EvaluationResult result =
|
|
|
|
break_handler.result().ToChecked();
|
|
|
|
CHECK(result.error.IsNothing());
|
|
|
|
CHECK_EQ(result.result.ToChecked(), "A");
|
|
|
|
}
|
|
|
|
|
2020-03-19 09:27:22 +00:00
|
|
|
} // namespace
|
|
|
|
} // namespace wasm
|
|
|
|
} // namespace internal
|
|
|
|
} // namespace v8
|