quickjs_rusty/
utils.rs

1//! utils
2
3use std::ffi::CString;
4
5use libquickjs_ng_sys as q;
6
7use crate::value::{JsFunction, OwnedJsValue};
8use crate::ValueError;
9
10pub(crate) fn deserialize_borrowed_str(
11    context: *mut q::JSContext,
12    value: &q::JSValue,
13) -> Result<&str, ValueError> {
14    let r = value;
15    let tag = unsafe { q::JS_Ext_ValueGetTag(*r) };
16
17    match tag {
18        q::JS_TAG_STRING => {
19            let ptr = unsafe { q::JS_ToCStringLen2(context, std::ptr::null_mut(), *r, false) };
20
21            if ptr.is_null() {
22                return Err(ValueError::Internal(
23                    "Could not convert string: got a null pointer".into(),
24                ));
25            }
26
27            let cstr = unsafe { std::ffi::CStr::from_ptr(ptr) };
28
29            let s = cstr
30                .to_str()
31                .map_err(ValueError::InvalidString)?
32                .to_string()
33                .leak();
34
35            // Free the c string.
36            unsafe { q::JS_FreeCString(context, ptr) };
37
38            Ok(s)
39        }
40        _ => Err(ValueError::Internal(format!(
41            "Expected a string, got a {:?}",
42            tag
43        ))),
44    }
45}
46
47/// Helper for creating CStrings.
48#[inline]
49pub fn make_cstring(value: impl Into<Vec<u8>>) -> Result<CString, ValueError> {
50    CString::new(value).map_err(ValueError::StringWithZeroBytes)
51}
52
53#[cfg(feature = "chrono")]
54pub fn js_date_constructor(context: *mut q::JSContext) -> q::JSValue {
55    let global = unsafe { q::JS_GetGlobalObject(context) };
56    let tag = unsafe { q::JS_Ext_ValueGetTag(global) };
57    assert_eq!(tag, q::JS_TAG_OBJECT);
58
59    let date_constructor = unsafe {
60        q::JS_GetPropertyStr(
61            context,
62            global,
63            std::ffi::CStr::from_bytes_with_nul(b"Date\0")
64                .unwrap()
65                .as_ptr(),
66        )
67    };
68    let tag = unsafe { q::JS_Ext_ValueGetTag(date_constructor) };
69    assert_eq!(tag, q::JS_TAG_OBJECT);
70    unsafe { q::JS_FreeValue(context, global) };
71    date_constructor
72}
73
74#[cfg(feature = "bigint")]
75fn js_create_bigint_function(context: *mut q::JSContext) -> q::JSValue {
76    let global = unsafe { q::JS_GetGlobalObject(context) };
77    let tag = unsafe { q::JS_Ext_ValueGetTag(global) };
78    assert_eq!(tag, q::JS_TAG_OBJECT);
79
80    let bigint_function = unsafe {
81        q::JS_GetPropertyStr(
82            context,
83            global,
84            std::ffi::CStr::from_bytes_with_nul(b"BigInt\0")
85                .unwrap()
86                .as_ptr(),
87        )
88    };
89    let tag = unsafe { q::JS_Ext_ValueGetTag(bigint_function) };
90    assert_eq!(tag, q::JS_TAG_OBJECT);
91    unsafe { q::JS_FreeValue(context, global) };
92    bigint_function
93}
94
95pub fn create_undefined() -> q::JSValue {
96    unsafe { q::JS_Ext_NewSpecialValue(q::JS_TAG_UNDEFINED, 0) }
97}
98
99pub fn create_null() -> q::JSValue {
100    unsafe { q::JS_Ext_NewSpecialValue(q::JS_TAG_NULL, 0) }
101}
102
103pub fn create_bool(context: *mut q::JSContext, value: bool) -> q::JSValue {
104    unsafe { q::JS_Ext_NewBool(context, value as u8) }
105}
106
107pub fn create_int(context: *mut q::JSContext, value: i32) -> q::JSValue {
108    unsafe { q::JS_Ext_NewInt32(context, value) }
109}
110
111pub fn create_float(context: *mut q::JSContext, value: f64) -> q::JSValue {
112    unsafe { q::JS_Ext_NewFloat64(context, value) }
113}
114
115pub fn create_string(context: *mut q::JSContext, value: &str) -> Result<q::JSValue, ValueError> {
116    // although rust string is not null-terminated, but quickjs not require it to be null-terminated
117    let qval = unsafe { q::JS_NewStringLen(context, value.as_ptr() as *const _, value.len()) };
118
119    let tag = unsafe { q::JS_Ext_ValueGetTag(qval) };
120
121    if tag == q::JS_TAG_EXCEPTION {
122        return Err(ValueError::Internal(
123            "Could not create string in runtime".into(),
124        ));
125    }
126
127    Ok(qval)
128}
129
130pub fn create_empty_array(context: *mut q::JSContext) -> Result<q::JSValue, ValueError> {
131    // Allocate a new array in the runtime.
132    let arr = unsafe { q::JS_NewArray(context) };
133    let tag = unsafe { q::JS_Ext_ValueGetTag(arr) };
134    if tag == q::JS_TAG_EXCEPTION {
135        return Err(ValueError::Internal(
136            "Could not create array in runtime".into(),
137        ));
138    }
139
140    Ok(arr)
141}
142
143pub fn add_array_element(
144    context: *mut q::JSContext,
145    array: q::JSValue,
146    index: u32,
147    value: q::JSValue,
148) -> Result<(), ValueError> {
149    let result = unsafe { q::JS_SetPropertyUint32(context, array, index, value) };
150    if result < 0 {
151        return Err(ValueError::Internal(
152            "Could not add element to array".into(),
153        ));
154    }
155
156    Ok(())
157}
158
159pub fn create_empty_object(context: *mut q::JSContext) -> Result<q::JSValue, ValueError> {
160    let obj = unsafe { q::JS_NewObject(context) };
161    let tag = unsafe { q::JS_Ext_ValueGetTag(obj) };
162    if tag == q::JS_TAG_EXCEPTION {
163        return Err(ValueError::Internal("Could not create object".into()));
164    }
165
166    Ok(obj)
167}
168
169pub fn add_object_property(
170    context: *mut q::JSContext,
171    object: q::JSValue,
172    key: &str,
173    value: q::JSValue,
174) -> Result<(), ValueError> {
175    let key = make_cstring(key)?;
176    let result = unsafe { q::JS_SetPropertyStr(context, object, key.as_ptr(), value) };
177    if result < 0 {
178        return Err(ValueError::Internal(
179            "Could not add property to object".into(),
180        ));
181    }
182
183    Ok(())
184}
185
186pub fn create_function(_: *mut q::JSContext, func: JsFunction) -> Result<q::JSValue, ValueError> {
187    let owned_value = func.into_value();
188    let v = unsafe { owned_value.extract() };
189    Ok(v)
190}
191
192#[cfg(feature = "chrono")]
193pub fn create_date(
194    context: *mut q::JSContext,
195    datetime: chrono::DateTime<chrono::Utc>,
196) -> Result<q::JSValue, ValueError> {
197    let date_constructor = js_date_constructor(context);
198
199    let f = datetime.timestamp_millis() as f64;
200
201    let timestamp = unsafe { q::JS_Ext_NewFloat64(context, f) };
202
203    let mut args = vec![timestamp];
204
205    let value = unsafe {
206        q::JS_CallConstructor(
207            context,
208            date_constructor,
209            args.len() as i32,
210            args.as_mut_ptr(),
211        )
212    };
213    unsafe {
214        q::JS_FreeValue(context, date_constructor);
215    }
216
217    let tag = unsafe { q::JS_Ext_ValueGetTag(value) };
218    if tag != q::JS_TAG_OBJECT {
219        return Err(ValueError::Internal(
220            "Could not construct Date object".into(),
221        ));
222    }
223    Ok(value)
224}
225
226#[cfg(feature = "bigint")]
227pub fn create_bigint(
228    context: *mut q::JSContext,
229    int: crate::BigInt,
230) -> Result<q::JSValue, ValueError> {
231    use std::ffi::c_char;
232
233    use crate::value::BigIntOrI64;
234
235    let val = match int.inner {
236        BigIntOrI64::Int(int) => unsafe { q::JS_NewBigInt64(context, int) },
237        BigIntOrI64::BigInt(bigint) => {
238            let bigint_string = bigint.to_str_radix(10);
239            let s = unsafe {
240                q::JS_NewStringLen(
241                    context,
242                    bigint_string.as_ptr() as *const c_char,
243                    bigint_string.len(),
244                )
245            };
246
247            let s_tag = unsafe { q::JS_Ext_ValueGetTag(s) };
248            if s_tag != q::JS_TAG_STRING {
249                return Err(ValueError::Internal(
250                    "Could not construct String object needed to create BigInt object".into(),
251                ));
252            }
253
254            let mut args = vec![s];
255
256            let bigint_function = js_create_bigint_function(context);
257
258            let null = create_null();
259            let js_bigint =
260                unsafe { q::JS_Call(context, bigint_function, null, 1, args.as_mut_ptr()) };
261
262            unsafe {
263                q::JS_FreeValue(context, s);
264                q::JS_FreeValue(context, bigint_function);
265                q::JS_FreeValue(context, null);
266            }
267
268            let js_bigint_tag = unsafe { q::JS_Ext_ValueGetTag(js_bigint) };
269
270            if js_bigint_tag != q::JS_TAG_BIG_INT {
271                return Err(ValueError::Internal(
272                    "Could not construct BigInt object".into(),
273                ));
274            }
275
276            js_bigint
277        }
278    };
279
280    Ok(val)
281}
282
283pub fn create_symbol(_: *mut q::JSContext) -> Result<q::JSValue, ValueError> {
284    todo!("create symbol not implemented")
285}
286
287#[inline]
288pub fn own_raw_value(context: *mut q::JSContext, value: q::JSValue) -> OwnedJsValue {
289    OwnedJsValue::new(context, value)
290}
291
292#[macro_export]
293macro_rules! owned {
294    ($context:expr, $val:expr) => {
295        OwnedJsValue::from(($context, $val))
296    };
297}
298
299use crate::ExecutionError;
300
301/// Get the last exception from the runtime, and if present, convert it to a ExceptionError.
302pub(crate) fn get_exception(context: *mut q::JSContext) -> Option<ExecutionError> {
303    if unsafe { !q::JS_HasException(context) } {
304        return None;
305    }
306
307    let value = unsafe {
308        let raw = q::JS_GetException(context);
309        OwnedJsValue::new(context, raw)
310    };
311
312    if value.is_exception() {
313        Some(ExecutionError::Internal(
314            "Could get exception from runtime".into(),
315        ))
316    } else {
317        match value.js_to_string() {
318            Ok(strval) => {
319                if strval.contains("out of memory") {
320                    Some(ExecutionError::OutOfMemory)
321                } else {
322                    Some(ExecutionError::Exception(OwnedJsValue::new(
323                        context,
324                        create_string(context, &strval).unwrap(),
325                    )))
326                }
327            }
328            Err(e) => Some(e),
329        }
330    }
331}
332
333/// Returns `Result::Err` when an error ocurred.
334pub(crate) fn ensure_no_excpetion(context: *mut q::JSContext) -> Result<(), ExecutionError> {
335    if let Some(e) = get_exception(context) {
336        Err(e)
337    } else {
338        Ok(())
339    }
340}