rquickjs_core/value/function/
params.rs1use 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
8pub 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 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 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 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 pub fn ctx(&self) -> &Ctx<'js> {
83 &self.ctx
84 }
85
86 pub fn function(&self) -> Value<'js> {
88 unsafe { Value::from_js_value_const(self.ctx.clone(), self.function) }
89 }
90
91 pub fn this(&self) -> Value<'js> {
93 unsafe { Value::from_js_value_const(self.ctx.clone(), self.this) }
94 }
95
96 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 pub fn len(&self) -> usize {
105 self.args.len()
106 }
107
108 pub fn is_empty(&self) -> bool {
110 self.args.is_empty()
111 }
112
113 pub fn is_constructor(&self) -> bool {
117 self.is_constructor
118 }
119
120 pub fn access(self) -> ParamsAccessor<'a, 'js> {
122 ParamsAccessor {
123 params: self,
124 offset: 0,
125 }
126 }
127}
128
129pub struct ParamsAccessor<'a, 'js> {
131 params: Params<'a, 'js>,
132 offset: usize,
133}
134
135impl<'a, 'js> ParamsAccessor<'a, 'js> {
136 pub fn ctx(&self) -> &Ctx<'js> {
138 self.params.ctx()
139 }
140
141 pub fn this(&self) -> Value<'js> {
143 self.params.this()
144 }
145
146 pub fn function(&self) -> Value<'js> {
148 self.params.function()
149 }
150
151 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 unsafe { Value::from_js_value_const(self.params.ctx.clone(), res) }
166 }
167
168 pub fn len(&self) -> usize {
170 self.params.args.len() - self.offset
171 }
172 pub fn is_empty(&self) -> bool {
174 self.len() == 0
175 }
176}
177
178pub struct ParamRequirement {
180 min: usize,
181 max: usize,
182 exhaustive: bool,
183}
184
185impl ParamRequirement {
186 pub const fn single() -> Self {
188 ParamRequirement {
189 min: 1,
190 max: 1,
191 exhaustive: false,
192 }
193 }
194
195 pub const fn exhaustive() -> Self {
198 ParamRequirement {
199 min: 0,
200 max: 0,
201 exhaustive: true,
202 }
203 }
204
205 pub const fn optional() -> Self {
207 ParamRequirement {
208 min: 0,
209 max: 1,
210 exhaustive: false,
211 }
212 }
213
214 pub const fn any() -> Self {
216 ParamRequirement {
217 min: 0,
218 max: usize::MAX,
219 exhaustive: false,
220 }
221 }
222
223 pub const fn none() -> Self {
225 ParamRequirement {
226 min: 0,
227 max: 0,
228 exhaustive: false,
229 }
230 }
231
232 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 pub fn min(&self) -> usize {
243 self.min
244 }
245
246 pub fn max(&self) -> usize {
248 self.max
249 }
250
251 pub fn is_exhaustive(&self) -> bool {
255 self.exhaustive
256 }
257}
258
259pub trait FromParam<'js>: Sized {
261 fn param_requirement() -> ParamRequirement;
263
264 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
359pub trait FromParams<'js>: Sized {
361 fn param_requirements() -> ParamRequirement;
363
364 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}