diff --git a/CLAUDE.md b/CLAUDE.md index a7c97bf047..206f93b1ab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.1549 +**Current Version:** 0.5.1550 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index f270a9084f..d86309b53e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5690,7 +5690,7 @@ checksum = "4b2094dda4d997bf73a372cb660d02e9abdb0a13cbf834ddd2b2f8847bffe2cf" [[package]] name = "perry" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "anyhow", "base64 0.22.1", @@ -5754,7 +5754,7 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-dispatch", "serde", @@ -5762,7 +5762,7 @@ dependencies = [ [[package]] name = "perry-audio-miniaudio" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "cc", "libc", @@ -5771,7 +5771,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "aho-corasick", "anyhow", @@ -5789,7 +5789,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "anyhow", "perry-hir", @@ -5797,7 +5797,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "anyhow", "perry-hir", @@ -5805,7 +5805,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "anyhow", "perry-dispatch", @@ -5814,7 +5814,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "anyhow", "perry-hir", @@ -5822,7 +5822,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "anyhow", "base64 0.22.1", @@ -5834,7 +5834,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "anyhow", "perry-hir", @@ -5842,7 +5842,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "anyhow", "async-trait", @@ -5870,14 +5870,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "serde", "serde_json", @@ -5885,7 +5885,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1549" +version = "0.5.1550" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5896,7 +5896,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "anyhow", "clap", @@ -5911,7 +5911,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "block2", "objc2", @@ -5921,7 +5921,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "argon2", "perry-ffi", @@ -5930,7 +5930,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", "reqwest", @@ -5939,7 +5939,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "bcrypt", "perry-ffi", @@ -5947,7 +5947,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", "rusqlite", @@ -5955,7 +5955,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", "scraper", @@ -5963,7 +5963,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", "perry-runtime", @@ -5971,7 +5971,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "chrono", "cron", @@ -5981,7 +5981,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "chrono", "perry-ffi", @@ -5989,7 +5989,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", "rust_decimal", @@ -5997,7 +5997,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", "serde_json", @@ -6005,7 +6005,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", "rand 0.10.2", @@ -6013,7 +6013,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", "perry-runtime", @@ -6021,14 +6021,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "bytes", "http-body-util", @@ -6046,7 +6046,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "bytes", "lazy_static", @@ -6059,7 +6059,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "base64 0.22.1", "bytes", @@ -6091,7 +6091,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "lazy_static", "perry-ffi", @@ -6101,7 +6101,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "base64 0.22.1", "jsonwebtoken", @@ -6112,7 +6112,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "lru", "perry-ffi", @@ -6121,7 +6121,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "chrono", "perry-ffi", @@ -6129,7 +6129,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "bson", "futures-util", @@ -6141,7 +6141,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "chrono", "perry-ffi", @@ -6153,7 +6153,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "nanoid", "perry-ffi", @@ -6162,7 +6162,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "bytes", "perry-ffi", @@ -6177,7 +6177,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "const-oid 0.10.2", "der 0.8.1", @@ -6196,7 +6196,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "lettre", "perry-ffi", @@ -6206,7 +6206,7 @@ dependencies = [ [[package]] name = "perry-ext-parcel-watcher" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "notify", "perry-ffi", @@ -6218,7 +6218,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", "printpdf", @@ -6226,7 +6226,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", "sqlx", @@ -6235,7 +6235,7 @@ dependencies = [ [[package]] name = "perry-ext-qs" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", "perry-runtime", @@ -6244,7 +6244,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "governor", "perry-ffi", @@ -6252,7 +6252,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "fast_image_resize", "image", @@ -6263,7 +6263,7 @@ dependencies = [ [[package]] name = "perry-ext-streams" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "lazy_static", "perry-ffi", @@ -6272,7 +6272,7 @@ dependencies = [ [[package]] name = "perry-ext-typescript" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "anyhow", "perry-ffi", @@ -6292,7 +6292,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", "perry-runtime", @@ -6301,7 +6301,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", "uuid", @@ -6309,7 +6309,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", "perry-validation", @@ -6318,7 +6318,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "futures-util", "lazy_static", @@ -6331,7 +6331,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "brotli", "flate2", @@ -6341,7 +6341,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "dashmap 6.2.1", "once_cell", @@ -6351,7 +6351,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "anyhow", "perry-api-manifest", @@ -6372,11 +6372,11 @@ dependencies = [ [[package]] name = "perry-native-registration" -version = "0.5.1549" +version = "0.5.1550" [[package]] name = "perry-parser" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "anyhow", "perry-diagnostics", @@ -6390,7 +6390,7 @@ dependencies = [ [[package]] name = "perry-perex" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perex", "regex", @@ -6398,7 +6398,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "ahash", "anyhow", @@ -6458,14 +6458,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6560,14 +6560,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "anyhow", "perry-hir", @@ -6576,7 +6576,7 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "perry-ffi", "perry-ui-model", @@ -6584,7 +6584,7 @@ dependencies = [ [[package]] name = "perry-ui-android" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "base64 0.22.1", "itoa", @@ -6602,7 +6602,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "rand 0.10.2", "serde", @@ -6612,7 +6612,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "base64 0.22.1", "cairo-rs 0.22.9", @@ -6635,7 +6635,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "base64 0.22.1", "block2", @@ -6652,7 +6652,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "base64 0.22.1", "block2", @@ -6669,7 +6669,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1549" +version = "0.5.1550" [[package]] name = "perry-ui-test" @@ -6680,11 +6680,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1549" +version = "0.5.1550" [[package]] name = "perry-ui-tvos" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "base64 0.22.1", "block2", @@ -6701,7 +6701,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "base64 0.22.1", "block2", @@ -6718,7 +6718,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "block2", "libc", @@ -6732,7 +6732,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "base64 0.22.1", "libc", @@ -6751,7 +6751,7 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "base64 0.22.1", "libc", @@ -6764,7 +6764,7 @@ dependencies = [ [[package]] name = "perry-updater" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "anyhow", "base64 0.22.1", @@ -6780,7 +6780,7 @@ dependencies = [ [[package]] name = "perry-validation" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "idna", "regex", @@ -6790,7 +6790,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1549" +version = "0.5.1550" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index 5f937808b6..0bc188f5f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -338,7 +338,7 @@ codegen-units = 1 codegen-units = 1 [workspace.package] -version = "0.5.1549" +version = "0.5.1550" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/changelog.d/10176-regex-no-work-cap.md b/changelog.d/10176-regex-no-work-cap.md new file mode 100644 index 0000000000..3c10963c86 --- /dev/null +++ b/changelog.d/10176-regex-no-work-cap.md @@ -0,0 +1,3 @@ +### Fixed + +- **RegExp operations are no longer capped by work** (#10164). Valid programs that Node completes threw `RangeError: Regular expression work limit exceeded`: a 32,000-unit non-ASCII `split`, a 60,000-unit global `replace`, and linear splits and replaces of 11–15 million units. No finite allowance separates valid programs from pathological ones, so a catastrophic pattern now runs as long as it does in Node. Matching still yields to the collector and to cancellation, and the memory limits are unchanged. diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots.rs b/crates/perry-runtime/src/gc/tests/runtime_roots.rs index d21d61fe5c..a5e4bf025f 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots.rs @@ -42,6 +42,8 @@ mod perex_reuse; mod perex_split; #[cfg(feature = "regex-engine")] mod perex_strings; +#[cfg(feature = "regex-engine")] +mod perex_work_policy; mod prototype_addr_cache; mod regexp_last_index; mod segment_record_keys; diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_work_policy.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_work_policy.rs new file mode 100644 index 0000000000..dabc3f28c1 --- /dev/null +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_work_policy.rs @@ -0,0 +1,62 @@ +//! RegExp operations are not capped by work (#10164): a valid program whose +//! matching charges more than the former 100,000,000-unit allowance completes. +use super::*; +use crate::regex::perex_api as api; +use crate::regex::perex_memory::MemoryBudget; +use crate::regex::RegExpHeader; +use crate::string::StringHeader; +use perex::Budget; + +const FORMER_WORK_LIMIT: usize = 100_000_000; + +fn text<'s>(scope: &'s RuntimeHandleScope, bytes: &[u8]) -> RuntimeHandle<'s> { + scope.root_string_ptr(crate::string::js_string_from_bytes( + bytes.as_ptr(), + bytes.len() as u32, + )) +} + +#[test] +fn perex_operation_allowance_admits_linear_work_beyond_the_former_limit() { + let _guard = CopyingNurseryTestGuard::new(0); + let _triggers = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); + super::perex_public::register_host_roots(); + let scope = RuntimeHandleScope::new(); + // One search that is linear but heavy: at every position the lookahead + // reads 32 units and then fails on a character class the subject never + // contains, so it matches nothing and allocates no results. The pattern has + // no required literal on purpose; a literal lets admission reject the whole + // search in one pass without doing the per-position work. Measured at about + // 220 work units per subject unit, so 540,000 units charge about 1.2e8. + let pattern = text(&scope, br"\w(?=[\w,; ]{32}[^\w,; ])"); + let flags = text(&scope, b""); + let receiver = scope.root_raw_mut_ptr(pattern.with_const_ptr::(|pattern| { + flags.with_const_ptr::(|flags| crate::regex::js_regexp_new(pattern, flags)) + })); + let input = text(&scope, "ab12,cd345;ef6 ".repeat(36_000).as_bytes()); + let memory = MemoryBudget::new(api::SCRATCH_BYTES); + let mut budget = Budget::new(api::WORK); + let found = receiver.with_mut_ptr::(|receiver| { + input.with_const_ptr::(|input| { + api::execute_with_resources( + receiver, + input, + false, + &mut budget, + &memory, + &mut || Ok(()), + None, + ) + }) + }); + let charged = api::WORK - budget.remaining(); + assert!( + matches!(found, Ok(None)), + "a valid search must complete without a work-limit error" + ); + assert!( + charged > FORMER_WORK_LIMIT, + "the witness must charge more than the former limit to prove anything; \ + charged {charged}, so a Perex change may have made this pattern cheaper" + ); +} diff --git a/crates/perry-runtime/src/regex/perex_api.rs b/crates/perry-runtime/src/regex/perex_api.rs index 900e603280..2e23d56d6d 100644 --- a/crates/perry-runtime/src/regex/perex_api.rs +++ b/crates/perry-runtime/src/regex/perex_api.rs @@ -12,7 +12,18 @@ use perex::executor::ExecError; use perex::{span::Span, Budget}; // One explicit host policy; no retained scratch cache or alternate engine. -pub(crate) const WORK: usize = 100_000_000; +/// A RegExp operation's work allowance: effectively unlimited (#10164). +/// +/// JavaScript engines never abort regex matching for doing too much work, and +/// no finite allowance separates valid programs from pathological ones: Perex's +/// charge per subject unit depends on the program (about 1 for `/x/`, 60 for +/// `/([a-z]+)([0-9]+)/g`, over 200 for a 32-unit lookahead), so any cap throws +/// on some large linear input Node completes. The former 100,000,000 did, as +/// `RangeError: Regular expression work limit exceeded`. Searches still run in +/// `QUANTUM` slices with a GC poll between them, so collection and cancellation +/// keep working; a catastrophic pattern runs as long as it does in Node. The +/// memory limits below are unchanged. +pub(crate) const WORK: usize = usize::MAX; pub(crate) const SCRATCH_BYTES: usize = 64 * 1024 * 1024; pub(crate) const PROGRAM_BYTES: usize = 32 * 1024 * 1024; pub(crate) const QUANTUM: usize = 4096;