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