use ::std::cell::Cell;
use mozjs::jsapi::*;
use mozjs::jsval::{
DoubleValue, JSVal, ObjectValue, StringValue, UndefinedValue,
};
use mozjs::rooted;
use mozjs::rust::wrappers2::GetBuiltinClass;
const RESET: &str = "\x1b[0m";
const COLOR_STRING: &str = "\x1b[32m"; const COLOR_NUMBER: &str = "\x1b[33m"; const COLOR_KEY: &str = "\x1b[36m"; const COLOR_SPECIAL: &str = "\x1b[33m"; const COLOR_ERROR: &str = "\x1b[31m";
const PLACEHOLDER_THREW: &str = "<exception>";
thread_local! {
static SWALLOWED_EXCEPTIONS: Cell<u32> = const { Cell::new(0) };
}
fn note_swallowed() {
SWALLOWED_EXCEPTIONS.with(|c| c.set(c.get().saturating_add(1)));
}
pub fn swallowed_exception_count() -> u32 {
SWALLOWED_EXCEPTIONS.with(|c| c.get())
}
struct InspectOpts {
depth: u32,
colors: bool,
}
fn paint(opts: &InspectOpts, s: String, color: &str) -> String {
if opts.colors {
format!("{}{}{}", color, s, RESET)
} else {
s
}
}
fn format_key(key: &str) -> String {
let valid = !key.is_empty()
&& key
.chars()
.enumerate()
.all(|(i, c)| c == '_' || c == '$' || c.is_alphanumeric() && (i > 0 || !c.is_ascii_digit()));
if valid {
key.to_string()
} else {
escape_quoted(key)
}
}
fn escape_quoted(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
c => out.push(c),
}
}
out.push('"');
out
}
fn format_number(n: f64) -> String {
if n.is_nan() {
"NaN".to_string()
} else if n.is_infinite() {
if n > 0.0 { "Infinity".to_string() } else { "-Infinity".to_string() }
} else if n == n.trunc() && n.abs() < 1e21 {
format!("{}", n as i64)
} else {
format!("{}", n)
}
}
fn civil_from_days(z: i64) -> (i64, u32, u32) {
let z = z + 719_468;
let era = z.div_euclid(146_097);
let doe = z.rem_euclid(146_097);
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
let y = yoe + era * 400 + i64::from(m <= 2);
(y, m, d)
}
fn iso_string_from_msec(msec: f64) -> String {
let ms = msec as i64;
let days = ms.div_euclid(86_400_000);
let mut tod = ms.rem_euclid(86_400_000);
let (y, m, d) = civil_from_days(days);
let hour = tod / 3_600_000;
tod -= hour * 3_600_000;
let min = tod / 60_000;
tod -= min * 60_000;
let sec = tod / 1_000;
let frac = tod - sec * 1_000;
let year = if (0..=9999).contains(&y) {
format!("{:04}", y)
} else {
let sign = if y < 0 { "-" } else { "+" };
format!("{}{:06}", sign, y.abs())
};
format!(
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
year, m, d, hour, min, sec, frac
)
}
const ERROR_PROTO_TABLE: &[(JSProtoKey, &str)] = &[
(JSProtoKey::JSProto_Error, "Error"),
(JSProtoKey::JSProto_TypeError, "TypeError"),
(JSProtoKey::JSProto_RangeError, "RangeError"),
(JSProtoKey::JSProto_EvalError, "EvalError"),
(JSProtoKey::JSProto_ReferenceError, "ReferenceError"),
(JSProtoKey::JSProto_SyntaxError, "SyntaxError"),
(JSProtoKey::JSProto_URIError, "URIError"),
(JSProtoKey::JSProto_AggregateError, "AggregateError"),
];
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn native_error_class_name(cx: *mut JSContext, obj: *mut JSObject) -> String {
let mut wrapped = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
let mut cur: *mut JSObject = obj;
for _ in 0..10 {
rooted!(&in(cx_ref) let cur_root = cur);
let mut next: *mut JSObject = ::std::ptr::null_mut();
if !JS_GetPrototype(
cx,
cur_root.handle().into(),
MutableHandle::<*mut JSObject> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut next,
},
) {
JS_ClearPendingException(cx);
note_swallowed();
break;
}
if next.is_null() {
break;
}
for (key, name) in ERROR_PROTO_TABLE {
let mut proto: *mut JSObject = ::std::ptr::null_mut();
if JS_GetClassPrototype(
cx,
*key,
MutableHandle::<*mut JSObject> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut proto,
},
) && proto == next
{
return name.to_string();
}
}
cur = next;
}
"Error".to_string()
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn obj_string_prop(cx: *mut JSContext, obj: mozjs::rust::Handle<*mut JSObject>, name: &[u8]) -> String {
let name_z = bun_core::ZBox::from_bytes(name);
let mut v = UndefinedValue();
if !JS_GetProperty(
cx,
obj.into(),
name_z.as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut v,
},
) {
JS_ClearPendingException(cx);
note_swallowed();
return PLACEHOLDER_THREW.to_string();
}
if v.is_string() {
crate::js_to_rust_string(cx, v)
} else {
String::new()
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn value_to_display_string(
cx: *mut JSContext,
cx_ref: &mut mozjs::context::JSContext,
v: JSVal,
) -> String {
if v.is_string() {
return crate::js_to_rust_string(cx, v);
}
rooted!(&in(cx_ref) let hv = v);
let jsstr = mozjs::rust::ToString(cx_ref, hv.handle());
if !jsstr.is_null() {
let sval = StringValue(&*jsstr);
return crate::js_to_rust_string(cx, sval);
}
JS_ClearPendingException(cx);
note_swallowed();
PLACEHOLDER_THREW.to_string()
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn own_prop_string(
cx: *mut JSContext,
cx_ref: &mut mozjs::context::JSContext,
obj: mozjs::rust::Handle<*mut JSObject>,
name: &[u8],
) -> Option<String> {
let name_z = bun_core::ZBox::from_bytes(name);
let mut found = false;
if !JS_HasOwnProperty(cx, obj.into(), name_z.as_ptr(), &mut found) {
JS_ClearPendingException(cx);
note_swallowed();
return None;
}
if !found {
return None;
}
let mut v = UndefinedValue();
if !JS_GetProperty(
cx,
obj.into(),
name_z.as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut v,
},
) {
JS_ClearPendingException(cx);
note_swallowed();
return None;
}
Some(value_to_display_string(cx, cx_ref, v))
}
#[allow(unsafe_op_in_unsafe_fn)]
pub(crate) unsafe fn received_type_fragment(cx: *mut JSContext, val: JSVal) -> String {
if val.is_number() {
return format!("type number ({})", format_number(val.to_number()));
}
if val.is_string() {
return format!("type string ('{}')", crate::js_to_rust_string(cx, val));
}
if val.is_boolean() {
return format!("type boolean ({})", val.to_boolean());
}
if val.is_symbol() || val.is_bigint() {
let mut wrapped =
mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let hv = val);
let jsstr = mozjs::rust::ToString(cx_ref, hv.handle());
if !jsstr.is_null() {
let s = crate::js_to_rust_string(cx, StringValue(&*jsstr));
return if val.is_bigint() {
format!("type bigint ({}n)", s)
} else {
format!("type symbol ({})", s)
};
}
JS_ClearPendingException(cx);
note_swallowed();
return if val.is_bigint() { "type bigint".to_string() } else { "type symbol".to_string() };
}
"type object".to_string()
}
#[allow(unsafe_op_in_unsafe_fn)]
pub(crate) unsafe fn throw_invalid_arg_type(cx: *mut JSContext, msg: &str) -> bool {
let c_msg = ::std::ffi::CString::new(msg)
.unwrap_or_else(|_| ::std::ffi::CString::new("error").unwrap());
{
let mut cx_s = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
mozjs::error::throw_type_error_safe(&mut cx_s, c_msg.as_ref());
}
if JS_IsExceptionPending(cx) {
rooted!(in(cx) let mut exn = UndefinedValue());
JS_GetPendingException(cx, exn.handle_mut().into());
let exn_val = exn.get();
if !exn_val.is_undefined() && exn_val.is_object() {
let cx_ref_err =
mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
rooted!(&in(cx_ref_err) let exn_root = exn_val.to_object());
let code_str = JS_NewStringCopyZ(cx, bun_core::ZBox::from_bytes(b"ERR_INVALID_ARG_TYPE").as_ptr());
if !code_str.is_null() {
let code_val = StringValue(&*code_str);
rooted!(&in(cx_ref_err) let code_r = code_val);
JS_DefineProperty(
cx,
exn_root.handle().into(),
c"code".as_ptr(),
code_r.handle().into(),
JSPROP_ENUMERATE as u32,
);
JS_SetPendingException(
cx,
exn.handle().into(),
ExceptionStackBehavior::DoNotCapture,
);
}
}
}
false
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn inspect_val(
cx: *mut JSContext,
val: JSVal,
depth: u32,
opts: &InspectOpts,
seen: &mut Vec<usize>,
) -> String {
if val.is_undefined() {
return paint(opts, "undefined".into(), COLOR_SPECIAL);
}
if val.is_null() {
return paint(opts, "null".into(), COLOR_SPECIAL);
}
if val.is_boolean() {
return paint(
opts,
if val.to_boolean() { "true" } else { "false" }.into(),
COLOR_SPECIAL,
);
}
if val.is_int32() {
return paint(opts, format_number(val.to_int32() as f64), COLOR_NUMBER);
}
if val.is_double() {
return paint(opts, format_number(val.to_double()), COLOR_NUMBER);
}
if val.is_string() {
let s = crate::js_to_rust_string(cx, val);
return paint(opts, escape_quoted(&s), COLOR_STRING);
}
if val.is_symbol() || val.is_bigint() {
let mut wrapped = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(
cx,
));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let sv = val);
let jsstr = mozjs::rust::ToString(cx_ref, sv.handle());
if !jsstr.is_null() {
let str_val = StringValue(&*jsstr);
return paint(
opts,
crate::js_to_rust_string(cx, str_val),
if val.is_bigint() { COLOR_NUMBER } else { COLOR_SPECIAL },
);
}
JS_ClearPendingException(cx);
note_swallowed();
return paint(opts, "Symbol()".into(), COLOR_SPECIAL);
}
if !val.is_object() {
return "undefined".to_string();
}
let mut wrapped = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let obj = val.to_object());
let obj_ptr = obj.get() as usize;
if seen.contains(&obj_ptr) {
return "[Circular]".to_string();
}
let at_depth_cap = depth == 0;
let mut cls = ESClass::Other;
let classified = GetBuiltinClass(cx_ref, obj.handle(), &mut cls);
if !classified {
JS_ClearPendingException(cx);
note_swallowed();
cls = ESClass::Other;
}
if JS_ObjectIsFunction(obj.get()) {
let name = obj_string_prop(cx, obj.handle(), b"name");
return if name.is_empty() {
"[Function (anonymous)]".to_string()
} else {
format!("[Function: {}]", name)
};
}
match cls {
ESClass::RegExp => {
let src = obj_string_prop(cx, obj.handle(), b"source");
let flags = obj_string_prop(cx, obj.handle(), b"flags");
return format!("/{}/{}", src, flags);
}
ESClass::Date => {
let mut valid = false;
if !mozjs_sys::jsapi::js::DateIsValid(cx, obj.handle().into(), &mut valid) {
JS_ClearPendingException(cx);
note_swallowed();
valid = false;
}
if !valid {
return "Invalid Date".to_string();
}
let mut msec: f64 = 0.0;
if !mozjs_sys::jsapi::js::DateGetMsecSinceEpoch(cx, obj.handle().into(), &mut msec)
|| !msec.is_finite()
{
JS_ClearPendingException(cx);
note_swallowed();
return "Invalid Date".to_string();
}
return iso_string_from_msec(msec);
}
ESClass::Error => {
let name = match own_prop_string(cx, cx_ref, obj.handle(), b"name") {
Some(n) => n,
None => native_error_class_name(cx, obj.get()),
};
let msg = own_prop_string(cx, cx_ref, obj.handle(), b"message").unwrap_or_default();
let head = if msg.is_empty() {
if name.is_empty() { "Error".to_string() } else { name }
} else if name.is_empty() {
format!("Error: {}", msg)
} else {
format!("{}: {}", name, msg)
};
let stack = obj_string_prop(cx, obj.handle(), b"stack");
if !stack.is_empty() {
if let Some(second) = stack.split('\n').nth(1) {
let trimmed = second.trim();
if !trimmed.is_empty() {
return paint(
opts,
format!("{}\n {}", head, trimmed.trim_start_matches("at ")),
COLOR_ERROR,
);
}
}
}
return paint(opts, head, COLOR_ERROR);
}
ESClass::Promise => {
let state = JS::GetPromiseState(obj.handle().into());
let body = match state {
PromiseState::Fulfilled => {
let mut rv = UndefinedValue();
mozjs::glue::JS_GetPromiseResult(
obj.handle().into(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rv,
},
);
seen.push(obj_ptr);
let s = inspect_val(cx, rv, depth.saturating_sub(1), opts, seen);
seen.pop();
s
}
PromiseState::Rejected => {
let mut rv = UndefinedValue();
mozjs::glue::JS_GetPromiseResult(
obj.handle().into(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rv,
},
);
JS_ClearPendingException(cx);
seen.push(obj_ptr);
let s = inspect_val(cx, rv, depth.saturating_sub(1), opts, seen);
seen.pop();
format!("<rejected> {}", s)
}
_ => "<pending>".to_string(),
};
return format!("Promise {{ {} }}", body);
}
ESClass::Map | ESClass::Set => {
if at_depth_cap {
return if cls == ESClass::Map { "[Map]".into() } else { "[Set]".into() };
}
seen.push(obj_ptr);
let is_map = cls == ESClass::Map;
let head = if is_map { "Map" } else { "Set" };
let mut iter_val = UndefinedValue();
let made_iter = if is_map {
mozjs_sys::jsapi::JS::MapEntries(
cx,
obj.handle().into(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut iter_val,
},
)
} else {
mozjs_sys::jsapi::JS::SetValues(
cx,
obj.handle().into(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut iter_val,
},
)
};
if !made_iter || !iter_val.is_object() {
JS_ClearPendingException(cx);
note_swallowed();
seen.pop();
return format!("{} {{ {} }}", head, PLACEHOLDER_THREW);
}
rooted!(&in(cx_ref) let iter_root = iter_val.to_object());
let mut next_val = UndefinedValue();
let have_next = primordial_iter_next(cx, cx_ref, is_map, &mut next_val);
rooted!(&in(cx_ref) let next_root = next_val);
let n = if is_map {
mozjs_sys::jsapi::JS::MapSize(cx, obj.handle().into())
} else {
mozjs_sys::jsapi::JS::SetSize(cx, obj.handle().into())
} as usize;
let render_n = n.min(100);
let mut parts: Vec<String> = Vec::with_capacity(render_n);
for _ in 0..render_n {
let mut res_val = UndefinedValue();
let called = if have_next {
JS_CallFunctionValue(
cx,
iter_root.handle().into(),
next_root.handle().into(),
&HandleValueArray {
length_: 0,
elements_: ::std::ptr::null(),
},
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut res_val,
},
)
} else {
JS_CallFunctionName(
cx,
iter_root.handle().into(),
c"next".as_ptr(),
&HandleValueArray {
length_: 0,
elements_: ::std::ptr::null(),
},
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut res_val,
},
)
};
if !called || !res_val.is_object() {
if !called {
JS_ClearPendingException(cx);
note_swallowed();
}
break;
}
rooted!(&in(cx_ref) let res_root = res_val.to_object());
let mut done_v = UndefinedValue();
if !JS_GetProperty(
cx,
res_root.handle().into(),
c"done".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut done_v,
},
) {
JS_ClearPendingException(cx);
note_swallowed();
break;
}
if done_v.to_boolean() {
break;
}
let mut value_v = UndefinedValue();
if !JS_GetProperty(
cx,
res_root.handle().into(),
c"value".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut value_v,
},
) {
JS_ClearPendingException(cx);
note_swallowed();
break;
}
if is_map && !value_v.is_object() {
note_swallowed();
parts.push(PLACEHOLDER_THREW.to_string());
continue;
}
if is_map {
rooted!(&in(cx_ref) let pair = value_v.to_object());
let mut k = UndefinedValue();
let got_k = JS_GetElement(cx, pair.handle().into(), 0, MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut k,
});
let mut v = UndefinedValue();
let got_v = JS_GetElement(cx, pair.handle().into(), 1, MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut v,
});
if !got_k || !got_v {
JS_ClearPendingException(cx);
note_swallowed();
continue;
}
parts.push(format!(
"{} => {}",
inspect_val(cx, k, depth - 1, opts, seen),
inspect_val(cx, v, depth - 1, opts, seen)
));
} else {
parts.push(inspect_val(cx, value_v, depth - 1, opts, seen));
}
}
seen.pop();
return format!("{}({}) {{ {} }}", head, n, parts.join(", "));
}
ESClass::ArrayBuffer => {
let mut bl = UndefinedValue();
if !JS_GetProperty(
cx,
obj.handle().into(),
c"byteLength".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut bl,
},
) {
JS_ClearPendingException(cx);
note_swallowed();
return format!("ArrayBuffer {{ byteLength: {} }}", PLACEHOLDER_THREW);
}
if bl.is_number() {
return format!("ArrayBuffer {{ byteLength: {} }}", format_number(bl.to_number()));
}
note_swallowed();
return format!("ArrayBuffer {{ byteLength: {} }}", PLACEHOLDER_THREW);
}
_ => {}
}
let ta_bytes = crate::node_buffer::collect_byte_view(cx, val);
{
let mut view_length: usize = 0;
let mut view_shared = false;
let mut view_data: *mut u8 = ::std::ptr::null_mut();
let view_unwrapped = mozjs_sys::jsapi::JS_GetObjectAsArrayBufferView(
obj.get(),
&mut view_length,
&mut view_shared,
&mut view_data,
);
if view_unwrapped.is_null() {
return plain_object_or_array(cx, val, obj_ptr, at_depth_cap, depth, opts, seen);
}
}
{
let mut ctor_name = String::new();
let mut ctor_val = UndefinedValue();
let got_ctor = JS_GetProperty(
cx,
obj.handle().into(),
c"constructor".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ctor_val,
},
);
if got_ctor && ctor_val.is_object() {
rooted!(&in(cx_ref) let ctor_obj = ctor_val.to_object());
ctor_name = obj_string_prop(cx, ctor_obj.handle(), b"name");
} else if !got_ctor {
JS_ClearPendingException(cx);
note_swallowed();
}
let tname = if ctor_name.is_empty() {
"TypedArray".to_string()
} else {
ctor_name
};
if at_depth_cap {
return format!("{}(...)", tname);
}
if let Some(bytes) = ta_bytes {
if tname == "Uint8Array" || tname == "Uint8ClampedArray" || tname == "Int8Array" {
let elems: Vec<String> = bytes.iter().map(|b| b.to_string()).collect();
return format!("{}({}) [ {} ]", tname, bytes.len(), elems.join(", "));
}
}
let len = {
let mut lv = UndefinedValue();
JS_GetProperty(cx, obj.handle().into(), c"length".as_ptr(), MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut lv,
});
if lv.is_number() { lv.to_number() as u32 } else { 0 }
};
seen.push(obj_ptr);
let mut elems: Vec<String> = Vec::new();
for i in 0..len.min(100) {
let mut ev = UndefinedValue();
JS_GetElement(cx, obj.handle().into(), i, MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ev,
});
elems.push(inspect_val(cx, ev, depth - 1, opts, seen));
}
seen.pop();
return format!("{}({}) [ {} ]", tname, len, elems.join(", "));
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn render_own_keyed_entries(
cx: *mut JSContext,
cx_ref: &mut mozjs::context::JSContext,
obj: mozjs::rust::Handle<*mut JSObject>,
skip_indices_below: Option<u32>,
depth: u32,
opts: &InspectOpts,
seen: &mut Vec<usize>,
parts: &mut Vec<String>,
) {
let mut ids = mozjs::rust::IdVector::new(cx_ref);
let ok = GetPropertyKeys(cx, obj.into(), JSITER_OWNONLY, ids.handle_mut());
if !ok {
JS_ClearPendingException(cx);
note_swallowed();
return;
}
for jsid in &*ids {
if jsid.is_int() {
let idx = jsid.to_int() as u32;
if let Some(below) = skip_indices_below {
if idx < below {
continue;
}
}
let mut v = UndefinedValue();
if !JS_GetElement(cx, obj.into(), idx, MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut v,
}) {
JS_ClearPendingException(cx);
note_swallowed();
continue;
}
let rendered_key = paint(opts, idx.to_string(), COLOR_KEY);
parts.push(format!("{}: {}", rendered_key, inspect_val(cx, v, depth - 1, opts, seen)));
continue;
}
if !jsid.is_string() {
continue;
}
let key_ptr = jsid.to_string();
if key_ptr.is_null() {
continue;
}
let key = mozjs::conversions::unsafe_jsstr_to_string(
cx,
::std::ptr::NonNull::new_unchecked(key_ptr),
);
let key_z = bun_core::ZBox::from_bytes(key.as_bytes());
let mut v = UndefinedValue();
if !JS_GetProperty(
cx,
obj.into(),
key_z.as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut v,
},
) {
JS_ClearPendingException(cx);
note_swallowed();
continue;
}
let rendered_key = paint(opts, format_key(&key), COLOR_KEY);
parts.push(format!("{}: {}", rendered_key, inspect_val(cx, v, depth - 1, opts, seen)));
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn invoke_custom_inspect(
cx: *mut JSContext,
cx_ref: &mut mozjs::context::JSContext,
obj: mozjs::rust::Handle<*mut JSObject>,
custom_v: JSVal,
depth: u32,
) -> Option<String> {
if !custom_v.is_object() {
return None;
}
rooted!(&in(cx_ref) let custom_fn = custom_v.to_object());
if !JS_ObjectIsFunction(custom_fn.get()) {
return None;
}
let depth_arg = DoubleValue(depth as f64);
let args = [depth_arg];
let call_args = HandleValueArray {
length_: 1,
elements_: args.as_ptr(),
};
rooted!(&in(cx_ref) let custom_val = ObjectValue(custom_fn.get()));
let mut rval = UndefinedValue();
let ok = JS_CallFunctionValue(
cx,
obj.into(),
custom_val.handle().into(),
&call_args,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
},
);
if ok && rval.is_string() {
return Some(crate::js_to_rust_string(cx, rval));
}
if !ok {
JS_ClearPendingException(cx);
note_swallowed();
}
None
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn try_custom_inspect(
cx: *mut JSContext,
cx_ref: &mut mozjs::context::JSContext,
obj: mozjs::rust::Handle<*mut JSObject>,
depth: u32,
) -> Option<String> {
rooted!(&in(cx_ref) let key_str = JS_NewStringCopyZ(cx, c"nodejs.util.inspect.custom".as_ptr()));
if !key_str.is_null() {
let sym = mozjs_sys::jsapi::JS::GetSymbolFor(cx, key_str.handle().into());
if !sym.is_null() {
let custom_id = mozjs::jsid::SymbolId(sym);
let mut custom_v = UndefinedValue();
if JS_GetPropertyById(
cx,
obj.into(),
Handle::from_marked_location(&custom_id),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut custom_v,
},
) {
if let Some(out) = invoke_custom_inspect(cx, cx_ref, obj, custom_v, depth) {
return Some(out);
}
} else {
JS_ClearPendingException(cx);
note_swallowed();
}
}
}
let mut custom_v = UndefinedValue();
let has_custom = JS_GetProperty(
cx,
obj.into(),
c"nodejs.util.inspect.custom".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut custom_v,
},
);
if has_custom {
invoke_custom_inspect(cx, cx_ref, obj, custom_v, depth)
} else {
JS_ClearPendingException(cx);
note_swallowed();
None
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn plain_object_or_array(
cx: *mut JSContext,
val: JSVal,
obj_ptr: usize,
at_depth_cap: bool,
depth: u32,
opts: &InspectOpts,
seen: &mut Vec<usize>,
) -> String {
let mut wrapped = mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let obj = val.to_object());
let mut is_arr = false;
rooted!(&in(cx_ref) let v_root = val);
IsArrayObject(cx, v_root.handle().into(), &mut is_arr);
if is_arr {
if at_depth_cap {
return "[Array]".to_string();
}
let mut len_v = UndefinedValue();
JS_GetProperty(cx, obj.handle().into(), c"length".as_ptr(), MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut len_v,
});
let len = if len_v.is_number() { len_v.to_number() as u32 } else { 0 };
seen.push(obj_ptr);
let mut parts: Vec<String> = Vec::new();
for i in 0..len.min(100) {
let mut ev = UndefinedValue();
JS_GetElement(cx, obj.handle().into(), i, MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ev,
});
parts.push(inspect_val(cx, ev, depth - 1, opts, seen));
}
render_own_keyed_entries(cx, cx_ref, obj.handle(), Some(len), depth, opts, seen, &mut parts);
seen.pop();
if parts.is_empty() {
return "[]".to_string();
}
return format!("[ {} ]", parts.join(", "));
}
if at_depth_cap {
return "[Object]".to_string();
}
if let Some(custom_out) = try_custom_inspect(cx, cx_ref, obj.handle(), depth.saturating_sub(1)) {
return custom_out;
}
seen.push(obj_ptr);
let mut parts: Vec<String> = Vec::new();
render_own_keyed_entries(cx, cx_ref, obj.handle(), None, depth, opts, seen, &mut parts);
seen.pop();
if parts.is_empty() {
return "{}".to_string();
}
format!("{{ {} }}", parts.join(", "))
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_inspect(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if args.argc_ == 0 {
let js_str = JS_NewStringCopyZ(cx, c"undefined".as_ptr());
args.rval().set(if js_str.is_null() {
UndefinedValue()
} else {
StringValue(&*js_str)
});
return true;
}
SWALLOWED_EXCEPTIONS.with(|c| c.set(0));
let mut opts = InspectOpts { depth: 2, colors: false };
if args.argc_ > 1 {
let o = *args.get(1).ptr;
if !o.is_undefined() && !o.is_null() {
if !o.is_object() {
let msg = format!(
"The \"options\" argument must be of type object. Received {}",
received_type_fragment(cx, o)
);
return throw_invalid_arg_type(cx, &msg);
}
let mut wrapped =
mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let oobj = o.to_object());
let mut dv = UndefinedValue();
let got_depth = JS_GetProperty(
cx,
oobj.handle().into(),
c"depth".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut dv,
},
);
if !got_depth {
JS_ClearPendingException(cx);
note_swallowed();
} else if dv.is_number() {
let d = dv.to_number();
opts.depth = if d.is_infinite() { u32::MAX } else { d.max(0.0) as u32 };
}
let mut cv = UndefinedValue();
let got_colors = JS_GetProperty(
cx,
oobj.handle().into(),
c"colors".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut cv,
},
);
if !got_colors {
JS_ClearPendingException(cx);
note_swallowed();
} else if cv.is_boolean() {
opts.colors = cv.to_boolean();
}
}
}
let val = *args.get(0).ptr;
let s = inspect_val(cx, val, opts.depth.saturating_add(1), &opts, &mut Vec::new());
let utf16: Vec<u16> = s.encode_utf16().collect();
let js_str = JS_NewUCStringCopyN(cx, utf16.as_ptr(), utf16.len());
args.rval().set(if js_str.is_null() {
UndefinedValue()
} else {
StringValue(&*js_str)
});
true
}
const PRIMORDIAL_BAG_PROP: &[u8] = b"__baoInspectPrims";
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn capture_iter_next_into_bag(
cx: &mut mozjs::context::JSContext,
bag: mozjs::rust::Handle<*mut JSObject>,
table: *mut JSObject,
map: bool,
prop: &[u8],
) {
let raw = cx.raw_cx();
rooted!(&in(cx) let table_root = table);
let mut iter_val = UndefinedValue();
let made_iter = if map {
mozjs_sys::jsapi::JS::MapEntries(raw, table_root.handle().into(), MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut iter_val,
})
} else {
mozjs_sys::jsapi::JS::SetValues(raw, table_root.handle().into(), MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut iter_val,
})
};
if !made_iter || !iter_val.is_object() {
JS_ClearPendingException(raw);
return;
}
rooted!(&in(cx) let iter_root = iter_val.to_object());
let mut proto: *mut JSObject = ::std::ptr::null_mut();
if !JS_GetPrototype(
raw,
iter_root.handle().into(),
MutableHandle::<*mut JSObject> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut proto,
},
) || proto.is_null() {
JS_ClearPendingException(raw);
return;
}
rooted!(&in(cx) let proto_root = proto);
let mut next_val = UndefinedValue();
let next_z = bun_core::ZBox::from_bytes(b"next");
if !JS_GetProperty(
raw,
proto_root.handle().into(),
next_z.as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut next_val,
},
) || !next_val.is_object() || !JS_ObjectIsFunction(next_val.to_object()) {
JS_ClearPendingException(raw);
return;
}
rooted!(&in(cx) let next_root = next_val.to_object());
let prop_z = bun_core::ZBox::from_bytes(prop);
mozjs::rust::wrappers2::JS_DefineProperty3(
cx,
bag,
prop_z.as_ptr(),
next_root.handle(),
(JSPROP_READONLY | JSPROP_PERMANENT) as u32,
);
}
#[allow(unsafe_op_in_unsafe_fn)]
pub(crate) unsafe fn install_primordial_bag(cx: &mut mozjs::context::JSContext) {
let global = CurrentGlobalOrNull(cx.raw_cx());
if global.is_null() {
return;
}
rooted!(&in(cx) let global_root = global);
{
let mut existing = UndefinedValue();
let bag_z = bun_core::ZBox::from_bytes(PRIMORDIAL_BAG_PROP);
let got = JS_GetProperty(
cx.raw_cx(),
global_root.handle().into(),
bag_z.as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut existing,
},
);
if !got {
JS_ClearPendingException(cx.raw_cx());
} else if existing.is_object() {
return;
}
}
rooted!(&in(cx) let bag = mozjs::rust::wrappers2::JS_NewPlainObject(cx));
if bag.get().is_null() {
return;
}
let map_obj = mozjs_sys::jsapi::JS::NewMapObject(cx.raw_cx());
if !map_obj.is_null() {
capture_iter_next_into_bag(cx, bag.handle(), map_obj, true, b"mapIterNext");
}
let set_obj = mozjs_sys::jsapi::JS::NewSetObject(cx.raw_cx());
if !set_obj.is_null() {
capture_iter_next_into_bag(cx, bag.handle(), set_obj, false, b"setIterNext");
}
let bag_z = bun_core::ZBox::from_bytes(PRIMORDIAL_BAG_PROP);
mozjs::rust::wrappers2::JS_DefineProperty3(
cx,
global_root.handle(),
bag_z.as_ptr(),
bag.handle(),
(JSPROP_READONLY | JSPROP_PERMANENT) as u32,
);
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn primordial_iter_next(
cx: *mut JSContext,
cx_ref: &mut mozjs::context::JSContext,
map: bool,
out: &mut JSVal,
) -> bool {
let global = CurrentGlobalOrNull(cx);
if global.is_null() {
return false;
}
rooted!(&in(cx_ref) let global_root = global);
let mut bag_val = UndefinedValue();
let bag_z = bun_core::ZBox::from_bytes(PRIMORDIAL_BAG_PROP);
if !JS_GetProperty(
cx,
global_root.handle().into(),
bag_z.as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut bag_val,
},
) || !bag_val.is_object() {
JS_ClearPendingException(cx);
return false;
}
rooted!(&in(cx_ref) let bag_root = bag_val.to_object());
let mut fn_val = UndefinedValue();
let name_z = bun_core::ZBox::from_bytes(if map { b"mapIterNext" } else { b"setIterNext" });
if !JS_GetProperty(
cx,
bag_root.handle().into(),
name_z.as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut fn_val,
},
) || !fn_val.is_object() || !JS_ObjectIsFunction(fn_val.to_object()) {
JS_ClearPendingException(cx);
return false;
}
*out = fn_val;
true
}
pub unsafe fn install(
cx: &mut mozjs::context::JSContext,
bun_obj: mozjs::rust::Handle<*mut JSObject>,
) {
install_primordial_bag(cx);
mozjs::rust::wrappers2::JS_DefineFunction(
cx,
bun_obj,
c"inspect".as_ptr(),
Some(bun_inspect),
2,
JSPROP_ENUMERATE as u32,
);
}