mirror of
https://github.com/denoland/rusty_v8.git
synced 2024-12-27 01:29:19 -05:00
1054 lines
27 KiB
Rust
1054 lines
27 KiB
Rust
// Copyright 2019-2021 the Deno authors. All rights reserved. MIT license.
|
|
|
|
//! Bindings to the V8 Inspector API. Documentation for the V8 inspector API is
|
|
//! very sparse, so here are a few references for the next sorry soul who has to
|
|
//! dig into it.
|
|
//!
|
|
//! https://medium.com/@hyperandroid/v8-inspector-from-an-embedder-standpoint-7f9c0472e2b7
|
|
//! https://v8.dev/docs/inspector
|
|
//! https://chromedevtools.github.io/debugger-protocol-viewer/tot/
|
|
//! https://cs.chromium.org/chromium/src/v8/include/v8-inspector.h
|
|
//! https://github.com/nodejs/node/blob/v13.7.0/src/inspector_agent.cc
|
|
//! https://github.com/nodejs/node/blob/v13.7.0/src/inspector_agent.h
|
|
//! https://github.com/nodejs/node/tree/v13.7.0/src/inspector
|
|
//! https://github.com/denoland/deno/blob/v0.38.0/cli/inspector.rs
|
|
|
|
use crate::support::int;
|
|
use crate::support::CxxVTable;
|
|
use crate::support::FieldOffset;
|
|
use crate::support::Opaque;
|
|
use crate::support::RustVTable;
|
|
use crate::support::UniquePtr;
|
|
use crate::support::UniqueRef;
|
|
use crate::Context;
|
|
use crate::Isolate;
|
|
use crate::Local;
|
|
use crate::StackTrace;
|
|
use crate::Value;
|
|
use std::fmt::{self, Debug, Formatter};
|
|
|
|
extern "C" {
|
|
fn v8_inspector__V8Inspector__Channel__BASE__CONSTRUCT(
|
|
buf: &mut std::mem::MaybeUninit<Channel>,
|
|
);
|
|
|
|
fn v8_inspector__V8Inspector__Channel__sendResponse(
|
|
this: &mut Channel,
|
|
call_id: int,
|
|
message: UniquePtr<StringBuffer>,
|
|
);
|
|
fn v8_inspector__V8Inspector__Channel__sendNotification(
|
|
this: &mut Channel,
|
|
message: UniquePtr<StringBuffer>,
|
|
);
|
|
fn v8_inspector__V8Inspector__Channel__flushProtocolNotifications(
|
|
this: &mut Channel,
|
|
);
|
|
|
|
fn v8_inspector__V8InspectorClient__BASE__CONSTRUCT(
|
|
buf: &mut std::mem::MaybeUninit<V8InspectorClient>,
|
|
);
|
|
|
|
fn v8_inspector__V8InspectorClient__generateUniqueId(
|
|
this: &mut V8InspectorClient,
|
|
) -> i64;
|
|
fn v8_inspector__V8InspectorClient__runMessageLoopOnPause(
|
|
this: &mut V8InspectorClient,
|
|
context_group_id: int,
|
|
);
|
|
fn v8_inspector__V8InspectorClient__quitMessageLoopOnPause(
|
|
this: &mut V8InspectorClient,
|
|
);
|
|
fn v8_inspector__V8InspectorClient__runIfWaitingForDebugger(
|
|
this: &mut V8InspectorClient,
|
|
context_group_id: int,
|
|
);
|
|
fn v8_inspector__V8InspectorClient__consoleAPIMessage(
|
|
this: &mut V8InspectorClient,
|
|
context_group_id: int,
|
|
level: int,
|
|
message: &StringView,
|
|
url: &StringView,
|
|
line_number: u32,
|
|
column_number: u32,
|
|
stack_trace: &mut V8StackTrace,
|
|
);
|
|
|
|
fn v8_inspector__V8InspectorSession__DELETE(this: &mut V8InspectorSession);
|
|
fn v8_inspector__V8InspectorSession__dispatchProtocolMessage(
|
|
session: *mut V8InspectorSession,
|
|
message: StringView,
|
|
);
|
|
fn v8_inspector__V8InspectorSession__schedulePauseOnNextStatement(
|
|
session: *mut V8InspectorSession,
|
|
break_reason: StringView,
|
|
break_details: StringView,
|
|
);
|
|
fn v8_inspector__V8InspectorSession__canDispatchMethod(
|
|
method: StringView,
|
|
) -> bool;
|
|
|
|
fn v8_inspector__StringBuffer__DELETE(this: &mut StringBuffer);
|
|
fn v8_inspector__StringBuffer__string(this: &StringBuffer) -> StringView;
|
|
fn v8_inspector__StringBuffer__create(
|
|
source: StringView,
|
|
) -> UniquePtr<StringBuffer>;
|
|
|
|
fn v8_inspector__V8Inspector__DELETE(this: &mut V8Inspector);
|
|
fn v8_inspector__V8Inspector__create(
|
|
isolate: *mut Isolate,
|
|
client: *mut V8InspectorClient,
|
|
) -> *mut V8Inspector;
|
|
fn v8_inspector__V8Inspector__connect(
|
|
inspector: *mut V8Inspector,
|
|
context_group_id: int,
|
|
channel: *mut Channel,
|
|
state: StringView,
|
|
client_trust_level: V8InspectorClientTrustLevel,
|
|
) -> *mut V8InspectorSession;
|
|
fn v8_inspector__V8Inspector__contextCreated(
|
|
this: *mut V8Inspector,
|
|
context: *const Context,
|
|
contextGroupId: int,
|
|
humanReadableName: StringView,
|
|
auxData: StringView,
|
|
);
|
|
fn v8_inspector__V8Inspector__contextDestroyed(
|
|
this: *mut V8Inspector,
|
|
context: *const Context,
|
|
);
|
|
fn v8_inspector__V8Inspector__exceptionThrown(
|
|
this: *mut V8Inspector,
|
|
context: *const Context,
|
|
message: StringView,
|
|
exception: *const Value,
|
|
detailed_message: StringView,
|
|
url: StringView,
|
|
line_number: u32,
|
|
column_number: u32,
|
|
stack_trace: *mut V8StackTrace,
|
|
script_id: int,
|
|
) -> u32;
|
|
fn v8_inspector__V8Inspector__createStackTrace(
|
|
this: *mut V8Inspector,
|
|
stack_trace: *const StackTrace,
|
|
) -> *mut V8StackTrace;
|
|
fn v8_inspector__V8StackTrace__DELETE(this: &mut V8StackTrace);
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn v8_inspector__V8Inspector__Channel__BASE__sendResponse(
|
|
this: &mut Channel,
|
|
call_id: int,
|
|
message: UniquePtr<StringBuffer>,
|
|
) {
|
|
ChannelBase::dispatch_mut(this).send_response(call_id, message)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn v8_inspector__V8Inspector__Channel__BASE__sendNotification(
|
|
this: &mut Channel,
|
|
message: UniquePtr<StringBuffer>,
|
|
) {
|
|
ChannelBase::dispatch_mut(this).send_notification(message)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn v8_inspector__V8Inspector__Channel__BASE__flushProtocolNotifications(
|
|
this: &mut Channel,
|
|
) {
|
|
ChannelBase::dispatch_mut(this).flush_protocol_notifications()
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn v8_inspector__V8InspectorClient__BASE__generateUniqueId(
|
|
this: &mut V8InspectorClient,
|
|
) -> i64 {
|
|
V8InspectorClientBase::dispatch_mut(this).generate_unique_id()
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn v8_inspector__V8InspectorClient__BASE__runMessageLoopOnPause(
|
|
this: &mut V8InspectorClient,
|
|
context_group_id: int,
|
|
) {
|
|
V8InspectorClientBase::dispatch_mut(this)
|
|
.run_message_loop_on_pause(context_group_id)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn v8_inspector__V8InspectorClient__BASE__quitMessageLoopOnPause(
|
|
this: &mut V8InspectorClient,
|
|
) {
|
|
V8InspectorClientBase::dispatch_mut(this).quit_message_loop_on_pause()
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn v8_inspector__V8InspectorClient__BASE__runIfWaitingForDebugger(
|
|
this: &mut V8InspectorClient,
|
|
context_group_id: int,
|
|
) {
|
|
V8InspectorClientBase::dispatch_mut(this)
|
|
.run_if_waiting_for_debugger(context_group_id)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn v8_inspector__V8InspectorClient__BASE__consoleAPIMessage(
|
|
this: &mut V8InspectorClient,
|
|
context_group_id: int,
|
|
level: int,
|
|
message: &StringView,
|
|
url: &StringView,
|
|
line_number: u32,
|
|
column_number: u32,
|
|
stack_trace: &mut V8StackTrace,
|
|
) {
|
|
V8InspectorClientBase::dispatch_mut(this).console_api_message(
|
|
context_group_id,
|
|
level,
|
|
message,
|
|
url,
|
|
line_number,
|
|
column_number,
|
|
stack_trace,
|
|
)
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug)]
|
|
pub struct Channel {
|
|
_cxx_vtable: CxxVTable,
|
|
}
|
|
|
|
impl Channel {
|
|
pub fn send_response(
|
|
&mut self,
|
|
call_id: i32,
|
|
message: UniquePtr<StringBuffer>,
|
|
) {
|
|
unsafe {
|
|
v8_inspector__V8Inspector__Channel__sendResponse(self, call_id, message)
|
|
}
|
|
}
|
|
pub fn send_notification(&mut self, message: UniquePtr<StringBuffer>) {
|
|
unsafe {
|
|
v8_inspector__V8Inspector__Channel__sendNotification(self, message)
|
|
}
|
|
}
|
|
pub fn flush_protocol_notifications(&mut self) {
|
|
unsafe {
|
|
v8_inspector__V8Inspector__Channel__flushProtocolNotifications(self)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait AsChannel {
|
|
fn as_channel(&self) -> &Channel;
|
|
fn as_channel_mut(&mut self) -> &mut Channel;
|
|
}
|
|
|
|
impl AsChannel for Channel {
|
|
fn as_channel(&self) -> &Channel {
|
|
self
|
|
}
|
|
fn as_channel_mut(&mut self) -> &mut Channel {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<T> AsChannel for T
|
|
where
|
|
T: ChannelImpl,
|
|
{
|
|
fn as_channel(&self) -> &Channel {
|
|
&self.base().cxx_base
|
|
}
|
|
fn as_channel_mut(&mut self) -> &mut Channel {
|
|
&mut self.base_mut().cxx_base
|
|
}
|
|
}
|
|
|
|
pub trait ChannelImpl: AsChannel {
|
|
fn base(&self) -> &ChannelBase;
|
|
fn base_mut(&mut self) -> &mut ChannelBase;
|
|
/// This is used for calculating the offset to the base field, and care must be taken not to create any references in the process of creating the pointer because the *const Self pointer is not valid (thus resulting in instant UB)
|
|
unsafe fn base_ptr(this: *const Self) -> *const ChannelBase
|
|
where
|
|
Self: Sized;
|
|
|
|
fn send_response(&mut self, call_id: i32, message: UniquePtr<StringBuffer>);
|
|
fn send_notification(&mut self, message: UniquePtr<StringBuffer>);
|
|
fn flush_protocol_notifications(&mut self);
|
|
}
|
|
|
|
pub struct ChannelBase {
|
|
cxx_base: Channel,
|
|
offset_within_embedder: FieldOffset<Self>,
|
|
rust_vtable: RustVTable<&'static dyn ChannelImpl>,
|
|
}
|
|
|
|
impl ChannelBase {
|
|
fn construct_cxx_base() -> Channel {
|
|
unsafe {
|
|
let mut buf = std::mem::MaybeUninit::<Channel>::uninit();
|
|
v8_inspector__V8Inspector__Channel__BASE__CONSTRUCT(&mut buf);
|
|
buf.assume_init()
|
|
}
|
|
}
|
|
|
|
fn get_cxx_base_offset() -> FieldOffset<Channel> {
|
|
let buf = std::mem::MaybeUninit::<Self>::uninit();
|
|
let base = unsafe { addr_of!((*buf.as_ptr()).cxx_base) };
|
|
FieldOffset::from_ptrs(buf.as_ptr(), base)
|
|
}
|
|
|
|
fn get_offset_within_embedder<T>() -> FieldOffset<Self>
|
|
where
|
|
T: ChannelImpl,
|
|
{
|
|
let buf = std::mem::MaybeUninit::<T>::uninit();
|
|
let embedder_ptr: *const T = buf.as_ptr();
|
|
let self_ptr: *const Self = unsafe { T::base_ptr(embedder_ptr) };
|
|
FieldOffset::from_ptrs(embedder_ptr, self_ptr)
|
|
}
|
|
|
|
fn get_rust_vtable<T>() -> RustVTable<&'static dyn ChannelImpl>
|
|
where
|
|
T: ChannelImpl,
|
|
{
|
|
let buf = std::mem::MaybeUninit::<T>::uninit();
|
|
let embedder_ptr = buf.as_ptr();
|
|
let trait_object: *const dyn ChannelImpl = embedder_ptr;
|
|
let (data_ptr, vtable): (*const T, RustVTable<_>) =
|
|
unsafe { std::mem::transmute(trait_object) };
|
|
assert_eq!(data_ptr, embedder_ptr);
|
|
vtable
|
|
}
|
|
|
|
pub fn new<T>() -> Self
|
|
where
|
|
T: ChannelImpl,
|
|
{
|
|
Self {
|
|
cxx_base: Self::construct_cxx_base(),
|
|
offset_within_embedder: Self::get_offset_within_embedder::<T>(),
|
|
rust_vtable: Self::get_rust_vtable::<T>(),
|
|
}
|
|
}
|
|
|
|
pub unsafe fn dispatch(channel: &Channel) -> &dyn ChannelImpl {
|
|
let this = Self::get_cxx_base_offset().to_embedder::<Self>(channel);
|
|
let embedder = this.offset_within_embedder.to_embedder::<Opaque>(this);
|
|
std::mem::transmute((embedder, this.rust_vtable))
|
|
}
|
|
|
|
pub unsafe fn dispatch_mut(channel: &mut Channel) -> &mut dyn ChannelImpl {
|
|
let this = Self::get_cxx_base_offset().to_embedder_mut::<Self>(channel);
|
|
let vtable = this.rust_vtable;
|
|
let embedder = this.offset_within_embedder.to_embedder_mut::<Opaque>(this);
|
|
std::mem::transmute((embedder, vtable))
|
|
}
|
|
}
|
|
|
|
impl Debug for ChannelBase {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("ChannelBase")
|
|
.field("cxx_base", &self.cxx_base)
|
|
.field("offset_within_embedder", &self.offset_within_embedder)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::support::UniquePtr;
|
|
use std::sync::atomic::AtomicUsize;
|
|
use std::sync::atomic::Ordering::SeqCst;
|
|
|
|
static MESSAGE: &[u8] = b"Hello Pluto!";
|
|
static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
// Using repr(C) to preserve field ordering and test that everything works
|
|
// when the ChannelBase field is not the first element of the struct.
|
|
#[repr(C)]
|
|
#[derive(Debug)]
|
|
pub struct TestChannel {
|
|
field1: i32,
|
|
base: ChannelBase,
|
|
field2: u64,
|
|
}
|
|
|
|
impl ChannelImpl for TestChannel {
|
|
fn base(&self) -> &ChannelBase {
|
|
&self.base
|
|
}
|
|
fn base_mut(&mut self) -> &mut ChannelBase {
|
|
&mut self.base
|
|
}
|
|
unsafe fn base_ptr(_this: *const Self) -> *const ChannelBase
|
|
where
|
|
Self: Sized,
|
|
{
|
|
unsafe { addr_of!((*_this).base) }
|
|
}
|
|
fn send_response(
|
|
&mut self,
|
|
call_id: i32,
|
|
mut message: UniquePtr<StringBuffer>,
|
|
) {
|
|
assert_eq!(call_id, 999);
|
|
assert_eq!(message.as_mut().unwrap().string().len(), MESSAGE.len());
|
|
self.log_call();
|
|
}
|
|
fn send_notification(&mut self, mut message: UniquePtr<StringBuffer>) {
|
|
assert_eq!(message.as_mut().unwrap().string().len(), MESSAGE.len());
|
|
self.log_call();
|
|
}
|
|
fn flush_protocol_notifications(&mut self) {
|
|
self.log_call()
|
|
}
|
|
}
|
|
|
|
impl TestChannel {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
base: ChannelBase::new::<Self>(),
|
|
field1: -42,
|
|
field2: 420,
|
|
}
|
|
}
|
|
|
|
fn log_call(&self) {
|
|
assert_eq!(self.field1, -42);
|
|
assert_eq!(self.field2, 420);
|
|
CALL_COUNT.fetch_add(1, SeqCst);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_channel() {
|
|
let mut channel = TestChannel::new();
|
|
let msg_view = StringView::from(MESSAGE);
|
|
channel.send_response(999, StringBuffer::create(msg_view));
|
|
assert_eq!(CALL_COUNT.swap(0, SeqCst), 1);
|
|
channel.send_notification(StringBuffer::create(msg_view));
|
|
assert_eq!(CALL_COUNT.swap(0, SeqCst), 1);
|
|
channel.flush_protocol_notifications();
|
|
assert_eq!(CALL_COUNT.swap(0, SeqCst), 1);
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug)]
|
|
pub struct V8InspectorClient {
|
|
_cxx_vtable: CxxVTable,
|
|
}
|
|
|
|
impl V8InspectorClient {
|
|
pub fn run_message_loop_on_pause(&mut self, context_group_id: i32) {
|
|
unsafe {
|
|
v8_inspector__V8InspectorClient__runMessageLoopOnPause(
|
|
self,
|
|
context_group_id,
|
|
)
|
|
}
|
|
}
|
|
|
|
pub fn quit_message_loop_on_pause(&mut self) {
|
|
unsafe { v8_inspector__V8InspectorClient__quitMessageLoopOnPause(self) }
|
|
}
|
|
|
|
pub fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) {
|
|
unsafe {
|
|
v8_inspector__V8InspectorClient__runIfWaitingForDebugger(
|
|
self,
|
|
context_group_id,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn console_api_message(
|
|
&mut self,
|
|
context_group_id: i32,
|
|
level: i32,
|
|
message: &StringView,
|
|
url: &StringView,
|
|
line_number: u32,
|
|
column_number: u32,
|
|
stack_trace: &mut V8StackTrace,
|
|
) {
|
|
unsafe {
|
|
v8_inspector__V8InspectorClient__consoleAPIMessage(
|
|
self,
|
|
context_group_id,
|
|
level,
|
|
message,
|
|
url,
|
|
line_number,
|
|
column_number,
|
|
stack_trace,
|
|
)
|
|
}
|
|
}
|
|
|
|
pub fn generate_unique_id(&mut self) -> i64 {
|
|
unsafe { v8_inspector__V8InspectorClient__generateUniqueId(self) }
|
|
}
|
|
}
|
|
|
|
pub trait AsV8InspectorClient {
|
|
fn as_client(&self) -> &V8InspectorClient;
|
|
fn as_client_mut(&mut self) -> &mut V8InspectorClient;
|
|
}
|
|
|
|
impl AsV8InspectorClient for V8InspectorClient {
|
|
fn as_client(&self) -> &V8InspectorClient {
|
|
self
|
|
}
|
|
fn as_client_mut(&mut self) -> &mut V8InspectorClient {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<T> AsV8InspectorClient for T
|
|
where
|
|
T: V8InspectorClientImpl,
|
|
{
|
|
fn as_client(&self) -> &V8InspectorClient {
|
|
&self.base().cxx_base
|
|
}
|
|
fn as_client_mut(&mut self) -> &mut V8InspectorClient {
|
|
&mut self.base_mut().cxx_base
|
|
}
|
|
}
|
|
|
|
#[allow(unused_variables)]
|
|
pub trait V8InspectorClientImpl: AsV8InspectorClient {
|
|
fn base(&self) -> &V8InspectorClientBase;
|
|
fn base_mut(&mut self) -> &mut V8InspectorClientBase;
|
|
/// This is used for calculating the offset to the base field, and care must be taken not to create any references in the process of creating the pointer because the *const Self pointer is not valid (thus resulting in instant UB)
|
|
unsafe fn base_ptr(this: *const Self) -> *const V8InspectorClientBase
|
|
where
|
|
Self: Sized;
|
|
|
|
fn run_message_loop_on_pause(&mut self, context_group_id: i32) {}
|
|
fn quit_message_loop_on_pause(&mut self) {}
|
|
fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) {}
|
|
|
|
fn generate_unique_id(&mut self) -> i64 {
|
|
0 // 0 = let V8 pick a unique id itself
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn console_api_message(
|
|
&mut self,
|
|
context_group_id: i32,
|
|
level: i32,
|
|
message: &StringView,
|
|
url: &StringView,
|
|
line_number: u32,
|
|
column_number: u32,
|
|
stack_trace: &mut V8StackTrace,
|
|
) {
|
|
}
|
|
}
|
|
|
|
pub struct V8InspectorClientBase {
|
|
cxx_base: V8InspectorClient,
|
|
offset_within_embedder: FieldOffset<Self>,
|
|
rust_vtable: RustVTable<&'static dyn V8InspectorClientImpl>,
|
|
}
|
|
|
|
impl V8InspectorClientBase {
|
|
fn construct_cxx_base() -> V8InspectorClient {
|
|
unsafe {
|
|
let mut buf = std::mem::MaybeUninit::<V8InspectorClient>::uninit();
|
|
v8_inspector__V8InspectorClient__BASE__CONSTRUCT(&mut buf);
|
|
buf.assume_init()
|
|
}
|
|
}
|
|
|
|
fn get_cxx_base_offset() -> FieldOffset<V8InspectorClient> {
|
|
let buf = std::mem::MaybeUninit::<Self>::uninit();
|
|
let base = unsafe { addr_of!((*buf.as_ptr()).cxx_base) };
|
|
FieldOffset::from_ptrs(buf.as_ptr(), base)
|
|
}
|
|
|
|
fn get_offset_within_embedder<T>() -> FieldOffset<Self>
|
|
where
|
|
T: V8InspectorClientImpl,
|
|
{
|
|
let buf = std::mem::MaybeUninit::<T>::uninit();
|
|
let embedder_ptr: *const T = buf.as_ptr();
|
|
let self_ptr: *const Self = unsafe { T::base_ptr(embedder_ptr) };
|
|
FieldOffset::from_ptrs(embedder_ptr, self_ptr)
|
|
}
|
|
|
|
fn get_rust_vtable<T>() -> RustVTable<&'static dyn V8InspectorClientImpl>
|
|
where
|
|
T: V8InspectorClientImpl,
|
|
{
|
|
let buf = std::mem::MaybeUninit::<T>::uninit();
|
|
let embedder_ptr = buf.as_ptr();
|
|
let trait_object: *const dyn V8InspectorClientImpl = embedder_ptr;
|
|
let (data_ptr, vtable): (*const T, RustVTable<_>) =
|
|
unsafe { std::mem::transmute(trait_object) };
|
|
assert_eq!(data_ptr, embedder_ptr);
|
|
vtable
|
|
}
|
|
|
|
pub fn new<T>() -> Self
|
|
where
|
|
T: V8InspectorClientImpl,
|
|
{
|
|
Self {
|
|
cxx_base: Self::construct_cxx_base(),
|
|
offset_within_embedder: Self::get_offset_within_embedder::<T>(),
|
|
rust_vtable: Self::get_rust_vtable::<T>(),
|
|
}
|
|
}
|
|
|
|
pub unsafe fn dispatch(
|
|
client: &V8InspectorClient,
|
|
) -> &dyn V8InspectorClientImpl {
|
|
let this = Self::get_cxx_base_offset().to_embedder::<Self>(client);
|
|
let embedder = this.offset_within_embedder.to_embedder::<Opaque>(this);
|
|
std::mem::transmute((embedder, this.rust_vtable))
|
|
}
|
|
|
|
pub unsafe fn dispatch_mut(
|
|
client: &mut V8InspectorClient,
|
|
) -> &mut dyn V8InspectorClientImpl {
|
|
let this = Self::get_cxx_base_offset().to_embedder_mut::<Self>(client);
|
|
let vtable = this.rust_vtable;
|
|
let embedder = this.offset_within_embedder.to_embedder_mut::<Opaque>(this);
|
|
std::mem::transmute((embedder, vtable))
|
|
}
|
|
}
|
|
|
|
impl Debug for V8InspectorClientBase {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("V8InspectorClientBase")
|
|
.field("cxx_base", &self.cxx_base)
|
|
.field("offset_within_embedder", &self.offset_within_embedder)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug)]
|
|
pub struct V8InspectorSession(Opaque);
|
|
|
|
impl V8InspectorSession {
|
|
pub fn can_dispatch_method(method: StringView) -> bool {
|
|
unsafe { v8_inspector__V8InspectorSession__canDispatchMethod(method) }
|
|
}
|
|
|
|
pub fn dispatch_protocol_message(&mut self, message: StringView) {
|
|
unsafe {
|
|
v8_inspector__V8InspectorSession__dispatchProtocolMessage(self, message)
|
|
}
|
|
}
|
|
|
|
pub fn schedule_pause_on_next_statement(
|
|
&mut self,
|
|
reason: StringView,
|
|
detail: StringView,
|
|
) {
|
|
unsafe {
|
|
v8_inspector__V8InspectorSession__schedulePauseOnNextStatement(
|
|
self, reason, detail,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for V8InspectorSession {
|
|
fn drop(&mut self) {
|
|
unsafe { v8_inspector__V8InspectorSession__DELETE(self) };
|
|
}
|
|
}
|
|
|
|
// TODO: in C++, this class is intended to be user-extensible, just like
|
|
// like `Task`, `Client`, `Channel`. In Rust this would ideally also be the
|
|
// case, but currently to obtain a `UniquePtr<StringBuffer>` is by making a
|
|
// copy using `StringBuffer::create()`.
|
|
#[repr(C)]
|
|
#[derive(Debug)]
|
|
pub struct StringBuffer {
|
|
_cxx_vtable: CxxVTable,
|
|
}
|
|
|
|
// TODO: make it possible to obtain a `UniquePtr<StringBuffer>` directly from
|
|
// an owned `Vec<u8>` or `Vec<u16>`,
|
|
impl StringBuffer {
|
|
// The C++ class definition does not declare `string()` to be a const method,
|
|
// therefore we declare self as mutable here.
|
|
// TODO: figure out whether it'd be safe to assume a const receiver here.
|
|
// That would make it possible to implement `Deref<Target = StringBuffer>`.
|
|
pub fn string(&self) -> StringView {
|
|
unsafe { v8_inspector__StringBuffer__string(self) }
|
|
}
|
|
|
|
/// This method copies contents.
|
|
pub fn create(source: StringView) -> UniquePtr<StringBuffer> {
|
|
unsafe { v8_inspector__StringBuffer__create(source) }
|
|
}
|
|
}
|
|
|
|
impl Drop for StringBuffer {
|
|
fn drop(&mut self) {
|
|
unsafe { v8_inspector__StringBuffer__DELETE(self) }
|
|
}
|
|
}
|
|
|
|
unsafe impl Send for StringBuffer {}
|
|
use std::iter::ExactSizeIterator;
|
|
use std::iter::IntoIterator;
|
|
use std::marker::PhantomData;
|
|
use std::ops::Deref;
|
|
use std::ptr::addr_of;
|
|
use std::ptr::null;
|
|
use std::ptr::NonNull;
|
|
use std::slice;
|
|
use std::string;
|
|
|
|
// Notes:
|
|
// * This class is ported, not wrapped using bindings.
|
|
// * Since Rust `repr(bool)` is not allowed, we're assuming that `bool` and
|
|
// `u8` have the same size. This is assumption is checked in 'support.h'.
|
|
// TODO: find/open upstream issue to allow #[repr(bool)] support.
|
|
|
|
#[derive(Clone, Debug, Copy)]
|
|
#[repr(u8)]
|
|
pub enum StringView<'a> {
|
|
// Do not reorder!
|
|
U16(CharacterArray<'a, u16>),
|
|
U8(CharacterArray<'a, u8>),
|
|
}
|
|
|
|
impl StringView<'static> {
|
|
pub fn empty() -> Self {
|
|
Self::U8(CharacterArray::<'static, u8>::empty())
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for StringView<'_> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
Self::U16(v) => write!(f, "{}", v),
|
|
Self::U8(v) => write!(f, "{}", v),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a [u8]> for StringView<'a> {
|
|
fn from(v: &'a [u8]) -> Self {
|
|
Self::U8(CharacterArray::<'a, u8>::from(v))
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a [u16]> for StringView<'a> {
|
|
fn from(v: &'a [u16]) -> Self {
|
|
Self::U16(CharacterArray::<'a, u16>::from(v))
|
|
}
|
|
}
|
|
|
|
impl<'a> StringView<'a> {
|
|
pub fn is_8bit(&self) -> bool {
|
|
match self {
|
|
Self::U16(..) => false,
|
|
Self::U8(..) => true,
|
|
}
|
|
}
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
self.len() == 0
|
|
}
|
|
|
|
pub fn len(&self) -> usize {
|
|
match self {
|
|
Self::U16(v) => v.len(),
|
|
Self::U8(v) => v.len(),
|
|
}
|
|
}
|
|
|
|
pub fn characters8(&self) -> Option<&[u8]> {
|
|
match self {
|
|
Self::U16(..) => None,
|
|
Self::U8(v) => Some(v),
|
|
}
|
|
}
|
|
|
|
pub fn characters16(&self) -> Option<&[u16]> {
|
|
match self {
|
|
Self::U16(v) => Some(v),
|
|
Self::U8(..) => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> IntoIterator for StringView<'a> {
|
|
type IntoIter = StringViewIterator<'a>;
|
|
type Item = u16;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
StringViewIterator { view: self, pos: 0 }
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct CharacterArray<'a, T> {
|
|
m_length: usize,
|
|
m_characters: *const T,
|
|
_phantom: PhantomData<&'a T>,
|
|
}
|
|
|
|
impl CharacterArray<'static, u8> {
|
|
pub fn empty() -> Self {
|
|
Self {
|
|
m_length: 0,
|
|
m_characters: null(),
|
|
_phantom: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, T> CharacterArray<'a, T>
|
|
where
|
|
T: Copy,
|
|
{
|
|
#[inline(always)]
|
|
fn len(&self) -> usize {
|
|
self.m_length
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn get_at(&self, index: usize) -> Option<T> {
|
|
if index < self.m_length {
|
|
Some(unsafe { *self.m_characters.add(index) })
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe impl<'a, T> Send for CharacterArray<'a, T> where T: Copy {}
|
|
unsafe impl<'a, T> Sync for CharacterArray<'a, T> where T: Sync {}
|
|
|
|
impl fmt::Display for CharacterArray<'_, u8> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.write_str(
|
|
self
|
|
.iter()
|
|
.cloned()
|
|
.map(char::from)
|
|
.collect::<string::String>()
|
|
.as_str(),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for CharacterArray<'_, u16> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.write_str(&string::String::from_utf16_lossy(self))
|
|
}
|
|
}
|
|
|
|
impl<'a, T> From<&'a [T]> for CharacterArray<'a, T> {
|
|
fn from(v: &'a [T]) -> Self {
|
|
Self {
|
|
m_length: v.len(),
|
|
m_characters: v.as_ptr(),
|
|
_phantom: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, T> Deref for CharacterArray<'a, T> {
|
|
type Target = [T];
|
|
|
|
fn deref(&self) -> &[T] {
|
|
let Self {
|
|
m_length,
|
|
mut m_characters,
|
|
..
|
|
} = *self;
|
|
if m_characters.is_null() {
|
|
assert_eq!(m_length, 0);
|
|
m_characters = NonNull::dangling().as_ptr()
|
|
};
|
|
unsafe { slice::from_raw_parts(m_characters, m_length) }
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct StringViewIterator<'a> {
|
|
view: StringView<'a>,
|
|
pos: usize,
|
|
}
|
|
|
|
impl<'a> Iterator for StringViewIterator<'a> {
|
|
type Item = u16;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
let result = Some(match self.view {
|
|
StringView::U16(v) => v.get_at(self.pos)?,
|
|
StringView::U8(v) => u16::from(v.get_at(self.pos)?),
|
|
});
|
|
self.pos += 1;
|
|
result
|
|
}
|
|
}
|
|
|
|
impl<'a> ExactSizeIterator for StringViewIterator<'a> {
|
|
fn len(&self) -> usize {
|
|
self.view.len()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn string_view_display() {
|
|
let ok: [u16; 2] = [111, 107];
|
|
assert_eq!("ok", format!("{}", StringView::from(&ok[..])));
|
|
assert_eq!("ok", format!("{}", StringView::from(&b"ok"[..])));
|
|
assert_eq!("ØÞ", format!("{}", StringView::from(&[216u8, 222u8][..])));
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
|
|
#[repr(C)]
|
|
pub enum V8InspectorClientTrustLevel {
|
|
Untrusted = 0,
|
|
FullyTrusted = 1,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug)]
|
|
pub struct V8Inspector(Opaque);
|
|
|
|
impl V8Inspector {
|
|
pub fn create<T>(
|
|
isolate: &mut Isolate,
|
|
client: &mut T,
|
|
) -> UniqueRef<V8Inspector>
|
|
where
|
|
T: AsV8InspectorClient,
|
|
{
|
|
unsafe {
|
|
UniqueRef::from_raw(v8_inspector__V8Inspector__create(
|
|
isolate,
|
|
client.as_client_mut(),
|
|
))
|
|
}
|
|
}
|
|
|
|
pub fn connect<T>(
|
|
&mut self,
|
|
context_group_id: i32,
|
|
channel: &mut T,
|
|
state: StringView,
|
|
client_trust_level: V8InspectorClientTrustLevel,
|
|
) -> UniqueRef<V8InspectorSession>
|
|
where
|
|
T: AsChannel,
|
|
{
|
|
unsafe {
|
|
UniqueRef::from_raw(v8_inspector__V8Inspector__connect(
|
|
self,
|
|
context_group_id,
|
|
channel.as_channel_mut(),
|
|
state,
|
|
client_trust_level,
|
|
))
|
|
}
|
|
}
|
|
|
|
/// Note: this method deviates from the C++ API here because it's a lot of
|
|
/// work to bind the V8ContextInfo, which is not used elsewhere.
|
|
pub fn context_created(
|
|
&mut self,
|
|
context: Local<Context>,
|
|
context_group_id: i32,
|
|
human_readable_name: StringView,
|
|
aux_data: StringView,
|
|
) {
|
|
unsafe {
|
|
v8_inspector__V8Inspector__contextCreated(
|
|
self,
|
|
&*context,
|
|
context_group_id,
|
|
human_readable_name,
|
|
aux_data,
|
|
)
|
|
}
|
|
}
|
|
|
|
pub fn context_destroyed(&mut self, context: Local<Context>) {
|
|
unsafe { v8_inspector__V8Inspector__contextDestroyed(self, &*context) }
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn exception_thrown(
|
|
&mut self,
|
|
context: Local<Context>,
|
|
message: StringView,
|
|
exception: Local<Value>,
|
|
detailed_message: StringView,
|
|
url: StringView,
|
|
line_number: u32,
|
|
column_number: u32,
|
|
stack_trace: UniquePtr<V8StackTrace>,
|
|
script_id: i32,
|
|
) -> u32 {
|
|
unsafe {
|
|
v8_inspector__V8Inspector__exceptionThrown(
|
|
self,
|
|
&*context,
|
|
message,
|
|
&*exception,
|
|
detailed_message,
|
|
url,
|
|
line_number,
|
|
column_number,
|
|
stack_trace.into_raw(),
|
|
script_id,
|
|
)
|
|
}
|
|
}
|
|
|
|
pub fn create_stack_trace(
|
|
&mut self,
|
|
stack_trace: Local<StackTrace>,
|
|
) -> UniquePtr<V8StackTrace> {
|
|
unsafe {
|
|
UniquePtr::from_raw(v8_inspector__V8Inspector__createStackTrace(
|
|
self,
|
|
&*stack_trace,
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for V8Inspector {
|
|
fn drop(&mut self) {
|
|
unsafe { v8_inspector__V8Inspector__DELETE(self) };
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug)]
|
|
pub struct V8StackTrace {
|
|
_cxx_vtable: CxxVTable,
|
|
}
|
|
|
|
impl Drop for V8StackTrace {
|
|
fn drop(&mut self) {
|
|
unsafe { v8_inspector__V8StackTrace__DELETE(self) };
|
|
}
|
|
}
|
|
|
|
// TODO(bnoordhuis) This needs to be fleshed out more but that can wait
|
|
// until it's actually needed.
|