1use crate::host::{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
306pub fn construct(kind: &str, args: &[Value]) -> Result<Value, String> {
309 if kind == "ArrayBuffer" {
310 let n = super::arg_num(args, 0).max(0.0) as usize;
311 let ab = new_array_buffer(n);
312 if let Some(opts) = args.get(1) {
315 let max = crate::builtins::get_property(opts, "maxByteLength").unwrap_or(Value::Undef);
316 if !matches!(max, Value::Undef) {
317 let m = with_host(|h| h.to_number(&max)).max(0.0) as usize;
318 with_host(|h| {
319 if let Some(JsObj::Object(p)) = h.get_mut(&ab) {
320 p.insert("@@maxByteLength".into(), Value::Float(m as f64));
321 p.insert("maxByteLength".into(), Value::Float(m as f64));
322 p.insert("resizable".into(), Value::Bool(true));
323 }
324 h.hide_prop(&ab, "maxByteLength");
325 h.hide_prop(&ab, "resizable");
326 });
327 }
328 }
329 return Ok(ab);
330 }
331 if let Some(first) = args.first() {
336 if super::native_tag(first).as_deref() == Some("ArrayBuffer") {
337 if is_detached(first) {
339 return Err(crate::host::type_error(
340 "Cannot perform Construct on a detached ArrayBuffer",
341 ));
342 }
343 let bpe = bytes_per_element(kind);
344 let total = buffer_byte_length(first);
345 let off = super::arg_num(args, 1).max(0.0) as usize;
346 if off > total || off % bpe != 0 {
347 return Err(crate::host::range_error(
348 "start offset of Uint8Array should be a multiple of element size",
349 ));
350 }
351 let len = match args.get(2) {
352 Some(Value::Undef) | None => (total - off) / bpe,
353 Some(_) => super::arg_num(args, 2).max(0.0) as usize,
354 };
355 if off + len * bpe > total {
356 return Err(crate::host::range_error("Invalid typed array length"));
357 }
358 return Ok(make_view(kind, first, off, len));
359 }
360 }
361 let elems = build_elems(kind, args)?;
362 Ok(make(kind, elems))
363}
364
365pub const DATAVIEW_METHODS: &[&str] = &[
367 "getInt8",
368 "getUint8",
369 "getInt16",
370 "getUint16",
371 "getInt32",
372 "getUint32",
373 "getFloat32",
374 "getFloat64",
375 "getBigInt64",
376 "getBigUint64",
377 "setInt8",
378 "setUint8",
379 "setInt16",
380 "setUint16",
381 "setInt32",
382 "setUint32",
383 "setFloat32",
384 "setFloat64",
385 "setBigInt64",
386 "setBigUint64",
387];
388
389pub fn construct_dataview(args: &[Value]) -> Result<Value, String> {
391 let buf = args.first().cloned().unwrap_or(Value::Undef);
392 if super::native_tag(&buf).as_deref() != Some("ArrayBuffer") {
393 return Err(crate::host::type_error(
394 "First argument to DataView constructor must be an ArrayBuffer",
395 ));
396 }
397 let total = buffer_byte_length(&buf);
398 let off = super::arg_num(args, 1).max(0.0) as usize;
399 if off > total {
400 return Err(crate::host::range_error(
401 "Start offset is outside the bounds of the buffer",
402 ));
403 }
404 let len = match args.get(2) {
405 Some(Value::Undef) | None => total - off,
406 Some(_) => super::arg_num(args, 2).max(0.0) as usize,
407 };
408 if off + len > total {
409 return Err(crate::host::range_error("Invalid DataView length"));
410 }
411 Ok(with_host(|h| {
412 let mut m = IndexMap::new();
413 m.insert("@@native".into(), h.new_str("DataView"));
414 m.insert("@@buffer".into(), buf.clone());
415 m.insert("buffer".into(), buf.clone());
416 m.insert("byteOffset".into(), Value::Float(off as f64));
417 m.insert("byteLength".into(), Value::Float(len as f64));
418 let obj = h.new_object(m);
419 for k in ["buffer", "byteOffset", "byteLength"] {
420 h.hide_prop(&obj, k);
421 }
422 h.ensure_native_protos();
423 if let Some(p) = h.ensure_ctor_proto("DataView") {
424 h.set_proto(&obj, p);
425 }
426 obj
427 }))
428}
429
430pub fn dataview_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
433 if view_detached(recv) {
434 return Err(detached_error("DataView.prototype", method, false));
435 }
436 let Some(spec) = method.get(3..) else {
437 return Err(crate::host::type_error(&format!(
438 "{method} is not a function"
439 )));
440 };
441 let width = match spec {
442 "Int8" | "Uint8" => 1,
443 "Int16" | "Uint16" => 2,
444 "Int32" | "Uint32" | "Float32" => 4,
445 "Float64" | "BigInt64" | "BigUint64" => 8,
446 _ => {
447 return Err(crate::host::type_error(&format!(
448 "{method} is not a function"
449 )))
450 }
451 };
452 let is_get = method.starts_with("get");
453 let requested = super::arg_num(args, 0);
458 let requested = if requested.is_nan() {
459 0.0
460 } else {
461 requested.trunc()
462 };
463 let span = with_host(|h| match h.get(recv) {
464 Some(JsObj::Object(p)) => p.get("byteLength").map(|l| h.to_number(l)).unwrap_or(0.0),
465 _ => 0.0,
466 });
467 if requested < 0.0 || requested + width as f64 > span {
468 return Err(crate::host::range_error(
469 "Offset is outside the bounds of the DataView",
470 ));
471 }
472 let at = requested as usize;
473 let le = with_host(|h| {
476 h.truthy(
477 args.get(if is_get { 1 } else { 2 })
478 .unwrap_or(&Value::Undef),
479 )
480 });
481 if is_get {
482 let mut b = view_bytes(recv, at, width).unwrap_or_else(|| vec![0; width]);
483 if !le {
484 b.reverse();
485 }
486 return Ok(match spec {
487 "Int8" => Value::Float(b[0] as i8 as f64),
488 "Uint8" => Value::Float(b[0] as f64),
489 "Int16" => Value::Float(i16::from_le_bytes([b[0], b[1]]) as f64),
490 "Uint16" => Value::Float(u16::from_le_bytes([b[0], b[1]]) as f64),
491 "Int32" => Value::Float(i32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
492 "Uint32" => Value::Float(u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
493 "Float32" => Value::Float(f32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
494 "Float64" => Value::Float(f64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]))),
495 "BigInt64" => {
496 let raw = i64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
497 with_host(|h| h.new_bigint(num_bigint::BigInt::from(raw)))
498 }
499 _ => {
500 let raw = u64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
501 with_host(|h| h.new_bigint(num_bigint::BigInt::from(raw)))
502 }
503 });
504 }
505 let val = args.get(1).cloned().unwrap_or(Value::Undef);
506 let mut b = match spec {
507 "BigInt64" | "BigUint64" => {
508 use num_traits::cast::ToPrimitive;
509 let big = crate::builtins::to_bigint(&val)?;
512 let raw = if spec == "BigInt64" {
513 big.to_i64().unwrap_or(0) as u64
514 } else {
515 big.to_u64().unwrap_or(0)
516 };
517 raw.to_le_bytes().to_vec()
518 }
519 _ => {
520 let n = with_host(|h| h.to_number(&val));
521 match spec {
522 "Int8" | "Uint8" => vec![n as i64 as u8],
523 "Int16" | "Uint16" => (n as i64 as u16).to_le_bytes().to_vec(),
524 "Int32" | "Uint32" => (n as i64 as u32).to_le_bytes().to_vec(),
525 "Float32" => (n as f32).to_le_bytes().to_vec(),
526 _ => n.to_le_bytes().to_vec(),
527 }
528 }
529 };
530 if !le {
531 b.reverse();
532 }
533 write_view_bytes(recv, at, &b);
534 Ok(Value::Undef)
535}
536
537pub fn buffer_resize(ab: &Value, args: &[Value]) -> Result<Value, String> {
540 let max = with_host(|h| match h.get(ab) {
541 Some(JsObj::Object(p)) => p.get("@@maxByteLength").map(|m| h.to_number(m) as usize),
542 _ => None,
543 })
544 .ok_or_else(|| {
545 crate::host::type_error(
546 "ArrayBuffer.prototype.resize called on a non-resizable ArrayBuffer",
547 )
548 })?;
549 let n = super::arg_num(args, 0).max(0.0) as usize;
550 if n > max {
551 return Err(crate::host::range_error("Invalid array buffer length"));
552 }
553 let store = store_of(ab);
554 with_host(|h| {
555 if let Some(a) = store {
556 if let Some(JsObj::Array(items)) = h.get_mut(&a) {
557 items.resize(n, Value::Float(0.0));
558 }
559 }
560 if let Some(JsObj::Object(p)) = h.get_mut(ab) {
561 p.insert("byteLength".into(), Value::Float(n as f64));
562 }
563 });
564 Ok(Value::Undef)
565}
566
567pub fn write_buffer_bytes(ab: &Value, bytes: &[u8]) {
570 let Some(store) = store_of(ab) else { return };
571 with_host(|h| {
572 if let Some(JsObj::Array(items)) = h.get_mut(&store) {
573 *items = bytes.iter().map(|b| Value::Float(*b as f64)).collect();
574 }
575 if let Some(JsObj::Object(p)) = h.get_mut(ab) {
576 p.insert("byteLength".into(), Value::Float(bytes.len() as f64));
577 }
578 });
579}
580
581pub fn buffer_store(ab: &Value) -> Option<Value> {
584 store_of(ab)
585}
586
587pub fn buffer_bytes_snapshot(ab: &Value) -> Option<Vec<u8>> {
589 let store = store_of(ab)?;
590 with_host(|h| match h.get(&store) {
591 Some(JsObj::Array(items)) => {
592 Some(items.iter().map(|x| h.to_number(x) as i64 as u8).collect())
593 }
594 _ => None,
595 })
596}
597
598pub fn buffer_byte_length(ab: &Value) -> usize {
600 with_host(|h| match h.get(ab) {
601 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|a| h.get(a)) {
602 Some(JsObj::Array(items)) => items.len(),
603 _ => 0,
604 },
605 _ => 0,
606 })
607}
608
609pub fn buffer_slice(ab: &Value, args: &[Value]) -> Value {
612 let total = buffer_byte_length(ab) as i64;
613 let idx = |v: Option<&Value>, dflt: i64| -> usize {
614 let n = match v {
615 None | Some(Value::Undef) => dflt,
616 Some(x) => with_host(|h| h.to_number(x)) as i64,
617 };
618 (if n < 0 { total + n } else { n }).clamp(0, total) as usize
619 };
620 let start = idx(args.first(), 0);
621 let end = idx(args.get(1), total).max(start);
622 let out = new_array_buffer(end - start);
623 let src = with_host(|h| match h.get(ab) {
624 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|a| h.get(a)) {
625 Some(JsObj::Array(items)) => items[start..end].to_vec(),
626 _ => Vec::new(),
627 },
628 _ => Vec::new(),
629 });
630 with_host(|h| {
631 if let Some(JsObj::Object(p)) = h.get(&out) {
632 if let Some(arr) = p.get("@@bytes").cloned() {
633 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
634 *items = src;
635 }
636 }
637 }
638 });
639 out
640}
641
642fn build_elems(kind: &str, args: &[Value]) -> Result<Vec<Value>, String> {
646 match args.first() {
647 None | Some(Value::Undef) => Ok(Vec::new()),
648 Some(Value::Int(_)) | Some(Value::Float(_)) => {
649 let n = super::arg_num(args, 0).max(0.0) as usize;
650 Ok(vec![zero_of(kind); n])
651 }
652 Some(v) => {
653 let items = match super::native_tag(v).as_deref() {
656 Some("TypedArray") | Some("Buffer") => elem_values(v),
657 _ => crate::host::iter_all(v).unwrap_or_default(),
658 };
659 items.iter().map(|x| coerce_val(kind, x)).collect()
660 }
661 }
662}
663
664#[derive(Clone, Copy, PartialEq)]
669enum LastChunk {
670 Loose,
671 Strict,
672 StopBeforePartial,
673}
674
675fn base64_options(opt: Option<&Value>) -> Result<(bool, LastChunk), String> {
679 let Some(o) = opt.filter(|v| !matches!(v, Value::Undef)) else {
680 return Ok((false, LastChunk::Loose));
681 };
682 if !with_host(|h| matches!(h.get(o), Some(JsObj::Object(_)))) {
683 return Err(crate::host::type_error("invalid_argument"));
684 }
685 let read = |k: &str| {
686 with_host(|h| match h.get(o) {
687 Some(JsObj::Object(p)) => p.get(k).filter(|v| !matches!(v, Value::Undef)).cloned(),
688 _ => None,
689 })
690 };
691 let url = match read("alphabet") {
692 None => false,
693 Some(v) => match with_host(|h| h.str_of(&v)).as_str() {
694 "base64" => false,
695 "base64url" => true,
696 other => return Err(crate::host::type_error(&format!("invalid option {other}"))),
697 },
698 };
699 let last = match read("lastChunkHandling") {
700 None => LastChunk::Loose,
701 Some(v) => match with_host(|h| h.str_of(&v)).as_str() {
702 "loose" => LastChunk::Loose,
703 "strict" => LastChunk::Strict,
704 "stop-before-partial" => LastChunk::StopBeforePartial,
705 other => return Err(crate::host::type_error(&format!("invalid option {other}"))),
706 },
707 };
708 Ok((url, last))
709}
710
711const B64_BAD: &str =
712 "SyntaxError: Found a character that cannot be part of a valid base64 string.";
713const B64_SINGLE: &str =
714 "SyntaxError: The base64 input terminates with a single character, excluding padding (=).";
715
716fn decode_base64_strict(s: &str, url: bool, last: LastChunk) -> Result<(Vec<u8>, usize), String> {
723 let value = |c: char| -> Option<u32> {
724 let table = if url {
725 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
726 } else {
727 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
728 };
729 table.find(c).map(|i| i as u32)
730 };
731 let chars: Vec<char> = s.chars().collect();
732 let mut out = Vec::new();
733 let mut chunk: Vec<u32> = Vec::new();
734 let mut consumed = 0usize;
735 let mut i = 0usize;
736 while i < chars.len() {
737 let c = chars[i];
738 if c.is_ascii_whitespace() {
739 i += 1;
740 continue;
741 }
742 if c == '=' {
743 let pads = chars[i..].iter().filter(|c| **c == '=').count();
746 let rest_ok = chars[i..]
747 .iter()
748 .all(|c| *c == '=' || c.is_ascii_whitespace());
749 let want = 4 - chunk.len();
750 if !rest_ok || chunk.len() < 2 || pads != want {
751 return Err(B64_BAD.into());
752 }
753 out.extend(flush_base64_chunk(&chunk));
754 return Ok((out, chars.len()));
755 }
756 let Some(v) = value(c) else {
757 return Err(B64_BAD.into());
758 };
759 chunk.push(v);
760 i += 1;
761 if chunk.len() == 4 {
762 out.extend(flush_base64_chunk(&chunk));
763 chunk.clear();
764 consumed = i;
765 }
766 }
767 match chunk.len() {
768 0 => Ok((out, consumed)),
769 1 if last != LastChunk::StopBeforePartial => Err(B64_SINGLE.into()),
771 _ if last == LastChunk::StopBeforePartial => Ok((out, consumed)),
772 1 => Ok((out, consumed)),
773 _ if last == LastChunk::Strict => Err(B64_SINGLE.into()),
774 _ => {
775 out.extend(flush_base64_chunk(&chunk));
776 Ok((out, chars.len()))
777 }
778 }
779}
780
781fn flush_base64_chunk(chunk: &[u32]) -> Vec<u8> {
783 let mut acc = 0u32;
784 for v in chunk {
785 acc = (acc << 6) | v;
786 }
787 let bytes = chunk.len() - 1;
788 acc <<= 6 * (4 - chunk.len());
789 let all = [(acc >> 16) as u8, (acc >> 8) as u8, acc as u8];
790 all[..bytes].to_vec()
791}
792
793const HEX_BAD: &str = "SyntaxError: Input string must contain hex characters in even length";
794
795fn decode_hex_strict(s: &str) -> Result<Vec<u8>, String> {
798 let chars: Vec<char> = s.chars().collect();
799 if chars.len() % 2 != 0 || !chars.iter().all(|c| c.is_ascii_hexdigit()) {
800 return Err(HEX_BAD.into());
801 }
802 Ok(chars
803 .chunks(2)
804 .map(|p| {
805 let hi = p[0].to_digit(16).expect("checked");
806 let lo = p[1].to_digit(16).expect("checked");
807 (hi * 16 + lo) as u8
808 })
809 .collect())
810}
811
812fn base64_input(args: &[Value]) -> Result<String, String> {
815 let v = args.first().cloned().unwrap_or(Value::Undef);
816 let is_str = matches!(v, Value::Str(_))
817 || with_host(|h| matches!(h.get(&v), Some(crate::host::JsObj::Str(_))));
818 if !is_str {
819 return Err(crate::host::type_error("input argument must be a string"));
820 }
821 Ok(with_host(|h| h.str_of(&v)))
822}
823
824fn from_base64_static(method: &str, args: &[Value]) -> Result<Value, String> {
826 let s = base64_input(args)?;
827 let bytes = if method == "fromHex" {
828 decode_hex_strict(&s)?
829 } else {
830 let (url, last) = base64_options(args.get(1))?;
831 decode_base64_strict(&s, url, last)?.0
832 };
833 Ok(make(
834 "Uint8Array",
835 bytes.iter().map(|b| Value::Float(*b as f64)).collect(),
836 ))
837}
838
839pub fn static_call(kind: &str, method: &str, args: &[Value]) -> Option<Result<Value, String>> {
842 if matches!(method, "fromBase64" | "fromHex") {
844 if kind != "Uint8Array" {
845 return None;
846 }
847 return Some(from_base64_static(method, args));
848 }
849 Some(match method {
850 "of" => args
851 .iter()
852 .map(|x| coerce_val(kind, x))
853 .collect::<Result<Vec<Value>, String>>()
854 .map(|e| make(kind, e)),
855 "from" => from(kind, args),
856 "isView" => Ok(Value::Bool(with_host(|h| {
859 matches!(
860 h.get(&args.first().cloned().unwrap_or(Value::Undef)),
861 Some(crate::host::JsObj::Object(p))
862 if matches!(
863 p.get("@@native").map(|t| h.str_of(t)).as_deref(),
864 Some("TypedArray") | Some("Buffer") | Some("DataView")
865 )
866 )
867 }))),
868 _ => return None,
869 })
870}
871
872fn from(kind: &str, args: &[Value]) -> Result<Value, String> {
873 let src = args.first().cloned().unwrap_or(Value::Undef);
874 let map_fn = args
875 .get(1)
876 .cloned()
877 .filter(|f| with_host(|h| crate::host::is_callable(h, f)));
878 let items = if let Some(e) = elems_of(&src) {
879 e.into_iter().map(Value::Float).collect()
880 } else {
881 crate::host::iter_all(&src).unwrap_or_default()
882 };
883 let mut out = Vec::with_capacity(items.len());
884 for (i, it) in items.into_iter().enumerate() {
885 let mapped = match &map_fn {
886 Some(f) => crate::host::invoke(f, vec![it, Value::Float(i as f64)], None)?,
887 None => it,
888 };
889 out.push(coerce_val(kind, &mapped)?);
890 }
891 Ok(make(kind, out))
892}
893
894pub fn elems_of(v: &Value) -> Option<Vec<f64>> {
896 let tag = super::native_tag(v)?;
897 if !matches!(tag.as_str(), "TypedArray" | "Buffer") {
898 return None;
899 }
900 let vals = elem_values(v);
901 Some(with_host(|h| vals.iter().map(|x| h.to_number(x)).collect()))
902}
903
904pub fn index_len(v: &Value) -> Option<usize> {
913 match super::native_tag(v)?.as_str() {
914 "TypedArray" => Some(view_len(v)),
915 "Buffer" => with_host(|h| match h.get(v) {
916 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|a| h.get(a)) {
917 Some(JsObj::Array(items)) => Some(items.len()),
918 _ => None,
919 },
920 _ => None,
921 }),
922 _ => None,
923 }
924}
925
926pub fn has_index(v: &Value, key: &str) -> Option<bool> {
929 let len = index_len(v)?;
930 Some(key.parse::<usize>().map(|i| i < len).unwrap_or(false))
931}
932
933pub fn kind_of(recv: &Value) -> String {
935 with_host(|h| match h.get(recv) {
936 Some(JsObj::Object(p)) => p
937 .get("@@kind")
938 .map(|v| h.str_of(v))
939 .unwrap_or_else(|| "Uint8Array".into()),
940 _ => "Uint8Array".into(),
941 })
942}
943
944pub fn new_array_buffer(n: usize) -> Value {
956 with_host(|h| {
957 let arr = h.new_array(vec![Value::Float(0.0); n]);
958 let mut m = IndexMap::new();
959 m.insert("@@native".into(), h.new_str("ArrayBuffer"));
960 m.insert("@@bytes".into(), arr);
961 m.insert("byteLength".into(), Value::Float(n as f64));
962 m.insert("detached".into(), Value::Bool(false));
966 m.insert("resizable".into(), Value::Bool(false));
969 m.insert("maxByteLength".into(), Value::Float(n as f64));
970 let obj = h.new_object(m);
971 for k in ["byteLength", "detached", "resizable", "maxByteLength"] {
972 h.hide_prop(&obj, k);
973 }
974 if let Some(p) = h.ensure_ctor_proto("ArrayBuffer") {
977 h.set_proto(&obj, p);
978 }
979 obj
980 })
981}
982
983pub fn is_detached(ab: &Value) -> bool {
989 with_host(|h| match h.get(ab) {
990 Some(JsObj::Object(p)) => p.get("detached").map(|v| h.truthy(v)).unwrap_or(false),
991 _ => false,
992 })
993}
994
995pub fn view_detached(v: &Value) -> bool {
997 with_host(|h| view_detached_h(h, v))
998}
999
1000pub fn view_detached_h(h: &crate::host::JsHost, v: &Value) -> bool {
1003 let buf = match h.get(v) {
1004 Some(JsObj::Object(p)) => p.get("@@buffer").cloned(),
1005 _ => None,
1006 };
1007 match buf.and_then(|b| match h.get(&b) {
1008 Some(JsObj::Object(p)) => p.get("detached").cloned(),
1009 _ => None,
1010 }) {
1011 Some(d) => h.truthy(&d),
1012 None => false,
1013 }
1014}
1015
1016pub fn detach_buffer(ab: &Value) {
1019 detach(ab)
1020}
1021
1022fn detach(ab: &Value) {
1023 with_host(|h| {
1024 let empty = h.new_array(Vec::new());
1025 if let Some(JsObj::Object(p)) = h.get_mut(ab) {
1026 p.insert("@@bytes".into(), empty);
1027 p.insert("byteLength".into(), Value::Float(0.0));
1028 p.insert("detached".into(), Value::Bool(true));
1029 }
1030 h.hide_prop(ab, "byteLength");
1031 h.hide_prop(ab, "detached");
1032 });
1033}
1034
1035pub fn buffer_transfer(ab: &Value, args: &[Value], fixed: bool) -> Result<Value, String> {
1041 let method = if fixed {
1042 "transferToFixedLength"
1043 } else {
1044 "transfer"
1045 };
1046 if is_detached(ab) {
1047 return Err(crate::host::type_error(&format!(
1048 "Cannot perform ArrayBuffer.prototype.{method} on a detached ArrayBuffer"
1049 )));
1050 }
1051 let old = byte_len_of(ab);
1052 let new_len = match args.first().filter(|v| !matches!(v, Value::Undef)) {
1053 Some(v) => with_host(|h| h.to_number(v)).max(0.0) as usize,
1054 None => old,
1055 };
1056 let mut bytes = view_bytes_of_buffer(ab, old);
1057 bytes.resize(new_len, 0);
1058 let out = new_array_buffer(new_len);
1059 write_buffer_bytes(&out, &bytes);
1060 if !fixed {
1061 let resizable = with_host(|h| match h.get(ab) {
1064 Some(JsObj::Object(p)) => p.contains_key("@@maxByteLength"),
1065 _ => false,
1066 });
1067 if resizable {
1068 let max = with_host(|h| match h.get(ab) {
1069 Some(JsObj::Object(p)) => p.get("@@maxByteLength").cloned(),
1070 _ => None,
1071 });
1072 if let Some(max) = max {
1073 with_host(|h| {
1074 if let Some(JsObj::Object(p)) = h.get_mut(&out) {
1075 p.insert("@@maxByteLength".into(), max);
1076 }
1077 });
1078 }
1079 }
1080 }
1081 detach(ab);
1082 Ok(out)
1083}
1084
1085fn byte_len_of(ab: &Value) -> usize {
1087 with_host(|h| match h.get(ab) {
1088 Some(JsObj::Object(p)) => {
1089 p.get("byteLength").map(|l| h.to_number(l)).unwrap_or(0.0) as usize
1090 }
1091 _ => 0,
1092 })
1093}
1094
1095fn view_bytes_of_buffer(ab: &Value, n: usize) -> Vec<u8> {
1097 let Some(store) = store_of(ab) else {
1098 return Vec::new();
1099 };
1100 with_host(|h| match h.get(&store) {
1101 Some(JsObj::Array(items)) => items
1102 .iter()
1103 .take(n)
1104 .map(|x| h.to_number(x) as i64 as u8)
1105 .collect(),
1106 _ => Vec::new(),
1107 })
1108}
1109
1110pub fn detached_error(label: &str, method: &str, buffer_only: bool) -> String {
1113 let tail = if buffer_only {
1114 "a detached ArrayBuffer"
1115 } else {
1116 "a detached or out-of-bounds ArrayBuffer"
1117 };
1118 let method = match method {
1124 "@@iterator" => "values",
1125 other => other,
1126 };
1127 crate::host::type_error(&format!("Cannot perform {label}.{method} on {tail}"))
1128}
1129
1130fn store_of(ab: &Value) -> Option<Value> {
1132 with_host(|h| match h.get(ab) {
1133 Some(JsObj::Object(p)) => p.get("@@bytes").cloned(),
1134 _ => None,
1135 })
1136}
1137
1138fn view_base(v: &Value) -> Option<(Value, usize)> {
1140 with_host(|h| match h.get(v) {
1141 Some(JsObj::Object(p)) => {
1142 let buf = p.get("@@buffer").cloned()?;
1143 let off = p.get("byteOffset").map(|o| h.to_number(o)).unwrap_or(0.0);
1144 Some((buf, off.max(0.0) as usize))
1145 }
1146 _ => None,
1147 })
1148}
1149
1150pub fn view_bytes(v: &Value, at: usize, n: usize) -> Option<Vec<u8>> {
1152 let (buf, off) = view_base(v)?;
1153 let store = store_of(&buf)?;
1154 with_host(|h| match h.get(&store) {
1155 Some(JsObj::Array(items)) => {
1156 let start = off + at;
1157 if start + n > items.len() {
1158 return None;
1159 }
1160 Some(
1161 items[start..start + n]
1162 .iter()
1163 .map(|x| h.to_number(x) as i64 as u8)
1164 .collect(),
1165 )
1166 }
1167 _ => None,
1168 })
1169}
1170
1171pub fn write_view_bytes(v: &Value, at: usize, bytes: &[u8]) -> bool {
1174 let Some((buf, off)) = view_base(v) else {
1175 return false;
1176 };
1177 let Some(store) = store_of(&buf) else {
1178 return false;
1179 };
1180 with_host(|h| match h.get_mut(&store) {
1181 Some(JsObj::Array(items)) => {
1182 let start = off + at;
1183 if start + bytes.len() > items.len() {
1184 return false;
1185 }
1186 for (i, b) in bytes.iter().enumerate() {
1187 items[start + i] = Value::Float(*b as f64);
1188 }
1189 true
1190 }
1191 _ => false,
1192 })
1193}
1194
1195fn decode(kind: &str, b: &[u8]) -> Value {
1198 match kind {
1199 "Int8Array" => Value::Float(b[0] as i8 as f64),
1200 "Uint8Array" | "Uint8ClampedArray" => Value::Float(b[0] as f64),
1201 "Int16Array" => Value::Float(i16::from_le_bytes([b[0], b[1]]) as f64),
1202 "Uint16Array" => Value::Float(u16::from_le_bytes([b[0], b[1]]) as f64),
1203 "Int32Array" => Value::Float(i32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
1204 "Uint32Array" => Value::Float(u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
1205 "Float32Array" => Value::Float(f32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f64),
1206 "BigInt64Array" => {
1207 let raw = i64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
1208 with_host(|h| h.new_bigint(num_bigint::BigInt::from(raw)))
1209 }
1210 "BigUint64Array" => {
1211 let raw = u64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
1212 with_host(|h| h.new_bigint(num_bigint::BigInt::from(raw)))
1213 }
1214 _ => Value::Float(f64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]))),
1215 }
1216}
1217
1218fn encode(kind: &str, v: &Value) -> Vec<u8> {
1220 if is_bigint_kind(kind) {
1221 use num_traits::cast::ToPrimitive;
1222 let b = bigint_of(v);
1223 let raw = if kind == "BigInt64Array" {
1224 b.to_i64().unwrap_or(0) as u64
1225 } else {
1226 b.to_u64().unwrap_or(0)
1227 };
1228 return raw.to_le_bytes().to_vec();
1229 }
1230 let n = num(v);
1231 match kind {
1232 "Int8Array" => vec![n as i64 as i8 as u8],
1233 "Uint8Array" | "Uint8ClampedArray" => vec![n as i64 as u8],
1234 "Int16Array" => (n as i64 as i16).to_le_bytes().to_vec(),
1235 "Uint16Array" => (n as i64 as u16).to_le_bytes().to_vec(),
1236 "Int32Array" => (n as i64 as i32).to_le_bytes().to_vec(),
1237 "Uint32Array" => (n as i64 as u32).to_le_bytes().to_vec(),
1238 "Float32Array" => (n as f32).to_le_bytes().to_vec(),
1239 _ => n.to_le_bytes().to_vec(),
1240 }
1241}
1242
1243pub fn elems_with_host(h: &crate::host::JsHost, v: &Value) -> Vec<Value> {
1248 if let Some(JsObj::Object(p)) = h.get(v) {
1249 if let Some(arr) = p.get("@@bytes") {
1250 return match h.get(arr) {
1251 Some(JsObj::Array(items)) => items.clone(),
1252 _ => Vec::new(),
1253 };
1254 }
1255 }
1256 let Some((kind, raws)) = raw_elems(h, v) else {
1257 return Vec::new();
1258 };
1259 if is_bigint_kind(&kind) {
1262 return vec![Value::Undef; raws.len()];
1263 }
1264 raws.iter().map(|b| decode(&kind, b)).collect()
1265}
1266
1267fn raw_elems(h: &crate::host::JsHost, v: &Value) -> Option<(String, Vec<Vec<u8>>)> {
1270 let JsObj::Object(p) = h.get(v)? else {
1271 return None;
1272 };
1273 let kind = p
1274 .get("@@kind")
1275 .map(|k| h.str_of(k))
1276 .unwrap_or_else(|| "Uint8Array".into());
1277 let bpe = bytes_per_element(&kind);
1278 let len = if view_detached_h(h, v) {
1282 0
1283 } else {
1284 p.get("length").map(|l| h.to_number(l)).unwrap_or(0.0) as usize
1285 };
1286 let off = p.get("byteOffset").map(|o| h.to_number(o)).unwrap_or(0.0) as usize;
1287 let store = match p.get("@@buffer").and_then(|b| h.get(b)) {
1288 Some(JsObj::Object(bp)) => bp.get("@@bytes").and_then(|a| h.get(a)),
1289 _ => None,
1290 };
1291 let JsObj::Array(bytes) = store? else {
1292 return None;
1293 };
1294 let out = (0..len)
1295 .map(|i| {
1296 let start = off + i * bpe;
1297 if start + bpe > bytes.len() {
1298 return vec![0u8; bpe];
1299 }
1300 bytes[start..start + bpe]
1301 .iter()
1302 .map(|x| h.to_number(x) as i64 as u8)
1303 .collect()
1304 })
1305 .collect();
1306 Some((kind, out))
1307}
1308
1309pub fn elems_mut_host(h: &mut crate::host::JsHost, v: &Value) -> Vec<Value> {
1313 if let Some(JsObj::Object(p)) = h.get(v) {
1314 if let Some(arr) = p.get("@@bytes").cloned() {
1315 return match h.get(&arr) {
1316 Some(JsObj::Array(items)) => items.clone(),
1317 _ => Vec::new(),
1318 };
1319 }
1320 }
1321 let Some((kind, raws)) = raw_elems(h, v) else {
1322 return Vec::new();
1323 };
1324 raws.iter()
1325 .map(|b| {
1326 if !is_bigint_kind(&kind) {
1327 return decode(&kind, b);
1328 }
1329 let raw = u64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
1330 h.new_bigint(if kind == "BigInt64Array" {
1331 num_bigint::BigInt::from(raw as i64)
1332 } else {
1333 num_bigint::BigInt::from(raw)
1334 })
1335 })
1336 .collect()
1337}
1338
1339pub fn elems_display(h: &crate::host::JsHost, v: &Value) -> Vec<String> {
1343 let Some((kind, raws)) = raw_elems(h, v) else {
1344 return Vec::new();
1345 };
1346 raws.iter()
1347 .map(|b| {
1348 if !is_bigint_kind(&kind) {
1349 return h.inspect(&decode(&kind, b));
1350 }
1351 let raw = u64::from_le_bytes(b[..8].try_into().unwrap_or([0; 8]));
1352 if kind == "BigInt64Array" {
1353 format!("{}n", raw as i64)
1354 } else {
1355 format!("{raw}n")
1356 }
1357 })
1358 .collect()
1359}
1360
1361fn view_len(v: &Value) -> usize {
1363 if view_detached(v) {
1366 return 0;
1367 }
1368 with_host(|h| match h.get(v) {
1369 Some(JsObj::Object(p)) => p.get("length").map(|l| h.to_number(l)).unwrap_or(0.0) as usize,
1370 _ => 0,
1371 })
1372}
1373
1374pub fn elem_get(recv: &Value, key: &str) -> Option<Value> {
1379 let i: usize = key.parse().ok()?;
1380 if i >= view_len(recv) {
1381 return None;
1382 }
1383 let kind = kind_of(recv);
1384 let bpe = bytes_per_element(&kind);
1385 let bytes = view_bytes(recv, i * bpe, bpe)?;
1386 Some(decode(&kind, &bytes))
1387}
1388
1389pub fn elem_set(recv: &Value, key: &str, val: &Value) -> Result<bool, String> {
1391 let Ok(i) = key.parse::<usize>() else {
1392 return Ok(false);
1393 };
1394 let kind = kind_of(recv);
1395 let n = coerce_val(&kind, val)?;
1398 if i >= view_len(recv) {
1399 return Ok(false);
1400 }
1401 let bpe = bytes_per_element(&kind);
1402 Ok(write_view_bytes(recv, i * bpe, &encode(&kind, &n)))
1403}
1404
1405fn species(recv: &Value, kind: &str, elems: Vec<Value>) -> Value {
1410 if super::native_tag(recv).as_deref() == Some("Buffer") {
1411 let bytes: Vec<u8> = elems.iter().map(|x| num(x) as i64 as u8).collect();
1412 return super::buffer::from_bytes(&bytes);
1413 }
1414 make(kind, elems)
1415}
1416
1417fn write_elems(recv: &Value, kind: &str, vals: &[Value]) -> Result<(), String> {
1422 if super::native_tag(recv).as_deref() == Some("TypedArray") {
1423 let bpe = bytes_per_element(kind);
1424 let coerced: Vec<Value> = vals
1425 .iter()
1426 .map(|v| coerce_val(kind, v))
1427 .collect::<Result<_, _>>()?;
1428 for (i, v) in coerced.iter().enumerate() {
1429 write_view_bytes(recv, i * bpe, &encode(kind, v));
1430 }
1431 return Ok(());
1432 }
1433 let field = "@@bytes";
1434 let coerced: Vec<Value> = vals
1437 .iter()
1438 .map(|v| coerce_val(kind, v))
1439 .collect::<Result<_, _>>()?;
1440 with_host(|h| {
1441 if let Some(JsObj::Object(p)) = h.get(recv) {
1442 if let Some(arr) = p.get(field).cloned() {
1443 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
1444 for (i, v) in coerced.into_iter().enumerate() {
1445 if i < items.len() {
1446 items[i] = v;
1447 }
1448 }
1449 }
1450 }
1451 }
1452 });
1453 Ok(())
1454}
1455
1456fn sort_elements(elems: &mut Vec<Value>, kind: &str, cmp: Option<&Value>) -> Result<(), String> {
1460 let cmp = cmp.cloned().unwrap_or(Value::Undef);
1461 if with_host(|h| crate::host::is_callable(h, &cmp)) {
1462 return crate::builtins::sort_values(elems, Some(&cmp));
1468 }
1469 if is_bigint_kind(kind) {
1476 let keys: Vec<num_bigint::BigInt> = elems.iter().map(bigint_of).collect();
1477 let mut idx: Vec<usize> = (0..elems.len()).collect();
1478 idx.sort_by(|a, b| keys[*a].cmp(&keys[*b]));
1479 *elems = idx.into_iter().map(|i| elems[i].clone()).collect();
1480 } else {
1481 elems.sort_by(|a, b| {
1482 num(a)
1483 .partial_cmp(&num(b))
1484 .unwrap_or(std::cmp::Ordering::Equal)
1485 });
1486 }
1487 Ok(())
1488}
1489
1490fn rel_index(args: &[Value], idx: usize, len: usize, default: usize) -> usize {
1494 if args.len() <= idx {
1495 return default;
1496 }
1497 let n = super::arg_num(args, idx);
1498 if n < 0.0 {
1499 (len as f64 + n).max(0.0) as usize
1500 } else {
1501 (n as usize).min(len)
1502 }
1503}
1504
1505fn base64_instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
1510 let kind = kind_of(recv);
1511 if kind != "Uint8Array" {
1512 return Err(crate::host::type_error(&format!(
1519 "Method Uint8Array.prototype.{method} called on incompatible receiver undefined"
1520 )));
1521 }
1522 let bytes: Vec<u8> = elem_values(recv)
1523 .iter()
1524 .map(|v| with_host(|h| h.to_number(v)) as u8)
1525 .collect();
1526 match method {
1527 "toBase64" => {
1528 let (url, _) = base64_options(args.first())?;
1529 let omit = args
1530 .first()
1531 .filter(|v| !matches!(v, Value::Undef))
1532 .map(|o| {
1533 with_host(|h| match h.get(o) {
1534 Some(JsObj::Object(p)) => {
1535 p.get("omitPadding").map(|v| h.truthy(v)).unwrap_or(false)
1536 }
1537 _ => false,
1538 })
1539 })
1540 .unwrap_or(false);
1541 let mut s = super::to_base64(&bytes);
1544 if url {
1545 s = s.replace('+', "-").replace('/', "_");
1546 }
1547 if omit {
1548 s = s.trim_end_matches('=').to_string();
1549 }
1550 Ok(with_host(|h| h.new_str(s)))
1551 }
1552 "toHex" => Ok(with_host(|h| h.new_str(super::to_hex(&bytes)))),
1553 "setFromBase64" | "setFromHex" => {
1556 let s = base64_input(args)?;
1557 let (decoded, read) = if method == "setFromHex" {
1558 let d = decode_hex_strict(&s)?;
1559 let fits = d.len().min(bytes.len());
1560 (d[..fits].to_vec(), fits * 2)
1561 } else {
1562 let (url, last) = base64_options(args.get(1))?;
1563 let whole = (bytes.len() / 3) * 4;
1566 let head: String = s.chars().take(whole).collect();
1567 let (mut d, mut consumed) = decode_base64_strict(&head, url, last)?;
1568 if d.len() < bytes.len() {
1569 let (full, full_read) = decode_base64_strict(&s, url, last)?;
1570 if full.len() <= bytes.len() {
1571 d = full;
1572 consumed = full_read;
1573 }
1574 }
1575 (d, consumed)
1576 };
1577 write_view_bytes(recv, 0, &decoded);
1578 Ok(with_host(|h| {
1579 let mut m = IndexMap::new();
1580 m.insert("read".to_string(), Value::Float(read as f64));
1581 m.insert("written".to_string(), Value::Float(decoded.len() as f64));
1582 h.new_object(m)
1583 }))
1584 }
1585 _ => unreachable!("caller gates the method name"),
1586 }
1587}
1588
1589pub fn instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
1590 if view_detached(recv) {
1594 return Err(detached_error("%TypedArray%.prototype", method, false));
1595 }
1596 if matches!(
1597 method,
1598 "toBase64" | "toHex" | "setFromBase64" | "setFromHex"
1599 ) {
1600 return base64_instance_call(recv, method, args);
1601 }
1602 let kind = kind_of(recv);
1603 let elems = elem_values(recv);
1608 let this_arg = args.get(1).filter(|v| !matches!(v, Value::Undef)).cloned();
1614 let call_cb = |i: usize, v: &Value| -> Result<Value, String> {
1615 crate::host::invoke(
1616 &args.first().cloned().unwrap_or(Value::Undef),
1617 vec![v.clone(), Value::Float(i as f64), recv.clone()],
1618 this_arg.clone(),
1619 )
1620 };
1621 match method {
1622 "every" => {
1623 for (i, v) in elems.iter().enumerate() {
1624 let r = call_cb(i, v)?;
1625 if !with_host(|h| h.truthy(&r)) {
1626 return Ok(Value::Bool(false));
1627 }
1628 }
1629 Ok(Value::Bool(true))
1630 }
1631 "some" => {
1632 for (i, v) in elems.iter().enumerate() {
1633 let r = call_cb(i, v)?;
1634 if with_host(|h| h.truthy(&r)) {
1635 return Ok(Value::Bool(true));
1636 }
1637 }
1638 Ok(Value::Bool(false))
1639 }
1640 "forEach" => {
1641 for (i, v) in elems.iter().enumerate() {
1642 call_cb(i, v)?;
1643 }
1644 Ok(Value::Undef)
1645 }
1646 "map" => {
1647 let mut out = Vec::with_capacity(elems.len());
1648 for (i, v) in elems.iter().enumerate() {
1649 let r = call_cb(i, v)?;
1650 out.push(coerce_val(&kind, &r)?);
1651 }
1652 Ok(species(recv, &kind, out))
1653 }
1654 "filter" => {
1655 let mut out = Vec::new();
1656 for (i, v) in elems.iter().enumerate() {
1657 let r = call_cb(i, v)?;
1658 if with_host(|h| h.truthy(&r)) {
1659 out.push(v.clone());
1660 }
1661 }
1662 Ok(species(recv, &kind, out))
1663 }
1664 "find" | "findIndex" | "findLast" | "findLastIndex" => {
1665 let last = method.starts_with("findLast");
1666 let idxs: Vec<usize> = if last {
1667 (0..elems.len()).rev().collect()
1668 } else {
1669 (0..elems.len()).collect()
1670 };
1671 for i in idxs {
1672 let r = call_cb(i, &elems[i])?;
1673 if with_host(|h| h.truthy(&r)) {
1674 return Ok(if method.ends_with("Index") {
1675 Value::Float(i as f64)
1676 } else {
1677 elems[i].clone()
1678 });
1679 }
1680 }
1681 Ok(if method.ends_with("Index") {
1682 Value::Float(-1.0)
1683 } else {
1684 Value::Undef
1685 })
1686 }
1687 "reduce" | "reduceRight" => {
1688 let right = method == "reduceRight";
1689 let order: Vec<usize> = if right {
1690 (0..elems.len()).rev().collect()
1691 } else {
1692 (0..elems.len()).collect()
1693 };
1694 let cb = args.first().cloned().unwrap_or(Value::Undef);
1695 let mut it = order.into_iter();
1696 let mut acc = if args.len() >= 2 {
1697 args[1].clone()
1698 } else {
1699 match it.next() {
1700 Some(i) => elems[i].clone(),
1701 None => {
1702 return Err(crate::host::type_error(
1703 "Reduce of empty array with no initial value",
1704 ))
1705 }
1706 }
1707 };
1708 for i in it {
1709 acc = crate::host::invoke(
1710 &cb,
1711 vec![acc, elems[i].clone(), Value::Float(i as f64), recv.clone()],
1712 None,
1713 )?;
1714 }
1715 Ok(acc)
1716 }
1717 "reverse" => {
1718 let mut out = elems.clone();
1719 out.reverse();
1720 write_elems(recv, &kind, &out)?;
1721 Ok(recv.clone())
1722 }
1723 "sort" => {
1724 let mut out = elems.clone();
1725 sort_elements(&mut out, &kind, args.first())?;
1726 write_elems(recv, &kind, &out)?;
1727 Ok(recv.clone())
1728 }
1729 "copyWithin" => {
1730 let len = elems.len();
1731 let target = rel_index(args, 0, len, 0);
1732 let start = rel_index(args, 1, len, 0);
1733 let end = rel_index(args, 2, len, len);
1734 let src: Vec<Value> = elems[start.min(end)..end.max(start)].to_vec();
1735 let mut out = elems.clone();
1736 for (k, v) in src.iter().enumerate() {
1737 if target + k < len {
1738 out[target + k] = v.clone();
1739 }
1740 }
1741 write_elems(recv, &kind, &out)?;
1742 Ok(recv.clone())
1743 }
1744 "at" => {
1745 let n = super::arg_num(args, 0);
1746 let i = if n < 0.0 { elems.len() as f64 + n } else { n };
1747 if i < 0.0 || i >= elems.len() as f64 {
1748 return Ok(Value::Undef);
1749 }
1750 Ok(elems[i as usize].clone())
1751 }
1752 "lastIndexOf" => {
1753 let needle = args.first().cloned().unwrap_or(Value::Undef);
1754 let from = (args.len() > 1).then(|| super::arg_num(args, 1));
1755 let found = crate::builtins::search_start_last(from, elems.len()).and_then(|start| {
1756 elems[..=start]
1757 .iter()
1758 .rposition(|x| same_element(x, &needle, false))
1759 });
1760 Ok(Value::Float(found.map(|p| p as f64).unwrap_or(-1.0)))
1761 }
1762 "keys" | "values" | "entries" | "@@iterator" => {
1767 let items: Vec<Value> = with_host(|h| match method {
1768 "keys" => (0..elems.len()).map(|i| Value::Float(i as f64)).collect(),
1769 "values" | "@@iterator" => elems.clone(),
1770 _ => elems
1771 .iter()
1772 .enumerate()
1773 .map(|(i, v)| h.new_array(vec![Value::Float(i as f64), v.clone()]))
1774 .collect(),
1775 });
1776 Ok(with_host(|h| h.alloc(JsObj::Iter { items, idx: 0 })))
1777 }
1778 "toString" | "join" => {
1779 let sep = if method == "join" && !args.is_empty() {
1780 super::arg_str(args, 0)
1781 } else {
1782 ",".into()
1783 };
1784 let parts: Vec<String> = with_host(|h| elems.iter().map(|n| h.str_of(n)).collect());
1785 Ok(with_host(|h| h.new_str(parts.join(&sep))))
1786 }
1787 "slice" | "subarray" => {
1788 let len = elems.len();
1789 let norm = |n: f64| -> usize {
1790 if n < 0.0 {
1791 (len as f64 + n).max(0.0) as usize
1792 } else {
1793 (n as usize).min(len)
1794 }
1795 };
1796 let s = if args.is_empty() {
1797 0
1798 } else {
1799 norm(super::arg_num(args, 0))
1800 };
1801 let e = if args.len() < 2 {
1802 len
1803 } else {
1804 norm(super::arg_num(args, 1))
1805 };
1806 let (lo, hi) = (s.min(e), e.max(s));
1807 if method == "subarray" && super::native_tag(recv).as_deref() == Some("TypedArray") {
1810 if let Some((buf, off)) = view_base(recv) {
1811 let bpe = bytes_per_element(&kind);
1812 return Ok(make_view(&kind, &buf, off + lo * bpe, hi - lo));
1813 }
1814 }
1815 Ok(species(recv, &kind, elems[lo..hi].to_vec()))
1816 }
1817 "indexOf" => {
1818 let needle = args.first().cloned().unwrap_or(Value::Undef);
1819 let start = crate::builtins::search_start(super::arg_num(args, 1), elems.len());
1820 Ok(Value::Float(
1821 elems
1822 .iter()
1823 .skip(start)
1824 .position(|x| same_element(x, &needle, false))
1825 .map(|p| (p + start) as f64)
1826 .unwrap_or(-1.0),
1827 ))
1828 }
1829 "includes" => {
1830 let needle = args.first().cloned().unwrap_or(Value::Undef);
1831 let start = crate::builtins::search_start(super::arg_num(args, 1), elems.len());
1832 Ok(Value::Bool(
1833 elems
1834 .iter()
1835 .skip(start)
1836 .any(|x| same_element(x, &needle, true)),
1837 ))
1838 }
1839 "fill" => {
1846 let len = elems.len();
1847 let v = coerce_val(&kind, args.first().unwrap_or(&Value::Undef))?;
1848 let start = rel_index(args, 1, len, 0);
1849 let end = rel_index(args, 2, len, len);
1850 let mut out = elems.clone();
1851 for slot in out.iter_mut().take(end).skip(start) {
1852 *slot = v.clone();
1853 }
1854 write_elems(recv, &kind, &out)?;
1855 Ok(recv.clone())
1856 }
1857 "toReversed" | "toSorted" => {
1862 let mut out = elems.clone();
1863 if method == "toReversed" {
1864 out.reverse();
1865 } else {
1866 sort_elements(&mut out, &kind, args.first())?;
1867 }
1868 Ok(make(&kind, out))
1869 }
1870 "with" => {
1871 let len = elems.len();
1872 let n = super::arg_num(args, 0);
1873 let i = if n < 0.0 { len as f64 + n } else { n };
1874 if !(0.0..len as f64).contains(&i) {
1875 return Err("RangeError: Invalid typed array index".into());
1876 }
1877 let mut out = elems.clone();
1878 out[i as usize] = coerce_val(&kind, args.get(1).unwrap_or(&Value::Undef))?;
1879 Ok(make(&kind, out))
1880 }
1881 "set" => {
1882 let arg = args.first().cloned().unwrap_or(Value::Undef);
1884 let src = match super::native_tag(&arg).as_deref() {
1885 Some("TypedArray") | Some("Buffer") => elem_values(&arg),
1886 _ => crate::host::iter_all(&arg).unwrap_or_default(),
1887 };
1888 let off = super::arg_num(args, 1).max(0.0) as usize;
1889 let src: Vec<Value> = src
1891 .iter()
1892 .map(|v| coerce_val(&kind, v))
1893 .collect::<Result<_, _>>()?;
1894 let bpe = bytes_per_element(&kind);
1895 let len = view_len(recv);
1896 for (k, v) in src.into_iter().enumerate() {
1897 if off + k < len {
1898 write_view_bytes(recv, (off + k) * bpe, &encode(&kind, &v));
1899 }
1900 }
1901 Ok(Value::Undef)
1902 }
1903 _ => Err(crate::host::type_error(&format!(
1904 "{method} is not a function"
1905 ))),
1906 }
1907}
1908
1909pub fn construct_weakref(args: &[Value]) -> Result<Value, String> {
1912 let target = args.first().cloned().unwrap_or(Value::Undef);
1913 Ok(with_host(|h| {
1914 let mut m = IndexMap::new();
1915 m.insert("@@native".into(), h.new_str("WeakRef"));
1916 m.insert("@@target".into(), target);
1917 h.new_object(m)
1918 }))
1919}
1920
1921pub fn weakref_call(recv: &Value, method: &str) -> Result<Value, String> {
1922 match method {
1923 "deref" => Ok(with_host(|h| match h.get(recv) {
1924 Some(JsObj::Object(p)) => p.get("@@target").cloned().unwrap_or(Value::Undef),
1925 _ => Value::Undef,
1926 })),
1927 _ => Err(crate::host::type_error(&format!(
1928 "{method} is not a function"
1929 ))),
1930 }
1931}
1932
1933fn is_object_value(v: &Value) -> bool {
1946 matches!(v, Value::Obj(_))
1947 && with_host(|h| {
1948 !matches!(
1949 h.get(v),
1950 Some(JsObj::Str(_))
1951 | Some(JsObj::Symbol { .. })
1952 | Some(JsObj::BigInt(_))
1953 | Some(JsObj::Null)
1954 )
1955 })
1956}
1957
1958pub fn construct_finalization_registry(args: &[Value]) -> Result<Value, String> {
1959 let cb = args.first().cloned().unwrap_or(Value::Undef);
1960 if !with_host(|h| crate::host::is_callable(h, &cb)) {
1961 return Err(crate::host::type_error(
1962 "FinalizationRegistry: cleanup must be callable",
1963 ));
1964 }
1965 Ok(with_host(|h| {
1966 let tokens = h.new_array(Vec::new());
1967 let mut m = IndexMap::new();
1968 m.insert("@@native".into(), h.new_str("FinalizationRegistry"));
1969 m.insert("@@fr_cb".into(), cb);
1970 m.insert("@@fr_tokens".into(), tokens);
1971 h.new_object(m)
1972 }))
1973}
1974
1975pub fn finalization_registry_call(
1976 recv: &Value,
1977 method: &str,
1978 args: &[Value],
1979) -> Result<Value, String> {
1980 match method {
1981 "register" => {
1982 let target = args.first().cloned().unwrap_or(Value::Undef);
1983 let held = args.get(1).cloned().unwrap_or(Value::Undef);
1984 let token = args.get(2).cloned().unwrap_or(Value::Undef);
1985 if !is_object_value(&target) {
1986 return Err(crate::host::type_error(
1989 "FinalizationRegistry.prototype.register: invalid target",
1990 ));
1991 }
1992 if with_host(|h| h.strict_eq(&target, &held)) {
1993 return Err(crate::host::type_error(
1994 "FinalizationRegistry.prototype.register: target and holdings must not be same",
1995 ));
1996 }
1997 if !matches!(token, Value::Undef) {
2000 if !is_object_value(&token) {
2001 return Err(crate::host::type_error(&format!(
2002 "Invalid unregisterToken ('{}')",
2003 with_host(|h| h.str_of(&token))
2004 )));
2005 }
2006 with_host(|h| {
2007 let toks = registry_tokens(h, recv);
2008 if let Some(JsObj::Array(items)) = h.get_mut(&toks) {
2009 items.push(token);
2010 }
2011 });
2012 }
2013 Ok(Value::Undef)
2014 }
2015 "unregister" => {
2016 let token = args.first().cloned().unwrap_or(Value::Undef);
2017 if !is_object_value(&token) {
2018 return Err(crate::host::type_error(&format!(
2020 "Invalid unregisterToken ('{}')",
2021 with_host(|h| h.str_of(&token))
2022 )));
2023 }
2024 Ok(Value::Bool(with_host(|h| {
2025 let toks = registry_tokens(h, recv);
2026 let kept: Vec<Value> = match h.get(&toks) {
2027 Some(JsObj::Array(items)) => items
2028 .iter()
2029 .filter(|t| !h.strict_eq(t, &token))
2030 .cloned()
2031 .collect(),
2032 _ => Vec::new(),
2033 };
2034 let removed = match h.get(&toks) {
2035 Some(JsObj::Array(items)) => items.len() != kept.len(),
2036 _ => false,
2037 };
2038 if let Some(JsObj::Array(items)) = h.get_mut(&toks) {
2039 *items = kept;
2040 }
2041 removed
2042 })))
2043 }
2044 _ => Err(crate::host::type_error(&format!(
2045 "{method} is not a function"
2046 ))),
2047 }
2048}
2049
2050fn registry_tokens(h: &crate::host::JsHost, recv: &Value) -> Value {
2052 match h.get(recv) {
2053 Some(JsObj::Object(p)) => p.get("@@fr_tokens").cloned().unwrap_or(Value::Undef),
2054 _ => Value::Undef,
2055 }
2056}
2057
2058pub fn construct_text_encoder() -> Result<Value, String> {
2061 Ok(with_host(|h| {
2062 let mut m = IndexMap::new();
2063 m.insert("@@native".into(), h.new_str("TextEncoder"));
2064 m.insert("@@encoding".into(), h.new_str("utf-8"));
2069 h.new_object(m)
2070 }))
2071}
2072
2073pub fn text_encoder_call(_recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
2074 match method {
2075 "encode" => {
2077 let s = super::arg_str(args, 0);
2078 Ok(make(
2079 "Uint8Array",
2080 s.as_bytes()
2081 .iter()
2082 .map(|b| Value::Float(*b as f64))
2083 .collect(),
2084 ))
2085 }
2086 _ => Err(crate::host::type_error(&format!(
2087 "{method} is not a function"
2088 ))),
2089 }
2090}
2091
2092fn encoding_for_label(label: &str) -> Option<&'static str> {
2099 Some(match label.trim().to_ascii_lowercase().as_str() {
2100 "utf-8" | "utf8" | "unicode-1-1-utf-8" | "unicode11utf8" | "unicode20utf8"
2101 | "x-unicode20utf8" => "utf-8",
2102 "latin1" | "iso-8859-1" | "iso8859-1" | "iso88591" | "ascii" | "us-ascii" | "cp1252"
2103 | "cp819" | "ibm819" | "l1" | "windows-1252" | "x-cp1252" => "windows-1252",
2104 "utf-16le" | "utf-16" | "ucs-2" | "ucs2" | "unicodefeff" | "unicodefffe"
2105 | "iso-10646-ucs-2" | "csunicode" => "utf-16le",
2106 _ => return None,
2107 })
2108}
2109
2110pub fn construct_text_decoder(args: &[Value]) -> Result<Value, String> {
2111 let label = if args.is_empty() || matches!(args[0], Value::Undef) {
2112 "utf-8".to_string()
2113 } else {
2114 super::arg_str(args, 0)
2115 };
2116 let Some(encoding) = encoding_for_label(&label) else {
2117 return Err(crate::host::coded_error(
2118 "RangeError",
2119 "ERR_ENCODING_NOT_SUPPORTED",
2120 &format!("The \"{label}\" encoding is not supported"),
2121 ));
2122 };
2123 let flag = |key: &str| {
2127 args.get(1)
2128 .map(|o| crate::builtins::get_property(o, key).unwrap_or(Value::Undef))
2129 .map(|v| with_host(|h| h.truthy(&v)))
2130 .unwrap_or(false)
2131 };
2132 let (fatal, ignore_bom) = (flag("fatal"), flag("ignoreBOM"));
2133 Ok(with_host(|h| {
2134 let mut m = IndexMap::new();
2135 m.insert("@@native".into(), h.new_str("TextDecoder"));
2136 m.insert("@@encoding".into(), h.new_str(encoding.to_string()));
2137 m.insert("@@fatal".into(), Value::Bool(fatal));
2138 m.insert("@@ignoreBOM".into(), Value::Bool(ignore_bom));
2139 h.new_object(m)
2140 }))
2141}
2142
2143const CP1252_HIGH: [char; 32] = [
2149 '\u{20ac}', '\u{81}', '\u{201a}', '\u{192}', '\u{201e}', '\u{2026}', '\u{2020}', '\u{2021}',
2150 '\u{2c6}', '\u{2030}', '\u{160}', '\u{2039}', '\u{152}', '\u{8d}', '\u{17d}', '\u{8f}',
2151 '\u{90}', '\u{2018}', '\u{2019}', '\u{201c}', '\u{201d}', '\u{2022}', '\u{2013}', '\u{2014}',
2152 '\u{2dc}', '\u{2122}', '\u{161}', '\u{203a}', '\u{153}', '\u{9d}', '\u{17e}', '\u{178}',
2153];
2154
2155pub fn text_decoder_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
2156 match method {
2157 "decode" => {
2159 let bytes: Vec<u8> = elems_of(&args.first().cloned().unwrap_or(Value::Undef))
2160 .unwrap_or_default()
2161 .iter()
2162 .map(|n| *n as u8)
2163 .collect();
2164 let slot = |key: &str| {
2165 with_host(|h| match h.get(recv) {
2166 Some(JsObj::Object(p)) => p.get(key).cloned(),
2167 _ => None,
2168 })
2169 };
2170 let enc = slot("@@encoding")
2171 .map(|v| with_host(|h| h.str_of(&v)))
2172 .unwrap_or_else(|| "utf-8".into());
2173 let flag = |key: &str| matches!(slot(key), Some(Value::Bool(true)));
2174 let s = match enc.as_str() {
2175 "windows-1252" => bytes
2176 .iter()
2177 .map(|b| match b {
2178 0x80..=0x9f => CP1252_HIGH[(b - 0x80) as usize],
2179 _ => *b as char,
2180 })
2181 .collect(),
2182 "utf-16le" => {
2183 let units: Vec<u16> = bytes
2184 .chunks_exact(2)
2185 .map(|c| u16::from_le_bytes([c[0], c[1]]))
2186 .collect();
2187 String::from_utf16_lossy(&units)
2188 }
2189 _ if flag("@@fatal") => match std::str::from_utf8(&bytes) {
2194 Ok(s) => s.to_string(),
2195 Err(_) => {
2196 return Err(crate::host::coded_error(
2197 "TypeError",
2198 "ERR_ENCODING_INVALID_ENCODED_DATA",
2199 &format!("The encoded data was not valid for encoding {enc}"),
2200 ))
2201 }
2202 },
2203 _ => String::from_utf8_lossy(&bytes).into_owned(),
2204 };
2205 let s = match s.strip_prefix('\u{feff}') {
2207 Some(rest) if !flag("@@ignoreBOM") => rest.to_string(),
2208 _ => s,
2209 };
2210 Ok(with_host(|h| h.new_str(s)))
2211 }
2212 _ => Err(crate::host::type_error(&format!(
2213 "{method} is not a function"
2214 ))),
2215 }
2216}