1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// @trace REQ-ENG-002 [module:bun_sm]
//! `CallFrame` — wrapper over SpiderMonkey's `CallArgs`.
//!
//! # ABI Difference
//!
//! ```text
//! JSC: unsafe extern "C" fn(*mut JSGlobalObject, *mut CallFrame) -> JSValue
//! SM: unsafe extern "C" fn(*mut JSContext, argc: u32, vp: *mut JSVal) -> bool
//! ```
//!
//! This wrapper bridges the gap: `CallFrame` wraps SM's `CallArgs` and
//! provides JSC-compatible methods like `argument()`, `this_object()`.
use mozjs::jsapi::{CallArgs, JSContext as RawJSContext, JSObject};
use mozjs::jsval::{JSVal, UndefinedValue};
use crate::global_object::JSGlobalObject;
use crate::js_value::JSValue;
/// Wrapper over SpiderMonkey's `CallArgs`, providing JSC-compatible API.
///
/// # Safety
///
/// The `CallArgs` must be valid for the duration of the JSNative callback.
pub struct CallFrame {
cx: *mut RawJSContext,
args: CallArgs,
}
impl CallFrame {
/// Create a CallFrame from SM's callback parameters.
///
/// # Safety
/// `cx` must be a valid JSContext. `vp` must point to a valid `JSVal` array.
#[inline]
pub unsafe fn from_vp(cx: *mut RawJSContext, vp: *mut JSVal, argc: u32) -> Self {
CallFrame {
cx,
args: unsafe { CallArgs::from_vp(vp, argc) },
}
}
/// Create from existing `CallArgs`.
///
/// # Safety
/// `cx` must be a valid JSContext for the duration of this call.
#[inline]
pub unsafe fn from_call_args(cx: *mut RawJSContext, args: CallArgs) -> Self {
CallFrame { cx, args }
}
/// Number of arguments passed (excluding `this`).
#[inline]
pub fn argument_count(&self) -> u32 {
self.args.argc_
}
/// Number of arguments including `this` (JSC convention).
#[inline]
pub fn argument_count_including_this(&self) -> u32 {
self.args.argc_ + 1
}
/// Get argument at index, or `JSValue::UNDEFINED` if out of bounds.
#[allow(unsafe_op_in_unsafe_fn)]
pub fn argument(&self, index: u32) -> JSValue {
if index < self.args.argc_ {
unsafe { JSValue::from_raw(self.cx, *self.args.get(index).ptr) }
} else {
JSValue::UNDEFINED
}
}
/// Get argument at index, or `JSValue::UNDEFINED` if out of bounds.
/// Alias for `argument()` with JSC naming convention.
pub fn argument_or_undefined(&self, index: u32) -> JSValue {
self.argument(index)
}
/// Get all arguments as a slice of raw JSVals.
#[inline]
pub unsafe fn arguments_slice(&self) -> &[JSVal] {
unsafe { std::slice::from_raw_parts(self.args.argv_, self.args.argc_ as usize) }
}
/// Get the `this` value as a `JSValue`.
#[allow(unsafe_op_in_unsafe_fn)]
pub fn this_value(&self) -> JSValue {
let thisv = self.args.thisv();
unsafe { JSValue::from_raw(self.cx, thisv.get()) }
}
/// Get the `this` value as a `*mut JSObject`, or null.
#[allow(unsafe_op_in_unsafe_fn)]
pub fn this_object(&self) -> *mut JSObject {
let thisv = self.args.thisv();
if thisv.is_object() {
thisv.to_object()
} else {
std::ptr::null_mut()
}
}
/// Get the `this` value as a `*mut JSObject`, panicking if not an object.
#[allow(unsafe_op_in_unsafe_fn)]
pub fn unwrap_this(&self) -> *mut JSObject {
let thisv = self.args.thisv();
assert!(
thisv.is_object(),
"CallFrame::unwrap_this: this is not an object"
);
thisv.to_object()
}
/// Get the callee (the function being called).
#[allow(unsafe_op_in_unsafe_fn)]
pub fn callee(&self) -> *mut JSObject {
self.args.callee()
}
/// Get the `JSGlobalObject` (the JSContext) for this call.
pub fn global_object(&self) -> JSGlobalObject {
JSGlobalObject(self.cx)
}
/// Get the raw `*mut JSContext`.
#[inline]
pub fn cx(&self) -> *mut RawJSContext {
self.cx
}
/// Get the underlying SM `CallArgs`.
#[inline]
pub fn call_args(&self) -> &CallArgs {
&self.args
}
/// Set the return value.
#[allow(unsafe_op_in_unsafe_fn)]
pub unsafe fn set_return_value(&mut self, val: JSValue) {
self.args.rval().set(val.into_inner().to_jsval(self.cx));
}
/// Set the return value to undefined.
#[allow(unsafe_op_in_unsafe_fn)]
pub unsafe fn return_undefined(&mut self) {
self.args.rval().set(UndefinedValue());
}
/// Consume the first argument and return it, shifting remaining arguments.
/// Decrements argc and increments argv pointer by one slot.
#[allow(unsafe_op_in_unsafe_fn)]
pub fn shift(&mut self) -> JSValue {
if self.args.argc_ == 0 {
return JSValue::UNDEFINED;
}
let first = unsafe { JSValue::from_raw(self.cx, *self.args.argv_) };
self.args.argv_ = unsafe { self.args.argv_.add(1) };
self.args.argc_ -= 1;
first
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn call_frame_type_size() {
let size = std::mem::size_of::<CallFrame>();
assert!(size <= 32, "CallFrame is too large: {size} bytes");
}
}