Skip to main content

rquickjs_core/value/function/
params.rs

1use crate::{
2    function::{Exhaustive, Flat, FuncArg, Opt, Rest, This},
3    qjs, Ctx, FromJs, Result, Value,
4};
5use alloc::{borrow::Cow, vec::Vec};
6use core::{mem::size_of, slice};
7
8/// A struct which contains the values a callback is called with.
9///
10/// Arguments retrieved from the JavaScript side for calling Rust functions.
11pub struct Params<'a, 'js> {
12    ctx: Ctx<'js>,
13    function: qjs::JSValue,
14    this: qjs::JSValue,
15    args: Cow<'a, [qjs::JSValue]>,
16    is_constructor: bool,
17}
18
19impl<'a, 'js> Params<'a, 'js> {
20    /// Create params from the arguments returned by the class callback.
21    pub(crate) unsafe fn from_ffi_class(
22        ctx: *mut qjs::JSContext,
23        function: qjs::JSValue,
24        this: qjs::JSValue,
25        argc: qjs::c_int,
26        argv: *mut qjs::JSValue,
27        flags: qjs::c_int,
28    ) -> Self {
29        let args: Cow<'a, [qjs::JSValue]> = if argv.is_null() {
30            assert_eq!(
31                argc, 0,
32                "got a null pointer from quickjs for a non-zero number of args"
33            );
34            Cow::Borrowed(&[])
35        } else {
36            let argc = usize::try_from(argc).expect("invalid argument number");
37            if argv.is_aligned() {
38                Cow::Borrowed(slice::from_raw_parts(argv, argc))
39            } else {
40                // QuickJS only guarantees four-byte alignment here on 32-bit MSVC.
41                let bytes = argv.cast::<u8>();
42                Cow::Owned(
43                    (0..argc)
44                        .map(|index| {
45                            bytes
46                                .add(index * size_of::<qjs::JSValue>())
47                                .cast::<qjs::JSValue>()
48                                .read_unaligned()
49                        })
50                        .collect(),
51                )
52            }
53        };
54
55        let is_constructor = (flags & qjs::JS_CALL_FLAG_CONSTRUCTOR as qjs::c_int) != 0;
56
57        Self {
58            ctx: Ctx::from_ptr(ctx),
59            function,
60            this,
61            args,
62            is_constructor,
63        }
64    }
65
66    /// Checks if the parameters fit the param num requirements.
67    pub fn check_params(&self, num: ParamRequirement) -> Result<()> {
68        if self.args.len() < num.min {
69            return Err(crate::Error::MissingArgs {
70                expected: num.min,
71                given: self.args.len(),
72            });
73        }
74        if num.exhaustive && self.args.len() > num.max {
75            return Err(crate::Error::TooManyArgs {
76                expected: num.max,
77                given: self.args.len(),
78            });
79        }
80        Ok(())
81    }
82
83    /// Returns the context associated with call.
84    pub fn ctx(&self) -> &Ctx<'js> {
85        &self.ctx
86    }
87
88    /// Returns the value on which this function called. i.e. in `bla.foo()` the `foo` value.
89    pub fn function(&self) -> Value<'js> {
90        unsafe { Value::from_js_value_const(self.ctx.clone(), self.function) }
91    }
92
93    /// Returns the this on which this function called. i.e. in `bla.foo()` the `bla` value.
94    pub fn this(&self) -> Value<'js> {
95        unsafe { Value::from_js_value_const(self.ctx.clone(), self.this) }
96    }
97
98    /// Returns the argument at a given index..
99    pub fn arg(&self, index: usize) -> Option<Value<'js>> {
100        self.args
101            .get(index)
102            .map(|arg| unsafe { Value::from_js_value_const(self.ctx.clone(), *arg) })
103    }
104
105    /// Returns the number of arguments.
106    pub fn len(&self) -> usize {
107        self.args.len()
108    }
109
110    /// Returns if there are no arguments
111    pub fn is_empty(&self) -> bool {
112        self.args.is_empty()
113    }
114
115    /// Returns if the function is called as a constructor.
116    ///
117    /// If it is the value return by `this` is actually the `new.target` value.
118    pub fn is_constructor(&self) -> bool {
119        self.is_constructor
120    }
121
122    /// Turns the params into an accessor object for extracting the arguments.
123    pub fn access(self) -> ParamsAccessor<'a, 'js> {
124        ParamsAccessor {
125            params: self,
126            offset: 0,
127        }
128    }
129}
130
131/// Accessor to parameters used for retrieving arguments in order one at the time.
132pub struct ParamsAccessor<'a, 'js> {
133    params: Params<'a, 'js>,
134    offset: usize,
135}
136
137impl<'a, 'js> ParamsAccessor<'a, 'js> {
138    /// Returns the context associated with the params.
139    pub fn ctx(&self) -> &Ctx<'js> {
140        self.params.ctx()
141    }
142
143    /// Returns this value of call from which the params originate.
144    pub fn this(&self) -> Value<'js> {
145        self.params.this()
146    }
147
148    /// Returns the value on which this function called. i.e. in `bla.foo()` the `foo` value.
149    pub fn function(&self) -> Value<'js> {
150        self.params.function()
151    }
152
153    /// Returns the next arguments.
154    ///
155    /// Each call to this function returns a different argument
156    ///
157    /// # Panic
158    /// This function panics if it is called more times then there are arguments.
159    pub fn arg(&mut self) -> Value<'js> {
160        assert!(
161            self.offset < self.params.args.len(),
162            "arg called too many times"
163        );
164        let res = self.params.args[self.offset];
165        self.offset += 1;
166        // TODO: figure out ownership
167        unsafe { Value::from_js_value_const(self.params.ctx.clone(), res) }
168    }
169
170    /// returns the number of arguments remaining
171    pub fn len(&self) -> usize {
172        self.params.args.len() - self.offset
173    }
174    /// returns whether there are any arguments remaining.
175    pub fn is_empty(&self) -> bool {
176        self.len() == 0
177    }
178}
179
180/// A struct encoding the requirements of a parameter set.
181pub struct ParamRequirement {
182    min: usize,
183    max: usize,
184    exhaustive: bool,
185}
186
187impl ParamRequirement {
188    /// Returns the requirement of a single required parameter
189    pub const fn single() -> Self {
190        ParamRequirement {
191            min: 1,
192            max: 1,
193            exhaustive: false,
194        }
195    }
196
197    /// Makes the requirements exhaustive i.e. the parameter set requires that the function is
198    /// called with no arguments than parameters
199    pub const fn exhaustive() -> Self {
200        ParamRequirement {
201            min: 0,
202            max: 0,
203            exhaustive: true,
204        }
205    }
206
207    /// Returns the requirements for a single optional parameter
208    pub const fn optional() -> Self {
209        ParamRequirement {
210            min: 0,
211            max: 1,
212            exhaustive: false,
213        }
214    }
215
216    /// Returns the requirements for a any number of parameters
217    pub const fn any() -> Self {
218        ParamRequirement {
219            min: 0,
220            max: usize::MAX,
221            exhaustive: false,
222        }
223    }
224
225    /// Returns the requirements for no parameters
226    pub const fn none() -> Self {
227        ParamRequirement {
228            min: 0,
229            max: 0,
230            exhaustive: false,
231        }
232    }
233
234    /// Combine to requirements into one which covers both.
235    pub const fn combine(self, other: Self) -> ParamRequirement {
236        Self {
237            min: self.min.saturating_add(other.min),
238            max: self.max.saturating_add(other.max),
239            exhaustive: self.exhaustive || other.exhaustive,
240        }
241    }
242
243    /// Returns the minimum number of arguments for this requirement
244    pub fn min(&self) -> usize {
245        self.min
246    }
247
248    /// Returns the maximum number of arguments for this requirement
249    pub fn max(&self) -> usize {
250        self.max
251    }
252
253    /// Returns whether this function is required to be exhaustive called
254    ///
255    /// i.e. there can be no more arguments then parameters.
256    pub fn is_exhaustive(&self) -> bool {
257        self.exhaustive
258    }
259}
260
261/// A trait to extract argument values.
262pub trait FromParam<'js>: Sized {
263    /// The parameters requirements this value requires.
264    fn param_requirement() -> ParamRequirement;
265
266    /// Convert from a parameter value.
267    fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self>;
268}
269
270impl<'js, T: FromJs<'js>> FromParam<'js> for T {
271    fn param_requirement() -> ParamRequirement {
272        ParamRequirement::single()
273    }
274
275    fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
276        let ctx = params.ctx().clone();
277        T::from_js(&ctx, params.arg())
278    }
279}
280
281impl<'js> FromParam<'js> for Ctx<'js> {
282    fn param_requirement() -> ParamRequirement {
283        ParamRequirement::none()
284    }
285
286    fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
287        Ok(params.ctx().clone())
288    }
289}
290
291impl<'js, T: FromJs<'js>> FromParam<'js> for Opt<T> {
292    fn param_requirement() -> ParamRequirement {
293        ParamRequirement::optional()
294    }
295
296    fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
297        if !params.is_empty() {
298            let ctx = params.ctx().clone();
299            Ok(Opt(Some(T::from_js(&ctx, params.arg())?)))
300        } else {
301            Ok(Opt(None))
302        }
303    }
304}
305
306impl<'js, T: FromJs<'js>> FromParam<'js> for This<T> {
307    fn param_requirement() -> ParamRequirement {
308        ParamRequirement::any()
309    }
310
311    fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
312        T::from_js(params.ctx(), params.this()).map(This)
313    }
314}
315
316impl<'js, T: FromJs<'js>> FromParam<'js> for FuncArg<T> {
317    fn param_requirement() -> ParamRequirement {
318        ParamRequirement::any()
319    }
320
321    fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
322        T::from_js(params.ctx(), params.function()).map(FuncArg)
323    }
324}
325
326impl<'js, T: FromJs<'js>> FromParam<'js> for Rest<T> {
327    fn param_requirement() -> ParamRequirement {
328        ParamRequirement::any()
329    }
330
331    fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
332        let mut res = Vec::with_capacity(params.len());
333        for _ in 0..params.len() {
334            let p = params.arg();
335            res.push(T::from_js(params.ctx(), p)?);
336        }
337        Ok(Rest(res))
338    }
339}
340
341impl<'js, T: FromParams<'js>> FromParam<'js> for Flat<T> {
342    fn param_requirement() -> ParamRequirement {
343        T::param_requirements()
344    }
345
346    fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
347        T::from_params(params).map(Flat)
348    }
349}
350
351impl<'js> FromParam<'js> for Exhaustive {
352    fn param_requirement() -> ParamRequirement {
353        ParamRequirement::exhaustive()
354    }
355
356    fn from_param<'a>(_params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
357        Ok(Exhaustive)
358    }
359}
360
361/// A trait to extract a tuple of argument values.
362pub trait FromParams<'js>: Sized {
363    /// The parameters requirements this value requires.
364    fn param_requirements() -> ParamRequirement;
365
366    /// Convert from a parameter value.
367    fn from_params<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self>;
368}
369
370macro_rules! impl_from_params{
371    ($($t:ident),*) => {
372        #[allow(non_snake_case)]
373        impl<'js $(,$t)*> FromParams<'js> for ($($t,)*)
374        where
375            $($t : FromParam<'js>,)*
376        {
377
378            fn param_requirements() -> ParamRequirement{
379                ParamRequirement::none()
380                    $(.combine($t::param_requirement()))*
381            }
382
383            fn from_params<'a>(_args: &mut ParamsAccessor<'a,'js>) -> Result<Self>{
384                Ok((
385                    $($t::from_param(_args)?,)*
386                ))
387            }
388        }
389    };
390}
391
392impl_from_params!();
393impl_from_params!(A);
394impl_from_params!(A, B);
395impl_from_params!(A, B, C);
396impl_from_params!(A, B, C, D);
397impl_from_params!(A, B, C, D, E);
398impl_from_params!(A, B, C, D, E, F);
399impl_from_params!(A, B, C, D, E, F, G);
400
401#[cfg(test)]
402mod tests {
403    use crate::{function::IntoJsFunc, Function, IntoJs};
404
405    use super::*;
406    use core::mem::{align_of, size_of};
407
408    #[test]
409    fn misaligned_ffi_arguments_are_read_safely() {
410        crate::test_with(|ctx| {
411            let values = [
412                qjs::JS_MKVAL(qjs::JS_TAG_INT, 17),
413                qjs::JS_MKVAL(qjs::JS_TAG_INT, 42),
414            ];
415            let mut storage = vec![0_u8; size_of_val(&values) + 2 * align_of::<qjs::JSValue>()];
416            let offset = storage.as_ptr().align_offset(align_of::<qjs::JSValue>())
417                + align_of::<qjs::JSValue>() / 2;
418            let argv = unsafe { storage.as_mut_ptr().add(offset).cast::<qjs::JSValue>() };
419
420            for (index, value) in values.into_iter().enumerate() {
421                unsafe {
422                    storage
423                        .as_mut_ptr()
424                        .add(offset + index * size_of::<qjs::JSValue>())
425                        .cast::<qjs::JSValue>()
426                        .write_unaligned(value);
427                }
428            }
429
430            assert!(!argv.is_aligned());
431            let params = unsafe {
432                Params::from_ffi_class(
433                    ctx.as_ptr(),
434                    qjs::JS_UNDEFINED,
435                    qjs::JS_UNDEFINED,
436                    values.len() as _,
437                    argv,
438                    0,
439                )
440            };
441
442            assert_eq!(unsafe { qjs::JS_VALUE_GET_INT(params.args[0]) }, 17);
443            assert_eq!(unsafe { qjs::JS_VALUE_GET_INT(params.args[1]) }, 42);
444        });
445    }
446
447    #[test]
448    fn is_constructor() {
449        crate::test_with(|ctx| {
450            struct ReturnIsConstructor;
451            impl<'js> IntoJsFunc<'js, ()> for ReturnIsConstructor {
452                fn param_requirements() -> ParamRequirement {
453                    ParamRequirement::any()
454                }
455                fn call<'a>(&self, params: Params<'a, 'js>) -> Result<Value<'js>> {
456                    params.is_constructor().into_js(params.ctx())
457                }
458            }
459            let f = Function::new(ctx, ReturnIsConstructor)
460                .unwrap()
461                .with_constructor(true);
462            assert!(!f.call::<(), bool>(()).unwrap());
463            assert!(f
464                .as_constructor()
465                .unwrap()
466                .construct::<(), bool>(())
467                .unwrap());
468        });
469    }
470}