Skip to main content

atmos/os_lib/js/builtins/
text_clone.rs

1// 分割: builtins.rs より機械的に移動(2026-07-16 リファクタ フェーズ2)。
2// ロジック不変。可視性のみ pub(crate) へ昇格し、親が pub(crate) use で再エクスポート。
3use super::*;
4// structuredClone が RegExp を複製する経路で使う(元は builtins.rs のファイル
5// スコープ use で共有されていた。分割に伴い明示 import)。
6use super::super::regex::{self, RegExpData};
7
8// ===== TextEncoder / TextDecoder(UTF-8。Uint8Array を返す) =====
9pub(crate) fn text_encode(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
10    let s = arg(a, 0).to_js_string();
11    Ok(make_uint8array(it, s.as_bytes().to_vec()))
12}
13/// `TextEncoder.prototype.encodeInto(source, destination)` が丸ごと未対応
14/// だった(`encode()` は既に対応済みだったが、既存の `Uint8Array` バッファへ
15/// 直接書き込むこちらの性能向けバリアントが欠けていた)。仕様どおり UTF-8
16/// コードポイント境界で打ち切り(マルチバイト文字を途中で分割しない)、
17/// `{read, written}`(`read` は消費した UTF-16 コード単位数、`written` は
18/// 書き込んだバイト数)を返す。
19pub(crate) fn text_encode_into(_it: &mut Interp, _this: Value, a: &[Value]) -> Result<Value, Value> {
20    let source = arg(a, 0).to_js_string();
21    let dest = arg(a, 1);
22    let Value::Object(dest_obj) = &dest else {
23        return Ok(Value::Undefined);
24    };
25    let capacity = match &dest_obj.borrow().kind {
26        ObjKind::Array(items) => items.len(),
27        _ => 0,
28    };
29    let mut read_units = 0usize;
30    let mut written_bytes: alloc::vec::Vec<u8> = alloc::vec::Vec::new();
31    for c in source.chars() {
32        let mut buf = [0u8; 4];
33        let bytes = c.encode_utf8(&mut buf).as_bytes();
34        if written_bytes.len() + bytes.len() > capacity {
35            break;
36        }
37        written_bytes.extend_from_slice(bytes);
38        read_units += c.len_utf16();
39    }
40    if let ObjKind::Array(items) = &mut dest_obj.borrow_mut().kind {
41        for (i, b) in written_bytes.iter().enumerate() {
42            if let Some(slot) = items.get_mut(i) {
43                *slot = Value::Number(*b as f64);
44            }
45        }
46    }
47    let result = Obj::plain();
48    {
49        let mut b = result.borrow_mut();
50        b.props.insert("read".into(), Value::Number(read_units as f64));
51        b.props
52            .insert("written".into(), Value::Number(written_bytes.len() as f64));
53    }
54    Ok(Value::Object(result))
55}
56/// `TextDecoder` のラベル正規化(WHATWG Encoding の簡易サブセット)。未知の
57/// ラベルは寛容に `utf-8` へフォールバックする(この処理系の他のパース系と
58/// 同水準の簡略化)。
59pub(crate) fn normalize_text_encoding_label(label: &str) -> &'static str {
60    match label.trim().to_lowercase().as_str() {
61        "utf-16le" | "utf-16" => "utf-16le",
62        "utf-16be" => "utf-16be",
63        _ => "utf-8",
64    }
65}
66/// UTF-16(LE/BE)のバイト列を Rust `String` へデコードする。不正な
67/// サロゲートは仕様どおり置換文字(U+FFFD)に置き換える。
68pub(crate) fn decode_utf16_bytes(bytes: &[u8], little_endian: bool) -> alloc::string::String {
69    let units: alloc::vec::Vec<u16> = bytes
70        .chunks(2)
71        .map(|c| {
72            let lo = *c.first().unwrap_or(&0) as u16;
73            let hi = *c.get(1).unwrap_or(&0) as u16;
74            if little_endian {
75                lo | (hi << 8)
76            } else {
77                (lo << 8) | hi
78            }
79        })
80        .collect();
81    core::char::decode_utf16(units)
82        .map(|r| r.unwrap_or(core::char::REPLACEMENT_CHARACTER))
83        .collect()
84}
85/// UTF-8 バイト列の末尾が「まだ継続バイトが足りない不完全なマルチバイト
86/// シーケンスの途中」かどうかを判定し、その未完了分のバイト数(0なら
87/// 末尾は完結している)を返す。`TextDecoder.decode(chunk, {stream:true})`
88/// が末尾のバイトを次回呼び出しへ持ち越すために使う。
89pub(crate) fn utf8_incomplete_trailing_len(bytes: &[u8]) -> usize {
90    let len = bytes.len();
91    for back in 1..=3.min(len) {
92        let b = bytes[len - back];
93        if b & 0b1100_0000 == 0b1000_0000 {
94            continue; // 継続バイト。さらに前を見る
95        }
96        let needed = if b & 0b1000_0000 == 0 {
97            1
98        } else if b & 0b1110_0000 == 0b1100_0000 {
99            2
100        } else if b & 0b1111_0000 == 0b1110_0000 {
101            3
102        } else if b & 0b1111_1000 == 0b1111_0000 {
103            4
104        } else {
105            1 // 不正な先頭バイト。完結扱い(置換文字化に任せる)
106        };
107        return if needed > back { back } else { 0 };
108    }
109    0
110}
111/// `TextDecoder.prototype.decode()`。以前はコンストラクタに渡した encoding
112/// ラベル(`new TextDecoder('utf-16le')` 等)を完全に無視し、常に UTF-8 として
113/// デコードしていたバグだった(`utf-16` 系データを渡しても静かに文字化けする)。
114/// **重要**: `decode(chunk, {stream: true})`(ストリーミングデコード。丸ごと
115/// 未対応だった。チャンク境界でマルチバイト文字が分断された場合に文字化け
116/// させず次回呼び出しへ持ち越す定番パターン)にも対応。`this._pending`
117/// (隠しプロパティ)に前回分の未完了バイトを保持する。UTF-16 系は2バイト
118/// 単位のため奇数バイト余りを同様に持ち越す。
119pub(crate) fn text_decode(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
120    let mut bytes: Vec<u8> = if let Value::Object(o) = &this {
121        match o.borrow().props.get("_pending") {
122            Some(Value::Object(p)) => match &p.borrow().kind {
123                ObjKind::Array(items) => items.iter().map(|v| v.to_number() as u8).collect(),
124                _ => Vec::new(),
125            },
126            _ => Vec::new(),
127        }
128    } else {
129        Vec::new()
130    };
131    // `TextDecoder.decode(arrayBuffer)`(ビューではなく生の`ArrayBuffer`を
132    // 直接渡す、仕様上有効な呼び出し形式)が常に空文字列を返すバグだった。
133    // `iterable_values`は`ObjKind::Array`(TypedArray等)のみ扱い、生の
134    // `ArrayBuffer`(`ObjKind::Plain`+`_bytes`/`byteLength` props)は
135    // 反復可能でないため素通りして空になっていた。`blob_part_bytes`
136    // (`crypto.subtle.digest`等でも使う共通ヘルパー)に差し替えることで
137    // `_bytes`を持つ`ArrayBuffer`からも実際のバイト列を読めるようにする
138    // (TypedArray/配列/文字列の既存の挙動は維持)。2026-07-18 発見・実装。
139    let mut arg0_bytes_f64 = alloc::vec::Vec::new();
140    blob_part_bytes(&arg(a, 0), &mut arg0_bytes_f64);
141    bytes.extend(arg0_bytes_f64.iter().map(|v| *v as i64 as u8));
142    let stream = obj_prop(&arg(a, 1), "stream").map(|v| v.truthy()).unwrap_or(false);
143    let enc = if let Value::Object(o) = &this {
144        o.borrow()
145            .props
146            .get("encoding")
147            .map(|v| v.to_js_string())
148            .unwrap_or_else(|| String::from("utf-8"))
149    } else {
150        String::from("utf-8")
151    };
152    let pending_len = if stream {
153        match enc.as_str() {
154            "utf-16le" | "utf-16be" => bytes.len() % 2,
155            _ => utf8_incomplete_trailing_len(&bytes),
156        }
157    } else {
158        0
159    };
160    let ready_len = bytes.len() - pending_len;
161    let (ready, pending) = bytes.split_at(ready_len);
162    if let Value::Object(o) = &this {
163        let pending_vals: Vec<Value> = pending.iter().map(|&b| Value::Number(b as f64)).collect();
164        o.borrow_mut()
165            .props
166            .insert("_pending".into(), Value::Object(Obj::array(pending_vals)));
167    }
168    let decoded = match enc.as_str() {
169        "utf-16le" => decode_utf16_bytes(ready, true),
170        "utf-16be" => decode_utf16_bytes(ready, false),
171        _ => alloc::string::String::from_utf8_lossy(ready).into_owned(),
172    };
173    Ok(Value::str(decoded))
174}
175pub(crate) fn text_encoder_ctor(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
176    let o = Obj::plain();
177    {
178        let mut b = o.borrow_mut();
179        b.props.insert("encoding".into(), Value::str("utf-8"));
180        b.props.insert("encode".into(), nv("encode", text_encode));
181        b.props
182            .insert("encodeInto".into(), nv("encodeInto", text_encode_into));
183    }
184    Ok(Value::Object(o))
185}
186/// `new TextDecoder(label)` が丸ごと引数を無視し常に `utf-8` 決め打ちだった
187/// バグ(詳細は `text_decode`/`normalize_text_encoding_label` のコメント参照)。
188pub(crate) fn text_decoder_ctor(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
189    let label = arg(a, 0).to_js_string();
190    let enc = if label.is_empty() {
191        "utf-8"
192    } else {
193        normalize_text_encoding_label(&label)
194    };
195    let o = Obj::plain();
196    {
197        let mut b = o.borrow_mut();
198        b.props.insert("encoding".into(), Value::str(enc));
199        b.props.insert("decode".into(), nv("decode", text_decode));
200    }
201    Ok(Value::Object(o))
202}
203
204// ===== crypto =====
205/// `URL.createObjectURL(blob)`(`Blob`/`File` を一意な `blob:` URL 文字列へ
206/// 変換する定番パターン。`<img src=URL.createObjectURL(blob)>`/ダウンロード
207/// リンク生成等で広く使われる)が丸ごと未対応だった。実際の URL 解決
208/// (画像読み込みパイプラインへの配線)までは行わない発行のみの簡略実装だが、
209/// 呼び出し自体が TypeError になる致命的な欠落は解消する。既存の
210/// `crypto.randomUUID()` と同じ乱数源で一意な ID を生成する。
211pub(crate) fn url_create_object_url(it: &mut Interp, t: Value, _a: &[Value]) -> Result<Value, Value> {
212    let id = crypto_random_uuid(it, t, &[])?.to_js_string();
213    Ok(Value::str(alloc::format!("blob:{id}")))
214}
215/// `URL.revokeObjectURL(url)`。`createObjectURL` が実際の登録簿を持たない
216/// 簡略実装のため、対になる解放側も無害な no-op とする。
217pub(crate) fn url_revoke_object_url(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
218    Ok(Value::Undefined)
219}
220pub(crate) fn crypto_random_uuid(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
221    let mut bytes = [0u8; 16];
222    for b in bytes.iter_mut() {
223        *b = (next_rand_u64() & 0xff) as u8;
224    }
225    bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
226    bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 10
227    let mut s = String::new();
228    for (i, b) in bytes.iter().enumerate() {
229        if i == 4 || i == 6 || i == 8 || i == 10 {
230            s.push('-');
231        }
232        s.push_str(&format!("{:02x}", b));
233    }
234    Ok(Value::str(s))
235}
236/// `crypto.getRandomValues(typedArray)`。以前は種別を無視して全要素を無条件に
237/// `& 0xff`(0-255)で埋めており、`Int16Array`/`Int32Array`/`Uint32Array` 等
238/// 8bit より広い型に対しては本来の値域のごく一部(下位8bitのみ、常に非負)しか
239/// 埋められない、実質的にランダム性が大幅に弱まるバグだった。`_ta_kind` タグから
240/// 種別を復元し、コンストラクタ/`set`/索引代入と同じ `TaKind::convert()` で
241/// 型ごとの正しい値域に変換する。
242pub(crate) fn crypto_get_random_values(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
243    let v = arg(a, 0);
244    if let Value::Object(o) = &v {
245        let kind = o
246            .borrow()
247            .props
248            .get("_ta_kind")
249            .map(|v| v.to_js_string())
250            .map(|tag| TaKind::from_name(tag.as_str()));
251        let mut b = o.borrow_mut();
252        if let ObjKind::Array(items) = &mut b.kind {
253            for it2 in items.iter_mut() {
254                let raw = Value::Number(next_rand_u64() as f64);
255                *it2 = Value::Number(match kind {
256                    Some(k) => k.convert(&raw),
257                    None => (next_rand_u64() & 0xff) as f64,
258                });
259            }
260        }
261    }
262    Ok(v)
263}
264
265// ===== structuredClone(深いコピー。関数等の特殊種別は plain object に退化)=====
266/// `structuredClone` は仕様上、循環参照(`var a={}; a.self=a;`)や同一オブジェクトへの
267/// 複数参照(構造の共有)を正しく扱う必要がある。以前はそのための「複製済みオブジェクトの
268/// 記録」を一切持たず単純な再帰だけだったため、循環参照ではスタックオーバーフロー
269/// (この no_std カーネル環境ではクラッシュ/ハングになり得る)に陥っていた。
270/// `seen` に (元オブジェクト, 複製先オブジェクト) を積みながら辿ることで、複製先の
271/// 入れ物を先に作ってから中身を埋める(=自己参照が現れた時点で既に登録済みにする)。
272pub(crate) fn deep_clone_value(
273    it: &mut Interp,
274    v: &Value,
275    seen: &mut Vec<(super::super::value::ObjRef, super::super::value::ObjRef)>,
276) -> Result<Value, Value> {
277    deep_clone_value_depth(it, v, seen, 0)
278}
279
280fn deep_clone_value_depth(
281    it: &mut Interp,
282    v: &Value,
283    seen: &mut Vec<(super::super::value::ObjRef, super::super::value::ObjRef)>,
284    depth: usize,
285) -> Result<Value, Value> {
286    // 閾値30は、この関数がRustのネイティブ再帰で実装されているため
287    // JSの`max_steps`予算では防げない「Rustコールスタック自体のオーバーフロー」
288    // に対するガードとして機能する。ブート時点の`boot.S`初期コアスタックが
289    // わずか64KB(`(CoreID+1)*64KB`)しか無く、深い再帰がチェック到達前に
290    // スタックを枯渇させることを防止する。
291    // ※ 以前 `seen.len() > 30` でチェックしていたが、`seen` は累積の
292    // クローン済みオブジェクト数であるため、フラットな大配列(要素数>30)でも
293    // 誤ってエラーになるバグがあった。そのため再帰深さ `depth` で判定する。
294    if depth > 30 {
295        return Err(make_dom_exception("DataCloneError", "Exceeded maximum call stack size / nesting depth limit"));
296    }
297    let error_proto = match it.global.borrow().vars.get("Error") {
298        Some(Value::Object(ctor)) => match ctor.borrow().props.get("prototype") {
299            Some(Value::Object(p)) => Some(p.clone()),
300            _ => None,
301        },
302        _ => None,
303    };
304    match v {
305        Value::Object(o) => {
306            if let Some((_, cloned)) = seen.iter().find(|(orig, _)| Rc::ptr_eq(orig, o)) {
307                return Ok(Value::Object(cloned.clone()));
308            }
309            // 関数(`ObjKind::NativeFn`/`Closure`)は仕様上そもそも構造化複製
310            // 不可(`DataCloneError`)。以前は例外機構が無いという理由で他の
311            // 型(`Proxy`等)と同様「target へフォワード」する簡略実装が
312            // 検討されたが、関数には複製すべき target が無いため、汎用の
313            // plain object 複製に落ちて「呼出不能な壊れたオブジェクト」に
314            // 静かに化けるだけの実害しかなかった。`structured_clone`側は既に
315            // `Result`を返す口があるため、ここで素直に`DataCloneError`を
316            // 投げるよう修正(2026-07-17 発見・実装)。
317            if o.borrow().is_callable() {
318                return Err(make_dom_exception("DataCloneError", "could not be cloned"));
319            }
320            // `Promise`も仕様上構造化複製不可(`DataCloneError`)。実データが
321            // `.props`ではなく`ObjKind::PromiseObj`側にあるため、他の分岐に
322            // 落ちずここで先に処理しないと「`.props`だけをコピーする plain
323            // object化」に落ちて、`.then`/`.catch`(`ObjKind::PromiseObj`を
324            // 見て動的振り分けする実装のため`.props`には無い)が消えた
325            // 呼出不能な壊れたオブジェクトに静かに化ける(`Proxy`等と同種の
326            // 実害だが、`Proxy`と違い正しい簡略転送先が無いため素直に
327            // `DataCloneError`とする。2026-07-17 発見・実装)。
328            if matches!(&o.borrow().kind, ObjKind::PromiseObj(_)) {
329                return Err(make_dom_exception("DataCloneError", "could not be cloned"));
330            }
331            // `Proxy` は実データが `.props` ではなく `ObjKind::Proxy { target, .. }` 側に
332            // あるため、他の分岐に落ちずここで先に処理しないと「`.props` だけでコピーする
333            // plain object 化」に落ちて、常に空の `{}` に化ける壊れたクローンになる
334            // (`Date`/`Map`/`Set`/`TypedArray`/`Blob` 等で修正済みの「実体が消える」系と
335            // 同種のバグ)。仕様上 `Proxy` はそもそも構造化複製不可(`DataClone` エラー)
336            // だが、この処理系に例外機構の対応する仕組みが無いため、他の Proxy 透過性
337            // 修正(`Array.isArray`/`Object.keys` 等)と同じ「target へフォワード」方針で
338            // target を複製する簡略実装とする。
339            if matches!(&o.borrow().kind, ObjKind::Proxy { .. }) {
340                let target = unwrap_proxy_target(o);
341                let cloned = deep_clone_value_depth(it, &Value::Object(target), seen, depth + 1)?;
342                if let Value::Object(c) = &cloned {
343                    seen.push((o.clone(), c.clone()));
344                }
345                return Ok(cloned);
346            }
347            // `Date`/`RegExp` は実データが `props` ではなく `ObjKind::DateObj`/`RegExpObj`
348            // 側にあるため、他の分岐に落ちずここで先に処理しないと「`.props` だけを
349            // コピーする plain object 化」に落ちて実データの消えた壊れたオブジェクトになる
350            // (`Map`/`Set` で過去に修正した同種のバグ)。`structuredClone(new Date())` が
351            // `{}` になる、`structuredClone(/re/g)` がパターンの消えた空オブジェクトになる、
352            // という実害があった。
353            if let ObjKind::DateObj(ms) = &o.borrow().kind {
354                return Ok(Value::Object(Obj::date_obj(*ms)));
355            }
356            let regex_info = match &o.borrow().kind {
357                ObjKind::RegExpObj(r) => {
358                    let d = r.borrow();
359                    Some((d.re.source.clone(), d.re.flags.clone(), d.last_index))
360                }
361                _ => None,
362            };
363            if let Some((source, flags, last_index)) = regex_info {
364                let re = regex::Regex::new(&source, &flags);
365                return Ok(Value::Object(Obj::regexp(RegExpData { re, last_index })));
366            }
367            let is_array = matches!(&o.borrow().kind, ObjKind::Array(_));
368            if is_array {
369                // TypedArray も内部表現は `ObjKind::Array` を流用しており(`_ta_kind`
370                // タグで種別を区別)、この分岐が `.props` を一切コピーせず素の
371                // `Obj::array` を作るだけだったため、`structuredClone(new Int8Array(...))`
372                // は `_ta_kind`/`BYTES_PER_ELEMENT`/`set`/`slice`/`fill` 等が全て失われた
373                // 「ただの配列」に化けてしまうバグだった(`Date`/`RegExp`/`Map`/`Set` で
374                // 過去に修正した「plain object 化で実体が消える」系と同種)。
375                let ta_kind_tag = o.borrow().props.get("_ta_kind").map(|v| v.to_js_string());
376                if let Some(tag) = ta_kind_tag {
377                    let items = match &o.borrow().kind {
378                        ObjKind::Array(items) => items.iter().map(|v| v.to_number()).collect(),
379                        _ => unreachable!(),
380                    };
381                    let new_val = make_typed_array(it, items, TaKind::from_name(tag.as_str()));
382                    if let Value::Object(new) = &new_val {
383                        seen.push((o.clone(), new.clone()));
384                    }
385                    return Ok(new_val);
386                }
387                let new = Obj::array(Vec::new());
388                seen.push((o.clone(), new.clone()));
389                let items = match &o.borrow().kind {
390                    ObjKind::Array(items) => items.clone(),
391                    _ => unreachable!(),
392                };
393                let mut cloned_items: Vec<Value> = Vec::with_capacity(items.len());
394                for x in &items {
395                    cloned_items.push(deep_clone_value_depth(it, x, seen, depth + 1)?);
396                }
397                if let ObjKind::Array(v) = &mut new.borrow_mut().kind {
398                    *v = cloned_items;
399                }
400                return Ok(Value::Object(new));
401            }
402            // Map/Set は「`.props` だけをコピーする plain object 化」に落ちると、実データが
403            // 入っている ObjKind::MapObj/SetObj 自体は複製されず中身が消えた壊れた
404            // オブジェクトになる実害のあるバグだった(過去に修正済み)。
405            let is_map = matches!(&o.borrow().kind, ObjKind::MapObj(_));
406            if is_map {
407                let new = Obj::map_obj(Vec::new());
408                seen.push((o.clone(), new.clone()));
409                let entries = match &o.borrow().kind {
410                    ObjKind::MapObj(e) => e.clone(),
411                    _ => unreachable!(),
412                };
413                let mut cloned: Vec<(Value, Value)> = Vec::with_capacity(entries.len());
414                for (k, val) in &entries {
415                    let ck = deep_clone_value_depth(it, k, seen, depth + 1)?;
416                    let cv = deep_clone_value_depth(it, val, seen, depth + 1)?;
417                    cloned.push((ck, cv));
418                }
419                if let ObjKind::MapObj(e) = &mut new.borrow_mut().kind {
420                    *e = cloned;
421                }
422                return Ok(Value::Object(new));
423            }
424            let is_set = matches!(&o.borrow().kind, ObjKind::SetObj(_));
425            if is_set {
426                let new = Obj::set_obj(Vec::new());
427                seen.push((o.clone(), new.clone()));
428                let items = match &o.borrow().kind {
429                    ObjKind::SetObj(s) => s.clone(),
430                    _ => unreachable!(),
431                };
432                let mut cloned: Vec<Value> = Vec::with_capacity(items.len());
433                for x in &items {
434                    cloned.push(deep_clone_value_depth(it, x, seen, depth + 1)?);
435                }
436                if let ObjKind::SetObj(s) = &mut new.borrow_mut().kind {
437                    *s = cloned;
438                }
439                return Ok(Value::Object(new));
440            }
441            // `Blob`/`DataView` も `Obj::plain()` + 隠しプロパティで実データを持つ実装
442            // のため、汎用の plain object 複製(全 `.props` を再帰複製)に落ちると、
443            // `slice`/`text`/`arrayBuffer`/`getUint8` 等のネイティブ関数プロパティが
444            // 「関数ではない別の空オブジェクト」として複製されてしまい、複製結果が
445            // 呼出不能な壊れたオブジェクトになるバグだった(`Date`/`RegExp`/`Map`/`Set`/
446            // `TypedArray` で修正済みの「実体が消える」系と同種)。
447            if let Some(Value::Object(bytes_obj)) = o.borrow().props.get("_blob_bytes").cloned() {
448                let bytes: alloc::vec::Vec<f64> = match &bytes_obj.borrow().kind {
449                    ObjKind::Array(items) => items.iter().map(|v| v.to_number()).collect(),
450                    _ => alloc::vec::Vec::new(),
451                };
452                let mime = o.borrow().props.get("type").map(|v| v.to_js_string()).unwrap_or_default();
453                // `File`(`Blob`を継承し`name`/`lastModified`を追加する
454                // `file_ctor`由来のオブジェクト)を複製すると、`Blob`分岐に
455                // 落ちて`name`/`lastModified`が失われ、素の`Blob`へ格下げ
456                // されるバグだった(`structuredClone(new File(...))`
457                // `.name`が`undefined`になる「実体が消える」系の派生バグ。
458                // 2026-07-17 発見・実装)。`name`プロパティの有無を`File`か
459                // `Blob`かの目印として使い、`File`なら追加で引き継ぐ。
460                // `webkitRelativePath`(2026-07-17 追加)も同じ理由で
461                // 一緒に引き継ぐ。
462                let file_meta = o.borrow().props.get("name").cloned().map(|name| {
463                    let last_modified =
464                        o.borrow().props.get("lastModified").cloned().unwrap_or(Value::Number(0.0));
465                    let webkit_relative_path =
466                        o.borrow().props.get("webkitRelativePath").cloned().unwrap_or_else(|| Value::str(""));
467                    (name, last_modified, webkit_relative_path)
468                });
469                let new_val = make_blob(bytes, mime);
470                if let (Value::Object(new), Some((name, last_modified, webkit_relative_path))) = (&new_val, file_meta) {
471                    new.borrow_mut().props.insert("name".into(), name);
472                    new.borrow_mut().props.insert("lastModified".into(), last_modified);
473                    new.borrow_mut().props.insert("webkitRelativePath".into(), webkit_relative_path);
474                }
475                if let Value::Object(new) = &new_val {
476                    seen.push((o.clone(), new.clone()));
477                }
478                return Ok(new_val);
479            }
480            if let Some(Value::Object(bytes_obj)) = o.borrow().props.get("_dv_bytes").cloned() {
481                let byte_values: alloc::vec::Vec<Value> = match &bytes_obj.borrow().kind {
482                    ObjKind::Array(items) => items.clone(),
483                    _ => alloc::vec::Vec::new(),
484                };
485                let new_buffer = make_arraybuffer_with_bytes(byte_values.iter().map(|v| v.to_number()).collect());
486                let new_val = make_dataview(new_buffer, 0, byte_values);
487                if let Value::Object(new) = &new_val {
488                    seen.push((o.clone(), new.clone()));
489                }
490                return Ok(new_val);
491            }
492            // `ArrayBuffer` も同じ理由(`slice` がネイティブ関数プロパティ)で複製が
493            // 壊れる対象だった。`byteLength`+`slice` の組合せを `ArrayBuffer` の目印に
494            // 使う(この2つを両方持つのは `make_arraybuffer()` 由来のオブジェクトのみ)。
495            let is_arraybuffer = {
496                let b = o.borrow();
497                b.props.contains_key("byteLength")
498                    && matches!(b.props.get("slice"), Some(Value::Object(f)) if f.borrow().is_callable())
499            };
500            if is_arraybuffer {
501                // resizable な ArrayBuffer(ES2024。`.resizable`/`.maxByteLength`/
502                // `.resize()`)は複製時に非resizableな新規バッファへ落ちて
503                // いたバグだった(`make_arraybuffer_with_bytes`は常に
504                // 固定長版を作るため、`structuredClone(resizableBuf).resizable`
505                // が`false`になり`.resize()`も呼べなくなる「実体が消える」系の
506                // 派生バグ。2026-07-17 発見)。`make_arraybuffer_ex`で元の
507                // `resizable`/`maxByteLength`を引き継ぐよう修正。
508                let is_resizable = matches!(o.borrow().props.get("resizable"), Some(Value::Bool(true)));
509                let max_byte_length = o.borrow().props.get("maxByteLength").map(|v| v.to_number() as usize);
510                let bytes_prop = o.borrow().props.get("_bytes").cloned();
511                let byte_length = o
512                    .borrow()
513                    .props
514                    .get("byteLength")
515                    .map(|v| v.to_number() as usize)
516                    .unwrap_or(0);
517                let new_val = make_arraybuffer_ex(byte_length, if is_resizable { max_byte_length } else { None });
518                if let (Value::Object(new), Some(Value::Object(bytes_obj))) = (&new_val, &bytes_prop) {
519                    let bytes: Vec<Value> = match &bytes_obj.borrow().kind {
520                        ObjKind::Array(items) => items.clone(),
521                        _ => Vec::new(),
522                    };
523                    new.borrow_mut().props.insert("_bytes".into(), Value::Object(Obj::array(bytes)));
524                }
525                if let Value::Object(new) = &new_val {
526                    seen.push((o.clone(), new.clone()));
527                }
528                return Ok(new_val);
529            }
530            // `Error`(とそのサブタイプ)も同じ理由(汎用 plain object 複製に落ちると
531            // proto が引き継がれない)で複製が壊れるバグだった。`Error` 自体には
532            // 専用の `ObjKind` が無く `message`/`name`/`stack` を持つ通常の `props`
533            // オブジェクトのため、他の型と違って実データが消えるわけではないが、
534            // `structuredClone(new TypeError('x')) instanceof Error` が `false` に
535            // なる(プロトタイプ連鎖が Error.prototype に繋がらない)という実害が
536            // あった(2026-07-14、`Error.prototype.stack` 実装時の点検で発見)。
537            // この処理系は単一レルムのため、元オブジェクトの proto(`TypeError.
538            // prototype` 等)をそのまま複製先へ引き継いでよい。
539            let is_error = error_proto.as_ref().is_some_and(|ep| {
540                let mut cur = o.borrow().proto.clone();
541                let mut guard = 0;
542                while let Some(c) = cur {
543                    if Rc::ptr_eq(&c, ep) {
544                        return true;
545                    }
546                    cur = c.borrow().proto.clone();
547                    guard += 1;
548                    if guard > 1000 {
549                        break;
550                    }
551                }
552                false
553            });
554            let new = Obj::plain();
555            if is_error {
556                new.borrow_mut().proto = o.borrow().proto.clone();
557            }
558            seen.push((o.clone(), new.clone()));
559            let props: Vec<(String, Value)> =
560                o.borrow().props.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
561            for (k, val) in props {
562                let cv = deep_clone_value_depth(it, &val, seen, depth + 1)?;
563                new.borrow_mut().props.insert(k, cv);
564            }
565            Ok(Value::Object(new))
566        }
567        other => Ok(other.clone()),
568    }
569}
570/// `structuredClone(value, {transfer: [...]})` の第2引数 `transfer` が丸ごと
571/// 無視されていた。実際のメモリ移動は行わない簡略実装だが、転送対象の
572/// `ArrayBuffer` の `.detached` を `true` にし内部バイト列を破棄することで
573/// `ArrayBuffer.prototype.transfer()` 自身の「二重 transfer はエラー」という
574/// 既存チェック(`arraybuffer_transfer_impl`)とは整合する状態にする
575/// (`slice()`/`byteLength` 自体が `detached` を見ない、という既存の簡略化は
576/// そのまま踏襲する)。
577pub(crate) fn structured_clone(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
578    let mut seen = Vec::new();
579    let result = deep_clone_value(it, &arg(a, 0), &mut seen)?;
580    if let Value::Object(opts) = arg(a, 1) {
581        let transfer_list = opts.borrow().props.get("transfer").cloned();
582        if let Some(list) = transfer_list {
583            for item in this_items(&list) {
584                if let Value::Object(o) = &item {
585                    let is_arraybuffer = {
586                        let b = o.borrow();
587                        b.props.contains_key("byteLength")
588                            && matches!(b.props.get("slice"), Some(Value::Object(f)) if f.borrow().is_callable())
589                    };
590                    if is_arraybuffer {
591                        let mut b = o.borrow_mut();
592                        b.props.insert("detached".into(), Value::Bool(true));
593                        b.props.shift_remove("_bytes");
594                    }
595                }
596            }
597        }
598    }
599    Ok(result)
600}
601