diff --git a/CHANGELOG.md b/CHANGELOG.md index f9d59a3a..20bafcbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v0.33.0 + +- [#266](https://github.com/justjake/quickjs-emscripten/pull/266) Upgrade [bellard/quickjs](https://github.com/bellard/quickjs) to [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c). Review the [QuickJS changelog](https://github.com/bellard/quickjs/blob/04be246001599f5995fa2f2d8c91a0f198d3f34c/Changelog) for details. + - Fixed heap corruption in `getOwnPropertyNames` caused by a `malloc`/`js_free` mismatch that the new small-block allocator exposed; now uses `js_malloc`. + - Disabled QuickJS's new custom small-block allocator under Emscripten (`JS_MALLOC_LARGE_BLOCKS_ONLY`) so `setMemoryLimit` stays accurate. The release's micro-optimization speedups are unaffected. + ## v0.32.0 - [#227](https://github.com/justjake/quickjs-emscripten/pull/227) diff --git a/c/interface.c b/c/interface.c index 5e9fa3ae..10288bb1 100644 --- a/c/interface.c +++ b/c/interface.c @@ -926,7 +926,8 @@ MaybeAsync(JSValue *) QTS_GetOwnPropertyNames(JSContext *ctx, JSValue ***out_ptr } return jsvalue_to_heap(JS_GetException(ctx)); } - *out_ptrs = malloc(sizeof(JSValue) * total_props); + // Freed on the JS side with js_free, so allocate with js_malloc. + *out_ptrs = js_malloc(ctx, sizeof(JSValue *) * total_props); for (int i = 0; i < total_props; i++) { JSAtom atom = tab[i].atom; diff --git a/packages/quickjs-emscripten-core/README.md b/packages/quickjs-emscripten-core/README.md index 20f34fec..6ad0ccd0 100644 --- a/packages/quickjs-emscripten-core/README.md +++ b/packages/quickjs-emscripten-core/README.md @@ -74,7 +74,7 @@ Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS C | Variable | Setting | Description | | ------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | @@ -87,7 +87,7 @@ Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS C | Variable | Setting | Description | | ------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | @@ -100,7 +100,7 @@ Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS C | Variable | Setting | Description | | ------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | release | Optimized for performance; use when building/deploying your application. | | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | @@ -113,7 +113,7 @@ Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS C | Variable | Setting | Description | | ------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | release | Optimized for performance; use when building/deploying your application. | | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | @@ -178,7 +178,7 @@ Variant with the WASM data embedded into a universal (Node and Browser compatibl | Variable | Setting | Description | | ------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | @@ -191,7 +191,7 @@ Variant with the WASM data embedded into a universal (Node and Browser compatibl | Variable | Setting | Description | | ------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | @@ -204,7 +204,7 @@ Variant with the WASM data embedded into a universal (Node and Browser compatibl | Variable | Setting | Description | | ------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | release | Optimized for performance; use when building/deploying your application. | | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | @@ -217,7 +217,7 @@ Variant with the WASM data embedded into a universal (Node and Browser compatibl | Variable | Setting | Description | | ------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | release | Optimized for performance; use when building/deploying your application. | | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | @@ -230,7 +230,7 @@ Variant with the WASM data embedded into a NodeJS ESModule. | Variable | Setting | Description | | ------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | @@ -243,7 +243,7 @@ Variant with the WASM data embedded into a NodeJS ESModule. | Variable | Setting | Description | | ------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | @@ -256,7 +256,7 @@ Variant with the WASM data embedded into a NodeJS ESModule. | Variable | Setting | Description | | ------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | release | Optimized for performance; use when building/deploying your application. | | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | @@ -269,7 +269,7 @@ Variant with the WASM data embedded into a NodeJS ESModule. | Variable | Setting | Description | | ------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | release | Optimized for performance; use when building/deploying your application. | | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | @@ -282,7 +282,7 @@ Variant with the WASM data embedded into a browser ESModule. | Variable | Setting | Description | | ------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | @@ -295,7 +295,7 @@ Variant with the WASM data embedded into a browser ESModule. | Variable | Setting | Description | | ------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | @@ -308,7 +308,7 @@ Variant with the WASM data embedded into a browser ESModule. | Variable | Setting | Description | | ------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | release | Optimized for performance; use when building/deploying your application. | | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | @@ -321,7 +321,7 @@ Variant with the WASM data embedded into a browser ESModule. | Variable | Setting | Description | | ------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | release | Optimized for performance; use when building/deploying your application. | | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | @@ -334,7 +334,7 @@ Compiled to pure Javascript, no WebAssembly required. | Variable | Setting | Description | | ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | +| library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | | releaseMode | release | Optimized for performance; use when building/deploying your application. | | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | | emscriptenInclusion | asmjs | The C library code is compiled to Javascript, no WebAssembly used. Sometimes called "asmjs". This is the slowest possible option, and is intended for constrained environments that do not support WebAssembly, like quickjs-for-quickjs. | diff --git a/packages/variant-quickjs-asmjs-mjs-release-sync/README.md b/packages/variant-quickjs-asmjs-mjs-release-sync/README.md index e6812997..a4dd6c39 100644 --- a/packages/variant-quickjs-asmjs-mjs-release-sync/README.md +++ b/packages/variant-quickjs-asmjs-mjs-release-sync/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: release diff --git a/packages/variant-quickjs-asmjs-mjs-release-sync/src/index.ts b/packages/variant-quickjs-asmjs-mjs-release-sync/src/index.ts index 5aab9657..7a60ea8d 100644 --- a/packages/variant-quickjs-asmjs-mjs-release-sync/src/index.ts +++ b/packages/variant-quickjs-asmjs-mjs-release-sync/src/index.ts @@ -9,7 +9,7 @@ import { QuickJSFFI } from "./ffi.js" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | release | Optimized for performance; use when building/deploying your application. | * | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | * | emscriptenInclusion | asmjs | The C library code is compiled to Javascript, no WebAssembly used. Sometimes called "asmjs". This is the slowest possible option, and is intended for constrained environments that do not support WebAssembly, like quickjs-for-quickjs. | diff --git a/packages/variant-quickjs-singlefile-browser-debug-asyncify/README.md b/packages/variant-quickjs-singlefile-browser-debug-asyncify/README.md index dd643e37..d8a8aafd 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-asyncify/README.md +++ b/packages/variant-quickjs-singlefile-browser-debug-asyncify/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: debug diff --git a/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/index.ts b/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/index.ts index b627bbb0..4200c3ad 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/index.ts +++ b/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSAsyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | * | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | * | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | diff --git a/packages/variant-quickjs-singlefile-browser-debug-sync/README.md b/packages/variant-quickjs-singlefile-browser-debug-sync/README.md index fe5fe4ac..16663f52 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-sync/README.md +++ b/packages/variant-quickjs-singlefile-browser-debug-sync/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: debug diff --git a/packages/variant-quickjs-singlefile-browser-debug-sync/src/index.ts b/packages/variant-quickjs-singlefile-browser-debug-sync/src/index.ts index d47bace1..ef858c6b 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-sync/src/index.ts +++ b/packages/variant-quickjs-singlefile-browser-debug-sync/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSSyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | * | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | * | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | diff --git a/packages/variant-quickjs-singlefile-browser-release-asyncify/README.md b/packages/variant-quickjs-singlefile-browser-release-asyncify/README.md index 6262e81c..df90e512 100644 --- a/packages/variant-quickjs-singlefile-browser-release-asyncify/README.md +++ b/packages/variant-quickjs-singlefile-browser-release-asyncify/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: release diff --git a/packages/variant-quickjs-singlefile-browser-release-asyncify/src/index.ts b/packages/variant-quickjs-singlefile-browser-release-asyncify/src/index.ts index d958a04c..42c4095b 100644 --- a/packages/variant-quickjs-singlefile-browser-release-asyncify/src/index.ts +++ b/packages/variant-quickjs-singlefile-browser-release-asyncify/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSAsyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | release | Optimized for performance; use when building/deploying your application. | * | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | * | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | diff --git a/packages/variant-quickjs-singlefile-browser-release-sync/README.md b/packages/variant-quickjs-singlefile-browser-release-sync/README.md index f7fb17af..f9a64e2a 100644 --- a/packages/variant-quickjs-singlefile-browser-release-sync/README.md +++ b/packages/variant-quickjs-singlefile-browser-release-sync/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: release diff --git a/packages/variant-quickjs-singlefile-browser-release-sync/src/index.ts b/packages/variant-quickjs-singlefile-browser-release-sync/src/index.ts index a6092682..f3fb8361 100644 --- a/packages/variant-quickjs-singlefile-browser-release-sync/src/index.ts +++ b/packages/variant-quickjs-singlefile-browser-release-sync/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSSyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | release | Optimized for performance; use when building/deploying your application. | * | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | * | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | diff --git a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/README.md b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/README.md index c9d52a2c..0048e06d 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/README.md +++ b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: debug diff --git a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/index.ts b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/index.ts index 96fda0a9..a416ca89 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/index.ts +++ b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSAsyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | * | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | * | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | diff --git a/packages/variant-quickjs-singlefile-cjs-debug-sync/README.md b/packages/variant-quickjs-singlefile-cjs-debug-sync/README.md index b346b829..7c9daea7 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-sync/README.md +++ b/packages/variant-quickjs-singlefile-cjs-debug-sync/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: debug diff --git a/packages/variant-quickjs-singlefile-cjs-debug-sync/src/index.ts b/packages/variant-quickjs-singlefile-cjs-debug-sync/src/index.ts index 39f8417b..de4d5c88 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-sync/src/index.ts +++ b/packages/variant-quickjs-singlefile-cjs-debug-sync/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSSyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | * | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | * | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | diff --git a/packages/variant-quickjs-singlefile-cjs-release-asyncify/README.md b/packages/variant-quickjs-singlefile-cjs-release-asyncify/README.md index 85353c14..5ab81bd9 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-asyncify/README.md +++ b/packages/variant-quickjs-singlefile-cjs-release-asyncify/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: release diff --git a/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/index.ts b/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/index.ts index 29ede7e6..b2045da1 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/index.ts +++ b/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSAsyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | release | Optimized for performance; use when building/deploying your application. | * | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | * | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | diff --git a/packages/variant-quickjs-singlefile-cjs-release-sync/README.md b/packages/variant-quickjs-singlefile-cjs-release-sync/README.md index 8796b52d..8a7c2be1 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-sync/README.md +++ b/packages/variant-quickjs-singlefile-cjs-release-sync/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: release diff --git a/packages/variant-quickjs-singlefile-cjs-release-sync/src/index.ts b/packages/variant-quickjs-singlefile-cjs-release-sync/src/index.ts index 8f653a12..ff83e12c 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-sync/src/index.ts +++ b/packages/variant-quickjs-singlefile-cjs-release-sync/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSSyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | release | Optimized for performance; use when building/deploying your application. | * | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | * | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | diff --git a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/README.md b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/README.md index 38f82feb..271f2395 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/README.md +++ b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: debug diff --git a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/index.ts b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/index.ts index bb6e908f..57d3936f 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/index.ts +++ b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSAsyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | * | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | * | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | diff --git a/packages/variant-quickjs-singlefile-mjs-debug-sync/README.md b/packages/variant-quickjs-singlefile-mjs-debug-sync/README.md index 6956fe76..7406e727 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-sync/README.md +++ b/packages/variant-quickjs-singlefile-mjs-debug-sync/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: debug diff --git a/packages/variant-quickjs-singlefile-mjs-debug-sync/src/index.ts b/packages/variant-quickjs-singlefile-mjs-debug-sync/src/index.ts index 64f8a1d0..5f347b21 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-sync/src/index.ts +++ b/packages/variant-quickjs-singlefile-mjs-debug-sync/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSSyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | * | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | * | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | diff --git a/packages/variant-quickjs-singlefile-mjs-release-asyncify/README.md b/packages/variant-quickjs-singlefile-mjs-release-asyncify/README.md index 827c43bd..fb1cb8ae 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-asyncify/README.md +++ b/packages/variant-quickjs-singlefile-mjs-release-asyncify/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: release diff --git a/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/index.ts b/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/index.ts index c333f223..c61216b1 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/index.ts +++ b/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSAsyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | release | Optimized for performance; use when building/deploying your application. | * | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | * | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | diff --git a/packages/variant-quickjs-singlefile-mjs-release-sync/README.md b/packages/variant-quickjs-singlefile-mjs-release-sync/README.md index 8f3d7970..b07dbb0f 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-sync/README.md +++ b/packages/variant-quickjs-singlefile-mjs-release-sync/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: release diff --git a/packages/variant-quickjs-singlefile-mjs-release-sync/src/index.ts b/packages/variant-quickjs-singlefile-mjs-release-sync/src/index.ts index a69d8d29..86200b4e 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-sync/src/index.ts +++ b/packages/variant-quickjs-singlefile-mjs-release-sync/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSSyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | release | Optimized for performance; use when building/deploying your application. | * | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | * | emscriptenInclusion | singlefile | The WASM runtime is included directly in the JS file. Use if you run into issues with missing .wasm files when building or deploying your app. | diff --git a/packages/variant-quickjs-wasmfile-debug-asyncify/README.md b/packages/variant-quickjs-wasmfile-debug-asyncify/README.md index b25605d9..95d7bee0 100644 --- a/packages/variant-quickjs-wasmfile-debug-asyncify/README.md +++ b/packages/variant-quickjs-wasmfile-debug-asyncify/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: debug diff --git a/packages/variant-quickjs-wasmfile-debug-asyncify/src/index.ts b/packages/variant-quickjs-wasmfile-debug-asyncify/src/index.ts index fa522edf..f6068aaa 100644 --- a/packages/variant-quickjs-wasmfile-debug-asyncify/src/index.ts +++ b/packages/variant-quickjs-wasmfile-debug-asyncify/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSAsyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | * | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | * | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | diff --git a/packages/variant-quickjs-wasmfile-debug-sync/README.md b/packages/variant-quickjs-wasmfile-debug-sync/README.md index b4e58630..e165432c 100644 --- a/packages/variant-quickjs-wasmfile-debug-sync/README.md +++ b/packages/variant-quickjs-wasmfile-debug-sync/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: debug diff --git a/packages/variant-quickjs-wasmfile-debug-sync/src/index.ts b/packages/variant-quickjs-wasmfile-debug-sync/src/index.ts index b6f3b99e..7c6bef71 100644 --- a/packages/variant-quickjs-wasmfile-debug-sync/src/index.ts +++ b/packages/variant-quickjs-wasmfile-debug-sync/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSSyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | * | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | * | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | diff --git a/packages/variant-quickjs-wasmfile-release-asyncify/README.md b/packages/variant-quickjs-wasmfile-release-asyncify/README.md index ae2c0081..6ad6ae77 100644 --- a/packages/variant-quickjs-wasmfile-release-asyncify/README.md +++ b/packages/variant-quickjs-wasmfile-release-asyncify/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: release diff --git a/packages/variant-quickjs-wasmfile-release-asyncify/src/index.ts b/packages/variant-quickjs-wasmfile-release-asyncify/src/index.ts index 341112de..00075804 100644 --- a/packages/variant-quickjs-wasmfile-release-asyncify/src/index.ts +++ b/packages/variant-quickjs-wasmfile-release-asyncify/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSAsyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | release | Optimized for performance; use when building/deploying your application. | * | syncMode | asyncify | Build run through the ASYNCIFY WebAssembly transform. This imposes substantial size (2x the size of sync) and speed penalties (40% the speed of sync). In return, allows synchronous calls from the QuickJS WASM runtime to async functions on the host. The extra magic makes this variant slower than sync variants. Note that both variants support regular async functions. Only adopt ASYNCIFY if you need to! The [QuickJSAsyncRuntime](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md) and [QuickJSAsyncContext](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md) classes expose the ASYNCIFY-specific APIs. | * | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | diff --git a/packages/variant-quickjs-wasmfile-release-sync/README.md b/packages/variant-quickjs-wasmfile-release-sync/README.md index 732a75cf..a6511b49 100644 --- a/packages/variant-quickjs-wasmfile-release-sync/README.md +++ b/packages/variant-quickjs-wasmfile-release-sync/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: The original [bellard/quickjs](https://github.com/bellard/quickjs) library. -Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. +Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. ## Release mode: release diff --git a/packages/variant-quickjs-wasmfile-release-sync/src/index.ts b/packages/variant-quickjs-wasmfile-release-sync/src/index.ts index 0e5a593f..013f4061 100644 --- a/packages/variant-quickjs-wasmfile-release-sync/src/index.ts +++ b/packages/variant-quickjs-wasmfile-release-sync/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSSyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2025-09-13+f1139494](https://github.com/bellard/quickjs/commit/f1139494d18a2053630c5ed3384a42bb70db3c53) vendored to quickjs-emscripten on 2026-02-15. | + * | library | quickjs | The original [bellard/quickjs](https://github.com/bellard/quickjs) library. Version [2026-06-04+04be2460](https://github.com/bellard/quickjs/commit/04be246001599f5995fa2f2d8c91a0f198d3f34c) vendored to quickjs-emscripten on 2026-07-22. | * | releaseMode | release | Optimized for performance; use when building/deploying your application. | * | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | * | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | diff --git a/vendor/quickjs-patches/0167-emscripten-disable-small-block-malloc.patch b/vendor/quickjs-patches/0167-emscripten-disable-small-block-malloc.patch new file mode 100644 index 00000000..daf2bbb1 --- /dev/null +++ b/vendor/quickjs-patches/0167-emscripten-disable-small-block-malloc.patch @@ -0,0 +1,16 @@ +diff --git a/vendor/quickjs/quickjs.c b/vendor/quickjs/quickjs.c +index 4b732ed..76c9259 100644 +--- a/vendor/quickjs/quickjs.c ++++ b/vendor/quickjs/quickjs.c +@@ -245,8 +245,9 @@ typedef enum OPCodeEnum OPCodeEnum; + #define JS_MALLOC_BLOCK_SIZE_COUNT 31 + #define JS_MALLOC_MIN_SMALL_SIZE 16 + #define JS_MALLOC_MAX_SMALL_SIZE 512 +-#if defined(__SANITIZE_ADDRESS__) +-/* use the host malloc() for all allocations */ ++#if defined(__SANITIZE_ADDRESS__) || defined(__EMSCRIPTEN__) ++/* quickjs-emscripten: use the host malloc() under Emscripten too, otherwise the ++ small-block allocator over-commits arenas and makes JS_SetMemoryLimit inaccurate. */ + #define JS_MALLOC_LARGE_BLOCKS_ONLY 1 + #else + #define JS_MALLOC_LARGE_BLOCKS_ONLY 0 diff --git a/vendor/quickjs/Changelog b/vendor/quickjs/Changelog index 3c08f0c5..f5c536b1 100644 --- a/vendor/quickjs/Changelog +++ b/vendor/quickjs/Changelog @@ -1,3 +1,6 @@ +2026-06-04: + +- added custom malloc for small blocks (11% faster on bench-v8) - micro optimizations (30% faster on bench-v8) - added resizable array buffers - added ArrayBuffer.prototype.transfer @@ -7,6 +10,7 @@ - added added Map and WeakMap upsert methods - added Math.sumPrecise() - added regexp duplicate named groups +- added base64 and hexadecimal encodings for Uint8Array - misc bug fixes 2025-09-13: diff --git a/vendor/quickjs/Makefile b/vendor/quickjs/Makefile index dcbbf7e5..1239e0ef 100644 --- a/vendor/quickjs/Makefile +++ b/vendor/quickjs/Makefile @@ -53,9 +53,11 @@ PREFIX?=/usr/local #CONFIG_MSAN=y # use UB sanitizer #CONFIG_UBSAN=y +# use thread sanitizer +#CONFIG_TSAN=y # TEST262 bootstrap config: commit id and shallow "since" parameter -TEST262_COMMIT?=d0994d64b07cb6c164dd9f345c94ed797a53d69f +TEST262_COMMIT?=5c8206929d81b2d3d727ca6aac56c18358c8d790 TEST262_SINCE?=2025-09-01 OBJDIR=.obj @@ -192,6 +194,10 @@ ifdef CONFIG_UBSAN CFLAGS+=-fsanitize=undefined -fno-omit-frame-pointer LDFLAGS+=-fsanitize=undefined -fno-omit-frame-pointer endif +ifdef CONFIG_TSAN +CFLAGS+=-fsanitize=thread -fno-omit-frame-pointer +LDFLAGS+=-fsanitize=thread -fno-omit-frame-pointer +endif ifdef CONFIG_WIN32 LDEXPORT= else @@ -456,6 +462,7 @@ test: qjs$(EXE) $(WINE) ./qjs$(EXE) tests/test_worker.js ifndef CONFIG_WIN32 $(WINE) ./qjs$(EXE) tests/test_std.js + $(WINE) ./qjs$(EXE) tests/test_rw_handler.js endif ifdef CONFIG_SHARED_LIBS $(WINE) ./qjs$(EXE) tests/test_bjson.js @@ -486,7 +493,7 @@ test2o: run-test262 time ./run-test262 -t -m -c test262o.conf test2o-update: run-test262 - ./run-test262 -t -u -c test262o.conf + ./run-test262 -t -u -c test262o.conf -T 1 endif ifeq ($(wildcard test262/features.txt),) diff --git a/vendor/quickjs/TODO b/vendor/quickjs/TODO index a0cb6043..c518c264 100644 --- a/vendor/quickjs/TODO +++ b/vendor/quickjs/TODO @@ -63,4 +63,4 @@ Test262o: 0/11262 errors, 463 excluded Test262o commit: 7da91bceb9ce7613f87db47ddd1292a2dda58b42 (es5-tests branch) Test262: -Result: 66/83341 errors, 2567 excluded, 5767 skipped +Result: 58/83558 errors, 3356 excluded, 6000 skipped diff --git a/vendor/quickjs/VERSION b/vendor/quickjs/VERSION index 433b8f85..ba2d8bcd 100644 --- a/vendor/quickjs/VERSION +++ b/vendor/quickjs/VERSION @@ -1 +1 @@ -2025-09-13 +2026-06-04 diff --git a/vendor/quickjs/cutils.c b/vendor/quickjs/cutils.c index 52ff1649..6a3aeca4 100644 --- a/vendor/quickjs/cutils.c +++ b/vendor/quickjs/cutils.c @@ -315,7 +315,7 @@ int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp) #if 0 -#if defined(EMSCRIPTEN) || defined(__ANDROID__) +#if defined(__EMSCRIPTEN__) || defined(__ANDROID__) static void *rqsort_arg; static int (*rqsort_cmp)(const void *, const void *, void *); diff --git a/vendor/quickjs/doc/quickjs.texi b/vendor/quickjs/doc/quickjs.texi index 0b8c744e..79807e82 100644 --- a/vendor/quickjs/doc/quickjs.texi +++ b/vendor/quickjs/doc/quickjs.texi @@ -23,9 +23,8 @@ @chapter Introduction QuickJS (version @value{VERSION}) is a small and embeddable Javascript -engine. It supports most of the ES2024 specification -@footnote{@url{https://tc39.es/ecma262/2024 }} including modules, -asynchronous generators, proxies and BigInt. +engine. It supports most of the ES2025 specification +@footnote{@url{https://tc39.es/ecma262/2025 }}. @section Main Features @@ -33,14 +32,11 @@ asynchronous generators, proxies and BigInt. @item Small and easily embeddable: just a few C files, no external dependency, 210 KiB of x86 code for a simple ``hello world'' program. -@item Fast interpreter with very low startup time: runs the 77000 tests of the ECMAScript Test Suite@footnote{@url{https://github.com/tc39/test262}} in less than 2 minutes on a single core of a desktop PC. The complete life cycle of a runtime instance completes in less than 300 microseconds. +@item Fast interpreter with very low startup time: runs the tests of the ECMAScript Test Suite@footnote{@url{https://github.com/tc39/test262}} in less than 2 minutes on a single core of a desktop PC. The complete life cycle of a runtime instance completes in less than 300 microseconds. -@item Almost complete ES2024 support including modules, asynchronous -generators and full Annex B support (legacy web compatibility). Some -features from the upcoming ES2024 specification -@footnote{@url{https://tc39.es/ecma262/}} are also supported. +@item Almost complete ES2025 support. -@item Passes nearly 100% of the ECMAScript Test Suite tests when selecting the ES2024 features. +@item Passes nearly 100% of the ECMAScript Test Suite tests when selecting the ES2025 features. @item Compile Javascript sources to executables with no external dependency. @@ -250,9 +246,9 @@ about 100 seconds). @section Language support -@subsection ES2024 support +@subsection ES2025 support -The ES2024 specification is almost fully supported including the Annex +The ES2025 specification is almost fully supported including the Annex B (legacy web compatibility) and the Unicode related features. The following features are not supported yet: @@ -1033,7 +1029,7 @@ stack holds the Javascript parameters and local variables. @section RegExp A specific regular expression engine was developed. It is both small -and efficient and supports all the ES2024 features including the +and efficient and supports all the ES2025 features including the Unicode properties. As the Javascript compiler, it directly generates bytecode without a parse tree. diff --git a/vendor/quickjs/fuzz/fuzz_bytecode.c b/vendor/quickjs/fuzz/fuzz_bytecode.c new file mode 100644 index 00000000..bb616a7b --- /dev/null +++ b/vendor/quickjs/fuzz/fuzz_bytecode.c @@ -0,0 +1,142 @@ +// Copyright 2025 Google LLC +// Fuzz target for QuickJS bytecode execution + +#include "quickjs.h" +#include "quickjs-libc.h" +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 8) return 0; // Need at least minimal bytecode header + + JSRuntime* rt = JS_NewRuntime(); + if (!rt) return 0; + + JSContext* ctx = JS_NewContext(rt); + if (!ctx) { + JS_FreeRuntime(rt); + return 0; + } + + char load_script[256]; + snprintf(load_script, sizeof(load_script), + "(function() { " + " var buf = new Uint8Array(%zu); " + " for (var i = 0; i < %zu; i++) buf[i] = 0; " + " return evalBinary(buf); " + "})()", size, size); + + JSValue eval_result = JS_Eval(ctx, load_script, strlen(load_script), + "", 0); + + if (!JS_IsException(eval_result)) { + JS_FreeValue(ctx, eval_result); + } else { + JS_GetException(ctx); + } + + const char* simple_script = "({ a: 1, b: 'test', c: function() { return 42; } })"; + + JSValue bytecode = JS_Eval(ctx, simple_script, strlen(simple_script), + "", JS_EVAL_FLAG_COMPILE_ONLY); + + if (!JS_IsException(bytecode)) { + size_t bytecode_len; + uint8_t* bytecode_buf = JS_WriteObject(ctx, &bytecode_len, bytecode, + JS_WRITE_OBJ_BYTECODE); + + if (bytecode_buf) { + JSValue loaded = JS_ReadObject(ctx, bytecode_buf, bytecode_len, + JS_READ_OBJ_BYTECODE); + + if (!JS_IsException(loaded)) { + JSValue result = JS_EvalFunction(ctx, loaded); + if (!JS_IsException(result)) { + JS_FreeValue(ctx, result); + } else { + JS_GetException(ctx); + } + } else { + JS_GetException(ctx); + } + + js_free(ctx, bytecode_buf); + } + + JS_FreeValue(ctx, bytecode); + } else { + JS_GetException(ctx); + } + + if (size >= 8) { + uint8_t* fake_bytecode = malloc(size); + if (fake_bytecode) { + memcpy(fake_bytecode, data, size); + + if (data[0] % 2 == 0) { + fake_bytecode[0] = 'Q'; + fake_bytecode[1] = 'C'; + fake_bytecode[2] = 'A'; + fake_bytecode[3] = 'M'; + } + + JSValue malformed = JS_ReadObject(ctx, fake_bytecode, size, + JS_READ_OBJ_BYTECODE); + if (!JS_IsException(malformed)) { + JS_FreeValue(ctx, malformed); + } else { + JS_GetException(ctx); + } + + free(fake_bytecode); + } + } + + const char* eval_script_test = "typeof std !== 'undefined' ? std.evalScript : null"; + JSValue std_check = JS_Eval(ctx, eval_script_test, strlen(eval_script_test), + "", 0); + if (!JS_IsException(std_check)) { + JS_FreeValue(ctx, std_check); + } else { + JS_GetException(ctx); + } + + const char* module_script = "export default 42; export const x = 123;"; + JSValue module_bytecode = JS_Eval(ctx, module_script, strlen(module_script), + "", + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + + if (!JS_IsException(module_bytecode)) { + size_t mod_bc_len; + uint8_t* mod_bc_buf = JS_WriteObject(ctx, &mod_bc_len, module_bytecode, + JS_WRITE_OBJ_BYTECODE); + + if (mod_bc_buf) { + JSValue mod_loaded = JS_ReadObject(ctx, mod_bc_buf, mod_bc_len, + JS_READ_OBJ_BYTECODE); + if (!JS_IsException(mod_loaded)) { + JSValue mod_result = JS_EvalFunction(ctx, mod_loaded); + if (!JS_IsException(mod_result)) { + JS_FreeValue(ctx, mod_result); + } else { + JS_GetException(ctx); + } + } else { + JS_GetException(ctx); + } + + js_free(ctx, mod_bc_buf); + } + + JS_FreeValue(ctx, module_bytecode); + } else { + JS_GetException(ctx); + } + + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + return 0; +} diff --git a/vendor/quickjs/fuzz/fuzz_json.c b/vendor/quickjs/fuzz/fuzz_json.c new file mode 100644 index 00000000..a9c331d2 --- /dev/null +++ b/vendor/quickjs/fuzz/fuzz_json.c @@ -0,0 +1,131 @@ +// Copyright 2025 Google LLC +// Fuzz target for QuickJS JSON operations + +#include "quickjs.h" +#include "quickjs-libc.h" +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 1) return 0; + + JSRuntime* rt = JS_NewRuntime(); + if (!rt) return 0; + + JSContext* ctx = JS_NewContext(rt); + if (!ctx) { + JS_FreeRuntime(rt); + return 0; + } + + char* input = malloc(size + 1); + if (!input) { + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + } + memcpy(input, data, size); + input[size] = '\0'; + + char parse_script[8192]; + snprintf(parse_script, sizeof(parse_script), + "JSON.parse(JSON.stringify(%s))", input); + + JSValue parse_result = JS_Eval(ctx, parse_script, strlen(parse_script), + "", 0); + + if (JS_IsException(parse_result)) { + JS_GetException(ctx); + } else { + JS_FreeValue(ctx, parse_result); + } + + char direct_parse[16384]; + snprintf(direct_parse, sizeof(direct_parse), "JSON.parse('"); + + size_t script_len = strlen(direct_parse); + for (size_t i = 0; i < size && script_len < sizeof(direct_parse) - 20; i++) { + char c = input[i]; + if (c == '\\' || c == '\'') { + direct_parse[script_len++] = '\\'; + } + if (c >= 32 && c < 127) { + direct_parse[script_len++] = c; + } + } + strcat(direct_parse + script_len, "');"); + + JSValue direct_result = JS_Eval(ctx, direct_parse, strlen(direct_parse), + "", 0); + if (JS_IsException(direct_result)) { + JS_GetException(ctx); + } else { + JS_FreeValue(ctx, direct_result); + } + + char stringify_script[8192]; + snprintf(stringify_script, sizeof(stringify_script), + "var obj = { data: %s, num: 123, str: 'test', bool: true, nullv: null, " + "arr: [1,2,3], nested: { a: 1 } }; JSON.stringify(obj);", + input); + + JSValue stringify_result = JS_Eval(ctx, stringify_script, + strlen(stringify_script), "", 0); + if (JS_IsException(stringify_result)) { + JS_GetException(ctx); + } else { + const char* str = JS_ToCString(ctx, stringify_result); + if (str) { + JS_FreeCString(ctx, str); + } + JS_FreeValue(ctx, stringify_result); + } + + const char* spacing_tests[] = { + "JSON.stringify({a:1,b:2})", + "JSON.stringify({a:1,b:2}, null, 2)", + "JSON.stringify({a:1,b:2}, null, ' ')", + "JSON.stringify([1,2,3])", + "JSON.stringify(null)", + "JSON.stringify(undefined)", + "JSON.stringify(123)", + "JSON.stringify('string')", + "JSON.stringify(true)", + }; + + for (size_t i = 0; i < sizeof(spacing_tests) / sizeof(spacing_tests[0]); i++) { + JSValue r = JS_Eval(ctx, spacing_tests[i], strlen(spacing_tests[i]), + "", 0); + if (!JS_IsException(r)) { + JS_FreeValue(ctx, r); + } else { + JS_GetException(ctx); + } + } + + const char* reviver_test = "JSON.parse('{\"a\":1,\"b\":2}', function(k,v) { return v; })"; + JSValue reviver_result = JS_Eval(ctx, reviver_test, strlen(reviver_test), + "", 0); + if (!JS_IsException(reviver_result)) { + JS_FreeValue(ctx, reviver_result); + } else { + JS_GetException(ctx); + } + + const char* replacer_test = "JSON.stringify({a:1,b:2}, function(k,v) { return v; })"; + JSValue replacer_result = JS_Eval(ctx, replacer_test, strlen(replacer_test), + "", 0); + if (!JS_IsException(replacer_result)) { + JS_FreeValue(ctx, replacer_result); + } else { + JS_GetException(ctx); + } + + free(input); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + return 0; +} diff --git a/vendor/quickjs/fuzz/fuzz_module_export.c b/vendor/quickjs/fuzz/fuzz_module_export.c new file mode 100644 index 00000000..ece24f61 --- /dev/null +++ b/vendor/quickjs/fuzz/fuzz_module_export.c @@ -0,0 +1,105 @@ +// Copyright 2025 Google LLC +// Fuzz target for QuickJS ES6 module parsing + +#include "quickjs.h" +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 1) return 0; + + JSRuntime* rt = JS_NewRuntime(); + if (!rt) return 0; + + JSContext* ctx = JS_NewContext(rt); + if (!ctx) { + JS_FreeRuntime(rt); + return 0; + } + + char* input = malloc(size + 1); + if (!input) { + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + } + memcpy(input, data, size); + input[size] = '\0'; + + const char* export_patterns[] = { + "export default %s;", + "export const x = %s;", + "export let x = %s;", + "export var x = %s;", + "export function f() { %s }", + "export class C { %s }", + "export { %s };", + "export * from '%s';", + "export { %s } from 'module';", + "export { default as x } from '%s';", + }; + + int pattern_idx = data[0] % (sizeof(export_patterns) / sizeof(export_patterns[0])); + + char script[8192]; + snprintf(script, sizeof(script), export_patterns[pattern_idx], input); + + JSValue result = JS_Eval(ctx, script, strlen(script), "", + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + + if (!JS_IsException(result)) { + JS_FreeValue(ctx, result); + } else { + JS_GetException(ctx); + } + + JSValue result2 = JS_Eval(ctx, input, size, "", JS_EVAL_FLAG_COMPILE_ONLY); + if (!JS_IsException(result2)) { + JS_FreeValue(ctx, result2); + } else { + JS_GetException(ctx); + } + + const char* import_patterns[] = { + "import '%s';", + "import x from '%s';", + "import * as x from '%s';", + "import { x } from '%s';", + "import { x as y } from '%s';", + "import x, { y } from '%s';", + "import x, * as y from '%s';", + }; + + int import_idx = (data[0] >> 4) % (sizeof(import_patterns) / sizeof(import_patterns[0])); + char import_script[8192]; + char* sanitized = malloc(size + 1); + if (sanitized) { + size_t j = 0; + for (size_t i = 0; i < size && j < size; i++) { + if (data[i] != '\'' && data[i] != '"' && data[i] != '\n' && data[i] != '\r') { + sanitized[j++] = data[i]; + } + } + sanitized[j] = '\0'; + + snprintf(import_script, sizeof(import_script), import_patterns[import_idx], sanitized); + + JSValue import_result = JS_Eval(ctx, import_script, strlen(import_script), "", + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + if (!JS_IsException(import_result)) { + JS_FreeValue(ctx, import_result); + } else { + JS_GetException(ctx); + } + + free(sanitized); + } + + free(input); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + return 0; +} diff --git a/vendor/quickjs/fuzz/fuzz_regexp_compile.c b/vendor/quickjs/fuzz/fuzz_regexp_compile.c new file mode 100644 index 00000000..c501fc5d --- /dev/null +++ b/vendor/quickjs/fuzz/fuzz_regexp_compile.c @@ -0,0 +1,177 @@ +// Copyright 2025 Google LLC +// Fuzz target for QuickJS RegExp compilation + +#include "quickjs.h" +#include "quickjs-libc.h" +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 2) return 0; + + JSRuntime* rt = JS_NewRuntime(); + if (!rt) return 0; + + JSContext* ctx = JS_NewContext(rt); + if (!ctx) { + JS_FreeRuntime(rt); + return 0; + } + + size_t pattern_len = size / 2; + size_t flags_len = size - pattern_len; + + if (pattern_len == 0 || flags_len == 0) { + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + } + + char* pattern = malloc(pattern_len + 1); + char* flags = malloc(flags_len + 1); + + if (!pattern || !flags) { + free(pattern); + free(flags); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + } + + memcpy(pattern, data, pattern_len); + pattern[pattern_len] = '\0'; + + memcpy(flags, data + pattern_len, flags_len); + flags[flags_len] = '\0'; + + char valid_flags[16]; + size_t valid_idx = 0; + const char* valid = "gimsuy"; + for (size_t i = 0; i < flags_len && valid_idx < sizeof(valid_flags) - 1; i++) { + if (strchr(valid, flags[i]) && !strchr(valid_flags, flags[i])) { + valid_flags[valid_idx++] = flags[i]; + } + } + valid_flags[valid_idx] = '\0'; + + char script[8192]; + char escaped_pattern[4096]; + size_t esc_idx = 0; + for (size_t i = 0; i < pattern_len && esc_idx < sizeof(escaped_pattern) - 2; i++) { + if (pattern[i] == '\\' || pattern[i] == '"' || pattern[i] == '\n' || + pattern[i] == '\r' || pattern[i] == '\t') { + escaped_pattern[esc_idx++] = '\\'; + } + escaped_pattern[esc_idx++] = pattern[i]; + } + escaped_pattern[esc_idx] = '\0'; + + snprintf(script, sizeof(script), "new RegExp(\"%s\", \"%s\")", + escaped_pattern, valid_flags); + + JSValue regexp_result = JS_Eval(ctx, script, strlen(script), "", 0); + + if (!JS_IsException(regexp_result)) { + const char* test_strings[] = { + "'test string'", + "''", + "'aaaaaaaaaa'", + "'1234567890'", + "'!@#$%^&*()'", + }; + + for (size_t i = 0; i < sizeof(test_strings) / sizeof(test_strings[0]); i++) { + char match_script[4096]; + snprintf(match_script, sizeof(match_script), + "var re = %s; re.test(%s); re.exec(%s); %s.match(re);", + script, test_strings[i], test_strings[i], test_strings[i]); + + JSValue match_result = JS_Eval(ctx, match_script, strlen(match_script), + "", 0); + if (!JS_IsException(match_result)) { + JS_FreeValue(ctx, match_result); + } else { + JS_GetException(ctx); + } + } + + const char* split_test = "'a,b,c,d'.split(/,/)"; + JSValue split_result = JS_Eval(ctx, split_test, strlen(split_test), + "", 0); + if (!JS_IsException(split_result)) { + JS_FreeValue(ctx, split_result); + } else { + JS_GetException(ctx); + } + + const char* replace_test = "'hello world'.replace(/world/, 'universe')"; + JSValue replace_result = JS_Eval(ctx, replace_test, strlen(replace_test), + "", 0); + if (!JS_IsException(replace_result)) { + JS_FreeValue(ctx, replace_result); + } else { + JS_GetException(ctx); + } + + const char* search_test = "'abc123def'.search(/[0-9]+/)"; + JSValue search_result = JS_Eval(ctx, search_test, strlen(search_test), + "", 0); + if (!JS_IsException(search_result)) { + JS_FreeValue(ctx, search_result); + } else { + JS_GetException(ctx); + } + + JS_FreeValue(ctx, regexp_result); + } else { + JS_GetException(ctx); + } + + char literal_script[4096]; + char slash_escaped[2048]; + size_t slash_idx = 0; + for (size_t i = 0; i < pattern_len && slash_idx < sizeof(slash_escaped) - 2; i++) { + if (pattern[i] == '/') { + slash_escaped[slash_idx++] = '\\'; + } + slash_escaped[slash_idx++] = pattern[i]; + } + slash_escaped[slash_idx] = '\0'; + + snprintf(literal_script, sizeof(literal_script), "/%s/%s.test('test')", slash_escaped, valid_flags); + + JSValue literal_result = JS_Eval(ctx, literal_script, strlen(literal_script), + "", 0); + if (!JS_IsException(literal_result)) { + JS_FreeValue(ctx, literal_result); + } else { + JS_GetException(ctx); + } + + const char* builtin_tests[] = { + "RegExp.prototype.compile", + "/a/g[Symbol.match]('a')", + "/a/g[Symbol.replace]('a', 'b')", + "/a/g[Symbol.search]('a')", + "/a/g[Symbol.split]('a,b,a')", + }; + + for (size_t i = 0; i < sizeof(builtin_tests) / sizeof(builtin_tests[0]); i++) { + JSValue r = JS_Eval(ctx, builtin_tests[i], strlen(builtin_tests[i]), + "", 0); + if (!JS_IsException(r)) { + JS_FreeValue(ctx, r); + } else { + JS_GetException(ctx); + } + } + + free(pattern); + free(flags); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + return 0; +} diff --git a/vendor/quickjs/libunicode.c b/vendor/quickjs/libunicode.c index 0c510ccb..0b7b6d0b 100644 --- a/vendor/quickjs/libunicode.c +++ b/vendor/quickjs/libunicode.c @@ -1189,7 +1189,8 @@ int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, goto not_latin1; } buf = (int *)dbuf->buf; - memcpy(buf, src, src_len * sizeof(int)); + if (src_len != 0) + memcpy(buf, src, src_len * sizeof(int)); *pdst = (uint32_t *)buf; return src_len; not_latin1: ; diff --git a/vendor/quickjs/libunicode.h b/vendor/quickjs/libunicode.h index 5b02c82b..c2fdeac8 100644 --- a/vendor/quickjs/libunicode.h +++ b/vendor/quickjs/libunicode.h @@ -26,6 +26,11 @@ #include +/* unicode standard version */ +#define LIBUNICODE_UNICODE_VERSION_MAJOR 17 +#define LIBUNICODE_UNICODE_VERSION_MINOR 0 +#define LIBUNICODE_UNICODE_VERSION_PATCH 0 + /* define it to include all the unicode tables (40KB larger) */ #define CONFIG_ALL_UNICODE diff --git a/vendor/quickjs/qjs.c b/vendor/quickjs/qjs.c index 0224f7cf..3ee63408 100644 --- a/vendor/quickjs/qjs.c +++ b/vendor/quickjs/qjs.c @@ -139,7 +139,7 @@ static size_t js_trace_malloc_usable_size(const void *ptr) return malloc_size(ptr); #elif defined(_WIN32) return _msize((void *)ptr); -#elif defined(EMSCRIPTEN) +#elif defined(__EMSCRIPTEN__) return 0; #elif defined(__linux__) || defined(__GLIBC__) return malloc_usable_size((void *)ptr); diff --git a/vendor/quickjs/quickjs-atom.h b/vendor/quickjs/quickjs-atom.h index dac2df67..13c1ccdb 100644 --- a/vendor/quickjs/quickjs-atom.h +++ b/vendor/quickjs/quickjs-atom.h @@ -147,6 +147,7 @@ DEF(flags, "flags") DEF(global, "global") DEF(unicode, "unicode") DEF(raw, "raw") +DEF(rawJSON, "rawJSON") DEF(new_target, "new.target") DEF(this_active_func, "this.active_func") DEF(home_object, "") @@ -189,6 +190,10 @@ DEF(unicodeSets, "unicodeSets") DEF(not_equal, "not-equal") DEF(timed_out, "timed-out") DEF(ok, "ok") +DEF(toISOString, "toISOString") +DEF(alphabet, "alphabet") +DEF(lastChunkHandling, "lastChunkHandling") +DEF(omitPadding, "omitPadding") /* */ DEF(toJSON, "toJSON") DEF(maxByteLength, "maxByteLength") diff --git a/vendor/quickjs/quickjs-libc.c b/vendor/quickjs/quickjs-libc.c index c24b6d53..26e9683a 100644 --- a/vendor/quickjs/quickjs-libc.c +++ b/vendor/quickjs/quickjs-libc.c @@ -46,6 +46,7 @@ #include #include #include +#include #if defined(__FreeBSD__) extern char **environ; @@ -87,6 +88,7 @@ typedef sig_t sighandler_t; typedef struct { struct list_head link; int fd; + int poll_fd_index; /* temporary use in js_os_poll() */ JSValue rw_func[2]; } JSOSRWHandler; @@ -134,6 +136,7 @@ typedef struct { struct list_head link; JSWorkerMessagePipe *recv_pipe; JSValue on_message_func; + int poll_fd_index; /* temporary use in js_os_poll() */ } JSWorkerMessageHandler; typedef struct { @@ -152,6 +155,10 @@ typedef struct JSThreadState { int next_timer_id; /* for setTimeout() */ /* not used in the main thread */ JSWorkerMessagePipe *recv_pipe, *send_pipe; +#if !defined(_WIN32) + struct pollfd *poll_fds; + int poll_fds_size; +#endif } JSThreadState; static uint64_t os_pending_signals; @@ -2507,16 +2514,44 @@ static int js_os_poll(JSContext *ctx) #else +static no_inline int js_poll_expand(JSThreadState *ts) +{ + struct pollfd *new_fds; + int new_size = max_int(ts->poll_fds_size + + ts->poll_fds_size / 2, 16); + new_fds = realloc(ts->poll_fds, new_size * sizeof(struct pollfd)); + if (!new_fds) + return -1; + ts->poll_fds = new_fds; + ts->poll_fds_size = new_size; + return 0; +} + +static int js_poll_add_poll_fd(JSThreadState *ts, int *pnfds, int fd, int events) +{ + struct pollfd *fds; + int nfds; + nfds = *pnfds; + if (unlikely(nfds >= ts->poll_fds_size)) { + if (js_poll_expand(ts)) + return -1; + } + fds = &ts->poll_fds[nfds++]; + fds->fd = fd; + fds->events = events; + fds->revents = 0; + *pnfds = nfds; + return 0; +} + static int js_os_poll(JSContext *ctx) { JSRuntime *rt = JS_GetRuntime(ctx); JSThreadState *ts = JS_GetRuntimeOpaque(rt); - int ret, fd_max, min_delay; + int min_delay, nfds; int64_t cur_time, delay; - fd_set rfds, wfds; JSOSRWHandler *rh; struct list_head *el; - struct timeval tv, *tvp; /* only check signals in the main thread */ if (!ts->recv_pipe && @@ -2558,46 +2593,49 @@ static int js_os_poll(JSContext *ctx) min_delay = delay; } } - tv.tv_sec = min_delay / 1000; - tv.tv_usec = (min_delay % 1000) * 1000; - tvp = &tv; } else { - tvp = NULL; + min_delay = -1; /* infinite */ } - FD_ZERO(&rfds); - FD_ZERO(&wfds); - fd_max = -1; + nfds = 0; list_for_each(el, &ts->os_rw_handlers) { + int events; + rh = list_entry(el, JSOSRWHandler, link); - fd_max = max_int(fd_max, rh->fd); + events = 0; if (!JS_IsNull(rh->rw_func[0])) - FD_SET(rh->fd, &rfds); + events |= POLLIN; if (!JS_IsNull(rh->rw_func[1])) - FD_SET(rh->fd, &wfds); + events |= POLLOUT; + if (events) { + rh->poll_fd_index = nfds; + if (js_poll_add_poll_fd(ts, &nfds, rh->fd, events)) + return -1; + } } list_for_each(el, &ts->port_list) { JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); if (!JS_IsNull(port->on_message_func)) { JSWorkerMessagePipe *ps = port->recv_pipe; - fd_max = max_int(fd_max, ps->waker.read_fd); - FD_SET(ps->waker.read_fd, &rfds); + port->poll_fd_index = nfds; + if (js_poll_add_poll_fd(ts, &nfds, ps->waker.read_fd, POLLIN)) + return -1; } } - ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp); - if (ret > 0) { + nfds = poll(ts->poll_fds, nfds, min_delay); + if (nfds > 0) { list_for_each(el, &ts->os_rw_handlers) { rh = list_entry(el, JSOSRWHandler, link); if (!JS_IsNull(rh->rw_func[0]) && - FD_ISSET(rh->fd, &rfds)) { + (ts->poll_fds[rh->poll_fd_index].revents & (POLLERR | POLLHUP | POLLNVAL | POLLIN))) { call_handler(ctx, rh->rw_func[0]); /* must stop because the list may have been modified */ goto done; } if (!JS_IsNull(rh->rw_func[1]) && - FD_ISSET(rh->fd, &wfds)) { + (ts->poll_fds[rh->poll_fd_index].revents & (POLLERR | POLLHUP | POLLNVAL | POLLOUT))) { call_handler(ctx, rh->rw_func[1]); /* must stop because the list may have been modified */ goto done; @@ -2607,8 +2645,7 @@ static int js_os_poll(JSContext *ctx) list_for_each(el, &ts->port_list) { JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); if (!JS_IsNull(port->on_message_func)) { - JSWorkerMessagePipe *ps = port->recv_pipe; - if (FD_ISSET(ps->waker.read_fd, &rfds)) { + if (ts->poll_fds[port->poll_fd_index].revents != 0) { if (handle_posted_message(rt, ctx, port)) goto done; } @@ -3268,14 +3305,14 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (chdir(cwd) < 0) _exit(127); } - if (uid != -1) { - if (setuid(uid) < 0) - _exit(127); - } if (gid != -1) { if (setgid(gid) < 0) _exit(127); } + if (uid != -1) { + if (setuid(uid) < 0) + _exit(127); + } if (!file) file = exec_argv[0]; @@ -3309,7 +3346,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, for(i = 0; i < exec_argc; i++) JS_FreeCString(ctx, exec_argv[i]); js_free(ctx, exec_argv); - if (envp != environ) { + if (envp && envp != environ) { char **p; p = envp; while (*p != NULL) { @@ -3879,7 +3916,7 @@ void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)) #define OS_PLATFORM "win32" #elif defined(__APPLE__) #define OS_PLATFORM "darwin" -#elif defined(EMSCRIPTEN) +#elif defined(__EMSCRIPTEN__) #define OS_PLATFORM "js" #else #define OS_PLATFORM "linux" @@ -4164,6 +4201,10 @@ void js_std_free_handlers(JSRuntime *rt) } #endif +#if !defined(_WIN32) + free(ts->poll_fds); +#endif + free(ts); JS_SetRuntimeOpaque(rt, NULL); /* fail safe */ } diff --git a/vendor/quickjs/quickjs.c b/vendor/quickjs/quickjs.c index e32f0f3d..76c92591 100644 --- a/vendor/quickjs/quickjs.c +++ b/vendor/quickjs/quickjs.c @@ -49,7 +49,7 @@ #define OPTIMIZE 1 #define SHORT_OPCODES 1 -#if defined(EMSCRIPTEN) +#if defined(__EMSCRIPTEN__) #define DIRECT_DISPATCH 0 #else #define DIRECT_DISPATCH 1 @@ -68,11 +68,11 @@ /* define to include Atomics.* operations which depend on the OS threads */ -#if !defined(EMSCRIPTEN) +#if !defined(__EMSCRIPTEN__) #define CONFIG_ATOMICS #endif -#if !defined(EMSCRIPTEN) +#if !defined(__EMSCRIPTEN__) /* enable stack limitation */ #define CONFIG_STACK_CHECK #endif @@ -107,6 +107,8 @@ //#define DUMP_PROMISE //#define DUMP_READ_OBJECT //#define DUMP_ROPE_REBALANCE +/* add asm labels to each opcode so that it is easier to see the generated code */ +//#define OPCODE_ASM_LABEL /* test the GC by forcing it before each object allocation */ //#define FORCE_GC_AT_MALLOC @@ -168,6 +170,7 @@ enum { JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */ JS_CLASS_GENERATOR, /* u.generator_data */ JS_CLASS_GLOBAL_OBJECT, /* u.global_object */ + JS_CLASS_RAWJSON, JS_CLASS_PROXY, /* u.proxy_data */ JS_CLASS_PROMISE, /* u.promise_data */ JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */ @@ -235,9 +238,87 @@ typedef enum { typedef enum OPCodeEnum OPCodeEnum; -struct JSRuntime { +/* JS malloc */ + +#define JS_MALLOC_ALIGN 8 +#define JS_MALLOC_ARENA_SIZE 4096 +#define JS_MALLOC_BLOCK_SIZE_COUNT 31 +#define JS_MALLOC_MIN_SMALL_SIZE 16 +#define JS_MALLOC_MAX_SMALL_SIZE 512 +#if defined(__SANITIZE_ADDRESS__) || defined(__EMSCRIPTEN__) +/* quickjs-emscripten: use the host malloc() under Emscripten too, otherwise the + small-block allocator over-commits arenas and makes JS_SetMemoryLimit inaccurate. */ +#define JS_MALLOC_LARGE_BLOCKS_ONLY 1 +#else +#define JS_MALLOC_LARGE_BLOCKS_ONLY 0 +#endif + +/* allow iteration among the allocated blocks. Currently not used. May + be used to suppress the memory overhead of JSGCObjectHeader */ +//#define JS_MALLOC_USE_ITER + +#define FREE_NIL 0xffff + +/* 8 byte header */ +/* Notes: + - the header is necessary at least to recover a pointer to + JSMallocArena because we don't want to enforce a page + alignment on the system malloc(). + - could store the block offset instead of (block_idx, + block_size_idx), but it would require a division to recover the block + index. +*/ +typedef struct JSMallocBlockHeader { + union { + uint16_t block_idx; /* FREE_NIL if large block */ + uint16_t free_next; /* FREE_NIL if none */ + } u; + uint8_t block_size_idx; + uint8_t gc_obj_type : 7; + uint8_t mark : 1; + int ref_count; + __attribute__((aligned(JS_MALLOC_ALIGN))) uint8_t user_data[]; +} JSMallocBlockHeader; + +typedef struct JSMallocLargeBlockHeader { +#ifdef JS_MALLOC_USE_ITER + struct list_head link; +#endif + JSMallocBlockHeader header; +} JSMallocLargeBlockHeader; + +typedef struct { + struct list_head free_link; + struct list_head link; + uint8_t block_size_idx; + uint16_t n_used_blocks; /* number of allocated blocks */ + uint16_t n_blocks; /* total number of blocks */ + uint16_t first_free_block; /* FREE_NIL if none */ +#ifdef JS_MALLOC_USE_ITER + /* bit set to 1 for allocated block */ + uint32_t bitmap[((JS_MALLOC_ARENA_SIZE / JS_MALLOC_MIN_SMALL_SIZE) + 31) / 32]; +#endif + /* n_blocks memory blocks of identical size */ + __attribute__((aligned(JS_MALLOC_ALIGN))) uint8_t blocks[]; +} JSMallocArena; + +typedef struct { + struct list_head arena_list[JS_MALLOC_BLOCK_SIZE_COUNT]; /* list of JSMallocArena.link (all arenas) */ + struct list_head free_arena_list[JS_MALLOC_BLOCK_SIZE_COUNT]; /* list of JSMallocArena.free_link (arenas where n_used_blocks < n_blocks) */ +#ifdef JS_MALLOC_USE_ITER + struct list_head large_block_list; /* list of JSMallocLargeBlockHeader.link */ +#endif + __attribute__((aligned(JS_MALLOC_ALIGN))) uint8_t zero_size_block[sizeof(JSMallocBlockHeader)]; + + /* callbacks to the host malloc */ JSMallocFunctions mf; JSMallocState malloc_state; +} JSMallocContext; + +/* end JS Malloc */ + +struct JSRuntime { + JSMallocContext malloc_ctx; const char *rt_info; int atom_hash_size; /* power of two */ @@ -353,12 +434,6 @@ typedef enum { reference count that can reference other GC objects. JS Objects are a particular type of GC object. */ struct JSGCObjectHeader { - int ref_count; /* must come first, 32-bit */ - JSGCObjectTypeEnum gc_obj_type : 4; - uint8_t mark : 1; /* used by the GC */ - uint8_t dummy0: 3; - uint8_t dummy1; /* not used by the GC */ - uint16_t dummy2; /* not used by the GC */ struct list_head link; }; @@ -374,16 +449,10 @@ typedef struct { } JSWeakRefHeader; typedef struct JSVarRef { - union { - JSGCObjectHeader header; /* must come first */ - struct { - int __gc_ref_count; /* corresponds to header.ref_count */ - uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ - uint8_t is_detached; - uint8_t is_lexical; /* only used with global variables */ - uint8_t is_const; /* only used with global variables */ - }; - }; + JSGCObjectHeader header; /* must come first */ + uint8_t is_detached; + uint8_t is_lexical; /* only used with global variables */ + uint8_t is_const; /* only used with global variables */ JSValue *pvalue; /* pointer to the value, either on the stack or to 'value' */ union { @@ -420,7 +489,6 @@ typedef uint128_t js_dlimb_t; #endif typedef struct JSBigInt { - JSRefCountHeader header; /* must come first, 32-bit */ uint32_t len; /* number of limbs, >= 1 */ js_limb_t tab[]; /* two's complement representation, always normalized so that 'len' is the minimum @@ -514,7 +582,6 @@ typedef enum { #define JS_ATOM_HASH_PRIVATE JS_ATOM_HASH_MASK struct JSString { - JSRefCountHeader header; /* must come first, 32-bit */ uint32_t len : 31; uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */ /* for JS_ATOM_TYPE_SYMBOL: hash = weakref_count, atom_type = 3, @@ -533,7 +600,6 @@ struct JSString { }; typedef struct JSStringRope { - JSRefCountHeader header; /* must come first, 32-bit */ uint32_t len; uint8_t is_wide_char; /* 0 = 8 bits, 1 = 16 bits characters */ uint8_t depth; /* max depth of the rope tree */ @@ -907,47 +973,40 @@ typedef struct JSShapeProperty { } JSShapeProperty; struct JSShape { - /* hash table of size hash_mask + 1 before the start of the - structure (see prop_hash_end()). */ JSGCObjectHeader header; /* true if the shape is inserted in the shape hash table. If not, JSShape.hash is not valid */ uint8_t is_hashed; uint32_t hash; /* current hash value */ - uint32_t prop_hash_mask; + uint32_t prop_hash_mask; /* >= 2 */ int prop_size; /* allocated properties */ int prop_count; /* include deleted properties */ int deleted_prop_count; JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */ JSObject *proto; - JSShapeProperty prop[0]; /* prop_size elements */ + uint32_t hash_table[]; /* prop_hash_mask + 1 elements */ + /* followed by JSShapeProperty prop[prop_size]; */ }; struct JSObject { - union { - JSGCObjectHeader header; - struct { - int __gc_ref_count; /* corresponds to header.ref_count */ - uint8_t __gc_mark : 7; /* corresponds to header.mark/gc_obj_type */ - /* TRUE if the array prototype is "normal": - - no small index properties which are get/set or non writable - - its prototype is Object.prototype - - Object.prototype has no small index properties which are get/set or non writable - - the prototype of Object.prototype is null (always true as it is immutable) - */ - uint8_t is_std_array_prototype : 1; - - uint8_t extensible : 1; - uint8_t free_mark : 1; /* only used when freeing objects with cycles */ - uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */ - uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_MAPPED_ARGUMENTS and typed arrays) */ - uint8_t is_constructor : 1; /* TRUE if object is a constructor function */ - uint8_t has_immutable_prototype : 1; /* cannot modify the prototype */ - uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ - uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */ - uint16_t class_id; /* see JS_CLASS_x */ - }; - }; + JSGCObjectHeader header; + /* TRUE if the array prototype is "normal": + - no small index properties which are get/set or non writable + - its prototype is Object.prototype + - Object.prototype has no small index properties which are get/set or non writable + - the prototype of Object.prototype is null (always true as it is immutable) + */ + uint8_t is_std_array_prototype : 1; + + uint8_t extensible : 1; + uint8_t free_mark : 1; /* only used when freeing objects with cycles */ + uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */ + uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_MAPPED_ARGUMENTS and typed arrays) */ + uint8_t is_constructor : 1; /* TRUE if object is a constructor function */ + uint8_t has_immutable_prototype : 1; /* cannot modify the prototype */ + uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ + uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */ + uint16_t class_id; /* see JS_CLASS_x */ /* count the number of weak references to this object. The object structure is freed only if header.ref_count = 0 and weakref_count = 0 */ @@ -1207,7 +1266,7 @@ typedef enum JSStrictEqModeEnum { JS_EQ_SAME_VALUE_ZERO, } JSStrictEqModeEnum; -static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, +static BOOL js_strict_eq2(JSContext *ctx, JSValueConst op1, JSValueConst op2, JSStrictEqModeEnum eq_mode); static BOOL js_strict_eq(JSContext *ctx, JSValueConst op1, JSValueConst op2); static BOOL js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2); @@ -1344,6 +1403,10 @@ static JSValue js_error_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); static JSVarRef *js_global_object_find_uninitialized_var(JSContext *ctx, JSObject *p, JSAtom atom, BOOL is_lexical); +static int typed_array_init(JSContext *ctx, JSValueConst obj, + JSValue buffer, uint64_t offset, uint64_t len, + BOOL track_rab); + static const JSClassExoticMethods js_arguments_exotic_methods; static const JSClassExoticMethods js_string_exotic_methods; @@ -1351,56 +1414,415 @@ static const JSClassExoticMethods js_proxy_exotic_methods; static const JSClassExoticMethods js_module_ns_exotic_methods; static JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT; +/* JS malloc */ + +/* max overhead for size >= 64: 12.5% */ +static const uint16_t js_malloc_block_sizes[JS_MALLOC_BLOCK_SIZE_COUNT] = { + 16, + 24, + 32, + 40, + 48, + 56, + 64, + 72, + 80, + 88, + 96, + 104, + 112, + 120, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 288, + 320, + 352, + 384, + 416, + 448, + 480, + 512, +}; + +static int get_block_size_index(size_t size) +{ + if (size <= 16) { + return 0; + } else if (size <= 128) { + return (size + 7) / 8 - 2; + } else if (size <= 256) { + return (size + 15) / 16 + 6; + } else if (size <= 512) { + return (size + 31) / 32 + 14; + } else { + return JS_MALLOC_BLOCK_SIZE_COUNT; + } +} + +static JSMallocBlockHeader *get_zero_size_block(JSMallocContext *s) +{ + return (JSMallocBlockHeader *)s->zero_size_block; +} + +static void js_malloc_init(JSMallocContext *s) +{ + int i; + memset(s, 0, sizeof(*s)); + get_zero_size_block(s)->u.block_idx = FREE_NIL; + for(i = 0; i < JS_MALLOC_BLOCK_SIZE_COUNT; i++) { + init_list_head(&s->arena_list[i]); + init_list_head(&s->free_arena_list[i]); + } +#ifdef JS_MALLOC_USE_ITER + init_list_head(&s->large_block_list); +#endif +} + +static void *get_arena_block(JSMallocArena *ar, unsigned int idx, unsigned int block_size) +{ + return ar->blocks + idx * block_size; +} + +static inline JSMallocBlockHeader *js_rc(void *ptr) +{ + return container_of(ptr, JSMallocBlockHeader, user_data); +} + +static no_inline JSMallocArena *js_malloc_new_arena(JSMallocContext *s, int block_size_idx) +{ + JSMallocBlockHeader *b; + JSMallocArena *ar; + int n_blocks, block_size, i; + + block_size = js_malloc_block_sizes[block_size_idx]; + n_blocks = (JS_MALLOC_ARENA_SIZE - sizeof(JSMallocArena)) / block_size; + ar = s->mf.js_malloc(&s->malloc_state, sizeof(JSMallocArena) + n_blocks * block_size); + if (!ar) + return NULL; + + ar->block_size_idx = block_size_idx; + ar->n_blocks = n_blocks; + ar->n_used_blocks = 0; + ar->first_free_block = 0; +#ifdef JS_MALLOC_USE_ITER + { + int n_bitmap_words = (n_blocks + 31) / 32; + for(i = 0; i < n_bitmap_words; i++) + ar->bitmap[i] = 0; + } +#endif + for(i = 0; i < n_blocks - 1; i++) { + b = get_arena_block(ar, i, block_size); + b->u.free_next = i + 1; + b->block_size_idx = block_size_idx; + } + b = get_arena_block(ar, n_blocks - 1, block_size); + b->u.free_next = FREE_NIL; + b->block_size_idx = block_size_idx; + + /* add to the head */ + list_add(&ar->link, &s->arena_list[block_size_idx]); + list_add(&ar->free_link, &s->free_arena_list[block_size_idx]); + return ar; +} + +static no_inline void *js_malloc_large(JSMallocContext *s, size_t size) +{ + JSMallocLargeBlockHeader *b; + b = s->mf.js_malloc(&s->malloc_state, sizeof(JSMallocLargeBlockHeader) + size); + if (!b) + return NULL; + b->header.u.block_idx = FREE_NIL; + b->header.block_size_idx = 0xff; /* fail safe */ +#ifdef JS_MALLOC_USE_ITER + list_add_tail(&b->link, &s->large_block_list); +#endif + return b->header.user_data; +} + +static void *__js_malloc(JSMallocContext *s, size_t size) +{ + size_t total_size; + if (unlikely(size == 0)) { + JSMallocBlockHeader *b = get_zero_size_block(s); + return b->user_data; + } else { + total_size = ((size + JS_MALLOC_ALIGN - 1) & ~(JS_MALLOC_ALIGN - 1)) + + sizeof(JSMallocBlockHeader); + if (!JS_MALLOC_LARGE_BLOCKS_ONLY && + total_size <= JS_MALLOC_MAX_SMALL_SIZE) { + int block_size_idx; + unsigned int block_idx, block_size; + JSMallocBlockHeader *b; + JSMallocArena *ar; + struct list_head *el, *head; + + block_size_idx = get_block_size_index(total_size); + block_size = js_malloc_block_sizes[block_size_idx]; + head = &s->free_arena_list[block_size_idx]; + el = head->next; + if (unlikely(el == head)) { + ar = js_malloc_new_arena(s, block_size_idx); + if (!ar) + return NULL; + } else { + ar = list_entry(el, JSMallocArena, free_link); + } + block_idx = ar->first_free_block; + b = get_arena_block(ar, ar->first_free_block, block_size); + ar->first_free_block = b->u.free_next; + b->u.block_idx = block_idx; + ar->n_used_blocks++; + if (unlikely(ar->n_used_blocks == ar->n_blocks)) { + list_del(&ar->free_link); + } +#ifdef JS_MALLOC_USE_ITER + ar->bitmap[block_idx / 32] |= 1 << (block_idx % 32); +#endif + return b->user_data; + } else { + return js_malloc_large(s, size); + } + } +} + +static void __js_free(JSMallocContext *s, void *ptr) +{ + JSMallocBlockHeader *b; + + if (!ptr) + return; + b = container_of(ptr, JSMallocBlockHeader, user_data); + if (unlikely(b->u.block_idx == FREE_NIL)) { + /* large or zero size block */ + if (b == get_zero_size_block(s)) { + /* nothing to do */ + } else { + JSMallocLargeBlockHeader *lb = container_of(ptr, JSMallocLargeBlockHeader, header.user_data); +#ifdef JS_MALLOC_USE_ITER + list_del(&lb->link); +#endif + s->mf.js_free(&s->malloc_state, lb); + } + } else { + unsigned int block_idx = b->u.block_idx; + unsigned int block_size_idx = b->block_size_idx; + unsigned int block_size = js_malloc_block_sizes[block_size_idx]; + JSMallocArena *ar = (JSMallocArena *)((uint8_t *)b - block_size * block_idx - sizeof(JSMallocArena)); + b->u.free_next = ar->first_free_block; + ar->first_free_block = block_idx; +#ifdef JS_MALLOC_USE_ITER + ar->bitmap[block_idx / 32] &= ~(1 << (block_idx % 32)); +#endif + /* add back to the free list if needed */ + if (unlikely(ar->n_used_blocks == ar->n_blocks)) { + list_add(&ar->free_link, &s->free_arena_list[block_size_idx]); + } + ar->n_used_blocks--; + if (unlikely(ar->n_used_blocks == 0)) { + list_del(&ar->link); + list_del(&ar->free_link); + s->mf.js_free(&s->malloc_state, ar); + } + } +} + +static void *__js_realloc(JSMallocContext *s, void *ptr, size_t size) +{ + JSMallocBlockHeader *b; + if (ptr == NULL) { + return __js_malloc(s, size); + } else if (size == 0) { + __js_free(s, ptr); + return NULL; + } + b = container_of(ptr, JSMallocBlockHeader, user_data); + if (b->u.block_idx == FREE_NIL) { + if (b == get_zero_size_block(s)) { + return __js_malloc(s, size); + } else { + JSMallocLargeBlockHeader *lb, *new_lb; + lb = container_of(ptr, JSMallocLargeBlockHeader, header.user_data); +#ifdef JS_MALLOC_USE_ITER + list_del(&lb->link); +#endif + new_lb = s->mf.js_realloc(&s->malloc_state, lb, sizeof(JSMallocLargeBlockHeader) + size); + if (!new_lb) { +#ifdef JS_MALLOC_USE_ITER + /* add again in the list */ + list_add_tail(&lb->link, &s->large_block_list); +#endif + return NULL; + } + new_lb->header.u.block_idx = FREE_NIL; + new_lb->header.block_size_idx = 0xff; /* fail safe */ +#ifdef JS_MALLOC_USE_ITER + list_add_tail(&new_lb->link, &s->large_block_list); +#endif + return new_lb->header.user_data; + } + } else { + unsigned int block_size_idx = b->block_size_idx; + size_t block_size = js_malloc_block_sizes[block_size_idx]; + size_t total_size, old_size; + void *new_ptr; + JSMallocBlockHeader *new_b; + + total_size = ((size + JS_MALLOC_ALIGN - 1) & ~(JS_MALLOC_ALIGN - 1)) + + sizeof(JSMallocBlockHeader); + if (total_size <= block_size) + return ptr; + new_ptr = __js_malloc(s, size); + if (!new_ptr) + return NULL; + new_b = container_of(new_ptr, JSMallocBlockHeader, user_data); + /* copy the GC data */ + new_b->gc_obj_type = b->gc_obj_type; + new_b->mark = b->mark; + new_b->ref_count = b->ref_count; + /* copy the data */ + old_size = block_size - sizeof(JSMallocBlockHeader); + if (size > old_size) + size = old_size; + memcpy(new_ptr, ptr, size); + __js_free(s, ptr); + return new_ptr; + } +} + +static size_t __js_malloc_usable_size(JSMallocContext *s, const char *ptr) +{ + JSMallocBlockHeader *b; + if (!ptr) + return 0; + b = container_of(ptr, JSMallocBlockHeader, user_data); + if (b->u.block_idx == FREE_NIL) { + if (b == get_zero_size_block(s)) { + return 0; + } else { + JSMallocLargeBlockHeader *lb; + size_t size; + lb = container_of(ptr, JSMallocLargeBlockHeader, header.user_data); + if (s->mf.js_malloc_usable_size) { + size = s->mf.js_malloc_usable_size(lb); + if (size != 0) + size -= sizeof(JSMallocLargeBlockHeader); + return size; + } else { + return 0; + } + } + } else { + size_t block_size = js_malloc_block_sizes[b->block_size_idx]; + return block_size - sizeof(*b); + } +} + +static __maybe_unused void js_malloc_dump_arenas(JSMallocContext *s) +{ + struct list_head *el; + int block_size_idx; + + printf("%20s %10s %10s\n", "PTR", "BLK_SIZE", "ALLOC"); + for(block_size_idx = 0; block_size_idx < JS_MALLOC_BLOCK_SIZE_COUNT; block_size_idx++) { + int block_size = js_malloc_block_sizes[block_size_idx]; + list_for_each(el, &s->arena_list[block_size_idx]) { + JSMallocArena *ar = list_entry(el, JSMallocArena, link); + printf("%20p %10u %9.1f%%\n", + ar, block_size, + (double)ar->n_used_blocks / ar->n_blocks * 100); + } + } +} + +#ifdef JS_MALLOC_USE_ITER +typedef void JSMallocIterFunc(void *opaque, void *ptr); + +/* iterate thru allocated blocks. The allocated block list should not + be modified while iterating. */ +static __maybe_unused void js_malloc_iter(JSMallocContext *s, JSMallocIterFunc *iter_func, void *iter_opaque) +{ + struct list_head *el; + int block_size_idx; + int i, j, n_words; + uint32_t bmp; + + for(block_size_idx = 0; block_size_idx < JS_MALLOC_BLOCK_SIZE_COUNT; block_size_idx++) { + unsigned int block_size = js_malloc_block_sizes[block_size_idx]; + list_for_each(el, &s->arena_list[block_size_idx]) { + JSMallocArena *ar = list_entry(el, JSMallocArena, link); + n_words = (ar->n_blocks + 31) / 32; + for(i = 0; i < n_words; i++) { + bmp = ar->bitmap[i]; + while (bmp != 0) { + j = ctz32(bmp); + bmp &= ~(1 << j); + iter_func(iter_opaque, get_arena_block(ar, i * 32+ j, block_size)); + } + } + } + } + list_for_each(el, &s->large_block_list) { + JSMallocLargeBlockHeader *lb = list_entry(el, JSMallocLargeBlockHeader, link); + iter_func(iter_opaque, lb->header.user_data); + } +} +#endif + +/* end JS malloc */ + static void js_trigger_gc(JSRuntime *rt, size_t size) { BOOL force_gc; #ifdef FORCE_GC_AT_MALLOC force_gc = TRUE; #else - force_gc = ((rt->malloc_state.malloc_size + size) > + force_gc = ((rt->malloc_ctx.malloc_state.malloc_size + size) > rt->malloc_gc_threshold); #endif if (force_gc) { #ifdef DUMP_GC printf("GC: size=%" PRIu64 "\n", - (uint64_t)rt->malloc_state.malloc_size); + (uint64_t)rt->malloc_ctx.malloc_state.malloc_size); #endif JS_RunGC(rt); - rt->malloc_gc_threshold = rt->malloc_state.malloc_size + - (rt->malloc_state.malloc_size >> 1); + rt->malloc_gc_threshold = rt->malloc_ctx.malloc_state.malloc_size + + (rt->malloc_ctx.malloc_state.malloc_size >> 1); } } -static size_t js_malloc_usable_size_unknown(const void *ptr) -{ - return 0; -} - void *js_malloc_rt(JSRuntime *rt, size_t size) { - return rt->mf.js_malloc(&rt->malloc_state, size); + return __js_malloc(&rt->malloc_ctx, size); } void js_free_rt(JSRuntime *rt, void *ptr) { - rt->mf.js_free(&rt->malloc_state, ptr); + __js_free(&rt->malloc_ctx, ptr); } void *js_realloc_rt(JSRuntime *rt, void *ptr, size_t size) { - return rt->mf.js_realloc(&rt->malloc_state, ptr, size); + return __js_realloc(&rt->malloc_ctx, ptr, size); } size_t js_malloc_usable_size_rt(JSRuntime *rt, const void *ptr) { - return rt->mf.js_malloc_usable_size(ptr); + return __js_malloc_usable_size(&rt->malloc_ctx, ptr); } void *js_mallocz_rt(JSRuntime *rt, size_t size) { void *ptr; ptr = js_malloc_rt(rt, size); - if (!ptr) + if (unlikely(!ptr)) return NULL; return memset(ptr, 0, size); } @@ -1524,7 +1946,7 @@ static void *js_realloc_bytecode_rt(void *opaque, void *ptr, size_t size) avoid some overflows. */ return NULL; } else { - return rt->mf.js_realloc(&rt->malloc_state, ptr, size); + return js_realloc_rt(rt, ptr, size); } } @@ -1597,6 +2019,7 @@ static JSClassShortDef const js_std_class_def[] = { { JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_REGEXP_STRING_ITERATOR */ { JS_ATOM_Generator, js_generator_finalizer, js_generator_mark }, /* JS_CLASS_GENERATOR */ { JS_ATOM_Object, js_global_object_finalizer, js_global_object_mark }, /* JS_CLASS_GLOBAL_OBJECT */ + { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_RAWJSON */ }; static int init_class_range(JSRuntime *rt, JSClassShortDef const *tab, @@ -1655,12 +2078,9 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) if (!rt) return NULL; memset(rt, 0, sizeof(*rt)); - rt->mf = *mf; - if (!rt->mf.js_malloc_usable_size) { - /* use dummy function if none provided */ - rt->mf.js_malloc_usable_size = js_malloc_usable_size_unknown; - } - rt->malloc_state = ms; + js_malloc_init(&rt->malloc_ctx); + rt->malloc_ctx.mf = *mf; + rt->malloc_ctx.malloc_state = ms; rt->malloc_gc_threshold = 256 * 1024; init_list_head(&rt->context_list); @@ -1721,7 +2141,7 @@ static size_t js_def_malloc_usable_size(const void *ptr) return malloc_size(ptr); #elif defined(_WIN32) return _msize((void *)ptr); -#elif defined(EMSCRIPTEN) +#elif defined(__EMSCRIPTEN__) return 0; #elif defined(__linux__) || defined(__GLIBC__) return malloc_usable_size((void *)ptr); @@ -1801,7 +2221,7 @@ JSRuntime *JS_NewRuntime(void) void JS_SetMemoryLimit(JSRuntime *rt, size_t limit) { - rt->malloc_state.malloc_limit = limit; + rt->malloc_ctx.malloc_state.malloc_limit = limit; } /* use -1 to disable automatic GC */ @@ -1841,15 +2261,17 @@ int JS_GetStripInfo(JSRuntime *rt) return rt->strip_flags; } -/* return 0 if OK, < 0 if exception */ -int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, - int argc, JSValueConst *argv) +static int JS_EnqueueJob2(JSContext *ctx, JSJobFunc *job_func, + int argc, JSValueConst *argv, BOOL no_exception) { JSRuntime *rt = ctx->rt; JSJobEntry *e; int i; - e = js_malloc(ctx, sizeof(*e) + argc * sizeof(JSValue)); + if (no_exception) + e = js_malloc_rt(ctx->rt, sizeof(*e) + argc * sizeof(JSValue)); + else + e = js_malloc(ctx, sizeof(*e) + argc * sizeof(JSValue)); if (!e) return -1; e->realm = JS_DupContext(ctx); @@ -1862,6 +2284,13 @@ int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, return 0; } +/* return 0 if OK, < 0 if exception */ +int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, + int argc, JSValueConst *argv) +{ + return JS_EnqueueJob2(ctx, job_func, argc, argv, FALSE); +} + BOOL JS_IsJobPending(JSRuntime *rt) { return !list_empty(&rt->job_list); @@ -1899,7 +2328,7 @@ int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx) JS_FreeValue(ctx, res); js_free(ctx, e); if (pctx) { - if (ctx->header.ref_count > 1) + if (js_rc(ctx)->ref_count > 1) *pctx = ctx; else *pctx = NULL; @@ -1930,7 +2359,7 @@ static JSString *js_alloc_string_rt(JSRuntime *rt, int max_len, int is_wide_char str = js_malloc_rt(rt, sizeof(JSString) + (max_len << is_wide_char) + 1 - is_wide_char); if (unlikely(!str)) return NULL; - str->header.ref_count = 1; + js_rc(str)->ref_count = 1; str->is_wide_char = is_wide_char; str->len = max_len; str->atom_type = 0; @@ -1956,7 +2385,7 @@ static JSString *js_alloc_string(JSContext *ctx, int max_len, int is_wide_char) /* same as JS_FreeValueRT() but faster */ static inline void js_free_string(JSRuntime *rt, JSString *str) { - if (--str->header.ref_count <= 0) { + if (--js_rc(str)->ref_count <= 0) { if (str->atom_type) { JS_FreeAtomStruct(rt, str); } else { @@ -2005,14 +2434,14 @@ void JS_FreeRuntime(JSRuntime *rt) referenced externally */ list_for_each(el, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); - p->mark = 0; + js_rc(p)->mark = 0; } gc_decref(rt); header_done = FALSE; list_for_each(el, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); - if (p->ref_count != 0) { + if (js_rc(p)->ref_count != 0) { if (!header_done) { printf("Object leaks:\n"); JS_DumpObjectHeader(rt); @@ -2025,7 +2454,7 @@ void JS_FreeRuntime(JSRuntime *rt) count = 0; list_for_each(el, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); - if (p->ref_count == 0) { + if (js_rc(p)->ref_count == 0) { count++; } } @@ -2053,7 +2482,7 @@ void JS_FreeRuntime(JSRuntime *rt) for(i = 0; i < rt->atom_size; i++) { JSAtomStruct *p = rt->atom_array[i]; if (!atom_is_free(p) /* && p->str*/) { - if (i >= JS_ATOM_END || p->header.ref_count != 1) { + if (i >= JS_ATOM_END || js_rc(p)->ref_count != 1) { if (!header_done) { header_done = TRUE; if (rt->rt_info) { @@ -2067,7 +2496,7 @@ void JS_FreeRuntime(JSRuntime *rt) if (rt->rt_info) { printf(" "); } else { - printf(" %6u %6u ", i, p->header.ref_count); + printf(" %6u %6u ", i, js_rc(p)->ref_count); } switch (p->atom_type) { case JS_ATOM_TYPE_STRING: @@ -2091,7 +2520,7 @@ void JS_FreeRuntime(JSRuntime *rt) break; } if (rt->rt_info) { - printf(":%u", p->header.ref_count); + printf(":%u", js_rc(p)->ref_count); } else { printf("\n"); } @@ -2130,11 +2559,11 @@ void JS_FreeRuntime(JSRuntime *rt) if (rt->rt_info) { printf(" "); } else { - printf(" %6u ", str->header.ref_count); + printf(" %6u ", js_rc(str)->ref_count); } JS_DumpString(rt, str); if (rt->rt_info) { - printf(":%u", str->header.ref_count); + printf(":%u", js_rc(str)->ref_count); } else { printf("\n"); } @@ -2145,7 +2574,7 @@ void JS_FreeRuntime(JSRuntime *rt) printf("\n"); } { - JSMallocState *s = &rt->malloc_state; + JSMallocState *s = &rt->malloc_ctx.malloc_state; if (s->malloc_count > 1) { if (rt->rt_info) printf("%s:1: ", rt->rt_info); @@ -2157,8 +2586,8 @@ void JS_FreeRuntime(JSRuntime *rt) #endif { - JSMallocState ms = rt->malloc_state; - rt->mf.js_free(&ms, rt); + JSMallocState ms = rt->malloc_ctx.malloc_state; + rt->malloc_ctx.mf.js_free(&ms, rt); } } @@ -2170,7 +2599,7 @@ JSContext *JS_NewContextRaw(JSRuntime *rt) ctx = js_mallocz_rt(rt, sizeof(JSContext)); if (!ctx) return NULL; - ctx->header.ref_count = 1; + js_rc(ctx)->ref_count = 1; add_gc_object(rt, &ctx->header, JS_GC_OBJ_TYPE_JS_CONTEXT); ctx->class_proto = js_malloc_rt(rt, sizeof(ctx->class_proto[0]) * @@ -2281,7 +2710,7 @@ static void js_free_modules(JSContext *ctx, JSFreeModuleEnum flag) JSContext *JS_DupContext(JSContext *ctx) { - ctx->header.ref_count++; + js_rc(ctx)->ref_count++; return ctx; } @@ -2339,9 +2768,9 @@ void JS_FreeContext(JSContext *ctx) JSRuntime *rt = ctx->rt; int i; - if (--ctx->header.ref_count > 0) + if (--js_rc(ctx)->ref_count > 0) return; - assert(ctx->header.ref_count == 0); + assert(js_rc(ctx)->ref_count == 0); #ifdef DUMP_ATOMS JS_DumpAtoms(ctx->rt); @@ -2573,8 +3002,8 @@ static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p) printf(""); return; } - printf("%d", p->header.ref_count); - sep = (p->header.ref_count == 1) ? '\"' : '\''; + printf("%d", js_rc((void *)p)->ref_count); + sep = (js_rc((void *)p)->ref_count == 1) ? '\"' : '\''; putchar(sep); for(i = 0; i < p->len; i++) { JS_DumpChar(stdout, string_get(p, i), sep); @@ -2682,7 +3111,7 @@ static JSAtom JS_DupAtomRT(JSRuntime *rt, JSAtom v) if (!__JS_AtomIsConst(v)) { p = rt->atom_array[v]; - p->header.ref_count++; + js_rc(p)->ref_count++; } return v; } @@ -2695,7 +3124,7 @@ JSAtom JS_DupAtom(JSContext *ctx, JSAtom v) if (!__JS_AtomIsConst(v)) { rt = ctx->rt; p = rt->atom_array[v]; - p->header.ref_count++; + js_rc(p)->ref_count++; } return v; } @@ -2764,7 +3193,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) i = js_get_atom_index(rt, str); /* reduce string refcount and increase atom's unless constant */ if (__JS_AtomIsConst(i)) - str->header.ref_count--; + js_rc(str)->ref_count--; return i; } /* try and locate an already registered atom */ @@ -2780,7 +3209,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) p->len == len && js_string_memcmp(p, 0, str, 0, len) == 0) { if (!__JS_AtomIsConst(i)) - p->header.ref_count++; + js_rc(p)->ref_count++; goto done; } i = p->hash_next; @@ -2820,7 +3249,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) js_free_rt(rt, new_array); goto fail; } - p->header.ref_count = 1; /* not refcounted */ + js_rc(p)->ref_count = 1; /* not refcounted */ p->atom_type = JS_ATOM_TYPE_SYMBOL; #ifdef DUMP_LEAKS list_add_tail(&p->link, &rt->string_list); @@ -2852,7 +3281,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) 1 - str->is_wide_char); if (unlikely(!p)) goto fail; - p->header.ref_count = 1; + js_rc(p)->ref_count = 1; p->is_wide_char = str->is_wide_char; p->len = str->len; #ifdef DUMP_LEAKS @@ -2866,7 +3295,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) p = js_malloc_rt(rt, sizeof(JSAtomStruct)); /* empty wide string */ if (!p) return JS_ATOM_NULL; - p->header.ref_count = 1; + js_rc(p)->ref_count = 1; p->is_wide_char = 1; /* Hack to represent NULL as a JSString */ p->len = 0; #ifdef DUMP_LEAKS @@ -2935,7 +3364,7 @@ static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len, p->is_wide_char == 0 && memcmp(p->u.str8, str, len) == 0) { if (!__JS_AtomIsConst(i)) - p->header.ref_count++; + js_rc(p)->ref_count++; return i; } i = p->hash_next; @@ -2947,7 +3376,7 @@ static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p) { #if 0 /* JS_ATOM_NULL is not refcounted: __JS_AtomIsConst() includes 0 */ if (unlikely(i == JS_ATOM_NULL)) { - p->header.ref_count = INT32_MAX / 2; + js_rc(p)->ref_count = INT32_MAX / 2; return; } #endif @@ -2997,7 +3426,7 @@ static void __JS_FreeAtom(JSRuntime *rt, uint32_t i) JSAtomStruct *p; p = rt->atom_array[i]; - if (--p->header.ref_count > 0) + if (--js_rc(p)->ref_count > 0) return; JS_FreeAtomStruct(rt, p); } @@ -4247,7 +4676,7 @@ static BOOL JS_ConcatStringInPlace(JSContext *ctx, JSString *p1, JSValueConst op if (p2->len == 0) return TRUE; - if (p1->header.ref_count != 1) + if (js_rc(p1)->ref_count != 1) return FALSE; size1 = js_malloc_usable_size(ctx, p1); if (p1->is_wide_char) { @@ -4420,7 +4849,7 @@ static JSValue js_linearize_string_rope(JSContext *ctx, JSValue rope) if (string_buffer_concat_value(b, rope)) goto fail; ret = string_buffer_end(b); - if (r->header.ref_count > 1) { + if (js_rc(r)->ref_count > 1) { /* update the rope so that it won't need to be linearized again */ JS_FreeValue(ctx, r->left); JS_FreeValue(ctx, r->right); @@ -4473,7 +4902,7 @@ static JSValue js_new_string_rope(JSContext *ctx, JSValue op1, JSValue op2) r = js_malloc(ctx, sizeof(*r)); if (!r) goto fail; - r->header.ref_count = 1; + js_rc(r)->ref_count = 1; r->len = len; r->is_wide_char = is_wide_char; r->depth = depth + 1; @@ -4692,28 +5121,13 @@ static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2) static inline size_t get_shape_size(size_t hash_size, size_t prop_size) { - return hash_size * sizeof(uint32_t) + sizeof(JSShape) + + return sizeof(JSShape) + hash_size * sizeof(uint32_t) + prop_size * sizeof(JSShapeProperty); } -static inline JSShape *get_shape_from_alloc(void *sh_alloc, size_t hash_size) -{ - return (JSShape *)(void *)((uint32_t *)sh_alloc + hash_size); -} - -static inline uint32_t *prop_hash_end(JSShape *sh) -{ - return (uint32_t *)sh; -} - -static inline void *get_alloc_from_shape(JSShape *sh) -{ - return prop_hash_end(sh) - ((intptr_t)sh->prop_hash_mask + 1); -} - static inline JSShapeProperty *get_shape_prop(JSShape *sh) { - return sh->prop; + return (JSShapeProperty *)((uint32_t *)(sh + 1) + sh->prop_hash_mask + 1); } static int init_shape_hash(JSRuntime *rt) @@ -4802,20 +5216,17 @@ static inline JSShape *js_new_shape_nohash(JSContext *ctx, JSObject *proto, int hash_size, int prop_size) { JSRuntime *rt = ctx->rt; - void *sh_alloc; JSShape *sh; - sh_alloc = js_malloc(ctx, get_shape_size(hash_size, prop_size)); - if (!sh_alloc) + sh = js_malloc(ctx, get_shape_size(hash_size, prop_size)); + if (!sh) return NULL; - sh = get_shape_from_alloc(sh_alloc, hash_size); - sh->header.ref_count = 1; + js_rc(sh)->ref_count = 1; add_gc_object(rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); if (proto) JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, proto)); sh->proto = proto; - memset(prop_hash_end(sh) - hash_size, 0, sizeof(prop_hash_end(sh)[0]) * - hash_size); + memset(sh->hash_table, 0, sizeof(sh->hash_table[0]) * hash_size); sh->prop_hash_mask = hash_size - 1; sh->prop_size = prop_size; sh->prop_count = 0; @@ -4858,20 +5269,18 @@ static JSShape *js_new_shape(JSContext *ctx, JSObject *proto) static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1) { JSShape *sh; - void *sh_alloc, *sh_alloc1; size_t size; JSShapeProperty *pr; uint32_t i, hash_size; hash_size = sh1->prop_hash_mask + 1; size = get_shape_size(hash_size, sh1->prop_size); - sh_alloc = js_malloc(ctx, size); - if (!sh_alloc) + sh = js_malloc(ctx, size); + if (!sh) return NULL; - sh_alloc1 = get_alloc_from_shape(sh1); - memcpy(sh_alloc, sh_alloc1, size); - sh = get_shape_from_alloc(sh_alloc, hash_size); - sh->header.ref_count = 1; + memcpy(&sh->header + 1, &sh1->header + 1, + size - sizeof(JSGCObjectHeader)); + js_rc(sh)->ref_count = 1; add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); sh->is_hashed = FALSE; if (sh->proto) { @@ -4885,7 +5294,7 @@ static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1) static JSShape *js_dup_shape(JSShape *sh) { - sh->header.ref_count++; + js_rc(sh)->ref_count++; return sh; } @@ -4894,7 +5303,7 @@ static void js_free_shape0(JSRuntime *rt, JSShape *sh) uint32_t i; JSShapeProperty *pr; - assert(sh->header.ref_count == 0); + assert(js_rc(sh)->ref_count == 0); if (sh->is_hashed) js_shape_hash_unlink(rt, sh); if (sh->proto != NULL) { @@ -4906,12 +5315,12 @@ static void js_free_shape0(JSRuntime *rt, JSShape *sh) pr++; } remove_gc_object(&sh->header); - js_free_rt(rt, get_alloc_from_shape(sh)); + js_free_rt(rt, sh); } static void js_free_shape(JSRuntime *rt, JSShape *sh) { - if (unlikely(--sh->header.ref_count <= 0)) { + if (unlikely(--js_rc(sh)->ref_count <= 0)) { js_free_shape0(rt, sh); } } @@ -4929,7 +5338,6 @@ static no_inline int resize_properties(JSContext *ctx, JSShape **psh, JSShape *sh; uint32_t new_size, new_hash_size, new_hash_mask, i; JSShapeProperty *pr; - void *sh_alloc; intptr_t h; JSShape *old_sh; @@ -4950,35 +5358,41 @@ static no_inline int resize_properties(JSContext *ctx, JSShape **psh, /* resize the property shapes. Using js_realloc() is not possible in case the GC runs during the allocation */ old_sh = sh; - sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); - if (!sh_alloc) + sh = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh) return -1; - sh = get_shape_from_alloc(sh_alloc, new_hash_size); - list_del(&old_sh->header.link); - /* copy all the shape properties */ - memcpy(sh, old_sh, - sizeof(JSShape) + sizeof(sh->prop[0]) * old_sh->prop_count); - list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + remove_gc_object(&old_sh->header); + + js_rc(sh)->ref_count = 1; + add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); + memcpy(&sh->header + 1, &old_sh->header + 1, + sizeof(JSShape) - sizeof(JSGCObjectHeader)); + if (new_hash_size != (sh->prop_hash_mask + 1)) { /* resize the hash table and the properties */ new_hash_mask = new_hash_size - 1; sh->prop_hash_mask = new_hash_mask; - memset(prop_hash_end(sh) - new_hash_size, 0, - sizeof(prop_hash_end(sh)[0]) * new_hash_size); - for(i = 0, pr = sh->prop; i < sh->prop_count; i++, pr++) { + memset(sh->hash_table, 0, + sizeof(sh->hash_table[0]) * new_hash_size); + memcpy(get_shape_prop(sh), get_shape_prop(old_sh), + sizeof(JSShapeProperty) * old_sh->prop_count); + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { if (pr->atom != JS_ATOM_NULL) { h = ((uintptr_t)pr->atom & new_hash_mask); - pr->hash_next = prop_hash_end(sh)[-h - 1]; - prop_hash_end(sh)[-h - 1] = i + 1; + pr->hash_next = sh->hash_table[h]; + sh->hash_table[h] = i + 1; } } } else { - /* just copy the previous hash table */ - memcpy(prop_hash_end(sh) - new_hash_size, prop_hash_end(old_sh) - new_hash_size, - sizeof(prop_hash_end(sh)[0]) * new_hash_size); + /* just copy the previous hash table and the properties */ + memcpy(sh->hash_table, old_sh->hash_table, + sizeof(sh->hash_table[0]) * new_hash_size); + + memcpy(get_shape_prop(sh), get_shape_prop(old_sh), + sizeof(JSShapeProperty) * old_sh->prop_count); } - js_free(ctx, get_alloc_from_shape(old_sh)); + js_free(ctx, old_sh); *psh = sh; sh->prop_size = new_size; return 0; @@ -4988,7 +5402,6 @@ static no_inline int resize_properties(JSContext *ctx, JSShape **psh, static int compact_properties(JSContext *ctx, JSObject *p) { JSShape *sh, *old_sh; - void *sh_alloc; intptr_t h; uint32_t new_hash_size, i, j, new_hash_mask, new_size; JSShapeProperty *old_pr, *pr; @@ -5008,28 +5421,31 @@ static int compact_properties(JSContext *ctx, JSObject *p) /* resize the hash table and the properties */ old_sh = sh; - sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); - if (!sh_alloc) + sh = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh) return -1; - sh = get_shape_from_alloc(sh_alloc, new_hash_size); - list_del(&old_sh->header.link); - memcpy(sh, old_sh, sizeof(JSShape)); - list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + remove_gc_object(&old_sh->header); - memset(prop_hash_end(sh) - new_hash_size, 0, - sizeof(prop_hash_end(sh)[0]) * new_hash_size); + js_rc(sh)->ref_count = 1; + add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); + + memcpy(&sh->header + 1, &old_sh->header + 1, + sizeof(JSShape) - sizeof(JSGCObjectHeader)); + + memset(sh->hash_table, 0, sizeof(sh->hash_table[0]) * new_hash_size); + sh->prop_hash_mask = new_hash_mask; j = 0; - old_pr = old_sh->prop; - pr = sh->prop; + old_pr = get_shape_prop(old_sh); + pr = get_shape_prop(sh); prop = p->prop; for(i = 0; i < sh->prop_count; i++) { if (old_pr->atom != JS_ATOM_NULL) { pr->atom = old_pr->atom; pr->flags = old_pr->flags; h = ((uintptr_t)old_pr->atom & new_hash_mask); - pr->hash_next = prop_hash_end(sh)[-h - 1]; - prop_hash_end(sh)[-h - 1] = j + 1; + pr->hash_next = sh->hash_table[h]; + sh->hash_table[h] = j + 1; prop[j] = prop[i]; j++; pr++; @@ -5037,13 +5453,12 @@ static int compact_properties(JSContext *ctx, JSObject *p) old_pr++; } assert(j == (sh->prop_count - sh->deleted_prop_count)); - sh->prop_hash_mask = new_hash_mask; sh->prop_size = new_size; sh->deleted_prop_count = 0; sh->prop_count = j; p->shape = sh; - js_free(ctx, get_alloc_from_shape(old_sh)); + js_free(ctx, old_sh); /* reduce the size of the object properties */ new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); @@ -5090,8 +5505,8 @@ static int add_shape_property(JSContext *ctx, JSShape **psh, /* add in hash table */ hash_mask = sh->prop_hash_mask; h = atom & hash_mask; - pr->hash_next = prop_hash_end(sh)[-h - 1]; - prop_hash_end(sh)[-h - 1] = sh->prop_count; + pr->hash_next = sh->hash_table[h]; + sh->hash_table[h] = sh->prop_count; return 0; } @@ -5132,13 +5547,15 @@ static JSShape *find_hashed_shape_prop(JSRuntime *rt, JSShape *sh, if (sh1->hash == h && sh1->proto == sh->proto && sh1->prop_count == ((n = sh->prop_count) + 1)) { + JSShapeProperty *prop = get_shape_prop(sh); + JSShapeProperty *prop1 = get_shape_prop(sh1); for(i = 0; i < n; i++) { - if (unlikely(sh1->prop[i].atom != sh->prop[i].atom) || - unlikely(sh1->prop[i].flags != sh->prop[i].flags)) + if (unlikely(prop1[i].atom != prop[i].atom) || + unlikely(prop1[i].flags != prop[i].flags)) goto next; } - if (unlikely(sh1->prop[n].atom != atom) || - unlikely(sh1->prop[n].flags != prop_flags)) + if (unlikely(prop1[n].atom != atom) || + unlikely(prop1[n].flags != prop_flags)) goto next; return sh1; } @@ -5154,11 +5571,11 @@ static __maybe_unused void JS_DumpShape(JSRuntime *rt, int i, JSShape *sh) /* XXX: should output readable class prototype */ printf("%5d %3d%c %14p %5d %5d", i, - sh->header.ref_count, " *"[sh->is_hashed], + js_rc(sh)->ref_count, " *"[sh->is_hashed], (void *)sh->proto, sh->prop_size, sh->prop_count); for(j = 0; j < sh->prop_count; j++) { printf(" %s", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), - sh->prop[j].atom)); + get_shape_prop(sh)[j].atom)); } printf("\n"); } @@ -5182,7 +5599,7 @@ static __maybe_unused void JS_DumpShapes(JSRuntime *rt) /* dump non-hashed shapes */ list_for_each(el, &rt->gc_obj_list) { gp = list_entry(el, JSGCObjectHeader, link); - if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + if (js_rc(gp)->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { p = (JSObject *)gp; if (!p->shape->is_hashed) { JS_DumpShape(rt, -1, p->shape); @@ -5306,7 +5723,7 @@ static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID clas } break; } - p->header.ref_count = 1; + js_rc(p)->ref_count = 1; add_gc_object(ctx->rt, &p->header, JS_GC_OBJ_TYPE_JS_OBJECT); if (props) { for(i = 0; i < sh->prop_count; i++) @@ -5704,7 +6121,7 @@ static force_inline JSShapeProperty *find_own_property1(JSObject *p, intptr_t h; sh = p->shape; h = (uintptr_t)atom & sh->prop_hash_mask; - h = prop_hash_end(sh)[-h - 1]; + h = sh->hash_table[h]; prop = get_shape_prop(sh); while (h) { pr = &prop[h - 1]; @@ -5725,7 +6142,7 @@ static force_inline JSShapeProperty *find_own_property(JSProperty **ppr, intptr_t h; sh = p->shape; h = (uintptr_t)atom & sh->prop_hash_mask; - h = prop_hash_end(sh)[-h - 1]; + h = sh->hash_table[h]; prop = get_shape_prop(sh); while (h) { pr = &prop[h - 1]; @@ -5748,8 +6165,8 @@ static void set_cycle_flag(JSContext *ctx, JSValueConst obj) static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref) { if (var_ref) { - assert(var_ref->header.ref_count > 0); - if (--var_ref->header.ref_count == 0) { + assert(js_rc(var_ref)->ref_count > 0); + if (--js_rc(var_ref)->ref_count == 0) { if (var_ref->is_detached) { JS_FreeValueRT(rt, var_ref->value); } else { @@ -5958,7 +6375,7 @@ static void free_object(JSRuntime *rt, JSObject *p) remove_gc_object(&p->header); if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES) { - if (p->header.ref_count == 0 && p->weakref_count == 0) { + if (js_rc(p)->ref_count == 0 && p->weakref_count == 0) { js_free_rt(rt, p); } else { /* keep the object structure because there are may be @@ -5970,14 +6387,14 @@ static void free_object(JSRuntime *rt, JSObject *p) if (p->weakref_count == 0) { js_free_rt(rt, p); } else { - p->header.mark = 0; /* reset the mark so that the weakref can be freed */ + js_rc(p)->mark = 0; /* reset the mark so that the weakref can be freed */ } } } static void free_gc_object(JSRuntime *rt, JSGCObjectHeader *gp) { - switch(gp->gc_obj_type) { + switch(js_rc(gp)->gc_obj_type) { case JS_GC_OBJ_TYPE_JS_OBJECT: free_object(rt, (JSObject *)gp); break; @@ -6006,7 +6423,7 @@ static void free_zero_refcount(JSRuntime *rt) if (el == &rt->gc_zero_ref_count_list) break; p = list_entry(el, JSGCObjectHeader, link); - assert(p->ref_count == 0); + assert(js_rc(p)->ref_count == 0); free_gc_object(rt, p); } rt->gc_phase = JS_GC_PHASE_NONE; @@ -6060,7 +6477,7 @@ void __JS_FreeValueRT(JSRuntime *rt, JSValue v) if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { list_del(&p->link); list_add(&p->link, &rt->gc_zero_ref_count_list); - p->mark = 1; /* indicate that the object is about to be freed */ + js_rc(p)->mark = 1; /* indicate that the object is about to be freed */ if (rt->gc_phase == JS_GC_PHASE_NONE) { free_zero_refcount(rt); } @@ -6124,8 +6541,8 @@ static void gc_remove_weak_objects(JSRuntime *rt) static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, JSGCObjectTypeEnum type) { - h->mark = 0; - h->gc_obj_type = type; + js_rc(h)->mark = 0; + js_rc(h)->gc_obj_type = type; list_add_tail(&h->link, &rt->gc_obj_list); } @@ -6152,7 +6569,7 @@ void JS_MarkValue(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp, JS_MarkFunc *mark_func) { - switch(gp->gc_obj_type) { + switch(js_rc(gp)->gc_obj_type) { case JS_GC_OBJ_TYPE_JS_OBJECT: { JSObject *p = (JSObject *)gp; @@ -6270,9 +6687,9 @@ static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp, static void gc_decref_child(JSRuntime *rt, JSGCObjectHeader *p) { - assert(p->ref_count > 0); - p->ref_count--; - if (p->ref_count == 0 && p->mark == 1) { + assert(js_rc(p)->ref_count > 0); + js_rc(p)->ref_count--; + if (js_rc(p)->ref_count == 0 && js_rc(p)->mark == 1) { list_del(&p->link); list_add_tail(&p->link, &rt->tmp_obj_list); } @@ -6290,10 +6707,10 @@ static void gc_decref(JSRuntime *rt) tmp_obj_list */ list_for_each_safe(el, el1, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); - assert(p->mark == 0); + assert(js_rc(p)->mark == 0); mark_children(rt, p, gc_decref_child); - p->mark = 1; - if (p->ref_count == 0) { + js_rc(p)->mark = 1; + if (js_rc(p)->ref_count == 0) { list_del(&p->link); list_add_tail(&p->link, &rt->tmp_obj_list); } @@ -6302,19 +6719,19 @@ static void gc_decref(JSRuntime *rt) static void gc_scan_incref_child(JSRuntime *rt, JSGCObjectHeader *p) { - p->ref_count++; - if (p->ref_count == 1) { + js_rc(p)->ref_count++; + if (js_rc(p)->ref_count == 1) { /* ref_count was 0: remove from tmp_obj_list and add at the end of gc_obj_list */ list_del(&p->link); list_add_tail(&p->link, &rt->gc_obj_list); - p->mark = 0; /* reset the mark for the next GC call */ + js_rc(p)->mark = 0; /* reset the mark for the next GC call */ } } static void gc_scan_incref_child2(JSRuntime *rt, JSGCObjectHeader *p) { - p->ref_count++; + js_rc(p)->ref_count++; } static void gc_scan(JSRuntime *rt) @@ -6325,8 +6742,8 @@ static void gc_scan(JSRuntime *rt) /* keep the objects with a refcount > 0 and their children. */ list_for_each(el, &rt->gc_obj_list) { p = list_entry(el, JSGCObjectHeader, link); - assert(p->ref_count > 0); - p->mark = 0; /* reset the mark for the next GC call */ + assert(js_rc(p)->ref_count > 0); + js_rc(p)->mark = 0; /* reset the mark for the next GC call */ mark_children(rt, p, gc_scan_incref_child); } @@ -6355,7 +6772,7 @@ static void gc_free_cycles(JSRuntime *rt) /* Only need to free the GC object associated with JS values or async functions. The rest will be automatically removed because they must be referenced by them. */ - switch(p->gc_obj_type) { + switch(js_rc(p)->gc_obj_type) { case JS_GC_OBJ_TYPE_JS_OBJECT: case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: @@ -6380,14 +6797,14 @@ static void gc_free_cycles(JSRuntime *rt) list_for_each_safe(el, el1, &rt->gc_zero_ref_count_list) { p = list_entry(el, JSGCObjectHeader, link); - assert(p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT || - p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE || - p->gc_obj_type == JS_GC_OBJ_TYPE_ASYNC_FUNCTION || - p->gc_obj_type == JS_GC_OBJ_TYPE_MODULE); - if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT && + assert(js_rc(p)->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT || + js_rc(p)->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE || + js_rc(p)->gc_obj_type == JS_GC_OBJ_TYPE_ASYNC_FUNCTION || + js_rc(p)->gc_obj_type == JS_GC_OBJ_TYPE_MODULE); + if (js_rc(p)->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT && ((JSObject *)p)->weakref_count != 0) { /* keep the object because there are weak references to it */ - p->mark = 0; + js_rc(p)->mark = 0; } else { js_free_rt(rt, p); } @@ -6451,7 +6868,7 @@ static void compute_value_size(JSValueConst val, JSMemoryUsage_helper *hp); static void compute_jsstring_size(JSString *str, JSMemoryUsage_helper *hp) { if (!str->atom_type) { /* atoms are handled separately */ - double s_ref_count = str->header.ref_count; + double s_ref_count = js_rc(str)->ref_count; hp->str_count += 1 / s_ref_count; hp->str_size += ((sizeof(*str) + (str->len << str->is_wide_char) + 1 - str->is_wide_char) / s_ref_count); @@ -6516,9 +6933,9 @@ void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) JSMemoryUsage_helper mem = { 0 }, *hp = &mem; memset(s, 0, sizeof(*s)); - s->malloc_count = rt->malloc_state.malloc_count; - s->malloc_size = rt->malloc_state.malloc_size; - s->malloc_limit = rt->malloc_state.malloc_limit; + s->malloc_count = rt->malloc_ctx.malloc_state.malloc_count; + s->malloc_size = rt->malloc_ctx.malloc_state.malloc_size; + s->malloc_limit = rt->malloc_ctx.malloc_state.malloc_limit; s->memory_used_count = 2; /* rt + rt->class_array */ s->memory_used_size = sizeof(JSRuntime) + sizeof(JSValue) * rt->class_count; @@ -6578,10 +6995,10 @@ void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) JSShapeProperty *prs; /* XXX: could count the other GC object types too */ - if (gp->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) { + if (js_rc(gp)->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) { compute_bytecode_size((JSFunctionBytecode *)gp, hp); continue; - } else if (gp->gc_obj_type != JS_GC_OBJ_TYPE_JS_OBJECT) { + } else if (js_rc(gp)->gc_obj_type != JS_GC_OBJ_TYPE_JS_OBJECT) { continue; } p = (JSObject *)gp; @@ -6659,7 +7076,7 @@ void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) s->js_func_size += b->closure_var_count * sizeof(*var_refs); for (i = 0; i < b->closure_var_count; i++) { if (var_refs[i]) { - double ref_count = var_refs[i]->header.ref_count; + double ref_count = js_rc(var_refs[i])->ref_count; s->memory_used_count += 1 / ref_count; s->js_func_size += sizeof(*var_refs[i]) / ref_count; /* handle non object closed values */ @@ -6845,7 +7262,7 @@ void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) list_for_each(el, &rt->gc_obj_list) { JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); JSObject *p; - if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + if (js_rc(gp)->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { p = (JSObject *)gp; obj_classes[min_uint32(p->class_id, JS_CLASS_INIT_COUNT)]++; } @@ -7774,7 +8191,7 @@ static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, /* WARNING: a varref is returned as a string ! */ prs->flags |= JS_PROP_VARREF; pr->u.var_ref = JS_VALUE_GET_PTR(val); - pr->u.var_ref->header.ref_count++; + js_rc(pr->u.var_ref)->ref_count++; } else if (p->class_id == JS_CLASS_GLOBAL_OBJECT) { JSVarRef *var_ref; /* in the global object we use references */ @@ -8804,7 +9221,7 @@ static JSProperty *add_property(JSContext *ctx, p->shape = js_dup_shape(new_sh); js_free_shape(ctx->rt, sh); return &p->prop[new_sh->prop_count - 1]; - } else if (sh->header.ref_count != 1) { + } else if (js_rc(sh)->ref_count != 1) { /* if the shape is shared, clone it */ new_sh = js_clone_shape(ctx, sh); if (!new_sh) @@ -8816,7 +9233,7 @@ static JSProperty *add_property(JSContext *ctx, p->shape = new_sh; } } - assert(p->shape->header.ref_count == 1); + assert(js_rc(p->shape)->ref_count == 1); if (add_shape_property(ctx, &p->shape, p, prop, prop_flags)) return NULL; return &p->prop[p->shape->prop_count - 1]; @@ -8877,14 +9294,14 @@ static int remove_global_object_property(JSContext *ctx, JSObject *p, JSProperty *pr1; var_ref = pr->u.var_ref; - if (var_ref->header.ref_count == 1) + if (js_rc(var_ref)->ref_count == 1) return 0; p1 = JS_VALUE_GET_OBJ(p->u.global_object.uninitialized_vars); pr1 = add_property(ctx, p1, prs->atom, JS_PROP_C_W_E | JS_PROP_VARREF); if (!pr1) return -1; pr1->u.var_ref = var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; JS_FreeValue(ctx, var_ref->value); var_ref->is_lexical = FALSE; var_ref->is_const = FALSE; @@ -8903,7 +9320,7 @@ static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) redo: sh = p->shape; h1 = atom & sh->prop_hash_mask; - h = prop_hash_end(sh)[-h1 - 1]; + h = sh->hash_table[h1]; prop = get_shape_prop(sh); lpr = NULL; lpr_idx = 0; /* prevent warning */ @@ -8924,7 +9341,7 @@ static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) lpr = get_shape_prop(sh) + lpr_idx; lpr->hash_next = pr->hash_next; } else { - prop_hash_end(sh)[-h1 - 1] = pr->hash_next; + sh->hash_table[h1] = pr->hash_next; } sh->deleted_prop_count++; /* free the entry */ @@ -9025,7 +9442,7 @@ static int set_array_length(JSContext *ctx, JSObject *p, JSValue val, if (ret) return -1; /* JS_ToArrayLengthFree() must be done before the read-only test */ - if (unlikely(!(p->shape->prop[0].flags & JS_PROP_WRITABLE))) + if (unlikely(!(get_shape_prop(p->shape)[0].flags & JS_PROP_WRITABLE))) return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); if (likely(p->fast_array)) { @@ -9153,14 +9570,15 @@ static inline int add_fast_array_element(JSContext *ctx, JSObject *p, return TRUE; } -/* Allocate a new fast array. Its 'length' property is set to zero. It - maximum size is 2^31-1 elements. For convenience, 'len' is a 64 bit - integer. WARNING: the content of the array is not initialized. */ +/* Allocate a new fast array initialized to JS_UNDEFINED. Its maximum + size is 2^31-1 elements. For convenience, 'len' is a 64 bit + integer. */ static JSValue js_allocate_fast_array(JSContext *ctx, int64_t len) { JSValue arr; JSObject *p; - + int i; + if (len > INT32_MAX) return JS_ThrowRangeError(ctx, "invalid array length"); arr = JS_NewArray(ctx); @@ -9173,6 +9591,10 @@ static JSValue js_allocate_fast_array(JSContext *ctx, int64_t len) return JS_EXCEPTION; } p->u.array.count = len; + for(i = 0; i < len; i++) + p->u.array.u.values[i] = JS_UNDEFINED; + /* update the 'length' field */ + set_value(ctx, &p->prop[0].u.value, JS_NewInt32(ctx, len)); } return arr; } @@ -9801,7 +10223,7 @@ static int JS_CreateProperty(JSContext *ctx, JSObject *p, if (prs1) { delete_obj = p1; var_ref = pr1->u.var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; } else { var_ref = js_create_var_ref(ctx, FALSE); if (!var_ref) @@ -9886,7 +10308,7 @@ static int js_shape_prepare_update(JSContext *ctx, JSObject *p, sh = p->shape; if (sh->is_hashed) { - if (sh->header.ref_count != 1) { + if (js_rc(sh)->ref_count != 1) { if (pprs) idx = *pprs - get_shape_prop(sh); /* clone the shape (the resulting one is no longer hashed) */ @@ -11177,7 +11599,7 @@ static JSBigInt *js_bigint_new(JSContext *ctx, int len) r = js_malloc(ctx, sizeof(JSBigInt) + len * sizeof(js_limb_t)); if (!r) return NULL; - r->header.ref_count = 1; + js_rc(r)->ref_count = 1; r->len = len; return r; } @@ -11185,7 +11607,6 @@ static JSBigInt *js_bigint_new(JSContext *ctx, int len) static JSBigInt *js_bigint_set_si(JSBigIntBuf *buf, js_slimb_t a) { JSBigInt *r = (JSBigInt *)buf->big_int_buf; - r->header.ref_count = 0; /* fail safe */ r->len = 1; r->tab[0] = a; return r; @@ -11197,7 +11618,6 @@ static JSBigInt *js_bigint_set_si64(JSBigIntBuf *buf, int64_t a) return js_bigint_set_si(buf, a); #else JSBigInt *r = (JSBigInt *)buf->big_int_buf; - r->header.ref_count = 0; /* fail safe */ if (a >= INT32_MIN && a <= INT32_MAX) { r->len = 1; r->tab[0] = a; @@ -11312,7 +11732,7 @@ static JSBigInt *js_bigint_normalize1(JSContext *ctx, JSBigInt *a, int l) { js_limb_t v; - assert(a->header.ref_count == 1); + assert(js_rc(a)->ref_count == 1); while (l > 1) { v = a->tab[l - 1]; if ((v != 0 && v != -1) || @@ -12127,7 +12547,11 @@ static JSBigInt *js_bigint_from_string(JSContext *ctx, } /* 2 <= base <= 36 */ -static char const digits[36] = "0123456789abcdefghijklmnopqrstuvwxyz"; +static char const digits[36] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' +}; /* special version going backwards */ /* XXX: use dtoa.c */ @@ -12422,7 +12846,7 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, to_digit((uint8_t)p[1]) < radix)) { p++; } - if (!(flags & ATOD_INT_ONLY)) { + if (!(flags & ATOD_INT_ONLY) && radix == 10) { if (*p == '.' && (p > p_start || to_digit((uint8_t)p[1]) < radix)) { is_float = TRUE; p++; @@ -12432,9 +12856,7 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, (*p == sep && to_digit((uint8_t)p[1]) < radix)) p++; } - if (p > p_start && - (((*p == 'e' || *p == 'E') && radix == 10) || - ((*p == 'p' || *p == 'P') && (radix == 2 || radix == 8 || radix == 16)))) { + if (p > p_start && (*p == 'e' || *p == 'E')) { const char *p1 = p + 1; is_float = TRUE; if (*p1 == '+') { @@ -12471,19 +12893,9 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, } buf[j] = '\0'; - if (flags & ATOD_ACCEPT_SUFFIX) { - if (*p == 'n') { - p++; - atod_type = ATOD_TYPE_BIG_INT; - } else { - if (is_float && radix != 10) - goto fail; - } - } else { - if (atod_type == ATOD_TYPE_FLOAT64) { - if (is_float && radix != 10) - goto fail; - } + if ((flags & ATOD_ACCEPT_SUFFIX) && *p == 'n') { + p++; + atod_type = ATOD_TYPE_BIG_INT; } switch(atod_type) { @@ -13403,8 +13815,8 @@ static void js_print_string(JSPrintValueState *s, JSValueConst val) int sep; if (s->options.raw_dump && JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { JSString *p = JS_VALUE_GET_STRING(val); - js_printf(s, "%d", p->header.ref_count); - sep = (p->header.ref_count == 1) ? '\"' : '\''; + js_printf(s, "%d", js_rc(p)->ref_count); + sep = (js_rc(p)->ref_count == 1) ? '\"' : '\''; } else { sep = '\"'; } @@ -14086,10 +14498,10 @@ static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) sh = p->shape; /* the shape can be NULL while freeing an object */ printf("%14p %4d ", (void *)p, - p->header.ref_count); + js_rc(p)->ref_count); if (sh) { printf("%3d%c %14p ", - sh->header.ref_count, + js_rc(sh)->ref_count, " *"[sh->is_hashed], (void *)sh->proto); } else { @@ -14107,13 +14519,13 @@ static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) { - if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + if (js_rc(p)->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { JS_DumpObject(rt, (JSObject *)p); } else { printf("%14p %4d ", (void *)p, - p->ref_count); - switch(p->gc_obj_type) { + js_rc(p)->ref_count); + switch(js_rc(p)->gc_obj_type) { case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: printf("[function bytecode]"); break; @@ -14133,7 +14545,7 @@ static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) printf("[module]"); break; default: - printf("[unknown %d]", p->gc_obj_type); + printf("[unknown %d]", js_rc(p)->gc_obj_type); break; } printf("\n"); @@ -15231,12 +15643,16 @@ static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, } } else if (tag1 == tag2) { res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) || (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) { res = TRUE; } else if (tag_is_string(tag1) && tag_is_string(tag2)) { /* needed when comparing strings and ropes */ res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); } else if ((tag_is_string(tag1) && tag_is_number(tag2)) || (tag_is_string(tag2) && tag_is_number(tag1))) { @@ -15272,6 +15688,8 @@ static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, } } res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); } else if (tag1 == JS_TAG_BOOL) { op1 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1)); goto redo; @@ -15353,8 +15771,7 @@ static no_inline int js_shr_slow(JSContext *ctx, JSValue *sp) return -1; } -/* XXX: Should take JSValueConst arguments */ -static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, +static BOOL js_strict_eq2(JSContext *ctx, JSValueConst op1, JSValueConst op2, JSStrictEqModeEnum eq_mode) { BOOL res; @@ -15369,7 +15786,6 @@ static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, res = FALSE; } else { res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); - goto done_no_free; } break; case JS_TAG_NULL: @@ -15445,7 +15861,7 @@ static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, } else { res = (d1 == d2); /* if NaN return false and +0 == -0 */ } - goto done_no_free; + break; case JS_TAG_SHORT_BIG_INT: case JS_TAG_BIG_INT: { @@ -15473,17 +15889,12 @@ static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, res = FALSE; break; } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - done_no_free: return res; } static BOOL js_strict_eq(JSContext *ctx, JSValueConst op1, JSValueConst op2) { - return js_strict_eq2(ctx, - JS_DupValue(ctx, op1), JS_DupValue(ctx, op2), - JS_EQ_STRICT); + return js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); } BOOL JS_StrictEq(JSContext *ctx, JSValueConst op1, JSValueConst op2) @@ -15493,9 +15904,7 @@ BOOL JS_StrictEq(JSContext *ctx, JSValueConst op1, JSValueConst op2) static BOOL js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2) { - return js_strict_eq2(ctx, - JS_DupValue(ctx, op1), JS_DupValue(ctx, op2), - JS_EQ_SAME_VALUE); + return js_strict_eq2(ctx, op1, op2, JS_EQ_SAME_VALUE); } BOOL JS_SameValue(JSContext *ctx, JSValueConst op1, JSValueConst op2) @@ -15505,9 +15914,7 @@ BOOL JS_SameValue(JSContext *ctx, JSValueConst op1, JSValueConst op2) static BOOL js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2) { - return js_strict_eq2(ctx, - JS_DupValue(ctx, op1), JS_DupValue(ctx, op2), - JS_EQ_SAME_VALUE_ZERO); + return js_strict_eq2(ctx, op1, op2, JS_EQ_SAME_VALUE_ZERO); } BOOL JS_SameValueZero(JSContext *ctx, JSValueConst op1, JSValueConst op2) @@ -15515,15 +15922,6 @@ BOOL JS_SameValueZero(JSContext *ctx, JSValueConst op1, JSValueConst op2) return js_same_value_zero(ctx, op1, op2); } -static no_inline int js_strict_eq_slow(JSContext *ctx, JSValue *sp, - BOOL is_neq) -{ - BOOL res; - res = js_strict_eq2(ctx, sp[-2], sp[-1], JS_EQ_STRICT); - sp[-2] = JS_NewBool(ctx, res ^ is_neq); - return 0; -} - static __exception int js_operator_in(JSContext *ctx, JSValue *sp) { JSValue op1, op2; @@ -16398,18 +16796,6 @@ static JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val, static JSValue js_create_array_iterator(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic); -static BOOL js_is_fast_array(JSContext *ctx, JSValueConst obj) -{ - /* Try and handle fast arrays explicitly */ - if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { - JSObject *p = JS_VALUE_GET_OBJ(obj); - if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { - return TRUE; - } - } - return FALSE; -} - /* Access an Array's internal JSValue array if available */ static BOOL js_get_fast_array(JSContext *ctx, JSValueConst obj, JSValue **arrpp, uint32_t *countp) @@ -16596,7 +16982,7 @@ static JSVarRef *js_create_var_ref(JSContext *ctx, BOOL is_lexical) var_ref = js_malloc(ctx, sizeof(JSVarRef)); if (!var_ref) return NULL; - var_ref->header.ref_count = 1; + js_rc(var_ref)->ref_count = 1; if (is_lexical) var_ref->value = JS_UNINITIALIZED; else @@ -16636,7 +17022,7 @@ static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, if (var_ref) { /* reference to the already created local variable */ assert(var_ref->pvalue == pvalue); - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; return var_ref; } @@ -16644,7 +17030,7 @@ static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, var_ref = js_malloc(ctx, sizeof(JSVarRef)); if (!var_ref) return NULL; - var_ref->header.ref_count = 1; + js_rc(var_ref)->ref_count = 1; add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); var_ref->is_detached = FALSE; var_ref->is_lexical = FALSE; @@ -16662,7 +17048,7 @@ static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, the JSVarRef of async functions during the GC. It would have the advantage of allowing the release of unused stack frames in a cycle. */ - async_func->header.ref_count++; + js_rc(async_func)->ref_count++; } var_ref->pvalue = pvalue; return var_ref; @@ -16693,7 +17079,7 @@ static JSVarRef *js_global_object_get_uninitialized_var(JSContext *ctx, JSObject if (prs) { assert((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF); var_ref = pr->u.var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; return var_ref; } @@ -16706,7 +17092,7 @@ static JSVarRef *js_global_object_get_uninitialized_var(JSContext *ctx, JSObject return NULL; } pr->u.var_ref = var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; return var_ref; } @@ -16725,7 +17111,7 @@ static JSVarRef *js_global_object_find_uninitialized_var(JSContext *ctx, JSObjec if (prs) { assert((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF); var_ref = pr->u.var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; delete_property(ctx, p1, atom); if (!is_lexical) var_ref->value = JS_UNDEFINED; @@ -16756,7 +17142,7 @@ static JSVarRef *js_closure_define_global_var(JSContext *ctx, JSClosureVar *cv, if (prs) { assert((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF); var_ref = pr->u.var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; return var_ref; } @@ -16794,7 +17180,7 @@ static JSVarRef *js_closure_define_global_var(JSContext *ctx, JSClosureVar *cv, return NULL; } else { var_ref = pr->u.var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; } if (cv->var_kind == JS_VAR_GLOBAL_FUNCTION_DECL && (prs->flags & JS_PROP_CONFIGURABLE)) { @@ -16804,7 +17190,7 @@ static JSVarRef *js_closure_define_global_var(JSContext *ctx, JSClosureVar *cv, free_property(ctx->rt, pr, prs->flags); prs->flags = flags | JS_PROP_VARREF; pr->u.var_ref = var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; } else { assert((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF); prs->flags = (prs->flags & ~JS_PROP_C_W_E) | flags; @@ -16836,7 +17222,7 @@ static JSVarRef *js_closure_define_global_var(JSContext *ctx, JSClosureVar *cv, return NULL; } pr->u.var_ref = var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; return var_ref; } @@ -16852,7 +17238,7 @@ static JSVarRef *js_closure_global_var(JSContext *ctx, JSClosureVar *cv) if (prs) { assert((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF); var_ref = pr->u.var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; return var_ref; } p = JS_VALUE_GET_OBJ(ctx->global_obj); @@ -16867,7 +17253,7 @@ static JSVarRef *js_closure_global_var(JSContext *ctx, JSClosureVar *cv) } if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { var_ref = pr->u.var_ref; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; return var_ref; } } @@ -16936,7 +17322,7 @@ static JSValue js_closure2(JSContext *ctx, JSValue func_obj, case JS_CLOSURE_REF: case JS_CLOSURE_GLOBAL_REF: var_ref = cur_var_refs[cv->var_idx]; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; break; default: abort(); @@ -17352,6 +17738,11 @@ typedef enum { #define FUNC_RET_YIELD_STAR 2 #define FUNC_RET_INITIAL_YIELD 3 +#ifdef OPCODE_ASM_LABEL +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-label" +#endif + /* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, JSValueConst this_obj, JSValueConst new_target, @@ -17385,7 +17776,11 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, [ OP_COUNT ... 255 ] = &&case_default }; #define SWITCH(pc) goto *dispatch_table[opcode = *pc++]; +#ifdef OPCODE_ASM_LABEL +#define CASE(op) case_ ## op: asm volatile("label_" #op ":\n.globl label_" #op); dummy_case_ ## op +#else #define CASE(op) case_ ## op +#endif #define DEFAULT case_default #define BREAK SWITCH(pc) #endif @@ -18396,7 +18791,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, goto exception; if (opcode == OP_make_var_ref_ref) { var_ref = var_refs[idx]; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; } else { var_ref = get_var_ref(ctx, sf, idx, opcode == OP_make_arg_ref); if (!var_ref) @@ -19313,9 +19708,24 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, sp[-2] = JS_NewInt32(ctx, r); } sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { - sp[-2] = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) + - JS_VALUE_GET_FLOAT64(op2)); + } else if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) || + JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) { + double d1, d2; + if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1))) { + d1 = JS_VALUE_GET_FLOAT64(op1); + } else if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + d1 = JS_VALUE_GET_INT(op1); + } else { + goto add_slow_case; + } + if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else if (JS_VALUE_GET_TAG(op2) == JS_TAG_INT) { + d2 = JS_VALUE_GET_INT(op2); + } else { + goto add_slow_case; + } + sp[-2] = __JS_NewFloat64(ctx, d1 + d2); sp--; } else if (JS_IsString(op1) && JS_IsString(op2)) { sp[-2] = JS_ConcatString(ctx, op1, op2); @@ -19323,6 +19733,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, if (JS_IsException(sp[-1])) goto exception; } else { + add_slow_case: sf->cur_pc = pc; if (js_add_slow(ctx, sp)) goto exception; @@ -19393,9 +19804,24 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, sp[-2] = JS_NewInt32(ctx, r); } sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { - sp[-2] = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) - - JS_VALUE_GET_FLOAT64(op2)); + } else if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) || + JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) { + double d1, d2; + if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1))) { + d1 = JS_VALUE_GET_FLOAT64(op1); + } else if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + d1 = JS_VALUE_GET_INT(op1); + } else { + goto binary_arith_slow; + } + if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else if (JS_VALUE_GET_TAG(op2) == JS_TAG_INT) { + d2 = JS_VALUE_GET_INT(op2); + } else { + goto binary_arith_slow; + } + sp[-2] = __JS_NewFloat64(ctx, d1 - d2); sp--; } else { goto binary_arith_slow; @@ -19425,8 +19851,24 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, } sp[-2] = JS_NewInt32(ctx, r); sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { - d = JS_VALUE_GET_FLOAT64(op1) * JS_VALUE_GET_FLOAT64(op2); + } else if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) || + JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) { + double d1, d2; + if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1))) { + d1 = JS_VALUE_GET_FLOAT64(op1); + } else if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + d1 = JS_VALUE_GET_INT(op1); + } else { + goto binary_arith_slow; + } + if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else if (JS_VALUE_GET_TAG(op2) == JS_TAG_INT) { + d2 = JS_VALUE_GET_INT(op2); + } else { + goto binary_arith_slow; + } + d = d1 * d2; mul_fp_res: sp[-2] = __JS_NewFloat64(ctx, d); sp--; @@ -19786,16 +20228,36 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, BREAK; -#define OP_CMP(opcode, binary_op, slow_call) \ - CASE(opcode): \ - { \ - JSValue op1, op2; \ - op1 = sp[-2]; \ - op2 = sp[-1]; \ +#define OP_CMP(opcode, binary_op, slow_call) \ + CASE(opcode): \ + { \ + JSValue op1, op2; \ + op1 = sp[-2]; \ + op2 = sp[-1]; \ if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \ sp[-2] = JS_NewBool(ctx, JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \ sp--; \ + } else if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1)) || \ + JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) { \ + double d1, d2; \ + if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1))) { \ + d1 = JS_VALUE_GET_FLOAT64(op1); \ + } else if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { \ + d1 = JS_VALUE_GET_INT(op1); \ + } else { \ + goto opcode ## _slow_case; \ + } \ + if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op2))) { \ + d2 = JS_VALUE_GET_FLOAT64(op2); \ + } else if (JS_VALUE_GET_TAG(op2) == JS_TAG_INT) { \ + d2 = JS_VALUE_GET_INT(op2); \ + } else { \ + goto opcode ## _slow_case; \ + } \ + sp[-2] = JS_NewBool(ctx, d1 binary_op d2); \ + sp--; \ } else { \ + opcode ## _slow_case: \ sf->cur_pc = pc; \ if (slow_call) \ goto exception; \ @@ -19808,10 +20270,133 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, OP_CMP(OP_lte, <=, js_relational_slow(ctx, sp, opcode)); OP_CMP(OP_gt, >, js_relational_slow(ctx, sp, opcode)); OP_CMP(OP_gte, >=, js_relational_slow(ctx, sp, opcode)); - OP_CMP(OP_eq, ==, js_eq_slow(ctx, sp, 0)); - OP_CMP(OP_neq, !=, js_eq_slow(ctx, sp, 1)); - OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, sp, 0)); - OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, sp, 1)); + +#define OP_CMP_EQ(opcode, inv) \ + CASE(opcode): \ + { \ + JSValue op1, op2; \ + int res; \ + uint32_t tag1, tag2; \ + op1 = sp[-2]; \ + op2 = sp[-1]; \ + tag1 = JS_VALUE_GET_TAG(op1); \ + tag2 = JS_VALUE_GET_TAG(op2); \ + if (likely(tag1 == JS_TAG_INT)) { \ + if (tag2 == JS_TAG_INT) { \ + res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); \ + } else if (JS_TAG_IS_FLOAT64(tag2)) { \ + res = (JS_VALUE_GET_INT(op1) == JS_VALUE_GET_FLOAT64(op2)); \ + } else { \ + goto slow_eq ## inv; \ + } \ + } else if (JS_TAG_IS_FLOAT64(tag1)) { \ + if (tag2 == JS_TAG_INT) { \ + res = JS_VALUE_GET_FLOAT64(op1) == JS_VALUE_GET_INT(op2); \ + } else if (JS_TAG_IS_FLOAT64(tag2)) { \ + res = (JS_VALUE_GET_FLOAT64(op1) == JS_VALUE_GET_FLOAT64(op2)); \ + } else { \ + goto slow_eq ## inv; \ + } \ + } else if (tag1 == JS_TAG_OBJECT) { \ + if (tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED) { \ + JSObject *p = JS_VALUE_GET_OBJ(op1); \ + res = p->is_HTMLDDA; \ + JS_FreeValue(ctx, op1); \ + } else if (tag2 == JS_TAG_OBJECT) { \ + res = JS_VALUE_GET_OBJ(op1) == JS_VALUE_GET_OBJ(op2); \ + JS_FreeValue(ctx, op1); \ + JS_FreeValue(ctx, op2); \ + } else { \ + goto slow_eq ## inv; \ + } \ + } else if (tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED) { \ + if (tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED) { \ + res = TRUE; \ + } else if (tag2 == JS_TAG_OBJECT) { \ + JSObject *p = JS_VALUE_GET_OBJ(op2); \ + res = p->is_HTMLDDA; \ + JS_FreeValue(ctx, op2); \ + } else { \ + goto slow_eq ## inv; \ + } \ + } else if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { \ + res = js_string_eq(ctx, JS_VALUE_GET_STRING(op1), \ + JS_VALUE_GET_STRING(op2)); \ + JS_FreeValue(ctx, op1); \ + JS_FreeValue(ctx, op2); \ + } else { \ + slow_eq ## inv: \ + sf->cur_pc = pc; \ + if (js_eq_slow(ctx, sp, inv)) \ + goto exception; \ + sp--; \ + goto slow_eq_done ## inv; \ + } \ + sp[-2] = JS_NewBool(ctx, res ^ inv); \ + sp--; \ + slow_eq_done ## inv: ; \ + } \ + BREAK + + OP_CMP_EQ(OP_eq, 0); + OP_CMP_EQ(OP_neq, 1); + +#define OP_CMP_STRICT_EQ(opcode, inv) \ + CASE(opcode): \ + { \ + JSValue op1, op2; \ + int res; \ + uint32_t tag1, tag2; \ + op1 = sp[-2]; \ + op2 = sp[-1]; \ + tag1 = JS_VALUE_GET_TAG(op1); \ + tag2 = JS_VALUE_GET_TAG(op2); \ + if (likely(tag1 == JS_TAG_INT)) { \ + if (tag2 == JS_TAG_INT) { \ + res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); \ + } else if (JS_TAG_IS_FLOAT64(tag2)) { \ + res = (JS_VALUE_GET_INT(op1) == JS_VALUE_GET_FLOAT64(op2)); \ + } else { \ + JS_FreeValue(ctx, op2); \ + res = FALSE; \ + } \ + } else if (JS_TAG_IS_FLOAT64(tag1)) { \ + if (tag2 == JS_TAG_INT) { \ + res = JS_VALUE_GET_FLOAT64(op1) == JS_VALUE_GET_INT(op2); \ + } else if (JS_TAG_IS_FLOAT64(tag2)) { \ + res = (JS_VALUE_GET_FLOAT64(op1) == JS_VALUE_GET_FLOAT64(op2)); \ + } else { \ + JS_FreeValue(ctx, op2); \ + res = FALSE; \ + } \ + } else if (tag1 == JS_TAG_OBJECT) { \ + if (tag2 == JS_TAG_OBJECT) { \ + res = JS_VALUE_GET_OBJ(op1) == JS_VALUE_GET_OBJ(op2); \ + } else { \ + res = FALSE; \ + } \ + JS_FreeValue(ctx, op1); \ + JS_FreeValue(ctx, op2); \ + } else if (tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED) { \ + res = (tag1 == tag2); \ + JS_FreeValue(ctx, op2); \ + } else if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { \ + res = js_string_eq(ctx, JS_VALUE_GET_STRING(op1), \ + JS_VALUE_GET_STRING(op2)); \ + JS_FreeValue(ctx, op1); \ + JS_FreeValue(ctx, op2); \ + } else { \ + res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); \ + JS_FreeValue(ctx, op1); \ + JS_FreeValue(ctx, op2); \ + } \ + sp[-2] = JS_NewBool(ctx, res ^ inv); \ + sp--; \ + } \ + BREAK + + OP_CMP_STRICT_EQ(OP_strict_eq, 0); + OP_CMP_STRICT_EQ(OP_strict_neq, 1); CASE(OP_in): sf->cur_pc = pc; @@ -20126,6 +20711,10 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, return ret_val; } +#ifdef OPCODE_ASM_LABEL +#pragma GCC diagnostic pop +#endif + JSValue JS_Call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv) { @@ -20319,7 +20908,7 @@ static JSAsyncFunctionState *async_func_init(JSContext *ctx, if (!s) return NULL; memset(s, 0, sizeof(*s)); - s->header.ref_count = 1; + js_rc(s)->ref_count = 1; add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION); sf = &s->frame; @@ -20409,7 +20998,7 @@ static void __async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) JS_FreeValueRT(rt, s->resolving_funcs[1]); remove_gc_object(&s->header); - if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && s->header.ref_count != 0) { + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && js_rc(s)->ref_count != 0) { list_add_tail(&s->header.link, &rt->gc_zero_ref_count_list); } else { js_free_rt(rt, s); @@ -20418,7 +21007,7 @@ static void __async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) { - if (--s->header.ref_count == 0) { + if (--js_rc(s)->ref_count == 0) { if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { list_del(&s->header.link); list_add(&s->header.link, &rt->gc_zero_ref_count_list); @@ -20641,7 +21230,7 @@ static int js_async_function_resolve_create(JSContext *ctx, return -1; } p = JS_VALUE_GET_OBJ(resolving_funcs[i]); - s->header.ref_count++; + js_rc(s)->ref_count++; p->u.async_function_data = s; } return 0; @@ -21100,9 +21689,8 @@ static JSValue js_async_generator_resolve_function(JSContext *ctx, } else { js_async_generator_resolve(ctx, s, arg, TRUE); } - } else { + } else if (s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING) { /* restart function execution after await() */ - assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING); s->func_state->throw_flag = is_reject; if (is_reject) { JS_Throw(ctx, JS_DupValue(ctx, arg)); @@ -21558,7 +22146,7 @@ typedef struct JSParseState { JSFunctionDef *cur_func; BOOL is_module; /* parsing a module */ BOOL allow_html_comments; - BOOL ext_json; /* true if accepting JSON superset */ + BOOL ext_json; /* JSON parsing: true if accepting JSON superset */ GetLineColCache get_line_col_cache; } JSParseState; @@ -27953,6 +28541,7 @@ static __exception int js_parse_var(JSParseState *s, int parse_flags, int tok, } if (s->token.val == '=') { + const uint8_t *source_ptr = s->token.ptr; if (next_token(s)) goto var_error; if (need_var_reference(s, tok)) { @@ -27970,12 +28559,14 @@ static __exception int js_parse_var(JSParseState *s, int parse_flags, int tok, goto var_error; } set_object_name(s, name); + emit_source_pos(s, source_ptr); put_lvalue(s, opcode, scope, name1, label, PUT_LVALUE_NOKEEP, FALSE); } else { if (js_parse_assign_expr2(s, parse_flags)) goto var_error; set_object_name(s, name); + emit_source_pos(s, source_ptr); emit_op(s, (tok == TOK_CONST || tok == TOK_LET) ? OP_scope_put_var_init : OP_scope_put_var); emit_atom(s, name); @@ -28191,8 +28782,7 @@ static __exception int js_parse_for_in_of(JSParseState *s, int label_name, JS_FreeAtom(ctx, var_name); if (token_is_pseudo_keyword(s, JS_ATOM_of)) { - break_entry.has_iterator = is_for_of = TRUE; - break_entry.drop_count += 2; + is_for_of = TRUE; if (has_initializer) goto initializer_error; } else if (s->token.val == TOK_IN) { @@ -28221,6 +28811,11 @@ static __exception int js_parse_for_in_of(JSParseState *s, int label_name, the TDZ values are in the closures */ close_scopes(s, s->cur_func->scope_level, block_scope_level); if (is_for_of) { + /* set has_iterator after the iterable expression is parsed so + that a yield in the expression does not try to close a + not-yet-created iterator */ + break_entry.has_iterator = TRUE; + break_entry.drop_count += 2; if (is_async) emit_op(s, OP_for_await_of_start); else @@ -28241,7 +28836,8 @@ static __exception int js_parse_for_in_of(JSParseState *s, int label_name, int chunk_size = pos_expr - pos_next; int offset = bc->size - pos_next; int i; - dbuf_claim(bc, chunk_size); + if (dbuf_claim(bc, chunk_size)) + return -1; dbuf_put(bc, bc->buf + pos_next, chunk_size); memset(bc->buf + pos_next, OP_nop, chunk_size); /* `next` part ends with a goto */ @@ -28647,7 +29243,8 @@ static __exception int js_parse_statement_or_decl(JSParseState *s, int chunk_size = pos_body - pos_cont; int offset = bc->size - pos_cont; int i; - dbuf_claim(bc, chunk_size); + if (dbuf_claim(bc, chunk_size)) + goto fail; dbuf_put(bc, bc->buf + pos_cont, chunk_size); memset(bc->buf + pos_cont, OP_nop, chunk_size); /* increment part ends with a goto */ @@ -28774,7 +29371,7 @@ static __exception int js_parse_statement_or_decl(JSParseState *s, if (js_parse_expect(s, '}')) goto fail; if (default_label_pos >= 0) { - /* Ugly patch for the the `default` label, shameful and risky */ + /* Ugly patch for the `default` label, shameful and risky */ put_u32(s->cur_func->byte_code.buf + default_label_pos, label_case); s->cur_func->label_slots[label_case].pos = default_label_pos + 4; @@ -29073,7 +29670,7 @@ static JSModuleDef *js_new_module_def(JSContext *ctx, JSAtom name) JS_FreeAtom(ctx, name); return NULL; } - m->header.ref_count = 1; + js_rc(m)->ref_count = 1; add_gc_object(ctx->rt, &m->header, JS_GC_OBJ_TYPE_MODULE); m->module_name = name; m->module_ns = JS_UNDEFINED; @@ -29161,7 +29758,7 @@ static void js_free_module_def(JSRuntime *rt, JSModuleDef *m) list_del(&m->link); } remove_gc_object(&m->header); - if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && m->header.ref_count != 0) { + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && js_rc(m)->ref_count != 0) { list_add_tail(&m->header.link, &rt->gc_zero_ref_count_list); } else { js_free_rt(rt, m); @@ -29881,7 +30478,7 @@ static JSValue js_build_module_ns(JSContext *ctx, JSModuleDef *m) JS_PROP_VARREF); if (!pr) goto fail; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; pr->u.var_ref = var_ref; } break; @@ -30172,7 +30769,7 @@ static int js_inner_module_linking(JSContext *ctx, JSModuleDef *m, p1 = JS_VALUE_GET_OBJ(res_m->func_obj); var_ref = p1->u.func.var_refs[res_me->u.local.var_idx]; } - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; var_refs[mi->var_idx] = var_ref; #ifdef DUMP_MODULE_RESOLVE printf("local export (var_ref=%p)\n", var_ref); @@ -30188,7 +30785,7 @@ static int js_inner_module_linking(JSContext *ctx, JSModuleDef *m, JSExportEntry *me = &m->export_entries[i]; if (me->export_type == JS_EXPORT_TYPE_LOCAL) { var_ref = var_refs[me->u.local.var_idx]; - var_ref->header.ref_count++; + js_rc(var_ref)->ref_count++; me->u.local.var_ref = var_ref; } } @@ -31662,7 +32259,7 @@ static void dump_byte_code(JSContext *ctx, int pass, const JSOpCode *oi; int pos, pos_next, op, size, idx, addr, line, line1, in_source, line_num; uint8_t *bits = js_mallocz(ctx, len * sizeof(*bits)); - BOOL use_short_opcodes = (b != NULL); + BOOL use_short_opcodes = (b != NULL), dump_pc; if (b) { int col_num; @@ -31768,7 +32365,12 @@ static void dump_byte_code(JSContext *ctx, int pass, printf("%*s", x0 + 20 - x, ""); } #endif - if (bits[pos]) { +#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 32) + dump_pc = TRUE; +#else + dump_pc = bits[pos]; +#endif + if (dump_pc) { printf("%5d: ", pos); } else { printf(" "); @@ -35536,7 +36138,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) b = js_mallocz(ctx, function_size); if (!b) goto fail; - b->header.ref_count = 1; + js_rc(b)->ref_count = 1; b->byte_code_buf = (void *)((uint8_t*)b + byte_code_offset); b->byte_code_len = fd->byte_code.size; @@ -35718,7 +36320,7 @@ static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b) } remove_gc_object(&b->header); - if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && b->header.ref_count != 0) { + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && js_rc(b)->ref_count != 0) { list_add_tail(&b->header.link, &rt->gc_zero_ref_count_list); } else { js_free_rt(rt, b); @@ -36739,7 +37341,6 @@ static JSValue JS_EvalObject(JSContext *ctx, JSValueConst this_obj, ret = JS_EvalInternal(ctx, this_obj, str, len, "", flags, scope_idx); JS_FreeCString(ctx, str); return ret; - } JSValue JS_EvalThis(JSContext *ctx, JSValueConst this_obj, @@ -37573,11 +38174,14 @@ static int JS_WriteObjectRec(BCWriterState *s, JSValueConst obj) case JS_TAG_STRING_ROPE: { JSValue str; + int ret; str = JS_ToString(s->ctx, obj); if (JS_IsException(str)) goto fail; - JS_WriteObjectRec(s, str); + ret = JS_WriteObjectRec(s, str); JS_FreeValue(s->ctx, str); + if (ret) + goto fail; } break; case JS_TAG_FUNCTION_BYTECODE: @@ -38148,12 +38752,11 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) uint16_t v16; uint8_t v8; int idx, i, local_count; - int function_size, cpool_offset, byte_code_offset; + int cpool_offset, byte_code_offset; int closure_var_offset, vardefs_offset; - + uint64_t function_size; + memset(&bc, 0, sizeof(bc)); - bc.header.ref_count = 1; - //bc.gc_header.mark = 0; if (bc_get_u16(s, &v16)) goto fail; @@ -38200,22 +38803,24 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) function_size = offsetof(JSFunctionBytecode, debug); } cpool_offset = function_size; - function_size += bc.cpool_count * sizeof(*bc.cpool); + function_size += (uint64_t)bc.cpool_count * sizeof(*bc.cpool); vardefs_offset = function_size; - function_size += local_count * sizeof(*bc.vardefs); + function_size += (uint64_t)local_count * sizeof(*bc.vardefs); closure_var_offset = function_size; - function_size += bc.closure_var_count * sizeof(*bc.closure_var); + function_size += (uint64_t)bc.closure_var_count * sizeof(*bc.closure_var); byte_code_offset = function_size; if (!bc.read_only_bytecode) { function_size += bc.byte_code_len; } + if (function_size > INT32_MAX) + return JS_ThrowOutOfMemory(ctx); + b = js_mallocz(ctx, function_size); if (!b) return JS_EXCEPTION; memcpy(b, &bc, offsetof(JSFunctionBytecode, debug)); - b->header.ref_count = 1; if (local_count != 0) { b->vardefs = (void *)((uint8_t*)b + vardefs_offset); } @@ -38226,6 +38831,7 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) b->cpool = (void *)((uint8_t*)b + cpool_offset); } + js_rc(b)->ref_count = 1; add_gc_object(ctx->rt, &b->header, JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); obj = JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b); @@ -41254,10 +41860,10 @@ static JSValue js_get_this(JSContext *ctx, return JS_DupValue(ctx, this_val); } -static JSValue JS_ArraySpeciesCreate(JSContext *ctx, JSValueConst obj, - JSValueConst len_val) +/* XXX: optimize */ +static JSValue JS_ArraySpeciesGetCtor(JSContext *ctx, JSValueConst obj) { - JSValue ctor, ret, species; + JSValue ctor, species; int res; JSContext *realm; @@ -41265,7 +41871,7 @@ static JSValue JS_ArraySpeciesCreate(JSContext *ctx, JSValueConst obj, if (res < 0) return JS_EXCEPTION; if (!res) - return js_array_constructor(ctx, JS_UNDEFINED, 1, &len_val); + return JS_UNDEFINED; ctor = JS_GetProperty(ctx, obj, JS_ATOM_constructor); if (JS_IsException(ctor)) return ctor; @@ -41291,13 +41897,39 @@ static JSValue JS_ArraySpeciesCreate(JSContext *ctx, JSValueConst obj, if (JS_IsNull(ctor)) ctor = JS_UNDEFINED; } + if (!JS_IsUndefined(ctor) && + js_same_value(ctx, ctor, ctx->array_ctor)) { + JS_FreeValue(ctx, ctor); + ctor = JS_UNDEFINED; + } + return ctor; +} + +static JSValue JS_ArrayCreateFromCtor(JSContext *ctx, JSValueConst ctor, int64_t len) +{ + JSValue len_val, ret; + + len_val = JS_NewInt64(ctx, len); if (JS_IsUndefined(ctor)) { - return js_array_constructor(ctx, JS_UNDEFINED, 1, &len_val); + ret = js_array_constructor(ctx, JS_UNDEFINED, 1, (JSValueConst *)&len_val); } else { - ret = JS_CallConstructor(ctx, ctor, 1, &len_val); - JS_FreeValue(ctx, ctor); - return ret; + ret = JS_CallConstructor(ctx, ctor, 1, (JSValueConst *)&len_val); } + JS_FreeValue(ctx, len_val); + return ret; +} + +/* len must be >= 0 */ +static JSValue JS_ArraySpeciesCreate(JSContext *ctx, JSValueConst obj, int64_t len) +{ + JSValue ctor, ret; + + ctor = JS_ArraySpeciesGetCtor(ctx, obj); + if (JS_IsException(ctor)) + return ctor; + ret = JS_ArrayCreateFromCtor(ctx, ctor, len); + JS_FreeValue(ctx, ctor); + return ret; } static const JSCFunctionListEntry js_array_funcs[] = { @@ -41397,21 +42029,14 @@ static JSValue js_array_with(JSContext *ctx, JSValueConst this_val, } else { for (; i < idx; i++, pval++) if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) - goto fill_and_fail; + goto exception; *pval = JS_DupValue(ctx, argv[1]); for (i++, pval++; i < len; i++, pval++) { - if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) { - fill_and_fail: - for (; i < len; i++, pval++) - *pval = JS_UNDEFINED; + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) goto exception; - } } } - if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, len)) < 0) - goto exception; - ret = arr; arr = JS_UNDEFINED; @@ -41434,7 +42059,7 @@ static JSValue js_array_concat(JSContext *ctx, JSValueConst this_val, if (JS_IsException(obj)) goto exception; - arr = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt32(ctx, 0)); + arr = JS_ArraySpeciesCreate(ctx, obj, 0); if (JS_IsException(arr)) goto exception; n = 0; @@ -41537,13 +42162,12 @@ static JSValue js_array_every(JSContext *ctx, JSValueConst this_val, ret = JS_FALSE; break; case special_map: - /* XXX: JS_ArraySpeciesCreate should take int64_t */ - ret = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt64(ctx, len)); + ret = JS_ArraySpeciesCreate(ctx, obj, len); if (JS_IsException(ret)) goto exception; break; case special_filter: - ret = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt32(ctx, 0)); + ret = JS_ArraySpeciesCreate(ctx, obj, 0); if (JS_IsException(ret)) goto exception; break; @@ -41804,8 +42428,7 @@ static JSValue js_array_includes(JSContext *ctx, JSValueConst this_val, } if (js_get_fast_array(ctx, obj, &arrp, &count)) { for (; n < count; n++) { - if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), - JS_DupValue(ctx, arrp[n]), + if (js_strict_eq2(ctx, argv[0], arrp[n], JS_EQ_SAME_VALUE_ZERO)) { res = TRUE; goto done; @@ -41816,11 +42439,13 @@ static JSValue js_array_includes(JSContext *ctx, JSValueConst this_val, val = JS_GetPropertyInt64(ctx, obj, n); if (JS_IsException(val)) goto exception; - if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), val, + if (js_strict_eq2(ctx, argv[0], val, JS_EQ_SAME_VALUE_ZERO)) { + JS_FreeValue(ctx, val); res = TRUE; break; } + JS_FreeValue(ctx, val); } } done: @@ -41853,8 +42478,7 @@ static JSValue js_array_indexOf(JSContext *ctx, JSValueConst this_val, } if (js_get_fast_array(ctx, obj, &arrp, &count)) { for (; n < count; n++) { - if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), - JS_DupValue(ctx, arrp[n]), JS_EQ_STRICT)) { + if (js_strict_eq2(ctx, argv[0], arrp[n], JS_EQ_STRICT)) { res = n; goto done; } @@ -41865,10 +42489,12 @@ static JSValue js_array_indexOf(JSContext *ctx, JSValueConst this_val, if (present < 0) goto exception; if (present) { - if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), val, JS_EQ_STRICT)) { + if (js_strict_eq2(ctx, argv[0], val, JS_EQ_STRICT)) { + JS_FreeValue(ctx, val); res = n; break; } + JS_FreeValue(ctx, val); } } } @@ -41905,10 +42531,12 @@ static JSValue js_array_lastIndexOf(JSContext *ctx, JSValueConst this_val, if (present < 0) goto exception; if (present) { - if (js_strict_eq2(ctx, JS_DupValue(ctx, argv[0]), val, JS_EQ_STRICT)) { + if (js_strict_eq2(ctx, argv[0], val, JS_EQ_STRICT)) { + JS_FreeValue(ctx, val); res = n; break; } + JS_FreeValue(ctx, val); } } } @@ -42295,17 +42923,10 @@ static JSValue js_array_toReversed(JSContext *ctx, JSValueConst this_val, } else { // Query order is observable; test262 expects descending order. for (; i >= 0; i--, pval++) { - if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) { - // Exception; initialize remaining elements. - for (; i >= 0; i--, pval++) - *pval = JS_UNDEFINED; + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) goto exception; - } } } - - if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, len)) < 0) - goto exception; } ret = arr; @@ -42318,13 +42939,13 @@ static JSValue js_array_toReversed(JSContext *ctx, JSValueConst this_val, } static JSValue js_array_slice(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int splice) + int argc, JSValueConst *argv) { - JSValue obj, arr, val, len_val; - int64_t len, start, k, final, n, count, del_count, new_len; + JSValue obj, arr, val, ctor; + int64_t len, start, k, final, n, count; int kPresent; JSValue *arrp; - uint32_t count32, i, item_count; + uint32_t count32; arr = JS_UNDEFINED; obj = JS_ToObject(ctx, this_val); @@ -42334,69 +42955,150 @@ static JSValue js_array_slice(JSContext *ctx, JSValueConst this_val, if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len)) goto exception; - if (splice) { - if (argc == 0) { - item_count = 0; - del_count = 0; - } else - if (argc == 1) { - item_count = 0; - del_count = len - start; - } else { - item_count = argc - 2; - if (JS_ToInt64Clamp(ctx, &del_count, argv[1], 0, len - start, 0)) - goto exception; - } - if (len + item_count - del_count > MAX_SAFE_INTEGER) { - JS_ThrowTypeError(ctx, "Array loo long"); + final = len; + if (!JS_IsUndefined(argv[1])) { + if (JS_ToInt64Clamp(ctx, &final, argv[1], 0, len, len)) goto exception; - } - count = del_count; - } else { - item_count = 0; /* avoid warning */ - final = len; - if (!JS_IsUndefined(argv[1])) { - if (JS_ToInt64Clamp(ctx, &final, argv[1], 0, len, len)) - goto exception; - } - count = max_int64(final - start, 0); } - len_val = JS_NewInt64(ctx, count); - arr = JS_ArraySpeciesCreate(ctx, obj, len_val); - JS_FreeValue(ctx, len_val); - if (JS_IsException(arr)) + count = max_int64(final - start, 0); + + ctor = JS_ArraySpeciesGetCtor(ctx, obj); + if (JS_IsException(ctor)) goto exception; - k = start; final = start + count; - n = 0; - /* The fast array test on arr ensures that - JS_CreateDataPropertyUint32() won't modify obj in case arr is - an exotic object */ - /* Special case fast arrays */ - if (js_get_fast_array(ctx, obj, &arrp, &count32) && - js_is_fast_array(ctx, arr)) { - /* XXX: should share code with fast array constructor */ - for (; k < final && k < count32; k++, n++) { - if (JS_CreateDataPropertyUint32(ctx, arr, n, JS_DupValue(ctx, arrp[k]), JS_PROP_THROW) < 0) + if (JS_IsUndefined(ctor) && + js_get_fast_array(ctx, obj, &arrp, &count32) && + final <= count32) { + /* fast case */ + arr = js_create_array(ctx, count, (JSValueConst *)arrp + start); + } else { + arr = JS_ArrayCreateFromCtor(ctx, ctor, count); + JS_FreeValue(ctx, ctor); + if (JS_IsException(arr)) + goto exception; + + n = 0; + for (k = start; k < final; k++, n++) { + kPresent = JS_TryGetPropertyInt64(ctx, obj, k, &val); + if (kPresent < 0) goto exception; + if (kPresent) { + if (JS_CreateDataPropertyUint32(ctx, arr, n, val, JS_PROP_THROW) < 0) + goto exception; + } } + if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, n)) < 0) + goto exception; } - /* Copy the remaining elements if any (handle case of inherited properties) */ - for (; k < final; k++, n++) { - kPresent = JS_TryGetPropertyInt64(ctx, obj, k, &val); - if (kPresent < 0) + JS_FreeValue(ctx, obj); + return arr; + + exception: + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, arr); + return JS_EXCEPTION; +} + +static JSValue js_array_splice(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue obj, arr, val, ctor; + int64_t len, start, k, final, n, del_count, new_len; + int kPresent; + uint32_t i, item_count; + JSObject *p; + + arr = JS_UNDEFINED; + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len)) + goto exception; + + if (argc == 0) { + item_count = 0; + del_count = 0; + } else if (argc == 1) { + item_count = 0; + del_count = len - start; + } else { + item_count = argc - 2; + if (JS_ToInt64Clamp(ctx, &del_count, argv[1], 0, len - start, 0)) goto exception; - if (kPresent) { - if (JS_CreateDataPropertyUint32(ctx, arr, n, val, JS_PROP_THROW) < 0) - goto exception; - } } - if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, n)) < 0) + if (len + item_count - del_count > MAX_SAFE_INTEGER) { + JS_ThrowTypeError(ctx, "Array loo long"); + goto exception; + } + final = start + del_count; + /* warning: 'len' may be different from the actual array length + because it may have been modified */ + new_len = len + item_count - del_count; + + ctor = JS_ArraySpeciesGetCtor(ctx, obj); + if (JS_IsException(ctor)) goto exception; - if (splice) { - new_len = len + item_count - del_count; + p = JS_VALUE_GET_PTR(obj); + if (JS_IsUndefined(ctor) && + p->class_id == JS_CLASS_ARRAY && + p->fast_array && + final <= p->u.array.count && + (get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE) && /* writable array length */ + can_extend_fast_array(p)) { + uint32_t count32 = p->u.array.count; + JSValue *arrp = p->u.array.u.values; + + /* fast case */ + arr = js_create_array(ctx, del_count, (JSValueConst *)arrp + start); + if (JS_IsException(arr)) + goto exception; + + if (item_count != del_count) { + /* resize */ + uint32_t new_count32; + new_count32 = count32 + item_count - del_count; + if (del_count > item_count) { + for(i = 0; i < del_count - item_count; i++) + JS_FreeValue(ctx, arrp[start + item_count + i]); + memmove(arrp + start + item_count, arrp + final, + (count32 - final) * sizeof(arrp[0])); + } else { + if (unlikely(new_count32 > p->u.array.u1.size)) { + if (expand_fast_array(ctx, p, new_count32)) + goto exception; + arrp = p->u.array.u.values; + } + memmove(arrp + start + item_count, arrp + final, + (count32 - final) * sizeof(arrp[0])); + for(i = 0; i < item_count - del_count; i++) + arrp[start + del_count + i] = JS_UNDEFINED; + } + p->u.array.count = new_count32; + } + for(i = 0; i < item_count; i++) + set_value(ctx, &arrp[start + i], JS_DupValue(ctx, argv[i + 2])); + } else { + arr = JS_ArrayCreateFromCtor(ctx, ctor, del_count); + JS_FreeValue(ctx, ctor); + if (JS_IsException(arr)) + goto exception; + + n = 0; + for (k = start; k < final; k++, n++) { + kPresent = JS_TryGetPropertyInt64(ctx, obj, k, &val); + if (kPresent < 0) + goto exception; + if (kPresent) { + if (JS_CreateDataPropertyUint32(ctx, arr, n, val, JS_PROP_THROW) < 0) + goto exception; + } + } + if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, n)) < 0) + goto exception; + if (item_count != del_count) { if (JS_CopySubArray(ctx, obj, start + item_count, start + del_count, len - (start + del_count), @@ -42412,9 +43114,9 @@ static JSValue js_array_slice(JSContext *ctx, JSValueConst this_val, if (JS_SetPropertyInt64(ctx, obj, start + i, JS_DupValue(ctx, argv[i + 2])) < 0) goto exception; } - if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, new_len)) < 0) - goto exception; } + if (JS_SetProperty(ctx, obj, JS_ATOM_length, JS_NewInt64(ctx, new_len)) < 0) + goto exception; JS_FreeValue(ctx, obj); return arr; @@ -42494,17 +43196,11 @@ static JSValue js_array_toSpliced(JSContext *ctx, JSValueConst this_val, assert(pval == last); - if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, newlen)) < 0) - goto exception; - done: ret = arr; arr = JS_UNDEFINED; exception: - while (pval != last) - *pval++ = JS_UNDEFINED; - JS_FreeValue(ctx, arr); JS_FreeValue(ctx, obj); return ret; @@ -42636,7 +43332,7 @@ static JSValue js_array_flatten(JSContext *ctx, JSValueConst this_val, goto exception; } } - arr = JS_ArraySpeciesCreate(ctx, obj, JS_NewInt32(ctx, 0)); + arr = JS_ArraySpeciesCreate(ctx, obj, 0); if (JS_IsException(arr)) goto exception; if (JS_FlattenIntoArray(ctx, arr, obj, sourceLen, 0, depthNum, @@ -42849,16 +43545,10 @@ static JSValue js_array_toSorted(JSContext *ctx, JSValueConst this_val, *pval = JS_DupValue(ctx, arrp[i]); } else { for (; i < len; i++, pval++) { - if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) { - for (; i < len; i++, pval++) - *pval = JS_UNDEFINED; + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) goto exception; - } } } - - if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, len)) < 0) - goto exception; } ret = js_array_sort(ctx, arr, argc, argv); @@ -43979,6 +44669,7 @@ static JSValue js_iterator_helper_next(JSContext *ctx, JSValueConst this_val, args[1] = index_val; ret = JS_Call(ctx, it->func, JS_UNDEFINED, countof(args), args); JS_FreeValue(ctx, index_val); + JS_FreeValue(ctx, item); if (JS_IsException(ret)) goto fail; goto done; @@ -44104,8 +44795,8 @@ static const JSCFunctionListEntry js_array_proto_funcs[] = { JS_CFUNC_DEF("toReversed", 0, js_array_toReversed ), JS_CFUNC_DEF("sort", 1, js_array_sort ), JS_CFUNC_DEF("toSorted", 1, js_array_toSorted ), - JS_CFUNC_MAGIC_DEF("slice", 2, js_array_slice, 0 ), - JS_CFUNC_MAGIC_DEF("splice", 2, js_array_slice, 1 ), + JS_CFUNC_DEF("slice", 2, js_array_slice ), + JS_CFUNC_DEF("splice", 2, js_array_splice ), JS_CFUNC_DEF("toSpliced", 2, js_array_toSpliced ), JS_CFUNC_DEF("copyWithin", 2, js_array_copyWithin ), JS_CFUNC_MAGIC_DEF("flatMap", 1, js_array_flatten, 1 ), @@ -47336,9 +48027,10 @@ static JSValue js_regexp_escape(JSContext *ctx, JSValueConst this_val, JSValue str; StringBuffer b_s, *b = &b_s; JSString *p; - uint32_t c, i; + uint32_t c; char s[16]; - + int i, i0; + if (!JS_IsString(argv[0])) return JS_ThrowTypeError(ctx, "not a string"); str = JS_ToString(ctx, argv[0]); /* must call it to linearlize ropes */ @@ -47346,8 +48038,9 @@ static JSValue js_regexp_escape(JSContext *ctx, JSValueConst this_val, return JS_EXCEPTION; p = JS_VALUE_GET_STRING(str); string_buffer_init2(ctx, b, 0, p->is_wide_char); - for (i = 0; i < p->len; i++) { - c = string_get(p, i); + for (i = 0; i < p->len; ) { + i0 = i; + c = string_getc(p, &i); if (c < 33) { if (c >= 9 && c <= 13) { string_buffer_putc8(b, '\\'); @@ -47359,7 +48052,7 @@ static JSValue js_regexp_escape(JSContext *ctx, JSValueConst this_val, if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) { - if (i == 0) + if (i0 == 0) goto hex2; } else if (strchr(",-=<>#&!%:;@~'`\"", c)) { goto hex2; @@ -47375,7 +48068,7 @@ static JSValue js_regexp_escape(JSContext *ctx, JSValueConst this_val, snprintf(s, sizeof(s), "\\u%04x", c); string_buffer_puts8(b, s); } else { - string_buffer_putc16(b, c); + string_buffer_putc(b, c); } } JS_FreeValue(ctx, str); @@ -48110,6 +48803,8 @@ static BOOL check_regexp_getter(JSContext *ctx, return FALSE; if ((prs->flags & JS_PROP_TMASK) != JS_PROP_GETSET) return FALSE; + if (!pr->u.getset.getter) + return FALSE; return JS_IsCFunction(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter), func, magic); } @@ -48633,23 +49328,189 @@ static int json_parse_expect(JSParseState *s, int tok) return json_next_token(s); } -static JSValue json_parse_value(JSParseState *s) + +typedef struct { + int count; + uint32_t hash_size; + struct JSONParseRecordEntry *entries; + uint32_t *hash_table; +} JSONParseRecordObject; + +typedef struct JSONParseRecord { + JSValue value; + union { + JSONParseRecordObject obj; + struct { + int count; + struct JSONParseRecord *elements; + } array; + struct { + uint32_t source_pos; + uint32_t source_len; + } primitive; + } u; +} JSONParseRecord; + +typedef struct JSONParseRecordEntry { + JSAtom atom; + uint32_t hash_next; + JSONParseRecord parse_record; +} JSONParseRecordEntry; + +static void json_parse_record_init_obj(JSContext *ctx, JSONParseRecord *pr, JSValueConst val) +{ + pr->value = JS_DupValue(ctx, val); + pr->u.obj.count = 0; + pr->u.obj.entries = NULL; + pr->u.obj.hash_table = NULL; + pr->u.obj.hash_size = 0; +} + +static void json_parse_record_init_array(JSContext *ctx, JSONParseRecord *pr, JSValueConst val) +{ + pr->value = JS_DupValue(ctx, val); + pr->u.array.count = 0; + pr->u.array.elements = NULL; +} + +static void json_parse_record_init_primitive(JSContext *ctx, JSONParseRecord *pr, JSValueConst val, + uint32_t source_pos, uint32_t source_len) +{ + pr->value = JS_DupValue(ctx, val); + pr->u.primitive.source_pos = source_pos; + pr->u.primitive.source_len = source_len; +} + +static int json_parse_record_resize_hash(JSContext *ctx, JSONParseRecordObject *po, uint32_t new_hash_size) +{ + uint32_t i, h, *new_hash_table; + JSONParseRecordEntry *e; + + new_hash_table = js_malloc(ctx, sizeof(new_hash_table[0]) * new_hash_size); + if (!new_hash_table) + return -1; + js_free(ctx, po->hash_table); + po->hash_table = new_hash_table; + po->hash_size = new_hash_size; + + for(i = 0; i < po->hash_size; i++) { + po->hash_table[i] = -1; + } + for(i = 0; i < po->count; i++) { + e = &po->entries[i]; + h = e->atom & (po->hash_size - 1); + e->hash_next = po->hash_table[h]; + po->hash_table[h] = i; + } + return 0; +} + +static JSONParseRecord *json_parse_record_add(JSContext *ctx, JSONParseRecord *pr, JSAtom key, int *psize) +{ + JSONParseRecordObject *po = &pr->u.obj; + JSONParseRecordEntry *e; + JSONParseRecord *pr1; + uint32_t h; + + if (js_resize_array(ctx, (void **)&po->entries, sizeof(po->entries[0]), + psize, po->count + 1)) { + return NULL; + } + /* don't use a hash table when the number of entries is small */ + if (po->count >= 8 && (po->count + 1) > po->hash_size) { + int hash_bits = 32 - clz32(po->count); + if (json_parse_record_resize_hash(ctx, po, 1 << hash_bits)) + return NULL; + } + + e = &po->entries[po->count++]; + e->atom = JS_DupAtom(ctx, key); + pr1 = &e->parse_record; + pr1->value = JS_UNDEFINED; + if (po->hash_size != 0) { + h = key & (po->hash_size - 1); + e->hash_next = po->hash_table[h]; + po->hash_table[h] = po->count - 1; + } + return pr1; +} + +static JSONParseRecord *json_parse_record_find(JSONParseRecord *pr, JSAtom key) +{ + JSONParseRecordObject *po = &pr->u.obj; + JSONParseRecordEntry *e; + uint32_t h, i; + + if (po->hash_size == 0) { + for(i = 0; i < po->count; i++) { + if (po->entries[i].atom == key) + return &po->entries[i].parse_record; + } + } else { + h = key & (po->hash_size - 1); + i = po->hash_table[h]; + while (i != -1) { + e = &po->entries[i]; + if (e->atom == key) + return &e->parse_record; + i = e->hash_next; + } + } + return NULL; +} + +static void json_free_parse_record(JSContext *ctx, JSONParseRecord *pr) +{ + int i; + if (!pr) + return; + if (JS_IsObject(pr->value)) { + if (JS_IsArray(ctx, pr->value)) { + for(i = 0; i < pr->u.array.count; i++) { + json_free_parse_record(ctx, &pr->u.array.elements[i]); + } + js_free(ctx, pr->u.array.elements); + } else { + for(i = 0; i < pr->u.obj.count; i++) { + JS_FreeAtom(ctx, pr->u.obj.entries[i].atom); + json_free_parse_record(ctx, &pr->u.obj.entries[i].parse_record); + } + js_free(ctx, pr->u.obj.entries); + js_free(ctx, pr->u.obj.hash_table); + } + } + JS_FreeValue(ctx, pr->value); + pr->value = JS_UNDEFINED; /* fail safe */ +} + +/* 'pr' can be NULL */ +static JSValue json_parse_value(JSParseState *s, JSONParseRecord *pr) { JSContext *ctx = s->ctx; JSValue val = JS_NULL; int ret; + if (pr) { + pr->value = JS_UNDEFINED; + } + switch(s->token.val) { case '{': { JSValue prop_val; JSAtom prop_name; - + JSONParseRecord *pr1; + int pr_size; + if (json_next_token(s)) goto fail; val = JS_NewObject(ctx); if (JS_IsException(val)) goto fail; + if (pr) { + json_parse_record_init_obj(ctx, pr, val); + pr_size = 0; + } if (s->token.val != '}') { for(;;) { if (s->token.val == TOK_STRING) { @@ -48666,7 +49527,14 @@ static JSValue json_parse_value(JSParseState *s) goto fail1; if (json_parse_expect(s, ':')) goto fail1; - prop_val = json_parse_value(s); + if (pr) { + pr1 = json_parse_record_add(ctx, pr, prop_name, &pr_size); + if (!pr1) + goto fail1; + } else { + pr1 = NULL; + } + prop_val = json_parse_value(s, pr1); if (JS_IsException(prop_val)) { fail1: JS_FreeAtom(ctx, prop_name); @@ -48694,16 +49562,31 @@ static JSValue json_parse_value(JSParseState *s) { JSValue el; uint32_t idx; - + JSONParseRecord *pr1; + int pr_size; + if (json_next_token(s)) goto fail; val = JS_NewArray(ctx); if (JS_IsException(val)) goto fail; + if (pr) { + json_parse_record_init_array(ctx, pr, val); + pr_size = 0; + } if (s->token.val != ']') { idx = 0; for(;;) { - el = json_parse_value(s); + if (pr) { + if (js_resize_array(ctx, (void **)&pr->u.array.elements, sizeof(pr->u.array.elements[0]), + &pr_size, pr->u.array.count + 1)) + goto fail; + pr1 = &pr->u.array.elements[pr->u.array.count++]; + pr1->value = JS_UNDEFINED; + } else { + pr1 = NULL; + } + el = json_parse_value(s, pr1); if (JS_IsException(el)) goto fail; ret = JS_DefinePropertyValueUint32(ctx, val, idx, el, JS_PROP_C_W_E); @@ -48724,11 +49607,21 @@ static JSValue json_parse_value(JSParseState *s) break; case TOK_STRING: val = JS_DupValue(ctx, s->token.u.str.str); + if (pr) { + json_parse_record_init_primitive(ctx, pr, val, + s->token.ptr - s->buf_start, + s->buf_ptr - s->token.ptr); + } if (json_next_token(s)) goto fail; break; case TOK_NUMBER: val = s->token.u.num.val; + if (pr) { + json_parse_record_init_primitive(ctx, pr, val, + s->token.ptr - s->buf_start, + s->buf_ptr - s->token.ptr); + } if (json_next_token(s)) goto fail; break; @@ -48736,8 +49629,18 @@ static JSValue json_parse_value(JSParseState *s) if (s->token.u.ident.atom == JS_ATOM_false || s->token.u.ident.atom == JS_ATOM_true) { val = JS_NewBool(ctx, s->token.u.ident.atom == JS_ATOM_true); + if (pr) { + json_parse_record_init_primitive(ctx, pr, val, + s->token.ptr - s->buf_start, + s->buf_ptr - s->token.ptr); + } } else if (s->token.u.ident.atom == JS_ATOM_null) { val = JS_NULL; + if (pr) { + json_parse_record_init_primitive(ctx, pr, val, + s->token.ptr - s->buf_start, + s->buf_ptr - s->token.ptr); + } } else if (s->token.u.ident.atom == JS_ATOM_NaN && s->ext_json) { /* Note: json5 identifier handling is ambiguous e.g. is '{ NaN: 1 }' a valid JSON5 production ? */ @@ -48762,12 +49665,13 @@ static JSValue json_parse_value(JSParseState *s) } return val; fail: + json_free_parse_record(ctx, pr); JS_FreeValue(ctx, val); return JS_EXCEPTION; } -JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len, - const char *filename, int flags) +JSValue JS_ParseJSON3(JSContext *ctx, const char *buf, size_t buf_len, + const char *filename, int flags, JSONParseRecord *pr) { JSParseState s1, *s = &s1; JSValue val = JS_UNDEFINED; @@ -48776,12 +49680,14 @@ JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len, s->ext_json = ((flags & JS_PARSE_JSON_EXT) != 0); if (json_next_token(s)) goto fail; - val = json_parse_value(s); + val = json_parse_value(s, pr); if (JS_IsException(val)) goto fail; if (s->token.val != TOK_EOF) { - if (js_parse_error(s, "unexpected data at the end")) + if (js_parse_error(s, "unexpected data at the end")) { + json_free_parse_record(ctx, pr); goto fail; + } } return val; fail: @@ -48790,17 +49696,25 @@ JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len, return JS_EXCEPTION; } +JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len, + const char *filename, int flags) +{ + return JS_ParseJSON3(ctx, buf, buf_len, filename, flags, NULL); +} + JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len, const char *filename) { - return JS_ParseJSON2(ctx, buf, buf_len, filename, 0); + return JS_ParseJSON3(ctx, buf, buf_len, filename, 0, NULL); } +/* if pr != NULL, then pr->value = holder by construction */ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, - JSAtom name, JSValueConst reviver) + JSAtom name, JSValueConst reviver, + const char *text_str, JSONParseRecord *pr) { - JSValue val, new_el, name_val, res; - JSValueConst args[2]; + JSValue val, new_el, name_val, res, context; + JSValueConst args[3]; int ret, is_array; uint32_t i, len = 0; JSAtom prop; @@ -48813,6 +49727,29 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, val = JS_GetProperty(ctx, holder, name); if (JS_IsException(val)) return val; + + if (pr) { + if (JS_IsArray(ctx, pr->value)) { + if (__JS_AtomIsTaggedInt(name)) { + uint32_t idx = __JS_AtomToUInt32(name); + if (idx < pr->u.array.count) { + pr = &pr->u.array.elements[idx]; + } else { + pr = NULL; + } + } + } else { + pr = json_parse_record_find(pr, name); + } + if (pr && !js_same_value(ctx, pr->value, val)) { + pr = NULL; + } + } + + context = JS_NewObject(ctx); + if (JS_IsException(context)) + goto fail; + if (JS_IsObject(val)) { is_array = JS_IsArray(ctx, val); if (is_array < 0) @@ -48833,7 +49770,7 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, } else { prop = JS_DupAtom(ctx, atoms[i].atom); } - new_el = internalize_json_property(ctx, val, prop, reviver); + new_el = internalize_json_property(ctx, val, prop, reviver, text_str, pr); if (JS_IsException(new_el)) { JS_FreeAtom(ctx, prop); goto fail; @@ -48847,6 +49784,15 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, if (ret < 0) goto fail; } + } else { + if (pr) { + new_el = JS_NewStringLen(ctx, text_str + pr->u.primitive.source_pos, + pr->u.primitive.source_len); + if (JS_IsException(new_el)) + goto fail; + if (JS_DefinePropertyValue(ctx, context, JS_ATOM_source, new_el, JS_PROP_C_W_E) < 0) + goto fail; + } } JS_FreePropertyEnum(ctx, atoms, len); atoms = NULL; @@ -48855,12 +49801,15 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, goto fail; args[0] = name_val; args[1] = val; - res = JS_Call(ctx, reviver, holder, 2, args); + args[2] = context; + res = JS_Call(ctx, reviver, holder, 3, args); JS_FreeValue(ctx, name_val); JS_FreeValue(ctx, val); + JS_FreeValue(ctx, context); return res; fail: JS_FreePropertyEnum(ctx, atoms, len); + JS_FreeValue(ctx, context); JS_FreeValue(ctx, val); return JS_EXCEPTION; } @@ -48868,37 +49817,113 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, static JSValue js_json_parse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - JSValue obj, root; - JSValueConst reviver; + JSValue obj; const char *str; size_t len; - + str = JS_ToCStringLen(ctx, &len, argv[0]); if (!str) return JS_EXCEPTION; - obj = JS_ParseJSON(ctx, str, len, ""); - JS_FreeCString(ctx, str); - if (JS_IsException(obj)) - return obj; if (argc > 1 && JS_IsFunction(ctx, argv[1])) { + JSONParseRecord pr_s, *pr = &pr_s, *pr1; + JSValue root; + JSValueConst reviver; + int size; + reviver = argv[1]; root = JS_NewObject(ctx); - if (JS_IsException(root)) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } + if (JS_IsException(root)) + goto fail; + json_parse_record_init_obj(ctx, pr, root); + size = 0; + pr1 = json_parse_record_add(ctx, pr, JS_ATOM_empty_string, &size); + if (!pr1) + goto fail1; + + obj = JS_ParseJSON3(ctx, str, len, "", 0, pr1); + if (JS_IsException(obj)) + goto fail1; + if (JS_DefinePropertyValue(ctx, root, JS_ATOM_empty_string, obj, JS_PROP_C_W_E) < 0) { + JS_FreeValue(ctx, obj); + fail1: + json_free_parse_record(ctx, pr); JS_FreeValue(ctx, root); - return JS_EXCEPTION; + goto fail; } + obj = internalize_json_property(ctx, root, JS_ATOM_empty_string, - reviver); + reviver, str, pr); + json_free_parse_record(ctx, pr); JS_FreeValue(ctx, root); + } else { + obj = JS_ParseJSON3(ctx, str, len, "", 0, NULL); + } + JS_FreeCString(ctx, str); + return obj; + fail: + JS_FreeCString(ctx, str); + return JS_EXCEPTION; +} + +static JSValue js_json_isRawJSON(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValueConst obj = argv[0]; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + return JS_NewBool(ctx, p->class_id == JS_CLASS_RAWJSON); + } else { + return JS_FALSE; + } +} + +static BOOL is_valid_raw_json_char(int c) +{ + return ((c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + c == '-' || + c == '"'); +} + +static JSValue js_json_rawJSON(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue str, res, obj; + JSString *p; + str = JS_ToString(ctx, argv[0]); + if (JS_IsException(str)) + return str; + p = JS_VALUE_GET_STRING(str); + if (p->len == 0 || + !is_valid_raw_json_char(string_get(p, 0)) || + !is_valid_raw_json_char(string_get(p, p->len - 1))) { + goto syntax_error; + } + res = js_json_parse(ctx, JS_UNDEFINED, 1, (JSValueConst *)&str); + if (JS_IsException(res)) { + syntax_error: + JS_ThrowSyntaxError(ctx, "invalid rawJSON string"); + goto fail; } + JS_FreeValue(ctx, res); + + obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_RAWJSON); + if (JS_IsException(obj)) + goto fail; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_rawJSON, str, JS_PROP_ENUMERABLE) < 0) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + JS_PreventExtensions(ctx, obj); return obj; + fail: + JS_FreeValue(ctx, str); + return JS_EXCEPTION; } + typedef struct JSONStringifyContext { JSValueConst replacer_func; JSValue stack; @@ -49068,11 +50093,18 @@ static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc, if (JS_IsException(val)) goto exception; goto concat_primitive; - } else if (cl == JS_CLASS_BOOLEAN || cl == JS_CLASS_BIG_INT) - { + } else if (cl == JS_CLASS_BOOLEAN || cl == JS_CLASS_BIG_INT) { /* This will thow the same error as for the primitive object */ set_value(ctx, &val, JS_DupValue(ctx, p->u.object_data)); goto concat_primitive; + } else if (cl == JS_CLASS_RAWJSON) { + JSValue val1; + val1 = JS_GetProperty(ctx, val, JS_ATOM_rawJSON); + if (JS_IsException(val1)) + goto exception; + JS_FreeValue(ctx, val); + val = val1; + goto concat_value; } v = js_array_includes(ctx, jsc->stack, 1, (JSValueConst *)&val); if (JS_IsException(v)) @@ -49361,7 +50393,9 @@ static JSValue js_json_stringify(JSContext *ctx, JSValueConst this_val, } static const JSCFunctionListEntry js_json_funcs[] = { + JS_CFUNC_DEF("isRawJSON", 1, js_json_isRawJSON ), JS_CFUNC_DEF("parse", 2, js_json_parse ), + JS_CFUNC_DEF("rawJSON", 1, js_json_rawJSON ), JS_CFUNC_DEF("stringify", 3, js_json_stringify ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "JSON", JS_PROP_CONFIGURABLE ), }; @@ -50681,11 +51715,11 @@ static BOOL js_weakref_is_target(JSValueConst val) /* XXX: add a specific JSWeakRef value type ? */ static BOOL js_weakref_is_live(JSValueConst val) { - int *pref_count; + void *p; if (JS_IsUndefined(val)) return TRUE; - pref_count = JS_VALUE_GET_PTR(val); - return (*pref_count != 0); + p = JS_VALUE_GET_PTR(val); + return (js_rc(p)->ref_count != 0); } /* 'val' can be JS_UNDEFINED */ @@ -50698,15 +51732,15 @@ static void js_weakref_free(JSRuntime *rt, JSValue val) /* 'mark' is tested to avoid freeing the object structure when it is about to be freed in a cycle or in free_zero_refcount() */ - if (p->weakref_count == 0 && p->header.ref_count == 0 && - p->header.mark == 0) { + if (p->weakref_count == 0 && js_rc(p)->ref_count == 0 && + js_rc(p)->mark == 0) { js_free_rt(rt, p); } } else if (JS_VALUE_GET_TAG(val) == JS_TAG_SYMBOL) { JSString *p = JS_VALUE_GET_STRING(val); assert(p->hash >= 1); p->hash--; - if (p->hash == 0 && p->header.ref_count == 0) { + if (p->hash == 0 && js_rc(p)->ref_count == 0) { /* can remove the dummy structure */ js_free_rt(rt, p); } @@ -55040,7 +56074,7 @@ static JSValue js_date_toJSON(JSContext *ctx, JSValueConst this_val, goto done; } } - method = JS_GetPropertyStr(ctx, obj, "toISOString"); + method = JS_GetProperty(ctx, obj, JS_ATOM_toISOString); if (JS_IsException(method)) goto exception; if (!JS_IsFunction(ctx, method)) { @@ -56091,6 +57125,7 @@ static JSValue js_array_buffer_transfer(JSContext *ctx, { JSArrayBuffer *abuf; uint64_t new_len, *pmax_len, max_len; + JSValue res; abuf = JS_GetOpaque2(ctx, this_val, JS_CLASS_ARRAY_BUFFER); if (!abuf) @@ -56114,18 +57149,17 @@ static JSValue js_array_buffer_transfer(JSContext *ctx, pmax_len = &max_len; } } + /* create an empty AB */ if (new_len == 0) { + res = js_array_buffer_constructor2(ctx, JS_UNDEFINED, 0, pmax_len, JS_CLASS_ARRAY_BUFFER); + if (JS_IsException(res)) + return res; JS_DetachArrayBuffer(ctx, this_val); - return js_array_buffer_constructor2(ctx, JS_UNDEFINED, 0, pmax_len, JS_CLASS_ARRAY_BUFFER); } else { uint64_t old_len; - uint8_t *bs, *new_bs; - JSFreeArrayBufferDataFunc *free_func; - bs = abuf->data; old_len = abuf->byte_length; - free_func = abuf->free_func; /* if length mismatch, realloc. Otherwise, use the same backing buffer. */ if (new_len != old_len) { @@ -56133,35 +57167,53 @@ static JSValue js_array_buffer_transfer(JSContext *ctx, if (new_len > INT32_MAX) return JS_ThrowRangeError(ctx, "invalid array buffer length"); - if (free_func != js_array_buffer_free) { + if (abuf->free_func != js_array_buffer_free) { + JSArrayBuffer *new_abuf; /* cannot use js_realloc() because the buffer was allocated with a custom allocator */ - new_bs = js_mallocz(ctx, new_len); - if (!new_bs) - return JS_EXCEPTION; - memcpy(new_bs, bs, min_int(old_len, new_len)); - abuf->free_func(ctx->rt, abuf->opaque, bs); - bs = new_bs; - free_func = js_array_buffer_free; + res = js_array_buffer_constructor2(ctx, JS_UNDEFINED, new_len, pmax_len, JS_CLASS_ARRAY_BUFFER); + if (JS_IsException(res)) + return res; + new_abuf = JS_GetOpaque2(ctx, res, JS_CLASS_ARRAY_BUFFER); + memcpy(new_abuf->data, abuf->data, min_int(old_len, new_len)); + abuf->free_func(ctx->rt, abuf->opaque, abuf->data); } else { - new_bs = js_realloc(ctx, bs, new_len); - if (!new_bs) + JSArrayBuffer *new_abuf; + uint8_t *new_bs; + /* reallocate the buffer after the new array buffer is + created in case the new array buffer creation + fails. */ + res = js_array_buffer_constructor2(ctx, JS_UNDEFINED, 0, pmax_len, JS_CLASS_ARRAY_BUFFER); + if (JS_IsException(res)) + return res; + new_bs = js_realloc(ctx, abuf->data, new_len); + if (!new_bs) { + JS_FreeValue(ctx, res); return JS_EXCEPTION; - bs = new_bs; + } if (new_len > old_len) - memset(bs + old_len, 0, new_len - old_len); + memset(new_bs + old_len, 0, new_len - old_len); + new_abuf = JS_GetOpaque2(ctx, res, JS_CLASS_ARRAY_BUFFER); + js_free(ctx, new_abuf->data); + new_abuf->data = new_bs; + new_abuf->byte_length = new_len; } + } else { + /* can keep the custom free function */ + res = js_array_buffer_constructor3(ctx, JS_UNDEFINED, new_len, pmax_len, + JS_CLASS_ARRAY_BUFFER, + abuf->data, abuf->free_func, + abuf->opaque, FALSE); + if (JS_IsException(res)) + return res; } /* neuter the backing buffer */ abuf->data = NULL; abuf->byte_length = 0; abuf->detached = TRUE; js_array_buffer_update_typed_arrays(abuf); - return js_array_buffer_constructor3(ctx, JS_UNDEFINED, new_len, pmax_len, - JS_CLASS_ARRAY_BUFFER, - bs, free_func, - NULL, FALSE); } + return res; } static JSValue js_array_buffer_resize(JSContext *ctx, JSValueConst this_val, @@ -56623,6 +57675,8 @@ static JSValue js_typed_array_with(JSContext *ctx, JSValueConst this_val, if (typed_array_is_oob(p) || idx < 0 || idx >= p->u.array.count) return JS_ThrowRangeError(ctx, "invalid array index"); + /* warning: 'this_val' may have been resized, so 'len' may be + larger than its length */ arr = js_typed_array_constructor_ta(ctx, JS_UNDEFINED, this_val, p->class_id, len); if (JS_IsException(arr)) { @@ -57699,7 +58753,7 @@ static JSValue js_TA_get_float64(JSContext *ctx, const void *a) { struct TA_sort_context { JSContext *ctx; int exception; /* 1 = exception, 2 = detached typed array */ - JSValueConst arr; + uint8_t *array; JSValueConst cmp; JSValue (*getfun)(JSContext *ctx, const void *a); int elt_size; @@ -57712,7 +58766,6 @@ static int js_TA_cmp_generic(const void *a, const void *b, void *opaque) { JSValueConst argv[2]; JSValue res; int cmp; - JSObject *p; cmp = 0; if (!psc->exception) { @@ -57720,15 +58773,9 @@ static int js_TA_cmp_generic(const void *a, const void *b, void *opaque) { error */ a_idx = *(uint32_t *)a; b_idx = *(uint32_t *)b; - p = JS_VALUE_GET_PTR(psc->arr); - if (a_idx >= p->u.array.count || b_idx >= p->u.array.count) { - /* OOB case */ - psc->exception = 2; - return 0; - } - argv[0] = psc->getfun(ctx, p->u.array.u.uint8_ptr + + argv[0] = psc->getfun(ctx, psc->array + a_idx * (size_t)psc->elt_size); - argv[1] = psc->getfun(ctx, p->u.array.u.uint8_ptr + + argv[1] = psc->getfun(ctx, psc->array + b_idx * (size_t)(psc->elt_size)); res = JS_Call(ctx, psc->cmp, JS_UNDEFINED, 2, argv); if (JS_IsException(res)) { @@ -57769,7 +58816,6 @@ static JSValue js_typed_array_sort(JSContext *ctx, JSValueConst this_val, tsc.ctx = ctx; tsc.exception = 0; - tsc.arr = this_val; tsc.cmp = argv[0]; if (!JS_IsUndefined(tsc.cmp) && check_function(ctx, tsc.cmp)) @@ -57832,65 +58878,69 @@ static JSValue js_typed_array_sort(JSContext *ctx, JSValueConst this_val, elt_size = 1 << typed_array_size_log2(p->class_id); if (!JS_IsUndefined(tsc.cmp)) { uint32_t *array_idx; - void *array_tmp; + void *array; size_t i, j; - /* XXX: a stable sort would use less memory */ + /* the array must be copied because the comparison + function may modify it */ + array = js_malloc(ctx, len * elt_size); + if (!array) + return JS_EXCEPTION; + memcpy(array, p->u.array.u.ptr, len * elt_size); + + /* array_idx is needed to have a stable sort */ array_idx = js_malloc(ctx, len * sizeof(array_idx[0])); - if (!array_idx) + if (!array_idx) { + js_free(ctx, array); return JS_EXCEPTION; + } for(i = 0; i < len; i++) array_idx[i] = i; tsc.elt_size = elt_size; + tsc.array = array; rqsort(array_idx, len, sizeof(array_idx[0]), js_TA_cmp_generic, &tsc); if (tsc.exception) { - if (tsc.exception == 1) - goto fail; + if (tsc.exception == 1) { + js_free(ctx, array_idx); + js_free(ctx, array); + return JS_EXCEPTION; + } /* detached typed array during the sort: no error */ } else { void *array_ptr = p->u.array.u.ptr; len = min_int(len, p->u.array.count); - if (len != 0) { - array_tmp = js_malloc(ctx, len * elt_size); - if (!array_tmp) { - fail: - js_free(ctx, array_idx); - return JS_EXCEPTION; + switch(elt_size) { + case 1: + for(i = 0; i < len; i++) { + j = array_idx[i]; + ((uint8_t *)array_ptr)[i] = ((uint8_t *)array)[j]; } - memcpy(array_tmp, array_ptr, len * elt_size); - switch(elt_size) { - case 1: - for(i = 0; i < len; i++) { - j = array_idx[i]; - ((uint8_t *)array_ptr)[i] = ((uint8_t *)array_tmp)[j]; - } - break; - case 2: - for(i = 0; i < len; i++) { - j = array_idx[i]; - ((uint16_t *)array_ptr)[i] = ((uint16_t *)array_tmp)[j]; - } - break; - case 4: - for(i = 0; i < len; i++) { - j = array_idx[i]; - ((uint32_t *)array_ptr)[i] = ((uint32_t *)array_tmp)[j]; - } - break; - case 8: - for(i = 0; i < len; i++) { - j = array_idx[i]; - ((uint64_t *)array_ptr)[i] = ((uint64_t *)array_tmp)[j]; - } - break; - default: - abort(); + break; + case 2: + for(i = 0; i < len; i++) { + j = array_idx[i]; + ((uint16_t *)array_ptr)[i] = ((uint16_t *)array)[j]; + } + break; + case 4: + for(i = 0; i < len; i++) { + j = array_idx[i]; + ((uint32_t *)array_ptr)[i] = ((uint32_t *)array)[j]; } - js_free(ctx, array_tmp); + break; + case 8: + for(i = 0; i < len; i++) { + j = array_idx[i]; + ((uint64_t *)array_ptr)[i] = ((uint64_t *)array)[j]; + } + break; + default: + abort(); } } js_free(ctx, array_idx); + js_free(ctx, array); } else { rqsort(p->u.array.u.ptr, len, elt_size, cmpfun, &tsc); if (tsc.exception) @@ -57918,6 +58968,797 @@ static JSValue js_typed_array_toSorted(JSContext *ctx, JSValueConst this_val, return ret; } +/* Uint8Array base64/hex (tc39 proposal-arraybuffer-base64) */ + +enum { + B64_ALPHABET_BASE64 = 0, + B64_ALPHABET_BASE64URL = 1, +}; + +enum { + B64_LAST_LOOSE = 0, + B64_LAST_STRICT = 1, + B64_LAST_STOP_BEFORE_PARTIAL = 2, +}; + +static const unsigned char b64_enc[64] = { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p', + 'q','r','s','t','u','v','w','x','y','z', + '0','1','2','3','4','5','6','7','8','9', + '+','/' +}; + +static const unsigned char b64url_enc[64] = { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p', + 'q','r','s','t','u','v','w','x','y','z', + '0','1','2','3','4','5','6','7','8','9', + '-','_' +}; + +#define K_WS 64 +#define K_ER 65 + +static const uint8_t b64_dec[256] = { + [ 0]=K_ER, [ 1]=K_ER, [ 2]=K_ER, [ 3]=K_ER, [ 4]=K_ER, [ 5]=K_ER, [ 6]=K_ER, [ 7]=K_ER, + [ 8]=K_ER, [ 9]=K_WS, [ 10]=K_WS, [ 11]=K_ER, [ 12]=K_WS, [ 13]=K_WS, [ 14]=K_ER, [ 15]=K_ER, + [ 16]=K_ER, [ 17]=K_ER, [ 18]=K_ER, [ 19]=K_ER, [ 20]=K_ER, [ 21]=K_ER, [ 22]=K_ER, [ 23]=K_ER, + [ 24]=K_ER, [ 25]=K_ER, [ 26]=K_ER, [ 27]=K_ER, [ 28]=K_ER, [ 29]=K_ER, [ 30]=K_ER, [ 31]=K_ER, + [' ']=K_WS, ['!']=K_ER, ['"']=K_ER, ['#']=K_ER, ['$']=K_ER, ['%']=K_ER, ['&']=K_ER, [ 39]=K_ER, + ['(']=K_ER, [')']=K_ER, ['*']=K_ER, ['+']= 62, [',']=K_ER, ['-']=K_ER, ['.']=K_ER, ['/']= 63, + ['0']= 52, ['1']= 53, ['2']= 54, ['3']= 55, ['4']= 56, ['5']= 57, ['6']= 58, ['7']= 59, + ['8']= 60, ['9']= 61, [':']=K_ER, [';']=K_ER, ['<']=K_ER, ['=']=K_ER, ['>']=K_ER, ['?']=K_ER, + ['@']=K_ER, ['A']= 0, ['B']= 1, ['C']= 2, ['D']= 3, ['E']= 4, ['F']= 5, ['G']= 6, + ['H']= 7, ['I']= 8, ['J']= 9, ['K']= 10, ['L']= 11, ['M']= 12, ['N']= 13, ['O']= 14, + ['P']= 15, ['Q']= 16, ['R']= 17, ['S']= 18, ['T']= 19, ['U']= 20, ['V']= 21, ['W']= 22, + ['X']= 23, ['Y']= 24, ['Z']= 25, ['[']=K_ER, [ 92]=K_ER, [']']=K_ER, ['^']=K_ER, ['_']=K_ER, + ['`']=K_ER, ['a']= 26, ['b']= 27, ['c']= 28, ['d']= 29, ['e']= 30, ['f']= 31, ['g']= 32, + ['h']= 33, ['i']= 34, ['j']= 35, ['k']= 36, ['l']= 37, ['m']= 38, ['n']= 39, ['o']= 40, + ['p']= 41, ['q']= 42, ['r']= 43, ['s']= 44, ['t']= 45, ['u']= 46, ['v']= 47, ['w']= 48, + ['x']= 49, ['y']= 50, ['z']= 51, ['{']=K_ER, ['|']=K_ER, ['}']=K_ER, ['~']=K_ER, [127]=K_ER, + [128]=K_ER, [129]=K_ER, [130]=K_ER, [131]=K_ER, [132]=K_ER, [133]=K_ER, [134]=K_ER, [135]=K_ER, + [136]=K_ER, [137]=K_ER, [138]=K_ER, [139]=K_ER, [140]=K_ER, [141]=K_ER, [142]=K_ER, [143]=K_ER, + [144]=K_ER, [145]=K_ER, [146]=K_ER, [147]=K_ER, [148]=K_ER, [149]=K_ER, [150]=K_ER, [151]=K_ER, + [152]=K_ER, [153]=K_ER, [154]=K_ER, [155]=K_ER, [156]=K_ER, [157]=K_ER, [158]=K_ER, [159]=K_ER, + [160]=K_ER, [161]=K_ER, [162]=K_ER, [163]=K_ER, [164]=K_ER, [165]=K_ER, [166]=K_ER, [167]=K_ER, + [168]=K_ER, [169]=K_ER, [170]=K_ER, [171]=K_ER, [172]=K_ER, [173]=K_ER, [174]=K_ER, [175]=K_ER, + [176]=K_ER, [177]=K_ER, [178]=K_ER, [179]=K_ER, [180]=K_ER, [181]=K_ER, [182]=K_ER, [183]=K_ER, + [184]=K_ER, [185]=K_ER, [186]=K_ER, [187]=K_ER, [188]=K_ER, [189]=K_ER, [190]=K_ER, [191]=K_ER, + [192]=K_ER, [193]=K_ER, [194]=K_ER, [195]=K_ER, [196]=K_ER, [197]=K_ER, [198]=K_ER, [199]=K_ER, + [200]=K_ER, [201]=K_ER, [202]=K_ER, [203]=K_ER, [204]=K_ER, [205]=K_ER, [206]=K_ER, [207]=K_ER, + [208]=K_ER, [209]=K_ER, [210]=K_ER, [211]=K_ER, [212]=K_ER, [213]=K_ER, [214]=K_ER, [215]=K_ER, + [216]=K_ER, [217]=K_ER, [218]=K_ER, [219]=K_ER, [220]=K_ER, [221]=K_ER, [222]=K_ER, [223]=K_ER, + [224]=K_ER, [225]=K_ER, [226]=K_ER, [227]=K_ER, [228]=K_ER, [229]=K_ER, [230]=K_ER, [231]=K_ER, + [232]=K_ER, [233]=K_ER, [234]=K_ER, [235]=K_ER, [236]=K_ER, [237]=K_ER, [238]=K_ER, [239]=K_ER, + [240]=K_ER, [241]=K_ER, [242]=K_ER, [243]=K_ER, [244]=K_ER, [245]=K_ER, [246]=K_ER, [247]=K_ER, + [248]=K_ER, [249]=K_ER, [250]=K_ER, [251]=K_ER, [252]=K_ER, [253]=K_ER, [254]=K_ER, [255]=K_ER, +}; + +static const uint8_t b64url_dec[256] = { + [ 0]=K_ER, [ 1]=K_ER, [ 2]=K_ER, [ 3]=K_ER, [ 4]=K_ER, [ 5]=K_ER, [ 6]=K_ER, [ 7]=K_ER, + [ 8]=K_ER, [ 9]=K_WS, [ 10]=K_WS, [ 11]=K_ER, [ 12]=K_WS, [ 13]=K_WS, [ 14]=K_ER, [ 15]=K_ER, + [ 16]=K_ER, [ 17]=K_ER, [ 18]=K_ER, [ 19]=K_ER, [ 20]=K_ER, [ 21]=K_ER, [ 22]=K_ER, [ 23]=K_ER, + [ 24]=K_ER, [ 25]=K_ER, [ 26]=K_ER, [ 27]=K_ER, [ 28]=K_ER, [ 29]=K_ER, [ 30]=K_ER, [ 31]=K_ER, + [' ']=K_WS, ['!']=K_ER, ['"']=K_ER, ['#']=K_ER, ['$']=K_ER, ['%']=K_ER, ['&']=K_ER, [ 39]=K_ER, + ['(']=K_ER, [')']=K_ER, ['*']=K_ER, ['+']=K_ER, [',']=K_ER, ['-']= 62, ['.']=K_ER, ['/']=K_ER, + ['0']= 52, ['1']= 53, ['2']= 54, ['3']= 55, ['4']= 56, ['5']= 57, ['6']= 58, ['7']= 59, + ['8']= 60, ['9']= 61, [':']=K_ER, [';']=K_ER, ['<']=K_ER, ['=']=K_ER, ['>']=K_ER, ['?']=K_ER, + ['@']=K_ER, ['A']= 0, ['B']= 1, ['C']= 2, ['D']= 3, ['E']= 4, ['F']= 5, ['G']= 6, + ['H']= 7, ['I']= 8, ['J']= 9, ['K']= 10, ['L']= 11, ['M']= 12, ['N']= 13, ['O']= 14, + ['P']= 15, ['Q']= 16, ['R']= 17, ['S']= 18, ['T']= 19, ['U']= 20, ['V']= 21, ['W']= 22, + ['X']= 23, ['Y']= 24, ['Z']= 25, ['[']=K_ER, [ 92]=K_ER, [']']=K_ER, ['^']=K_ER, ['_']= 63, + ['`']=K_ER, ['a']= 26, ['b']= 27, ['c']= 28, ['d']= 29, ['e']= 30, ['f']= 31, ['g']= 32, + ['h']= 33, ['i']= 34, ['j']= 35, ['k']= 36, ['l']= 37, ['m']= 38, ['n']= 39, ['o']= 40, + ['p']= 41, ['q']= 42, ['r']= 43, ['s']= 44, ['t']= 45, ['u']= 46, ['v']= 47, ['w']= 48, + ['x']= 49, ['y']= 50, ['z']= 51, ['{']=K_ER, ['|']=K_ER, ['}']=K_ER, ['~']=K_ER, [127]=K_ER, + [128]=K_ER, [129]=K_ER, [130]=K_ER, [131]=K_ER, [132]=K_ER, [133]=K_ER, [134]=K_ER, [135]=K_ER, + [136]=K_ER, [137]=K_ER, [138]=K_ER, [139]=K_ER, [140]=K_ER, [141]=K_ER, [142]=K_ER, [143]=K_ER, + [144]=K_ER, [145]=K_ER, [146]=K_ER, [147]=K_ER, [148]=K_ER, [149]=K_ER, [150]=K_ER, [151]=K_ER, + [152]=K_ER, [153]=K_ER, [154]=K_ER, [155]=K_ER, [156]=K_ER, [157]=K_ER, [158]=K_ER, [159]=K_ER, + [160]=K_ER, [161]=K_ER, [162]=K_ER, [163]=K_ER, [164]=K_ER, [165]=K_ER, [166]=K_ER, [167]=K_ER, + [168]=K_ER, [169]=K_ER, [170]=K_ER, [171]=K_ER, [172]=K_ER, [173]=K_ER, [174]=K_ER, [175]=K_ER, + [176]=K_ER, [177]=K_ER, [178]=K_ER, [179]=K_ER, [180]=K_ER, [181]=K_ER, [182]=K_ER, [183]=K_ER, + [184]=K_ER, [185]=K_ER, [186]=K_ER, [187]=K_ER, [188]=K_ER, [189]=K_ER, [190]=K_ER, [191]=K_ER, + [192]=K_ER, [193]=K_ER, [194]=K_ER, [195]=K_ER, [196]=K_ER, [197]=K_ER, [198]=K_ER, [199]=K_ER, + [200]=K_ER, [201]=K_ER, [202]=K_ER, [203]=K_ER, [204]=K_ER, [205]=K_ER, [206]=K_ER, [207]=K_ER, + [208]=K_ER, [209]=K_ER, [210]=K_ER, [211]=K_ER, [212]=K_ER, [213]=K_ER, [214]=K_ER, [215]=K_ER, + [216]=K_ER, [217]=K_ER, [218]=K_ER, [219]=K_ER, [220]=K_ER, [221]=K_ER, [222]=K_ER, [223]=K_ER, + [224]=K_ER, [225]=K_ER, [226]=K_ER, [227]=K_ER, [228]=K_ER, [229]=K_ER, [230]=K_ER, [231]=K_ER, + [232]=K_ER, [233]=K_ER, [234]=K_ER, [235]=K_ER, [236]=K_ER, [237]=K_ER, [238]=K_ER, [239]=K_ER, + [240]=K_ER, [241]=K_ER, [242]=K_ER, [243]=K_ER, [244]=K_ER, [245]=K_ER, [246]=K_ER, [247]=K_ER, + [248]=K_ER, [249]=K_ER, [250]=K_ER, [251]=K_ER, [252]=K_ER, [253]=K_ER, [254]=K_ER, [255]=K_ER, +}; + +static size_t b64_encode(const uint8_t *src, size_t len, char *dst, + const unsigned char *alpha) +{ + size_t i, j; + + for (i = 0, j = 0; i + 3 <= len; i += 3, j += 4) { + uint32_t v = 65536*src[i] + 256*src[i + 1] + src[i + 2]; + dst[j + 0] = alpha[(v >> 18) & 63]; + dst[j + 1] = alpha[(v >> 12) & 63]; + dst[j + 2] = alpha[(v >> 6) & 63]; + dst[j + 3] = alpha[v & 63]; + } + + size_t rem = len - i; + if (rem == 1) { + uint32_t v = 65536*src[i]; + dst[j++] = alpha[(v >> 18) & 63]; + dst[j++] = alpha[(v >> 12) & 63]; + dst[j++] = '='; + dst[j++] = '='; + } else if (rem == 2) { + uint32_t v = 65536*src[i] + 256*src[i + 1]; + dst[j++] = alpha[(v >> 18) & 63]; + dst[j++] = alpha[(v >> 12) & 63]; + dst[j++] = alpha[(v >> 6) & 63]; + dst[j++] = '='; + } + return j; +} + +static size_t b64_skip_ws(const char *src, size_t len, size_t index, + const uint8_t *dec_table) +{ + while (index < len && dec_table[(unsigned char)src[index]] == K_WS) + index++; + return index; +} + +/* Implements the FromBase64 abstract operation. + src/src_len: the input string (must be ASCII/latin1) + dst/max_len: output buffer + flags: b64_flags or b64_flags_url (selects valid characters) + last_chunk: B64_LAST_LOOSE, B64_LAST_STRICT, or B64_LAST_STOP_BEFORE_PARTIAL + *p_read: set to number of input characters consumed + *p_err: set to 1 on error, 0 on success + Returns: number of bytes written to dst */ +static size_t from_base64(const char *src, size_t src_len, + uint8_t *dst, size_t max_len, + const uint8_t *dec_table, int last_chunk, + size_t *p_read, int *p_err) +{ + size_t read = 0, written = 0; + uint32_t v, acc = 0; + int seen = 0; + size_t index = 0; + uint8_t ch; + + *p_err = 0; + + if (max_len == 0) { + *p_read = 0; + return 0; + } + + for (;;) { + if (seen == 0) { + /* Fast path: decode complete groups of 4 valid characters. + Breaks out on whitespace, padding, invalid chars, or capacity. */ + while (index + 4 <= src_len && written + 3 <= max_len) { + uint32_t v0, v1, v2, v3; + v0 = dec_table[(unsigned char)src[index]]; + v1 = dec_table[(unsigned char)src[index + 1]]; + v2 = dec_table[(unsigned char)src[index + 2]]; + v3 = dec_table[(unsigned char)src[index + 3]]; + if ((v0 | v1 | v2 | v3) >= 64) + break; + v = (v0 << 18) | (v1 << 12) | (v2 << 6) | v3; + dst[written] = (uint8_t)(v >> 16); + dst[written + 1] = (uint8_t)(v >> 8); + dst[written + 2] = (uint8_t)(v); + written += 3; + index += 4; + } + read = index; + + if (written >= max_len) { + *p_read = read; + return written; + } + } + + /* Slow path: handle whitespace, padding, partial groups, capacity. */ + index = b64_skip_ws(src, src_len, index, dec_table); + + if (index == src_len) { + if (seen > 0) { + if (last_chunk == B64_LAST_STOP_BEFORE_PARTIAL) { + *p_read = read; + return written; + } + if (last_chunk == B64_LAST_STRICT) { + *p_err = 1; + return 0; + } + /* loose */ + if (seen == 1) { + *p_err = 1; + return 0; + } + break; + } + *p_read = src_len; + return written; + } + + ch = src[index++]; + + if (ch == '=') { + if (seen < 2) { + *p_err = 1; + return 0; + } + index = b64_skip_ws(src, src_len, index, dec_table); + if (seen == 2) { + if (index == src_len) { + if (last_chunk == B64_LAST_STOP_BEFORE_PARTIAL) { + *p_read = read; + return written; + } + *p_err = 1; + return 0; + } + if (src[index] == '=') { + index++; + index = b64_skip_ws(src, src_len, index, dec_table); + } else { + *p_err = 1; + return 0; + } + } + /* After padding, only whitespace is allowed */ + if (index != src_len) { + *p_err = 1; + return 0; + } + if (last_chunk == B64_LAST_STRICT) { + uint32_t mask = (seen == 2) ? 0xF : 0x3; + if (acc & mask) { + *p_err = 1; + return 0; + } + } + break; + } + + v = dec_table[ch]; + if (v >= 64) { + *p_err = 1; + return 0; + } + + /* Check remaining capacity before committing to this group */ + { + size_t remaining = max_len - written; + if ((remaining == 1 && seen == 2) || + (remaining == 2 && seen == 3)) { + *p_read = read; + return written; + } + } + + acc = (acc << 6) | v; + seen++; + + if (seen == 4) { + dst[written] = (uint8_t)(acc >> 16); + dst[written + 1] = (uint8_t)(acc >> 8); + dst[written + 2] = (uint8_t)(acc); + written += 3; + acc = 0; + seen = 0; + read = index; + if (written >= max_len) { + *p_read = read; + return written; + } + } + } + + if (seen == 2) { + dst[written++] = (uint8_t)(acc >> 4); + } else if (seen == 3) { + dst[written] = (uint8_t)(acc >> 10); + dst[written + 1] = (uint8_t)(acc >> 2); + written += 2; + } + *p_read = src_len; + return written; +} + +/* Hex helpers */ +static const char u8a_hex_digits[] = "0123456789abcdef"; + +static size_t u8a_hex_encode(const uint8_t *src, size_t len, char *dst) +{ + for (size_t i = 0; i < len; i++) { + dst[i * 2] = u8a_hex_digits[src[i] >> 4]; + dst[i * 2 + 1] = u8a_hex_digits[src[i] & 0xF]; + } + return len * 2; +} + +/* Decode hex string to bytes. + Returns bytes written. Sets *p_read to chars consumed, *p_err on error. */ +static size_t u8a_hex_decode(const char *src, size_t src_len, + uint8_t *dst, size_t max_len, + size_t *p_read, int *p_err) +{ + size_t written = 0, i = 0; + *p_err = 0; + + if (src_len & 1) { + *p_err = 1; + return 0; + } + + while (i < src_len && written < max_len) { + int hi = from_hex(src[i]); + int lo = from_hex(src[i + 1]); + if (hi < 0 || lo < 0) { + *p_err = 1; + return 0; + } + dst[written++] = (uint8_t)((hi << 4) | lo); + i += 2; + } + + *p_read = i; + return written; +} + +static JSValue JS_NewUint8ArrayCopy(JSContext *ctx, const uint8_t *buf, size_t len) +{ + JSValue buffer, obj; + JSArrayBuffer *abuf; + + buffer = js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, NULL, + JS_CLASS_ARRAY_BUFFER, + (uint8_t *)buf, + js_array_buffer_free, NULL, + TRUE); + if (JS_IsException(buffer)) + return JS_EXCEPTION; + obj = js_create_from_ctor(ctx, JS_UNDEFINED, JS_CLASS_UINT8_ARRAY); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, buffer); + return JS_EXCEPTION; + } + abuf = js_get_array_buffer(ctx, buffer); + assert(abuf != NULL); + if (typed_array_init(ctx, obj, buffer, 0, abuf->byte_length, /*track_rab*/FALSE)) { + // 'buffer' is freed on error above. + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +/* Validate that this_val is a Uint8Array (type check only, no detach check). + Returns the JSObject pointer or NULL on error (throws). */ +static JSObject *check_uint8array(JSContext *ctx, JSValueConst this_val) +{ + JSObject *p; + + if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) + goto fail; + p = JS_VALUE_GET_OBJ(this_val); + if (p->class_id != JS_CLASS_UINT8_ARRAY) + goto fail; + return p; +fail: + JS_ThrowTypeError(ctx, "not a Uint8Array"); + return NULL; +} + +/* Get the data pointer and length of a Uint8Array, checking for detached + buffers. Must be called after options are read (per spec ordering). + Returns 0 on success, -1 on error (throws). */ +static int get_uint8array_bytes(JSContext *ctx, JSObject *p, + uint8_t **pdata, size_t *plen) +{ + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); + *pdata = NULL; /* fail safe */ + *plen = 0; + return -1; + } + *pdata = p->u.array.u.uint8_ptr; + *plen = p->u.array.count; + return 0; +} + +/* Validate options is undefined or an object (GetOptionsObject). + Returns 0 on success, -1 on error (throws). */ +static int check_options_object(JSContext *ctx, JSValueConst options) +{ + if (JS_IsUndefined(options)) + return 0; + if (!JS_IsObject(options)) { + JS_ThrowTypeError(ctx, "options must be an object"); + return -1; + } + return 0; +} + +/* Parse the 'alphabet' option from an options object. + Returns B64_ALPHABET_BASE64 or B64_ALPHABET_BASE64URL, or -1 on error. */ +static int parse_alphabet_option(JSContext *ctx, JSValueConst options) +{ + JSValue val; + const char *str; + int ret; + + if (JS_IsUndefined(options)) + return B64_ALPHABET_BASE64; + + val = JS_GetProperty(ctx, options, JS_ATOM_alphabet); + if (JS_IsException(val)) + return -1; + if (JS_IsUndefined(val)) + return B64_ALPHABET_BASE64; + if (!JS_IsString(val)) { + JS_FreeValue(ctx, val); + JS_ThrowTypeError(ctx, "expected string for alphabet"); + return -1; + } + + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) + return -1; + + if (!strcmp(str, "base64")) + ret = B64_ALPHABET_BASE64; + else if (!strcmp(str, "base64url")) + ret = B64_ALPHABET_BASE64URL; + else { + JS_ThrowTypeError(ctx, "invalid alphabet"); + ret = -1; + } + JS_FreeCString(ctx, str); + return ret; +} + +/* Parse the 'lastChunkHandling' option. Returns mode or -1 on error. */ +static int parse_last_chunk_option(JSContext *ctx, JSValueConst options) +{ + JSValue val; + const char *str; + int ret; + + if (JS_IsUndefined(options)) + return B64_LAST_LOOSE; + + val = JS_GetProperty(ctx, options, JS_ATOM_lastChunkHandling); + if (JS_IsException(val)) + return -1; + if (JS_IsUndefined(val)) + return B64_LAST_LOOSE; + if (!JS_IsString(val)) { + JS_FreeValue(ctx, val); + JS_ThrowTypeError(ctx, "expected string for lastChunkHandling"); + return -1; + } + + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) + return -1; + + if (!strcmp(str, "loose")) + ret = B64_LAST_LOOSE; + else if (!strcmp(str, "strict")) + ret = B64_LAST_STRICT; + else if (!strcmp(str, "stop-before-partial")) + ret = B64_LAST_STOP_BEFORE_PARTIAL; + else { + JS_ThrowTypeError(ctx, "invalid lastChunkHandling option"); + ret = -1; + } + JS_FreeCString(ctx, str); + return ret; +} + +/* Uint8Array.prototype.toBase64([options]) */ +static JSValue js_uint8array_to_base64(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *data; + size_t len; + JSValueConst options; + JSObject *p; + int alphabet, omit_padding; + size_t out_len, written; + JSString *ostr; + char *dst; + + p = check_uint8array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + + options = argc > 0 ? argv[0] : JS_UNDEFINED; + if (check_options_object(ctx, options)) + return JS_EXCEPTION; + alphabet = parse_alphabet_option(ctx, options); + if (alphabet < 0) + return JS_EXCEPTION; + + omit_padding = 0; + if (!JS_IsUndefined(options)) { + JSValue op_val = JS_GetProperty(ctx, options, JS_ATOM_omitPadding); + if (JS_IsException(op_val)) + return JS_EXCEPTION; + omit_padding = JS_ToBool(ctx, op_val); + JS_FreeValue(ctx, op_val); + } + + if (get_uint8array_bytes(ctx, p, &data, &len)) + return JS_EXCEPTION; + + out_len = 4 * ((len + 2) / 3); + + if (unlikely(out_len > JS_STRING_LEN_MAX)) + return JS_ThrowRangeError(ctx, "output too large"); + + ostr = js_alloc_string(ctx, out_len, 0); + if (!ostr) + return JS_EXCEPTION; + + dst = (char *)ostr->u.str8; + written = b64_encode(data, len, dst, + alphabet == B64_ALPHABET_BASE64URL ? b64url_enc : b64_enc); + if (omit_padding) { + while (written > 0 && dst[written - 1] == '=') + written--; + } + dst[written] = '\0'; + + ostr->len = written; + return JS_MKPTR(JS_TAG_STRING, ostr); +} + +/* Uint8Array.prototype.toHex() */ +static JSValue js_uint8array_to_hex(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *data; + size_t len, out_len; + JSObject *p; + JSString *ostr; + + p = check_uint8array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (get_uint8array_bytes(ctx, p, &data, &len)) + return JS_EXCEPTION; + + out_len = len * 2; + if (unlikely(out_len > JS_STRING_LEN_MAX)) + return JS_ThrowRangeError(ctx, "output too large"); + + ostr = js_alloc_string(ctx, out_len, 0); + if (!ostr) + return JS_EXCEPTION; + + u8a_hex_encode(data, len, (char *)ostr->u.str8); + ostr->u.str8[out_len] = '\0'; + return JS_MKPTR(JS_TAG_STRING, ostr); +} + +/* Uint8Array.fromBase64(string[, options]) */ +static JSValue js_uint8array_from_base64(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *str; + size_t str_len, read_pos, decoded_len, out_cap; + int alphabet, last_chunk, err; + uint8_t *buf; + JSValue result; + JSValueConst options; + + if (!JS_IsString(argv[0])) + return JS_ThrowTypeError(ctx, "expected string"); + + str = JS_ToCStringLen(ctx, &str_len, argv[0]); + if (!str) + return JS_EXCEPTION; + + options = argc > 1 ? argv[1] : JS_UNDEFINED; + if (check_options_object(ctx, options)) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + alphabet = parse_alphabet_option(ctx, options); + if (alphabet < 0) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + last_chunk = parse_last_chunk_option(ctx, options); + if (last_chunk < 0) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + out_cap = (str_len / 4) * 3 + 3; + buf = js_malloc(ctx, out_cap); + if (!buf) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + decoded_len = from_base64(str, str_len, buf, out_cap, + alphabet == B64_ALPHABET_BASE64URL + ? b64url_dec : b64_dec, + last_chunk, &read_pos, &err); + JS_FreeCString(ctx, str); + + if (err) { + js_free(ctx, buf); + return JS_ThrowSyntaxError(ctx, "invalid base64 string"); + } + + result = JS_NewUint8ArrayCopy(ctx, buf, decoded_len); + js_free(ctx, buf); + return result; +} + +/* Uint8Array.fromHex(string) */ +static JSValue js_uint8array_from_hex(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *str; + size_t str_len, read_pos, decoded_len, out_cap; + int err; + uint8_t *buf; + JSValue result; + + if (!JS_IsString(argv[0])) + return JS_ThrowTypeError(ctx, "expected string"); + + str = JS_ToCStringLen(ctx, &str_len, argv[0]); + if (!str) + return JS_EXCEPTION; + + out_cap = str_len / 2 + 1; + buf = js_malloc(ctx, out_cap); + if (!buf) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + decoded_len = u8a_hex_decode(str, str_len, buf, out_cap, &read_pos, &err); + JS_FreeCString(ctx, str); + + if (err) { + js_free(ctx, buf); + return JS_ThrowSyntaxError(ctx, "invalid hex string"); + } + + /* XXX: could avoid the copy */ + result = JS_NewUint8ArrayCopy(ctx, buf, decoded_len); + js_free(ctx, buf); + return result; +} + +/* Return a { read, written } result object */ +static JSValue js_make_read_written(JSContext *ctx, size_t read, size_t written) +{ + JSValue obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + if (JS_DefinePropertyValueStr(ctx, obj, "read", + JS_NewUint32(ctx, read), JS_PROP_C_W_E) < 0) + goto fail; + if (JS_DefinePropertyValueStr(ctx, obj, "written", + JS_NewUint32(ctx, written), JS_PROP_C_W_E) < 0) + goto fail; + return obj; +fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +/* Uint8Array.prototype.setFromBase64(string[, options]) */ +static JSValue js_uint8array_set_from_base64(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *data; + size_t len; + const char *str; + size_t str_len, read_pos, decoded_len; + JSObject *p; + int alphabet, last_chunk, err; + JSValueConst options; + + p = check_uint8array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + + if (!JS_IsString(argv[0])) + return JS_ThrowTypeError(ctx, "expected string"); + + str = JS_ToCStringLen(ctx, &str_len, argv[0]); + if (!str) + return JS_EXCEPTION; + + options = argc > 1 ? argv[1] : JS_UNDEFINED; + if (check_options_object(ctx, options)) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + alphabet = parse_alphabet_option(ctx, options); + if (alphabet < 0) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + last_chunk = parse_last_chunk_option(ctx, options); + if (last_chunk < 0) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + if (get_uint8array_bytes(ctx, p, &data, &len)) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + decoded_len = from_base64(str, str_len, data, len, + alphabet == B64_ALPHABET_BASE64URL + ? b64url_dec : b64_dec, + last_chunk, &read_pos, &err); + JS_FreeCString(ctx, str); + + if (err) + return JS_ThrowSyntaxError(ctx, "invalid base64 string"); + + return js_make_read_written(ctx, read_pos, decoded_len); +} + +/* Uint8Array.prototype.setFromHex(string) */ +static JSValue js_uint8array_set_from_hex(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *data; + size_t len; + const char *str; + size_t str_len, read_pos, decoded_len; + JSObject *p; + int err; + + p = check_uint8array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + + if (!JS_IsString(argv[0])) + return JS_ThrowTypeError(ctx, "expected string"); + + str = JS_ToCStringLen(ctx, &str_len, argv[0]); + if (!str) + return JS_EXCEPTION; + + if (get_uint8array_bytes(ctx, p, &data, &len)) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + decoded_len = u8a_hex_decode(str, str_len, data, len, &read_pos, &err); + JS_FreeCString(ctx, str); + + if (err) + return JS_ThrowSyntaxError(ctx, "invalid hex string"); + + return js_make_read_written(ctx, read_pos, decoded_len); +} + static const JSCFunctionListEntry js_typed_array_base_funcs[] = { JS_CFUNC_DEF("from", 1, js_typed_array_from ), JS_CFUNC_DEF("of", 0, js_typed_array_of ), @@ -57971,6 +59812,20 @@ static const JSCFunctionListEntry js_typed_array_funcs[] = { JS_PROP_INT32_DEF("BYTES_PER_ELEMENT", 8, 0), }; +static const JSCFunctionListEntry js_uint8array_proto_funcs[] = { + JS_PROP_INT32_DEF("BYTES_PER_ELEMENT", 1, 0), + JS_CFUNC_DEF("toBase64", 0, js_uint8array_to_base64), + JS_CFUNC_DEF("toHex", 0, js_uint8array_to_hex), + JS_CFUNC_DEF("setFromBase64", 1, js_uint8array_set_from_base64), + JS_CFUNC_DEF("setFromHex", 1, js_uint8array_set_from_hex), +}; + +static const JSCFunctionListEntry js_uint8array_funcs[] = { + JS_PROP_INT32_DEF("BYTES_PER_ELEMENT", 1, 0), + JS_CFUNC_DEF("fromBase64", 1, js_uint8array_from_base64), + JS_CFUNC_DEF("fromHex", 1, js_uint8array_from_hex), +}; + static JSValue js_typed_array_base_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -58123,9 +59978,6 @@ static JSValue js_typed_array_constructor_ta(JSContext *ctx, JS_ThrowTypeErrorArrayBufferOOB(ctx); goto fail; } - ta = p->u.typed_array; - src_buffer = ta->buffer; - src_abuf = src_buffer->u.array_buffer; size_log2 = typed_array_size_log2(classid); buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED, (uint64_t)len << size_log2, @@ -58141,8 +59993,12 @@ static JSValue js_typed_array_constructor_ta(JSContext *ctx, abuf = JS_GetOpaque(buffer, JS_CLASS_ARRAY_BUFFER); if (typed_array_init(ctx, obj, buffer, 0, len, /*track_rab*/FALSE)) goto fail; - if (p->class_id == classid) { - /* same type: copy the content */ + ta = p->u.typed_array; + src_buffer = ta->buffer; + src_abuf = src_buffer->u.array_buffer; + if (p->class_id == classid && + (int64_t)ta->offset + (int64_t)abuf->byte_length <= src_abuf->byte_length) { + /* same type and no overflow: copy the content */ memcpy(abuf->data, src_abuf->data + ta->offset, abuf->byte_length); } else { for(i = 0; i < len; i++) { @@ -58175,38 +60031,54 @@ static JSValue js_typed_array_constructor(JSContext *ctx, if (JS_VALUE_GET_TAG(argv[0]) != JS_TAG_OBJECT) { if (JS_ToIndex(ctx, &len, argv[0])) return JS_EXCEPTION; + obj = js_create_from_ctor(ctx, new_target, classid); + if (JS_IsException(obj)) + return JS_EXCEPTION; buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED, len << size_log2, NULL); if (JS_IsException(buffer)) - return JS_EXCEPTION; + goto fail; offset = 0; } else { JSObject *p = JS_VALUE_GET_OBJ(argv[0]); if (p->class_id == JS_CLASS_ARRAY_BUFFER || p->class_id == JS_CLASS_SHARED_ARRAY_BUFFER) { - abuf = p->u.array_buffer; - if (JS_ToIndex(ctx, &offset, argv[1])) + obj = js_create_from_ctor(ctx, new_target, classid); + if (JS_IsException(obj)) return JS_EXCEPTION; - if (abuf->detached) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - if ((offset & ((1 << size_log2) - 1)) != 0 || - offset > abuf->byte_length) - return JS_ThrowRangeError(ctx, "invalid offset"); + if (JS_ToIndex(ctx, &offset, argv[1])) + goto fail; + if ((offset & ((1 << size_log2) - 1)) != 0) + goto invalid_offset; + abuf = p->u.array_buffer; if (JS_IsUndefined(argv[2])) { + if (abuf->detached) { + JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + goto fail; + } + if (offset > abuf->byte_length) { + invalid_offset: + JS_ThrowRangeError(ctx, "invalid offset"); + goto fail; + } track_rab = array_buffer_is_resizable(abuf); - if (!track_rab) + if (!track_rab) { if ((abuf->byte_length & ((1 << size_log2) - 1)) != 0) goto invalid_length; + } len = (abuf->byte_length - offset) >> size_log2; } else { if (JS_ToIndex(ctx, &len, argv[2])) - return JS_EXCEPTION; - if (abuf->detached) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + goto fail; + if (abuf->detached) { + JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + goto fail; + } if ((offset + (len << size_log2)) > abuf->byte_length) { invalid_length: - return JS_ThrowRangeError(ctx, "invalid length"); + JS_ThrowRangeError(ctx, "invalid length"); + goto fail; } } buffer = JS_DupValue(ctx, argv[0]); @@ -58220,17 +60092,12 @@ static JSValue js_typed_array_constructor(JSContext *ctx, } } } - - obj = js_create_from_ctor(ctx, new_target, classid); - if (JS_IsException(obj)) { - JS_FreeValue(ctx, buffer); - return JS_EXCEPTION; - } - if (typed_array_init(ctx, obj, buffer, offset, len, track_rab)) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } + if (typed_array_init(ctx, obj, buffer, offset, len, track_rab)) + goto fail; return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; } static void js_typed_array_finalizer(JSRuntime *rt, JSValue val) @@ -58659,20 +60526,16 @@ typedef enum AtomicsOpEnum { ATOMICS_OP_LOAD, } AtomicsOpEnum; -static int js_atomics_get_ptr(JSContext *ctx, - void **pptr, - JSArrayBuffer **pabuf, - int *psize_log2, JSClassID *pclass_id, - JSValueConst obj, JSValueConst idx_val, - int is_waitable) +static JSObject *js_atomics_get_buf(JSContext *ctx, + JSValueConst obj, JSValueConst idx_val, + uint64_t *pidx, int is_waitable) { JSObject *p; JSTypedArray *ta; JSArrayBuffer *abuf; - void *ptr; uint64_t idx; BOOL err; - int size_log2, old_len; + int old_len; if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) goto fail; @@ -58686,56 +60549,44 @@ static int js_atomics_get_ptr(JSContext *ctx, if (err) { fail: JS_ThrowTypeError(ctx, "integer TypedArray expected"); - return -1; + return NULL; } ta = p->u.typed_array; abuf = ta->buffer->u.array_buffer; if (!abuf->shared) { if (is_waitable == 2) { JS_ThrowTypeError(ctx, "not a SharedArrayBuffer TypedArray"); - return -1; + return NULL; } if (abuf->detached) { JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - return -1; + return NULL; } } old_len = p->u.array.count; if (JS_ToIndex(ctx, &idx, idx_val)) { - return -1; + return NULL; } if (idx >= old_len) goto oob; - if (is_waitable == 1) { - /* notify(): just avoid having an invalid pointer if overflow */ - if (idx >= p->u.array.count) - ptr = NULL; - } else { + if (is_waitable != 1) { /* RevalidateAtomicAccess() */ if (typed_array_is_oob(p)) { JS_ThrowTypeErrorArrayBufferOOB(ctx); - return -1; + return NULL; } if (idx >= p->u.array.count) { oob: JS_ThrowRangeError(ctx, "out-of-bound access"); - return -1; + return NULL; } } - size_log2 = typed_array_size_log2(p->class_id); - ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); - if (pabuf) - *pabuf = abuf; - if (psize_log2) - *psize_log2 = size_log2; - if (pclass_id) - *pclass_id = p->class_id; - *pptr = ptr; - return 0; + *pidx = idx; + return p; } static JSValue js_atomics_op(JSContext *ctx, @@ -58743,15 +60594,15 @@ static JSValue js_atomics_op(JSContext *ctx, int argc, JSValueConst *argv, int op) { int size_log2; - uint64_t v, a, rep_val; + uint64_t v, a, rep_val, idx; void *ptr; JSValue ret; - JSClassID class_id; - JSArrayBuffer *abuf; - - if (js_atomics_get_ptr(ctx, &ptr, &abuf, &size_log2, &class_id, - argv[0], argv[1], 0)) + JSObject *p; + + p = js_atomics_get_buf(ctx, argv[0], argv[1], &idx, 0); + if (!p) return JS_EXCEPTION; + size_log2 = typed_array_size_log2(p->class_id); rep_val = 0; if (op == ATOMICS_OP_LOAD) { v = 0; @@ -58777,11 +60628,14 @@ static JSValue js_atomics_op(JSContext *ctx, rep_val = v32; } } - if (abuf->detached) + if (typed_array_is_oob(p)) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - } - - switch(op | (size_log2 << 3)) { + if (idx >= p->u.array.count) + return JS_ThrowRangeError(ctx, "out-of-bound access"); + } + ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); + + switch(op | (size_log2 << 3)) { #define OP(op_name, func_name) \ case ATOMICS_OP_ ## op_name | (0 << 3): \ @@ -58850,7 +60704,7 @@ static JSValue js_atomics_op(JSContext *ctx, abort(); } - switch(class_id) { + switch(p->class_id) { case JS_CLASS_INT8_ARRAY: a = (int8_t)a; goto done; @@ -58889,48 +60743,56 @@ static JSValue js_atomics_store(JSContext *ctx, int size_log2; void *ptr; JSValue ret; - JSArrayBuffer *abuf; + JSObject *p; + uint64_t idx; + int64_t v; - if (js_atomics_get_ptr(ctx, &ptr, &abuf, &size_log2, NULL, - argv[0], argv[1], 0)) + p = js_atomics_get_buf(ctx, argv[0], argv[1], &idx, 0); + if (!p) return JS_EXCEPTION; + size_log2 = typed_array_size_log2(p->class_id); if (size_log2 == 3) { - int64_t v64; ret = JS_ToBigIntFree(ctx, JS_DupValue(ctx, argv[2])); if (JS_IsException(ret)) return ret; - if (JS_ToBigInt64(ctx, &v64, ret)) { + if (JS_ToBigInt64(ctx, &v, ret)) { JS_FreeValue(ctx, ret); return JS_EXCEPTION; } - if (abuf->detached) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - atomic_store((_Atomic(uint64_t) *)ptr, v64); } else { - uint32_t v; + uint32_t v32; /* XXX: spec, would be simpler to return the written value */ ret = JS_ToIntegerFree(ctx, JS_DupValue(ctx, argv[2])); if (JS_IsException(ret)) return ret; - if (JS_ToUint32(ctx, &v, ret)) { + if (JS_ToUint32(ctx, &v32, ret)) { JS_FreeValue(ctx, ret); return JS_EXCEPTION; } - if (abuf->detached) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - switch(size_log2) { - case 0: - atomic_store((_Atomic(uint8_t) *)ptr, v); - break; - case 1: - atomic_store((_Atomic(uint16_t) *)ptr, v); - break; - case 2: - atomic_store((_Atomic(uint32_t) *)ptr, v); - break; - default: - abort(); - } + v = v32; + } + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (idx >= p->u.array.count) + return JS_ThrowRangeError(ctx, "out-of-bound access"); + + ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); + + switch(size_log2) { + case 0: + atomic_store((_Atomic(uint8_t) *)ptr, v); + break; + case 1: + atomic_store((_Atomic(uint16_t) *)ptr, v); + break; + case 2: + atomic_store((_Atomic(uint32_t) *)ptr, v); + break; + case 3: + atomic_store((_Atomic(uint64_t) *)ptr, v); + break; + default: + abort(); } return ret; } @@ -59004,8 +60866,10 @@ static JSValue js_atomics_wait(JSContext *ctx, JSValueConst this_obj, int argc, JSValueConst *argv) { + JSObject *p; int64_t v; int32_t v32; + uint64_t idx; void *ptr; int64_t timeout; struct timespec ts; @@ -59013,9 +60877,13 @@ static JSValue js_atomics_wait(JSContext *ctx, int ret, size_log2, res; double d; - if (js_atomics_get_ptr(ctx, &ptr, NULL, &size_log2, NULL, - argv[0], argv[1], 2)) + p = js_atomics_get_buf(ctx, argv[0], argv[1], &idx, 2); + if (!p) return JS_EXCEPTION; + size_log2 = typed_array_size_log2(p->class_id); + ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); + + /* 'argv[0]' is a SharedArrayBuffer so it cannot be detached nor reduced */ if (size_log2 == 3) { if (JS_ToBigInt64(ctx, &v, argv[2])) return JS_EXCEPTION; @@ -59088,12 +60956,17 @@ static JSValue js_atomics_notify(JSContext *ctx, { struct list_head *el, *el1, waiter_list; int32_t count, n; + uint64_t idx; + int size_log2; void *ptr; JSAtomicsWaiter *waiter; JSArrayBuffer *abuf; - - if (js_atomics_get_ptr(ctx, &ptr, &abuf, NULL, NULL, argv[0], argv[1], 1)) + JSObject *p; + + p = js_atomics_get_buf(ctx, argv[0], argv[1], &idx, 1); + if (!p) return JS_EXCEPTION; + size_log2 = typed_array_size_log2(p->class_id); if (JS_IsUndefined(argv[2])) { count = INT32_MAX; @@ -59103,7 +60976,10 @@ static JSValue js_atomics_notify(JSContext *ctx, } n = 0; + abuf = p->u.typed_array->buffer->u.array_buffer; if (abuf->shared && count > 0) { + /* 'argv[0]' is a SharedArrayBuffer so it cannot be detached nor reduced */ + ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); pthread_mutex_lock(&js_atomics_mutex); init_list_head(&waiter_list); list_for_each_safe(el, el1, &js_atomics_waiter_list) { @@ -59210,17 +61086,25 @@ int JS_AddIntrinsicTypedArrays(JSContext *ctx) for(i = JS_CLASS_UINT8C_ARRAY; i < JS_CLASS_UINT8C_ARRAY + JS_TYPED_ARRAY_COUNT; i++) { char buf[ATOM_GET_STR_BUF_SIZE]; const char *name; - const JSCFunctionListEntry *bpe; name = JS_AtomGetStr(ctx, buf, sizeof(buf), JS_ATOM_Uint8ClampedArray + i - JS_CLASS_UINT8C_ARRAY); - bpe = js_typed_array_funcs + typed_array_size_log2(i); - obj = JS_NewCConstructor(ctx, i, name, - ft.generic, 3, JS_CFUNC_constructor_magic, i, - typed_array_base_func, - bpe, 1, - bpe, 1, - 0); + if (i == JS_CLASS_UINT8_ARRAY) { + obj = JS_NewCConstructor(ctx, i, name, + ft.generic, 3, JS_CFUNC_constructor_magic, i, + typed_array_base_func, + js_uint8array_funcs, countof(js_uint8array_funcs), + js_uint8array_proto_funcs, countof(js_uint8array_proto_funcs), + 0); + } else { + const JSCFunctionListEntry *bpe = js_typed_array_funcs + typed_array_size_log2(i); + obj = JS_NewCConstructor(ctx, i, name, + ft.generic, 3, JS_CFUNC_constructor_magic, i, + typed_array_base_func, + bpe, 1, + bpe, 1, + 0); + } if (JS_IsException(obj)) { fail: JS_FreeValue(ctx, typed_array_base_func); @@ -59392,7 +61276,8 @@ static void finrec_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh) JSValueConst args[2]; args[0] = frd->cb; args[1] = fre->held_val; - JS_EnqueueJob(frd->realm, js_finrec_job, 2, args); + /* no exception is raised to avoid recursing into the GC */ + JS_EnqueueJob2(frd->realm, js_finrec_job, 2, args, TRUE); js_weakref_free(rt, fre->target); js_weakref_free(rt, fre->token); diff --git a/vendor/quickjs/quickjs.h b/vendor/quickjs/quickjs.h index 92cc000d..476d7351 100644 --- a/vendor/quickjs/quickjs.h +++ b/vendor/quickjs/quickjs.h @@ -95,6 +95,7 @@ enum { /* any larger tag is FLOAT64 if JS_NAN_BOXING */ }; +/* must match the layout of 'JSMallocBlockHeader' */ typedef struct JSRefCountHeader { int ref_count; } JSRefCountHeader; @@ -215,7 +216,7 @@ static inline JSValue __JS_NewShortBigInt(JSContext *ctx, int32_t d) #else /* !JS_NAN_BOXING */ typedef union JSValueUnion { - int32_t int32; + uint64_t uint64; double float64; void *ptr; #if JS_SHORT_BIG_INT_BITS == 32 @@ -235,13 +236,15 @@ typedef struct JSValue { #define JS_VALUE_GET_TAG(v) ((int32_t)(v).tag) /* same as JS_VALUE_GET_TAG, but return JS_TAG_FLOAT64 with NaN boxing */ #define JS_VALUE_GET_NORM_TAG(v) JS_VALUE_GET_TAG(v) -#define JS_VALUE_GET_INT(v) ((v).u.int32) -#define JS_VALUE_GET_BOOL(v) ((v).u.int32) +#define JS_VALUE_GET_INT(v) ((int)(v).u.uint64) +#define JS_VALUE_GET_BOOL(v) ((int)(v).u.uint64) #define JS_VALUE_GET_FLOAT64(v) ((v).u.float64) #define JS_VALUE_GET_SHORT_BIG_INT(v) ((v).u.short_big_int) #define JS_VALUE_GET_PTR(v) ((v).u.ptr) -#define JS_MKVAL(tag, val) (JSValue){ (JSValueUnion){ .int32 = val }, tag } +/* avoid uninitialized data by using a 64 bit field even if only 32 + bits are needed because some compilers generate slower code */ +#define JS_MKVAL(tag, val) (JSValue){ (JSValueUnion){ .uint64 = (uint32_t)(val) }, tag } #define JS_MKPTR(tag, p) (JSValue){ (JSValueUnion){ .ptr = p }, tag } #define JS_TAG_IS_FLOAT64(tag) ((unsigned)(tag) == JS_TAG_FLOAT64) @@ -675,10 +678,16 @@ JSValue __js_printf_like(2, 3) JS_ThrowInternalError(JSContext *ctx, const char JSValue JS_ThrowOutOfMemory(JSContext *ctx); void __JS_FreeValue(JSContext *ctx, JSValue v); + +static inline JSRefCountHeader *__js_rc(void *ptr) +{ + return (JSRefCountHeader *)((uint32_t *)ptr - 1); +} + static inline void JS_FreeValue(JSContext *ctx, JSValue v) { if (JS_VALUE_HAS_REF_COUNT(v)) { - JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); + JSRefCountHeader *p = __js_rc(JS_VALUE_GET_PTR(v)); if (--p->ref_count <= 0) { __JS_FreeValue(ctx, v); } @@ -688,7 +697,7 @@ void __JS_FreeValueRT(JSRuntime *rt, JSValue v); static inline void JS_FreeValueRT(JSRuntime *rt, JSValue v) { if (JS_VALUE_HAS_REF_COUNT(v)) { - JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); + JSRefCountHeader *p = __js_rc(JS_VALUE_GET_PTR(v)); if (--p->ref_count <= 0) { __JS_FreeValueRT(rt, v); } @@ -698,7 +707,7 @@ static inline void JS_FreeValueRT(JSRuntime *rt, JSValue v) static inline JSValue JS_DupValue(JSContext *ctx, JSValueConst v) { if (JS_VALUE_HAS_REF_COUNT(v)) { - JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); + JSRefCountHeader *p = __js_rc(JS_VALUE_GET_PTR(v)); p->ref_count++; } return (JSValue)v; @@ -707,7 +716,7 @@ static inline JSValue JS_DupValue(JSContext *ctx, JSValueConst v) static inline JSValue JS_DupValueRT(JSRuntime *rt, JSValueConst v) { if (JS_VALUE_HAS_REF_COUNT(v)) { - JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); + JSRefCountHeader *p = __js_rc(JS_VALUE_GET_PTR(v)); p->ref_count++; } return (JSValue)v; diff --git a/vendor/quickjs/release.sh b/vendor/quickjs/release.sh index 6f4bdf50..0edbd4dd 100755 --- a/vendor/quickjs/release.sh +++ b/vendor/quickjs/release.sh @@ -32,7 +32,7 @@ rm -rf $outdir mkdir -p $outdir $outdir/unicode $outdir/tests cp unicode/* $outdir/unicode -cp -a tests/bench-v8 $outdir/tests +cp -a tests/bench-v8 tests/octane tests/cli $outdir/tests ( cd /tmp && tar Jcvf /tmp/${name}.tar.xz ${d} ) diff --git a/vendor/quickjs/run-test262.c b/vendor/quickjs/run-test262.c index 100ed134..9d03964e 100644 --- a/vendor/quickjs/run-test262.c +++ b/vendor/quickjs/run-test262.c @@ -34,26 +34,67 @@ #include #include #include +#include +#include +#ifdef _WIN32 +#include +#endif #include "cutils.h" #include "list.h" #include "quickjs-libc.h" -/* enable test262 thread support to test SharedArrayBuffer and Atomics */ -#define CONFIG_AGENT - #define CMD_NAME "run-test262" typedef struct namelist_t { char **array; int count; int size; - unsigned int sorted : 1; } namelist_t; +/* per execution thread context */ +typedef struct { + pthread_mutex_t agent_mutex; + pthread_cond_t agent_cond; + /* list of Test262Agent.link */ + struct list_head agent_list; + + pthread_mutex_t report_mutex; + /* list of AgentReport.link */ + struct list_head report_list; + + int async_done; +} ThreadLocalStorage; + +typedef struct { + struct list_head link; + ThreadLocalStorage *tls; + pthread_t tid; + char *script; + JSValue broadcast_func; + BOOL broadcast_pending; + JSValue broadcast_sab; /* in the main context */ + uint8_t *broadcast_sab_buf; + size_t broadcast_sab_size; + int32_t broadcast_val; +} Test262Agent; + +typedef struct { + struct list_head link; + char *str; +} AgentReport; + namelist_t test_list; namelist_t exclude_list; namelist_t exclude_dir_list; +namelist_t error_list; +pthread_mutex_t error_list_mutex; + +int nthreads; +pthread_t progress_thread; +BOOL progress_exit_request; +pthread_cond_t progress_cond; +pthread_mutex_t progress_mutex; FILE *outfile; enum test_mode_t { @@ -73,6 +114,7 @@ int stats_count; JSMemoryUsage stats_all, stats_avg, stats_min, stats_max; char *stats_min_filename; char *stats_max_filename; +pthread_mutex_t stats_mutex; int verbose; char *harness_dir; char *harness_exclude; @@ -81,16 +123,117 @@ char *harness_skip_features; int *harness_skip_features_count; char *error_filename; char *error_file; -FILE *error_out; char *report_filename; int update_errors; -int test_count, test_failed, test_index, test_skipped, test_excluded; -int new_errors, changed_errors, fixed_errors; -int async_done; +int slow_test_threshold; +int start_index, stop_index; +int test_excluded; +_Atomic int test_count, test_failed, test_skipped; +_Atomic int new_errors, changed_errors, fixed_errors; void warning(const char *, ...) __attribute__((__format__(__printf__, 1, 2))); void fatal(int, const char *, ...) __attribute__((__format__(__printf__, 2, 3))); +void atomic_inc(volatile _Atomic int *p) +{ + atomic_fetch_add(p, 1); +} + +#if defined(_WIN32) +static int cpu_count(void) +{ + DWORD_PTR procmask, sysmask; + long count; + int i; + + count = 0; + if (GetProcessAffinityMask(GetCurrentProcess(), &procmask, &sysmask)) + for (i = 0; i < 8 * sizeof(procmask); i++) + count += 1 & (procmask >> i); + return count; +} +#elif defined(__linux__) +/* return the number of available physical cores or -1 if not available */ +static int get_cpu_info_physical_cores(void) +{ + FILE *f; + int nb_cores, physical_id; + char line[1024], *p; + char *field, *value; + int len; + + f = fopen("/proc/cpuinfo", "rb"); + if (!f) + return -1; + nb_cores = 0; + physical_id = -1; + for(;;) { + if (fgets(line, sizeof(line), f) == NULL) + break; + len = strlen(line); + while (len > 0 && isspace(line[len - 1])) + len--; + line[len] = '\0'; + field = line; + p = line; + if (*p == '#') + continue; + while (*p != ':' && *p != '\0') + p++; + if (*p == '\0') + continue; + *p = '\0'; + p++; + while (isspace(*p)) + p++; + value = p; + + len = strlen(field); + while (len > 0 && isspace(field[len - 1])) + len--; + field[len] = '\0'; + + // printf("'%s' '%s'\n", field, value); + if (!strcmp(field, "cpu cores")) { + if (nb_cores == 0) { + nb_cores = strtol(value, NULL, 0); + } + } else if (!strcmp(field, "physical id")) { + physical_id = max_int(physical_id, strtol(value, NULL, 0)); + } + } + fclose(f); + // printf("nb_cores=%d physical_id=%d\n", nb_cores, physical_id); + if (nb_cores <= 0 || physical_id < 0) + return -1; + return nb_cores * (physical_id + 1); +} + +static int cpu_count(void) +{ + int n = get_cpu_info_physical_cores(); + if (n <= 0) + n = 1; + return n; +} +#else /* __linux__ */ +static int cpu_count(void) +{ + return sysconf(_SC_NPROCESSORS_ONLN); +} +#endif /* !__linux__ */ + +static void init_thread_local_storage(ThreadLocalStorage *tls) +{ + memset(tls, 0, sizeof(*tls)); + pthread_mutex_init(&tls->agent_mutex, NULL); + pthread_cond_init(&tls->agent_cond, NULL); + init_list_head(&tls->agent_list); + + pthread_mutex_init(&tls->report_mutex, NULL); + init_list_head(&tls->report_list); +} + void warning(const char *fmt, ...) { va_list ap; @@ -248,31 +391,30 @@ int namelist_cmp_indirect(const void *a, const void *b) return namelist_cmp(*(const char **)a, *(const char **)b); } -void namelist_sort(namelist_t *lp) +void namelist_sort(namelist_t *lp, BOOL remove_duplicates) { int i, count; if (lp->count > 1) { qsort(lp->array, lp->count, sizeof(*lp->array), namelist_cmp_indirect); /* remove duplicates */ - for (count = i = 1; i < lp->count; i++) { - if (namelist_cmp(lp->array[count - 1], lp->array[i]) == 0) { - free(lp->array[i]); - } else { - lp->array[count++] = lp->array[i]; + if (remove_duplicates) { + for (count = i = 1; i < lp->count; i++) { + if (namelist_cmp(lp->array[count - 1], lp->array[i]) == 0) { + free(lp->array[i]); + } else { + lp->array[count++] = lp->array[i]; + } } + lp->count = count; } - lp->count = count; } - lp->sorted = 1; } -int namelist_find(namelist_t *lp, const char *name) +/* the list must be sorted */ +int namelist_find(const namelist_t *lp, const char *name) { int a, b, m, cmp; - if (!lp->sorted) { - namelist_sort(lp); - } for (a = 0, b = lp->count; a < b;) { m = a + (b - a) / 2; cmp = namelist_cmp(lp->array[m], name); @@ -382,33 +524,37 @@ static void js_print_value_write(void *opaque, const char *buf, size_t len) static JSValue js_print(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); int i; JSValueConst v; - if (outfile) { - for (i = 0; i < argc; i++) { - if (i != 0) - fputc(' ', outfile); - v = argv[i]; - if (JS_IsString(v)) { - const char *str; - size_t len; - str = JS_ToCStringLen(ctx, &len, v); - if (!str) - return JS_EXCEPTION; - if (!strcmp(str, "Test262:AsyncTestComplete")) { - async_done++; - } else if (strstart(str, "Test262:AsyncTestFailure", NULL)) { - async_done = 2; /* force an error */ - } + for (i = 0; i < argc; i++) { + if (i != 0 && outfile) + fputc(' ', outfile); + v = argv[i]; + if (JS_IsString(v)) { + const char *str; + size_t len; + str = JS_ToCStringLen(ctx, &len, v); + if (!str) + return JS_EXCEPTION; + if (!strcmp(str, "Test262:AsyncTestComplete")) { + tls->async_done++; + } else if (strstart(str, "Test262:AsyncTestFailure", NULL)) { + tls->async_done = 2; /* force an error */ + } + if (outfile) { fwrite(str, 1, len, outfile); - JS_FreeCString(ctx, str); - } else { + } + JS_FreeCString(ctx, str); + } else { + if (outfile) { JS_PrintValue(ctx, js_print_value_write, outfile, v, NULL); } } - fputc('\n', outfile); } + if (outfile) + fputc('\n', outfile); return JS_UNDEFINED; } @@ -433,42 +579,13 @@ static JSValue js_evalScript(JSContext *ctx, JSValue this_val, return ret; } -#ifdef CONFIG_AGENT - -#include - -typedef struct { - struct list_head link; - pthread_t tid; - char *script; - JSValue broadcast_func; - BOOL broadcast_pending; - JSValue broadcast_sab; /* in the main context */ - uint8_t *broadcast_sab_buf; - size_t broadcast_sab_size; - int32_t broadcast_val; -} Test262Agent; - -typedef struct { - struct list_head link; - char *str; -} AgentReport; - static JSValue add_helpers1(JSContext *ctx); static void add_helpers(JSContext *ctx); -static pthread_mutex_t agent_mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t agent_cond = PTHREAD_COND_INITIALIZER; -/* list of Test262Agent.link */ -static struct list_head agent_list = LIST_HEAD_INIT(agent_list); - -static pthread_mutex_t report_mutex = PTHREAD_MUTEX_INITIALIZER; -/* list of AgentReport.link */ -static struct list_head report_list = LIST_HEAD_INIT(report_list); - static void *agent_start(void *arg) { Test262Agent *agent = arg; + ThreadLocalStorage *tls = agent->tls; JSRuntime *rt; JSContext *ctx; JSValue ret_val; @@ -478,6 +595,7 @@ static void *agent_start(void *arg) if (rt == NULL) { fatal(1, "JS_NewRuntime failure"); } + JS_SetRuntimeOpaque(rt, tls); ctx = JS_NewContext(rt); if (ctx == NULL) { JS_FreeRuntime(rt); @@ -507,15 +625,15 @@ static void *agent_start(void *arg) } else { JSValue args[2]; - pthread_mutex_lock(&agent_mutex); + pthread_mutex_lock(&tls->agent_mutex); while (!agent->broadcast_pending) { - pthread_cond_wait(&agent_cond, &agent_mutex); + pthread_cond_wait(&tls->agent_cond, &tls->agent_mutex); } agent->broadcast_pending = FALSE; - pthread_cond_signal(&agent_cond); + pthread_cond_signal(&tls->agent_cond); - pthread_mutex_unlock(&agent_mutex); + pthread_mutex_unlock(&tls->agent_mutex); args[0] = JS_NewArrayBuffer(ctx, agent->broadcast_sab_buf, agent->broadcast_sab_size, @@ -543,6 +661,7 @@ static void *agent_start(void *arg) static JSValue js_agent_start(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); const char *script; Test262Agent *agent; pthread_attr_t attr; @@ -555,11 +674,12 @@ static JSValue js_agent_start(JSContext *ctx, JSValue this_val, return JS_EXCEPTION; agent = malloc(sizeof(*agent)); memset(agent, 0, sizeof(*agent)); + agent->tls = tls; agent->broadcast_func = JS_UNDEFINED; agent->broadcast_sab = JS_UNDEFINED; agent->script = strdup(script); JS_FreeCString(ctx, script); - list_add_tail(&agent->link, &agent_list); + list_add_tail(&agent->link, &tls->agent_list); pthread_attr_init(&attr); // musl libc gives threads 80 kb stacks, much smaller than // JS_DEFAULT_STACK_SIZE (256 kb) @@ -571,10 +691,11 @@ static JSValue js_agent_start(JSContext *ctx, JSValue this_val, static void js_agent_free(JSContext *ctx) { + ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); struct list_head *el, *el1; Test262Agent *agent; - list_for_each_safe(el, el1, &agent_list) { + list_for_each_safe(el, el1, &tls->agent_list) { agent = list_entry(el, Test262Agent, link); pthread_join(agent->tid, NULL); JS_FreeValue(ctx, agent->broadcast_sab); @@ -593,11 +714,11 @@ static JSValue js_agent_leaving(JSContext *ctx, JSValue this_val, return JS_UNDEFINED; } -static BOOL is_broadcast_pending(void) +static BOOL is_broadcast_pending(ThreadLocalStorage *tls) { struct list_head *el; Test262Agent *agent; - list_for_each(el, &agent_list) { + list_for_each(el, &tls->agent_list) { agent = list_entry(el, Test262Agent, link); if (agent->broadcast_pending) return TRUE; @@ -608,6 +729,7 @@ static BOOL is_broadcast_pending(void) static JSValue js_agent_broadcast(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); JSValueConst sab = argv[0]; struct list_head *el; Test262Agent *agent; @@ -626,8 +748,8 @@ static JSValue js_agent_broadcast(JSContext *ctx, JSValue this_val, /* broadcast the values and wait until all agents have started calling their callbacks */ - pthread_mutex_lock(&agent_mutex); - list_for_each(el, &agent_list) { + pthread_mutex_lock(&tls->agent_mutex); + list_for_each(el, &tls->agent_list) { agent = list_entry(el, Test262Agent, link); agent->broadcast_pending = TRUE; /* the shared array buffer is used by the thread, so increment @@ -637,12 +759,12 @@ static JSValue js_agent_broadcast(JSContext *ctx, JSValue this_val, agent->broadcast_sab_size = buf_size; agent->broadcast_val = val; } - pthread_cond_broadcast(&agent_cond); + pthread_cond_broadcast(&tls->agent_cond); - while (is_broadcast_pending()) { - pthread_cond_wait(&agent_cond, &agent_mutex); + while (is_broadcast_pending(tls)) { + pthread_cond_wait(&tls->agent_cond, &tls->agent_mutex); } - pthread_mutex_unlock(&agent_mutex); + pthread_mutex_unlock(&tls->agent_mutex); return JS_UNDEFINED; } @@ -685,17 +807,18 @@ static JSValue js_agent_monotonicNow(JSContext *ctx, JSValue this_val, static JSValue js_agent_getReport(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); AgentReport *rep; JSValue ret; - pthread_mutex_lock(&report_mutex); - if (list_empty(&report_list)) { + pthread_mutex_lock(&tls->report_mutex); + if (list_empty(&tls->report_list)) { rep = NULL; } else { - rep = list_entry(report_list.next, AgentReport, link); + rep = list_entry(tls->report_list.next, AgentReport, link); list_del(&rep->link); } - pthread_mutex_unlock(&report_mutex); + pthread_mutex_unlock(&tls->report_mutex); if (rep) { ret = JS_NewString(ctx, rep->str); free(rep->str); @@ -709,6 +832,7 @@ static JSValue js_agent_getReport(JSContext *ctx, JSValue this_val, static JSValue js_agent_report(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); const char *str; AgentReport *rep; @@ -719,9 +843,9 @@ static JSValue js_agent_report(JSContext *ctx, JSValue this_val, rep->str = strdup(str); JS_FreeCString(ctx, str); - pthread_mutex_lock(&report_mutex); - list_add_tail(&rep->link, &report_list); - pthread_mutex_unlock(&report_mutex); + pthread_mutex_lock(&tls->report_mutex); + list_add_tail(&rep->link, &tls->report_list); + pthread_mutex_unlock(&tls->report_mutex); return JS_UNDEFINED; } @@ -747,7 +871,6 @@ static JSValue js_new_agent(JSContext *ctx) countof(js_agent_funcs)); return agent; } -#endif static JSValue js_createRealm(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) @@ -798,9 +921,7 @@ static JSValue add_helpers1(JSContext *ctx) JS_SetPropertyStr(ctx, obj262, "codePointRange", JS_NewCFunction(ctx, js_string_codePointRange, "codePointRange", 2)); -#ifdef CONFIG_AGENT JS_SetPropertyStr(ctx, obj262, "agent", js_new_agent(ctx)); -#endif JS_SetPropertyStr(ctx, obj262, "global", JS_DupValue(ctx, global_obj)); @@ -947,7 +1068,7 @@ void update_exclude_dirs(void) char *name; int i, j, count; - /* split directpries from exclude_list */ + /* split directories from exclude_list */ for (count = i = 0; i < ep->count; i++) { name = ep->array[i]; if (has_suffix(name, "/")) { @@ -959,7 +1080,7 @@ void update_exclude_dirs(void) } ep->count = count; - namelist_sort(dp); + namelist_sort(dp, TRUE); /* filter out excluded directories */ for (count = i = 0; i < lp->count; i++) { @@ -1240,11 +1361,28 @@ int longest_match(const char *str, const char *find, int pos, int *ppos, int lin return maxlen; } +static __attribute__((__format__(__printf__, 1, 2))) void print_error(const char *fmt, ...) +{ + char buf[1024]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (update_errors) { + pthread_mutex_lock(&error_list_mutex); + namelist_add(&error_list, NULL, buf); + pthread_mutex_unlock(&error_list_mutex); + } else { + fputs(buf, stdout); + } +} + static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len, const char *filename, int is_test, int is_negative, const char *error_type, FILE *outfile, int eval_flags, int is_async) { + ThreadLocalStorage *tls = JS_GetRuntimeOpaque(JS_GetRuntime(ctx)); JSValue res_val, exception_val; int ret, error_line, pos, pos_line; BOOL is_error, has_error_line, ret_promise; @@ -1258,7 +1396,7 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len, /* a module evaluation returns a promise */ ret_promise = ((eval_flags & JS_EVAL_TYPE_MODULE) != 0); - async_done = 0; /* counter of "Test262:AsyncTestComplete" messages */ + tls->async_done = 0; /* counter of "Test262:AsyncTestComplete" messages */ res_val = JS_Eval(ctx, buf, buf_len, filename, eval_flags); @@ -1277,7 +1415,7 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len, } else if (ret == 0) { if (is_async) { /* test if the test called $DONE() once */ - if (async_done != 1) { + if (tls->async_done != 1) { res_val = JS_ThrowTypeError(ctx, "$DONE() not called"); } else { res_val = JS_UNDEFINED; @@ -1386,11 +1524,11 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len, } else { if (!s) { // not yet reported if (msg) { - fprintf(error_out, "%s:%d: %sunexpected error type: %s\n", - filename, error_line, strict_mode, msg); + print_error("%s:%d: %sunexpected error type: %s\n", + filename, error_line, strict_mode, msg); } else { - fprintf(error_out, "%s:%d: %sexpected error\n", - filename, error_line, strict_mode); + print_error("%s:%d: %sexpected error\n", + filename, error_line, strict_mode); } new_errors++; } @@ -1406,8 +1544,8 @@ static int eval_buf(JSContext *ctx, const char *buf, size_t buf_len, longest_match(buf, p, pos, &pos, pos_line, &error_line); } } - fprintf(error_out, "%s:%d: %s%s%s\n", filename, error_line, strict_mode, - error_file ? "unexpected error: " : "", msg); + print_error("%s:%d: %s%s%s\n", filename, error_line, strict_mode, + error_file ? "unexpected error: " : "", msg); if (s && (!str_equal(s, msg) || error_line != s_line)) { printf("%s:%d: %sprevious error: %s\n", filename, s_line, strict_mode, s); @@ -1545,6 +1683,8 @@ static char *get_option(char **pp, int *state) void update_stats(JSRuntime *rt, const char *filename) { JSMemoryUsage stats; JS_ComputeMemoryUsage(rt, &stats); + + pthread_mutex_lock(&stats_mutex); if (stats_count++ == 0) { stats_avg = stats_all = stats_min = stats_max = stats; stats_min_filename = strdup(filename); @@ -1587,9 +1727,11 @@ void update_stats(JSRuntime *rt, const char *filename) { update(fast_array_elements); } #undef update + pthread_mutex_unlock(&stats_mutex); } -int run_test_buf(const char *filename, const char *harness, namelist_t *ip, +int run_test_buf(ThreadLocalStorage *tls, + const char *filename, const char *harness, namelist_t *ip, char *buf, size_t buf_len, const char* error_type, int eval_flags, BOOL is_negative, BOOL is_async, BOOL can_block) @@ -1602,6 +1744,7 @@ int run_test_buf(const char *filename, const char *harness, namelist_t *ip, if (rt == NULL) { fatal(1, "JS_NewRuntime failure"); } + JS_SetRuntimeOpaque(rt, tls); ctx = JS_NewContext(rt); if (ctx == NULL) { JS_FreeRuntime(rt); @@ -1630,15 +1773,13 @@ int run_test_buf(const char *filename, const char *harness, namelist_t *ip, if (dump_memory) { update_stats(rt, filename); } -#ifdef CONFIG_AGENT js_agent_free(ctx); -#endif JS_FreeContext(ctx); JS_FreeRuntime(rt); - test_count++; + atomic_inc(&test_count); if (ret) { - test_failed++; + atomic_inc(&test_failed); if (outfile) { /* do not output a failure number to minimize diff */ fprintf(outfile, " FAILED\n"); @@ -1647,7 +1788,7 @@ int run_test_buf(const char *filename, const char *harness, namelist_t *ip, return ret; } -int run_test(const char *filename, int index) +int run_test(ThreadLocalStorage *tls, const char *filename, int index) { char harnessbuf[1024]; char *harness; @@ -1853,7 +1994,7 @@ int run_test(const char *filename, int index) } if (skip || use_strict + use_nostrict == 0) { - test_skipped++; + atomic_inc(&test_skipped); ret = -2; } else { clock_t clocks; @@ -1866,12 +2007,12 @@ int run_test(const char *filename, int index) clocks = clock(); ret = 0; if (use_nostrict) { - ret = run_test_buf(filename, harness, ip, buf, buf_len, + ret = run_test_buf(tls, filename, harness, ip, buf, buf_len, error_type, eval_flags, is_negative, is_async, can_block); } if (use_strict) { - ret |= run_test_buf(filename, harness, ip, buf, buf_len, + ret |= run_test_buf(tls, filename, harness, ip, buf, buf_len, error_type, eval_flags | JS_EVAL_FLAG_STRICT, is_negative, is_async, can_block); } @@ -1889,7 +2030,8 @@ int run_test(const char *filename, int index) } /* run a test when called by test262-harness+eshost */ -int run_test262_harness_test(const char *filename, BOOL is_module) +int run_test262_harness_test(ThreadLocalStorage *tls, + const char *filename, BOOL is_module, BOOL can_block) { JSRuntime *rt; JSContext *ctx; @@ -1897,7 +2039,6 @@ int run_test262_harness_test(const char *filename, BOOL is_module) size_t buf_len; int eval_flags, ret_code, ret; JSValue res_val; - BOOL can_block; outfile = stdout; /* for js_print */ @@ -1905,6 +2046,7 @@ int run_test262_harness_test(const char *filename, BOOL is_module) if (rt == NULL) { fatal(1, "JS_NewRuntime failure"); } + JS_SetRuntimeOpaque(rt, tls); ctx = JS_NewContext(rt); if (ctx == NULL) { JS_FreeRuntime(rt); @@ -1912,7 +2054,6 @@ int run_test262_harness_test(const char *filename, BOOL is_module) } JS_SetRuntimeInfo(rt, filename); - can_block = TRUE; JS_SetCanBlock(rt, can_block); /* loader for ES6 modules */ @@ -1960,78 +2101,115 @@ int run_test262_harness_test(const char *filename, BOOL is_module) JS_FreeValue(ctx, promise); } free(buf); -#ifdef CONFIG_AGENT js_agent_free(ctx); -#endif JS_FreeContext(ctx); JS_FreeRuntime(rt); return ret_code; } -clock_t last_clock; +static int pthread_cond_timedwait2(pthread_cond_t *cond, pthread_mutex_t *mutex, int timeout) +{ + struct timespec ts; + + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += timeout / 1000; + ts.tv_nsec += (timeout % 1000) * 1000000; + if (ts.tv_nsec >= 1000000000) { + ts.tv_nsec -= 1000000000; + ts.tv_sec++; + } + return pthread_cond_timedwait(cond, mutex, &ts); +} + +void *show_progress(void *opaque) +{ + int test_skipped1, test_failed1, test_count1; + + pthread_mutex_lock(&progress_mutex); + for(;;) { + pthread_cond_timedwait2(&progress_cond, &progress_mutex, 50); + + test_failed1 = atomic_load(&test_failed); + test_count1 = atomic_load(&test_count); + test_skipped1 = atomic_load(&test_skipped); -void show_progress(int force) { - clock_t t = clock(); - if (force || !last_clock || (t - last_clock) > CLOCKS_PER_SEC / 20) { - last_clock = t; if (compact) { static int last_test_skipped; static int last_test_failed; static int dots; char c = '.'; - if (test_skipped > last_test_skipped) + + if (test_skipped1 > last_test_skipped) c = '-'; - if (test_failed > last_test_failed) + if (test_failed1 > last_test_failed) c = '!'; - last_test_skipped = test_skipped; - last_test_failed = test_failed; + last_test_skipped = test_skipped1; + last_test_failed = test_failed1; + fputc(c, stderr); - if (force || ++dots % 60 == 0) { + if (progress_exit_request || ++dots % 60 == 0) { fprintf(stderr, " %d/%d/%d\n", - test_failed, test_count, test_skipped); + test_failed1, test_count1, test_skipped1); } } else { /* output progress indicator: erase end of line and return to col 0 */ fprintf(stderr, "%d/%d/%d\033[K\r", - test_failed, test_count, test_skipped); + test_failed1, test_count1, test_skipped1); } fflush(stderr); + if (progress_exit_request) + break; } + pthread_mutex_unlock(&progress_mutex); + return NULL; } -static int slow_test_threshold; +enum { INCLUDE, EXCLUDE, SKIP }; -void run_test_dir_list(namelist_t *lp, int start_index, int stop_index) +int include_exclude_or_skip(int i) // naming is hard... { - int i; + if (namelist_find(&exclude_list, test_list.array[i]) >= 0) + return EXCLUDE; + if (i < start_index) + return SKIP; + if (stop_index >= 0 && i > stop_index) + return SKIP; + return INCLUDE; +} - namelist_sort(lp); - for (i = 0; i < lp->count; i++) { +typedef struct { + pthread_t tid; + int thread_index; +} RunTestDirThread; + +void *run_test_dir_list(void *opaque) +{ + RunTestDirThread *th = opaque; + ThreadLocalStorage tls_s, *tls = &tls_s; + namelist_t *lp = &test_list; + int i; + + init_thread_local_storage(tls); + + for (i = th->thread_index; i < lp->count; i += nthreads) { const char *p = lp->array[i]; - if (namelist_find(&exclude_list, p) >= 0) { - test_excluded++; - } else if (test_index < start_index) { - test_skipped++; - } else if (stop_index >= 0 && test_index > stop_index) { - test_skipped++; + int ti; + if (INCLUDE != include_exclude_or_skip(i)) + continue; + + if (slow_test_threshold != 0) { + ti = get_clock_ms(); } else { - int ti; - if (slow_test_threshold != 0) { - ti = get_clock_ms(); - } else { - ti = 0; - } - run_test(p, test_index); - if (slow_test_threshold != 0) { - ti = get_clock_ms() - ti; - if (ti >= slow_test_threshold) - fprintf(stderr, "\n%s (%d ms)\n", p, ti); - } - show_progress(FALSE); + ti = 0; + } + run_test(tls, p, i); + if (slow_test_threshold != 0) { + ti = get_clock_ms() - ti; + if (ti >= slow_test_threshold) + fprintf(stderr, "\n%s (%d ms)\n", p, ti); } - test_index++; } - show_progress(TRUE); + return NULL; } void help(void) @@ -2049,13 +2227,15 @@ void help(void) "-t show timings\n" "-u update error file\n" "-v verbose: output error messages\n" - "-T duration display tests taking more than 'duration' ms\n" + "-D duration display tests taking more than 'duration' ms\n" + "-T threads number of parallel threads\n" "-c file read configuration from 'file'\n" "-d dir run all test files in directory tree 'dir'\n" "-e file load the known errors from 'file'\n" "-f file execute single test from 'file'\n" "-r file set the report file name (default=none)\n" - "-x file exclude tests listed in 'file'\n"); + "-x file exclude tests listed in 'file'\n" + "--no-can-block set [[CanBlock]] to false (Atomics.wait will throw)\n"); exit(1); } @@ -2069,15 +2249,21 @@ char *get_opt_arg(const char *option, char *arg) int main(int argc, char **argv) { - int optind, start_index, stop_index; + ThreadLocalStorage tls_s, *tls = &tls_s; + int optind; BOOL is_dir_list; BOOL only_check_errors = FALSE; const char *filename; const char *ignore = ""; BOOL is_test262_harness = FALSE; BOOL is_module = FALSE; + BOOL can_block = TRUE; BOOL count_skipped_features = FALSE; clock_t clocks; + + init_thread_local_storage(tls); + pthread_mutex_init(&stats_mutex, NULL); + pthread_mutex_init(&error_list_mutex, NULL); #if !defined(_WIN32) compact = !isatty(STDERR_FILENO); @@ -2091,7 +2277,7 @@ int main(int argc, char **argv) if (*arg != '-') break; optind++; - if (strstr("-c -d -e -x -f -r -E -T", arg)) + if (strstr("-c -d -e -x -f -r -E -D -T", arg)) optind++; if (strstr("-d -f", arg)) ignore = "testdir"; // run only the tests from -d or -f @@ -2138,12 +2324,16 @@ int main(int argc, char **argv) report_filename = get_opt_arg(arg, argv[optind++]); } else if (str_equal(arg, "-E")) { only_check_errors = TRUE; - } else if (str_equal(arg, "-T")) { + } else if (str_equal(arg, "-D")) { slow_test_threshold = atoi(get_opt_arg(arg, argv[optind++])); + } else if (str_equal(arg, "-T")) { + nthreads = atoi(get_opt_arg(arg, argv[optind++])); } else if (str_equal(arg, "-N")) { is_test262_harness = TRUE; } else if (str_equal(arg, "--module")) { is_module = TRUE; + } else if (str_equal(arg, "--no-can-block")) { + can_block = FALSE; } else if (str_equal(arg, "--count_skipped_features")) { count_skipped_features = TRUE; } else { @@ -2156,10 +2346,18 @@ int main(int argc, char **argv) help(); if (is_test262_harness) { - return run_test262_harness_test(argv[optind], is_module); + return run_test262_harness_test(tls, argv[optind], is_module, can_block); + } + + if (nthreads == 0) { + nthreads = cpu_count(); + if (nthreads >= 8) { + // minus one to not (over)commit the system completely + nthreads--; + } } + nthreads = max_int(nthreads, 1); - error_out = stdout; if (error_filename) { error_file = load_file(error_filename, NULL); if (only_check_errors && error_file) { @@ -2169,10 +2367,6 @@ int main(int argc, char **argv) if (update_errors) { free(error_file); error_file = NULL; - error_out = fopen(error_filename, "w"); - if (!error_out) { - perror_exit(1, error_filename); - } } } @@ -2189,10 +2383,14 @@ int main(int argc, char **argv) } if (is_dir_list) { + RunTestDirThread *threads; + int i; + if (optind < argc && !isdigit((unsigned char)argv[optind][0])) { filename = argv[optind++]; namelist_load(&test_list, filename); } + start_index = 0; stop_index = -1; if (optind < argc) { @@ -2201,7 +2399,8 @@ int main(int argc, char **argv) stop_index = atoi(argv[optind++]); } } - if (!report_filename || str_equal(report_filename, "none")) { + /* XXX: could reorder the report and the errors when nthreads > 1 */ + if (!report_filename || str_equal(report_filename, "none") || nthreads > 1) { outfile = NULL; } else if (str_equal(report_filename, "-")) { outfile = stdout; @@ -2211,7 +2410,50 @@ int main(int argc, char **argv) perror_exit(1, report_filename); } } - run_test_dir_list(&test_list, start_index, stop_index); + + // exclude_dir_list has already been sorted by update_exclude_dirs() + namelist_sort(&test_list, TRUE); + namelist_sort(&exclude_list, TRUE); + + for (i = 0; i < test_list.count; i++) { + switch (include_exclude_or_skip(i)) { + case EXCLUDE: + test_excluded++; + break; + case SKIP: + test_skipped++; + break; + } + } + + pthread_cond_init(&progress_cond, NULL); + pthread_mutex_init(&progress_mutex, NULL); + pthread_create(&progress_thread, NULL, show_progress, NULL); + + threads = malloc(sizeof(threads[0]) * nthreads); + for (i = 0; i < nthreads; i++) { + RunTestDirThread *th = &threads[i]; + pthread_attr_t attr; + + th->thread_index = i; + + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 2 << 20); // 2 MB, glibc default + pthread_create(&th->tid, &attr, run_test_dir_list, th); + pthread_attr_destroy(&attr); + } + for (i = 0; i < nthreads; i++) + pthread_join(threads[i].tid, NULL); + free(threads); + + pthread_mutex_lock(&progress_mutex); + progress_exit_request = TRUE; + pthread_cond_signal(&progress_cond); + pthread_mutex_unlock(&progress_mutex); + pthread_join(progress_thread, NULL); + + pthread_mutex_destroy(&progress_mutex); + pthread_cond_destroy(&progress_cond); if (outfile && outfile != stdout) { fclose(outfile); @@ -2220,7 +2462,7 @@ int main(int argc, char **argv) } else { outfile = stdout; while (optind < argc) { - run_test(argv[optind++], -1); + run_test(tls, argv[optind++], -1); } } @@ -2279,17 +2521,28 @@ int main(int argc, char **argv) } fprintf(stderr, "\n"); if (show_timings) - fprintf(stderr, "Total time: %.3fs\n", (double)clocks / CLOCKS_PER_SEC); + fprintf(stderr, "Total user time: %.3fs (nthreads=%d)\n", (double)clocks / CLOCKS_PER_SEC, nthreads); } - if (error_out && error_out != stdout) { + if (update_errors) { + FILE *error_out = fopen(error_filename, "w"); + int i; + if (!error_out) { + perror_exit(1, error_filename); + } + /* sort the error list so that its order does not depend on + the thread scheduling */ + namelist_sort(&error_list, FALSE); + for (i = 0; i < error_list.count; i++) { + fputs(error_list.array[i], error_out); + } fclose(error_out); - error_out = NULL; } namelist_free(&test_list); namelist_free(&exclude_list); namelist_free(&exclude_dir_list); + namelist_free(&error_list); free(harness_dir); free(harness_skip_features); free(harness_skip_features_count); diff --git a/vendor/quickjs/test262.conf b/vendor/quickjs/test262.conf index aa76e639..af5cd647 100644 --- a/vendor/quickjs/test262.conf +++ b/vendor/quickjs/test262.conf @@ -71,6 +71,7 @@ async-iteration Atomics Atomics.pause Atomics.waitAsync=skip +await-dictionary=skip BigInt caller canonical-tz=skip @@ -119,7 +120,9 @@ hashbang host-gc-required immutable-arraybuffer=skip import-attributes +import-bytes=skip import-defer=skip +import-text=skip import.meta joint-iteration=skip Int16Array @@ -135,6 +138,7 @@ Intl.DateTimeFormat-fractionalSecondDigits=skip Intl.DisplayNames-v2=skip Intl.DisplayNames=skip Intl.DurationFormat=skip +Intl.Era-monthcode=skip Intl.ListFormat=skip Intl.Locale-info=skip Intl.Locale=skip @@ -146,7 +150,7 @@ IsHTMLDDA iterator-helpers iterator-sequencing json-modules -json-parse-with-source=skip +json-parse-with-source json-superset legacy-regexp=skip let @@ -232,7 +236,7 @@ u180e Uint16Array Uint32Array Uint8Array -uint8array-base64=skip +uint8array-base64 Uint8ClampedArray upsert WeakMap @@ -296,5 +300,15 @@ test262/test/staging/sm/syntax/syntax-parsed-arrow-then-directive.js # returning "bound fn" as initialName for a function is permitted by the spec test262/test/staging/sm/Function/function-toString-builtin.js +# very slow tests which only test DST offset caching (QuickJS does not optimize it) +test262/test/staging/sm/Date/dst-offset-caching-1-of-8.js +test262/test/staging/sm/Date/dst-offset-caching-2-of-8.js +test262/test/staging/sm/Date/dst-offset-caching-3-of-8.js +test262/test/staging/sm/Date/dst-offset-caching-4-of-8.js +test262/test/staging/sm/Date/dst-offset-caching-5-of-8.js +test262/test/staging/sm/Date/dst-offset-caching-6-of-8.js +test262/test/staging/sm/Date/dst-offset-caching-7-of-8.js +test262/test/staging/sm/Date/dst-offset-caching-8-of-8.js + [tests] # list test files or use config.testdir diff --git a/vendor/quickjs/test262_errors.txt b/vendor/quickjs/test262_errors.txt index faeb5895..66e83f20 100644 --- a/vendor/quickjs/test262_errors.txt +++ b/vendor/quickjs/test262_errors.txt @@ -31,14 +31,6 @@ test262/test/staging/sm/Function/function-name-for.js:13: Test262Error: Expected test262/test/staging/sm/Function/implicit-this-in-parameter-expression.js:12: Test262Error: Expected SameValue(«[object Object]», «undefined») to be true test262/test/staging/sm/Function/invalid-parameter-list.js:13: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all test262/test/staging/sm/Function/invalid-parameter-list.js:13: strict mode: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all -test262/test/staging/sm/String/string-upper-lower-mapping.js:16: Test262Error: Expected SameValue(«"꟏"», «"꟎"») to be true -test262/test/staging/sm/String/string-upper-lower-mapping.js:16: strict mode: Test262Error: Expected SameValue(«"꟏"», «"꟎"») to be true -test262/test/staging/sm/TypedArray/constructor-buffer-sequence.js:29: Test262Error: Expected a ExpectedError but got a Error -test262/test/staging/sm/TypedArray/constructor-buffer-sequence.js:29: strict mode: Test262Error: Expected a ExpectedError but got a Error -test262/test/staging/sm/TypedArray/prototype-constructor-identity.js:17: Test262Error: Expected SameValue(«2», «6») to be true -test262/test/staging/sm/TypedArray/prototype-constructor-identity.js:17: strict mode: Test262Error: Expected SameValue(«2», «6») to be true -test262/test/staging/sm/TypedArray/sort_modifications.js:9: Test262Error: Int8Array at index 0 for size 4 Expected SameValue(«0», «1») to be true -test262/test/staging/sm/TypedArray/sort_modifications.js:9: strict mode: Test262Error: Int8Array at index 0 for size 4 Expected SameValue(«0», «1») to be true test262/test/staging/sm/async-functions/async-contains-unicode-escape.js:11: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all test262/test/staging/sm/async-functions/async-contains-unicode-escape.js:11: strict mode: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all test262/test/staging/sm/async-functions/await-in-arrow-parameters.js:10: Test262Error: AsyncFunction:(a = (b = await/r/g) => {}) => {} Expected a SyntaxError to be thrown but no exception was thrown at all diff --git a/vendor/quickjs/tests/microbench.js b/vendor/quickjs/tests/microbench.js index a9fc5f17..37e6a279 100644 --- a/vendor/quickjs/tests/microbench.js +++ b/vendor/quickjs/tests/microbench.js @@ -1577,8 +1577,14 @@ function main(argc, argv, g) } if (typeof scriptArgs === "undefined") { - scriptArgs = []; - if (typeof process.argv === "object") + if (typeof process !== "undefined" && typeof process.argv === "object") { + /* node case */ scriptArgs = process.argv.slice(1); + } else if (typeof arguments !== "undefined") { + /* d8 case */ + scriptArgs = arguments; + } else { + scriptArgs = []; + } } main(scriptArgs.length, scriptArgs, this); diff --git a/vendor/quickjs/tests/test_language.js b/vendor/quickjs/tests/test_language.js index 5c51f0df..f6580447 100644 --- a/vendor/quickjs/tests/test_language.js +++ b/vendor/quickjs/tests/test_language.js @@ -664,6 +664,16 @@ function test_global_var_opt() assert(gvar1, 5); } +function test_number_literals() +{ + assert(0.1.a, undefined); + assert(0x1.a, undefined); + assert(0b1.a, undefined); + assert(01.a, undefined); + assert(0o1.a, undefined); + assert_throws(SyntaxError, () => eval('0.a')); +} + test_op1(); test_cvt(); test_eq(); @@ -690,3 +700,4 @@ test_optional_chaining(); test_parse_arrow_function(); test_unicode_ident(); test_global_var_opt(); +test_number_literals(); diff --git a/vendor/quickjs/tests/test_rw_handler.js b/vendor/quickjs/tests/test_rw_handler.js new file mode 100644 index 00000000..c4cd19af --- /dev/null +++ b/vendor/quickjs/tests/test_rw_handler.js @@ -0,0 +1,57 @@ +import * as std from "std"; +import * as os from "os"; + +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (Object.is(actual, expected)) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function handle_read(fd_r, fd_w, i) +{ + var buf = new Uint32Array(1); + var val, len; + len = os.read(fd_r, buf.buffer, 0, 4); + os.setReadHandler(fd_r, null); + val = buf[0]; +// print("read fd=", fd_r, "val=", val, "len=", len); + assert(val, i); +} + +function handle_write(fd_r, fd_w, i) +{ + var buf = new Uint32Array(1); + buf[0] = i; + os.write(fd_w, buf.buffer, 0, 4); + os.setWriteHandler(fd_w, null); +} + +function test_rw_handlers(n) +{ + var tab, fd_r, fd_w, i; + tab = []; + for(i = 0; i < n; i++) { + tab[i] = os.pipe(); + fd_r = tab[i][0]; + fd_w = tab[i][1]; + os.setReadHandler(fd_r, handle_read.bind(null, fd_r, fd_w, i)); + } + for(i = n - 1; i >= 0; i--) { + fd_r = tab[i][0]; + fd_w = tab[i][1]; + os.setWriteHandler(fd_w, handle_write.bind(null, fd_r, fd_w, i)); + } +} + +test_rw_handlers(100);