Skip to main content

atmos/os_lib/js/interp/
exec.rs

1// 分割: interp.rs の `impl Interp` から機械的に移動(2026-07-16 リファクタ フェーズ5)。
2// ロジック不変。メソッド可視性のみ pub(crate) へ昇格(別モジュールの impl Interp
3// からの self 呼び出しのため)。impl ブロックは型 Interp に自動で結合する。
4use super::*;
5
6impl Interp {
7    /// ステップを 1 消費。予算切れなら true(中断)。
8    pub(crate) fn tick(&mut self) -> bool {
9        self.steps += 1;
10        if self.steps > self.max_steps {
11            if !self.aborted {
12                self.log_progress("ABORT step budget");
13            }
14            self.aborted = true;
15            return true;
16        }
17        false
18    }
19
20    fn log_progress(&self, what: &str) {
21        let n = self.call_names.len();
22        let top: Vec<&str> = self.call_names.iter().rev().take(4).map(|s| s.as_str()).collect();
23        let (used, _) = crate::kernel::allocator::get_heap_stats();
24        crate::warn!(
25            "[JSPROF] {} steps={} depth={} t={}ms heap={}MB top={:?}",
26            what,
27            self.steps,
28            n,
29            crate::kernel::timer::get_system_time_ms(),
30            used / (1024 * 1024),
31            top
32        );
33    }
34
35    /// ビルトインから使う public な例外生成。
36    pub fn error(&self, msg: impl Into<String>) -> Value {
37        self.throw(msg)
38    }
39
40    /// `it.error()`と同じだが`Error.prototype`ではなく`TypeError.prototype`へ
41    /// proto を繋ぐ(`name`文字列を`"TypeError"`へ書き換えるだけでは
42    /// `instanceof TypeError`が満たされない、`new Node()`等が投げる
43    /// "Illegal constructor" 例外で発覚したバグの修正用に新設。
44    /// 2026-07-18 発見・実装)。
45    pub fn type_error(&self, msg: impl Into<String>) -> Value {
46        self.error_of_kind("TypeError", msg)
47    }
48
49    /// `RangeError`(呼び出しが深すぎる等)。
50    pub fn range_error(&self, msg: impl Into<String>) -> Value {
51        self.error_of_kind("RangeError", msg)
52    }
53
54    /// `kind`(`TypeError`・`RangeError` 等)の例外オブジェクト(proto もその種類へ繋ぐ)。
55    fn error_of_kind(&self, kind: &str, msg: impl Into<String>) -> Value {
56        let err = self.throw(msg);
57        if let Value::Object(o) = &err {
58            let msg_str = o.borrow().props.get("message").map(|v| v.to_js_string()).unwrap_or_default();
59            o.borrow_mut().props.insert(
60                String::from("stack"),
61                Value::str(super::super::builtins::build_error_stack(kind, &msg_str)),
62            );
63            o.borrow_mut().props.insert(String::from("name"), Value::str(kind));
64            if let Some(Value::Object(ctor)) = self.global.borrow().vars.get(kind) {
65                if let Some(Value::Object(proto)) = ctor.borrow().props.get("prototype") {
66                    o.borrow_mut().proto = Some(proto.clone());
67                }
68            }
69        }
70        err
71    }
72
73    pub(crate) fn throw(&self, msg: impl Into<String>) -> Value {
74        // 簡易 Error オブジェクト(message プロパティ付き)。
75        let o = Obj::plain();
76        let msg: String = msg.into();
77        // `Error.prototype.stack`(丸ごと未対応だった。ツリーウォーク評価器
78        // なので実際のコールフレーム一覧は再現できず、V8 の1行目と同じ
79        // `"Error: message"` 形式の簡略実装。詳細は `builtins::
80        // build_error_stack` 参照。ここはエンジン内部が直接投げる大半の
81        // 例外(TypeError相当のメッセージ等)の共通経路のため、
82        // `set_error_props`(`new TypeError()` 等の明示コンストラクタ経由)
83        // とは別にここでも設定する)。
84        // 【2026-09-26】呼び出し中の関数名を V8 と同じ `    at 名前` 形式で積む(内側から、最大 12 段)。
85        let mut stack_str = if msg.is_empty() {
86            String::from("Error")
87        } else {
88            alloc::format!("Error: {msg}")
89        };
90        for name in self.call_names.iter().rev().take(12) {
91            stack_str.push_str("\n    at ");
92            stack_str.push_str(name);
93        }
94        o.borrow_mut().props.insert(String::from("stack"), Value::str(stack_str));
95        o.borrow_mut()
96            .props
97            .insert(String::from("message"), Value::str(msg));
98        o.borrow_mut()
99            .props
100            .insert(String::from("name"), Value::str("Error"));
101        // グローバルの `Error` コンストラクタが持つ `.prototype` に proto を繋ぐ。
102        // これによりエンジン内部から投げるエラー("X is not defined" 等)も
103        // `catch (e) { e instanceof Error }` で true になる。
104        if let Some(Value::Object(ctor)) = self.global.borrow().vars.get("Error") {
105            if let Some(Value::Object(proto)) = ctor.borrow().props.get("prototype") {
106                o.borrow_mut().proto = Some(proto.clone());
107            }
108        }
109        Value::Object(o)
110    }
111
112    // ============ 文 ============
113
114    /// グローバル `eval(source)` / `Function(...)` の実体。仕様上 direct eval は呼び出し元の
115    /// レキシカルスコープを見るが、この処理系は常にグローバルスコープで実行する簡略実装
116    /// (indirect eval `(0,eval)(code)` と同じセマンティクス)。
117    pub(crate) fn eval_source(&mut self, source: &str) -> Result<Value, Value> {
118        let mut parser = Parser::new_lazy(Lexer::new(source));
119        let program = parser.parse_program();
120        // `eval()` に渡されたソースの構文エラーも黙って捨てない
121        // (本来 SyntaxError を throw すべき箇所だが、既存のベストエフォート
122        //  実行を壊さないため、まずは可視化にとどめる)。
123        let errors = parser.error_count();
124        if errors > 0 {
125            crate::warn!("[JS] eval(): {} syntax error(s) recovered", errors);
126            for e in parser.errors().iter().take(3) {
127                crate::warn!("[JS] eval() SyntaxError: {}", e.message);
128            }
129        }
130        let scope = self.global.clone();
131        // 間接 eval / `Function` 相当のグローバル実行では `this` は window。
132        let global_this = self.global.borrow().vars.get("window").cloned().unwrap_or(Value::Undefined);
133        match self.exec_statements(&program.body, &scope, &global_this) {
134            Ok(Completion::Normal(v)) | Ok(Completion::Return(v)) => Ok(v),
135            Ok(_) => Ok(Value::Undefined),
136            Err(thrown) => Err(thrown),
137        }
138    }
139
140    pub(crate) fn exec_statements(
141        &mut self,
142        stmts: &[Statement],
143        scope: &Rc<RefCell<Scope>>,
144        this: &Value,
145    ) -> Result<Completion, Value> {
146        // 【2026-10-16】`var` の巻き上げ(ECMA-262 §10.2.11 FunctionDeclarationInstantiation /
147        // §16.1.7 GlobalDeclarationInstantiation)。関数(またはプログラム)の本体に入った時点で、
148        // 本体のどこに書かれた `var` も(ブロック・ループ・try の中を含み、入れ子の関数は除く)
149        // undefined で宣言しておく。従来は `var` 文に達した時点で宣言していたため、
150        // 宣言より前の代入が外側の同名変数を書き換えた。Cloudflare の beacon.min.js の
151        // `function _t(){ ... bi:(a=st, ...) ...; var a; ... }` が外側の関数 `a` をオブジェクトで
152        // 上書きし、後の `a(...)` が `a is not a function` で落ちていた(sugi-lab.net 全ページ)。
153        // 既に同名がある(引数・巻き上げ済みの関数・前のスクリプトの var)なら触らない。
154        if !scope.borrow().is_block {
155            let mut names: Vec<String> = Vec::new();
156            collect_var_names(stmts, &mut names);
157            if !names.is_empty() {
158                // グローバル(プログラム)では var と window のプロパティは同じ束縛なので、
159                // `window.x` が既にあればその値で宣言する(`window.$` を undefined で隠さない)。
160                let is_global = scope.borrow().parent.is_none();
161                let window = if is_global {
162                    match scope_get(scope, "window") {
163                        Some(Value::Object(w)) => Some(w),
164                        _ => None,
165                    }
166                } else {
167                    None
168                };
169                let mut sc = scope.borrow_mut();
170                for n in names {
171                    if sc.vars.contains_key(&n) {
172                        continue;
173                    }
174                    let init = window
175                        .as_ref()
176                        .and_then(|w| w.borrow().props.get(n.as_str()).cloned())
177                        .unwrap_or(Value::Undefined);
178                    sc.vars.insert(n, init);
179                }
180            }
181        }
182        // 関数宣言の巻き上げ(`export function` でラップされたものも対象)。
183        for s in stmts {
184            let fdecl = match s {
185                Statement::FunctionDeclaration { .. } => Some(s),
186                Statement::ExportDecl { declaration, .. }
187                    if matches!(**declaration, Statement::FunctionDeclaration { .. }) =>
188                {
189                    Some(&**declaration)
190                }
191                _ => None,
192            };
193            if let Some(Statement::FunctionDeclaration {
194                name,
195                params,
196                body,
197                is_async,
198                is_generator,
199            }) = fdecl
200            {
201                let f = self.make_function(
202                    Some(name.clone()),
203                    params.clone(),
204                    body.clone(),
205                    false,
206                    *is_async,
207                    *is_generator,
208                    scope,
209                    this,
210                );
211                // ブロック内の関数宣言は、ブラウザ互換(Annex B.3.3)として関数側からも見える。
212                if scope.borrow().is_block {
213                    scope_declare(&var_scope(scope), name, f.clone());
214                }
215                scope_declare(scope, name, f);
216            }
217        }
218        // `using`/`await using`(Explicit Resource Management): このステートメントリストの
219        // 実行が(正常終了・break/continue/return・例外のいずれで)終わっても、宣言の逆順で
220        // `[Symbol.dispose]`/`[Symbol.asyncDispose]` を呼ぶ。Phase 1 の簡略実装につき、
221        // 本来の「ブロックスコープ終了時」ではなく「この文リスト(関数本体/ブロック/
222        // プログラム本体)の実行終了時」を単位として扱う(ブロックが独自スコープを持たない
223        // という既存の制約と同じ粒度)。
224        let mut disposables: Vec<(Value, bool)> = Vec::new();
225        let mut last = Value::Undefined;
226        let mut outcome: Result<Completion, Value> = Ok(Completion::Normal(Value::Undefined));
227        for s in stmts {
228            if let Statement::VarDeclaration { kind, decls } = s {
229                if matches!(kind, VarKind::Using | VarKind::AwaitUsing) {
230                    let is_await = matches!(kind, VarKind::AwaitUsing);
231                    let mut bind_err = None;
232                    for (pat, init) in decls {
233                        let v = match init {
234                            Some(e) => match self.eval(e, scope, this) {
235                                Ok(v) => v,
236                                Err(e) => {
237                                    bind_err = Some(e);
238                                    break;
239                                }
240                            },
241                            None => Value::Undefined,
242                        };
243                        if let Err(e) = self.bind_pattern(pat, v.clone(), scope, this) {
244                            bind_err = Some(e);
245                            break;
246                        }
247                        if !matches!(v, Value::Undefined | Value::Null) {
248                            disposables.push((v, is_await));
249                        }
250                    }
251                    if let Some(e) = bind_err {
252                        outcome = Err(e);
253                        break;
254                    }
255                    last = Value::Undefined;
256                    continue;
257                }
258            }
259            match self.exec_stmt(s, scope, this) {
260                Ok(Completion::Normal(v)) => last = v,
261                Ok(other) => {
262                    outcome = Ok(other);
263                    break;
264                }
265                Err(e) => {
266                    outcome = Err(e);
267                    break;
268                }
269            }
270        }
271        if disposables.is_empty() {
272            return match outcome {
273                Ok(Completion::Normal(_)) => Ok(Completion::Normal(last)),
274                other => other,
275            };
276        }
277        // 宣言と逆順で dispose する。dispose 自体が投げた例外は、既存の outcome が
278        // 正常系ならそのまま採用し、既に別の例外が保留中なら `SuppressedError`
279        // (新しい例外を error、既存の保留中の例外を suppressed として包む)へ差し替える
280        // ことで、仕様どおり複数のエラーを1つのチェーンに集約する。
281        let mut pending_err: Option<Value> = match &outcome {
282            Err(e) => Some(e.clone()),
283            _ => None,
284        };
285        for (resource, is_await) in disposables.into_iter().rev() {
286            let key = if is_await {
287                "Symbol(Symbol.asyncDispose)"
288            } else {
289                "Symbol(Symbol.dispose)"
290            };
291            let method = match self.get_property(&resource, key) {
292                Ok(m) => m,
293                Err(e) => {
294                    pending_err = Some(self.chain_suppressed(pending_err, e));
295                    continue;
296                }
297            };
298            if matches!(method, Value::Undefined | Value::Null) {
299                continue;
300            }
301            let call_result = self.call_value(&method, resource, &[]);
302            let dispose_result = match call_result {
303                Ok(v) if is_await => self.await_value(v),
304                other => other,
305            };
306            if let Err(e) = dispose_result {
307                pending_err = Some(self.chain_suppressed(pending_err, e));
308            }
309        }
310        if let Some(e) = pending_err {
311            return Err(e);
312        }
313        match outcome {
314            Ok(Completion::Normal(_)) => Ok(Completion::Normal(last)),
315            other => other,
316        }
317    }
318
319    pub(crate) fn exec_stmt(
320        &mut self,
321        stmt: &Statement,
322        scope: &Rc<RefCell<Scope>>,
323        this: &Value,
324    ) -> Result<Completion, Value> {
325        if self.tick() {
326            return Err(Value::Undefined);
327        }
328        match stmt {
329            Statement::Empty => Ok(Completion::Normal(Value::Undefined)),
330            Statement::Expression(e) => {
331                let v = self.eval(e, scope, this)?;
332                Ok(Completion::Normal(v))
333            }
334            Statement::VarDeclaration { kind, decls } => {
335                // `var` はブロックを素通りして関数(プログラム)のスコープへ。
336                let target = if matches!(kind, VarKind::Var) { var_scope(scope) } else { scope.clone() };
337                for (pat, init) in decls {
338                    // 初期化子の無い `var x;` は既存の値を消さない(再宣言は何もしない)。
339                    if init.is_none() && matches!(kind, VarKind::Var) {
340                        if let Pattern::Identifier(name) = pat {
341                            if target.borrow().vars.contains_key(name.as_str()) {
342                                continue;
343                            }
344                        }
345                    }
346                    let v = match init {
347                        Some(e) => self.eval(e, scope, this)?,
348                        None => Value::Undefined,
349                    };
350                    // NamedEvaluation(ES2015): `const f = function(){}`/`const f = () => {}`
351                    // のような無名関数式を単純な識別子へ代入する場合、仕様上 `f.name` は
352                    // その識別子名を継承する。以前はこの推論が一切無く、`.name` が常に
353                    // 空文字列のままだった(デバッグ表示やスタックトレース、`.name` を
354                    // 参照するライブラリで無名関数が区別できない不便があった)。
355                    if let Pattern::Identifier(name) = pat {
356                        infer_function_name(&v, name);
357                    }
358                    self.bind_pattern(pat, v, &target, this)?;
359                }
360                Ok(Completion::Normal(Value::Undefined))
361            }
362            Statement::Block(body) => {
363                // 【2026-10-03】`let`/`const` を宣言するブロックは自前のスコープを持つ。
364                // 従来はすべて外側と共有で、`if(l){const t=...}` の `t` が関数全体の `t` を
365                // 上書きしていた(Cloudflare の beacon.min.js が `t.SpaMonitoringType` で落ちていた)。
366                if block_has_lexical(body) {
367                    let inner = Scope::block(scope.clone());
368                    self.exec_statements(body, &inner, this)
369                } else {
370                    self.exec_statements(body, scope, this)
371                }
372            }
373            Statement::FunctionDeclaration { .. } => {
374                // 既に巻き上げ済み。
375                Ok(Completion::Normal(Value::Undefined))
376            }
377            Statement::If {
378                test,
379                consequent,
380                alternate,
381            } => {
382                if self.eval(test, scope, this)?.truthy() {
383                    self.exec_stmt(consequent, scope, this)
384                } else if let Some(alt) = alternate {
385                    self.exec_stmt(alt, scope, this)
386                } else {
387                    Ok(Completion::Normal(Value::Undefined))
388                }
389            }
390            Statement::While { test, body } => {
391                let own_label = self.pending_label.take();
392                while self.eval(test, scope, this)?.truthy() {
393                    if self.tick() {
394                        return Err(Value::Undefined);
395                    }
396                    match self.exec_stmt(body, scope, this)? {
397                        Completion::Break(lbl) if lbl.is_none() || lbl == own_label => break,
398                        Completion::Break(lbl) => return Ok(Completion::Break(lbl)),
399                        Completion::Continue(lbl) if lbl.is_none() || lbl == own_label => {}
400                        Completion::Continue(lbl) => return Ok(Completion::Continue(lbl)),
401                        Completion::Normal(_) => {}
402                        ret @ Completion::Return(_) => return Ok(ret),
403                    }
404                }
405                Ok(Completion::Normal(Value::Undefined))
406            }
407            Statement::DoWhile { body, test } => {
408                let own_label = self.pending_label.take();
409                loop {
410                    if self.tick() {
411                        return Err(Value::Undefined);
412                    }
413                    match self.exec_stmt(body, scope, this)? {
414                        Completion::Break(lbl) if lbl.is_none() || lbl == own_label => break,
415                        Completion::Break(lbl) => return Ok(Completion::Break(lbl)),
416                        Completion::Continue(lbl) if lbl.is_none() || lbl == own_label => {}
417                        Completion::Continue(lbl) => return Ok(Completion::Continue(lbl)),
418                        Completion::Normal(_) => {}
419                        ret @ Completion::Return(_) => return Ok(ret),
420                    }
421                    if !self.eval(test, scope, this)?.truthy() {
422                        break;
423                    }
424                }
425                Ok(Completion::Normal(Value::Undefined))
426            }
427            Statement::For {
428                init,
429                test,
430                update,
431                body,
432            } => {
433                let own_label = self.pending_label.take();
434                // `for (let i=0; ...)` は仕様上「反復ごとの束縛」(per-iteration binding)
435                // が必須で、各反復の本体内で作られたクロージャはその反復時点の値を捕捉
436                // しなければならない。以前は `init`/`test`/`body`/`update` を通して単一の
437                // 共有スコープしか使っておらず、`for(let i=0;i<3;i++){arr.push(()=>i)}` の
438                // ようなクロージャが全て同じ最終値(3)を見てしまうバグだった
439                // (`var` はこの per-iteration 束縛の対象外で、以前と同じ単一スコープの
440                // ままで仕様どおり)。
441                let is_let_like = matches!(
442                    init.as_deref(),
443                    Some(Statement::VarDeclaration {
444                        kind: VarKind::Let | VarKind::Const,
445                        ..
446                    })
447                );
448                let mut loop_scope = if is_let_like {
449                    Scope::block(scope.clone())
450                } else {
451                    scope.clone()
452                };
453                if let Some(init) = init {
454                    self.exec_stmt(init, &loop_scope, this)?;
455                }
456                let let_names: Vec<String> = if is_let_like {
457                    loop_scope.borrow().vars.keys().cloned().collect()
458                } else {
459                    Vec::new()
460                };
461                loop {
462                    if let Some(t) = test {
463                        if !self.eval(t, &loop_scope, this)?.truthy() {
464                            break;
465                        }
466                    }
467                    if self.tick() {
468                        return Err(Value::Undefined);
469                    }
470                    match self.exec_stmt(body, &loop_scope, this)? {
471                        Completion::Break(lbl) if lbl.is_none() || lbl == own_label => break,
472                        Completion::Break(lbl) => return Ok(Completion::Break(lbl)),
473                        Completion::Continue(lbl) if lbl.is_none() || lbl == own_label => {}
474                        Completion::Continue(lbl) => return Ok(Completion::Continue(lbl)),
475                        Completion::Normal(_) => {}
476                        ret @ Completion::Return(_) => return Ok(ret),
477                    }
478                    if is_let_like {
479                        let next_scope = Scope::block(scope.clone());
480                        for name in &let_names {
481                            let v = scope_get(&loop_scope, name).unwrap_or(Value::Undefined);
482                            next_scope.borrow_mut().vars.insert(name.clone(), v);
483                        }
484                        loop_scope = next_scope;
485                    }
486                    if let Some(u) = update {
487                        self.eval(u, &loop_scope, this)?;
488                    }
489                }
490                Ok(Completion::Normal(Value::Undefined))
491            }
492            Statement::ForIn {
493                decl_kind,
494                pattern,
495                object,
496                body,
497                of,
498                is_await,
499            } => {
500                let own_label = self.pending_label.take();
501                // `for (let x of iterable)` も C 形式の `for` と同じ「反復ごとの束縛」が
502                // 必須(`var`/宣言無しの代入先は対象外)。以前は毎回同じ `scope` へ
503                // `bind_pattern` していたため、本体で作ったクロージャが全て最終値を
504                // 捕捉するバグだった。
505                let is_let_like = matches!(decl_kind, Some(VarKind::Let) | Some(VarKind::Const));
506                let obj = self.eval(object, scope, this)?;
507                // for await (x of obj): Symbol.asyncIterator があれば手動で駆動(next() の
508                // 戻り値を await し {value, done} を読む)。無ければ同期イテラブルへ
509                // フォールバックし各要素を await する(配列などの Promise 混在を想定)。
510                if *of && *is_await {
511                    let async_iter_method =
512                        self.get_property(&obj, "Symbol(Symbol.asyncIterator)")?;
513                    if !matches!(async_iter_method, Value::Undefined | Value::Null) {
514                        let iterator = self.call_value(&async_iter_method, obj, &[])?;
515                        loop {
516                            if self.tick() {
517                                return Err(Value::Undefined);
518                            }
519                            let next_fn = self.get_property(&iterator, "next")?;
520                            let result = self.call_value(&next_fn, iterator.clone(), &[])?;
521                            let awaited = self.await_value(result)?;
522                            let done = self.get_property(&awaited, "done")?.truthy();
523                            if done {
524                                break;
525                            }
526                            let value = self.get_property(&awaited, "value")?;
527                            let iter_scope = if is_let_like { Scope::block(scope.clone()) } else { scope.clone() };
528                            self.bind_pattern(pattern, value, &iter_scope, this)?;
529                            match self.exec_stmt(body, &iter_scope, this)? {
530                                Completion::Break(lbl) if lbl.is_none() || lbl == own_label => {
531                                    break
532                                }
533                                Completion::Break(lbl) => return Ok(Completion::Break(lbl)),
534                                Completion::Continue(lbl)
535                                    if lbl.is_none() || lbl == own_label => {}
536                                Completion::Continue(lbl) => return Ok(Completion::Continue(lbl)),
537                                Completion::Normal(_) => {}
538                                ret @ Completion::Return(_) => return Ok(ret),
539                            }
540                        }
541                        return Ok(Completion::Normal(Value::Undefined));
542                    }
543                    let items = self.iterate_values(&obj, true);
544                    for item in items {
545                        if self.tick() {
546                            return Err(Value::Undefined);
547                        }
548                        let awaited = self.await_value(item)?;
549                        let iter_scope = if is_let_like { Scope::block(scope.clone()) } else { scope.clone() };
550                        self.bind_pattern(pattern, awaited, &iter_scope, this)?;
551                        match self.exec_stmt(body, &iter_scope, this)? {
552                            Completion::Break(lbl) if lbl.is_none() || lbl == own_label => break,
553                            Completion::Break(lbl) => return Ok(Completion::Break(lbl)),
554                            Completion::Continue(lbl) if lbl.is_none() || lbl == own_label => {}
555                            Completion::Continue(lbl) => return Ok(Completion::Continue(lbl)),
556                            Completion::Normal(_) => {}
557                            ret @ Completion::Return(_) => return Ok(ret),
558                        }
559                    }
560                    return Ok(Completion::Normal(Value::Undefined));
561                }
562                let items = self.iterate_values(&obj, *of);
563                for item in items {
564                    if self.tick() {
565                        return Err(Value::Undefined);
566                    }
567                    let iter_scope = if is_let_like { Scope::block(scope.clone()) } else { scope.clone() };
568                    self.bind_pattern(pattern, item, &iter_scope, this)?;
569                    match self.exec_stmt(body, &iter_scope, this)? {
570                        Completion::Break(lbl) if lbl.is_none() || lbl == own_label => break,
571                        Completion::Break(lbl) => return Ok(Completion::Break(lbl)),
572                        Completion::Continue(lbl) if lbl.is_none() || lbl == own_label => {}
573                        Completion::Continue(lbl) => return Ok(Completion::Continue(lbl)),
574                        Completion::Normal(_) => {}
575                        ret @ Completion::Return(_) => return Ok(ret),
576                    }
577                }
578                Ok(Completion::Normal(Value::Undefined))
579            }
580            Statement::Return(e) => {
581                let v = match e {
582                    Some(e) => self.eval(e, scope, this)?,
583                    None => Value::Undefined,
584                };
585                Ok(Completion::Return(v))
586            }
587            Statement::Break(label) => Ok(Completion::Break(label.clone())),
588            Statement::Continue(label) => Ok(Completion::Continue(label.clone())),
589            Statement::Labeled(label, inner) => {
590                // このラベルが(直下の)ループ自身のものであることをループ実行側へ伝える。
591                // ループはその実行開始直後に `take()` して own_label として使う。
592                // ループ以外(`a: { … }` 等)に付いたラベルは保留しない。
593                // 【2026-10-20】保留したままだとブロック内の最初のループがそのラベルを
594                // 自分のものとして取り、`break a` をそのループで消費していた(YouTube の
595                // スケジューラ `a:{for(…)for(…){…;c=Z;break a}c=null}` で優先度 3 以下の
596                // ジョブが取り出されたまま実行されず、ytd-app が起動しなかった)。
597                self.pending_label = if matches!(
598                    &**inner,
599                    Statement::While { .. } | Statement::DoWhile { .. } | Statement::For { .. } | Statement::ForIn { .. }
600                ) {
601                    Some(label.clone())
602                } else {
603                    None
604                };
605                match self.exec_stmt(inner, scope, this)? {
606                    // 自ラベル宛ての break はここで消費して正常終了に変換する
607                    // (ループではない `label: { ... }` のようなブロックの break にも対応)。
608                    Completion::Break(Some(lbl)) if lbl == *label => {
609                        Ok(Completion::Normal(Value::Undefined))
610                    }
611                    other => Ok(other),
612                }
613            }
614            Statement::Switch {
615                discriminant,
616                cases,
617            } => {
618                let d = self.eval(discriminant, scope, this)?;
619                // マッチする case を探す。無ければ default。フォールスルーあり。
620                let mut start = None;
621                for (i, c) in cases.iter().enumerate() {
622                    if let Some(test) = &c.test {
623                        let tv = self.eval(test, scope, this)?;
624                        if d.strict_eq(&tv) {
625                            start = Some(i);
626                            break;
627                        }
628                    }
629                }
630                if start.is_none() {
631                    start = cases.iter().position(|c| c.test.is_none());
632                }
633                if let Some(s) = start {
634                    for c in &cases[s..] {
635                        match self.exec_statements(&c.body, scope, this)? {
636                            // ラベル無しの break のみこの switch を抜ける。ラベル付き break は
637                            // この switch 宛てではない(外側のラベル付きループ/ブロック宛て)ため
638                            // 上位へ伝播する。
639                            Completion::Break(None) => {
640                                return Ok(Completion::Normal(Value::Undefined))
641                            }
642                            Completion::Normal(_) => {} // フォールスルー
643                            other => return Ok(other), // Return/Continue/ラベル付きBreak は上位へ
644                        }
645                    }
646                }
647                Ok(Completion::Normal(Value::Undefined))
648            }
649            Statement::Throw(e) => {
650                let v = self.eval(e, scope, this)?;
651                Err(v)
652            }
653            Statement::Try {
654                block,
655                catch_param,
656                catch_block,
657                finally_block,
658            } => {
659                let result = self.exec_statements(block, scope, this);
660                let after_catch = match result {
661                    // suspend 番兵(generator 中断)および強制Return例外は捕捉せず最上位の resume まで巻き戻す。
662                    Err(thrown) if !self.aborted && !self.is_suspending() && !self.is_gen_returning() => {
663                        if let Some(cb) = catch_block {
664                            { // TMPCATCH 一時診断
665                                static N: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
666                                if N.fetch_add(1, core::sync::atomic::Ordering::Relaxed) < 120 {
667                                    let top: Vec<String> = self.call_names.iter().rev().take(8).cloned().collect();
668                                    let msg = super::calls::describe_thrown(&thrown, 1);
669                                    let first = msg.lines().take(2).collect::<Vec<_>>().join(" / ").chars().take(220).collect::<String>();
670                                    crate::warn!("[TMPCATCH] {} at {:?}", first, top);
671                                }
672                            }
673                            // catch の引数と本体は自前のスコープ(`catch(t){}` が外の `t` を壊さない)。
674                            let inner = Scope::block(scope.clone());
675                            if let Some(p) = catch_param {
676                                self.bind_pattern(p, thrown, &inner, this)?;
677                            }
678                            self.exec_statements(cb, &inner, this)
679                        } else {
680                            Err(thrown)
681                        }
682                    }
683                    other => other,
684                };
685                // generator 中断中は finally を実行せず巻き戻す(resume 時に replay される)。
686                if self.is_suspending() {
687                    return after_catch;
688                }
689                if let Some(fb) = finally_block {
690                    // finally は常に実行。finally が return/throw すればそれが優先。
691                    let fin = self.exec_statements(fb, scope, this)?;
692                    if let Completion::Return(_) | Completion::Break(_) | Completion::Continue(_) = fin {
693                        if let Some(r) = &mut self.gen_replay {
694                            r.returning = None;
695                        }
696                        return Ok(fin);
697                    }
698                }
699                after_catch
700            }
701            Statement::Import {
702                source,
703                default,
704                namespace,
705                named,
706                side_effect_only,
707            } => {
708                let exports = self.load_module(source)?;
709                if !side_effect_only {
710                    let exp = exports.borrow();
711                    if let Some(local) = default {
712                        let v = exp.get("default").cloned().unwrap_or(Value::Undefined);
713                        scope_declare(scope, local, v);
714                    }
715                    if let Some(ns) = namespace {
716                        // 名前空間オブジェクト: 全 export を props に持つ plain オブジェクト。
717                        let o = Obj::plain();
718                        {
719                            let mut ob = o.borrow_mut();
720                            for (k, v) in exp.iter() {
721                                ob.props.insert(k.clone(), v.clone());
722                            }
723                        }
724                        scope_declare(scope, ns, Value::Object(o));
725                    }
726                    for (orig, local) in named {
727                        let v = exp.get(orig).cloned().unwrap_or(Value::Undefined);
728                        scope_declare(scope, local, v);
729                    }
730                }
731                Ok(Completion::Normal(Value::Undefined))
732            }
733            Statement::ExportDefault(expr) => {
734                let v = self.eval(expr, scope, this)?;
735                self.record_export(scope, "default", v);
736                Ok(Completion::Normal(Value::Undefined))
737            }
738            Statement::ExportDecl { declaration, names } => {
739                let c = self.exec_stmt(declaration, scope, this)?;
740                // 宣言実行後、対象名をスコープから引いて export に転記する。
741                for name in names {
742                    let v = scope_get(scope, name).unwrap_or(Value::Undefined);
743                    self.record_export(scope, name, v);
744                }
745                Ok(c)
746            }
747            Statement::ExportNamed { specifiers, source } => {
748                match source {
749                    // 再エクスポート: `export { a, b as c } from "mod"`。
750                    Some(src) => {
751                        let exports = self.load_module(src)?;
752                        let exp = exports.borrow();
753                        for (orig, public) in specifiers {
754                            let v = exp.get(orig).cloned().unwrap_or(Value::Undefined);
755                            self.record_export(scope, public, v);
756                        }
757                    }
758                    // 自モジュールのローカル束縛を公開: `export { a, b as c }`。
759                    None => {
760                        for (local, public) in specifiers {
761                            let v = scope_get(scope, local).unwrap_or(Value::Undefined);
762                            self.record_export(scope, public, v);
763                        }
764                    }
765                }
766                Ok(Completion::Normal(Value::Undefined))
767            }
768            Statement::ExportAll { source } => {
769                let exports = self.load_module(source)?;
770                let exp = exports.borrow();
771                for (k, v) in exp.iter() {
772                    // `export *` は default を再エクスポートしない。
773                    if k != "default" {
774                        self.record_export(scope, k, v.clone());
775                    }
776                }
777                Ok(Completion::Normal(Value::Undefined))
778            }
779        }
780    }
781
782    /// 現在評価中モジュールの export マップにエントリを記録する。
783    /// モジュール文脈でない(通常スクリプト)の場合は無視する。
784    pub(crate) fn record_export(&mut self, scope: &Rc<RefCell<Scope>>, name: &str, val: Value) {
785        // モジュールの export マップは評価対象スコープに番兵キーで保持される。
786        if let Some(Value::Object(o)) = scope_get(scope, MODULE_EXPORTS_KEY) {
787            o.borrow_mut().props.insert(String::from(name), val);
788        }
789    }
790
791    /// 指定子のモジュールを(必要なら)評価し、export マップを返す。
792    /// 循環 import に備え、評価開始前に空マップをレジストリへ入れておく。
793    pub(crate) fn load_module(
794        &mut self,
795        specifier: &str,
796    ) -> Result<Rc<RefCell<BTreeMap<String, Value>>>, Value> {
797        // 既に評価済みならそのまま返す。
798        if let Some(rec) = self.modules.borrow().get(specifier) {
799            if let Some(ex) = &rec.exports {
800                return Ok(ex.clone());
801            }
802        }
803        // ソースを取り出す(未登録なら空エクスポートのモジュール扱い)。
804        let source = match self.modules.borrow().get(specifier) {
805            Some(rec) => rec.source.clone(),
806            None => {
807                let empty = Rc::new(RefCell::new(BTreeMap::new()));
808                return Ok(empty);
809            }
810        };
811        // 循環対策: 評価開始前に空マップを登録。
812        let exports = Rc::new(RefCell::new(BTreeMap::new()));
813        if let Some(rec) = self.modules.borrow_mut().get_mut(specifier) {
814            rec.exports = Some(exports.clone());
815        }
816        // モジュール本体を専用スコープで評価。export 文の記録先として
817        // exports マップを props に持つ plain オブジェクトを番兵キーで束縛する。
818        let module_scope = Scope::child(self.global.clone());
819        let collector = Obj::plain();
820        scope_declare(
821            &module_scope,
822            MODULE_EXPORTS_KEY,
823            Value::Object(collector.clone()),
824        );
825        let program = Parser::new_lazy(Lexer::new(&source)).parse_program();
826        let _ = self.exec_statements(&program.body, &module_scope, &Value::Undefined)?;
827        // collector の props を exports マップへ確定。
828        {
829            let cb = collector.borrow();
830            let mut ex = exports.borrow_mut();
831            for (k, v) in cb.props.iter() {
832                ex.insert(k.clone(), v.clone());
833            }
834        }
835        Ok(exports)
836    }
837
838}
839
840/// 文の並びから `var` で宣言される名前を集める(巻き上げ用)。ブロック・制御構文・
841/// `try`/`switch`/ラベルの中へは入り、入れ子の関数(宣言・式)の本体へは入らない。
842pub(crate) fn collect_var_names(stmts: &[Statement], out: &mut Vec<String>) {
843    for s in stmts {
844        collect_var_names_stmt(s, out);
845    }
846}
847
848fn collect_var_names_stmt(s: &Statement, out: &mut Vec<String>) {
849    match s {
850        Statement::VarDeclaration { kind: VarKind::Var, decls, .. } => {
851            for (pat, _) in decls {
852                collect_pattern_names(pat, out);
853            }
854        }
855        Statement::Block(body) => collect_var_names(body, out),
856        Statement::If { consequent, alternate, .. } => {
857            collect_var_names_stmt(consequent, out);
858            if let Some(a) = alternate {
859                collect_var_names_stmt(a, out);
860            }
861        }
862        Statement::While { body, .. } | Statement::DoWhile { body, .. } => collect_var_names_stmt(body, out),
863        Statement::For { init, body, .. } => {
864            if let Some(i) = init {
865                collect_var_names_stmt(i, out);
866            }
867            collect_var_names_stmt(body, out);
868        }
869        Statement::ForIn { decl_kind, pattern, body, .. } => {
870            if matches!(decl_kind, Some(VarKind::Var)) {
871                collect_pattern_names(pattern, out);
872            }
873            collect_var_names_stmt(body, out);
874        }
875        Statement::Labeled(_, body) => collect_var_names_stmt(body, out),
876        Statement::Switch { cases, .. } => {
877            for c in cases {
878                collect_var_names(&c.body, out);
879            }
880        }
881        Statement::Try { block, catch_block, finally_block, .. } => {
882            collect_var_names(block, out);
883            if let Some(b) = catch_block {
884                collect_var_names(b, out);
885            }
886            if let Some(b) = finally_block {
887                collect_var_names(b, out);
888            }
889        }
890        Statement::ExportDecl { declaration, .. } => collect_var_names_stmt(declaration, out),
891        _ => {}
892    }
893}
894
895fn collect_pattern_names(p: &Pattern, out: &mut Vec<String>) {
896    match p {
897        Pattern::Identifier(n) => {
898            if !out.contains(n) {
899                out.push(n.clone());
900            }
901        }
902        Pattern::Object(props) => {
903            for pr in props {
904                collect_pattern_names(&pr.value, out);
905            }
906        }
907        Pattern::Array(elems) => {
908            for e in elems {
909                if let Some(pat) = &e.pattern {
910                    collect_pattern_names(pat, out);
911                }
912            }
913        }
914        Pattern::Expr(_) => {}
915    }
916}