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 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 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 pub fn ctx(&self) -> &Ctx<'js> {
85 &self.ctx
86 }
87
88 pub fn function(&self) -> Value<'js> {
90 unsafe { Value::from_js_value_const(self.ctx.clone(), self.function) }
91 }
92
93 pub fn this(&self) -> Value<'js> {
95 unsafe { Value::from_js_value_const(self.ctx.clone(), self.this) }
96 }
97
98 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 pub fn len(&self) -> usize {
107 self.args.len()
108 }
109
110 pub fn is_empty(&self) -> bool {
112 self.args.is_empty()
113 }
114
115 pub fn is_constructor(&self) -> bool {
119 self.is_constructor
120 }
121
122 pub fn access(self) -> ParamsAccessor<'a, 'js> {
124 ParamsAccessor {
125 params: self,
126 offset: 0,
127 }
128 }
129}
130
131pub struct ParamsAccessor<'a, 'js> {
133 params: Params<'a, 'js>,
134 offset: usize,
135}
136
137impl<'a, 'js> ParamsAccessor<'a, 'js> {
138 pub fn ctx(&self) -> &Ctx<'js> {
140 self.params.ctx()
141 }
142
143 pub fn this(&self) -> Value<'js> {
145 self.params.this()
146 }
147
148 pub fn function(&self) -> Value<'js> {
150 self.params.function()
151 }
152
153 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 unsafe { Value::from_js_value_const(self.params.ctx.clone(), res) }
168 }
169
170 pub fn len(&self) -> usize {
172 self.params.args.len() - self.offset
173 }
174 pub fn is_empty(&self) -> bool {
176 self.len() == 0
177 }
178}
179
180pub struct ParamRequirement {
182 min: usize,
183 max: usize,
184 exhaustive: bool,
185}
186
187impl ParamRequirement {
188 pub const fn single() -> Self {
190 ParamRequirement {
191 min: 1,
192 max: 1,
193 exhaustive: false,
194 }
195 }
196
197 pub const fn exhaustive() -> Self {
200 ParamRequirement {
201 min: 0,
202 max: 0,
203 exhaustive: true,
204 }
205 }
206
207 pub const fn optional() -> Self {
209 ParamRequirement {
210 min: 0,
211 max: 1,
212 exhaustive: false,
213 }
214 }
215
216 pub const fn any() -> Self {
218 ParamRequirement {
219 min: 0,
220 max: usize::MAX,
221 exhaustive: false,
222 }
223 }
224
225 pub const fn none() -> Self {
227 ParamRequirement {
228 min: 0,
229 max: 0,
230 exhaustive: false,
231 }
232 }
233
234 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 pub fn min(&self) -> usize {
245 self.min
246 }
247
248 pub fn max(&self) -> usize {
250 self.max
251 }
252
253 pub fn is_exhaustive(&self) -> bool {
257 self.exhaustive
258 }
259}
260
261pub trait FromParam<'js>: Sized {
263 fn param_requirement() -> ParamRequirement;
265
266 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
361pub trait FromParams<'js>: Sized {
363 fn param_requirements() -> ParamRequirement;
365
366 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}