1use crate::host::{fmt_number, with_host, JsObj};
17use fusevm::Value;
18use indexmap::IndexMap;
19
20pub const STATIC_METHODS: &[&str] = &["from", "of", "isView"];
21
22pub const UINT8_STATIC_METHODS: &[&str] = &["from", "of", "isView", "fromBase64", "fromHex"];
25
26pub const UINT8_PROTOTYPE_METHODS: &[&str] = &["toBase64", "setFromBase64", "toHex", "setFromHex"];
29
30pub fn static_methods(kind: &str) -> &'static [&'static str] {
32 if kind == "Uint8Array" {
33 UINT8_STATIC_METHODS
34 } else {
35 STATIC_METHODS
36 }
37}
38
39pub const PROTOTYPE_METHODS: &[&str] = &[
44 "at",
45 "copyWithin",
46 "entries",
47 "every",
48 "fill",
49 "filter",
50 "find",
51 "findIndex",
52 "findLast",
53 "findLastIndex",
54 "forEach",
55 "includes",
56 "indexOf",
57 "join",
58 "keys",
59 "lastIndexOf",
60 "map",
61 "reduce",
62 "reduceRight",
63 "reverse",
64 "set",
65 "slice",
66 "some",
67 "sort",
68 "subarray",
69 "toReversed",
70 "toSorted",
71 "toString",
72 "values",
73 "with",
74];
75
76pub fn is_ctor(name: &str) -> bool {
78 ELEMENT_KINDS.contains(&name) || matches!(name, "ArrayBuffer" | "DataView")
79}
80
81pub const ELEMENT_KINDS: &[&str] = &[
90 "Uint8Array",
91 "Int8Array",
92 "Uint8ClampedArray",
93 "Int16Array",
94 "Uint16Array",
95 "Int32Array",
96 "Uint32Array",
97 "Float32Array",
98 "Float64Array",
99 "BigInt64Array",
101 "BigUint64Array",
102];
103
104pub fn bytes_per_element(kind: &str) -> usize {
106 match kind {
107 "Int8Array" | "Uint8Array" | "Uint8ClampedArray" => 1,
108 "Int16Array" | "Uint16Array" => 2,
109 "Int32Array" | "Uint32Array" | "Float32Array" => 4,
110 "Float64Array" | "BigInt64Array" | "BigUint64Array" => 8,
111 _ => 1,
112 }
113}
114
115fn coerce(kind: &str, n: f64) -> f64 {
118 match kind {
119 "Int8Array" => (n as i64 as i8) as f64,
120 "Uint8Array" => (n as i64 as u8) as f64,
121 "Uint8ClampedArray" => {
122 if n.is_nan() {
123 0.0
124 } else {
125 n.round().clamp(0.0, 255.0)
126 }
127 }
128 "Int16Array" => (n as i64 as i16) as f64,
129 "Uint16Array" => (n as i64 as u16) as f64,
130 "Int32Array" => (n as i64 as i32) as f64,
131 "Uint32Array" => (n as i64 as u32) as f64,
132 "Float32Array" => n as f32 as f64,
133 _ => n, }
135}
136
137pub fn is_bigint_kind(kind: &str) -> bool {
141 matches!(kind, "BigInt64Array" | "BigUint64Array")
142}
143
144fn coerce_val(kind: &str, v: &Value) -> Result<Value, String> {
148 if !is_bigint_kind(kind) {
149 return Ok(Value::Float(coerce(kind, with_host(|h| h.to_number(v)))));
150 }
151 let big = crate::builtins::to_bigint(v)?;
157 Ok(with_host(|h| h.new_bigint(wrap_bigint(kind, big))))
158}
159
160fn wrap_bigint(kind: &str, b: num_bigint::BigInt) -> num_bigint::BigInt {
163 use num_traits::cast::ToPrimitive;
164 let modulus = num_bigint::BigInt::from(1u128 << 64);
165 let mut m = b % &modulus;
166 if m.sign() == num_bigint::Sign::Minus {
167 m += &modulus;
168 }
169 let raw = m.to_u64().unwrap_or(0);
171 if kind == "BigInt64Array" {
172 num_bigint::BigInt::from(raw as i64)
173 } else {
174 num_bigint::BigInt::from(raw)
175 }
176}
177
178fn bigint_of(v: &Value) -> num_bigint::BigInt {
181 with_host(|h| match h.get(v) {
182 Some(JsObj::BigInt(b)) => b.clone(),
183 _ => num_bigint::BigInt::from(0),
184 })
185}
186
187fn same_element(stored: &Value, needle: &Value, nan_matches: bool) -> bool {
195 if nan_matches {
196 if let (Value::Float(a), Value::Float(b)) = (stored, needle) {
197 if a.is_nan() && b.is_nan() {
198 return true;
199 }
200 }
201 }
202 with_host(|h| h.strict_eq(stored, needle))
203}
204
205fn zero_of(kind: &str) -> Value {
207 if is_bigint_kind(kind) {
208 with_host(|h| h.new_bigint(num_bigint::BigInt::from(0)))
209 } else {
210 Value::Float(0.0)
211 }
212}
213
214fn num(v: &Value) -> f64 {
218 with_host(|h| h.to_number(v))
219}
220
221pub fn elem_values(v: &Value) -> Vec<Value> {
225 let Some(tag) = super::native_tag(v) else {
226 return Vec::new();
227 };
228 if tag == "TypedArray" {
229 let kind = kind_of(v);
230 let bpe = bytes_per_element(&kind);
231 return (0..view_len(v))
232 .map(|i| {
233 view_bytes(v, i * bpe, bpe)
234 .map(|b| decode(&kind, &b))
235 .unwrap_or(Value::Undef)
236 })
237 .collect();
238 }
239 if tag != "Buffer" {
240 return Vec::new();
241 }
242 with_host(|h| match h.get(v) {
243 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|a| h.get(a)) {
244 Some(JsObj::Array(items)) => items.clone(),
245 _ => Vec::new(),
246 },
247 _ => Vec::new(),
248 })
249}
250
251fn make(kind: &str, elems: Vec<Value>) -> Value {
253 let bpe = bytes_per_element(kind);
254 let len = elems.len();
255 let buf = new_array_buffer(len * bpe);
256 let view = make_view(kind, &buf, 0, len);
257 for (i, e) in elems.iter().enumerate() {
258 write_view_bytes(&view, i * bpe, &encode(kind, e));
259 }
260 view
261}
262
263fn make_view(kind: &str, buf: &Value, byte_off: usize, len: usize) -> Value {
265 with_host(|h| {
266 let bpe = bytes_per_element(kind);
267 let mut m = IndexMap::new();
268 m.insert("@@native".into(), h.new_str("TypedArray"));
269 m.insert("@@kind".into(), h.new_str(kind));
270 m.insert("@@buffer".into(), buf.clone());
271 m.insert("buffer".into(), buf.clone());
274 m.insert("length".into(), Value::Float(len as f64));
275 m.insert("byteLength".into(), Value::Float((len * bpe) as f64));
276 m.insert("byteOffset".into(), Value::Float(byte_off as f64));
281 m.insert("BYTES_PER_ELEMENT".into(), Value::Float(bpe as f64));
282 let obj = h.new_object(m);
283 h.ensure_native_protos();
289 if let Some(p) = h.native_proto(kind) {
290 h.set_proto(&obj, p);
291 }
292 for k in [
294 "buffer",
295 "length",
296 "byteLength",
297 "byteOffset",
298 "BYTES_PER_ELEMENT",
299 ] {
300 h.hide_prop(&obj, k);
301 }
302 obj
303 })
304}
305
306fn integer_or_infinity(n: f64) -> f64 {
308 if n.is_nan() {
309 0.0
310 } else {
311 n.trunc() + 0.0
312 }
313}
314
315fn to_index(n: f64) -> Option<usize> {
317 let i = integer_or_infinity(n);
318 (0.0..=9_007_199_254_740_991.0)
319 .contains(&i)
320 .then_some(i as usize)
321}
322
323fn is_primitive(v: &Value) -> bool {
325 match v {
326 Value::Obj(_) => with_host(|h| {
327 matches!(
328 h.get(v),
329 Some(JsObj::Str(_))
330 | Some(JsObj::Null)
331 | Some(JsObj::BigInt(_))
332 | Some(JsObj::Symbol { .. })
333 )
334 }),
335 _ => true,
336 }
337}
338
339pub fn construct(kind: &str, args: &[Value]) -> Result<Value, String> {
342 if kind == "ArrayBuffer" {
343 let n = to_index(super::arg_num(args, 0))
344 .ok_or_else(|| crate::host::range_error("Invalid array buffer length"))?;
345 let max = match args.get(1) {
347 Some(opts) => {
348 crate::builtins::get_property(opts, "maxByteLength").unwrap_or(Value::Undef)
349 }
350 None => Value::Undef,
351 };
352 let max_len = match max {
353 Value::Undef => None,
354 _ => match to_index(with_host(|h| h.to_number(&max))) {
355 Some(m) if m >= n => Some(m),
356 _ => return Err(crate::host::range_error("Invalid array buffer max length")),
357 },
358 };
359 let ab = new_array_buffer(n);
360 if let Some(m) = max_len {
363 with_host(|h| {
364 if let Some(JsObj::Object(p)) = h.get_mut(&ab) {
365 p.insert("@@maxByteLength".into(), Value::Float(m as f64));
366 p.insert("maxByteLength".into(), Value::Float(m as f64));
367 p.insert("resizable".into(), Value::Bool(true));
368 }
369 h.hide_prop(&ab, "maxByteLength");
370 h.hide_prop(&ab, "resizable");
371 });
372 }
373 return Ok(ab);
374 }
375 if let Some(first) = args.first() {
380 if super::native_tag(first).as_deref() == Some("ArrayBuffer") {
381 if is_detached(first) {
383 return Err(crate::host::type_error(
384 "Cannot perform Construct on a detached ArrayBuffer",
385 ));
386 }
387 let bpe = bytes_per_element(kind);
390 let total = buffer_byte_length(first);
391 let off_n = super::arg_num(args, 1);
392 let off = to_index(off_n).ok_or_else(|| {
393 crate::host::range_error(&format!(
394 "Start offset {} is outside the bounds of the buffer",
395 fmt_number(off_n)
396 ))
397 })?;
398 if off % bpe != 0 {
399 return Err(crate::host::range_error(&format!(
400 "start offset of {kind} should be a multiple of {bpe}"
401 )));
402 }
403 let len = match args.get(2) {
404 Some(Value::Undef) | None => {
405 if total % bpe != 0 {
406 return Err(crate::host::range_error(&format!(
407 "byte length of {kind} should be a multiple of {bpe}"
408 )));
409 }
410 if off > total {
411 return Err(crate::host::range_error(&format!(
412 "Start offset {off} is outside the bounds of the buffer"
413 )));
414 }
415 (total - off) / bpe
416 }
417 Some(_) => {
418 let len_n = super::arg_num(args, 2);
419 let bad = || {
420 crate::host::range_error(&format!(
421 "Invalid typed array length: {}",
422 fmt_number(len_n)
423 ))
424 };
425 let len = to_index(len_n).ok_or_else(bad)?;
426 if off + len * bpe > total {
427 return Err(bad());
428 }
429 len
430 }
431 };
432 return Ok(make_view(kind, first, off, len));
433 }
434 }
435 let elems = build_elems(kind, args)?;
436 Ok(make(kind, elems))
437}
438
439pub const DATAVIEW_METHODS: &[&str] = &[
441 "getInt8",
442 "getUint8",
443 "getInt16",
444 "getUint16",
445 "getInt32",
446 "getUint32",
447 "getFloat32",
448 "getFloat64",
449 "getBigInt64",
450 "getBigUint64",
451 "setInt8",
452 "setUint8",
453 "setInt16",
454 "setUint16",
455 "setInt32",
456 "setUint32",
457 "setFloat32",
458 "setFloat64",
459 "setBigInt64",
460 "setBigUint64",
461];
462
463pub fn construct_dataview(args: &[Value]) -> Result<Value, String> {
465 let buf = args.first().cloned().unwrap_or(Value::Undef);
466 if super::native_tag(&buf).as_deref() != Some("ArrayBuffer") {
467 return Err(crate::host::type_error(
468 "First argument to DataView constructor must be an ArrayBuffer",
469 ));
470 }
471 let total = buffer_byte_length(&buf);
474 let off_n = super::arg_num(args, 1);
475 let outside = |n: f64| {
476 crate::host::range_error(&format!(
477 "Start offset {} is outside the bounds of the buffer",
478 fmt_number(integer_or_infinity(n))
479 ))
480 };
481 let off = to_index(off_n).ok_or_else(|| outside(off_n))?;
482 if off > total {
483 return Err(outside(off_n));
484 }
485 let bad_len = |n: f64| {
486 crate::host::range_error(&format!(
487 "Invalid DataView length {}",
488 fmt_number(integer_or_infinity(n))
489 ))
490 };
491 let len = match args.get(2) {
492 Some(Value::Undef) | None => total - off,
493 Some(_) => {
494 let len_n = super::arg_num(args, 2);
495 let len = to_index(len_n).ok_or_else(|| bad_len(len_n))?;
496 if off + len > total {
497 return Err(bad_len(len_n));
498 }
499 len
500 }
501 };
502 Ok(with_host(|h| {
503 let mut m = IndexMap::new();
504 m.insert("@@native".into(), h.new_str("DataView"));
505 m.insert("@@buffer".into(), buf.clone());
506 m.insert("buffer".into(), buf.clone());
507 m.insert("byteOffset".into(), Value::Float(off as f64));
508 m.insert("byteLength".into(), Value::Float(len as f64));
509 let obj = h.new_object(m);
510 for k in ["buffer", "byteOffset", "byteLength"] {
511 h.hide_prop(&obj, k);
512 }
513 h.ensure_native_protos();
514 if let Some(p) = h.ensure_ctor_proto("DataView") {
515 h.set_proto(&obj, p);
516 }
517 obj
518 }))
519}
520
521pub fn dataview_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
524 if view_detached(recv) {
525 return Err(detached_error("DataView.prototype", method, false));
526 }
527 let Some(spec) = method.get(3..) else {
528 return Err(crate::host::type_error(&format!(
529 "{method} is not a function"
530 )));
531 };
532 let width = match spec {
533 "Int8" | "Uint8" => 1,
534 "Int16" | "Uint16" => 2,
535 "Int32" | "Uint32" | "Float32" => 4,
536 "Float64" | "BigInt64" | "BigUint64" => 8,
537 _ => {
538 return Err(crate::host::type_error(&format!(
539 "{method} is not a function"
540 )))
541 }
542 };
543 let is_get = method.starts_with("get");
544 let requested = super::arg_num(args, 0);
549 let requested = if requested.is_nan() {
550 0.0
551 } else {
552 requested.trunc()
553 };
554 let span = with_host(|h| match h.get(recv) {
555 Some(JsObj::Object(p)) => p.get("byteLength").map(|l| h.to_number(l)).unwrap_or(0.0),
556 _ => 0.0,
557 });
558 if requested < 0.0 || requested + width as f64 > span {
559 return Err(crate::host::range_error(
560 "Offset is outside the bounds of the DataView",
561 ));
562 }
563 let at = requested as usize;
564 let le = with_host(|h| {
567 h.truthy(
568 args.get(if is_get { 1 } else { 2 })
569 .unwrap_or(&Value::Undef),
570 )
571 });
572 if is_get {
573 let mut b = view_bytes(recv, at, width).unwrap_or_else(|| vec![0; width]);
574 if !le {
575 b.reverse();
576 }
577 return Ok(match spec {
578 "Int8" => Value::Float(b[0] as i8 as f64),
579 "Uint8" => Value::Float(b[0] as f64),
580 "Int16" => Value::Float(i16::from_le_bytes([b[0], b[1]]) as f64),
581 "Uint16" => Value::Float(u16::from_le_bytes([b[0], b[1]]) as f64),
582 "Int32" => Value::Float(i32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
583 "Uint32" => Value::Float(u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
584 "Float32" => Value::Float(f32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
585 "Float64" => Value::Float(f64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]))),
586 "BigInt64" => {
587 let raw = i64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
588 with_host(|h| h.new_bigint(num_bigint::BigInt::from(raw)))
589 }
590 _ => {
591 let raw = u64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
592 with_host(|h| h.new_bigint(num_bigint::BigInt::from(raw)))
593 }
594 });
595 }
596 let val = args.get(1).cloned().unwrap_or(Value::Undef);
597 let mut b = match spec {
598 "BigInt64" | "BigUint64" => {
599 use num_traits::cast::ToPrimitive;
600 let big = crate::builtins::to_bigint(&val)?;
603 let raw = if spec == "BigInt64" {
604 big.to_i64().unwrap_or(0) as u64
605 } else {
606 big.to_u64().unwrap_or(0)
607 };
608 raw.to_le_bytes().to_vec()
609 }
610 _ => {
611 let n = with_host(|h| h.to_number(&val));
612 match spec {
613 "Int8" | "Uint8" => vec![n as i64 as u8],
614 "Int16" | "Uint16" => (n as i64 as u16).to_le_bytes().to_vec(),
615 "Int32" | "Uint32" => (n as i64 as u32).to_le_bytes().to_vec(),
616 "Float32" => (n as f32).to_le_bytes().to_vec(),
617 _ => n.to_le_bytes().to_vec(),
618 }
619 }
620 };
621 if !le {
622 b.reverse();
623 }
624 write_view_bytes(recv, at, &b);
625 Ok(Value::Undef)
626}
627
628pub fn buffer_resize(ab: &Value, args: &[Value]) -> Result<Value, String> {
631 let max = with_host(|h| match h.get(ab) {
632 Some(JsObj::Object(p)) => p.get("@@maxByteLength").map(|m| h.to_number(m) as usize),
633 _ => None,
634 })
635 .ok_or_else(|| {
636 crate::host::type_error(
637 "ArrayBuffer.prototype.resize called on a non-resizable ArrayBuffer",
638 )
639 })?;
640 let n = super::arg_num(args, 0).max(0.0) as usize;
641 if n > max {
642 return Err(crate::host::range_error("Invalid array buffer length"));
643 }
644 let store = store_of(ab);
645 with_host(|h| {
646 if let Some(a) = store {
647 if let Some(JsObj::Array(items)) = h.get_mut(&a) {
648 items.resize(n, Value::Float(0.0));
649 }
650 }
651 if let Some(JsObj::Object(p)) = h.get_mut(ab) {
652 p.insert("byteLength".into(), Value::Float(n as f64));
653 }
654 });
655 Ok(Value::Undef)
656}
657
658pub fn write_buffer_bytes(ab: &Value, bytes: &[u8]) {
661 let Some(store) = store_of(ab) else { return };
662 with_host(|h| {
663 if let Some(JsObj::Array(items)) = h.get_mut(&store) {
664 *items = bytes.iter().map(|b| Value::Float(*b as f64)).collect();
665 }
666 if let Some(JsObj::Object(p)) = h.get_mut(ab) {
667 p.insert("byteLength".into(), Value::Float(bytes.len() as f64));
668 }
669 });
670}
671
672pub fn buffer_store(ab: &Value) -> Option<Value> {
675 store_of(ab)
676}
677
678pub fn buffer_bytes_snapshot(ab: &Value) -> Option<Vec<u8>> {
680 let store = store_of(ab)?;
681 with_host(|h| match h.get(&store) {
682 Some(JsObj::Array(items)) => {
683 Some(items.iter().map(|x| h.to_number(x) as i64 as u8).collect())
684 }
685 _ => None,
686 })
687}
688
689pub fn buffer_byte_length(ab: &Value) -> usize {
691 with_host(|h| match h.get(ab) {
692 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|a| h.get(a)) {
693 Some(JsObj::Array(items)) => items.len(),
694 _ => 0,
695 },
696 _ => 0,
697 })
698}
699
700pub fn buffer_slice(ab: &Value, args: &[Value]) -> Value {
703 let total = buffer_byte_length(ab) as i64;
704 let idx = |v: Option<&Value>, dflt: i64| -> usize {
705 let n = match v {
706 None | Some(Value::Undef) => dflt,
707 Some(x) => with_host(|h| h.to_number(x)) as i64,
708 };
709 (if n < 0 { total + n } else { n }).clamp(0, total) as usize
710 };
711 let start = idx(args.first(), 0);
712 let end = idx(args.get(1), total).max(start);
713 let out = new_array_buffer(end - start);
714 let src = with_host(|h| match h.get(ab) {
715 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|a| h.get(a)) {
716 Some(JsObj::Array(items)) => items[start..end].to_vec(),
717 _ => Vec::new(),
718 },
719 _ => Vec::new(),
720 });
721 with_host(|h| {
722 if let Some(JsObj::Object(p)) = h.get(&out) {
723 if let Some(arr) = p.get("@@bytes").cloned() {
724 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
725 *items = src;
726 }
727 }
728 }
729 });
730 out
731}
732
733fn build_elems(kind: &str, args: &[Value]) -> Result<Vec<Value>, String> {
737 match args.first() {
738 None | Some(Value::Undef) => Ok(Vec::new()),
739 Some(v) if is_primitive(v) => {
743 let n_raw = super::arg_num(args, 0);
744 let n = to_index(n_raw).ok_or_else(|| {
745 crate::host::range_error(&format!(
746 "Invalid typed array length: {}",
747 fmt_number(n_raw)
748 ))
749 })?;
750 Ok(vec![zero_of(kind); n])
751 }
752 Some(v) => {
753 let items = match super::native_tag(v).as_deref() {
756 Some("TypedArray") | Some("Buffer") => elem_values(v),
757 _ => crate::host::iter_all(v).unwrap_or_default(),
758 };
759 items.iter().map(|x| coerce_val(kind, x)).collect()
760 }
761 }
762}
763
764#[derive(Clone, Copy, PartialEq)]
769enum LastChunk {
770 Loose,
771 Strict,
772 StopBeforePartial,
773}
774
775fn base64_options(opt: Option<&Value>) -> Result<(bool, LastChunk), String> {
779 let Some(o) = opt.filter(|v| !matches!(v, Value::Undef)) else {
780 return Ok((false, LastChunk::Loose));
781 };
782 if !with_host(|h| matches!(h.get(o), Some(JsObj::Object(_)))) {
783 return Err(crate::host::type_error("invalid_argument"));
784 }
785 let read = |k: &str| {
786 with_host(|h| match h.get(o) {
787 Some(JsObj::Object(p)) => p.get(k).filter(|v| !matches!(v, Value::Undef)).cloned(),
788 _ => None,
789 })
790 };
791 let url = match read("alphabet") {
792 None => false,
793 Some(v) => match with_host(|h| h.str_of(&v)).as_str() {
794 "base64" => false,
795 "base64url" => true,
796 other => return Err(crate::host::type_error(&format!("invalid option {other}"))),
797 },
798 };
799 let last = match read("lastChunkHandling") {
800 None => LastChunk::Loose,
801 Some(v) => match with_host(|h| h.str_of(&v)).as_str() {
802 "loose" => LastChunk::Loose,
803 "strict" => LastChunk::Strict,
804 "stop-before-partial" => LastChunk::StopBeforePartial,
805 other => return Err(crate::host::type_error(&format!("invalid option {other}"))),
806 },
807 };
808 Ok((url, last))
809}
810
811const B64_BAD: &str =
812 "SyntaxError: Found a character that cannot be part of a valid base64 string.";
813const B64_SINGLE: &str =
814 "SyntaxError: The base64 input terminates with a single character, excluding padding (=).";
815
816fn decode_base64_strict(s: &str, url: bool, last: LastChunk) -> Result<(Vec<u8>, usize), String> {
823 let value = |c: char| -> Option<u32> {
824 let table = if url {
825 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
826 } else {
827 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
828 };
829 table.find(c).map(|i| i as u32)
830 };
831 let chars: Vec<char> = s.chars().collect();
832 let mut out = Vec::new();
833 let mut chunk: Vec<u32> = Vec::new();
834 let mut consumed = 0usize;
835 let mut i = 0usize;
836 while i < chars.len() {
837 let c = chars[i];
838 if c.is_ascii_whitespace() {
839 i += 1;
840 continue;
841 }
842 if c == '=' {
843 let pads = chars[i..].iter().filter(|c| **c == '=').count();
846 let rest_ok = chars[i..]
847 .iter()
848 .all(|c| *c == '=' || c.is_ascii_whitespace());
849 let want = 4 - chunk.len();
850 if !rest_ok || chunk.len() < 2 || pads != want {
851 return Err(B64_BAD.into());
852 }
853 out.extend(flush_base64_chunk(&chunk));
854 return Ok((out, chars.len()));
855 }
856 let Some(v) = value(c) else {
857 return Err(B64_BAD.into());
858 };
859 chunk.push(v);
860 i += 1;
861 if chunk.len() == 4 {
862 out.extend(flush_base64_chunk(&chunk));
863 chunk.clear();
864 consumed = i;
865 }
866 }
867 match chunk.len() {
868 0 => Ok((out, consumed)),
869 1 if last != LastChunk::StopBeforePartial => Err(B64_SINGLE.into()),
871 _ if last == LastChunk::StopBeforePartial => Ok((out, consumed)),
872 1 => Ok((out, consumed)),
873 _ if last == LastChunk::Strict => Err(B64_SINGLE.into()),
874 _ => {
875 out.extend(flush_base64_chunk(&chunk));
876 Ok((out, chars.len()))
877 }
878 }
879}
880
881fn flush_base64_chunk(chunk: &[u32]) -> Vec<u8> {
883 let mut acc = 0u32;
884 for v in chunk {
885 acc = (acc << 6) | v;
886 }
887 let bytes = chunk.len() - 1;
888 acc <<= 6 * (4 - chunk.len());
889 let all = [(acc >> 16) as u8, (acc >> 8) as u8, acc as u8];
890 all[..bytes].to_vec()
891}
892
893const HEX_BAD: &str = "SyntaxError: Input string must contain hex characters in even length";
894
895fn decode_hex_strict(s: &str) -> Result<Vec<u8>, String> {
898 let chars: Vec<char> = s.chars().collect();
899 if chars.len() % 2 != 0 || !chars.iter().all(|c| c.is_ascii_hexdigit()) {
900 return Err(HEX_BAD.into());
901 }
902 Ok(chars
903 .chunks(2)
904 .map(|p| {
905 let hi = p[0].to_digit(16).expect("checked");
906 let lo = p[1].to_digit(16).expect("checked");
907 (hi * 16 + lo) as u8
908 })
909 .collect())
910}
911
912fn base64_input(args: &[Value]) -> Result<String, String> {
915 let v = args.first().cloned().unwrap_or(Value::Undef);
916 let is_str = matches!(v, Value::Str(_))
917 || with_host(|h| matches!(h.get(&v), Some(crate::host::JsObj::Str(_))));
918 if !is_str {
919 return Err(crate::host::type_error("input argument must be a string"));
920 }
921 Ok(with_host(|h| h.str_of(&v)))
922}
923
924fn from_base64_static(method: &str, args: &[Value]) -> Result<Value, String> {
926 let s = base64_input(args)?;
927 let bytes = if method == "fromHex" {
928 decode_hex_strict(&s)?
929 } else {
930 let (url, last) = base64_options(args.get(1))?;
931 decode_base64_strict(&s, url, last)?.0
932 };
933 Ok(make(
934 "Uint8Array",
935 bytes.iter().map(|b| Value::Float(*b as f64)).collect(),
936 ))
937}
938
939pub fn static_call(kind: &str, method: &str, args: &[Value]) -> Option<Result<Value, String>> {
942 if matches!(method, "fromBase64" | "fromHex") {
944 if kind != "Uint8Array" {
945 return None;
946 }
947 return Some(from_base64_static(method, args));
948 }
949 Some(match method {
950 "of" => args
951 .iter()
952 .map(|x| coerce_val(kind, x))
953 .collect::<Result<Vec<Value>, String>>()
954 .map(|e| make(kind, e)),
955 "from" => from(kind, args),
956 "isView" => Ok(Value::Bool(with_host(|h| {
959 matches!(
960 h.get(&args.first().cloned().unwrap_or(Value::Undef)),
961 Some(crate::host::JsObj::Object(p))
962 if matches!(
963 p.get("@@native").map(|t| h.str_of(t)).as_deref(),
964 Some("TypedArray") | Some("Buffer") | Some("DataView")
965 )
966 )
967 }))),
968 _ => return None,
969 })
970}
971
972fn from(kind: &str, args: &[Value]) -> Result<Value, String> {
973 let src = args.first().cloned().unwrap_or(Value::Undef);
974 let map_fn = args
975 .get(1)
976 .cloned()
977 .filter(|f| with_host(|h| crate::host::is_callable(h, f)));
978 let items = if let Some(e) = elems_of(&src) {
981 e.into_iter().map(Value::Float).collect()
982 } else {
983 crate::host::iter_all(&src).unwrap_or_else(|_| crate::builtins::array_like_items(&src))
984 };
985 let mut out = Vec::with_capacity(items.len());
986 for (i, it) in items.into_iter().enumerate() {
987 let mapped = match &map_fn {
988 Some(f) => crate::host::invoke(f, vec![it, Value::Float(i as f64)], None)?,
989 None => it,
990 };
991 out.push(coerce_val(kind, &mapped)?);
992 }
993 Ok(make(kind, out))
994}
995
996pub fn elems_of(v: &Value) -> Option<Vec<f64>> {
998 let tag = super::native_tag(v)?;
999 if !matches!(tag.as_str(), "TypedArray" | "Buffer") {
1000 return None;
1001 }
1002 let vals = elem_values(v);
1003 Some(with_host(|h| vals.iter().map(|x| h.to_number(x)).collect()))
1004}
1005
1006pub fn index_len(v: &Value) -> Option<usize> {
1015 match super::native_tag(v)?.as_str() {
1016 "TypedArray" => Some(view_len(v)),
1017 "Buffer" => with_host(|h| match h.get(v) {
1018 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|a| h.get(a)) {
1019 Some(JsObj::Array(items)) => Some(items.len()),
1020 _ => None,
1021 },
1022 _ => None,
1023 }),
1024 _ => None,
1025 }
1026}
1027
1028pub fn has_index(v: &Value, key: &str) -> Option<bool> {
1031 let len = index_len(v)?;
1032 Some(key.parse::<usize>().map(|i| i < len).unwrap_or(false))
1033}
1034
1035pub fn kind_of(recv: &Value) -> String {
1037 with_host(|h| match h.get(recv) {
1038 Some(JsObj::Object(p)) => p
1039 .get("@@kind")
1040 .map(|v| h.str_of(v))
1041 .unwrap_or_else(|| "Uint8Array".into()),
1042 _ => "Uint8Array".into(),
1043 })
1044}
1045
1046pub fn new_array_buffer(n: usize) -> Value {
1058 with_host(|h| {
1059 let arr = h.new_array(vec![Value::Float(0.0); n]);
1060 let mut m = IndexMap::new();
1061 m.insert("@@native".into(), h.new_str("ArrayBuffer"));
1062 m.insert("@@bytes".into(), arr);
1063 m.insert("byteLength".into(), Value::Float(n as f64));
1064 m.insert("detached".into(), Value::Bool(false));
1068 m.insert("resizable".into(), Value::Bool(false));
1071 m.insert("maxByteLength".into(), Value::Float(n as f64));
1072 let obj = h.new_object(m);
1073 for k in ["byteLength", "detached", "resizable", "maxByteLength"] {
1074 h.hide_prop(&obj, k);
1075 }
1076 if let Some(p) = h.ensure_ctor_proto("ArrayBuffer") {
1079 h.set_proto(&obj, p);
1080 }
1081 obj
1082 })
1083}
1084
1085pub fn is_detached(ab: &Value) -> bool {
1091 with_host(|h| match h.get(ab) {
1092 Some(JsObj::Object(p)) => p.get("detached").map(|v| h.truthy(v)).unwrap_or(false),
1093 _ => false,
1094 })
1095}
1096
1097pub fn view_detached(v: &Value) -> bool {
1099 with_host(|h| view_detached_h(h, v))
1100}
1101
1102pub fn view_detached_h(h: &crate::host::JsHost, v: &Value) -> bool {
1105 let buf = match h.get(v) {
1106 Some(JsObj::Object(p)) => p.get("@@buffer").cloned(),
1107 _ => None,
1108 };
1109 match buf.and_then(|b| match h.get(&b) {
1110 Some(JsObj::Object(p)) => p.get("detached").cloned(),
1111 _ => None,
1112 }) {
1113 Some(d) => h.truthy(&d),
1114 None => false,
1115 }
1116}
1117
1118pub fn detach_buffer(ab: &Value) {
1121 detach(ab)
1122}
1123
1124fn detach(ab: &Value) {
1125 with_host(|h| {
1126 let empty = h.new_array(Vec::new());
1127 if let Some(JsObj::Object(p)) = h.get_mut(ab) {
1128 p.insert("@@bytes".into(), empty);
1129 p.insert("byteLength".into(), Value::Float(0.0));
1130 p.insert("detached".into(), Value::Bool(true));
1131 }
1132 h.hide_prop(ab, "byteLength");
1133 h.hide_prop(ab, "detached");
1134 });
1135}
1136
1137pub fn buffer_transfer(ab: &Value, args: &[Value], fixed: bool) -> Result<Value, String> {
1143 let method = if fixed {
1144 "transferToFixedLength"
1145 } else {
1146 "transfer"
1147 };
1148 if is_detached(ab) {
1149 return Err(crate::host::type_error(&format!(
1150 "Cannot perform ArrayBuffer.prototype.{method} on a detached ArrayBuffer"
1151 )));
1152 }
1153 let old = byte_len_of(ab);
1154 let new_len = match args.first().filter(|v| !matches!(v, Value::Undef)) {
1155 Some(v) => with_host(|h| h.to_number(v)).max(0.0) as usize,
1156 None => old,
1157 };
1158 let mut bytes = view_bytes_of_buffer(ab, old);
1159 bytes.resize(new_len, 0);
1160 let out = new_array_buffer(new_len);
1161 write_buffer_bytes(&out, &bytes);
1162 if !fixed {
1163 let resizable = with_host(|h| match h.get(ab) {
1166 Some(JsObj::Object(p)) => p.contains_key("@@maxByteLength"),
1167 _ => false,
1168 });
1169 if resizable {
1170 let max = with_host(|h| match h.get(ab) {
1171 Some(JsObj::Object(p)) => p.get("@@maxByteLength").cloned(),
1172 _ => None,
1173 });
1174 if let Some(max) = max {
1175 with_host(|h| {
1176 if let Some(JsObj::Object(p)) = h.get_mut(&out) {
1177 p.insert("@@maxByteLength".into(), max);
1178 }
1179 });
1180 }
1181 }
1182 }
1183 detach(ab);
1184 Ok(out)
1185}
1186
1187fn byte_len_of(ab: &Value) -> usize {
1189 with_host(|h| match h.get(ab) {
1190 Some(JsObj::Object(p)) => {
1191 p.get("byteLength").map(|l| h.to_number(l)).unwrap_or(0.0) as usize
1192 }
1193 _ => 0,
1194 })
1195}
1196
1197fn view_bytes_of_buffer(ab: &Value, n: usize) -> Vec<u8> {
1199 let Some(store) = store_of(ab) else {
1200 return Vec::new();
1201 };
1202 with_host(|h| match h.get(&store) {
1203 Some(JsObj::Array(items)) => items
1204 .iter()
1205 .take(n)
1206 .map(|x| h.to_number(x) as i64 as u8)
1207 .collect(),
1208 _ => Vec::new(),
1209 })
1210}
1211
1212pub fn detached_error(label: &str, method: &str, buffer_only: bool) -> String {
1215 let tail = if buffer_only {
1216 "a detached ArrayBuffer"
1217 } else {
1218 "a detached or out-of-bounds ArrayBuffer"
1219 };
1220 let method = match method {
1226 "@@iterator" => "values",
1227 other => other,
1228 };
1229 crate::host::type_error(&format!("Cannot perform {label}.{method} on {tail}"))
1230}
1231
1232fn store_of(ab: &Value) -> Option<Value> {
1234 with_host(|h| match h.get(ab) {
1235 Some(JsObj::Object(p)) => p.get("@@bytes").cloned(),
1236 _ => None,
1237 })
1238}
1239
1240fn view_base(v: &Value) -> Option<(Value, usize)> {
1242 with_host(|h| match h.get(v) {
1243 Some(JsObj::Object(p)) => {
1244 let buf = p.get("@@buffer").cloned()?;
1245 let off = p.get("byteOffset").map(|o| h.to_number(o)).unwrap_or(0.0);
1246 Some((buf, off.max(0.0) as usize))
1247 }
1248 _ => None,
1249 })
1250}
1251
1252pub fn view_bytes(v: &Value, at: usize, n: usize) -> Option<Vec<u8>> {
1254 let (buf, off) = view_base(v)?;
1255 let store = store_of(&buf)?;
1256 with_host(|h| match h.get(&store) {
1257 Some(JsObj::Array(items)) => {
1258 let start = off + at;
1259 if start + n > items.len() {
1260 return None;
1261 }
1262 Some(
1263 items[start..start + n]
1264 .iter()
1265 .map(|x| h.to_number(x) as i64 as u8)
1266 .collect(),
1267 )
1268 }
1269 _ => None,
1270 })
1271}
1272
1273pub fn write_view_bytes(v: &Value, at: usize, bytes: &[u8]) -> bool {
1276 let Some((buf, off)) = view_base(v) else {
1277 return false;
1278 };
1279 let Some(store) = store_of(&buf) else {
1280 return false;
1281 };
1282 with_host(|h| match h.get_mut(&store) {
1283 Some(JsObj::Array(items)) => {
1284 let start = off + at;
1285 if start + bytes.len() > items.len() {
1286 return false;
1287 }
1288 for (i, b) in bytes.iter().enumerate() {
1289 items[start + i] = Value::Float(*b as f64);
1290 }
1291 true
1292 }
1293 _ => false,
1294 })
1295}
1296
1297fn decode(kind: &str, b: &[u8]) -> Value {
1300 match kind {
1301 "Int8Array" => Value::Float(b[0] as i8 as f64),
1302 "Uint8Array" | "Uint8ClampedArray" => Value::Float(b[0] as f64),
1303 "Int16Array" => Value::Float(i16::from_le_bytes([b[0], b[1]]) as f64),
1304 "Uint16Array" => Value::Float(u16::from_le_bytes([b[0], b[1]]) as f64),
1305 "Int32Array" => Value::Float(i32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
1306 "Uint32Array" => Value::Float(u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
1307 "Float32Array" => Value::Float(f32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
1308 "BigInt64Array" => {
1309 let raw = i64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
1310 with_host(|h| h.new_bigint(num_bigint::BigInt::from(raw)))
1311 }
1312 "BigUint64Array" => {
1313 let raw = u64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
1314 with_host(|h| h.new_bigint(num_bigint::BigInt::from(raw)))
1315 }
1316 _ => Value::Float(f64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]))),
1317 }
1318}
1319
1320fn encode(kind: &str, v: &Value) -> Vec<u8> {
1322 if is_bigint_kind(kind) {
1323 use num_traits::cast::ToPrimitive;
1324 let b = bigint_of(v);
1325 let raw = if kind == "BigInt64Array" {
1326 b.to_i64().unwrap_or(0) as u64
1327 } else {
1328 b.to_u64().unwrap_or(0)
1329 };
1330 return raw.to_le_bytes().to_vec();
1331 }
1332 let n = num(v);
1333 match kind {
1334 "Int8Array" => vec![n as i64 as i8 as u8],
1335 "Uint8Array" | "Uint8ClampedArray" => vec![n as i64 as u8],
1336 "Int16Array" => (n as i64 as i16).to_le_bytes().to_vec(),
1337 "Uint16Array" => (n as i64 as u16).to_le_bytes().to_vec(),
1338 "Int32Array" => (n as i64 as i32).to_le_bytes().to_vec(),
1339 "Uint32Array" => (n as i64 as u32).to_le_bytes().to_vec(),
1340 "Float32Array" => (n as f32).to_le_bytes().to_vec(),
1341 _ => n.to_le_bytes().to_vec(),
1342 }
1343}
1344
1345pub fn elems_with_host(h: &crate::host::JsHost, v: &Value) -> Vec<Value> {
1350 if let Some(JsObj::Object(p)) = h.get(v) {
1351 if let Some(arr) = p.get("@@bytes") {
1352 return match h.get(arr) {
1353 Some(JsObj::Array(items)) => items.clone(),
1354 _ => Vec::new(),
1355 };
1356 }
1357 }
1358 let Some((kind, raws)) = raw_elems(h, v) else {
1359 return Vec::new();
1360 };
1361 if is_bigint_kind(&kind) {
1364 return vec![Value::Undef; raws.len()];
1365 }
1366 raws.iter().map(|b| decode(&kind, b)).collect()
1367}
1368
1369fn raw_elems(h: &crate::host::JsHost, v: &Value) -> Option<(String, Vec<Vec<u8>>)> {
1372 let JsObj::Object(p) = h.get(v)? else {
1373 return None;
1374 };
1375 let kind = p
1376 .get("@@kind")
1377 .map(|k| h.str_of(k))
1378 .unwrap_or_else(|| "Uint8Array".into());
1379 let bpe = bytes_per_element(&kind);
1380 let len = if view_detached_h(h, v) {
1384 0
1385 } else {
1386 p.get("length").map(|l| h.to_number(l)).unwrap_or(0.0) as usize
1387 };
1388 let off = p.get("byteOffset").map(|o| h.to_number(o)).unwrap_or(0.0) as usize;
1389 let store = match p.get("@@buffer").and_then(|b| h.get(b)) {
1390 Some(JsObj::Object(bp)) => bp.get("@@bytes").and_then(|a| h.get(a)),
1391 _ => None,
1392 };
1393 let JsObj::Array(bytes) = store? else {
1394 return None;
1395 };
1396 let out = (0..len)
1397 .map(|i| {
1398 let start = off + i * bpe;
1399 if start + bpe > bytes.len() {
1400 return vec![0u8; bpe];
1401 }
1402 bytes[start..start + bpe]
1403 .iter()
1404 .map(|x| h.to_number(x) as i64 as u8)
1405 .collect()
1406 })
1407 .collect();
1408 Some((kind, out))
1409}
1410
1411pub fn elems_mut_host(h: &mut crate::host::JsHost, v: &Value) -> Vec<Value> {
1415 if let Some(JsObj::Object(p)) = h.get(v) {
1416 if let Some(arr) = p.get("@@bytes").cloned() {
1417 return match h.get(&arr) {
1418 Some(JsObj::Array(items)) => items.clone(),
1419 _ => Vec::new(),
1420 };
1421 }
1422 }
1423 let Some((kind, raws)) = raw_elems(h, v) else {
1424 return Vec::new();
1425 };
1426 raws.iter()
1427 .map(|b| {
1428 if !is_bigint_kind(&kind) {
1429 return decode(&kind, b);
1430 }
1431 let raw = u64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
1432 h.new_bigint(if kind == "BigInt64Array" {
1433 num_bigint::BigInt::from(raw as i64)
1434 } else {
1435 num_bigint::BigInt::from(raw)
1436 })
1437 })
1438 .collect()
1439}
1440
1441pub fn elems_display(h: &crate::host::JsHost, v: &Value) -> Vec<String> {
1445 let Some((kind, raws)) = raw_elems(h, v) else {
1446 return Vec::new();
1447 };
1448 raws.iter()
1449 .map(|b| {
1450 if !is_bigint_kind(&kind) {
1451 return h.inspect(&decode(&kind, b));
1452 }
1453 let raw = u64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
1454 if kind == "BigInt64Array" {
1455 format!("{}n", raw as i64)
1456 } else {
1457 format!("{raw}n")
1458 }
1459 })
1460 .collect()
1461}
1462
1463fn view_len(v: &Value) -> usize {
1465 if view_detached(v) {
1468 return 0;
1469 }
1470 with_host(|h| match h.get(v) {
1471 Some(JsObj::Object(p)) => p.get("length").map(|l| h.to_number(l)).unwrap_or(0.0) as usize,
1472 _ => 0,
1473 })
1474}
1475
1476pub fn elem_get(recv: &Value, key: &str) -> Option<Value> {
1481 let i: usize = key.parse().ok()?;
1482 if i >= view_len(recv) {
1483 return None;
1484 }
1485 let kind = kind_of(recv);
1486 let bpe = bytes_per_element(&kind);
1487 let bytes = view_bytes(recv, i * bpe, bpe)?;
1488 Some(decode(&kind, &bytes))
1489}
1490
1491pub fn elem_set(recv: &Value, key: &str, val: &Value) -> Result<bool, String> {
1493 let Ok(i) = key.parse::<usize>() else {
1494 return Ok(false);
1495 };
1496 let kind = kind_of(recv);
1497 let n = coerce_val(&kind, val)?;
1500 if i >= view_len(recv) {
1501 return Ok(false);
1502 }
1503 let bpe = bytes_per_element(&kind);
1504 Ok(write_view_bytes(recv, i * bpe, &encode(&kind, &n)))
1505}
1506
1507fn species(recv: &Value, kind: &str, elems: Vec<Value>) -> Value {
1512 if super::native_tag(recv).as_deref() == Some("Buffer") {
1513 let bytes: Vec<u8> = elems.iter().map(|x| num(x) as i64 as u8).collect();
1514 return super::buffer::from_bytes(&bytes);
1515 }
1516 make(kind, elems)
1517}
1518
1519fn write_elems(recv: &Value, kind: &str, vals: &[Value]) -> Result<(), String> {
1524 if super::native_tag(recv).as_deref() == Some("TypedArray") {
1525 let bpe = bytes_per_element(kind);
1526 let coerced: Vec<Value> = vals
1527 .iter()
1528 .map(|v| coerce_val(kind, v))
1529 .collect::<Result<_, _>>()?;
1530 for (i, v) in coerced.iter().enumerate() {
1531 write_view_bytes(recv, i * bpe, &encode(kind, v));
1532 }
1533 return Ok(());
1534 }
1535 let field = "@@bytes";
1536 let coerced: Vec<Value> = vals
1539 .iter()
1540 .map(|v| coerce_val(kind, v))
1541 .collect::<Result<_, _>>()?;
1542 with_host(|h| {
1543 if let Some(JsObj::Object(p)) = h.get(recv) {
1544 if let Some(arr) = p.get(field).cloned() {
1545 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
1546 for (i, v) in coerced.into_iter().enumerate() {
1547 if i < items.len() {
1548 items[i] = v;
1549 }
1550 }
1551 }
1552 }
1553 }
1554 });
1555 Ok(())
1556}
1557
1558fn sort_elements(elems: &mut Vec<Value>, kind: &str, cmp: Option<&Value>) -> Result<(), String> {
1562 let cmp = cmp.cloned().unwrap_or(Value::Undef);
1563 if with_host(|h| crate::host::is_callable(h, &cmp)) {
1564 return crate::builtins::sort_values(elems, Some(&cmp));
1570 }
1571 if is_bigint_kind(kind) {
1578 let keys: Vec<num_bigint::BigInt> = elems.iter().map(bigint_of).collect();
1579 let mut idx: Vec<usize> = (0..elems.len()).collect();
1580 idx.sort_by(|a, b| keys[*a].cmp(&keys[*b]));
1581 *elems = idx.into_iter().map(|i| elems[i].clone()).collect();
1582 } else {
1583 elems.sort_by(|a, b| {
1584 num(a)
1585 .partial_cmp(&num(b))
1586 .unwrap_or(std::cmp::Ordering::Equal)
1587 });
1588 }
1589 Ok(())
1590}
1591
1592fn rel_index(args: &[Value], idx: usize, len: usize, default: usize) -> usize {
1596 if args.len() <= idx {
1597 return default;
1598 }
1599 let n = super::arg_num(args, idx);
1600 if n < 0.0 {
1601 (len as f64 + n).max(0.0) as usize
1602 } else {
1603 (n as usize).min(len)
1604 }
1605}
1606
1607fn base64_instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
1612 let kind = kind_of(recv);
1613 if kind != "Uint8Array" {
1614 return Err(crate::host::type_error(&format!(
1621 "Method Uint8Array.prototype.{method} called on incompatible receiver undefined"
1622 )));
1623 }
1624 let bytes: Vec<u8> = elem_values(recv)
1625 .iter()
1626 .map(|v| with_host(|h| h.to_number(v)) as u8)
1627 .collect();
1628 match method {
1629 "toBase64" => {
1630 let (url, _) = base64_options(args.first())?;
1631 let omit = args
1632 .first()
1633 .filter(|v| !matches!(v, Value::Undef))
1634 .map(|o| {
1635 with_host(|h| match h.get(o) {
1636 Some(JsObj::Object(p)) => {
1637 p.get("omitPadding").map(|v| h.truthy(v)).unwrap_or(false)
1638 }
1639 _ => false,
1640 })
1641 })
1642 .unwrap_or(false);
1643 let mut s = super::to_base64(&bytes);
1646 if url {
1647 s = s.replace('+', "-").replace('/', "_");
1648 }
1649 if omit {
1650 s = s.trim_end_matches('=').to_string();
1651 }
1652 Ok(with_host(|h| h.new_str(s)))
1653 }
1654 "toHex" => Ok(with_host(|h| h.new_str(super::to_hex(&bytes)))),
1655 "setFromBase64" | "setFromHex" => {
1658 let s = base64_input(args)?;
1659 let (decoded, read) = if method == "setFromHex" {
1660 let d = decode_hex_strict(&s)?;
1661 let fits = d.len().min(bytes.len());
1662 (d[..fits].to_vec(), fits * 2)
1663 } else {
1664 let (url, last) = base64_options(args.get(1))?;
1665 let whole = (bytes.len() / 3) * 4;
1668 let head: String = s.chars().take(whole).collect();
1669 let (mut d, mut consumed) = decode_base64_strict(&head, url, last)?;
1670 if d.len() < bytes.len() {
1671 let (full, full_read) = decode_base64_strict(&s, url, last)?;
1672 if full.len() <= bytes.len() {
1673 d = full;
1674 consumed = full_read;
1675 }
1676 }
1677 (d, consumed)
1678 };
1679 write_view_bytes(recv, 0, &decoded);
1680 Ok(with_host(|h| {
1681 let mut m = IndexMap::new();
1682 m.insert("read".to_string(), Value::Float(read as f64));
1683 m.insert("written".to_string(), Value::Float(decoded.len() as f64));
1684 h.new_object(m)
1685 }))
1686 }
1687 _ => unreachable!("caller gates the method name"),
1688 }
1689}
1690
1691pub fn instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
1692 if view_detached(recv) {
1696 return Err(detached_error("%TypedArray%.prototype", method, false));
1697 }
1698 if matches!(
1699 method,
1700 "toBase64" | "toHex" | "setFromBase64" | "setFromHex"
1701 ) {
1702 return base64_instance_call(recv, method, args);
1703 }
1704 let kind = kind_of(recv);
1705 let elems = elem_values(recv);
1710 let this_arg = args.get(1).filter(|v| !matches!(v, Value::Undef)).cloned();
1716 let call_cb = |i: usize, v: &Value| -> Result<Value, String> {
1717 crate::host::invoke(
1718 &args.first().cloned().unwrap_or(Value::Undef),
1719 vec![v.clone(), Value::Float(i as f64), recv.clone()],
1720 this_arg.clone(),
1721 )
1722 };
1723 match method {
1724 "every" => {
1725 for (i, v) in elems.iter().enumerate() {
1726 let r = call_cb(i, v)?;
1727 if !with_host(|h| h.truthy(&r)) {
1728 return Ok(Value::Bool(false));
1729 }
1730 }
1731 Ok(Value::Bool(true))
1732 }
1733 "some" => {
1734 for (i, v) in elems.iter().enumerate() {
1735 let r = call_cb(i, v)?;
1736 if with_host(|h| h.truthy(&r)) {
1737 return Ok(Value::Bool(true));
1738 }
1739 }
1740 Ok(Value::Bool(false))
1741 }
1742 "forEach" => {
1743 for (i, v) in elems.iter().enumerate() {
1744 call_cb(i, v)?;
1745 }
1746 Ok(Value::Undef)
1747 }
1748 "map" => {
1749 let mut out = Vec::with_capacity(elems.len());
1750 for (i, v) in elems.iter().enumerate() {
1751 let r = call_cb(i, v)?;
1752 out.push(coerce_val(&kind, &r)?);
1753 }
1754 Ok(species(recv, &kind, out))
1755 }
1756 "filter" => {
1757 let mut out = Vec::new();
1758 for (i, v) in elems.iter().enumerate() {
1759 let r = call_cb(i, v)?;
1760 if with_host(|h| h.truthy(&r)) {
1761 out.push(v.clone());
1762 }
1763 }
1764 Ok(species(recv, &kind, out))
1765 }
1766 "find" | "findIndex" | "findLast" | "findLastIndex" => {
1767 let last = method.starts_with("findLast");
1768 let idxs: Vec<usize> = if last {
1769 (0..elems.len()).rev().collect()
1770 } else {
1771 (0..elems.len()).collect()
1772 };
1773 for i in idxs {
1774 let r = call_cb(i, &elems[i])?;
1775 if with_host(|h| h.truthy(&r)) {
1776 return Ok(if method.ends_with("Index") {
1777 Value::Float(i as f64)
1778 } else {
1779 elems[i].clone()
1780 });
1781 }
1782 }
1783 Ok(if method.ends_with("Index") {
1784 Value::Float(-1.0)
1785 } else {
1786 Value::Undef
1787 })
1788 }
1789 "reduce" | "reduceRight" => {
1790 let right = method == "reduceRight";
1791 let order: Vec<usize> = if right {
1792 (0..elems.len()).rev().collect()
1793 } else {
1794 (0..elems.len()).collect()
1795 };
1796 let cb = args.first().cloned().unwrap_or(Value::Undef);
1797 let mut it = order.into_iter();
1798 let mut acc = if args.len() >= 2 {
1799 args[1].clone()
1800 } else {
1801 match it.next() {
1802 Some(i) => elems[i].clone(),
1803 None => {
1804 return Err(crate::host::type_error(
1805 "Reduce of empty array with no initial value",
1806 ))
1807 }
1808 }
1809 };
1810 for i in it {
1811 acc = crate::host::invoke(
1812 &cb,
1813 vec![acc, elems[i].clone(), Value::Float(i as f64), recv.clone()],
1814 None,
1815 )?;
1816 }
1817 Ok(acc)
1818 }
1819 "reverse" => {
1820 let mut out = elems.clone();
1821 out.reverse();
1822 write_elems(recv, &kind, &out)?;
1823 Ok(recv.clone())
1824 }
1825 "sort" => {
1826 let mut out = elems.clone();
1827 sort_elements(&mut out, &kind, args.first())?;
1828 write_elems(recv, &kind, &out)?;
1829 Ok(recv.clone())
1830 }
1831 "copyWithin" => {
1832 let len = elems.len();
1833 let target = rel_index(args, 0, len, 0);
1834 let start = rel_index(args, 1, len, 0);
1835 let end = rel_index(args, 2, len, len);
1836 let src: Vec<Value> = elems[start.min(end)..end.max(start)].to_vec();
1837 let mut out = elems.clone();
1838 for (k, v) in src.iter().enumerate() {
1839 if target + k < len {
1840 out[target + k] = v.clone();
1841 }
1842 }
1843 write_elems(recv, &kind, &out)?;
1844 Ok(recv.clone())
1845 }
1846 "at" => {
1847 let n = super::arg_num(args, 0);
1848 let i = if n < 0.0 { elems.len() as f64 + n } else { n };
1849 if i < 0.0 || i >= elems.len() as f64 {
1850 return Ok(Value::Undef);
1851 }
1852 Ok(elems[i as usize].clone())
1853 }
1854 "lastIndexOf" => {
1855 let needle = args.first().cloned().unwrap_or(Value::Undef);
1856 let from = (args.len() > 1).then(|| super::arg_num(args, 1));
1857 let found = crate::builtins::search_start_last(from, elems.len()).and_then(|start| {
1858 elems[..=start]
1859 .iter()
1860 .rposition(|x| same_element(x, &needle, false))
1861 });
1862 Ok(Value::Float(found.map(|p| p as f64).unwrap_or(-1.0)))
1863 }
1864 "keys" | "values" | "entries" | "@@iterator" => {
1869 let items: Vec<Value> = with_host(|h| match method {
1870 "keys" => (0..elems.len()).map(|i| Value::Float(i as f64)).collect(),
1871 "values" | "@@iterator" => elems.clone(),
1872 _ => elems
1873 .iter()
1874 .enumerate()
1875 .map(|(i, v)| h.new_array(vec![Value::Float(i as f64), v.clone()]))
1876 .collect(),
1877 });
1878 Ok(with_host(|h| {
1879 h.alloc(JsObj::Iter {
1880 items,
1881 idx: 0,
1882 array: None,
1883 })
1884 }))
1885 }
1886 "toString" | "join" => {
1887 let sep = if method == "join" && !args.is_empty() {
1888 super::arg_str(args, 0)
1889 } else {
1890 ",".into()
1891 };
1892 let parts: Vec<String> = with_host(|h| elems.iter().map(|n| h.str_of(n)).collect());
1893 Ok(with_host(|h| h.new_str(parts.join(&sep))))
1894 }
1895 "slice" | "subarray" => {
1896 let len = elems.len();
1897 let norm = |n: f64| -> usize {
1898 if n < 0.0 {
1899 (len as f64 + n).max(0.0) as usize
1900 } else {
1901 (n as usize).min(len)
1902 }
1903 };
1904 let s = if args.is_empty() {
1905 0
1906 } else {
1907 norm(super::arg_num(args, 0))
1908 };
1909 let e = if args.len() < 2 {
1910 len
1911 } else {
1912 norm(super::arg_num(args, 1))
1913 };
1914 let (lo, hi) = (s.min(e), e.max(s));
1915 if method == "subarray" && super::native_tag(recv).as_deref() == Some("TypedArray") {
1918 if let Some((buf, off)) = view_base(recv) {
1919 let bpe = bytes_per_element(&kind);
1920 return Ok(make_view(&kind, &buf, off + lo * bpe, hi - lo));
1921 }
1922 }
1923 Ok(species(recv, &kind, elems[lo..hi].to_vec()))
1924 }
1925 "indexOf" => {
1926 let needle = args.first().cloned().unwrap_or(Value::Undef);
1927 let start = crate::builtins::search_start(super::arg_num(args, 1), elems.len());
1928 Ok(Value::Float(
1929 elems
1930 .iter()
1931 .skip(start)
1932 .position(|x| same_element(x, &needle, false))
1933 .map(|p| (p + start) as f64)
1934 .unwrap_or(-1.0),
1935 ))
1936 }
1937 "includes" => {
1938 let needle = args.first().cloned().unwrap_or(Value::Undef);
1939 let start = crate::builtins::search_start(super::arg_num(args, 1), elems.len());
1940 Ok(Value::Bool(
1941 elems
1942 .iter()
1943 .skip(start)
1944 .any(|x| same_element(x, &needle, true)),
1945 ))
1946 }
1947 "fill" => {
1954 let len = elems.len();
1955 let v = coerce_val(&kind, args.first().unwrap_or(&Value::Undef))?;
1956 let start = rel_index(args, 1, len, 0);
1957 let end = rel_index(args, 2, len, len);
1958 let mut out = elems.clone();
1959 for slot in out.iter_mut().take(end).skip(start) {
1960 *slot = v.clone();
1961 }
1962 write_elems(recv, &kind, &out)?;
1963 Ok(recv.clone())
1964 }
1965 "toReversed" | "toSorted" => {
1970 let mut out = elems.clone();
1971 if method == "toReversed" {
1972 out.reverse();
1973 } else {
1974 sort_elements(&mut out, &kind, args.first())?;
1975 }
1976 Ok(make(&kind, out))
1977 }
1978 "with" => {
1979 let len = elems.len();
1980 let n = super::arg_num(args, 0);
1981 let i = if n < 0.0 { len as f64 + n } else { n };
1982 if !(0.0..len as f64).contains(&i) {
1983 return Err("RangeError: Invalid typed array index".into());
1984 }
1985 let mut out = elems.clone();
1986 out[i as usize] = coerce_val(&kind, args.get(1).unwrap_or(&Value::Undef))?;
1987 Ok(make(&kind, out))
1988 }
1989 "set" => {
1990 let arg = args.first().cloned().unwrap_or(Value::Undef);
1992 let src = match super::native_tag(&arg).as_deref() {
1993 Some("TypedArray") | Some("Buffer") => elem_values(&arg),
1994 _ => crate::host::iter_all(&arg)
1995 .unwrap_or_else(|_| crate::builtins::array_like_items(&arg)),
1996 };
1997 let off = super::arg_num(args, 1);
2000 let off = if off.is_nan() { 0.0 } else { off.trunc() };
2001 if off < 0.0 || off + src.len() as f64 > view_len(recv) as f64 {
2002 return Err(crate::host::range_error("offset is out of bounds"));
2003 }
2004 let off = off as usize;
2005 let src: Vec<Value> = src
2007 .iter()
2008 .map(|v| coerce_val(&kind, v))
2009 .collect::<Result<_, _>>()?;
2010 let bpe = bytes_per_element(&kind);
2011 let len = view_len(recv);
2012 for (k, v) in src.into_iter().enumerate() {
2013 if off + k < len {
2014 write_view_bytes(recv, (off + k) * bpe, &encode(&kind, &v));
2015 }
2016 }
2017 Ok(Value::Undef)
2018 }
2019 _ => Err(crate::host::type_error(&format!(
2020 "{method} is not a function"
2021 ))),
2022 }
2023}
2024
2025pub fn construct_weakref(args: &[Value]) -> Result<Value, String> {
2028 let target = args.first().cloned().unwrap_or(Value::Undef);
2029 Ok(with_host(|h| {
2030 let mut m = IndexMap::new();
2031 m.insert("@@native".into(), h.new_str("WeakRef"));
2032 m.insert("@@target".into(), target);
2033 h.new_object(m)
2034 }))
2035}
2036
2037pub fn weakref_call(recv: &Value, method: &str) -> Result<Value, String> {
2038 match method {
2039 "deref" => Ok(with_host(|h| match h.get(recv) {
2040 Some(JsObj::Object(p)) => p.get("@@target").cloned().unwrap_or(Value::Undef),
2041 _ => Value::Undef,
2042 })),
2043 _ => Err(crate::host::type_error(&format!(
2044 "{method} is not a function"
2045 ))),
2046 }
2047}
2048
2049fn is_object_value(v: &Value) -> bool {
2062 matches!(v, Value::Obj(_))
2063 && with_host(|h| {
2064 !matches!(
2065 h.get(v),
2066 Some(JsObj::Str(_))
2067 | Some(JsObj::Symbol { .. })
2068 | Some(JsObj::BigInt(_))
2069 | Some(JsObj::Null)
2070 )
2071 })
2072}
2073
2074pub fn construct_finalization_registry(args: &[Value]) -> Result<Value, String> {
2075 let cb = args.first().cloned().unwrap_or(Value::Undef);
2076 if !with_host(|h| crate::host::is_callable(h, &cb)) {
2077 return Err(crate::host::type_error(
2078 "FinalizationRegistry: cleanup must be callable",
2079 ));
2080 }
2081 Ok(with_host(|h| {
2082 let tokens = h.new_array(Vec::new());
2083 let mut m = IndexMap::new();
2084 m.insert("@@native".into(), h.new_str("FinalizationRegistry"));
2085 m.insert("@@fr_cb".into(), cb);
2086 m.insert("@@fr_tokens".into(), tokens);
2087 h.new_object(m)
2088 }))
2089}
2090
2091pub fn finalization_registry_call(
2092 recv: &Value,
2093 method: &str,
2094 args: &[Value],
2095) -> Result<Value, String> {
2096 match method {
2097 "register" => {
2098 let target = args.first().cloned().unwrap_or(Value::Undef);
2099 let held = args.get(1).cloned().unwrap_or(Value::Undef);
2100 let token = args.get(2).cloned().unwrap_or(Value::Undef);
2101 if !is_object_value(&target) {
2102 return Err(crate::host::type_error(
2105 "FinalizationRegistry.prototype.register: invalid target",
2106 ));
2107 }
2108 if with_host(|h| h.strict_eq(&target, &held)) {
2109 return Err(crate::host::type_error(
2110 "FinalizationRegistry.prototype.register: target and holdings must not be same",
2111 ));
2112 }
2113 if !matches!(token, Value::Undef) {
2116 if !is_object_value(&token) {
2117 return Err(crate::host::type_error(&format!(
2118 "Invalid unregisterToken ('{}')",
2119 with_host(|h| h.str_of(&token))
2120 )));
2121 }
2122 with_host(|h| {
2123 let toks = registry_tokens(h, recv);
2124 if let Some(JsObj::Array(items)) = h.get_mut(&toks) {
2125 items.push(token);
2126 }
2127 });
2128 }
2129 Ok(Value::Undef)
2130 }
2131 "unregister" => {
2132 let token = args.first().cloned().unwrap_or(Value::Undef);
2133 if !is_object_value(&token) {
2134 return Err(crate::host::type_error(&format!(
2136 "Invalid unregisterToken ('{}')",
2137 with_host(|h| h.str_of(&token))
2138 )));
2139 }
2140 Ok(Value::Bool(with_host(|h| {
2141 let toks = registry_tokens(h, recv);
2142 let kept: Vec<Value> = match h.get(&toks) {
2143 Some(JsObj::Array(items)) => items
2144 .iter()
2145 .filter(|t| !h.strict_eq(t, &token))
2146 .cloned()
2147 .collect(),
2148 _ => Vec::new(),
2149 };
2150 let removed = match h.get(&toks) {
2151 Some(JsObj::Array(items)) => items.len() != kept.len(),
2152 _ => false,
2153 };
2154 if let Some(JsObj::Array(items)) = h.get_mut(&toks) {
2155 *items = kept;
2156 }
2157 removed
2158 })))
2159 }
2160 _ => Err(crate::host::type_error(&format!(
2161 "{method} is not a function"
2162 ))),
2163 }
2164}
2165
2166fn registry_tokens(h: &crate::host::JsHost, recv: &Value) -> Value {
2168 match h.get(recv) {
2169 Some(JsObj::Object(p)) => p.get("@@fr_tokens").cloned().unwrap_or(Value::Undef),
2170 _ => Value::Undef,
2171 }
2172}
2173
2174pub fn construct_text_encoder() -> Result<Value, String> {
2177 Ok(with_host(|h| {
2178 let mut m = IndexMap::new();
2179 m.insert("@@native".into(), h.new_str("TextEncoder"));
2180 m.insert("@@encoding".into(), h.new_str("utf-8"));
2185 h.new_object(m)
2186 }))
2187}
2188
2189pub fn text_encoder_call(_recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
2190 match method {
2191 "encode" => {
2193 let s = super::arg_str(args, 0);
2194 Ok(make(
2195 "Uint8Array",
2196 s.as_bytes()
2197 .iter()
2198 .map(|b| Value::Float(*b as f64))
2199 .collect(),
2200 ))
2201 }
2202 _ => Err(crate::host::type_error(&format!(
2203 "{method} is not a function"
2204 ))),
2205 }
2206}
2207
2208fn encoding_for_label(label: &str) -> Option<&'static str> {
2215 Some(match label.trim().to_ascii_lowercase().as_str() {
2216 "utf-8" | "utf8" | "unicode-1-1-utf-8" | "unicode11utf8" | "unicode20utf8"
2217 | "x-unicode20utf8" => "utf-8",
2218 "latin1" | "iso-8859-1" | "iso8859-1" | "iso88591" | "ascii" | "us-ascii" | "cp1252"
2219 | "cp819" | "ibm819" | "l1" | "windows-1252" | "x-cp1252" => "windows-1252",
2220 "utf-16le" | "utf-16" | "ucs-2" | "ucs2" | "unicodefeff" | "unicodefffe"
2221 | "iso-10646-ucs-2" | "csunicode" => "utf-16le",
2222 _ => return None,
2223 })
2224}
2225
2226pub fn construct_text_decoder(args: &[Value]) -> Result<Value, String> {
2227 let label = if args.is_empty() || matches!(args[0], Value::Undef) {
2228 "utf-8".to_string()
2229 } else {
2230 super::arg_str(args, 0)
2231 };
2232 let Some(encoding) = encoding_for_label(&label) else {
2233 return Err(crate::host::coded_error(
2234 "RangeError",
2235 "ERR_ENCODING_NOT_SUPPORTED",
2236 &format!("The \"{label}\" encoding is not supported"),
2237 ));
2238 };
2239 let flag = |key: &str| {
2243 args.get(1)
2244 .map(|o| crate::builtins::get_property(o, key).unwrap_or(Value::Undef))
2245 .map(|v| with_host(|h| h.truthy(&v)))
2246 .unwrap_or(false)
2247 };
2248 let (fatal, ignore_bom) = (flag("fatal"), flag("ignoreBOM"));
2249 Ok(with_host(|h| {
2250 let mut m = IndexMap::new();
2251 m.insert("@@native".into(), h.new_str("TextDecoder"));
2252 m.insert("@@encoding".into(), h.new_str(encoding.to_string()));
2253 m.insert("@@fatal".into(), Value::Bool(fatal));
2254 m.insert("@@ignoreBOM".into(), Value::Bool(ignore_bom));
2255 h.new_object(m)
2256 }))
2257}
2258
2259const CP1252_HIGH: [char; 32] = [
2265 '\u{20ac}', '\u{81}', '\u{201a}', '\u{192}', '\u{201e}', '\u{2026}', '\u{2020}', '\u{2021}',
2266 '\u{2c6}', '\u{2030}', '\u{160}', '\u{2039}', '\u{152}', '\u{8d}', '\u{17d}', '\u{8f}',
2267 '\u{90}', '\u{2018}', '\u{2019}', '\u{201c}', '\u{201d}', '\u{2022}', '\u{2013}', '\u{2014}',
2268 '\u{2dc}', '\u{2122}', '\u{161}', '\u{203a}', '\u{153}', '\u{9d}', '\u{17e}', '\u{178}',
2269];
2270
2271pub fn text_decoder_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
2272 match method {
2273 "decode" => {
2275 let bytes: Vec<u8> = elems_of(&args.first().cloned().unwrap_or(Value::Undef))
2276 .unwrap_or_default()
2277 .iter()
2278 .map(|n| *n as u8)
2279 .collect();
2280 let slot = |key: &str| {
2281 with_host(|h| match h.get(recv) {
2282 Some(JsObj::Object(p)) => p.get(key).cloned(),
2283 _ => None,
2284 })
2285 };
2286 let enc = slot("@@encoding")
2287 .map(|v| with_host(|h| h.str_of(&v)))
2288 .unwrap_or_else(|| "utf-8".into());
2289 let flag = |key: &str| matches!(slot(key), Some(Value::Bool(true)));
2290 let s = match enc.as_str() {
2291 "windows-1252" => bytes
2292 .iter()
2293 .map(|b| match b {
2294 0x80..=0x9f => CP1252_HIGH[(b - 0x80) as usize],
2295 _ => *b as char,
2296 })
2297 .collect(),
2298 "utf-16le" => {
2299 let units: Vec<u16> = bytes
2300 .chunks_exact(2)
2301 .map(|c| u16::from_le_bytes([c[0], c[1]]))
2302 .collect();
2303 String::from_utf16_lossy(&units)
2304 }
2305 _ if flag("@@fatal") => match std::str::from_utf8(&bytes) {
2310 Ok(s) => s.to_string(),
2311 Err(_) => {
2312 return Err(crate::host::coded_error(
2313 "TypeError",
2314 "ERR_ENCODING_INVALID_ENCODED_DATA",
2315 &format!("The encoded data was not valid for encoding {enc}"),
2316 ))
2317 }
2318 },
2319 _ => String::from_utf8_lossy(&bytes).into_owned(),
2320 };
2321 let s = match s.strip_prefix('\u{feff}') {
2323 Some(rest) if !flag("@@ignoreBOM") => rest.to_string(),
2324 _ => s,
2325 };
2326 Ok(with_host(|h| h.new_str(s)))
2327 }
2328 _ => Err(crate::host::type_error(&format!(
2329 "{method} is not a function"
2330 ))),
2331 }
2332}