diff --git a/crates/js/src/builtins.rs b/crates/js/src/builtins.rs index 2228fac5a..90dc0d060 100644 --- a/crates/js/src/builtins.rs +++ b/crates/js/src/builtins.rs @@ -41,7 +41,8 @@ pub(crate) fn constructor(interp: &mut Interp, name: &'static str, ctor: ObjId, data(interp, global, name, Value::Object(ctor), HIDDEN) } -/// Creates the built-ins and the global object's properties. +/// Creates the built-ins and the global object's properties. The caller installs them before it +/// sets the realm's limits, so no limit stops the install. pub(crate) fn install(interp: &mut Interp) { if let Err(e) = try_install(interp) { // Installing touches only objects it has just made, so this does not happen; a realm diff --git a/crates/js/src/interp.rs b/crates/js/src/interp.rs index 895630fd7..9e72285f1 100644 --- a/crates/js/src/interp.rs +++ b/crates/js/src/interp.rs @@ -330,8 +330,10 @@ impl Interp { global, global_env, intrinsics: Intrinsics::placeholder(), - fuel: limits.fuel, - limits, + fuel: u64::MAX, + // The built-ins are installed without limits; the realm's limits apply from the + // first run. + limits: Limits { fuel: u64::MAX, heap_bytes: usize::MAX, frames: usize::MAX, reentry: usize::MAX }, options, interrupt: Arc::new(AtomicBool::new(false)), reentry: 0, @@ -343,6 +345,8 @@ impl Interp { held: Rc::default(), }; crate::builtins::install(&mut interp); + interp.fuel = limits.fuel; + interp.limits = limits; interp.gc_threshold = interp.next_threshold(); interp } diff --git a/crates/js/src/regexp.rs b/crates/js/src/regexp.rs index cda4f5307..ccb86b998 100644 --- a/crates/js/src/regexp.rs +++ b/crates/js/src/regexp.rs @@ -176,15 +176,8 @@ fn class_atom(pattern: &[u16], i: usize) -> Result<(Option, usize), String> _ => (Some(u32::from(b'\\')), i + 1), }, '0'..='7' => { - let mut value = 0; - let mut j = i + 1; - while j < i + 4 - && let Some(d) = pattern.get(j).and_then(|&u| char::from_u32(u32::from(u))).and_then(|ch| ch.to_digit(8)) - { - value = value * 8 + d; - j += 1; - } - (Some(value), j) + let (value, end) = crate::regexp_vm::octal(pattern, i + 1); + (Some(u32::from(value)), end) } _ => (Some(next), i + 2), }) diff --git a/crates/js/src/regexp_vm.rs b/crates/js/src/regexp_vm.rs index 139469422..476758bd6 100644 --- a/crates/js/src/regexp_vm.rs +++ b/crates/js/src/regexp_vm.rs @@ -473,8 +473,8 @@ fn hex_digits(pattern: &[u16], at: usize, n: usize) -> Option { } /// A legacy octal escape (Annex B) whose first digit is at `at`: its value and the position after -/// it. -fn octal(pattern: &[u16], at: usize) -> (u16, usize) { +/// it. [`crate::regexp::check`] reads class escapes with it too. +pub(crate) fn octal(pattern: &[u16], at: usize) -> (u16, usize) { let digit = |i: usize| pattern.get(i).filter(|&&u| (0x30..=0x37).contains(&u)).map(|&u| u - 0x30); let Some(first) = digit(at) else { return (0, at) }; let max = if first <= 3 { 3 } else { 2 }; diff --git a/crates/js/src/text_tests.rs b/crates/js/src/text_tests.rs index b150ba3f5..c873bdaf5 100644 --- a/crates/js/src/text_tests.rs +++ b/crates/js/src/text_tests.rs @@ -184,12 +184,26 @@ fn regular_expressions_match_as_section_15_10_2_says() { ("/]/.test(']') + ' ' + show(/[\\d-z]+/.exec('5-z')) + ' ' + /\\8/.test('8')", "true 5-z true"), ("/(?:a|b)*c/.exec('ababc').index + ' ' + /x*/.exec('yyy').index", "0 0"), ("/[]/.test('a') + ' ' + /[^]/.test('\\n')", "false true"), + // An octal escape starting with 4 to 7 takes two digits, so `\477-z` is `'` and the range `7-z`. + ("show(/[\\477-z]+/.exec(\"'8y\")) + ' ' + new RegExp('[\\\\477-z]').test('8') + ' ' + /[\\377]/.test('\\xff')", "'8y true true"), ]; for (src, want) in cases { shows(src, want); } } +#[test] +fn a_realm_made_under_low_limits_has_every_built_in() { + let low = Limits { fuel: 0, heap_bytes: 1, ..Limits::default() }; + let mut realm = Realm::with_options(Options::default(), low.clone()); + assert_eq!(realm.limits(), &low); + realm.set_limits(Limits::default()); + assert_eq!( + run_in(&mut realm, "typeof String + typeof Number + typeof RegExp + typeof Date + typeof /a/.exec + typeof new Date(0).getTime"), + JsValue::String("function".repeat(6)) + ); +} + #[test] fn regexp_objects_follow_sections_15_10_4_to_15_10_7() { truth("var r = /a/g; r.lastIndex === 0 && r.global && !r.ignoreCase && !r.multiline && r.source === 'a'"); diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 56da493f5..78938c36a 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -658,7 +658,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Headless CLI (`vectorcraft-cli run`, `convert`, `info`, `bench`, `perf`, `mcp`). A `run` step, like a `command_batch` step over MCP, can use an earlier step's result (`"$1.id"`, #785); `run --commands FILE` (or `-` for standard input) reads the steps from a JSON array or JSON Lines, past the Windows command-line limit (#3147, `apps/vectorcraft-cli/tests/it/cli.rs` `run_reads_*`, `run_refuses_malformed_commands_naming_the_step`). - File › Scripts › Check Script… reads a script written for Illustrator (`.jsx`, `.jsxinc` or a compiled `.jsxbin`) without running any of it and reports what it needs (the document model, files, dialogs, menu commands, features only Illustrator has), what it would do outside the document, the menu commands and document-model members it uses, its directives and included files, and the problems that would stop it loading (`script.check`, which also reads `.js` files); File › Open and drag-and-drop show the report for `.jsx`, `.jsxinc` and `.jsxbin` files. - `vectorcraft-js` parses JavaScript as ECMAScript 5.1 defines it, with the legacy octal forms, block functions and lenient regular expressions of Annex B, into a syntax tree with source spans; it reads the `#target`, `#include` and other preprocessor lines as directives without running them, and a script that breaks the grammar or an early-error rule gives a `SyntaxError` with its line and column. Nesting past the parser's depth limit (`Options::max_depth`, 200 by default) gives a `SyntaxError` instead of exhausting the stack. `cargo xtask test262` fetches the conformance suite at a pinned commit and parses its ES5 tests: 5,411 of 5,418 runs pass (the 7 others use `let` and `const`). This entry covers parsing; the next entry covers running scripts. Tests: `crates/js/src/tests.rs`, `crates/engine/tests/it/js_lexer_vs_scriptcheck.rs`. - - `vectorcraft-js`'s `Realm` runs ECMAScript 5.1 scripts: the parsed script compiles to verified bytecode that a non-recursive interpreter runs, with closures, `this`, `arguments`, `with`, direct and indirect `eval`, `new Function`, `try`/`catch`/`finally`, labeled `break` and `continue`, and `for`-`in` in insertion order, over a mark-and-sweep heap. Built-ins are limited to `Object`, `Function` with `call` and `apply`, `eval` and the error types. Every run is bounded by fuel, heap bytes, frames, nested host calls and an interrupt flag (smaller defaults in the wasm build), and a run that reaches a limit stops with a limit error that `try` cannot catch. `cargo xtask test262` also runs the suite's ES5 language tests that need only these built-ins: 3,074 of 3,078 runs pass (the 4 others expect the completion values of later editions). The app does not call it yet. Tests: `crates/js/src/interp_tests.rs`, `crates/js/tests/test262.rs`. + - `vectorcraft-js`'s `Realm` runs ECMAScript 5.1 scripts: the parsed script compiles to verified bytecode that a non-recursive interpreter runs, with closures, `this`, `arguments`, `with`, direct and indirect `eval`, `new Function`, `try`/`catch`/`finally`, labeled `break` and `continue`, and `for`-`in` in insertion order, over a mark-and-sweep heap. Built-ins are `Object`, `Function` with `call` and `apply`, `eval`, the error types, and the `String`, `Number`, `RegExp` and `Date` of the next entry. Every run is bounded by fuel, heap bytes, frames, nested host calls and an interrupt flag (smaller defaults in the wasm build), and a run that reaches a limit stops with a limit error that `try` cannot catch. `cargo xtask test262` also runs the suite's ES5 language tests that need only these built-ins: 3,742 of 3,746 runs pass (the 4 others expect the completion values of later editions). The app does not call it yet. Tests: `crates/js/src/interp_tests.rs`, `crates/js/tests/test262.rs`. - `vectorcraft-js` has `String`, `Number`, `RegExp` and `Date` as ECMAScript 5.1 defines them (sections 15.5, 15.7, 15.9 and 15.10, https://262.ecma-international.org/5.1/), with Annex B's `substr`, `getYear`, `setYear` and `toGMTString`. `toFixed`, `toExponential` and `toPrecision` format a number from its exact decimal value, and `toString(radix)` writes any base from 2 to 36. Regular expressions (flags `g`, `i` and `m`) run on a backtracking matcher whose choice points and capture state count against the heap limit and which takes fuel for every step, so a catastrophic pattern stops at the fuel limit and 50,000 nested groups give a `SyntaxError` at the default depth limit. A string method that would build a string past the heap limit stops before it allocates. `Date` reads the clock and time zone the host sets (`Realm::set_clock`, `Realm::set_time_zone`; local time is UTC by default). `cargo xtask test262` runs the suite's `built-ins` tests for the four (https://github.com/tc39/test262): of the runs of the ES5 tests that need only the built-ins present, 1,113 of 1,169 pass for `String`, 262 of 262 for `Number`, 878 of 908 for `RegExp` and 6 of 6 for `Date`. The others need `Array` and `Boolean` methods, or expect later editions' rules (built-in functions whose `length` can be deleted, flags given with a RegExp object, a negative `lastIndex`, `RegExp.prototype` as a plain object, and `RegExp.prototype`'s `global`, `ignoreCase` and `multiline` properties that can be deleted). The ES5 language tests that can now run give 3,742 of 3,746 passing runs. Tests: `crates/js/src/text_tests.rs`, `crates/js/tests/test262.rs`. - **UI:** Illustrator 2026 layout restyled to measured values: - Medium Dark theme, categorized and Advanced toolbars (a tool group's flyout opens on a right press, a long press or a press on its corner triangle), 35 pt document tabs, 33 pt panel tabs.