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