Expose a proxy object to evaluateOnCallFrame for WebAssembly
When debugging WebAssembly, calls to evaluateOnCallFrame always return undefined. This CL enables evaluateOnCallFrame for WebAssembly and creates a proxy object that is injected into the evaluation context. Bug: chromium:1127914 Change-Id: I3f5cff3be2c9de45c7b1f3f7ed4fc2e1cc545ac6 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2429265 Commit-Queue: Philip Pfaffe <pfaffe@chromium.org> Reviewed-by: Clemens Backes <clemensb@chromium.org> Reviewed-by: Benedikt Meurer <bmeurer@chromium.org> Reviewed-by: Simon Zünd <szuend@chromium.org> Cr-Commit-Position: refs/heads/master@{#70315}
This commit is contained in:
parent
a5024f9b4f
commit
ae3f94bd2a
@ -18,6 +18,7 @@
|
|||||||
#include "src/objects/contexts.h"
|
#include "src/objects/contexts.h"
|
||||||
#include "src/snapshot/snapshot.h"
|
#include "src/snapshot/snapshot.h"
|
||||||
#include "src/wasm/wasm-debug.h"
|
#include "src/wasm/wasm-debug.h"
|
||||||
|
#include "src/wasm/wasm-js.h"
|
||||||
|
|
||||||
namespace v8 {
|
namespace v8 {
|
||||||
namespace internal {
|
namespace internal {
|
||||||
@ -106,6 +107,9 @@ V8_EXPORT MaybeHandle<Object> DebugEvaluate::WebAssembly(
|
|||||||
|
|
||||||
StackTraceFrameIterator it(isolate, frame_id);
|
StackTraceFrameIterator it(isolate, frame_id);
|
||||||
if (!it.is_wasm()) return isolate->factory()->undefined_value();
|
if (!it.is_wasm()) return isolate->factory()->undefined_value();
|
||||||
|
WasmFrame* frame = WasmFrame::cast(it.frame());
|
||||||
|
|
||||||
|
Handle<JSProxy> context_extension = WasmJs::GetJSDebugProxy(frame);
|
||||||
|
|
||||||
DisableBreak disable_break_scope(isolate->debug(), /*disable=*/true);
|
DisableBreak disable_break_scope(isolate->debug(), /*disable=*/true);
|
||||||
|
|
||||||
@ -114,12 +118,14 @@ V8_EXPORT MaybeHandle<Object> DebugEvaluate::WebAssembly(
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<Context> context = isolate->native_context();
|
Handle<ScopeInfo> scope_info =
|
||||||
Handle<JSObject> receiver(context->global_proxy(), isolate);
|
ScopeInfo::CreateForWithScope(isolate, Handle<ScopeInfo>::null());
|
||||||
|
Handle<Context> context = isolate->factory()->NewWithContext(
|
||||||
|
isolate->native_context(), scope_info, context_extension);
|
||||||
|
|
||||||
Handle<Object> result;
|
Handle<Object> result;
|
||||||
if (!DebugEvaluate::Evaluate(isolate, shared_info, context, receiver, source,
|
if (!DebugEvaluate::Evaluate(isolate, shared_info, context, context_extension,
|
||||||
throw_on_side_effect)
|
source, throw_on_side_effect)
|
||||||
.ToHandle(&result)) {
|
.ToHandle(&result)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -340,6 +340,12 @@ class DebugInfoImpl {
|
|||||||
debug_break_fp);
|
debug_break_fp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WasmFunction& GetFunctionAtAddress(Address pc) {
|
||||||
|
FrameInspectionScope scope(this, pc);
|
||||||
|
auto* module = native_module_->module();
|
||||||
|
return module->functions[scope.code->index()];
|
||||||
|
}
|
||||||
|
|
||||||
Handle<JSObject> GetLocalScopeObject(Isolate* isolate, Address pc, Address fp,
|
Handle<JSObject> GetLocalScopeObject(Isolate* isolate, Address pc, Address fp,
|
||||||
Address debug_break_fp) {
|
Address debug_break_fp) {
|
||||||
FrameInspectionScope scope(this, pc);
|
FrameInspectionScope scope(this, pc);
|
||||||
@ -909,6 +915,10 @@ WasmValue DebugInfo::GetStackValue(int index, Address pc, Address fp,
|
|||||||
return impl_->GetStackValue(index, pc, fp, debug_break_fp);
|
return impl_->GetStackValue(index, pc, fp, debug_break_fp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const wasm::WasmFunction& DebugInfo::GetFunctionAtAddress(Address pc) {
|
||||||
|
return impl_->GetFunctionAtAddress(pc);
|
||||||
|
}
|
||||||
|
|
||||||
Handle<JSObject> DebugInfo::GetLocalScopeObject(Isolate* isolate, Address pc,
|
Handle<JSObject> DebugInfo::GetLocalScopeObject(Isolate* isolate, Address pc,
|
||||||
Address fp,
|
Address fp,
|
||||||
Address debug_break_fp) {
|
Address debug_break_fp) {
|
||||||
|
@ -34,6 +34,7 @@ class NativeModule;
|
|||||||
class WasmCode;
|
class WasmCode;
|
||||||
class WireBytesRef;
|
class WireBytesRef;
|
||||||
class WasmValue;
|
class WasmValue;
|
||||||
|
struct WasmFunction;
|
||||||
|
|
||||||
// Side table storing information used to inspect Liftoff frames at runtime.
|
// Side table storing information used to inspect Liftoff frames at runtime.
|
||||||
// This table is only created on demand for debugging, so it is not optimized
|
// This table is only created on demand for debugging, so it is not optimized
|
||||||
@ -153,6 +154,9 @@ class V8_EXPORT_PRIVATE DebugInfo {
|
|||||||
WasmValue GetLocalValue(int local, Address pc, Address fp,
|
WasmValue GetLocalValue(int local, Address pc, Address fp,
|
||||||
Address debug_break_fp);
|
Address debug_break_fp);
|
||||||
int GetStackDepth(Address pc);
|
int GetStackDepth(Address pc);
|
||||||
|
|
||||||
|
const wasm::WasmFunction& GetFunctionAtAddress(Address pc);
|
||||||
|
|
||||||
WasmValue GetStackValue(int index, Address pc, Address fp,
|
WasmValue GetStackValue(int index, Address pc, Address fp,
|
||||||
Address debug_break_fp);
|
Address debug_break_fp);
|
||||||
|
|
||||||
|
@ -10,13 +10,16 @@
|
|||||||
#include "src/api/api-inl.h"
|
#include "src/api/api-inl.h"
|
||||||
#include "src/api/api-natives.h"
|
#include "src/api/api-natives.h"
|
||||||
#include "src/ast/ast.h"
|
#include "src/ast/ast.h"
|
||||||
|
#include "src/base/logging.h"
|
||||||
#include "src/base/overflowing-math.h"
|
#include "src/base/overflowing-math.h"
|
||||||
#include "src/common/assert-scope.h"
|
#include "src/common/assert-scope.h"
|
||||||
#include "src/execution/execution.h"
|
#include "src/execution/execution.h"
|
||||||
|
#include "src/execution/frames-inl.h"
|
||||||
#include "src/execution/isolate.h"
|
#include "src/execution/isolate.h"
|
||||||
#include "src/handles/handles.h"
|
#include "src/handles/handles.h"
|
||||||
#include "src/heap/factory.h"
|
#include "src/heap/factory.h"
|
||||||
#include "src/init/v8.h"
|
#include "src/init/v8.h"
|
||||||
|
#include "src/objects/js-collection-inl.h"
|
||||||
#include "src/objects/js-promise-inl.h"
|
#include "src/objects/js-promise-inl.h"
|
||||||
#include "src/objects/objects-inl.h"
|
#include "src/objects/objects-inl.h"
|
||||||
#include "src/objects/templates.h"
|
#include "src/objects/templates.h"
|
||||||
@ -25,10 +28,12 @@
|
|||||||
#include "src/trap-handler/trap-handler.h"
|
#include "src/trap-handler/trap-handler.h"
|
||||||
#include "src/wasm/streaming-decoder.h"
|
#include "src/wasm/streaming-decoder.h"
|
||||||
#include "src/wasm/value-type.h"
|
#include "src/wasm/value-type.h"
|
||||||
|
#include "src/wasm/wasm-debug.h"
|
||||||
#include "src/wasm/wasm-engine.h"
|
#include "src/wasm/wasm-engine.h"
|
||||||
#include "src/wasm/wasm-limits.h"
|
#include "src/wasm/wasm-limits.h"
|
||||||
#include "src/wasm/wasm-objects-inl.h"
|
#include "src/wasm/wasm-objects-inl.h"
|
||||||
#include "src/wasm/wasm-serialization.h"
|
#include "src/wasm/wasm-serialization.h"
|
||||||
|
#include "src/wasm/wasm-value.h"
|
||||||
|
|
||||||
using v8::internal::wasm::ErrorThrower;
|
using v8::internal::wasm::ErrorThrower;
|
||||||
using v8::internal::wasm::ScheduledErrorThrower;
|
using v8::internal::wasm::ScheduledErrorThrower;
|
||||||
@ -1581,7 +1586,7 @@ constexpr const char* kName_WasmTableObject = "WebAssembly.Table";
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebAssemblyInstanceGetExports(
|
void WebAssemblyInstanceGetExports(
|
||||||
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||||
v8::Isolate* isolate = args.GetIsolate();
|
v8::Isolate* isolate = args.GetIsolate();
|
||||||
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
||||||
HandleScope scope(isolate);
|
HandleScope scope(isolate);
|
||||||
@ -2020,9 +2025,9 @@ Handle<JSFunction> InstallFunc(Isolate* isolate, Handle<JSObject> object,
|
|||||||
}
|
}
|
||||||
|
|
||||||
Handle<JSFunction> InstallConstructorFunc(Isolate* isolate,
|
Handle<JSFunction> InstallConstructorFunc(Isolate* isolate,
|
||||||
Handle<JSObject> object,
|
Handle<JSObject> object,
|
||||||
const char* str,
|
const char* str,
|
||||||
FunctionCallback func) {
|
FunctionCallback func) {
|
||||||
return InstallFunc(isolate, object, str, func, 1, true, DONT_ENUM);
|
return InstallFunc(isolate, object, str, func, 1, true, DONT_ENUM);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2281,6 +2286,388 @@ void WasmJs::Install(Isolate* isolate, bool exposed_on_global_object) {
|
|||||||
runtime_error, DONT_ENUM);
|
runtime_error, DONT_ENUM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void SetMapValue(Isolate* isolate, Handle<JSMap> map, Handle<Object> key,
|
||||||
|
Handle<Object> value) {
|
||||||
|
DCHECK(!map.is_null() && !key.is_null() && !value.is_null());
|
||||||
|
Handle<Object> argv[] = {key, value};
|
||||||
|
Execution::CallBuiltin(isolate, isolate->map_set(), map, arraysize(argv),
|
||||||
|
argv)
|
||||||
|
.Check();
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Object> GetMapValue(Isolate* isolate, Handle<JSMap> map,
|
||||||
|
Handle<Object> key) {
|
||||||
|
DCHECK(!map.is_null() && !key.is_null());
|
||||||
|
Handle<Object> argv[] = {key};
|
||||||
|
return Execution::CallBuiltin(isolate, isolate->map_get(), map,
|
||||||
|
arraysize(argv), argv)
|
||||||
|
.ToHandleChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up a name in a name table. Name tables are stored under the "names"
|
||||||
|
// property of the handler and map names to index.
|
||||||
|
base::Optional<int> ResolveValueSelector(Isolate* isolate,
|
||||||
|
Handle<Name> property,
|
||||||
|
Handle<JSObject> handler,
|
||||||
|
bool enable_index_lookup) {
|
||||||
|
size_t index = 0;
|
||||||
|
if (enable_index_lookup && property->AsIntegerIndex(&index) &&
|
||||||
|
index < kMaxInt) {
|
||||||
|
return static_cast<int>(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Object> name_table =
|
||||||
|
JSObject::GetProperty(isolate, handler, "names").ToHandleChecked();
|
||||||
|
DCHECK(name_table->IsJSMap());
|
||||||
|
|
||||||
|
Handle<Object> object =
|
||||||
|
GetMapValue(isolate, Handle<JSMap>::cast(name_table), property);
|
||||||
|
if (object->IsUndefined()) return {};
|
||||||
|
DCHECK(object->IsNumeric());
|
||||||
|
return NumberToInt32(*object);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for unpacking a maybe name that makes a default with an index if
|
||||||
|
// the name is empty. If the name is not empty, it's prefixed with a $.
|
||||||
|
Handle<String> GetNameOrDefault(Isolate* isolate,
|
||||||
|
MaybeHandle<String> maybe_name,
|
||||||
|
const char* default_name_prefix, int index) {
|
||||||
|
Handle<String> name;
|
||||||
|
if (maybe_name.ToHandle(&name)) {
|
||||||
|
return isolate->factory()
|
||||||
|
->NewConsString(isolate->factory()->NewStringFromAsciiChecked("$"),
|
||||||
|
name)
|
||||||
|
.ToHandleChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum length of the default names: $memory-2147483648\0
|
||||||
|
static constexpr int kMaxStrLen = 19;
|
||||||
|
EmbeddedVector<char, kMaxStrLen> value;
|
||||||
|
DCHECK_LT(strlen(default_name_prefix) + /*strlen(kMinInt)*/ 11, kMaxStrLen);
|
||||||
|
int len = SNPrintF(value, "%s%d", default_name_prefix, index);
|
||||||
|
return isolate->factory()->InternalizeString(value.SubVector(0, len));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate names for the locals. Names either come from the name table,
|
||||||
|
// otherwise the default $varX is used.
|
||||||
|
std::vector<Handle<String>> GetLocalNames(Handle<WasmInstanceObject> instance,
|
||||||
|
Address pc) {
|
||||||
|
wasm::NativeModule* native_module = instance->module_object().native_module();
|
||||||
|
wasm::DebugInfo* debug_info = native_module->GetDebugInfo();
|
||||||
|
int num_locals = debug_info->GetNumLocals(pc);
|
||||||
|
auto* isolate = instance->GetIsolate();
|
||||||
|
|
||||||
|
wasm::ModuleWireBytes module_wire_bytes(
|
||||||
|
instance->module_object().native_module()->wire_bytes());
|
||||||
|
const wasm::WasmFunction& function = debug_info->GetFunctionAtAddress(pc);
|
||||||
|
|
||||||
|
std::vector<Handle<String>> names;
|
||||||
|
for (int i = 0; i < num_locals; ++i) {
|
||||||
|
wasm::WireBytesRef local_name_ref =
|
||||||
|
debug_info->GetLocalName(function.func_index, i);
|
||||||
|
DCHECK(module_wire_bytes.BoundsCheck(local_name_ref));
|
||||||
|
Vector<const char> name_vec =
|
||||||
|
module_wire_bytes.GetNameOrNull(local_name_ref);
|
||||||
|
names.emplace_back(GetNameOrDefault(
|
||||||
|
isolate,
|
||||||
|
name_vec.empty() ? MaybeHandle<String>()
|
||||||
|
: isolate->factory()->NewStringFromUtf8(name_vec),
|
||||||
|
"$var", i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<WasmInstanceObject> GetInstance(Isolate* isolate,
|
||||||
|
Handle<JSObject> handler) {
|
||||||
|
Handle<Object> instance =
|
||||||
|
JSObject::GetProperty(isolate, handler, "instance").ToHandleChecked();
|
||||||
|
DCHECK(instance->IsWasmInstanceObject());
|
||||||
|
return Handle<WasmInstanceObject>::cast(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
Address GetPC(Isolate* isolate, Handle<JSObject> handler) {
|
||||||
|
Handle<Object> pc =
|
||||||
|
JSObject::GetProperty(isolate, handler, "pc").ToHandleChecked();
|
||||||
|
DCHECK(pc->IsBigInt());
|
||||||
|
return Handle<BigInt>::cast(pc)->AsUint64();
|
||||||
|
}
|
||||||
|
|
||||||
|
Address GetFP(Isolate* isolate, Handle<JSObject> handler) {
|
||||||
|
Handle<Object> fp =
|
||||||
|
JSObject::GetProperty(isolate, handler, "fp").ToHandleChecked();
|
||||||
|
DCHECK(fp->IsBigInt());
|
||||||
|
return Handle<BigInt>::cast(fp)->AsUint64();
|
||||||
|
}
|
||||||
|
|
||||||
|
Address GetCalleeFP(Isolate* isolate, Handle<JSObject> handler) {
|
||||||
|
Handle<Object> callee_fp =
|
||||||
|
JSObject::GetProperty(isolate, handler, "callee_fp").ToHandleChecked();
|
||||||
|
DCHECK(callee_fp->IsBigInt());
|
||||||
|
return Handle<BigInt>::cast(callee_fp)->AsUint64();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a WasmValue to an appropriate JS representation.
|
||||||
|
static Handle<Object> WasmValueToObject(Isolate* isolate,
|
||||||
|
wasm::WasmValue value) {
|
||||||
|
auto* factory = isolate->factory();
|
||||||
|
switch (value.type().kind()) {
|
||||||
|
case wasm::ValueType::kI32:
|
||||||
|
return factory->NewNumberFromInt(value.to_i32());
|
||||||
|
case wasm::ValueType::kI64:
|
||||||
|
return BigInt::FromInt64(isolate, value.to_i64());
|
||||||
|
case wasm::ValueType::kF32:
|
||||||
|
return factory->NewNumber(value.to_f32());
|
||||||
|
case wasm::ValueType::kF64:
|
||||||
|
return factory->NewNumber(value.to_f64());
|
||||||
|
case wasm::ValueType::kS128: {
|
||||||
|
wasm::Simd128 s128 = value.to_s128();
|
||||||
|
Handle<JSArrayBuffer> buffer;
|
||||||
|
if (!isolate->factory()
|
||||||
|
->NewJSArrayBufferAndBackingStore(
|
||||||
|
kSimd128Size, InitializedFlag::kUninitialized)
|
||||||
|
.ToHandle(&buffer)) {
|
||||||
|
isolate->FatalProcessOutOfHeapMemory(
|
||||||
|
"failed to allocate backing store");
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(buffer->allocation_base(), s128.bytes(), buffer->byte_length());
|
||||||
|
return isolate->factory()->NewJSTypedArray(kExternalUint8Array, buffer, 0,
|
||||||
|
buffer->byte_length());
|
||||||
|
}
|
||||||
|
case wasm::ValueType::kRef:
|
||||||
|
return value.to_externref();
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return factory->undefined_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasLocalImpl(Isolate* isolate, Handle<Name> property,
|
||||||
|
Handle<JSObject> handler, bool enable_index_lookup) {
|
||||||
|
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
|
||||||
|
|
||||||
|
base::Optional<int> index =
|
||||||
|
ResolveValueSelector(isolate, property, handler, enable_index_lookup);
|
||||||
|
if (!index) return false;
|
||||||
|
Address pc = GetPC(isolate, handler);
|
||||||
|
|
||||||
|
wasm::DebugInfo* debug_info =
|
||||||
|
instance->module_object().native_module()->GetDebugInfo();
|
||||||
|
int num_locals = debug_info->GetNumLocals(pc);
|
||||||
|
return 0 <= index && index < num_locals;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has trap callback for the locals index space proxy.
|
||||||
|
void HasLocalCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||||
|
if (args.Length() < 2) return;
|
||||||
|
Isolate* isolate = reinterpret_cast<Isolate*>(args.GetIsolate());
|
||||||
|
DCHECK(args.This()->IsObject());
|
||||||
|
Handle<JSObject> handler =
|
||||||
|
Handle<JSObject>::cast(Utils::OpenHandle(*args.This()));
|
||||||
|
|
||||||
|
DCHECK(args[1]->IsName());
|
||||||
|
Handle<Name> property = Handle<Name>::cast(Utils::OpenHandle(*args[1]));
|
||||||
|
args.GetReturnValue().Set(HasLocalImpl(isolate, property, handler, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Object> GetLocalImpl(Isolate* isolate, Handle<Name> property,
|
||||||
|
Handle<JSObject> handler,
|
||||||
|
bool enable_index_lookup) {
|
||||||
|
Factory* factory = isolate->factory();
|
||||||
|
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
|
||||||
|
|
||||||
|
base::Optional<int> index =
|
||||||
|
ResolveValueSelector(isolate, property, handler, enable_index_lookup);
|
||||||
|
if (!index) return factory->undefined_value();
|
||||||
|
Address pc = GetPC(isolate, handler);
|
||||||
|
Address fp = GetFP(isolate, handler);
|
||||||
|
Address callee_fp = GetCalleeFP(isolate, handler);
|
||||||
|
|
||||||
|
wasm::DebugInfo* debug_info =
|
||||||
|
instance->module_object().native_module()->GetDebugInfo();
|
||||||
|
int num_locals = debug_info->GetNumLocals(pc);
|
||||||
|
if (0 > index || index >= num_locals) return factory->undefined_value();
|
||||||
|
wasm::WasmValue value = debug_info->GetLocalValue(*index, pc, fp, callee_fp);
|
||||||
|
return WasmValueToObject(isolate, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get trap callback for the locals index space proxy.
|
||||||
|
void GetLocalCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||||
|
DCHECK_GE(args.Length(), 2);
|
||||||
|
Isolate* isolate = reinterpret_cast<Isolate*>(args.GetIsolate());
|
||||||
|
DCHECK(args.This()->IsObject());
|
||||||
|
Handle<JSObject> handler =
|
||||||
|
Handle<JSObject>::cast(Utils::OpenHandle(*args.This()));
|
||||||
|
|
||||||
|
DCHECK(args[1]->IsName());
|
||||||
|
Handle<Name> property = Handle<Name>::cast(Utils::OpenHandle(*args[1]));
|
||||||
|
args.GetReturnValue().Set(
|
||||||
|
Utils::ToLocal(GetLocalImpl(isolate, property, handler, true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has trap callback for the top-level proxy.
|
||||||
|
void HasToplevelCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||||
|
DCHECK_GE(args.Length(), 2);
|
||||||
|
Isolate* isolate = reinterpret_cast<Isolate*>(args.GetIsolate());
|
||||||
|
DCHECK(args[0]->IsObject());
|
||||||
|
Handle<JSObject> target = Handle<JSObject>::cast(Utils::OpenHandle(*args[0]));
|
||||||
|
|
||||||
|
DCHECK(args[1]->IsName());
|
||||||
|
Handle<Name> property = Handle<Name>::cast(Utils::OpenHandle(*args[1]));
|
||||||
|
|
||||||
|
// First check if the property exists on the target.
|
||||||
|
if (JSObject::HasProperty(target, property).FromMaybe(false)) {
|
||||||
|
args.GetReturnValue().Set(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check the index space proxies in order if they know the property.
|
||||||
|
Handle<Object> namespace_proxy =
|
||||||
|
JSObject::GetProperty(isolate, target, "locals").ToHandleChecked();
|
||||||
|
DCHECK(namespace_proxy->IsJSProxy());
|
||||||
|
Handle<JSObject> namespace_handler(
|
||||||
|
JSObject::cast(Handle<JSProxy>::cast(namespace_proxy)->handler()),
|
||||||
|
isolate);
|
||||||
|
if (HasLocalImpl(isolate, property, namespace_handler, false)) {
|
||||||
|
args.GetReturnValue().Set(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
args.GetReturnValue().Set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get trap callback for the top-level proxy.
|
||||||
|
void GetToplevelCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||||
|
if (args.Length() < 2) return;
|
||||||
|
Isolate* isolate = reinterpret_cast<Isolate*>(args.GetIsolate());
|
||||||
|
DCHECK(args[0]->IsObject());
|
||||||
|
Handle<JSObject> target = Handle<JSObject>::cast(Utils::OpenHandle(*args[0]));
|
||||||
|
|
||||||
|
DCHECK(args[1]->IsName());
|
||||||
|
Handle<Name> property = Handle<Name>::cast(Utils::OpenHandle(*args[1]));
|
||||||
|
|
||||||
|
// First, check if the property is a proper property on the target. If so,
|
||||||
|
// return its value.
|
||||||
|
Handle<Object> value =
|
||||||
|
JSObject::GetProperty(isolate, target, property).ToHandleChecked();
|
||||||
|
if (!value->IsUndefined()) {
|
||||||
|
args.GetReturnValue().Set(Utils::ToLocal(value));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try the index space proxies in the correct disambiguation order.
|
||||||
|
Handle<Object> namespace_proxy =
|
||||||
|
JSObject::GetProperty(isolate, target, "locals").ToHandleChecked();
|
||||||
|
DCHECK(!namespace_proxy->IsUndefined() && namespace_proxy->IsJSProxy());
|
||||||
|
Handle<JSObject> namespace_handler(
|
||||||
|
JSObject::cast(Handle<JSProxy>::cast(namespace_proxy)->handler()),
|
||||||
|
isolate);
|
||||||
|
value = GetLocalImpl(isolate, property, namespace_handler, false);
|
||||||
|
if (!value->IsUndefined()) args.GetReturnValue().Set(Utils::ToLocal(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate a JSMap with name->index mappings from an ordered list of names.
|
||||||
|
Handle<JSMap> GetNameTable(Isolate* isolate,
|
||||||
|
const std::vector<Handle<String>>& names) {
|
||||||
|
Factory* factory = isolate->factory();
|
||||||
|
Handle<JSMap> name_table = factory->NewJSMap();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < names.size(); ++i) {
|
||||||
|
SetMapValue(isolate, name_table, names[i], factory->NewNumberFromInt64(i));
|
||||||
|
}
|
||||||
|
return name_table;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produce a JSProxy with a given name table and get and has trap handlers.
|
||||||
|
Handle<JSProxy> GetJSProxy(
|
||||||
|
WasmFrame* frame, Handle<JSMap> name_table,
|
||||||
|
void (*get_callback)(const v8::FunctionCallbackInfo<v8::Value>&),
|
||||||
|
void (*has_callback)(const v8::FunctionCallbackInfo<v8::Value>&)) {
|
||||||
|
Isolate* isolate = frame->isolate();
|
||||||
|
Factory* factory = isolate->factory();
|
||||||
|
Handle<JSObject> target = factory->NewJSObjectWithNullProto();
|
||||||
|
Handle<JSObject> handler = factory->NewJSObjectWithNullProto();
|
||||||
|
|
||||||
|
// Besides the name table, the get and has traps need access to the instance
|
||||||
|
// and frame information.
|
||||||
|
JSObject::AddProperty(isolate, handler, "names", name_table, DONT_ENUM);
|
||||||
|
Handle<WasmInstanceObject> instance(frame->wasm_instance(), isolate);
|
||||||
|
JSObject::AddProperty(isolate, handler, "instance", instance, DONT_ENUM);
|
||||||
|
Handle<BigInt> pc = BigInt::FromInt64(isolate, frame->pc());
|
||||||
|
JSObject::AddProperty(isolate, handler, "pc", pc, DONT_ENUM);
|
||||||
|
Handle<BigInt> fp = BigInt::FromInt64(isolate, frame->fp());
|
||||||
|
JSObject::AddProperty(isolate, handler, "fp", fp, DONT_ENUM);
|
||||||
|
Handle<BigInt> callee_fp = BigInt::FromInt64(isolate, frame->callee_fp());
|
||||||
|
JSObject::AddProperty(isolate, handler, "callee_fp", callee_fp, DONT_ENUM);
|
||||||
|
|
||||||
|
InstallFunc(isolate, handler, "get", get_callback, 3, false, READ_ONLY);
|
||||||
|
InstallFunc(isolate, handler, "has", has_callback, 2, false, READ_ONLY);
|
||||||
|
|
||||||
|
return factory->NewJSProxy(target, handler);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// This function generates the JS debug proxy for a given Wasm frame. The debug
|
||||||
|
// proxy is used when evaluating debug JS expressions on a wasm frame and let's
|
||||||
|
// the developer inspect the engine state from JS. The proxy provides the
|
||||||
|
// following interface:
|
||||||
|
//
|
||||||
|
// type WasmSimdValue = Uint8Array;
|
||||||
|
// type WasmValue = number | bigint | object | WasmSimdValue;
|
||||||
|
// type WasmFunction = (... args : WasmValue[]) = > WasmValue;
|
||||||
|
// type WasmExport = {name : string} & ({func : number} | {table : number} |
|
||||||
|
// {mem : number} | {global : number});
|
||||||
|
// type WasmImport = {name : string, module : string} &
|
||||||
|
// ({func : number} | {table : number} | {mem : number} |
|
||||||
|
// {global : number});
|
||||||
|
// interface WasmInterface {
|
||||||
|
// $globalX: WasmValue;
|
||||||
|
// $varX: WasmValue;
|
||||||
|
// $funcX(a : WasmValue /*, ...*/) : WasmValue;
|
||||||
|
// readonly $memoryX : WebAssembly.Memory;
|
||||||
|
// readonly $tableX : WebAssembly.Table;
|
||||||
|
// readonly memories : {[nameOrIndex:string | number] : WebAssembly.Memory};
|
||||||
|
// readonly tables : {[nameOrIndex:string | number] : WebAssembly.Table};
|
||||||
|
// readonly stack : WasmValue[];
|
||||||
|
// readonly imports : {[nameOrIndex:string | number] : WasmImport};
|
||||||
|
// readonly exports : {[nameOrIndex:string | number] : WasmExport};
|
||||||
|
// readonly globals : {[nameOrIndex:string | number] : WasmValue};
|
||||||
|
// readonly locals : {[nameOrIndex:string | number] : WasmValue};
|
||||||
|
// readonly functions : {[nameOrIndex:string | number] : WasmFunction};
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The wasm index spaces memories, tables, imports, exports, globals, locals
|
||||||
|
// functions are JSProxies that lazily produce values either by index or by
|
||||||
|
// name. A top level JSProxy is wrapped around those for top-level lookup of
|
||||||
|
// names in the disambiguation order memory, local, table, function, global.
|
||||||
|
// Import and export names are not globally resolved.
|
||||||
|
|
||||||
|
Handle<JSProxy> WasmJs::GetJSDebugProxy(WasmFrame* frame) {
|
||||||
|
Isolate* isolate = frame->isolate();
|
||||||
|
Factory* factory = isolate->factory();
|
||||||
|
Handle<WasmInstanceObject> instance(frame->wasm_instance(), isolate);
|
||||||
|
|
||||||
|
// The top level proxy delegates lookups to the index space proxies.
|
||||||
|
Handle<JSObject> handler = factory->NewJSObjectWithNullProto();
|
||||||
|
InstallFunc(isolate, handler, "get", GetToplevelCallback, 3, false,
|
||||||
|
READ_ONLY);
|
||||||
|
InstallFunc(isolate, handler, "has", HasToplevelCallback, 2, false,
|
||||||
|
READ_ONLY);
|
||||||
|
|
||||||
|
Handle<JSObject> target = factory->NewJSObjectWithNullProto();
|
||||||
|
|
||||||
|
// Generate JSMaps per index space for name->index lookup. Every index space
|
||||||
|
// proxy is associated with its table for local name lookup.
|
||||||
|
auto local_name_table =
|
||||||
|
GetNameTable(isolate, GetLocalNames(instance, frame->pc()));
|
||||||
|
auto locals =
|
||||||
|
GetJSProxy(frame, local_name_table, GetLocalCallback, HasLocalCallback);
|
||||||
|
JSObject::AddProperty(isolate, target, "locals", locals, READ_ONLY);
|
||||||
|
|
||||||
|
return factory->NewJSProxy(target, handler);
|
||||||
|
}
|
||||||
|
|
||||||
#undef ASSIGN
|
#undef ASSIGN
|
||||||
#undef EXTRACT_THIS
|
#undef EXTRACT_THIS
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
namespace v8 {
|
namespace v8 {
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
class JSProxy;
|
||||||
|
class WasmFrame;
|
||||||
|
|
||||||
namespace wasm {
|
namespace wasm {
|
||||||
class StreamingDecoder;
|
class StreamingDecoder;
|
||||||
@ -19,6 +21,8 @@ class WasmJs {
|
|||||||
public:
|
public:
|
||||||
V8_EXPORT_PRIVATE static void Install(Isolate* isolate,
|
V8_EXPORT_PRIVATE static void Install(Isolate* isolate,
|
||||||
bool exposed_on_global_object);
|
bool exposed_on_global_object);
|
||||||
|
|
||||||
|
V8_EXPORT_PRIVATE static Handle<JSProxy> GetJSDebugProxy(WasmFrame* frame);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
@ -525,15 +525,37 @@ WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_JavaScript) {
|
|||||||
uint16_t index = 0;
|
uint16_t index = 0;
|
||||||
runner.builder().AddIndirectFunctionTable(&index, 1);
|
runner.builder().AddIndirectFunctionTable(&index, 1);
|
||||||
|
|
||||||
TestCode<int> code(
|
TestCode<int64_t> code(
|
||||||
&runner,
|
&runner,
|
||||||
{WASM_SET_GLOBAL(0, WASM_I32V_2('B')),
|
{WASM_SET_GLOBAL(0, WASM_I32V_2('B')),
|
||||||
WASM_SET_LOCAL(0, WASM_I32V_2('A')), WASM_RETURN1(WASM_GET_LOCAL(0))},
|
WASM_SET_LOCAL(0, WASM_I64V_2('A')), WASM_RETURN1(WASM_GET_LOCAL(0))},
|
||||||
{ValueType::kI32});
|
{ValueType::kI64});
|
||||||
code.BreakOnReturn(&runner);
|
code.BreakOnReturn(&runner);
|
||||||
|
|
||||||
Isolate* isolate = runner.main_isolate();
|
Isolate* isolate = runner.main_isolate();
|
||||||
Handle<String> snippet = V8String(isolate, "213");
|
Handle<String> snippet =
|
||||||
|
V8String(isolate,
|
||||||
|
"JSON.stringify(["
|
||||||
|
//"$global0, "
|
||||||
|
//"$table0, "
|
||||||
|
"$var0, "
|
||||||
|
//"$main, "
|
||||||
|
//"$memory0, "
|
||||||
|
//"globals[0], "
|
||||||
|
//"tables[0], "
|
||||||
|
"locals[0], "
|
||||||
|
//"functions[0], "
|
||||||
|
//"memories[0], "
|
||||||
|
//"memories, "
|
||||||
|
//"tables, "
|
||||||
|
//"stack, "
|
||||||
|
//"imports, "
|
||||||
|
//"exports, "
|
||||||
|
//"globals, "
|
||||||
|
"locals, "
|
||||||
|
//"functions, "
|
||||||
|
"], (k, v) => k === 'at' || typeof v === 'undefined' || typeof "
|
||||||
|
"v === 'object' ? v : v.toString())");
|
||||||
|
|
||||||
WasmJSBreakHandler break_handler(isolate, snippet);
|
WasmJSBreakHandler break_handler(isolate, snippet);
|
||||||
CHECK(!code.Run(&runner).is_null());
|
CHECK(!code.Run(&runner).is_null());
|
||||||
@ -541,7 +563,10 @@ WASM_COMPILED_EXEC_TEST(WasmDebugEvaluate_JavaScript) {
|
|||||||
WasmJSBreakHandler::EvaluationResult result =
|
WasmJSBreakHandler::EvaluationResult result =
|
||||||
break_handler.result().ToChecked();
|
break_handler.result().ToChecked();
|
||||||
CHECK_WITH_MSG(result.error.IsNothing(), result.error.ToChecked().c_str());
|
CHECK_WITH_MSG(result.error.IsNothing(), result.error.ToChecked().c_str());
|
||||||
CHECK_EQ(result.result.ToChecked(), "213");
|
CHECK_EQ(result.result.ToChecked(), "[\"65\",\"65\",{}]");
|
||||||
|
//"[\"66\",{},\"65\",\"function 0() { [native code] }\",{},"
|
||||||
|
//"\"66\",{},\"65\",\"function 0() { [native code] }\",{},"
|
||||||
|
//"{},{},{\"0\":\"65\"},{},{},{},{},{}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
Loading…
Reference in New Issue
Block a user