cred: isolate scripts in WASI and move RSA signing to AWS-LC

This commit is contained in:
2026-11-06 22:22:49 +00:00
parent 5b3e1393f5
commit 86c5d8a4e4
217 changed files with 155392 additions and 391 deletions
Generated
+2
View File
@@ -4994,7 +4994,9 @@ dependencies = [
"aes-gcm",
"anyhow",
"async-trait",
"aws-lc-rs",
"base64 0.22.1",
"bytes",
"chacha20poly1305",
"clap",
"deno_ast",
+2
View File
@@ -51,6 +51,8 @@ ssh-key = { version = "0.6.7", features = ["encryption", "ed25519", "ecdsa", "rs
uuid = { version = "1.29.0", features = ["v4"] }
webauthn-rs = "0.5"
openssl = { version = "0.10.82", features = ["vendored"] }
aws-lc-rs = "1.19.0"
bytes = "1.12.1"
[dev-dependencies]
tempfile = "3"
+584
View File
@@ -0,0 +1,584 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "async-lock"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0876700b0d007ad25d5508865a4259c3af9af6d296bf663a0d586d0bc3369f6f"
dependencies = [
"event-listener",
"event-listener-strategy",
"pin-project-lite",
]
[[package]]
name = "base64-simd"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195"
dependencies = [
"outref",
"vsimd",
]
[[package]]
name = "cc"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ff2a8126fff0f8c839080d619675a585e61720bb12d2baceae45769fd66f155"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "convert_case"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "equivalent"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
[[package]]
name = "event-listener"
version = "5.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
dependencies = [
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
dependencies = [
"event-listener",
"pin-project-lite",
]
[[package]]
name = "fastrand"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
[[package]]
name = "find-msvc-tools"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "hex-simd"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f7685beb53fc20efc2605f32f5d51e9ba18b8ef237961d1760169d2290d3bee"
dependencies = [
"outref",
"vsimd",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
"hashbrown 0.17.1",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "libc"
version = "0.2.191"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
[[package]]
name = "llrt_buffer"
version = "0.8.1-beta"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73954ae0a8202982b83797ba4a982cdbb6f7bd9d99aafcc0aa45e98026fd7ab8"
dependencies = [
"itoa",
"llrt_encoding",
"llrt_utils_watts",
"rquickjs",
"ryu",
]
[[package]]
name = "llrt_build"
version = "0.8.1-beta"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e770e9f9d0031d5788ab2f73254cdc9c516042b55db8bddf64d2f409b7015f4a"
dependencies = [
"rustc_version",
]
[[package]]
name = "llrt_encoding"
version = "0.8.1-beta"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb7748e8a3cfe2f8091fd6428c604c9bbf636a6972e5480e35fa6c245f92ea5"
dependencies = [
"base64-simd",
"hex-simd",
"llrt_build",
"memchr",
"phf",
]
[[package]]
name = "llrt_utils_watts"
version = "0.8.1-beta"
dependencies = [
"libc",
"llrt_build",
"rquickjs",
"tokio",
"tracing",
"windows-sys",
]
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "outref"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
[[package]]
name = "phf"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
dependencies = [
"phf_macros",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
dependencies = [
"fastrand",
"phf_shared",
]
[[package]]
name = "phf_macros"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "phf_shared"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "proc-macro-crate"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "relative-path"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38540a682c6e9bca04a8bca9e5ec5a6ba168dfbb04c9998fe517a9befb92eb68"
dependencies = [
"serde_core",
]
[[package]]
name = "rquickjs"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50dc6d6c587c339edb4769cf705867497a2baf0eca8b4645fa6ecd22f02c77a"
dependencies = [
"rquickjs-core",
"rquickjs-macro",
]
[[package]]
name = "rquickjs-core"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8bf7840285c321c3ab20e752a9afb95548c75cd7f4632a0627cea3507e310c1"
dependencies = [
"async-lock",
"hashbrown 0.16.1",
"relative-path",
"rquickjs-sys",
]
[[package]]
name = "rquickjs-macro"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7106215ff41a5677b104906a13e1a440b880f4b6362b5dc4f3978c267fad2b80"
dependencies = [
"convert_case",
"fnv",
"ident_case",
"indexmap",
"proc-macro-crate",
"proc-macro2",
"quote",
"rquickjs-core",
"syn 2.0.119",
]
[[package]]
name = "rquickjs-sys"
version = "0.11.0"
dependencies = [
"cc",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "sausage-cred-script"
version = "0.1.0"
dependencies = [
"llrt_buffer",
"rquickjs",
"serde",
"serde_json",
"thiserror",
"zeroize",
]
[[package]]
name = "semver"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
]
[[package]]
name = "serde_json"
version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "siphasher"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256"
[[package]]
name = "syn"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
]
[[package]]
name = "tokio"
version = "1.54.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3"
dependencies = [
"pin-project-lite",
]
[[package]]
name = "toml_datetime"
version = "1.1.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "173f25c883cd2176a2d8212155638c0a60d484a9d84546d270866f40f56b6e49"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.25.19+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd43688c4858d7264ae1cf9a9f874a67c3feb74a883b1cd7bebd6a4ea8307218"
dependencies = [
"indexmap",
"toml_datetime",
"toml_parser",
"winnow",
]
[[package]]
name = "toml_parser"
version = "1.1.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c"
dependencies = [
"winnow",
]
[[package]]
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
[[package]]
name = "unicode-ident"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
[[package]]
name = "unicode-segmentation"
version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
[[package]]
name = "vsimd"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "winnow"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3b93199df0ab874cdf0c6a94f0a0dc3423fe41f1cafb62b8bd7b1deb277aef"
dependencies = [
"memchr",
]
[[package]]
name = "zeroize"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
dependencies = [
"serde",
]
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "sausage-cred-script"
version = "0.1.0"
edition = "2024"
publish = false
[workspace]
[dependencies]
rquickjs = { version = "=0.11.0", default-features = false, features = ["std"] }
llrt_buffer = "=0.8.1-beta"
thiserror = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
zeroize = { version = "1", features = ["serde"] }
[patch.crates-io]
rquickjs-sys = { path = "vendor/rquickjs-sys" }
llrt_utils_watts = { path = "vendor/llrt_utils_watts" }
+46
View File
@@ -0,0 +1,46 @@
# Credential script guest (D58)
This host-internal WASI component runs credential validators and transforms in QuickJS.
The host embeds `script.wasm`. It never exposes this component as a world program.
`src/lib.rs` ports v2's bounded `jsrun` evaluator.
`src/tests.rs` preserves its tests unchanged.
The vendored compatibility patches and licenses remain in `vendor/`.
`vendor/PATCHES.md` explains the compatibility fixes and memory-error classification.
Rebuild the checked-in component from the repository root:
```sh
CARGO_TARGET_DIR="$PWD/target/cred-guest" nix develop -c nu host/cred-script/build.nu
```
The script sets `CARGO_BUILD_JOBS=2` and uses the locked, separate guest workspace.
The development shell supplies the WASI C compiler and Rust target.
Run the unchanged evaluator tests natively:
```sh
CARGO_BUILD_JOBS=2 CARGO_TARGET_DIR="$PWD/target/cred-guest" nix develop -c cargo test --manifest-path host/cred-script/Cargo.toml --release --all-targets --locked
```
These tests use optimized C and Rust code, like the shipped guest.
Native debug builds exceed the 16 KiB interpreter stack.
The JSON protocol accepts one source module, input, export name, and two limits.
The reply contains a JSON result or a static error category.
Script exception text never leaves the guest.
The guest owns its input in zeroizing buffers before the QuickJS boundary.
The host compiles the component once and caches its compiled code.
Construct `cred::quickjs::QuickJs::default()` at startup and reuse it through `cred::script::Script`.
The injection API does not change.
Each call uses a fresh Store with linear memory limited to the clamped heap plus 8 MiB.
The margin covers the Rust stack, WASI adapter, and protocol buffers.
Only two evaluations run concurrently. Queue time counts against the deadline.
Epoch callbacks check each call's deadline independently.
The guest receives no directories, environment variables, or network access.
Its wall clock returns zero. Its random sources use a fixed seed.
Its monotonic clock serves the evaluator's internal budget only.
JavaScript receives no clocks, random function, module loader, or host interfaces.
`Error.prepareStackTrace` is disabled and immutable. Errors retain plain stack strings.
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env nu
# Rebuild the checked-in credential script component with the pinned guest workspace.
let root = ($env.FILE_PWD | path join ../.. | path expand)
let target = ($env.CARGO_TARGET_DIR? | default ($root | path join target cred-script) | path expand)
with-env {CARGO_BUILD_JOBS: '2', CARGO_TARGET_DIR: $target} {
^cargo build --manifest-path ($env.FILE_PWD | path join Cargo.toml) --target wasm32-wasip2 --release --locked
if $env.LAST_EXIT_CODE != 0 { error make {msg: 'The credential script guest build failed.'} }
}
cp ($target | path join wasm32-wasip2 release sausage-cred-script.wasm) ($env.FILE_PWD | path join script.wasm)
Binary file not shown.
+243
View File
@@ -0,0 +1,243 @@
//! Bounded, stateless ES modules with JSON text boundaries. No host capabilities.
use rquickjs::{Context, Ctx, Function, Module, Promise, Runtime, Value, context::intrinsic};
use std::{
cell::Cell,
rc::Rc,
time::{Duration, Instant},
};
/// Requested limits. Values are clamped by the evaluator, including zero.
#[derive(Clone, Copy, Debug, Default)]
pub struct Limits {
pub max_time_ms: Option<u64>,
pub max_memory_bytes: Option<usize>,
pub max_stack_bytes: Option<usize>,
}
impl Limits {
/// Effective time (1..1000ms), heap (256KiB..32MiB), stack (4..16KiB).
pub fn clamped(self) -> Self {
Self {
max_time_ms: Some(self.max_time_ms.unwrap_or(100).clamp(1, 1000)),
max_memory_bytes: Some(
self.max_memory_bytes
.unwrap_or(8 * 1024 * 1024)
.clamp(256 * 1024, 32 * 1024 * 1024),
),
max_stack_bytes: Some(
self.max_stack_bytes
.unwrap_or(16 * 1024)
.clamp(4 * 1024, 16 * 1024),
),
}
}
}
/// Stable error categories, independent of QuickJS's exception wording.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorKind {
Limit,
Syntax,
Throw,
NonJson,
Engine,
}
/// An evaluation failure. Error text can include script-provided messages.
#[derive(Debug, thiserror::Error)]
#[error("{message}")]
pub struct Error {
pub kind: ErrorKind,
pub message: String,
}
fn failure(kind: ErrorKind, message: impl Into<String>) -> Error {
Error {
kind,
message: message.into(),
}
}
struct Budget {
start: Instant,
time: Duration,
ticks: Cell<u32>,
limited: Cell<bool>,
}
impl Budget {
// A finite job/callback allowance is also needed for a stream of tiny jobs
// and for hosts with a stalled monotonic clock. Not an instruction count.
fn step(&self) -> bool {
self.ticks.set(self.ticks.get().saturating_add(1));
let stop = self.ticks.get() >= 100_000 || self.start.elapsed() >= self.time;
self.limited.set(self.limited.get() || stop);
self.limited.get()
}
fn check(&self) -> Result<(), Error> {
if self.step() {
Err(failure(
ErrorKind::Limit,
"JavaScript execution limit exceeded",
))
} else {
Ok(())
}
}
}
unsafe extern "C" {
fn sausage_js_had_oom(ctx: *mut std::ffi::c_void) -> std::ffi::c_int;
}
fn memory_limited(ctx: &Ctx<'_>) -> bool {
// The live Ctx owns this context and holds the runtime lock.
unsafe { sausage_js_had_oom(ctx.as_raw().as_ptr().cast()) != 0 }
}
fn js_error(ctx: &Ctx<'_>, err: rquickjs::Error, phase: ErrorKind, budget: &Budget) -> Error {
if memory_limited(ctx) {
return failure(ErrorKind::Limit, "JavaScript memory limit exceeded");
}
if budget.limited.get() {
return failure(ErrorKind::Limit, "JavaScript execution limit exceeded");
}
let mut kind = phase;
let mut message = err.to_string();
if matches!(err, rquickjs::Error::Exception) {
let thrown = ctx.catch();
if let Some(object) = thrown.as_object() {
if let Ok(text) = object.get::<_, String>("message") {
message = text;
}
// Only genuine engine Error instances determine resource/syntax kinds;
// plain thrown objects remain throws even if they mimic the message.
if thrown.is_error() {
if message.contains("out of memory") || message.contains("stack size exceeded") {
kind = ErrorKind::Limit;
} else if object
.get::<_, String>("name")
.is_ok_and(|n| n == "SyntaxError")
{
kind = ErrorKind::Syntax;
}
}
} else if let Some(text) = thrown.as_string()
&& let Ok(text) = text.to_string()
{
message = text;
}
} else if matches!(err, rquickjs::Error::Allocation) {
kind = ErrorKind::Limit;
}
if memory_limited(ctx) {
return failure(
ErrorKind::Limit,
"JavaScript memory limit exceeded while reading error",
);
}
if budget.check().is_err() {
return failure(
ErrorKind::Limit,
"JavaScript execution limit exceeded while reading error",
);
}
failure(kind, message)
}
fn settle<'js>(
ctx: &Ctx<'js>,
promise: Promise<'js>,
budget: &Budget,
) -> Result<Value<'js>, Error> {
loop {
budget.check()?;
if let Some(result) = promise.result::<Value>() {
return result.map_err(|e| js_error(ctx, e, ErrorKind::Throw, budget));
}
// This is synchronous queue draining, not polling an external resource.
if !ctx.execute_pending_job() {
return Err(failure(
ErrorKind::Engine,
"Promise is pending with no runnable jobs (no ambient async capabilities)",
));
}
}
}
/// Compile an ES module, call its selected export (default `default`) with parsed
/// JSON, settle its returned Promise, and JSON.stringify the result. Fresh runtime
/// each call. Only LLRT's atob/btoa globals are retained, with no module loader
/// or ambient host capabilities. JSON.stringify
/// semantics apply (unsupported object properties omitted, nonfinite => null).
/// Limits cover compilation, execution, job draining and serialization; QuickJS
/// interrupts are cooperative, not preemptive deadlines for every C builtin.
pub fn evaluate(
script: &str,
input: &str,
export: Option<&str>,
limits: Limits,
) -> Result<String, Error> {
let limits = limits.clamped();
let budget = Rc::new(Budget {
start: Instant::now(),
time: Duration::from_millis(limits.max_time_ms.unwrap_or(100)),
ticks: Cell::new(0),
limited: Cell::new(false),
});
let rt = Runtime::new().map_err(|e| failure(ErrorKind::Engine, e.to_string()))?;
rt.set_memory_limit(limits.max_memory_bytes.unwrap_or(8 * 1024 * 1024));
rt.set_max_stack_size(limits.max_stack_bytes.unwrap_or(16 * 1024));
let cb = budget.clone();
rt.set_interrupt_handler(Some(Box::new(move || cb.step())));
// LLRT's BasePrimordials needs Date during initialization, as well as the
// pure collection/regexp/typed-array intrinsics. Date is removed before
// user code runs; no network, filesystem, process or timer modules load.
let context = Context::custom::<(
intrinsic::Eval,
intrinsic::Json,
intrinsic::Promise,
intrinsic::Date,
intrinsic::MapSet,
intrinsic::RegExp,
intrinsic::Proxy,
intrinsic::TypedArrays,
)>(&rt)
.map_err(|e| failure(ErrorKind::Limit, format!("context creation failed: {e}")))?;
context.with(|ctx| {
llrt_buffer::init(&ctx)
.map_err(|e| js_error(&ctx, e, ErrorKind::Engine, &budget))?;
// Retain only atob/btoa from the published buffer initializer. Buffer's
// external allocations bypass the heap limit; Blob/File are unnecessary
// and File exposes the clock. Buffer has both a global lexical class
// binding and a globalThis property: clear both. Date is init-only.
// Disable QuickJS's structured stack hook before it can expose call sites.
ctx.eval::<(), _>("Object.defineProperty(Error, 'prepareStackTrace', { value: undefined, writable: false, configurable: false }); Buffer = undefined; delete globalThis.Buffer; delete Math.random; delete globalThis.Date; delete globalThis.Blob; delete globalThis.File; for (const name of ['WebAssembly', 'SharedArrayBuffer', 'Atomics', 'Intl', '__infra', '__bootstrap', 'queueMicrotask']) delete globalThis[name];")
.map_err(|e| js_error(&ctx, e, ErrorKind::Engine, &budget))?;
let input = ctx
.json_parse(input)
.map_err(|e| js_error(&ctx, e, ErrorKind::Syntax, &budget))?;
let module = Module::declare(ctx.clone(), "jsrun:entry", script)
.map_err(|e| js_error(&ctx, e, ErrorKind::Syntax, &budget))?;
let (module, ready) = module
.eval()
.map_err(|e| js_error(&ctx, e, ErrorKind::Throw, &budget))?;
settle(&ctx, ready, &budget)?;
let function: Function = module
.get(export.unwrap_or("default"))
.map_err(|e| js_error(&ctx, e, ErrorKind::Engine, &budget))?;
let mut value: Value = function
.call((input,))
.map_err(|e| js_error(&ctx, e, ErrorKind::Throw, &budget))?;
if let Some(promise) = value.as_promise() {
value = settle(&ctx, promise.clone(), &budget)?;
}
let text = ctx
.json_stringify(value)
.map_err(|e| js_error(&ctx, e, ErrorKind::NonJson, &budget))?
.ok_or_else(|| {
failure(
ErrorKind::NonJson,
"result is undefined, a function, or otherwise not JSON serializable",
)
})?
.to_string()
.map_err(|e| js_error(&ctx, e, ErrorKind::NonJson, &budget))?;
budget.check()?;
Ok(text)
})
}
#[cfg(test)]
mod tests;
+58
View File
@@ -0,0 +1,58 @@
//! One JSON call over stdin/stdout. Script exception text never leaves this guest.
use sausage_cred_script::{ErrorKind, Limits, evaluate};
use std::io::{Read, Write};
use zeroize::Zeroizing;
#[derive(serde::Deserialize)]
struct Request {
source: String,
input: Zeroizing<String>,
export: Option<String>,
max_time_ms: u64,
max_memory_bytes: usize,
}
fn run() -> Result<(), Box<dyn std::error::Error>> {
let mut wire = Zeroizing::new(String::new());
std::io::stdin().read_to_string(&mut wire)?;
let request: Request = serde_json::from_str(&wire)?;
let result = evaluate(
&request.source,
&request.input,
request.export.as_deref(),
Limits {
max_time_ms: Some(request.max_time_ms),
max_memory_bytes: Some(request.max_memory_bytes),
..Limits::default()
},
);
let response = match result {
Ok(result) => serde_json::json!({"result": result}),
Err(error) => serde_json::json!({"kind": match error.kind {
ErrorKind::Limit => "Limit",
ErrorKind::Syntax => "Syntax",
ErrorKind::Throw => "Throw",
ErrorKind::NonJson => "NonJson",
ErrorKind::Engine => "Engine",
}}),
};
serde_json::to_writer(std::io::stdout().lock(), &response)?;
std::io::stdout().flush()?;
Ok(())
}
fn main() {
if run().is_err() {
std::process::exit(1);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_input_has_zeroizing_ownership() {
let request: Request = serde_json::from_str(r#"{"source":"export default x=>x","input":"null","export":null,"max_time_ms":100,"max_memory_bytes":8388608}"#).unwrap();
let _: &Zeroizing<String> = &request.input;
}
}
+196
View File
@@ -0,0 +1,196 @@
use super::*;
fn run(s: &str) -> Result<String, Error> {
evaluate(s, "null", None, Limits::default())
}
#[test]
fn module_default_and_named() {
assert_eq!(
evaluate(
"export default x => ({value:x}); export const other=x=>x+1",
"42",
None,
Limits::default()
)
.unwrap(),
"{\"value\":42}"
);
assert_eq!(
evaluate(
"export const other=x=>x+1",
"42",
Some("other"),
Limits::default()
)
.unwrap(),
"43"
);
assert_eq!(run("export default x=>null").unwrap(), "null");
}
#[test]
fn promise_settlement() {
assert_eq!(
run("export default async x => { await Promise.resolve(); return 42; }").unwrap(),
"42"
);
assert_eq!(
run("await Promise.resolve(); export default x=>true").unwrap(),
"true"
);
for s in [
"export default x=>Promise.reject(new Error('rejected'))",
"export default async x=>{ throw 'rejected'; }",
"await Promise.reject('rejected'); export default x=>true",
] {
let e = run(s).unwrap_err();
assert_eq!(e.kind, ErrorKind::Throw);
assert_eq!(e.message, "rejected");
}
assert_eq!(
run("export default x=>new Promise(()=>{})")
.unwrap_err()
.kind,
ErrorKind::Engine
);
}
#[test]
fn typed_errors() {
assert_eq!(run("export default (").unwrap_err().kind, ErrorKind::Syntax);
assert_eq!(
run("export default x=>{throw new Error('oops')}")
.unwrap_err()
.kind,
ErrorKind::Throw
);
for s in [
"export default x=>undefined",
"export default x=>(()=>1)",
"export default x=>{let a={};a.a=a;return a}",
"export default x=>Symbol()",
] {
assert_eq!(run(s).unwrap_err().kind, ErrorKind::NonJson);
}
assert_eq!(
evaluate("export default x=>x", "not json", None, Limits::default())
.unwrap_err()
.kind,
ErrorKind::Syntax
);
assert_eq!(run("export const x=1").unwrap_err().kind, ErrorKind::Engine);
assert_eq!(run("export default 1").unwrap_err().kind, ErrorKind::Engine);
}
#[test]
fn recursion_is_bounded() {
for stack in [4096, 16384, usize::MAX] {
let e = evaluate(
"export default x=>{function f(){return f()+1}return f()}",
"null",
None,
Limits {
max_stack_bytes: Some(stack),
..Limits::default()
},
)
.unwrap_err();
eprintln!("STACK {stack}: {e:?}");
assert_eq!(e.kind, ErrorKind::Limit);
assert!(e.message.contains("stack"));
}
}
#[test]
fn runaway_loop_and_jobs() {
for s in [
"export default x=>{while(true){}}",
"while(true){};export default x=>x",
"export default x=>({toJSON(){while(true){}}})",
"export default x=>{throw {get message(){while(true){}}}}",
"export default x=>new Promise(()=>{function f(){Promise.resolve().then(f)};f()})",
] {
let start = Instant::now();
assert_eq!(run(s).unwrap_err().kind, ErrorKind::Limit);
assert!(start.elapsed() < Duration::from_secs(2));
}
}
#[test]
fn runaway_allocation() {
let e = evaluate(
"export default x=>{let a=[];while(1)a.push('abcdefghijklmnopqrstuvwxyz'+a.length)}",
"null",
None,
Limits {
max_time_ms: Some(1000),
max_memory_bytes: Some(1024 * 1024),
..Limits::default()
},
)
.unwrap_err();
eprintln!("HEAP {e:?}");
assert_eq!(e.kind, ErrorKind::Limit);
assert!(e.message.contains("memory"));
}
#[test]
fn forbidden_globals_and_imports() {
for name in [
"Date",
"performance",
"Math.random",
"fetch",
"XMLHttpRequest",
"require",
"process",
"std",
"os",
"fs",
"crypto",
"console",
"setTimeout",
] {
assert_eq!(
run(&format!("export default x=>typeof {name}")).unwrap(),
"\"undefined\"",
"{name}"
);
}
assert!(run("import x from 'fs';export default x=>x").is_err());
assert_eq!(
run("export default x=>import('fs')").unwrap_err().kind,
ErrorKind::Throw
);
}
#[test]
fn isolation_and_limits() {
run("export default x=>{globalThis.leak=42;return true}").unwrap();
assert_eq!(
run("export default x=>typeof leak").unwrap(),
"\"undefined\""
);
assert_eq!(
Limits {
max_time_ms: Some(u64::MAX),
max_memory_bytes: Some(usize::MAX),
max_stack_bytes: Some(usize::MAX)
}
.clamped()
.max_time_ms,
Some(1000)
);
assert_eq!(
Limits {
max_time_ms: Some(0),
..Limits::default()
}
.clamped()
.max_time_ms,
Some(1)
);
}
#[test]
fn timings() {
let start = Instant::now();
assert_eq!(run("export default x=>true").unwrap(), "true");
eprintln!("TIMING first eval {}us", start.elapsed().as_micros());
let start = Instant::now();
for _ in 0..100 {
assert_eq!(run("export default x=>true").unwrap(), "true");
}
eprintln!("TIMING 100 evals {}us", start.elapsed().as_micros());
}
+50
View File
@@ -0,0 +1,50 @@
# Local compatibility patches
## LLRT utilities (SAU-330)
`llrt_buffer = 0.8.1-beta` and `llrt_encoding` remain published crate dependencies,
matching rquickjs 0.11 without changing the engine/vendor patches below.
The published `llrt_utils_watts = 0.8.1-beta` does not compile for wasm32-wasip2:
`signals::kill` unconditionally references `kill_process_raw` and `is_not_found`,
but those are only defined on Unix/Windows. The failed unpatched build is archived
at `/logs/cargo/2026-10-28/01a184d5-151b-7454-8333-b5d1d468b6d6.stderr.log`.
Fallback: `llrt_utils_watts/` is the unchanged published crate archive from
https://static.crates.io/crates/llrt_utils_watts/llrt_utils_watts-0.8.1-beta.crate
(SHA256 `5ace6f63a6b60a3bf738bf19f393c834c83a06d9e52c31d1c30a526584519569`),
except `src/lib.rs` adds `#[cfg(any(unix, windows))]` to `pub mod signals`.
The root Cargo patch points here. No utility module is registered as a JS module.
The upstream Apache-2.0 license is included (absent in the published archive).
This build-only patch does not alter Buffer. Before user module initialization,
the evaluator clears Buffer's global lexical binding and deletes its global-object
property, and removes Blob, File and Date, retaining only atob/btoa from the module.
SAU-330's mediator decision excludes the Node Buffer API rather than patching
its external-allocation heap bypass or empty-fill panic. Tests cover absent
identifiers, globalThis properties and global own-property names, plus base64
errors and clean near-heap-limit failure. See
`/docs/symphony/artifacts/SAU-330/implementation.md` for evidence.
## QuickJS (SAU-103)
`rquickjs-sys` was downloaded unchanged from https://crates.io/api/v1/crates/rquickjs-sys/0.11.0/download, then changed in exactly two files:
- `quickjs/quickjs.c`, `update_stack_limit`: remove the `__wasi__` special case that sets `stack_limit = 0` regardless of `JS_SetMaxStackSize`. Use the normal stack-top-minus-size implementation on WASI too. Without this patch the recursion test traps the WASM instance even with 16KiB / 256KiB limits. With the patch it returns `Maximum call stack size exceeded`.
- `build.rs`: skip upstream WASI SDK download/CC override when `CC_wasm32_wasip2` is supplied, letting `cc` honor cargo.build's target-specific CC/AR. Also emit the selected compiler/args as a diagnostic.
No engine features or host APIs were added. Keep this patch when reusing the evaluator; setting the Rust stack-limit API alone is **not sufficient** with unmodified rquickjs-sys 0.11.0. Upstream 0.14.0's source was inspected (not compiled) and still contains the WASI stack-limit bypass.
The bundled sources retain their upstream notices/licenses. This is a spike-local vendoring choice, not an SDK or live toolchain change.
## Sausage memory-error classification (D58)
`quickjs.c` records a sticky flag when `JS_ThrowOutOfMemory` runs.
`sausage_js_had_oom` exposes that flag to the guest's Rust evaluator.
`quickjs.h` declares this guest-internal function.
`build.rs` watches the QuickJS source directory, so Cargo rebuilds the library after a patch.
It adds no JavaScript API or host import.
A full heap can prevent QuickJS from creating its error object.
The unchanged `runaway_allocation` test reproduced that case and received `Throw` instead of `Limit`.
The flag preserves the memory-error category without another allocation.
Each evaluation creates a fresh runtime, so it also resets the flag.
@@ -0,0 +1,7 @@
{
"git": {
"sha1": "2946a77260e8cdd314f92a9789635cd4f7b825bf",
"dirty": true
},
"path_in_vcs": "libs/llrt_utils"
}
+879
View File
@@ -0,0 +1,879 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "anyhow"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
[[package]]
name = "async-lock"
version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
dependencies = [
"event-listener",
"event-listener-strategy",
"pin-project-lite",
]
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cc"
version = "1.2.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "convert_case"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "event-listener"
version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
dependencies = [
"concurrent-queue",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
dependencies = [
"event-listener",
"pin-project-lite",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "getrandom"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
"wasip3",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash 0.1.5",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash 0.2.0",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
"serde",
"serde_core",
]
[[package]]
name = "itoa"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "llrt_build"
version = "0.8.1-beta"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e770e9f9d0031d5788ab2f73254cdc9c516042b55db8bddf64d2f409b7015f4a"
dependencies = [
"rustc_version",
]
[[package]]
name = "llrt_test"
version = "0.8.1-beta"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44ff8cf3b62144ca17b132f7fedf983d935573684a140f5e7a5f81a06b215665"
dependencies = [
"rquickjs",
"tokio",
"uuid",
]
[[package]]
name = "llrt_utils_watts"
version = "0.8.1-beta"
dependencies = [
"libc",
"llrt_build",
"llrt_test",
"rquickjs",
"tokio",
"tracing",
"windows-sys 0.61.2",
]
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "mio"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
"libc",
"wasi",
"windows-sys 0.61.2",
]
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro-crate"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]]
name = "relative-path"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bca40a312222d8ba74837cb474edef44b37f561da5f773981007a10bbaa992b0"
dependencies = [
"serde",
]
[[package]]
name = "rquickjs"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50dc6d6c587c339edb4769cf705867497a2baf0eca8b4645fa6ecd22f02c77a"
dependencies = [
"rquickjs-core",
"rquickjs-macro",
]
[[package]]
name = "rquickjs-core"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8bf7840285c321c3ab20e752a9afb95548c75cd7f4632a0627cea3507e310c1"
dependencies = [
"async-lock",
"hashbrown 0.16.1",
"relative-path",
"rquickjs-sys",
]
[[package]]
name = "rquickjs-macro"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7106215ff41a5677b104906a13e1a440b880f4b6362b5dc4f3978c267fad2b80"
dependencies = [
"convert_case",
"fnv",
"ident_case",
"indexmap",
"proc-macro-crate",
"proc-macro2",
"quote",
"rquickjs-core",
"syn",
]
[[package]]
name = "rquickjs-sys"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27344601ef27460e82d6a4e1ecb9e7e99f518122095f3c51296da8e9be2b9d83"
dependencies = [
"cc",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
"errno",
"libc",
]
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
dependencies = [
"libc",
"windows-sys 0.60.2",
]
[[package]]
name = "syn"
version = "2.0.115"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "toml_datetime"
version = "0.7.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.23.10+spec-1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
dependencies = [
"indexmap",
"toml_datetime",
"toml_parser",
"winnow",
]
[[package]]
name = "toml_parser"
version = "1.0.8+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc"
dependencies = [
"winnow",
]
[[package]]
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
[[package]]
name = "unicode-ident"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "uuid"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb"
dependencies = [
"getrandom",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.2+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "winnow"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+74
View File
@@ -0,0 +1,74 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "llrt_utils_watts"
version = "0.8.1-beta"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "LLRT utilities"
readme = false
license = "Apache-2.0"
repository = "https://github.com/awslabs/llrt"
[features]
all = [
"fs",
"bytearray-buffer",
]
bytearray-buffer = ["tokio/sync"]
default = ["all"]
fs = ["tokio/fs"]
[lib]
name = "llrt_utils_watts"
path = "src/lib.rs"
[dependencies.rquickjs]
version = "0.11"
features = ["macro"]
default-features = false
[dependencies.tokio]
version = "1"
features = ["sync"]
default-features = false
[dependencies.tracing]
version = "0.1"
default-features = false
[dev-dependencies.llrt_test]
version = "0.8.1-beta"
[dev-dependencies.tokio]
version = "1"
features = ["full"]
[build-dependencies.llrt_build]
version = "0.8.1-beta"
[target."cfg(unix)".dependencies.libc]
version = "0.2"
default-features = false
[target."cfg(windows)".dependencies.windows-sys]
version = "0.61"
features = [
"Win32_Foundation",
"Win32_System_Threading",
]
default-features = false
+34
View File
@@ -0,0 +1,34 @@
[package]
name = "llrt_utils_watts"
description = "LLRT utilities"
version = "0.8.1-beta"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/awslabs/llrt"
[features]
default = ["all"]
all = ["fs", "bytearray-buffer"]
fs = ["tokio/fs"]
bytearray-buffer = ["tokio/sync"]
[dependencies]
rquickjs = { version = "0.11", features = [
"macro",
], default-features = false }
tokio = { version = "1", features = ["sync"], default-features = false }
tracing = { version = "0.1", default-features = false }
[target.'cfg(unix)'.dependencies]
libc = { version = "0.2", default-features = false }
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_System_Threading"], default-features = false }
[dev-dependencies]
llrt_test = { version = "0.8.1-beta", path = "../llrt_test" }
tokio = { version = "1", features = ["full"] }
[build-dependencies]
llrt_build = { version = "0.8.1-beta", path = "../llrt_build" }
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+298
View File
@@ -0,0 +1,298 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use rquickjs::{
class::{Trace, Tracer},
Ctx, FromJs, IntoJs, JsLifetime, Result, Value,
};
macro_rules! define_any_of {
($name:ident, $($variant:ident),+) => {
#[derive(Debug, Clone)]
pub enum $name<$($variant),+> {
$(
$variant($variant),
)+
}
define_any_of_from_js!($name, $($variant),+);
impl<'js, $($variant: IntoJs<'js>),+> IntoJs<'js> for $name<$($variant),+> {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
match self {
$(
Self::$variant(val) => val.into_js(ctx),
)+
}
}
}
unsafe impl<'js, $($variant: JsLifetime<'js>),+> JsLifetime<'js> for $name<$($variant),+> {
type Changed<'to> = $name<$($variant::Changed<'to>),+>;
}
impl<'js, $($variant: Trace<'js>),+> Trace<'js> for $name<$($variant),+> {
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
match self {
$(
Self::$variant(val) => val.trace(tracer),
)+
}
}
}
define_any_of_methods!($name, $($variant),+);
};
}
macro_rules! define_any_of_from_js {
($name:ident, $first:ident, $($rest:ident),+) => {
impl<'js, $first: FromJs<'js>, $($rest: FromJs<'js>),+> FromJs<'js> for $name<$first, $($rest),+> {
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
define_any_of_from_js_impl!($name, ctx, value, $first, $($rest),+)
}
}
};
}
macro_rules! define_any_of_from_js_impl {
($name:ident, $ctx:ident, $value:ident, $first:ident) => {
$first::from_js($ctx, $value).map($name::$first)
};
($name:ident, $ctx:ident, $value:ident, $first:ident, $($rest:ident),+) => {
$first::from_js($ctx, $value.clone()).map($name::$first).or_else(|error| {
if error.is_from_js() {
define_any_of_from_js_impl!($name, $ctx, $value, $($rest),+)
} else {
Err(error)
}
})
};
}
macro_rules! define_any_of_variant_methods {
($variant:ident, $is_fn:ident, $as_fn:ident, $as_mut_fn:ident, $into_fn:ident) => {
#[allow(dead_code)]
pub fn $is_fn(&self) -> bool {
matches!(self, Self::$variant(_))
}
#[allow(dead_code)]
pub fn $as_fn(&self) -> Option<&$variant> {
match self {
Self::$variant(val) => Some(val),
_ => None,
}
}
#[allow(dead_code)]
pub fn $as_mut_fn(&mut self) -> Option<&mut $variant> {
match self {
Self::$variant(val) => Some(val),
_ => None,
}
}
#[allow(dead_code)]
pub fn $into_fn(self) -> std::result::Result<$variant, Self> {
match self {
Self::$variant(val) => Ok(val),
other => Err(other),
}
}
};
(A) => {
define_any_of_variant_methods!(A, is_a, as_a, as_a_mut, into_a);
};
(B) => {
define_any_of_variant_methods!(B, is_b, as_b, as_b_mut, into_b);
};
(C) => {
define_any_of_variant_methods!(C, is_c, as_c, as_c_mut, into_c);
};
(D) => {
define_any_of_variant_methods!(D, is_d, as_d, as_d_mut, into_d);
};
(E) => {
define_any_of_variant_methods!(E, is_e, as_e, as_e_mut, into_e);
};
(F) => {
define_any_of_variant_methods!(F, is_f, as_f, as_f_mut, into_f);
};
(G) => {
define_any_of_variant_methods!(G, is_g, as_g, as_g_mut, into_g);
};
(H) => {
define_any_of_variant_methods!(H, is_h, as_h, as_h_mut, into_h);
};
}
macro_rules! define_any_of_methods {
($name:ident, $($variant:ident),+) => {
impl<$($variant),+> $name<$($variant),+> {
$(
define_any_of_variant_methods!($variant);
)+
}
};
}
define_any_of!(AnyOf2, A, B);
define_any_of!(AnyOf3, A, B, C);
define_any_of!(AnyOf4, A, B, C, D);
define_any_of!(AnyOf5, A, B, C, D, E);
define_any_of!(AnyOf6, A, B, C, D, E, F);
define_any_of!(AnyOf7, A, B, C, D, E, F, G);
define_any_of!(AnyOf8, A, B, C, D, E, F, G, H);
#[cfg(test)]
mod tests {
use super::*;
use rquickjs::{Context, Runtime};
#[test]
fn test_any_of_string_number() {
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
ctx.with(|ctx| {
// Test string conversion
let val: Value = ctx.eval("'hello'").unwrap();
let any: AnyOf2<String, i32> = AnyOf2::from_js(&ctx, val).unwrap();
assert!(any.is_a());
assert_eq!(any.as_a().unwrap(), "hello");
assert!(!any.is_b());
assert!(any.as_b().is_none());
// Test number conversion
let val: Value = ctx.eval("42").unwrap();
let any: AnyOf2<String, i32> = AnyOf2::from_js(&ctx, val).unwrap();
assert!(!any.is_a());
assert!(any.is_b());
assert_eq!(*any.as_b().unwrap(), 42);
});
}
#[test]
fn test_any_of_fallback() {
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
ctx.with(|ctx| {
// Test that it tries in order
let val: Value = ctx.eval("true").unwrap();
let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
assert!(any.is_c());
assert!(*any.as_c().unwrap());
assert!(!any.is_a());
assert!(!any.is_b());
});
}
#[test]
fn test_any_of_into_js() {
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
ctx.with(|ctx| {
let any: AnyOf2<String, i32> = AnyOf2::A("test".to_string());
let val: Value = any.into_js(&ctx).unwrap();
let result: String = val.get().unwrap();
assert_eq!(result, "test");
let any: AnyOf2<String, i32> = AnyOf2::B(99);
let val: Value = any.into_js(&ctx).unwrap();
let result: i32 = val.get().unwrap();
assert_eq!(result, 99);
});
}
#[test]
fn test_any_of_methods() {
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
ctx.with(|ctx| {
// Test all methods for variant A
let val: Value = ctx.eval("'test'").unwrap();
let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
assert!(any.is_a());
assert_eq!(any.as_a().unwrap(), "test");
assert_eq!(any.into_a().unwrap(), "test");
// Test all methods for variant B
let val: Value = ctx.eval("42").unwrap();
let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
assert!(any.is_b());
assert_eq!(*any.as_b().unwrap(), 42);
assert_eq!(any.into_b().unwrap(), 42);
// Test all methods for variant C
let val: Value = ctx.eval("true").unwrap();
let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
assert!(any.is_c());
assert!(*any.as_c().unwrap());
assert!(any.into_c().unwrap());
});
}
#[test]
fn test_any_of_mutable_methods() {
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
ctx.with(|ctx| {
let val: Value = ctx.eval("42").unwrap();
let mut any: AnyOf4<String, i32, bool, f64> = AnyOf4::from_js(&ctx, val).unwrap();
if let Some(n) = any.as_b_mut() {
*n = 100;
}
assert_eq!(any.into_b().unwrap(), 100);
});
}
#[test]
fn test_any_of_error_propagation() {
use rquickjs::{Array, Object};
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
ctx.with(|ctx| {
// Test that conversion errors cause fallback to next type
let val: Value = ctx.eval("42").unwrap();
let any: AnyOf2<String, i32> = AnyOf2::from_js(&ctx, val).unwrap();
assert!(any.is_b());
// Test that all types fail results in an error
let val: Value = ctx.eval("null").unwrap();
let result: Result<AnyOf2<Object, Array>> = AnyOf2::from_js(&ctx, val);
assert!(result.is_err());
});
}
#[test]
fn test_any_of_conversion_order() {
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
ctx.with(|ctx| {
// Test that conversion happens in order A, B, C, D, E
// Since 42 can be converted to f64, i32, etc., but String comes first and fails,
// it should try the next successful conversion
let val: Value = ctx.eval("42").unwrap();
// String should fail, so it tries i32 which succeeds
let any: AnyOf3<String, i32, f64> = AnyOf3::from_js(&ctx, val).unwrap();
assert!(any.is_b());
// If we flip the order, f64 would be tried first (but both work)
let val: Value = ctx.eval("3.14").unwrap();
let any: AnyOf3<String, f64, i32> = AnyOf3::from_js(&ctx, val).unwrap();
assert!(any.is_b()); // f64 should succeed first
});
}
}
@@ -0,0 +1,226 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::{
cmp::min,
collections::VecDeque,
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, Mutex,
},
};
use tokio::sync::{Notify, Semaphore};
#[derive(Clone)]
pub struct BytearrayBuffer {
inner: Arc<Mutex<VecDeque<u8>>>,
max_capacity: Arc<AtomicUsize>,
len: Arc<AtomicUsize>,
notify: Arc<Notify>,
closed: Arc<AtomicBool>,
write_semaphore: Arc<Semaphore>,
}
impl BytearrayBuffer {
pub fn new(capacity: usize) -> Self {
let queue = VecDeque::with_capacity(capacity);
let capacity = queue.capacity();
Self {
inner: Arc::new(Mutex::new(queue)),
len: Arc::new(AtomicUsize::new(0)),
max_capacity: Arc::new(AtomicUsize::new(capacity)),
notify: Arc::new(Notify::new()),
closed: Arc::new(AtomicBool::new(false)),
write_semaphore: Arc::new(Semaphore::new(1)),
}
}
pub fn len(&self) -> usize {
self.len.load(Ordering::Relaxed)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[allow(dead_code)]
pub fn write_forced(&self, item: &[u8]) {
let mut inner = self.inner.lock().unwrap();
inner.extend(item);
let capacity = inner.capacity();
self.len.fetch_add(item.len(), Ordering::Relaxed);
self.max_capacity.store(capacity, Ordering::Relaxed);
}
pub async fn write(&self, item: &mut [u8]) -> usize {
let _ = self.write_semaphore.acquire().await.unwrap();
let mut slice_index = 0;
loop {
let max_capacity = self.max_capacity.load(Ordering::Relaxed);
if self.closed.load(Ordering::Relaxed) {
return max_capacity;
}
let len = self.len.load(Ordering::Relaxed);
let available = max_capacity - len;
if available > 0 {
let end_index = min(item.len() - 1, slice_index + available - 1);
let sub_slice = &item[slice_index..=end_index];
let slice_length = sub_slice.len();
slice_index += slice_length;
self.inner.lock().unwrap().extend(sub_slice);
self.len.fetch_add(slice_length, Ordering::Relaxed);
if slice_index == item.len() {
return max_capacity;
}
}
self.notify.notified().await;
}
}
#[allow(dead_code)]
pub fn is_closed(&self) -> bool {
self.closed.load(Ordering::Relaxed)
}
pub async fn close(&self) {
self.closed.store(true, Ordering::Relaxed);
self.notify.notify_one();
//wait for write to finish
let _ = self.write_semaphore.acquire().await.unwrap();
}
pub async fn clear(&self) {
self.closed.store(false, Ordering::Relaxed);
self.notify.notify_one();
//wait for write to finish
let _ = self.write_semaphore.acquire().await.unwrap();
self.len.store(0, Ordering::Relaxed);
self.inner.lock().unwrap().clear();
self.closed.store(false, Ordering::Relaxed);
}
pub fn read(&self, desired_size: Option<usize>) -> Option<Vec<u8>> {
let mut inner = self.inner.lock().unwrap();
let done = self.closed.load(Ordering::Relaxed);
let items = if done {
Some(inner.drain(0..).collect())
} else if let Some(desired_len) = desired_size {
let max_capacity = self.max_capacity.load(Ordering::Relaxed);
if desired_len > max_capacity {
let diff = desired_len - max_capacity;
inner.reserve(diff - 1);
let mut max_capacity = inner.capacity();
if desired_len > max_capacity {
inner.reserve(desired_len - max_capacity);
max_capacity = inner.capacity();
}
drop(inner);
self.max_capacity.store(max_capacity, Ordering::Relaxed);
self.notify.notify_one();
return None;
}
let len = self.len.load(Ordering::Relaxed);
if desired_len > len {
self.notify.notify_one();
return None;
}
Some(inner.drain(0..desired_len).collect())
} else {
Some(inner.drain(0..).collect())
};
self.len.store(inner.len(), Ordering::Relaxed);
drop(inner);
self.notify.notify_one();
items
}
}
#[cfg(test)]
mod tests {
use super::BytearrayBuffer;
#[tokio::test]
async fn clear_while_writing() {
let queue = BytearrayBuffer::new(8);
let queue2 = queue.clone();
tokio::task::spawn(async move {
let mut vec: Vec<u8> = (0..=255).collect();
queue.write(&mut vec).await;
});
queue2.clear().await
}
#[tokio::test]
async fn write_one_at_a_time() {
let queue = BytearrayBuffer::new(8);
let queue2 = queue.clone();
let queue3 = queue.clone();
tokio::task::spawn(async move {
let mut vec: Vec<u8> = (0..=127).collect();
queue.write(&mut vec).await;
});
tokio::task::spawn(async move {
let mut vec: Vec<u8> = (128..=255).collect();
queue2.write(&mut vec).await;
});
let mut data = Vec::<u8>::new();
loop {
tokio::task::yield_now().await;
if let Some(bytes) = queue3.read(Some(256)) {
data.extend(bytes);
break;
}
}
//assert that data in vec is increment from 0 to 255
for i in 0..=255 {
assert_eq!(data[i as usize], i);
}
}
#[tokio::test]
async fn queue() {
let queue = BytearrayBuffer::new(8);
let queue2 = queue.clone();
let write_task = tokio::task::spawn(async move {
for _ in 0..=255 {
let mut vec: Vec<u8> = (0..=255).collect();
queue.write(&mut vec).await;
}
queue.close().await;
});
let mut data = Vec::<u8>::new();
loop {
let done = queue2.is_closed();
tokio::task::yield_now().await;
if let Some(bytes) = queue2.read(Some(9)) {
data.extend(bytes);
}
if done {
break;
}
}
let _ = write_task.await;
assert_eq!(data.len(), 256 * 256)
}
}
+380
View File
@@ -0,0 +1,380 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::rc::Rc;
use rquickjs::{
atom::PredefinedAtom,
class::{Trace, Tracer},
function::Constructor,
ArrayBuffer, Coerced, Ctx, Exception, FromJs, IntoJs, JsLifetime, Object, Result, TypedArray,
Value,
};
use crate::{error_messages::ERROR_MSG_ARRAY_BUFFER_DETACHED, result::ResultExt};
#[derive(Clone, PartialEq)]
pub enum ObjectBytes<'js> {
U8Array(TypedArray<'js, u8>),
I8Array(TypedArray<'js, i8>),
U16Array(TypedArray<'js, u16>),
I16Array(TypedArray<'js, i16>),
U32Array(TypedArray<'js, u32>),
I32Array(TypedArray<'js, i32>),
U64Array(TypedArray<'js, u64>),
I64Array(TypedArray<'js, i64>),
F32Array(TypedArray<'js, f32>),
F64Array(TypedArray<'js, f64>),
DataView(ArrayBuffer<'js>),
Vec(Vec<u8>),
}
// Requires manual implementation because rquickjs hasn't implemented JsLifetime for f32 or f64
unsafe impl<'js> JsLifetime<'js> for ObjectBytes<'js> {
type Changed<'to> = ObjectBytes<'to>;
}
impl<'js> Trace<'js> for ObjectBytes<'js> {
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
match self {
ObjectBytes::U8Array(a) => a.trace(tracer),
ObjectBytes::I8Array(a) => a.trace(tracer),
ObjectBytes::U16Array(a) => a.trace(tracer),
ObjectBytes::I16Array(a) => a.trace(tracer),
ObjectBytes::U32Array(a) => a.trace(tracer),
ObjectBytes::I32Array(a) => a.trace(tracer),
ObjectBytes::U64Array(a) => a.trace(tracer),
ObjectBytes::I64Array(a) => a.trace(tracer),
ObjectBytes::F32Array(a) => a.trace(tracer),
ObjectBytes::F64Array(a) => a.trace(tracer),
ObjectBytes::DataView(d) => d.trace(tracer),
ObjectBytes::Vec(v) => v.trace(tracer),
}
}
}
impl<'js> IntoJs<'js> for ObjectBytes<'js> {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
match self {
ObjectBytes::U8Array(a) => a.into_js(ctx),
ObjectBytes::I8Array(a) => a.into_js(ctx),
ObjectBytes::U16Array(a) => a.into_js(ctx),
ObjectBytes::I16Array(a) => a.into_js(ctx),
ObjectBytes::U32Array(a) => a.into_js(ctx),
ObjectBytes::I32Array(a) => a.into_js(ctx),
ObjectBytes::U64Array(a) => a.into_js(ctx),
ObjectBytes::I64Array(a) => a.into_js(ctx),
ObjectBytes::F32Array(a) => a.into_js(ctx),
ObjectBytes::F64Array(a) => a.into_js(ctx),
ObjectBytes::DataView(d) => {
let ctor: Constructor = ctx.globals().get(PredefinedAtom::DataView)?;
ctor.construct((d,))
},
ObjectBytes::Vec(v) => v.into_js(ctx),
}
}
}
impl<'js> TryFrom<ObjectBytes<'js>> for Vec<u8> {
type Error = Rc<str>;
fn try_from(value: ObjectBytes<'js>) -> std::result::Result<Self, Self::Error> {
value.into_bytes_inner()
}
}
impl<'a, 'js> TryFrom<&'a ObjectBytes<'js>> for &'a [u8] {
type Error = Rc<str>;
fn try_from(value: &'a ObjectBytes<'js>) -> std::result::Result<Self, Self::Error> {
value.as_bytes_inner()
}
}
impl<'js> FromJs<'js> for ObjectBytes<'js> {
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
Self::from_offset(ctx, &value, 0, None)
}
}
impl<'js> ObjectBytes<'js> {
pub fn from(ctx: &Ctx<'js>, value: &Value<'js>) -> Result<Self> {
Self::from_offset(ctx, value, 0, None)
}
pub fn from_offset(
ctx: &Ctx<'js>,
value: &Value<'js>,
offset: usize,
length: Option<usize>,
) -> Result<Self> {
if value.is_undefined() {
return Ok(ObjectBytes::Vec(vec![]));
}
if let Some(bytes) = get_string_bytes(value, offset, length)? {
return Ok(ObjectBytes::Vec(bytes));
}
if let Some(bytes) = get_array_bytes(value, offset, length)? {
return Ok(ObjectBytes::Vec(bytes));
}
if let Some(obj) = value.as_object() {
if let Some(bytes) = Self::from_array_buffer(obj)? {
return Ok(bytes);
}
}
if let Some(bytes) = get_coerced_string_bytes(value, offset, length) {
return Ok(ObjectBytes::Vec(bytes));
}
Err(Exception::throw_message(
ctx,
"value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or interpretable as string",
))
}
pub fn as_bytes(&self, ctx: &Ctx<'js>) -> Result<&[u8]> {
self.as_bytes_inner().or_throw(ctx)
}
fn as_bytes_inner(&self) -> std::result::Result<&[u8], Rc<str>> {
match self {
ObjectBytes::U8Array(array) => array.as_bytes(),
ObjectBytes::I8Array(array) => array.as_bytes(),
ObjectBytes::U16Array(array) => array.as_bytes(),
ObjectBytes::I16Array(array) => array.as_bytes(),
ObjectBytes::U32Array(array) => array.as_bytes(),
ObjectBytes::I32Array(array) => array.as_bytes(),
ObjectBytes::U64Array(array) => array.as_bytes(),
ObjectBytes::I64Array(array) => array.as_bytes(),
ObjectBytes::F32Array(array) => array.as_bytes(),
ObjectBytes::F64Array(array) => array.as_bytes(),
ObjectBytes::DataView(array_buffer) => array_buffer.as_bytes(),
ObjectBytes::Vec(bytes) => Some(bytes.as_ref()),
}
.ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED.into())
}
pub fn into_bytes(self, ctx: &Ctx<'_>) -> Result<Vec<u8>> {
self.into_bytes_inner().or_throw(ctx)
}
fn into_bytes_inner(self) -> std::result::Result<Vec<u8>, Rc<str>> {
if let ObjectBytes::Vec(bytes) = self {
return Ok(bytes);
}
Ok(self.as_bytes_inner()?.to_vec())
}
pub fn from_array_buffer(obj: &Object<'js>) -> Result<Option<ObjectBytes<'js>>> {
//most common
if let Ok(typed_array) = TypedArray::<u8>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::U8Array(typed_array)));
}
//second most common
if let Some(array_buffer) = ArrayBuffer::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::DataView(array_buffer)));
}
if let Ok(typed_array) = TypedArray::<i8>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::I8Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<u16>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::U16Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<i16>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::I16Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<u32>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::U32Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<i32>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::I32Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<u64>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::U64Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<i64>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::I64Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<f32>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::F32Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<f64>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::F64Array(typed_array)));
}
if let Ok(array_buffer) = obj.get::<_, ArrayBuffer>("buffer") {
return Ok(Some(ObjectBytes::DataView(array_buffer)));
}
Ok(None)
}
pub fn get_array_buffer(&self) -> Result<Option<(ArrayBuffer<'js>, usize, usize)>> {
let buffer = match self {
ObjectBytes::DataView(array_buffer) => (array_buffer.clone(), array_buffer.len(), 0),
ObjectBytes::U8Array(typed_array) => {
let byte_length = typed_array.len();
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::I8Array(typed_array) => {
let byte_length = typed_array.len();
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::U16Array(typed_array) => {
let byte_length = typed_array.len() * 2;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::I16Array(typed_array) => {
let byte_length = typed_array.len() * 2;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::U32Array(typed_array) => {
let byte_length = typed_array.len() * 4;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::I32Array(typed_array) => {
let byte_length = typed_array.len() * 4;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::U64Array(typed_array) => {
let byte_length = typed_array.len() * 8;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::I64Array(typed_array) => {
let byte_length = typed_array.len() * 8;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::F32Array(typed_array) => {
let byte_length = typed_array.len() * 4;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::F64Array(typed_array) => {
let byte_length = typed_array.len() * 8;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
_ => return Ok(None),
};
Ok(Some(buffer))
}
}
pub fn get_start_end_indexes(
source_len: usize,
target_len: Option<usize>,
offset: usize,
) -> (usize, usize) {
if offset > source_len {
return (0, 0);
}
let target_len = target_len.unwrap_or(source_len - offset);
if offset + target_len > source_len {
return (offset, source_len);
}
(offset, target_len + offset)
}
pub fn get_array_bytes(
value: &Value<'_>,
offset: usize,
length: Option<usize>,
) -> Result<Option<Vec<u8>>> {
if value.is_array() {
let array = value.as_array().unwrap();
let (start, end) = get_start_end_indexes(array.len(), length, offset);
let size = end - start;
let mut bytes: Vec<u8> = Vec::with_capacity(size);
for val in array.iter::<u8>().skip(start).take(size) {
let val: u8 = val?;
bytes.push(val);
}
return Ok(Some(bytes));
}
Ok(None)
}
pub fn get_coerced_string_bytes(
value: &Value<'_>,
offset: usize,
length: Option<usize>,
) -> Option<Vec<u8>> {
if let Ok(val) = value.get::<Coerced<String>>() {
return Some(bytes_from_js_string(val.0, offset, length));
};
None
}
fn bytes_from_js_string(string: String, offset: usize, length: Option<usize>) -> Vec<u8> {
let (start, end) = get_start_end_indexes(string.len(), length, offset);
string.as_bytes()[start..end].to_vec()
}
#[inline]
pub fn get_string_bytes(
value: &Value<'_>,
offset: usize,
length: Option<usize>,
) -> Result<Option<Vec<u8>>> {
if let Some(val) = value.as_string() {
let string = val.to_string()?;
return Ok(Some(bytes_from_js_string(string, offset, length)));
}
Ok(None)
}
pub fn bytes_to_typed_array<'js>(ctx: Ctx<'js>, bytes: &[u8]) -> Result<Value<'js>> {
TypedArray::<u8>::new(ctx.clone(), bytes).into_js(&ctx)
}
+77
View File
@@ -0,0 +1,77 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use rquickjs::{
atom::PredefinedAtom, class::JsClass, object::Accessor, prelude::This, Array, Class, Ctx,
Function, Object, Result, Value,
};
use crate::primordials::{BasePrimordials, Primordial};
use super::{object::ObjectExt, result::OptionExt};
pub static CUSTOM_INSPECT_SYMBOL_DESCRIPTION: &str = "llrt.inspect.custom";
pub trait IteratorDef<'js>
where
Self: 'js + JsClass<'js> + Sized,
{
fn js_entries(&self, ctx: Ctx<'js>) -> Result<Array<'js>>;
fn js_iterator(&self, ctx: Ctx<'js>) -> Result<Value<'js>> {
let value = self.js_entries(ctx)?;
let obj = value.as_object();
let values_fn: Function = obj.get(PredefinedAtom::Values)?;
values_fn.call((This(value),))
}
}
pub fn get_class_name(value: &Value) -> Result<Option<String>> {
value
.get_optional::<_, Object>(PredefinedAtom::Constructor)?
.and_then_ok(|ctor| ctor.get_optional::<_, String>(PredefinedAtom::Name))
}
#[inline(always)]
pub fn get_class<'js, C>(provided: &Value<'js>) -> Result<Option<Class<'js, C>>>
where
C: JsClass<'js>,
{
if provided
.as_object()
.map(|p| p.instance_of::<C>())
.unwrap_or_default()
{
return Ok(Some(Class::<C>::from_value(provided)?));
}
Ok(None)
}
pub trait CustomInspectExtension<'js> {
fn define_with_custom_inspect(globals: &Object<'js>) -> Result<()>;
}
pub trait CustomInspect<'js>
where
Self: JsClass<'js>,
{
fn custom_inspect(&self, ctx: Ctx<'js>) -> Result<Object<'js>>;
}
impl<'js, C> CustomInspectExtension<'js> for Class<'js, C>
where
C: JsClass<'js> + CustomInspect<'js> + 'js,
{
fn define_with_custom_inspect(globals: &Object<'js>) -> Result<()> {
Self::define(globals)?;
let custom_inspect_symbol = BasePrimordials::get(globals.ctx())?
.symbol_custom_inspect
.clone();
if let Some(proto) = Class::<C>::prototype(globals.ctx())? {
proto.prop(
custom_inspect_symbol,
Accessor::from(|this: This<Class<'js, C>>, ctx| this.borrow().custom_inspect(ctx)),
)?;
}
Ok(())
}
}
+487
View File
@@ -0,0 +1,487 @@
use std::collections::HashSet;
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use rquickjs::{
atom::PredefinedAtom,
function::{Constructor, Opt, This},
Array, ArrayBuffer, Ctx, Exception, Function, IntoJs, Null, Object, Result, Type, Value,
};
use super::{
hash,
object::ObjectExt,
primordials::{BasePrimordials, Primordial},
};
#[derive(Debug)]
enum StackItem<'js> {
Value(usize, Value<'js>, Option<String>, Option<usize>),
ObjectEnd,
}
#[derive(Debug)]
enum ObjectType {
Set,
Map,
}
#[derive(Debug)]
enum TapeValue<'js> {
Array(Array<'js>),
Object(Object<'js>),
Value(Value<'js>),
Collection(Option<Value<'js>>, ObjectType),
}
#[derive(Debug)]
struct TapeItem<'js> {
parent: usize,
object_key: Option<String>,
array_index: Option<usize>,
value: TapeValue<'js>,
}
pub fn structured_clone<'js>(
ctx: &Ctx<'js>,
value: Value<'js>,
options: Opt<Object<'js>>,
) -> Result<Value<'js>> {
let primordials = BasePrimordials::get(ctx)?;
let mut transfer_set = None;
if let Some(options) = options.0 {
if let Some(transfer_array) = options.get_optional::<_, Array>("transfer")? {
let mut set = HashSet::with_capacity(transfer_array.len());
for item in transfer_array.iter::<Value>() {
set.insert(item?);
}
transfer_set = Some(set);
}
}
let mut tape = Vec::<TapeItem>::with_capacity(10);
let mut stack = Vec::with_capacity(10);
let mut visited = Vec::<(usize, usize)>::with_capacity(10);
let mut index = 0usize;
stack.push(StackItem::Value(0, value, None, None));
while let Some(item) = stack.pop() {
match item {
StackItem::Value(parent, value, mut object_key, array_index) => {
if let Some(set) = &transfer_set {
if let Some(value) = set.get(&value) {
append_transfer_value(&mut tape, value, parent, object_key, array_index)?;
index += 1;
continue;
}
}
match value.type_of() {
Type::Proxy => {
return Err(Exception::throw_type(
ctx,
"A Proxy value could not be cloned",
));
},
Type::Object => {
if check_circular(
&mut tape,
&mut visited,
&value,
parent,
&mut object_key,
array_index,
index,
) {
index += 1;
continue;
}
//unsafe OK since we're guaranteed to be object by the match
let object = unsafe { value.as_object().unwrap_unchecked() };
if object.is_instance_of(&primordials.constructor_date) {
append_ctor_value(
&mut tape,
object,
&primordials.constructor_date,
parent,
object_key,
array_index,
)?;
index += 1;
continue;
}
if object.is_instance_of(&primordials.constructor_regexp) {
append_ctor_value(
&mut tape,
object,
&primordials.constructor_regexp,
parent,
object_key,
array_index,
)?;
index += 1;
continue;
}
let is_collection = if object.is_instance_of(&primordials.constructor_set) {
Some(ObjectType::Set)
} else if object.is_instance_of(&primordials.constructor_map) {
Some(ObjectType::Map)
} else {
None
};
if let Some(collection_type) = is_collection {
append_collection(
&mut tape,
&primordials.function_array_from,
object,
parent,
object_key,
array_index,
collection_type,
&mut stack,
index,
)?;
index += 1;
continue;
}
if primordials
.function_array_buffer_is_view
.call::<_, bool>((value.clone(),))?
{
append_buffer(&mut tape, object, parent, object_key, array_index)?;
index += 1;
continue;
}
let new: Object<'_> =
if object.is_instance_of(&primordials.constructor_error) {
primordials.constructor_error.construct(("",))
} else {
Object::new(ctx.clone())
}?;
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Object(new),
});
stack.push(StackItem::ObjectEnd);
for key in object.keys::<String>() {
let key = key?;
let value = object.get(&key)?;
stack.push(StackItem::Value(index, value, Some(key), None));
}
},
Type::Array => {
if check_circular(
&mut tape,
&mut visited,
&value,
parent,
&mut object_key,
array_index,
index,
) {
index += 1;
continue;
}
let new = Array::new(ctx.clone())?;
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Array(new),
});
stack.push(StackItem::ObjectEnd);
//unsafe OK since we're guaranteed to be object by the match
let array = unsafe { value.as_array().unwrap_unchecked() };
//reverse for loop of items in array
for array_index in (0usize..array.len()).rev() {
stack.push(StackItem::Value(
index,
array.get(array_index)?,
None,
Some(array_index),
));
}
},
_ => {
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Value(value),
});
},
}
index += 1;
},
StackItem::ObjectEnd => {
visited.pop();
},
}
}
while let Some(item) = tape.pop() {
let value = match item.value {
TapeValue::Array(array) => array.into_value(),
TapeValue::Object(object) => object.into_value(),
TapeValue::Value(value) => value,
TapeValue::Collection(mut value, _) => value.take().unwrap(),
};
if tape.is_empty() {
return Ok(value);
}
let parent = &mut tape[item.parent];
let array_index = item.array_index;
let object_key = item.object_key;
match &mut parent.value {
TapeValue::Array(array) => {
array.set(array_index.unwrap(), value)?;
},
TapeValue::Object(object) => {
let string = object_key.unwrap();
object.set(string, value)?;
},
TapeValue::Collection(collection_value, collection_type) => {
match collection_type {
ObjectType::Set => {
collection_value.replace(primordials.constructor_set.construct((value,))?);
},
ObjectType::Map => {
collection_value.replace(primordials.constructor_map.construct((value,))?);
},
};
},
_ => {},
};
}
Null.into_js(ctx)
}
#[inline(always)]
#[cold]
fn append_buffer<'js>(
tape: &mut Vec<TapeItem<'js>>,
object: &Object<'js>,
parent: usize,
object_key: Option<String>,
array_index: Option<usize>,
) -> Result<()> {
let ctor: Constructor = object.get(PredefinedAtom::Constructor)?;
let slice: Function = object.get("slice")?;
let clone: Value = slice.call((This(object.clone()),))?;
let new = ctor.construct((clone,))?;
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Value(new),
});
Ok(())
}
#[inline(always)]
#[cold]
#[allow(clippy::too_many_arguments)]
fn append_collection<'js>(
tape: &mut Vec<TapeItem<'js>>,
array_from: &Function<'js>,
object: &Object<'js>,
parent: usize,
object_key: Option<String>,
array_index: Option<usize>,
collection_type: ObjectType,
stack: &mut Vec<StackItem<'js>>,
index: usize,
) -> Result<()> {
let array: Array = array_from.call((object.clone(),))?;
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Collection(None, collection_type),
});
stack.push(StackItem::ObjectEnd);
stack.push(StackItem::Value(index, array.into(), None, None));
Ok(())
}
#[inline(always)]
fn check_circular(
tape: &mut Vec<TapeItem>,
visited: &mut Vec<(usize, usize)>,
value: &Value<'_>,
parent: usize,
object_key: &mut Option<String>,
array_index: Option<usize>,
index: usize,
) -> bool {
let hash = hash::default_hash(value);
if let Some(visited) = visited.iter().find(|v| v.0 == hash) {
append_circular(tape, visited, object_key, parent, array_index);
return true;
}
visited.push((hash, index));
false
}
#[inline(always)]
#[cold]
fn append_transfer_value<'js>(
tape: &mut Vec<TapeItem<'js>>,
value: &Value<'js>,
parent: usize,
object_key: Option<String>,
array_index: Option<usize>,
) -> Result<()> {
let value = if let Some(ab) = ArrayBuffer::from_value(value.clone()) {
ab.get::<_, Function>("transfer")?.call((This(ab),))?
} else {
value.clone()
};
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Value(value),
});
Ok(())
}
#[inline(always)]
#[cold]
fn append_circular(
tape: &mut Vec<TapeItem<'_>>,
visited: &(usize, usize),
object_key: &mut Option<String>,
parent: usize,
array_index: Option<usize>,
) {
let value = match &tape[visited.1].value {
TapeValue::Array(array) => array.clone().into_value(),
TapeValue::Object(object) => object.clone().into_value(),
TapeValue::Value(value) => value.clone(),
TapeValue::Collection(value, _) => value.clone().unwrap(),
};
let object_key = object_key.take();
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Value(value),
});
}
#[inline(always)]
#[cold]
fn append_ctor_value<'js>(
tape: &mut Vec<TapeItem<'js>>,
object: &Object<'js>,
ctor: &Constructor<'js>,
parent: usize,
object_key: Option<String>,
array_index: Option<usize>,
) -> Result<()> {
let clone: Value = ctor.construct((object.clone(),))?;
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Value(clone),
});
Ok(())
}
#[cfg(test)]
mod tests {
use llrt_test::test_sync_with;
use rquickjs::{function::Opt, Object, Value};
use crate::primordials::{BasePrimordials, Primordial};
use super::structured_clone;
#[tokio::test]
async fn clone() {
test_sync_with(|ctx| {
BasePrimordials::init(&ctx)?;
let value: Object = ctx.eval(
r#"
const a = {
"foo":{
"bar":"baz"
},
"foo1":{
"bar1":"baz1",
"bar11":"baz11"
}
};
a
"#,
)?;
let cloned = structured_clone(&ctx, value.clone().into_value(), Opt(None))?
.into_object()
.unwrap();
let json = ctx
.json_stringify(value.clone())?
.unwrap()
.to_string()?
.to_string();
let clone_json = ctx
.json_stringify(cloned.clone())?
.unwrap()
.to_string()?
.to_string();
assert_eq!(json, clone_json);
assert_ne!(
value.get::<_, Value>("foo")?,
cloned.get::<_, Value>("foo")?
);
Ok(())
})
.await
}
#[tokio::test]
async fn clone_circular() {
test_sync_with(|ctx| {
BasePrimordials::init(&ctx)?;
let _value: Object = ctx.eval(
r#"
const originalObject = { foo: { bar: "baz",arr: [1,2,3] } };
originalObject.foo.circularRef = originalObject;
originalObject.foo.circularRef2 = originalObject;
originalObject.foo.circularRef3 = originalObject.foo;
originalObject.ref2 = originalObject;
"#,
)?;
Ok(())
})
.await
}
}
+17
View File
@@ -0,0 +1,17 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use rquickjs::{Ctx, Result};
pub trait CtxExt {
fn get_script_or_module_name(&self) -> Result<String>;
}
impl CtxExt for Ctx<'_> {
fn get_script_or_module_name(&self) -> Result<String> {
if let Some(name) = self.script_or_module_name(0) {
name.to_string()
} else {
Ok(String::from("."))
}
}
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use rquickjs::{CatchResultExt, CaughtError, Ctx, Error, IntoJs, Result, Value};
pub trait ErrorExtensions<'js> {
fn into_value(self, ctx: &Ctx<'js>) -> Result<Value<'js>>;
}
impl<'js> ErrorExtensions<'js> for Error {
fn into_value(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
Err::<(), _>(self).catch(ctx).unwrap_err().into_value(ctx)
}
}
impl<'js> ErrorExtensions<'js> for CaughtError<'js> {
fn into_value(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
Ok(match self {
CaughtError::Error(err) => err.to_string().into_js(ctx)?,
CaughtError::Exception(ex) => ex.into_value(),
CaughtError::Value(val) => val,
})
}
}
@@ -0,0 +1,3 @@
pub const ERROR_MSG_NOT_ARRAY_BUFFER: &str = "Not an ArrayBuffer";
pub const ERROR_MSG_ARRAY_BUFFER_DETACHED: &str = "ArrayBuffer is detached";
pub const ERROR_MSG_BROADCAST_LAGGED: &str = "Lagged too much behind";
+103
View File
@@ -0,0 +1,103 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::{fs::Metadata, io, path::PathBuf};
use tokio::fs::{self};
pub struct DirectoryWalker<T>
where
T: Fn(&str) -> bool,
{
stack: Vec<(PathBuf, Option<Metadata>)>,
filter: T,
recursive: bool,
eat_root: bool,
}
impl<T> DirectoryWalker<T>
where
T: Fn(&str) -> bool,
{
pub fn new(root: PathBuf, filter: T) -> Self {
Self {
stack: vec![(root, None)],
filter,
recursive: false,
eat_root: true,
}
}
pub fn set_recursive(&mut self, recursive: bool) {
self.recursive = recursive;
}
pub async fn walk(&mut self) -> io::Result<Option<(PathBuf, Metadata)>> {
if self.eat_root {
self.eat_root = false;
let (dir, _) = self.stack.pop().unwrap();
self.append_stack(&dir).await?;
}
if let Some((entry, metadata)) = self.stack.pop() {
let metadata = metadata.unwrap();
if self.recursive && metadata.is_dir() {
self.append_stack(&entry).await?;
}
Ok(Some((entry, metadata)))
} else {
Ok(None)
}
}
pub fn walk_sync(&mut self) -> io::Result<Option<(PathBuf, Metadata)>> {
if self.eat_root {
self.eat_root = false;
let (dir, _) = self.stack.pop().unwrap();
self.append_stack_sync(&dir)?;
}
if let Some((entry, metadata)) = self.stack.pop() {
let metadata = metadata.unwrap();
if self.recursive && metadata.is_dir() {
self.append_stack_sync(&entry)?;
}
Ok(Some((entry, metadata)))
} else {
Ok(None)
}
}
async fn append_stack(&mut self, dir: &PathBuf) -> io::Result<()> {
let mut stream = fs::read_dir(dir).await?;
while let Some(entry) = stream.next_entry().await? {
let name = entry.file_name();
let name = name.to_string_lossy();
if !(self.filter)(name.as_ref()) {
continue;
}
let entry_path = entry.path();
let metadata = fs::symlink_metadata(&entry_path).await?;
self.stack.push((entry_path, Some(metadata)));
}
Ok(())
}
fn append_stack_sync(&mut self, dir: &PathBuf) -> io::Result<()> {
let dir = std::fs::read_dir(dir)?;
for entry in dir.flatten() {
let name = entry.file_name();
let name = name.to_string_lossy();
if !(self.filter)(name.as_ref()) {
continue;
}
let entry_path = entry.path();
let metadata = entry_path.symlink_metadata()?;
self.stack.push((entry_path, Some(metadata)))
}
Ok(())
}
}
+8
View File
@@ -0,0 +1,8 @@
use std::hash::{DefaultHasher, Hash, Hasher};
#[inline]
pub fn default_hash<T: Hash + ?Sized>(v: &T) -> usize {
let mut state = DefaultHasher::default();
v.hash(&mut state);
state.finish() as usize
}
+30
View File
@@ -0,0 +1,30 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
macro_rules! define_extension {
($base:ident, $file:ident, $ext:expr) => {
#[allow(dead_code)]
pub const $base: &str = $ext;
#[allow(dead_code)]
pub const $file: &str = concat!(".", $ext);
};
}
define_extension!(BYTECODE_EXT, BYTECODE_FILE_EXT, "lrt");
macro_rules! define_supported_extensions {
// Accepts a list of supported extensions and a single additional constant extension
($constant_ext:ident, $($ext:literal),*) => {
// Define the array of extensions as a constant
pub const SUPPORTED_EXTENSIONS: &[&str] = &[$($ext),*, $constant_ext];
pub const JS_EXTENSIONS: &[&str] = &[$($ext),*];
// Define the function `is_supported_ext` using a match statement
pub fn is_supported_ext(ext: &str) -> bool {
matches!(ext, $($ext)|* | $constant_ext)
}
};
}
define_supported_extensions!(BYTECODE_FILE_EXT, ".js", ".mjs", ".cjs");
+30
View File
@@ -0,0 +1,30 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::sync::atomic::{AtomicUsize, Ordering};
use tokio::sync::Notify;
#[derive(Default)]
pub struct Latch {
count: AtomicUsize,
notify: Notify,
}
impl Latch {
pub fn increment(&self) {
self.count.fetch_add(1, Ordering::Relaxed);
}
pub fn decrement(&self) {
let previous = self.count.fetch_sub(1, Ordering::Relaxed);
if previous == 1 {
self.notify.notify_waiters();
}
}
pub async fn wait(&self) {
if self.count.load(Ordering::Relaxed) > 0 {
self.notify.notified().await;
}
}
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
pub mod any_of;
#[cfg(feature = "bytearray-buffer")]
pub mod bytearray_buffer;
pub mod bytes;
pub mod class;
pub mod clone;
pub mod ctx;
pub mod error;
pub mod error_messages;
#[cfg(feature = "fs")]
pub mod fs;
pub mod hash;
pub mod io;
pub mod latch;
pub mod macros;
pub mod mc_oneshot;
pub mod module;
pub mod object;
pub mod option;
pub mod primordials;
pub mod provider;
pub mod result;
pub mod reuse_list;
pub mod string;
pub mod sysinfo;
pub mod time;
#[cfg(any(unix, windows))]
pub mod signals;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
+55
View File
@@ -0,0 +1,55 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#[macro_export]
macro_rules! count_members {
() => (0);
($head:tt $(,$tail:tt)*) => (1 + count_members!($($tail),*));
}
#[macro_export]
macro_rules! iterable_enum {
($name:ident, $($variant:ident),*) => {
impl $name {
const VARIANTS: &'static [$name] = &[$($name::$variant,)*];
pub fn iter() -> std::slice::Iter<'static, $name> {
Self::VARIANTS.iter()
}
#[allow(dead_code)]
fn _ensure_all_variants(s: Self) {
match s {
$($name::$variant => {},)*
}
}
}
};
}
#[macro_export]
macro_rules! str_enum {
($name:ident, $($variant:ident => $str:expr),*) => {
impl $name {
pub fn as_str(&self) -> &'static str {
match self {
$($name::$variant => $str,)*
}
}
}
impl AsRef<str> for $name {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl TryFrom<&str> for $name {
type Error = String;
fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
match s {
$($str => Ok($name::$variant),)*
_ => Err(["'", s, "' not available"].concat())
}
}
}
};
}
@@ -0,0 +1,118 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, RwLock,
};
use rquickjs::{
class::{Trace, Tracer},
Value,
};
use std::ops::Deref;
use tokio::sync::Notify;
#[derive(Debug)]
pub struct Shared<T> {
is_sent: AtomicBool,
value: RwLock<Option<T>>,
notify: Notify,
}
#[derive(Clone, Debug)]
pub struct Sender<T: Clone>(Arc<Shared<T>>);
impl<T: Clone> Deref for Sender<T> {
type Target = Arc<Shared<T>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'js> Trace<'js> for Sender<Value<'js>> {
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
if let Ok(v) = self.value.read() {
if let Some(v) = v.as_ref() {
tracer.mark(v)
}
}
}
}
impl<T: Clone> Sender<T> {
pub fn send(&self, value: T) {
if !self.is_sent.load(Ordering::Relaxed) {
self.value.write().unwrap().replace(value);
self.is_sent.store(true, Ordering::Release);
self.notify.notify_waiters();
}
}
pub fn subscribe(&self) -> Receiver<T> {
Receiver(Arc::clone(&self.0))
}
}
#[derive(Clone, Debug)]
pub struct Receiver<T: Clone>(Arc<Shared<T>>);
impl<T: Clone> Deref for Receiver<T> {
type Target = Arc<Shared<T>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: Clone> Receiver<T> {
pub async fn recv(&self) -> T {
if !self.is_sent.load(Ordering::Acquire) {
self.notify.notified().await;
}
self.value.read().unwrap().clone().unwrap()
}
}
pub fn channel<T: Clone>() -> (Sender<T>, Receiver<T>) {
let shared = Arc::new(Shared {
is_sent: AtomicBool::new(false),
value: RwLock::new(None),
notify: Notify::new(),
});
(Sender(Arc::clone(&shared)), Receiver(shared))
}
#[cfg(test)]
mod tests {
use tokio::join;
#[tokio::test]
async fn test() {
let (tx, rx1) = super::channel::<bool>();
let rx2 = tx.subscribe();
let rx3 = tx.subscribe();
let a = tokio::spawn(async move {
let val = rx1.recv().await; //wait for value to become false
assert!(val)
});
let b = tokio::spawn(async move {
let val = rx2.recv().await; //wait for value to become false
assert!(val)
});
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
tx.send(true);
let val = rx3.recv().await;
assert!(val);
let (a, b) = join!(a, b);
a.unwrap();
b.unwrap();
}
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use rquickjs::{
module::{Exports, ModuleDef},
Ctx, Object, Result, Value,
};
pub struct ModuleInfo<T: ModuleDef> {
pub name: &'static str,
pub module: T,
}
pub fn export_default<'js, F>(ctx: &Ctx<'js>, exports: &Exports<'js>, f: F) -> Result<()>
where
F: FnOnce(&Object<'js>) -> Result<()>,
{
let default = Object::new(ctx.clone())?;
f(&default)?;
for name in default.keys::<String>() {
let name = name?;
let value: Value = default.get(&name)?;
exports.export(name, value)?;
}
exports.export("default", default)?;
Ok(())
}
+176
View File
@@ -0,0 +1,176 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::collections::BTreeMap;
use rquickjs::{
atom::PredefinedAtom, function::IntoJsFunc, prelude::Func, Array, Coerced, Ctx, Error,
Exception, FromJs, IntoAtom, IntoJs, Object, Result, Symbol, Undefined, Value,
};
use crate::primordials::{BasePrimordials, Primordial};
pub trait ObjectExt<'js> {
fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js>>(&self, k: K) -> Result<Option<V>>;
fn get_required<K: AsRef<str>, V: FromJs<'js>>(
&self,
k: K,
object_name: &'static str,
) -> Result<V>;
fn into_object_or_throw(self, ctx: &Ctx<'js>, object_name: &'static str)
-> Result<Object<'js>>;
}
impl<'js> ObjectExt<'js> for Object<'js> {
fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js> + Sized>(
&self,
k: K,
) -> Result<Option<V>> {
self.get::<K, Option<V>>(k)
}
fn get_required<K: AsRef<str>, V: FromJs<'js>>(
&self,
k: K,
object_name: &'static str,
) -> Result<V> {
let k = k.as_ref();
self.get::<&str, Option<V>>(k)?.ok_or_else(|| {
Exception::throw_type(
self.ctx(),
&[object_name, " '", k, "' property required"].concat(),
)
})
}
fn into_object_or_throw(self, _: &Ctx<'js>, _: &'static str) -> Result<Object<'js>> {
Ok(self)
}
}
impl<'js> ObjectExt<'js> for Value<'js> {
fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js>>(&self, k: K) -> Result<Option<V>> {
if let Some(obj) = self.as_object() {
return obj.get_optional(k);
}
Ok(None)
}
fn get_required<K: AsRef<str>, V: FromJs<'js>>(
&self,
k: K,
object_name: &'static str,
) -> Result<V> {
self.as_object()
.ok_or_else(|| not_a_object_error(self.ctx(), object_name))?
.get_required(k, object_name)
}
fn into_object_or_throw(
self,
ctx: &Ctx<'js>,
object_name: &'static str,
) -> Result<Object<'js>> {
self.into_object()
.ok_or_else(|| not_a_object_error(ctx, object_name))
}
}
pub fn not_a_object_error(ctx: &Ctx<'_>, object_name: &str) -> Error {
Exception::throw_type(ctx, &[object_name, " is not an object"].concat())
}
pub trait CreateSymbol<'js> {
fn for_description(ctx: &Ctx<'js>, description: &str) -> Result<Symbol<'js>>;
}
impl<'js> CreateSymbol<'js> for Symbol<'js> {
fn for_description(ctx: &Ctx<'js>, description: &str) -> Result<Symbol<'js>> {
BasePrimordials::get(ctx)?
.function_symbol_for
.call((description,))
}
}
pub struct Proxy<'js> {
target: Value<'js>,
options: Object<'js>,
}
impl<'js> IntoJs<'js> for Proxy<'js> {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
BasePrimordials::get(ctx)?
.constructor_proxy
.construct::<_, Value>((self.target, self.options))
}
}
impl<'js> Proxy<'js> {
pub fn new(ctx: Ctx<'js>) -> Result<Self> {
let options = Object::new(ctx.clone())?;
Ok(Self {
target: Undefined.into_value(ctx),
options,
})
}
pub fn with_target(ctx: Ctx<'js>, target: Value<'js>) -> Result<Self> {
let options = Object::new(ctx)?;
Ok(Self { target, options })
}
pub fn setter<T, P>(&self, setter: Func<T, P>) -> Result<()>
where
T: IntoJsFunc<'js, P> + 'js,
{
self.options.set(PredefinedAtom::Setter, setter)?;
Ok(())
}
pub fn getter<T, P>(&self, getter: Func<T, P>) -> Result<()>
where
T: IntoJsFunc<'js, P> + 'js,
{
self.options.set(PredefinedAtom::Getter, getter)?;
Ok(())
}
}
pub fn map_to_entries<'js, K, V, M>(ctx: &Ctx<'js>, map: M) -> Result<Array<'js>>
where
M: IntoIterator<Item = (K, V)>,
K: IntoJs<'js>,
V: IntoJs<'js>,
{
let array = Array::new(ctx.clone())?;
for (idx, (key, value)) in map.into_iter().enumerate() {
let entry = Array::new(ctx.clone())?;
entry.set(0, key)?;
entry.set(1, value)?;
array.set(idx, entry)?;
}
Ok(array)
}
pub fn array_to_btree_map<'js>(
ctx: &Ctx<'js>,
array: Array<'js>,
) -> Result<BTreeMap<String, Coerced<String>>> {
let value = object_from_entries(ctx, array)?;
let value = value.into_value();
BTreeMap::from_js(ctx, value)
}
pub fn object_from_entries<'js>(ctx: &Ctx<'js>, array: Array<'js>) -> Result<Object<'js>> {
let obj = Object::new(ctx.clone())?;
for value in array.into_iter().flatten() {
if let Some(entry) = value.as_array() {
if let Ok(key) = entry.get::<Value>(0) {
if let Ok(value) = entry.get::<Value>(1) {
let _ = obj.set(key, value); //ignore result of failed
}
}
}
}
Ok(obj)
}
+101
View File
@@ -0,0 +1,101 @@
use rquickjs::{
class::{Trace, Tracer},
function::{FromParam, ParamRequirement, ParamsAccessor},
Ctx, FromJs, IntoJs, JsLifetime, Result, Type, Value,
};
/// Helper type for treating an undefined value as None, without treating null as None
#[derive(Clone)]
pub struct Undefined<T>(pub Option<T>);
impl<'js, T: FromJs<'js>> FromJs<'js> for Undefined<T> {
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
if value.type_of() == Type::Undefined {
Ok(Self(None))
} else {
Ok(Self(Some(FromJs::from_js(ctx, value)?)))
}
}
}
impl<'js, T: IntoJs<'js>> IntoJs<'js> for Undefined<T> {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
match self.0 {
None => Ok(Value::new_undefined(ctx.clone())),
Some(val) => val.into_js(ctx),
}
}
}
impl<T> Default for Undefined<T> {
fn default() -> Self {
Self(None)
}
}
unsafe impl<'js, T: JsLifetime<'js>> JsLifetime<'js> for Undefined<T> {
type Changed<'to> = Undefined<T::Changed<'to>>;
}
impl<'js, T: Trace<'js>> Trace<'js> for Undefined<T> {
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
self.0.trace(tracer)
}
}
/// Helper type for converting an None into null instead of undefined.
/// Needed while rquickjs::function::Null has no IntoJs implementation
#[derive(Clone)]
pub struct Null<T>(pub Option<T>);
impl<'js, T: FromJs<'js>> FromJs<'js> for Null<T> {
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
if value.type_of() == Type::Null {
Ok(Self(None))
} else {
Ok(Self(Some(FromJs::from_js(ctx, value)?)))
}
}
}
impl<'js, T: IntoJs<'js>> IntoJs<'js> for Null<T> {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
match self.0 {
None => Ok(Value::new_null(ctx.clone())),
Some(val) => val.into_js(ctx),
}
}
}
unsafe impl<'js, T: JsLifetime<'js>> JsLifetime<'js> for Null<T> {
type Changed<'to> = Null<T::Changed<'to>>;
}
impl<'js, T: Trace<'js>> Trace<'js> for Null<T> {
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
self.0.trace(tracer)
}
}
/// Helper type for accepting no value, or null, but considering undefined as a value
pub struct NullableOpt<T>(pub Option<T>);
impl<'js, T: FromJs<'js>> FromParam<'js> for NullableOpt<T> {
fn param_requirement() -> ParamRequirement {
ParamRequirement::optional()
}
fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
if !params.is_empty() {
let arg = params.arg();
if arg.is_null() {
Ok(NullableOpt(None))
} else {
let ctx = params.ctx().clone();
Ok(NullableOpt(Some(T::from_js(&ctx, arg)?)))
}
} else {
Ok(NullableOpt(None))
}
}
}
@@ -0,0 +1,159 @@
use std::any::type_name;
use rquickjs::{
atom::PredefinedAtom, function::Constructor, runtime::UserDataGuard, Ctx, Function, JsLifetime,
Object, Result, Symbol,
};
use crate::{class::CUSTOM_INSPECT_SYMBOL_DESCRIPTION, result::ResultExt};
#[derive(JsLifetime)]
pub struct BasePrimordials<'js> {
// Constructors
pub constructor_map: Constructor<'js>,
pub constructor_set: Constructor<'js>,
pub constructor_date: Constructor<'js>,
pub constructor_error: Constructor<'js>,
pub constructor_type_error: Constructor<'js>,
pub constructor_range_error: Constructor<'js>,
pub constructor_regexp: Constructor<'js>,
pub constructor_uint8array: Constructor<'js>,
pub constructor_array_buffer: Constructor<'js>,
pub constructor_proxy: Constructor<'js>,
pub constructor_object: Constructor<'js>,
pub constructor_bool: Constructor<'js>,
pub constructor_number: Constructor<'js>,
pub constructor_string: Constructor<'js>,
// Prototypes
pub prototype_object: Object<'js>,
pub prototype_date: Object<'js>,
pub prototype_regexp: Object<'js>,
pub prototype_set: Object<'js>,
pub prototype_map: Object<'js>,
pub prototype_error: Object<'js>,
// Functions
pub function_array_from: Function<'js>,
pub function_array_buffer_is_view: Function<'js>,
pub function_get_own_property_descriptor: Function<'js>,
pub function_parse_int: Function<'js>,
pub function_parse_float: Function<'js>,
pub function_symbol_for: Function<'js>,
// Symbols
pub symbol_custom_inspect: Symbol<'js>,
}
pub trait Primordial<'js>
where
Self: Sized + JsLifetime<'js>,
{
fn get<'a>(ctx: &'a Ctx<'js>) -> Result<UserDataGuard<'a, Self>> {
let userdata = ctx.userdata::<Self>().or_throw_msg(
ctx,
&[
"Userdata of ",
type_name::<Self>(),
" not initialized. Call init(&ctx) on this type.",
]
.concat(),
)?;
Ok(userdata)
}
fn init<'a>(ctx: &'a Ctx<'js>) -> Result<()> {
if ctx.userdata::<Self>().is_none() {
let primoridals = Self::new(ctx)?;
let _ = ctx.store_userdata(primoridals);
}
Ok(())
}
fn new(ctx: &Ctx<'js>) -> Result<Self>;
}
impl<'js> Primordial<'js> for BasePrimordials<'js> {
fn new(ctx: &Ctx<'js>) -> Result<Self> {
let globals = ctx.globals();
let constructor_object: Constructor = globals.get(PredefinedAtom::Object)?;
let prototype_object: Object = constructor_object.get(PredefinedAtom::Prototype)?;
let constructor_proxy: Constructor = globals.get(PredefinedAtom::Proxy)?;
let function_get_own_property_descriptor: Function =
constructor_object.get(PredefinedAtom::GetOwnPropertyDescriptor)?;
let constructor_date: Constructor = globals.get(PredefinedAtom::Date)?;
let prototype_date: Object = constructor_date.get(PredefinedAtom::Prototype)?;
let constructor_map: Constructor = globals.get(PredefinedAtom::Map)?;
let prototype_map: Object = constructor_map.get(PredefinedAtom::Prototype)?;
let constructor_set: Constructor = globals.get(PredefinedAtom::Set)?;
let prototype_set: Object = constructor_set.get(PredefinedAtom::Prototype)?;
let constructor_regexp: Constructor = globals.get(PredefinedAtom::RegExp)?;
let prototype_regexp: Object = constructor_regexp.get(PredefinedAtom::Prototype)?;
let constructor_uint8array: Constructor = globals.get(PredefinedAtom::Uint8Array)?;
let constructor_arraybuffer: Constructor = globals.get(PredefinedAtom::ArrayBuffer)?;
let constructor_error: Constructor = globals.get(PredefinedAtom::Error)?;
let constructor_type_error: Constructor = ctx.globals().get(PredefinedAtom::TypeError)?;
let constructor_range_error: Constructor = ctx.globals().get(PredefinedAtom::RangeError)?;
let prototype_error: Object = constructor_error.get(PredefinedAtom::Prototype)?;
let constructor_array: Object = globals.get(PredefinedAtom::Array)?;
let function_array_from: Function = constructor_array.get(PredefinedAtom::From)?;
let constructor_array_buffer: Object = globals.get(PredefinedAtom::ArrayBuffer)?;
let function_array_buffer_is_view: Function = constructor_array_buffer.get("isView")?;
let constructor_bool: Constructor = globals.get(PredefinedAtom::Boolean)?;
let constructor_number: Constructor = globals.get(PredefinedAtom::Number)?;
let function_parse_float: Function = constructor_number.get("parseFloat")?;
let function_parse_int: Function = constructor_number.get("parseInt")?;
let constructor_string: Constructor = globals.get(PredefinedAtom::String)?;
let constructor_symbol: Constructor = globals.get(PredefinedAtom::Symbol)?;
let function_symbol_for: Function = constructor_symbol.get(PredefinedAtom::For)?;
let symbol_custom_inspect: Symbol<'js> =
function_symbol_for.call((CUSTOM_INSPECT_SYMBOL_DESCRIPTION,))?;
Ok(Self {
constructor_map,
constructor_set,
constructor_date,
constructor_proxy,
constructor_error,
constructor_type_error,
constructor_range_error,
constructor_regexp,
constructor_uint8array,
constructor_array_buffer: constructor_arraybuffer,
constructor_object,
constructor_bool,
constructor_number,
constructor_string,
prototype_object,
prototype_date,
prototype_regexp,
prototype_set,
prototype_map,
prototype_error,
function_array_from,
function_array_buffer_is_view,
function_get_own_property_descriptor,
function_parse_float,
function_parse_int,
function_symbol_for,
symbol_custom_inspect,
})
}
}
@@ -0,0 +1,24 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#[derive(PartialEq)]
pub enum ProviderType {
None,
Resource(String), // Custom asynchronous resource
// Userland provider types
Immediate, // [Immediate] Processing by setImmediate()
Interval, // [Interval] Timer by setInterval()
MessagePort, // [MessagePort] Port for worker_threads
Microtask, // [Microtask] Processing by queueMicrotask()
TickObject, // [TickObject] Processing by process.nextTick()
Timeout, // [Timeout] Timer by setTimeout()
// Internal provider types
FsReqCallback, // [FSREQCALLBACK] Callback for file system operations
GetAddrInfoReqWrap, // [GETADDRINFOREQWRAP] When resolving DNS (dns.lookup(), etc.)
GetNameInfoReqWrap, // [GETNAMEINFOREQWRAP] DNS reverse lookup
PipeWrap, // [PIPEWRAP] Pipe connection
StatWatcher, // [STATWACHER] File monitoring such as fs.watch()
TcpWrap, // [TCPWRAP] TCP socket wrap (net.Socket, etc.)
TimerWrap, // [TIMERWRAP] Internal timer wrap (low level)
TlsWrap, // [TLSWRAP] TLS socket (HTTPS, etc.)
UdpWrap, // [UDPWRAP] UDP socket wrap (dgram module)
}
+104
View File
@@ -0,0 +1,104 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#![allow(clippy::uninlined_format_args)]
use std::{fmt::Write, result::Result as StdResult};
use rquickjs::{Ctx, Exception, Result};
pub trait ResultExt<T> {
fn or_throw_msg(self, ctx: &Ctx, msg: &str) -> Result<T>;
fn or_throw_range(self, ctx: &Ctx, msg: &str) -> Result<T>;
fn or_throw_type(self, ctx: &Ctx, msg: &str) -> Result<T>;
fn or_throw(self, ctx: &Ctx) -> Result<T>;
}
pub trait OptionExt<T> {
fn and_then_ok<U, E, F>(self, f: F) -> StdResult<Option<U>, E>
where
F: FnOnce(T) -> StdResult<Option<U>, E>;
fn unwrap_or_else_ok<E, F>(self, f: F) -> StdResult<T, E>
where
F: FnOnce() -> StdResult<T, E>;
}
impl<T, E: std::fmt::Display> ResultExt<T> for StdResult<T, E> {
fn or_throw_msg(self, ctx: &Ctx, msg: &str) -> Result<T> {
self.map_err(|e| {
let mut message = String::with_capacity(100);
message.push_str(msg);
message.push_str(". ");
write!(message, "{}", e).unwrap();
Exception::throw_message(ctx, &message)
})
}
fn or_throw_range(self, ctx: &Ctx, msg: &str) -> Result<T> {
self.map_err(|e| {
let mut message = String::with_capacity(100);
if !message.is_empty() {
message.push_str(msg);
message.push_str(". ");
}
write!(message, "{}", e).unwrap();
Exception::throw_range(ctx, &message)
})
}
fn or_throw_type(self, ctx: &Ctx, msg: &str) -> Result<T> {
self.map_err(|e| {
let mut message = String::with_capacity(100);
if !msg.is_empty() {
message.push_str(msg);
message.push_str(". ");
}
write!(message, "{}", e).unwrap();
Exception::throw_type(ctx, &message)
})
}
fn or_throw(self, ctx: &Ctx) -> Result<T> {
self.map_err(|err| Exception::throw_message(ctx, &err.to_string()))
}
}
impl<T> ResultExt<T> for Option<T> {
fn or_throw_msg(self, ctx: &Ctx, msg: &str) -> Result<T> {
self.ok_or_else(|| Exception::throw_message(ctx, msg))
}
fn or_throw_range(self, ctx: &Ctx, msg: &str) -> Result<T> {
self.ok_or_else(|| Exception::throw_range(ctx, msg))
}
fn or_throw_type(self, ctx: &Ctx, msg: &str) -> Result<T> {
self.ok_or_else(|| Exception::throw_type(ctx, msg))
}
fn or_throw(self, ctx: &Ctx) -> Result<T> {
self.ok_or_else(|| Exception::throw_message(ctx, "Value is not present"))
}
}
impl<T> OptionExt<T> for Option<T> {
fn and_then_ok<U, E, F>(self, f: F) -> StdResult<Option<U>, E>
where
F: FnOnce(T) -> StdResult<Option<U>, E>,
{
match self {
Some(v) => f(v),
None => Ok(None),
}
}
fn unwrap_or_else_ok<E, F>(self, f: F) -> StdResult<T, E>
where
F: FnOnce() -> StdResult<T, E>,
{
match self {
Some(v) => Ok(v),
None => f(),
}
}
}
@@ -0,0 +1,337 @@
use std::fmt::Debug;
#[derive(Default, Clone)]
pub struct ReuseList<T> {
items: Vec<Option<T>>,
slots: Vec<usize>,
last_slot_idx: usize,
len: usize,
slot_size: usize,
}
impl<T: Debug> Debug for ReuseList<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReuseList")
.field("items", &self.items)
.field("slots", &self.slots)
.finish()
}
}
impl<T> ReuseList<T> {
pub fn new() -> Self {
Self::with_capacity(0)
}
//is empty
pub fn is_empty(&self) -> bool {
self.len == 0
}
//create a with capacity
pub fn with_capacity(capacity: usize) -> Self {
Self {
items: Vec::with_capacity(capacity),
slots: Vec::with_capacity(capacity >> 2),
last_slot_idx: 0,
len: 0,
slot_size: 0,
}
}
pub fn append(&mut self, item: T) -> usize {
if self.slot_size > 0 {
//reuse empty slot if valid
let slot = self.slots[self.last_slot_idx - 1];
if slot > 0 {
self.items[slot - 1] = Some(item);
self.slots[self.last_slot_idx - 1] = 0;
if self.last_slot_idx > 1 {
self.last_slot_idx -= 1;
}
self.len += 1;
return slot - 1;
}
}
//no valid empty slots, append to end
self.items.push(Some(item));
self.len += 1;
self.items.len() - 1
}
pub fn remove(&mut self, index: usize) -> Option<T> {
if index >= self.items.len() {
return None;
}
let item = self.items[index].take();
if item.is_some() {
if self.slot_size > 0 && self.slots[self.last_slot_idx - 1] == 0 {
self.slots[self.last_slot_idx - 1] = index + 1;
} else {
self.slots.push(index + 1);
self.last_slot_idx += 1;
self.slot_size += 1;
}
self.len -= 1;
}
item
}
pub fn get(&self, index: usize) -> Option<&T> {
if index >= self.items.len() {
None
} else {
self.items[index].as_ref()
}
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
if index >= self.items.len() {
None
} else {
self.items[index].as_mut()
}
}
pub fn capacity(&self) -> usize {
self.items.capacity()
}
pub fn len(&self) -> usize {
self.len
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.items.iter().filter_map(|x| x.as_ref())
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.items.iter_mut().filter_map(|x| x.as_mut())
}
//implement clear
pub fn clear(&mut self) {
self.items.clear();
self.slots.clear();
self.last_slot_idx = 0;
self.len = 0;
self.slot_size = 0;
}
pub fn optimize(&mut self) {
let mut new_items = Vec::with_capacity(self.len);
for item in self.items.iter_mut() {
let a = item.take();
if a.is_some() {
new_items.push(a);
}
}
self.items = new_items;
self.slots.clear();
self.last_slot_idx = 0;
self.slot_size = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let list: ReuseList<i32> = ReuseList::new();
assert_eq!(list.len(), 0);
assert_eq!(list.capacity(), 0);
assert_eq!(list.items.len(), 0);
assert_eq!(list.slots.len(), 0);
}
#[test]
fn test_with_capacity() {
let list: ReuseList<i32> = ReuseList::with_capacity(10);
assert_eq!(list.len(), 0);
assert_eq!(list.capacity(), 10);
assert_eq!(list.items.len(), 0);
assert_eq!(list.slots.len(), 0);
}
#[test]
fn test_append() {
let mut list = ReuseList::new();
assert_eq!(list.append(1), 0);
assert_eq!(list.append(2), 1);
assert_eq!(list.append(3), 2);
assert_eq!(list.len(), 3);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![1, 2, 3]);
assert_eq!(list.items, vec![Some(1), Some(2), Some(3)]);
assert_eq!(list.slots, vec![]);
}
#[test]
fn test_remove() {
let mut list = ReuseList::new();
list.append(1);
list.append(2);
list.append(3);
assert_eq!(list.remove(1), Some(2));
assert_eq!(list.len(), 2);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![1, 3]);
assert_eq!(list.items, vec![Some(1), None, Some(3)]);
assert_eq!(list.slots, vec![2]);
assert_eq!(list.remove(5), None);
}
#[test]
fn test_reuse_slots() {
let mut list = ReuseList::new();
list.append(1);
list.append(2);
list.append(3);
list.remove(1); // Remove 2
assert_eq!(list.append(4), 1); // Should reuse index 1
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![1, 4, 3]);
assert_eq!(list.items, vec![Some(1), Some(4), Some(3)]);
assert_eq!(list.slots, vec![0]);
}
#[test]
fn test_get() {
let mut list = ReuseList::new();
list.append(1);
list.append(2);
assert_eq!(list.get(0), Some(&1));
assert_eq!(list.get(1), Some(&2));
assert_eq!(list.get(2), None);
assert_eq!(list.items, vec![Some(1), Some(2)]);
assert_eq!(list.slots, vec![]);
}
#[test]
fn test_get_mut() {
let mut list = ReuseList::new();
list.append(1);
list.append(2);
if let Some(value) = list.get_mut(0) {
*value = 10;
}
assert_eq!(list.get(0), Some(&10));
assert_eq!(list.items, vec![Some(10), Some(2)]);
assert_eq!(list.slots, vec![]);
}
#[test]
fn test_iter() {
let mut list = ReuseList::new();
list.append(1);
list.append(2);
list.append(3);
list.remove(1);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![1, 3]);
assert_eq!(list.items, vec![Some(1), None, Some(3)]);
assert_eq!(list.slots, vec![2]);
}
#[test]
fn test_iter_mut() {
let mut list = ReuseList::new();
list.append(1);
list.append(2);
list.append(3);
for item in list.iter_mut() {
*item *= 2;
}
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![2, 4, 6]);
assert_eq!(list.items, vec![Some(2), Some(4), Some(6)]);
assert_eq!(list.slots, vec![]);
}
#[test]
fn test_multiple_removes() {
let mut list = ReuseList::new();
for i in 0..5 {
list.append(i);
}
list.remove(1);
list.remove(3);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![0, 2, 4]);
assert_eq!(list.items, vec![Some(0), None, Some(2), None, Some(4)]);
assert_eq!(list.slots, vec![2, 4]);
// Test reuse of both slots
list.append(10);
list.append(11);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![0, 11, 2, 10, 4]);
assert_eq!(
list.items,
vec![Some(0), Some(11), Some(2), Some(10), Some(4)]
);
assert_eq!(list.slots, vec![0, 0]);
list.remove(0);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![11, 2, 10, 4]);
assert_eq!(list.items, vec![None, Some(11), Some(2), Some(10), Some(4)]);
assert_eq!(list.slots, vec![1, 0]);
list.append(20);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![20, 11, 2, 10, 4]);
assert_eq!(
list.items,
vec![Some(20), Some(11), Some(2), Some(10), Some(4)]
);
assert_eq!(list.slots, vec![0, 0]);
//remove all items
list.clear();
}
#[test]
fn test_optimize() {
let mut list = ReuseList::new();
list.append(1);
list.append(2);
list.append(3);
list.remove(1);
assert_eq!(list.items, vec![Some(1), None, Some(3)]);
assert_eq!(list.slots, vec![2]);
list.optimize();
assert_eq!(list.items, vec![Some(1), Some(3)]);
assert_eq!(list.slots, vec![]);
assert_eq!(list.last_slot_idx, 0);
assert_eq!(list.slot_size, 0);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![1, 3]);
}
}
+149
View File
@@ -0,0 +1,149 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use rquickjs::{prelude::Opt, Ctx, Exception, Result, Value};
use crate::result::ResultExt;
use std::io;
#[cfg(unix)]
macro_rules! generate_signal_from_str_fn {
($($signal:path),*) => {
pub fn signal_from_str(signal: &str) -> Option<i32> {
let signal = ["libc::", signal].concat();
match signal.as_str() {
$(stringify!($signal) => Some($signal),)*
_ => None,
}
}
pub fn signal_str_from_i32(signal: i32) -> Option<&'static str> {
$(if signal == $signal {
return Some(&stringify!($signal)[6..]);
})*
None
}
};
}
#[cfg(unix)]
generate_signal_from_str_fn!(
libc::SIGHUP,
libc::SIGINT,
libc::SIGQUIT,
libc::SIGILL,
libc::SIGABRT,
libc::SIGFPE,
libc::SIGKILL,
libc::SIGSEGV,
libc::SIGPIPE,
libc::SIGALRM,
libc::SIGTERM
);
#[cfg(not(unix))]
static WINDOWS_SIGTERM: i32 = -1;
pub fn parse_signal(signal: Option<Value<'_>>) -> Result<i32> {
let Some(val) = signal else {
#[cfg(unix)]
return Ok(libc::SIGTERM);
#[cfg(not(unix))]
return Ok(WINDOWS_SIGTERM);
};
if let Some(num) = val.as_number() {
let sig = num as i32;
#[cfg(unix)]
return Ok(sig);
// On Windows: 0 checks existence, anything else kills
#[cfg(not(unix))]
return Ok(if sig == 0 { 0 } else { WINDOWS_SIGTERM });
}
if let Some(str_val) = val.as_string() {
let s = str_val.to_string()?;
#[cfg(unix)]
let mapped_sig = signal_from_str(&s);
#[cfg(not(unix))]
let mapped_sig = match s.as_str() {
"SIGINT" | "SIGTERM" | "SIGKILL" | "SIGQUIT" | "SIGHUP" | "SIGUSR1" => {
Some(WINDOWS_SIGTERM)
},
_ => None,
};
return match mapped_sig {
Some(sig) => Ok(sig),
None => Err(Exception::throw_type(
val.ctx(),
&format!("Unknown signal: {}", s),
)),
};
}
Err(Exception::throw_type(val.ctx(), "Invalid signal type"))
}
#[cfg(unix)]
pub fn kill_process_raw(pid: u32, signal: i32) -> io::Result<()> {
// libc::kill returns 0 on success, -1 on error
// SAFETY: kill is a safe system call as long as the signal value is valid, which is ensured by parse_signal
if unsafe { libc::kill(pid as i32, signal) } == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
#[cfg(windows)]
pub fn kill_process_raw(pid: u32, signal: i32) -> io::Result<()> {
use windows_sys::Win32::Foundation::CloseHandle;
use windows_sys::Win32::System::Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE};
// SAFETY: OpenProcess is safe to call with valid parameters, and PROCESS_TERMINATE is a valid access right
let handle = unsafe { OpenProcess(PROCESS_TERMINATE, 0, pid) };
if handle == std::ptr::null_mut() {
return Err(io::Error::last_os_error());
}
let result = if signal == 0 {
Ok(())
} else {
// SAFETY: TerminateProcess is safe to call with a valid process handle obtained from OpenProcess
if unsafe { TerminateProcess(handle, 1) } != 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
};
// SAFETY: CloseHandle is safe to call with a valid handle obtained from OpenProcess
unsafe { CloseHandle(handle) };
result
}
pub fn kill(ctx: &Ctx<'_>, pid: u32, signal: Opt<Value<'_>>) -> Result<bool> {
let signal = parse_signal(signal.0)?;
kill_process_raw(pid, signal)
.map(|_| true)
.or_else(|e| {
// Handle "Process Not Found" / "Existence Check" logic
// If signal is 0 (check existence) and we hit a specific error, return Ok(false).
#[cfg(unix)]
let is_not_found = e.raw_os_error() == Some(libc::ESRCH); // Error 3
#[cfg(windows)]
let is_not_found = true; // On Windows, any OpenProcess failure during check implies "not found" (or not accessible)
if signal == 0 && is_not_found {
Ok(false)
} else {
Err(e)
}
})
.or_throw(ctx)
}
+17
View File
@@ -0,0 +1,17 @@
use rquickjs::{Coerced, Result, Value};
#[inline]
pub fn get_string(value: &Value<'_>) -> Result<Option<String>> {
if let Some(val) = value.as_string() {
let string = val.to_string()?;
return Ok(Some(string));
}
Ok(None)
}
pub fn get_coerced_string(value: &Value<'_>) -> Option<String> {
if let Ok(val) = value.get::<Coerced<String>>() {
return Some(val.0);
};
None
}
+13
View File
@@ -0,0 +1,13 @@
#[cfg(target_os = "macos")]
pub const PLATFORM: &str = "darwin";
#[cfg(target_os = "windows")]
pub const PLATFORM: &str = "win32";
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
pub const PLATFORM: &str = std::env::consts::OS;
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
pub const ARCH: &str = "x64";
#[cfg(target_arch = "aarch64")]
pub const ARCH: &str = "arm64";
#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))]
pub const ARCH: &str = std::env::consts::ARCH;
+47
View File
@@ -0,0 +1,47 @@
use std::{
sync::atomic::{AtomicU64, Ordering},
time::SystemTime,
};
static TIME_ORIGIN: AtomicU64 = AtomicU64::new(0);
/// Get the current time in nanoseconds.
///
/// # Safety
/// - Good until the year 2554
/// - Always use a checked substraction since this can return 0
pub fn now_nanos() -> u64 {
SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64
}
/// Get the current time in millis.
///
/// # Safety
/// - Good until the year 2554
/// - Always use a checked substraction since this can return 0
pub fn now_millis() -> i64 {
SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as i64
}
/// Get the origin time in nanoseconds.
///
/// # Safety
/// - Good until the year 2554
/// - Always use a checked substraction since this can return 0
pub fn origin_nanos() -> u64 {
TIME_ORIGIN.load(Ordering::Relaxed)
}
// For accuracy reasons, this function should be executed when the vm is initialized
pub fn init() {
if TIME_ORIGIN.load(Ordering::Relaxed) == 0 {
let time_origin = now_nanos();
TIME_ORIGIN.store(time_origin, Ordering::Relaxed)
}
}
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "c99675e50201d74554d0f667094f800e1287001b"
},
"path_in_vcs": "sys"
}
@@ -0,0 +1 @@
target
+319
View File
@@ -0,0 +1,319 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "bindgen"
version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "cc"
version = "1.2.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "env_logger"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "humantime"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
[[package]]
name = "is-terminal"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
]
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "libloading"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
"windows-link",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "pretty_env_logger"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c"
dependencies = [
"env_logger",
"log",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "rquickjs-sys"
version = "0.11.0"
dependencies = [
"bindgen",
"cc",
"pretty_env_logger",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
+68
View File
@@ -0,0 +1,68 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "rquickjs-sys"
version = "0.11.0"
authors = ["Mees Delzenne <mees.delzenne@gmail.com>"]
build = "build.rs"
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "QuickJS bindings for rquickjs"
readme = "README.md"
keywords = [
"quickjs",
"ecmascript",
"javascript",
"es6",
"es2020",
]
categories = ["external-ffi-bindings"]
license = "MIT"
repository = "https://github.com/DelSkayn/rquickjs.git"
[features]
bindgen = ["bindgen-rs"]
disable-assertions = []
dump-atoms = []
dump-bytecode = []
dump-free = []
dump-gc = []
dump-gc-free = []
dump-leaks = []
dump-mem = []
dump-module-resolve = []
dump-objects = []
dump-promise = []
dump-read-object = []
dump-shapes = []
logging = ["pretty_env_logger"]
update-bindings = ["bindgen"]
[lib]
name = "rquickjs_sys"
path = "src/lib.rs"
[build-dependencies.bindgen-rs]
version = "0.72"
optional = true
package = "bindgen"
[build-dependencies.cc]
version = "1"
[build-dependencies.pretty_env_logger]
version = "0.5"
optional = true
+48
View File
@@ -0,0 +1,48 @@
[package]
name = "rquickjs-sys"
version = "0.11.0"
authors = ["Mees Delzenne <mees.delzenne@gmail.com>"]
edition = "2021"
license = "MIT"
readme = "README.md"
description = "QuickJS bindings for rquickjs"
keywords = ["quickjs", "ecmascript", "javascript", "es6", "es2020"]
categories = ["external-ffi-bindings"]
repository = "https://github.com/DelSkayn/rquickjs.git"
[build-dependencies]
cc = "1"
[build-dependencies.bindgen-rs]
package = "bindgen"
version = "0.72"
optional = true
[build-dependencies.pretty_env_logger]
version = "0.5"
optional = true
[features]
bindgen = ["bindgen-rs"]
# Debug logging
logging = ["pretty_env_logger"]
# Special case for updating bundled bindings
update-bindings = ["bindgen"]
# Enable QuickJS dumps
dump-bytecode = []
dump-gc = []
dump-gc-free = []
dump-free = []
dump-leaks = []
dump-mem = []
dump-objects = []
dump-atoms = []
dump-shapes = []
dump-module-resolve = []
dump-promise = []
dump-read-object = []
# Disable QuickJS assertions for better performance
disable-assertions = []
+28
View File
@@ -0,0 +1,28 @@
patch = all #hotfix msvc exports
$(info patch = $(patch))
ifneq ($(filter hotfix all,$(patch)),)
PATCHES += get_function_proto
PATCHES += check_stack_overflow
endif
# apply patches
apply:
@cd quickjs $(foreach PATCH,$(PATCHES), && echo '*** $(PATCH) ***' && patch -p1 < ../patches/$(PATCH).patch)
# stage changes
stage:
@cd quickjs && git add *
# show unstaged diff
diff:
@cd quickjs && git diff -w
# update patch from unstaged changes
update.%:
@cd quickjs && git diff -w > ../patches/$*.patch
# reset patches
reset:
@cd quickjs && git reset --hard HEAD
+24
View File
@@ -0,0 +1,24 @@
# rquickjs-sys
[![github](https://img.shields.io/badge/github-delskayn/rquickjs-8da0cb.svg?style=for-the-badge&logo=github)](https://github.com/DelSkayn/rquickjs)
[![crates](https://img.shields.io/crates/v/rquickjs.svg?style=for-the-badge&color=fc8d62&logo=rust)](https://crates.io/crates/rquickjs-sys)
[![docs](https://img.shields.io/badge/docs.rs-rquickjs-66c2a5?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K)](https://docs.rs/rquickjs-sys)
[![status](https://img.shields.io/github/actions/workflow/status/DelSkayn/rquickjs/ci.yml?branch=master&style=for-the-badge&logo=github-actions&logoColor=white)](https://github.com/DelSkayn/rquickjs/actions?query=workflow%3ARust)
This crate is a low level unsafe raw bindings for the [QuickJS](https://bellard.org/quickjs/) JavaScript engine.
__NOTE:__ Usually you shouldn't use this crate directly, instead use [rquickjs](https://crates.io/crates/rquickjs) crate which provides high level safe bindings.
## Patches
In order to fix bugs and get support for some unimplemented features the series of patches applies to released sources.
Hot fixes:
- Fix for _check stack overflow_ (important for Rust)
- Atomic support for `JS_NewClassID` (important for Rust)
- Infinity handling (replacement `1.0 / 0.0` to `INFINITY` constant)
Special patches:
- Reading module exports (`exports` feature)
- Reset stack function (`parallel` feature)
- MSVC support
+372
View File
@@ -0,0 +1,372 @@
#![allow(clippy::uninlined_format_args)]
use std::{
env, fs,
path::{Path, PathBuf},
process::{self},
};
// WASI logic lifted from https://github.com/bytecodealliance/javy/blob/61616e1507d2bf896f46dc8d72687273438b58b2/crates/quickjs-wasm-sys/build.rs#L18
const WASI_SDK_VERSION_MAJOR: usize = 24;
const WASI_SDK_VERSION_MINOR: usize = 0;
fn download_wasi_sdk() -> PathBuf {
let mut wasi_sdk_dir: PathBuf = env::var("OUT_DIR").unwrap().into();
wasi_sdk_dir.push("wasi-sdk");
fs::create_dir_all(&wasi_sdk_dir).unwrap();
let major_version = WASI_SDK_VERSION_MAJOR;
let minor_version = WASI_SDK_VERSION_MINOR;
let mut archive_path = wasi_sdk_dir.clone();
archive_path.push(format!("wasi-sdk-{major_version}-{minor_version}.tar.gz"));
println!("SDK tar: {archive_path:?}");
// Download archive if necessary
if !archive_path.try_exists().unwrap() {
let file_suffix = match (env::consts::OS, env::consts::ARCH) {
("linux", "x86") | ("linux", "x86_64") => "x86_64-linux",
("linux", "aarch64") => "arm64-linux",
("macos", "x86") | ("macos", "x86_64") => "x86_64-macos",
("macos", "aarch64") => "arm64-macos",
("windows", "x86") | ("windows", "x86_64") => "x86_64-windows",
("windows", "aarch64") => "arm64-windows",
other => panic!("Unsupported platform tuple {:?}", other),
};
let uri = format!("https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-{major_version}/wasi-sdk-{major_version}.{minor_version}-{file_suffix}.tar.gz");
println!("Downloading WASI SDK archive from {uri} to {archive_path:?}");
let output = process::Command::new("curl")
.args([
"--location",
"-o",
archive_path.to_string_lossy().as_ref(),
uri.as_ref(),
])
.output()
.expect("failed to download the WASI SDK with curl");
println!("curl output: {}", String::from_utf8_lossy(&output.stdout));
println!("curl err: {}", String::from_utf8_lossy(&output.stderr));
if !output.status.success() {
panic!(
"curl WASI SDK failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
}
let mut test_binary = wasi_sdk_dir.clone();
test_binary.extend(["bin", "wasm-ld"]);
// Extract archive if necessary
if !test_binary.try_exists().unwrap() {
println!("Extracting WASI SDK archive {archive_path:?}");
let output = process::Command::new("tar")
.args([
"-zxf",
archive_path.to_string_lossy().as_ref(),
"--strip-components",
"1",
])
.current_dir(&wasi_sdk_dir)
.output()
.unwrap();
if !output.status.success() {
panic!(
"Unpacking WASI SDK failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
}
wasi_sdk_dir
}
fn get_wasi_sdk_path() -> PathBuf {
std::env::var_os("WASI_SDK")
.map(PathBuf::from)
.unwrap_or_else(download_wasi_sdk)
}
fn main() {
#[cfg(feature = "logging")]
pretty_env_logger::init();
let features = [
"bindgen",
"update-bindings",
"dump-bytecode",
"dump-gc",
"dump-gc-free",
"dump-free",
"dump-leaks",
"dump-mem",
"dump-objects",
"dump-atoms",
"dump-shapes",
"dump-module-resolve",
"dump-promise",
"dump-read-object",
"disable-assertions",
];
for feature in &features {
println!("cargo:rerun-if-env-changed={}", feature_to_cargo(feature));
}
println!("cargo:rerun-if-env-changed=CARGO_CFG_SANITIZE");
println!("cargo:rerun-if-changed=quickjs");
let src_dir = Path::new("quickjs");
let out_dir = env::var("OUT_DIR").expect("No OUT_DIR env var is set by cargo");
let out_dir = Path::new(&out_dir);
let header_files = [
"builtin-array-fromasync.h",
"dtoa.h",
"libregexp-opcode.h",
"libregexp.h",
"libunicode-table.h",
"libunicode.h",
"list.h",
"quickjs-atom.h",
"quickjs-opcode.h",
"quickjs-c-atomics.h",
"quickjs.h",
"cutils.h",
];
let source_files = [
"libregexp.c",
"libunicode.c",
"cutils.c",
"quickjs.c",
"dtoa.c",
];
let mut defines: Vec<(String, Option<&str>)> = vec![("_GNU_SOURCE".into(), None)];
#[cfg(feature = "disable-assertions")]
defines.push(("NDEBUG".into(), None));
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap();
let mut builder = cc::Build::new();
builder
.extra_warnings(false)
.flag_if_supported("-Wno-implicit-const-int-float-conversion")
//.flag("-Wno-array-bounds")
//.flag("-Wno-format-truncation")
;
match env::var("CARGO_CFG_SANITIZE").as_deref() {
Ok("address") => {
builder
.flag("-fsanitize=address")
.flag("-fno-sanitize-recover=all")
.flag("-fno-omit-frame-pointer");
}
Ok("memory") => {
builder
.flag("-fsanitize=memory")
.flag("-fno-sanitize-recover=all")
.flag("-fno-omit-frame-pointer");
}
Ok("thread") => {
builder
.flag("-fsanitize=thread")
.flag("-fno-sanitize-recover=all")
.flag("-fno-omit-frame-pointer");
}
Ok(x) => println!("cargo:warning=Unsupported sanitize_option: '{x}'"),
_ => {}
}
let mut bindgen_cflags = vec![];
if target_os == "windows" {
if target_env == "msvc" {
env::set_var(
"CFLAGS",
"/DWIN32_LEAN_AND_MEAN /std:c11 /experimental:c11atomics",
);
} else {
env::set_var("CFLAGS", "-DWIN32_LEAN_AND_MEAN -std=c11");
}
}
if target_os == "wasi" {
// pretend we're emscripten - there are already ifdefs that match
// also, wasi doesn't ahve FE_DOWNWARD or FE_UPWARD
defines.push(("EMSCRIPTEN".into(), Some("1")));
defines.push(("FE_DOWNWARD".into(), Some("0")));
defines.push(("FE_UPWARD".into(), Some("0")));
}
for file in source_files.iter().chain(header_files.iter()) {
fs::copy(src_dir.join(file), out_dir.join(file))
.expect("Unable to copy source; try 'git submodule update --init'");
}
fs::copy("quickjs.bind.h", out_dir.join("quickjs.bind.h")).expect("Unable to copy source");
// SAU-103: use cargo.build's target-specific WASI compiler/archive tools.
if target_os == "wasi" && env::var_os("CC_wasm32_wasip2").is_none() {
let wasi_sdk_path = get_wasi_sdk_path();
if !wasi_sdk_path.try_exists().unwrap() {
panic!(
"wasi-sdk not installed in specified path of {}",
wasi_sdk_path.display()
);
}
env::set_var("CC", wasi_sdk_path.join("bin/clang").to_str().unwrap());
env::set_var("AR", wasi_sdk_path.join("bin/ar").to_str().unwrap());
let sysroot = format!(
"--sysroot={}",
wasi_sdk_path.join("share/wasi-sysroot").display()
);
env::set_var("CFLAGS", &sysroot);
bindgen_cflags.push(sysroot);
}
// generating bindings
bindgen(
out_dir,
out_dir.join("quickjs.bind.h"),
&defines,
bindgen_cflags,
);
for (name, value) in &defines {
builder.define(name, *value);
}
for src in &source_files {
builder.file(out_dir.join(src));
}
let tool = builder.get_compiler();
println!("cargo:warning=SAU-103 QuickJS C compiler {:?} args {:?}", tool.path(), tool.args());
builder.compile("libquickjs.a");
}
fn feature_to_cargo(name: impl AsRef<str>) -> String {
format!("CARGO_FEATURE_{}", feature_to_define(name))
}
fn feature_to_define(name: impl AsRef<str>) -> String {
name.as_ref().to_uppercase().replace('-', "_")
}
#[cfg(not(feature = "bindgen"))]
fn bindgen<'a, D, H, X, K, V>(out_dir: D, _header_file: H, _defines: X, _add_cflags: Vec<String>)
where
D: AsRef<Path>,
H: AsRef<Path>,
X: IntoIterator<Item = &'a (K, Option<V>)>,
K: AsRef<str> + 'a,
V: AsRef<str> + 'a,
{
let target = env::var("TARGET").unwrap();
if !Path::new("./")
.join("src")
.join("bindings")
.join(format!("{}.rs", target))
.canonicalize()
.map(|x| x.exists())
.unwrap_or(false)
{
println!(
"cargo:warning=rquickjs probably doesn't ship bindings for platform `{}({})`. try the `bindgen` feature instead.",
target,
env::var("BUILD_TARGET").unwrap_or("n/a".into())
);
}
let bindings_file = out_dir.as_ref().join("bindings.rs");
fs::write(
bindings_file,
format!(
r#"macro_rules! bindings_env {{
("TARGET") => {{ "{target}" }};
}}"#
),
)
.unwrap();
}
#[cfg(feature = "bindgen")]
fn bindgen<'a, D, H, X, K, V>(out_dir: D, header_file: H, defines: X, mut add_cflags: Vec<String>)
where
D: AsRef<Path>,
H: AsRef<Path>,
X: IntoIterator<Item = &'a (K, Option<V>)>,
K: AsRef<str> + 'a,
V: AsRef<str> + 'a,
{
let mut target = env::var("TARGET").unwrap();
let out_dir = out_dir.as_ref();
let header_file = header_file.as_ref();
// *-pc-windows-gnullvm is special for Rust, Clang accepts only
// *-pc-windows-gnu
if target.ends_with("windows-gnullvm") {
target = target.replace("llvm", "");
}
let mut cflags = vec![format!("--target={}", target)];
cflags.append(&mut add_cflags);
//format!("-I{}", out_dir.parent().display()),
for (name, value) in defines {
cflags.push(if let Some(value) = value {
format!("-D{}={}", name.as_ref(), value.as_ref())
} else {
format!("-D{}", name.as_ref())
});
}
let mut builder = bindgen_rs::Builder::default()
.use_core()
.detect_include_paths(true)
.clang_arg("-xc")
.clang_arg("-v")
.clang_args(cflags)
.size_t_is_usize(false)
.header(header_file.display().to_string())
.allowlist_type("JS.*")
.allowlist_function("js.*")
.allowlist_function("JS.*")
.allowlist_function("__JS.*")
.allowlist_var("JS.*")
.opaque_type("FILE")
.blocklist_type("FILE")
.blocklist_function("JS_DumpMemoryUsage");
if env::var("CARGO_CFG_TARGET_OS").unwrap() == "wasi" {
builder = builder.clang_arg("-fvisibility=default");
}
let bindings = builder.generate().expect("Unable to generate bindings");
let bindings_file = out_dir.join("bindings.rs");
bindings
.write_to_file(&bindings_file)
.expect("Couldn't write bindings");
// Special case to support bundled bindings
if env::var("CARGO_FEATURE_UPDATE_BINDINGS").is_ok() {
let dest_dir = Path::new("src").join("bindings");
fs::create_dir_all(&dest_dir).unwrap();
let dest_file = format!("{}.rs", target);
fs::copy(&bindings_file, dest_dir.join(dest_file)).unwrap();
}
}
+16
View File
@@ -0,0 +1,16 @@
// A header which imports the all symbols of the quickjs header but also exports
// the static atoms.
#include "quickjs.h"
#if !defined(EMSCRIPTEN) && !defined(_MSC_VER)
#define CONFIG_ATOMICS
#endif
enum {
__JS_ATOM_NULL = JS_ATOM_NULL,
#define DEF(name, str) JS_ATOM_##name,
#include "quickjs-atom.h"
#undef DEF
JS_ATOM_END,
};
@@ -0,0 +1,4 @@
# Don't show changes in generated files when doing git diff
gen/** -diff
@@ -0,0 +1,7 @@
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
@@ -0,0 +1,780 @@
name: ci
on:
pull_request:
paths:
- '**'
- '!.gitignore'
- '!LICENSE'
- '!README.md'
- '!docs/**'
- '!examples/**'
- '.github/workflows/ci.yml'
push:
branches:
- master
paths:
- '**'
- '!.gitignore'
- '!LICENSE'
- '!README.md'
- '!docs/**'
- '!examples/**'
- '.github/workflows/ci.yml'
jobs:
codegen:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: build
run: |
make codegen
- name: Check if the git repository is clean
run: (exit "$(git status --porcelain --untracked-files=no | head -255 | wc -l)") || (echo "Dirty git tree"; git diff; exit 1)
ci:
runs-on: ${{ matrix.config.os }}
name: ${{ matrix.config.os }} (${{ matrix.config.configType }}${{ matrix.config.arch }})
env:
ASAN_OPTIONS: halt_on_error=1
MSAN_OPTIONS: halt_on_error=1
UBSAN_OPTIONS: halt_on_error=1
defaults:
run:
shell: ${{ matrix.config.arch == '' && 'sh' || 'alpine.sh' }} {0}
strategy:
fail-fast: false
matrix:
config:
# see https://github.com/quickjs-ng/quickjs/issues/879 - tl;dr flaky
#- { os: ubuntu-24.04-arm, configType: Debug }
#- { os: ubuntu-24.04-arm, configType: Release, runTest262: true }
- { os: ubuntu-latest, configType: Debug }
- { os: ubuntu-latest, configType: Release, runTest262: true }
- { os: ubuntu-latest, configType: examples }
- { os: ubuntu-latest, configType: shared }
- { os: ubuntu-latest, configType: asan+ubsan, runTest262: true }
- { os: ubuntu-latest, configType: msan }
- { os: ubuntu-latest, configType: tcc }
- { os: ubuntu-latest, arch: x86, runTest262: true }
- { os: ubuntu-latest, arch: riscv64 }
- { os: ubuntu-latest, arch: s390x }
- { os: macos-14, configType: Debug }
- { os: macos-14, configType: Release }
- { os: macos-14, configType: examples }
- { os: macos-14, configType: shared }
- { os: macos-14, configType: asan+ubsan }
steps:
- uses: actions/checkout@v6
with:
submodules: true
- name: install TCC
if: ${{ matrix.config.configType == 'tcc' }}
run: |
pushd /tmp
git clone https://repo.or.cz/tinycc.git
cd tinycc
git checkout c21576f8a32715ab439690d18184b0e02022bbbd
./configure
make
sudo make install
tcc -v
popd
echo "CC=tcc" >> $GITHUB_ENV;
- name: Install extra dependencies
if: ${{ matrix.config.arch != '' }}
shell: 'sh'
run: |
sudo apt update && sudo apt install -y binfmt-support
- name: Setup Alpine
if: ${{ matrix.config.arch != '' }}
uses: jirutka/setup-alpine@v1
with:
arch: ${{ matrix.config.arch }}
packages: "build-base make cmake git"
- name: uname
run: uname -a
- name: Set build ENV vars
run: |
if [ "${{ matrix.config.configType }}" = "Debug" ]; then
echo "BUILD_TYPE=Debug" >> $GITHUB_ENV;
elif [ "${{ matrix.config.configType }}" = "examples" ]; then
echo "QJS_BUILD_EXAMPLES=ON" >> $GITHUB_ENV;
elif [ "${{ matrix.config.configType }}" = "shared" ]; then
echo "BUILD_SHARED_LIBS=ON" >> $GITHUB_ENV;
elif [ "${{ matrix.config.configType }}" = "asan+ubsan" ]; then
echo "BUILD_TYPE=RelWithDebInfo" >> $GITHUB_ENV;
echo "QJS_ENABLE_ASAN=ON" >> $GITHUB_ENV;
echo "QJS_ENABLE_UBSAN=ON" >> $GITHUB_ENV;
elif [ "${{ matrix.config.configType }}" = "msan" ]; then
echo "BUILD_TYPE=RelWithDebInfo" >> $GITHUB_ENV;
echo "QJS_ENABLE_MSAN=ON" >> $GITHUB_ENV;
echo "CC=clang" >> $GITHUB_ENV;
fi
- name: build
run: |
make \
BUILD_TYPE=$BUILD_TYPE \
QJS_BUILD_EXAMPLES=$QJS_BUILD_EXAMPLES \
BUILD_SHARED_LIBS=$BUILD_SHARED_LIBS \
QJS_ENABLE_ASAN=$QJS_ENABLE_ASAN \
QJS_ENABLE_UBSAN=$QJS_ENABLE_UBSAN \
QJS_ENABLE_MSAN=$QJS_ENABLE_MSAN
- name: stats
if: ${{ matrix.config.configType != 'examples' }}
run: |
make stats
- name: cxxtest
run: |
make cxxtest
- name: test
if: ${{ matrix.config.configType != 'examples' }}
run: |
make test
- name: test examples
if: ${{ matrix.config.configType == 'examples' }}
run: |
cp build/fib.so examples/
cp build/point.so examples/
./build/qjs examples/test_fib.js
./build/qjs examples/test_point.js
./build/qjs tests/test_bjson.js
./build/function_source
- name: test 262
if: ${{ matrix.config.runTest262 }}
run: |
git submodule update --init --checkout --depth 1
make test262
- name: test standalone
run: |
./build/qjs -c examples/hello.js -o hello
./hello
- name: test api
run: |
./build/api-test
windows-msvc:
runs-on: ${{ matrix.config.os }}
strategy:
fail-fast: false
matrix:
config:
- { arch: x64, vsArch: x64, buildType: Debug, os: windows-latest }
- { arch: x64, vsArch: x64, buildType: Release, os: windows-latest }
- { arch: Win32, vsArch: x86, buildType: Debug, os: windows-latest }
- { arch: Win32, vsArch: x86, buildType: Release, os: windows-latest }
- { arch: ARM64, vsArch: arm64, buildType: Debug, os: windows-11-arm }
- { arch: ARM64, vsArch: arm64, buildType: Release, os: windows-11-arm }
steps:
- uses: actions/checkout@v6
- name: build
run: |
cmake -B build -DQJS_BUILD_EXAMPLES=ON -G "Visual Studio 17 2022" -A ${{matrix.config.arch}}
cmake --build build --config ${{matrix.config.buildType}}
- name: stats
run: |
build\${{matrix.config.buildType}}\qjs.exe -qd
- name: test
run: |
cp build\${{matrix.config.buildType}}\fib.dll examples\
cp build\${{matrix.config.buildType}}\point.dll examples\
build\${{matrix.config.buildType}}\qjs.exe examples\test_fib.js
build\${{matrix.config.buildType}}\qjs.exe examples\test_point.js
build\${{matrix.config.buildType}}\run-test262.exe -c tests.conf
build\${{matrix.config.buildType}}\function_source.exe
- name: test standalone
run: |
build\${{matrix.config.buildType}}\qjs.exe -c examples\hello.js -o hello.exe
.\hello.exe
- name: test api
run: |
build\${{matrix.config.buildType}}\api-test.exe
- name: Set up Visual Studio shell
uses: egor-tensin/vs-shell@v2
with:
arch: ${{ matrix.config.vsArch }}
- name: cxxtest
run: |
cl.exe /DJS_NAN_BOXING=0 /Zs cxxtest.cc
cl.exe /DJS_NAN_BOXING=1 /Zs cxxtest.cc
windows-clang:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
buildType: [Debug, Release]
steps:
- uses: actions/checkout@v6
- name: build
run: |
git submodule update --init --checkout --depth 1
cmake -B build -DQJS_BUILD_EXAMPLES=ON -G "Visual Studio 17 2022" -T ClangCL
cmake --build build --config ${{matrix.buildType}}
- name: stats
run: |
build\${{matrix.buildType}}\qjs.exe -qd
- name: cxxtest
run: |
clang-cl.exe /DJS_NAN_BOXING=0 /Zs cxxtest.cc
clang-cl.exe /DJS_NAN_BOXING=1 /Zs cxxtest.cc
- name: test
run: |
cp build\${{matrix.buildType}}\fib.dll examples\
cp build\${{matrix.buildType}}\point.dll examples\
build\${{matrix.buildType}}\qjs.exe examples\test_fib.js
build\${{matrix.buildType}}\qjs.exe examples\test_point.js
build\${{matrix.buildType}}\run-test262.exe -c tests.conf
build\${{matrix.buildType}}\function_source.exe
- name: test api
run: |
build\${{matrix.buildType}}\api-test.exe
windows-ninja:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
buildType: [Debug, Release]
steps:
- uses: actions/checkout@v6
- name: install ninja
run: |
choco install ninja
ninja.exe --version
- name: build
run: |
git submodule update --init --checkout --depth 1
cmake -B build -DQJS_BUILD_EXAMPLES=ON -DCMAKE_BUILD_TYPE=${{matrix.buildType}} -G "Ninja"
cmake --build build
- name: stats
run: |
build\qjs.exe -qd
- name: test
run: |
cp build\fib.dll examples\
cp build\point.dll examples\
build\qjs.exe examples\test_fib.js
build\qjs.exe examples\test_point.js
build\run-test262.exe -c tests.conf
build\function_source.exe
- name: test api
run: |
build\api-test.exe
windows-sdk:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
arch: [x64, Win32]
buildType: [Debug, Release]
steps:
- uses: actions/checkout@v6
- name: Install windows sdk
uses: ChristopheLav/windows-sdk-install@v1
with:
version-sdk: 26100
features: 'OptionId.DesktopCPPx86,OptionId.DesktopCPPx64'
- name: build
run: |
cmake -B build -DQJS_BUILD_EXAMPLES=ON -DCMAKE_SYSTEM_VERSION="10.0.26100.0" -A ${{matrix.arch}}
cmake --build build --config ${{matrix.buildType}}
- name: stats
run: |
build\${{matrix.buildType}}\qjs.exe -qd
- name: test
run: |
cp build\${{matrix.buildType}}\fib.dll examples\
cp build\${{matrix.buildType}}\point.dll examples\
build\${{matrix.buildType}}\qjs.exe examples\test_fib.js
build\${{matrix.buildType}}\qjs.exe examples\test_point.js
build\${{matrix.buildType}}\run-test262.exe -c tests.conf
build\${{matrix.buildType}}\function_source.exe
- name: test api
run: |
build\${{matrix.buildType}}\api-test.exe
windows-mingw:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
buildType: [Debug, Release]
sys:
- mingw32
- mingw64
- clang64
- ucrt64
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v6
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
update: true
msystem: ${{matrix.sys}}
install: >-
git
make
pacboy: >-
cmake:p
ninja:p
toolchain:p
- name: build
run: |
make BUILD_TYPE=${{matrix.buildType}}
- name: stats
run: |
make stats
ldd build/qjs
- name: cxxtest
if: ${{ matrix.sys != 'clang64' }}
run: |
make cxxtest
- name: cxxtest
if: ${{ matrix.sys == 'clang64' }}
run: |
make cxxtest CXX=clang++
- name: test
run: |
make test
- name: test standalone
run: |
./build/qjs -c examples/hello.js -o hello.exe
./hello
- name: test api
run: |
./build/api-test
windows-mingw-shared:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v6
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
install: >-
git
make
pacboy: >-
cmake:p
ninja:p
toolchain:p
- name: build
run: |
make BUILD_SHARED_LIBS=ON
ldd build/qjs
- name: stats
run: |
make stats
emscripten:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: mymindstorm/setup-emsdk@v14
- name: check emsdk
run: emcc -v
- name: build
run: |
emcmake cmake -B build -DQJS_BUILD_LIBC=ON
emmake make -C build qjs_wasm "-j$(getconf _NPROCESSORS_ONLN)"
- name: result
run: ls -lh build
wasi:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v6
- uses: jcbhmr/setup-wasmtime@v2
- name: setup wasi-sdk
run: |
wget -nv https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.deb -P /tmp
sudo apt install /tmp/wasi-sdk*.deb
- name: test
run: |
cmake -B build -DCMAKE_TOOLCHAIN_FILE=/opt/wasi-sdk/share/cmake/wasi-sdk.cmake
make -C build qjs_exe
wasmtime run build/qjs -qd
echo "console.log('hello wasi!');" > t.js
wasmtime run --dir . build/qjs t.js
cygwin:
runs-on: windows-latest
defaults:
run:
shell: C:\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}'
env:
CYGWIN_NOWINPATH: 1
CHERE_INVOKING: 1
steps:
- name: Set up Cygwin
uses: cygwin/cygwin-install-action@master
with:
packages: make cmake gcc-g++ bash
- uses: actions/checkout@v6
- name: build
run: make
- name: stats
run: make stats
- name: cxxtest
run: |
make cxxtest
- name: test
run: make test
- name: test api
run: |
./build/api-test
openbsd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: build + test
uses: vmactions/openbsd-vm@v1
with:
usesh: true
prepare: |
pkg_add cmake gmake
run: |
gmake
gmake stats
freebsd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: build + test
uses: vmactions/freebsd-vm@v1
with:
usesh: true
prepare: |
pkg install -y cmake gmake
run: |
gmake
gmake stats
android:
runs-on: ubuntu-latest
container: reactnativecommunity/react-native-android:v13.0
steps:
- uses: actions/checkout@v6
- name: Configure android arm64
# see build options you can use in https://developer.android.com/ndk/guides/cmake
run: |
mkdir build
cd build
"$ANDROID_HOME/cmake/3.22.1/bin/cmake" "-DCMAKE_TOOLCHAIN_FILE=$ANDROID_HOME/ndk/26.0.10792818/build/cmake/android.toolchain.cmake" -DCMAKE_BUILD_TYPE=Release -DANDROID_ABI="arm64-v8a" -DANDROID_PLATFORM=android-24 -DQJS_BUILD_LIBC=ON ..
- name: Build android arm64
run: |
"$ANDROID_HOME/cmake/3.22.1/bin/cmake" --build build --target qjs
ls -lh build
ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- name: configure
run: |
cmake -B build -GXcode -DCMAKE_SYSTEM_NAME:STRING=iOS -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED:BOOL=NO -DQJS_BUILD_LIBC=ON
- name: build
run: |
cmake --build build --config Release --target qjs
ls -lh build
mimalloc-linux:
runs-on: ubuntu-24.04
env:
QJS_BUILD_CLI_WITH_MIMALLOC: ON
MIMALLOC_SHOW_STATS: 1
steps:
- uses: actions/checkout@v6
- name: install dependencies
run: |
sudo apt update && sudo apt -y install libmimalloc-dev
- name: build
run: |
make
- name: cxxtest
run: |
make cxxtest
- name: test
run: |
make test
mimalloc-macos:
runs-on: macos-latest
env:
QJS_BUILD_CLI_WITH_STATIC_MIMALLOC: ON
MIMALLOC_SHOW_STATS: 1
steps:
- uses: actions/checkout@v6
- name: install dependencies
run: |
brew install mimalloc
- name: build
run: |
make
- name: cxxtest
run: |
make cxxtest
- name: test
run: |
make test
amalgam:
strategy:
matrix:
# TODO(bnoordhuis) test on windows
config:
- { os: ubuntu-latest }
- { os: macos-latest }
runs-on: ${{ matrix.config.os }}
steps:
- uses: actions/checkout@v6
- name: build
run: |
make
- name: create amalgamation
run: |
make amalgam
- name: build amalgamation
run: |
unzip -d "$RUNNER_TEMP" build/quickjs-amalgam.zip
cc -Wall -I. -o "$RUNNER_TEMP/run-test262.o" -c run-test262.c
cc -Wall -I/ -DQJS_BUILD_LIBC -o "$RUNNER_TEMP/quickjs-amalgam.o" -c "$RUNNER_TEMP/quickjs-amalgam.c"
cc -o "$RUNNER_TEMP/run-test262" "$RUNNER_TEMP/run-test262.o" "$RUNNER_TEMP/quickjs-amalgam.o" -lm
- name: test
run: |
make test RUN262="$RUNNER_TEMP/run-test262"
jscheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: jscheck
run: |
make jscheck
parserless:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: build
run: |
make QJS_DISABLE_PARSER=ON QJS_BUILD_EXAMPLES=ON
- name: test
run: |
./build/hello
./build/hello_module
meson:
runs-on: ${{ matrix.platform }}
name: meson on ${{ matrix.platform }} (${{ matrix.mode.name }} ${{ matrix.flavor }}, ${{ matrix.features.name }})
strategy:
fail-fast: false
matrix:
flavor:
- debug
- release
features:
- name: default
args: ""
- name: libc
args: -Dlibc=true
- name: mimalloc
args: -Dcli_mimalloc=auto
mode:
- name: default
args: -Dtests=enabled
extra_envs: {}
# Alternative compiler setups
- name: gcc
args: -Dtests=enabled
extra_envs:
CC: gcc
CXX: g++
- name: clang
args: -Dtests=enabled
extra_envs:
CC: clang
CXX: clang++
- name: sanitize
args: >-
"-Db_sanitize=address,undefined"
extra_envs: {}
# This is for MSVC, which only supports AddressSanitizer.
# https://learn.microsoft.com/en-us/cpp/sanitizers/
- name: sanitize+asanonly
args: -Db_sanitize=address
extra_envs:
ASAN_OPTIONS: report_globals=0:halt_on_error=1:abort_on_error=1:print_summary=1
- name: clang+sanitize
args: >-
"-Db_sanitize=address,undefined"
extra_envs:
CC: clang
CXX: clang++
- name: clang+msan
args: -Db_sanitize=memory
extra_envs:
CC: clang
CXX: clang++
# default clang on GitHub hosted runners is from MSYS2.
# Use Visual Studio supplied clang-cl instead.
- name: clang-cl+sanitize
args: >-
"-Db_sanitize=address,undefined"
extra_envs:
CC: clang-cl
CXX: clang-cl
platform:
- ubuntu-latest
- windows-latest
- macos-latest
exclude:
# clang-cl only makes sense on windows.
- platform: ubuntu-latest
mode:
name: clang-cl+sanitize
- platform: macos-latest
mode:
name: clang-cl+sanitize
# Use clang-cl instead of MSYS2 clang.
#
# we already tested clang+sanitize on linux,
# if this doesn't work, it should be an issue for MSYS2 team to consider.
- platform: windows-latest
mode:
name: clang
- platform: windows-latest
mode:
name: clang+sanitize
- platform: windows-latest
mode:
name: clang+msan
# MSVC-only sanitizers
- platform: ubuntu-latest
mode:
name: sanitize+asanonly
- platform: macos-latest
mode:
name: sanitize+asanonly
- platform: windows-latest
mode:
name: sanitize
# clang is the default on macos
# also gcc is an alias to clang
- platform: macos-latest
mode:
name: clang
- platform: macos-latest
mode:
name: gcc
# gcc is the default on linux
- platform: ubuntu-latest
mode:
name: gcc
# only run sanitizer tests on linux
#
# gcc/clang's codegen shouldn't massively change across platforms,
# and linux supports most of the sanitizers.
- platform: macos-latest
mode:
name: clang+sanitize
- platform: macos-latest
mode:
# macos does not support msan
name: clang+msan
- platform: macos-latest
mode:
name: sanitize
steps:
- name: Setup meson
run: |
pipx install meson ninja
- name: Install mimalloc
if: ${{ matrix.platform == 'ubuntu-latest' && matrix.features.name == 'mimalloc' }}
run: sudo apt update && sudo apt -y install libmimalloc-dev
- name: Install mimalloc
if: ${{ matrix.platform == 'macos-latest' && matrix.features.name == 'mimalloc' }}
run: brew install mimalloc
# TODO: Install mimalloc on Windows
# You need to:
# - checkout mimalloc
# - `msbuild mimalloc\ide\vs2022\mimalloc.sln`
# - not cmake, because https://github.com/microsoft/mimalloc/issues/575#issuecomment-1112723975
# - it is possible to integrate vcpkg with meson, but:
# - 1. the above issue
# - 2. the vcpkg port of mimalloc is outdated, and still broken on server 2019
# - or maintain a meson port for mimalloc (and ensure it behaves correctly)
# - Make it findable with cmake
# - not the simpler pkg-config. although there was pkg-config in PATH,
# but that was from strawberry perl and thus broken
# ... good job, microsoft :))))
- name: Checkout
uses: actions/checkout@v6
- name: Activate MSVC and Configure
if: ${{ matrix.platform == 'windows-latest' }}
env: ${{ matrix.mode.extra_envs }}
run: |
meson setup build-${{ matrix.flavor }} --buildtype=${{ matrix.flavor }} ${{ matrix.mode.args }} ${{ matrix.features.args }} --vsenv
- name: Configuring
if: ${{ matrix.platform != 'windows-latest' }}
env: ${{ matrix.mode.extra_envs }}
run: |
meson setup build-${{ matrix.flavor }} --buildtype=${{ matrix.flavor }} ${{ matrix.mode.args }} ${{ matrix.features.args }}
- name: Building
run: |
meson compile -C build-${{ matrix.flavor }}
- name: Running tests
env: ${{ matrix.mode.extra_envs }}
run: |
meson test -C build-${{ matrix.flavor }} --timeout-multiplier 5 --print-errorlogs
meson test --benchmark -C build-${{ matrix.flavor }} --timeout-multiplier 5 --print-errorlogs
- uses: actions/upload-artifact@v6
if: ${{ failure() }}
with:
name: ${{ matrix.platform }}-${{ matrix.mode.name }}-${{ matrix.features.name }}-${{ matrix.flavor }}-logs
path: build-${{ matrix.flavor }}/meson-logs
@@ -0,0 +1,49 @@
name: Docs
on:
push:
branches:
- master
paths:
- 'docs/**'
- '.github/workflows/*docs.yml'
jobs:
build:
name: Build Docusaurus
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: 'docs/.nvmrc'
- name: Install dependencies
working-directory: ./docs
run: npm install
- name: Build
working-directory: ./docs
run: npm run build
- name: Upload Build Artifact
uses: actions/upload-pages-artifact@v4
with:
path: docs/build
deploy:
name: Deploy to GitHub Pages
needs: build
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
permissions:
pages: write # to deploy to Pages
id-token: write # to verify the deployment originates from an appropriate source
# Deploy to the github-pages environment
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
@@ -0,0 +1,155 @@
name: release
on:
pull_request:
push:
tags:
- "v*.*.*"
jobs:
check_meson_version:
runs-on: ubuntu-22.04
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
steps:
- uses: actions/checkout@v6
- name: install meson and ninja
run: |
pip install --upgrade meson ninja
- name: check meson version
run: |
MESON_VERSION=$(meson introspect meson.build --projectinfo | jq -r '.version')
test "${{ github.ref_name }}" = "v${MESON_VERSION}"
linux:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
arch: [aarch64, riscv64, x86, x86_64]
steps:
- uses: actions/checkout@v6
- uses: jirutka/setup-alpine@v1
with:
arch: ${{matrix.arch}}
packages: "build-base make cmake"
- name: build
shell: alpine.sh {0}
run: |
mkdir build
cd build
cmake -DQJS_BUILD_CLI_STATIC=ON ..
cd ..
cmake --build build --target qjs_exe -j$(getconf _NPROCESSORS_ONLN)
cmake --build build --target qjsc -j$(getconf _NPROCESSORS_ONLN)
mv build/qjs build/qjs-linux-${{matrix.arch}}
mv build/qjsc build/qjsc-linux-${{matrix.arch}}
- name: check
shell: alpine.sh {0}
run: |
file build/*-linux-${{matrix.arch}}
- name: upload
uses: actions/upload-artifact@v6
with:
name: qjs-linux-${{matrix.arch}}
path: build/*-linux-${{matrix.arch}}
macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- name: build
run: |
mkdir build
cd build
cmake -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" ..
make -j$(getconf _NPROCESSORS_ONLN)
make -C .. amalgam # writes build/quickjs-amalgam.zip
mv qjs qjs-darwin
mv qjsc qjsc-darwin
- name: check
run: |
lipo -info build/qjs-darwin build/qjsc-darwin
- name: upload amalgamation
uses: actions/upload-artifact@v6
with:
name: quickjs-amalgam.zip
path: build/quickjs-amalgam.zip
compression-level: 0 # already compressed
- name: upload
uses: actions/upload-artifact@v6
with:
name: qjs-darwin
path: build/*-darwin
windows:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
arch: [x86, x86_64]
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v6
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.arch == 'x86' && 'mingw32' || 'ucrt64' }}
install: >-
git
make
pacboy: >-
cmake:p
ninja:p
toolchain:p
- name: build
run: |
make
mv build/qjs.exe build/qjs-windows-${{matrix.arch}}.exe
mv build/qjsc.exe build/qjsc-windows-${{matrix.arch}}.exe
- name: check
run: |
file build/qjs-windows-${{matrix.arch}}.exe
ldd build/qjs-windows-${{matrix.arch}}.exe build/qjsc-windows-${{matrix.arch}}.exe
- name: upload
uses: actions/upload-artifact@v6
with:
name: qjs-windows-${{matrix.arch}}
path: build/*-windows-${{matrix.arch}}.exe
wasi:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v6
- name: setup wasi-sdk
run: |
wget -nv https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.deb -P /tmp
sudo apt install /tmp/wasi-sdk*.deb
- name: build
run: |
cmake -B build -DCMAKE_TOOLCHAIN_FILE=/opt/wasi-sdk/share/cmake/wasi-sdk.cmake
make -C build qjs_exe
mv build/qjs build/qjs-wasi.wasm
- name: upload
uses: actions/upload-artifact@v6
with:
name: qjs-wasi
path: build/qjs-wasi.wasm
upload-to-release:
needs: [linux, macos, windows, wasi, check_meson_version]
runs-on: ubuntu-22.04
steps:
- name: get assets
uses: actions/download-artifact@v7
with:
path: build
merge-multiple: true
- run: ls -R build
- name: release
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
uses: softprops/action-gh-release@v2
with:
files: |
build/*
@@ -0,0 +1,23 @@
name: Test Docs
on:
pull_request:
paths:
- 'docs/**'
- '.github/workflows/*docs.yml'
jobs:
test-docs:
name: Test Docusaurus build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: 'docs/.nvmrc'
- name: Install dependencies
working-directory: ./docs
run: npm install
- name: Build
working-directory: ./docs
run: npm run build
@@ -0,0 +1,32 @@
name: tsan
on:
push:
branches:
- master
paths:
- '**'
- '!.gitignore'
- '!LICENSE'
- '!README.md'
- '!docs/**'
- '!examples/**'
- '.github/workflows/tsan.yml'
- '.github/workflows/valgrind.yml'
workflow_dispatch:
jobs:
linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: true
- name: build
run: |
cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DQJS_ENABLE_TSAN=ON
cmake --build build -j`nproc`
- name: test
run: |
git submodule update --init --checkout --depth 1
./build/run-test262 -m -c test262.conf -c test262-fast.conf -a
@@ -0,0 +1,33 @@
name: valgrind
on:
push:
branches:
- master
paths:
- '**'
- '!.gitignore'
- '!LICENSE'
- '!README.md'
- '!docs/**'
- '!examples/**'
- '.github/workflows/tsan.yml'
- '.github/workflows/valgrind.yml'
workflow_dispatch:
jobs:
linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: true
- name: install valgrind
run: sudo apt-get update && sudo apt-get install valgrind
- name: build
run: |
make BUILD_TYPE=RelWithDebInfo
- name: test
run: |
git submodule update --init --checkout --depth 1
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --error-exitcode=1 ./build/run-test262 -m -c test262.conf -c test262-fast.conf -a
@@ -0,0 +1,14 @@
*.a
*.orig
*.so
.obj/
build/
unicode/
.idea
cmake-*
.vs
out/
CMakeUserPresets.json
fuzz
.vscode/
microbench*.txt
@@ -0,0 +1,5 @@
[submodule "test262"]
path = test262
url = https://github.com/tc39/test262
shallow = true
update = none
@@ -0,0 +1,461 @@
cmake_minimum_required(VERSION 3.10)
project(quickjs LANGUAGES C)
include(CheckCCompilerFlag)
include(GNUInstallDirs)
set(CMAKE_C_VISIBILITY_PRESET hidden)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS ON)
set(CMAKE_C_STANDARD 11)
# MINGW doesn't exist in older cmake versions, newer versions don't know
# about CMAKE_COMPILER_IS_MINGW, and there is no unique CMAKE_C_COMPILER_ID
# for mingw-based compilers...
if(MINGW)
# do nothing
elseif(CMAKE_C_COMPILER MATCHES "mingw")
set(MINGW TRUE)
else()
set(MINGW FALSE)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "tvOS")
set(TVOS TRUE)
else()
set(TVOS FALSE)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "watchOS")
set(WATCHOS TRUE)
else()
set(WATCHOS FALSE)
endif()
if(NOT CMAKE_BUILD_TYPE)
message(STATUS "No build type selected, default to Release")
set(CMAKE_BUILD_TYPE "Release")
endif()
message(STATUS "Building in ${CMAKE_BUILD_TYPE} mode")
message(STATUS "Building with ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION} on ${CMAKE_SYSTEM}")
macro(xcheck_add_c_compiler_flag FLAG)
string(REPLACE "-" "" FLAG_NO_HYPHEN ${FLAG})
check_c_compiler_flag(${FLAG} COMPILER_SUPPORTS_${FLAG_NO_HYPHEN})
if(COMPILER_SUPPORTS_${FLAG_NO_HYPHEN})
add_compile_options(${FLAG})
endif()
endmacro()
xcheck_add_c_compiler_flag(-Wall)
if(NOT MSVC AND NOT IOS AND NOT TVOS AND NOT WATCHOS)
xcheck_add_c_compiler_flag(-Werror)
xcheck_add_c_compiler_flag(-Wextra)
endif()
xcheck_add_c_compiler_flag(-Wformat=2)
xcheck_add_c_compiler_flag(-Wno-implicit-fallthrough)
xcheck_add_c_compiler_flag(-Wno-sign-compare)
xcheck_add_c_compiler_flag(-Wno-missing-field-initializers)
xcheck_add_c_compiler_flag(-Wno-unused-parameter)
xcheck_add_c_compiler_flag(-Wno-unused-but-set-variable)
xcheck_add_c_compiler_flag(-Wno-unused-result)
xcheck_add_c_compiler_flag(-Wno-stringop-truncation)
xcheck_add_c_compiler_flag(-Wno-array-bounds)
xcheck_add_c_compiler_flag(-funsigned-char)
# ClangCL is command line compatible with MSVC, so 'MSVC' is set.
if(MSVC)
xcheck_add_c_compiler_flag(-Wno-unsafe-buffer-usage)
xcheck_add_c_compiler_flag(-Wno-sign-conversion)
xcheck_add_c_compiler_flag(-Wno-nonportable-system-include-path)
xcheck_add_c_compiler_flag(-Wno-implicit-int-conversion)
xcheck_add_c_compiler_flag(-Wno-shorten-64-to-32)
xcheck_add_c_compiler_flag(-Wno-reserved-macro-identifier)
xcheck_add_c_compiler_flag(-Wno-reserved-identifier)
xcheck_add_c_compiler_flag(-Wdeprecated-declarations)
xcheck_add_c_compiler_flag(/experimental:c11atomics)
xcheck_add_c_compiler_flag(/wd4018) # -Wno-sign-conversion
xcheck_add_c_compiler_flag(/wd4061) # -Wno-implicit-fallthrough
xcheck_add_c_compiler_flag(/wd4100) # -Wno-unused-parameter
xcheck_add_c_compiler_flag(/wd4200) # -Wno-zero-length-array
xcheck_add_c_compiler_flag(/wd4242) # -Wno-shorten-64-to-32
xcheck_add_c_compiler_flag(/wd4244) # -Wno-shorten-64-to-32
xcheck_add_c_compiler_flag(/wd4245) # -Wno-sign-compare
xcheck_add_c_compiler_flag(/wd4267) # -Wno-shorten-64-to-32
xcheck_add_c_compiler_flag(/wd4388) # -Wno-sign-compare
xcheck_add_c_compiler_flag(/wd4389) # -Wno-sign-compare
xcheck_add_c_compiler_flag(/wd4456) # Hides previous local declaration
xcheck_add_c_compiler_flag(/wd4457) # Hides function parameter
xcheck_add_c_compiler_flag(/wd4710) # Function not inlined
xcheck_add_c_compiler_flag(/wd4711) # Function was inlined
xcheck_add_c_compiler_flag(/wd4820) # Padding added after construct
xcheck_add_c_compiler_flag(/wd4996) # -Wdeprecated-declarations
xcheck_add_c_compiler_flag(/wd5045) # Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified
endif()
# Set a 8MB default stack size on Windows.
# It defaults to 1MB on MSVC, which is the same as our current JS stack size,
# so it will overflow and crash otherwise.
# On MinGW it defaults to 2MB.
if(WIN32)
if(MSVC)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:8388608")
else()
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--stack,8388608")
endif()
endif()
# MacOS and GCC 11 or later need -Wno-maybe-uninitialized
# https://github.com/quickjs-ng/quickjs/issues/453
if(APPLE AND CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 11)
xcheck_add_c_compiler_flag(-Wno-maybe-uninitialized)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "WASI")
add_compile_definitions(
_WASI_EMULATED_PROCESS_CLOCKS
_WASI_EMULATED_SIGNAL
)
add_link_options(
-lwasi-emulated-process-clocks
-lwasi-emulated-signal
)
endif()
if(CMAKE_BUILD_TYPE MATCHES "Debug")
xcheck_add_c_compiler_flag(/Od)
xcheck_add_c_compiler_flag(-O0)
xcheck_add_c_compiler_flag(-ggdb)
xcheck_add_c_compiler_flag(-fno-omit-frame-pointer)
endif()
macro(xoption OPTION_NAME OPTION_TEXT OPTION_DEFAULT)
option(${OPTION_NAME} ${OPTION_TEXT} ${OPTION_DEFAULT})
if(DEFINED ENV{${OPTION_NAME}})
# Allow setting the option through an environment variable.
set(${OPTION_NAME} $ENV{${OPTION_NAME}})
endif()
if(${OPTION_NAME})
add_definitions(-D${OPTION_NAME})
endif()
message(STATUS " ${OPTION_NAME}: ${${OPTION_NAME}}")
endmacro()
xoption(BUILD_SHARED_LIBS "Build a shared library" OFF)
if(BUILD_SHARED_LIBS)
message(STATUS "Building a shared library")
endif()
# note: QJS_ENABLE_TSAN is currently incompatible with the other sanitizers but we
# don't explicitly check for that because who knows what the future will bring?
# QJS_ENABLE_MSAN only works with clang at the time of writing; also not checked
# for the same reason
xoption(QJS_BUILD_EXAMPLES "Build examples" OFF)
xoption(QJS_BUILD_CLI_STATIC "Build a static qjs executable" OFF)
xoption(QJS_BUILD_CLI_WITH_MIMALLOC "Build the qjs executable with mimalloc" OFF)
xoption(QJS_BUILD_CLI_WITH_STATIC_MIMALLOC "Build the qjs executable with mimalloc (statically linked)" OFF)
xoption(QJS_DISABLE_PARSER "Disable JS source code parser" OFF)
xoption(QJS_ENABLE_ASAN "Enable AddressSanitizer (ASan)" OFF)
xoption(QJS_ENABLE_MSAN "Enable MemorySanitizer (MSan)" OFF)
xoption(QJS_ENABLE_TSAN "Enable ThreadSanitizer (TSan)" OFF)
xoption(QJS_ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer (UBSan)" OFF)
if(QJS_ENABLE_ASAN)
message(STATUS "Building with ASan")
add_compile_options(
-fsanitize=address
-fno-sanitize-recover=all
-fno-omit-frame-pointer
)
add_link_options(
-fsanitize=address
-fno-sanitize-recover=all
-fno-omit-frame-pointer
)
endif()
if(QJS_ENABLE_MSAN)
message(STATUS "Building with MSan")
add_compile_options(
-fsanitize=memory
-fno-sanitize-recover=all
-fno-omit-frame-pointer
)
add_link_options(
-fsanitize=memory
-fno-sanitize-recover=all
-fno-omit-frame-pointer
)
endif()
if(QJS_ENABLE_TSAN)
message(STATUS "Building with TSan")
add_compile_options(
-fsanitize=thread
-fno-sanitize-recover=all
-fno-omit-frame-pointer
)
add_link_options(
-fsanitize=thread
-fno-sanitize-recover=all
-fno-omit-frame-pointer
)
endif()
if(QJS_ENABLE_UBSAN)
message(STATUS "Building with UBSan")
add_compile_options(
-fsanitize=undefined
-fno-sanitize-recover=all
-fno-omit-frame-pointer
)
add_link_options(
-fsanitize=undefined
-fno-sanitize-recover=all
-fno-omit-frame-pointer
)
endif()
# QuickJS library
#
xoption(QJS_BUILD_LIBC "Build standard library modules as part of the library" OFF)
macro(add_qjs_libc_if_needed target)
if(NOT QJS_BUILD_LIBC)
target_sources(${target} PRIVATE quickjs-libc.c)
endif()
endmacro()
macro(add_static_if_needed target)
if(QJS_BUILD_CLI_STATIC OR MINGW)
target_link_options(${target} PRIVATE -static)
if(MINGW)
target_link_options(${target} PRIVATE -static-libgcc)
endif()
endif()
endmacro()
set(qjs_sources
cutils.c
dtoa.c
libregexp.c
libunicode.c
quickjs.c
)
if(QJS_BUILD_LIBC)
list(APPEND qjs_sources quickjs-libc.c)
endif()
list(APPEND qjs_defines _GNU_SOURCE)
if(WIN32)
# NB: Windows 7 is EOL and we are only supporting in so far as it doesn't interfere with progress.
list(APPEND qjs_defines WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0601)
endif()
if(TVOS)
list(APPEND qjs_defines _TVOS)
endif()
if(WATCHOS)
list(APPEND qjs_defines _WATCHOS)
endif()
list(APPEND qjs_libs ${CMAKE_DL_LIBS})
find_package(Threads)
if(NOT CMAKE_SYSTEM_NAME STREQUAL "WASI")
list(APPEND qjs_libs ${CMAKE_THREAD_LIBS_INIT})
endif()
# try to find libm
find_library(M_LIBRARIES m)
if(M_LIBRARIES OR CMAKE_C_COMPILER_ID STREQUAL "TinyCC")
list(APPEND qjs_libs m)
endif()
add_library(qjs ${qjs_sources})
target_compile_definitions(qjs PRIVATE ${qjs_defines})
target_include_directories(qjs PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
target_link_libraries(qjs PUBLIC ${qjs_libs})
if(EMSCRIPTEN)
add_executable(qjs_wasm ${qjs_sources})
target_link_options(qjs_wasm PRIVATE
# in emscripten 3.x, this will be set to 16k which is too small for quickjs. #write sth. to force github rebuild
-sSTACK_SIZE=2097152 # let it be 2m = 2 * 1024 * 1024 = 2097152, otherwise, stack overflow may be occured at bootstrap
-sNO_INVOKE_RUN
-sNO_EXIT_RUNTIME
-sMODULARIZE # do not mess the global
-sEXPORT_ES6 # export js file to morden es module
-sEXPORT_NAME=getQuickJs # give a name
-sTEXTDECODER=1 # it will be 2 if we use -Oz, and that will cause js -> c string convertion fail
-sNO_DEFAULT_TO_CXX # this project is pure c project, no need for c plus plus handle
-sEXPORTED_RUNTIME_METHODS=ccall,cwrap
)
target_compile_definitions(qjs_wasm PRIVATE ${qjs_defines})
target_link_libraries(qjs_wasm m)
endif()
# QuickJS bytecode compiler
#
add_executable(qjsc
qjsc.c
)
add_qjs_libc_if_needed(qjsc)
add_static_if_needed(qjsc)
target_compile_definitions(qjsc PRIVATE ${qjs_defines})
target_link_libraries(qjsc qjs)
# QuickJS CLI
#
add_executable(qjs_exe
gen/repl.c
gen/standalone.c
qjs.c
)
add_qjs_libc_if_needed(qjs_exe)
add_static_if_needed(qjs_exe)
set_target_properties(qjs_exe PROPERTIES
OUTPUT_NAME "qjs"
)
target_compile_definitions(qjs_exe PRIVATE ${qjs_defines})
target_link_libraries(qjs_exe qjs)
if(NOT WIN32)
set_target_properties(qjs_exe PROPERTIES ENABLE_EXPORTS TRUE)
endif()
if(QJS_BUILD_CLI_WITH_MIMALLOC OR QJS_BUILD_CLI_WITH_STATIC_MIMALLOC)
find_package(mimalloc REQUIRED)
# Upstream mimalloc doesn't provide a way to know if both libraries are supported.
if(QJS_BUILD_CLI_WITH_STATIC_MIMALLOC)
target_link_libraries(qjs_exe mimalloc-static)
else()
target_link_libraries(qjs_exe mimalloc)
endif()
endif()
# Test262 runner
#
if(NOT EMSCRIPTEN)
add_executable(run-test262
run-test262.c
)
add_qjs_libc_if_needed(run-test262)
target_compile_definitions(run-test262 PRIVATE ${qjs_defines})
target_link_libraries(run-test262 qjs)
endif()
# Interrupt test
#
add_executable(api-test
api-test.c
)
target_compile_definitions(api-test PRIVATE ${qjs_defines})
target_link_libraries(api-test qjs)
# Unicode generator
#
add_executable(unicode_gen EXCLUDE_FROM_ALL
cutils.c
libunicode.c
unicode_gen.c
)
target_compile_definitions(unicode_gen PRIVATE ${qjs_defines})
add_executable(function_source
gen/function_source.c
)
add_qjs_libc_if_needed(function_source)
target_include_directories(function_source PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(function_source PRIVATE ${qjs_defines})
target_link_libraries(function_source qjs)
# Examples
#
if(QJS_BUILD_EXAMPLES)
add_executable(hello
gen/hello.c
)
add_qjs_libc_if_needed(hello)
target_include_directories(hello PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(hello PRIVATE ${qjs_defines})
target_link_libraries(hello qjs)
add_executable(hello_module
gen/hello_module.c
)
add_qjs_libc_if_needed(hello_module)
target_include_directories(hello_module PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(hello_module PRIVATE ${qjs_defines})
target_link_libraries(hello_module qjs)
add_library(fib MODULE examples/fib.c)
set_target_properties(fib PROPERTIES
PREFIX ""
C_VISIBILITY_PRESET default
)
target_compile_definitions(fib PRIVATE JS_SHARED_LIBRARY)
if(WIN32)
target_link_libraries(fib qjs)
elseif(APPLE)
target_link_options(fib PRIVATE -undefined dynamic_lookup)
endif()
add_library(point MODULE examples/point.c)
set_target_properties(point PROPERTIES
PREFIX ""
C_VISIBILITY_PRESET default
)
target_compile_definitions(point PRIVATE JS_SHARED_LIBRARY)
if(WIN32)
target_link_libraries(point qjs)
elseif(APPLE)
target_link_options(point PRIVATE -undefined dynamic_lookup)
endif()
add_executable(test_fib
examples/fib.c
gen/test_fib.c
)
add_qjs_libc_if_needed(test_fib)
target_include_directories(test_fib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(test_fib PRIVATE ${qjs_defines})
target_link_libraries(test_fib qjs)
endif()
# Install target
#
file(STRINGS quickjs.h quickjs_h REGEX QJS_VERSION)
string(REGEX MATCH "QJS_VERSION_MAJOR ([0-9]*)" _ "${quickjs_h}")
set(QJS_VERSION_MAJOR ${CMAKE_MATCH_1})
string(REGEX MATCH "QJS_VERSION_MINOR ([0-9]*)" _ "${quickjs_h}")
set(QJS_VERSION_MINOR ${CMAKE_MATCH_1})
string(REGEX MATCH "QJS_VERSION_PATCH ([0-9]*)" _ "${quickjs_h}")
set(QJS_VERSION_PATCH ${CMAKE_MATCH_1})
set_target_properties(qjs PROPERTIES
VERSION ${QJS_VERSION_MAJOR}.${QJS_VERSION_MINOR}.${QJS_VERSION_PATCH}
SOVERSION ${QJS_VERSION_MAJOR}
)
install(FILES quickjs.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
if(QJS_BUILD_LIBC)
install(FILES quickjs-libc.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
endif()
if(NOT IOS AND NOT TVOS AND NOT WATCHOS)
install(TARGETS qjs_exe RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(TARGETS qjsc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
install(TARGETS qjs EXPORT qjsConfig
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(EXPORT qjsConfig DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/quickjs)
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(DIRECTORY examples DESTINATION ${CMAKE_INSTALL_DOCDIR})
+24
View File
@@ -0,0 +1,24 @@
The MIT License (MIT)
Copyright (c) 2017-2024 Fabrice Bellard
Copyright (c) 2017-2024 Charlie Gordon
Copyright (c) 2023-2025 Ben Noordhuis
Copyright (c) 2023-2025 Saúl Ibarra Corretgé
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+147
View File
@@ -0,0 +1,147 @@
#
# QuickJS Javascript Engine
#
# Copyright (c) 2017-2024 Fabrice Bellard
# Copyright (c) 2017-2024 Charlie Gordon
# Copyright (c) 2023-2025 Ben Noordhuis
# Copyright (c) 2023-2025 Saúl Ibarra Corretgé
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
BUILD_DIR=build
BUILD_TYPE?=Release
INSTALL_PREFIX?=/usr/local
QJS=$(BUILD_DIR)/qjs
QJSC=$(BUILD_DIR)/qjsc
RUN262=$(BUILD_DIR)/run-test262
JOBS?=$(shell getconf _NPROCESSORS_ONLN)
ifeq ($(JOBS),)
JOBS := $(shell sysctl -n hw.ncpu)
endif
ifeq ($(JOBS),)
JOBS := $(shell nproc)
endif
ifeq ($(JOBS),)
JOBS := 4
endif
all: $(QJS)
amalgam: TEMP := $(shell mktemp -d)
amalgam: $(QJS)
$(QJS) amalgam.js $(TEMP)/quickjs-amalgam.c
cp quickjs.h quickjs-libc.h $(TEMP)
cd $(TEMP) && zip -9 quickjs-amalgam.zip quickjs-amalgam.c quickjs.h quickjs-libc.h
cp $(TEMP)/quickjs-amalgam.zip $(BUILD_DIR)
cd $(TEMP) && $(RM) quickjs-amalgam.zip quickjs-amalgam.c quickjs.h quickjs-libc.h
$(RM) -d $(TEMP)
fuzz:
clang -g -O1 -fsanitize=address,undefined,fuzzer -o fuzz fuzz.c
./fuzz
$(BUILD_DIR):
cmake -B $(BUILD_DIR) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) -DCMAKE_INSTALL_PREFIX=$(INSTALL_PREFIX)
$(QJS): $(BUILD_DIR)
cmake --build $(BUILD_DIR) -j $(JOBS)
$(QJSC): $(BUILD_DIR)
cmake --build $(BUILD_DIR) --target qjsc -j $(JOBS)
install: $(QJS) $(QJSC)
cmake --build $(BUILD_DIR) --target install
clean:
cmake --build $(BUILD_DIR) --target clean
codegen: $(QJSC)
$(QJSC) -ss -o gen/repl.c -m repl.js
$(QJSC) -ss -o gen/standalone.c -m standalone.js
$(QJSC) -e -o gen/function_source.c tests/function_source.js
$(QJSC) -e -o gen/hello.c examples/hello.js
$(QJSC) -e -o gen/hello_module.c -m examples/hello_module.js
$(QJSC) -e -o gen/test_fib.c -m examples/test_fib.js
$(QJSC) -C -ss -o builtin-array-fromasync.h builtin-array-fromasync.js
debug:
BUILD_TYPE=Debug $(MAKE)
distclean:
@rm -rf $(BUILD_DIR)
stats: $(QJS)
$(QJS) -qd
jscheck: CFLAGS=-I. -D_GNU_SOURCE -DJS_CHECK_JSVALUE -Wall -Werror -fsyntax-only -c -o /dev/null
jscheck:
$(CC) $(CFLAGS) api-test.c
$(CC) $(CFLAGS) ctest.c
$(CC) $(CFLAGS) fuzz.c
$(CC) $(CFLAGS) gen/function_source.c
$(CC) $(CFLAGS) gen/hello.c
$(CC) $(CFLAGS) gen/hello_module.c
$(CC) $(CFLAGS) gen/repl.c
$(CC) $(CFLAGS) gen/standalone.c
$(CC) $(CFLAGS) gen/test_fib.c
$(CC) $(CFLAGS) qjs.c
$(CC) $(CFLAGS) qjsc.c
$(CC) $(CFLAGS) quickjs-libc.c
$(CC) $(CFLAGS) quickjs.c
$(CC) $(CFLAGS) run-test262.c
# effectively .PHONY because it doesn't generate output
ctest: CFLAGS=-std=c11 -fsyntax-only -Wall -Wextra -Werror -pedantic
ctest: ctest.c quickjs.h
$(CC) $(CFLAGS) -DJS_NAN_BOXING=0 $<
$(CC) $(CFLAGS) -DJS_NAN_BOXING=1 $<
# effectively .PHONY because it doesn't generate output
cxxtest: CXXFLAGS=-std=c++11 -fsyntax-only -Wall -Wextra -Werror -pedantic
cxxtest: cxxtest.cc quickjs.h
$(CXX) $(CXXFLAGS) -DJS_NAN_BOXING=0 $<
$(CXX) $(CXXFLAGS) -DJS_NAN_BOXING=1 $<
test: $(QJS)
$(RUN262) -c tests.conf
test262: $(QJS)
$(RUN262) -m -c test262.conf -a
test262-fast: $(QJS)
$(RUN262) -m -c test262.conf -c test262-fast.conf -a
test262-update: $(QJS)
$(RUN262) -u -c test262.conf -a -t 1
test262-check: $(QJS)
$(RUN262) -m -c test262.conf -E -a
microbench: $(QJS)
$(QJS) tests/microbench.js
unicode_gen: $(BUILD_DIR)
cmake --build $(BUILD_DIR) --target unicode_gen
libunicode-table.h: unicode_gen
$(BUILD_DIR)/unicode_gen unicode $@
.PHONY: all amalgam ctest cxxtest debug fuzz jscheck install clean codegen distclean stats test test262 test262-update test262-check microbench unicode_gen $(QJS) $(QJSC)
+24
View File
@@ -0,0 +1,24 @@
# ⚡️ QuickJS - A mighty JavaScript engine
## Overview
QuickJS is a small and embeddable JavaScript engine. It aims to support the latest
[ECMAScript] specification.
This project is a _fork_ of the [original QuickJS project] by Fabrice Bellard and Charlie Gordon, after it went dormant, with the intent of reigniting its development.
## Getting started
Head over to the [project website] for instructions on how to get started and more
documentation.
## Authors
[@bnoordhuis], [@saghul], and many more [contributors].
[ECMAScript]: https://tc39.es/ecma262/
[original QuickJS project]: https://bellard.org/quickjs
[@bnoordhuis]: https://github.com/bnoordhuis
[@saghul]: https://github.com/saghul
[contributors]: https://github.com/quickjs-ng/quickjs/graphs/contributors
[project website]: https://quickjs-ng.github.io/quickjs/
+49
View File
@@ -0,0 +1,49 @@
import {loadFile, writeFile} from "qjs:std"
const cutils_c = loadFile("cutils.c")
const cutils_h = loadFile("cutils.h")
const dtoa_c = loadFile("dtoa.c")
const dtoa_h = loadFile("dtoa.h")
const libregexp_c = loadFile("libregexp.c")
const libregexp_h = loadFile("libregexp.h")
const libregexp_opcode_h = loadFile("libregexp-opcode.h")
const libunicode_c = loadFile("libunicode.c")
const libunicode_h = loadFile("libunicode.h")
const libunicode_table_h = loadFile("libunicode-table.h")
const list_h = loadFile("list.h")
const quickjs_atom_h = loadFile("quickjs-atom.h")
const quickjs_c = loadFile("quickjs.c")
const quickjs_c_atomics_h = loadFile("quickjs-c-atomics.h")
const quickjs_h = loadFile("quickjs.h")
const quickjs_libc_c = loadFile("quickjs-libc.c")
const quickjs_libc_h = loadFile("quickjs-libc.h")
const quickjs_opcode_h = loadFile("quickjs-opcode.h")
const gen_builtin_array_fromasync_h = loadFile("builtin-array-fromasync.h")
let source = "#if defined(QJS_BUILD_LIBC) && defined(__linux__) && !defined(_GNU_SOURCE)\n"
+ "#define _GNU_SOURCE\n"
+ "#endif\n"
+ quickjs_c_atomics_h
+ cutils_h
+ dtoa_h
+ list_h
+ libunicode_h // exports lre_is_id_start, used by libregexp.h
+ libregexp_h
+ libunicode_table_h
+ quickjs_h
+ quickjs_c
+ cutils_c
+ dtoa_c
+ libregexp_c
+ libunicode_c
+ "#ifdef QJS_BUILD_LIBC\n"
+ quickjs_libc_h
+ quickjs_libc_c
+ "#endif // QJS_BUILD_LIBC\n"
source = source.replace(/#include "quickjs-atom.h"/g, quickjs_atom_h)
source = source.replace(/#include "quickjs-opcode.h"/g, quickjs_opcode_h)
source = source.replace(/#include "libregexp-opcode.h"/g, libregexp_opcode_h)
source = source.replace(/#include "builtin-array-fromasync.h"/g,
gen_builtin_array_fromasync_h)
source = source.replace(/#include "[^"]+"/g, "")
writeFile(execArgv[2] ?? "quickjs-amalgam.c", source)
+854
View File
@@ -0,0 +1,854 @@
#ifdef NDEBUG
#undef NDEBUG
#endif
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "quickjs.h"
#include "cutils.h"
static JSValue eval(JSContext *ctx, const char *code)
{
return JS_Eval(ctx, code, strlen(code), "<input>", JS_EVAL_TYPE_GLOBAL);
}
static JSValue cfunc_callback(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
return JS_ThrowTypeError(ctx, "from cfunc");
}
static JSValue cfuncdata_callback(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv,
int magic, JSValueConst *func_data)
{
return JS_ThrowTypeError(ctx, "from cfuncdata");
}
static JSValue cclosure_callback(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv,
int magic, void *func_data)
{
return JS_ThrowTypeError(ctx, "from cclosure");
}
static bool closure_finalized = false;
static void cclosure_opaque_finalize(void *opaque)
{
if ((intptr_t)opaque == 12)
closure_finalized = true;
}
static void cfunctions(void)
{
uint32_t length;
const char *s;
JSValue ret, stack;
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
JSValue cfunc = JS_NewCFunction(ctx, cfunc_callback, "cfunc", 42);
JSValue cfuncdata =
JS_NewCFunctionData2(ctx, cfuncdata_callback, "cfuncdata",
/*length*/1337, /*magic*/0, /*data_len*/0, NULL);
JSValue cclosure =
JS_NewCClosure(ctx, cclosure_callback, "cclosure", cclosure_opaque_finalize,
/*length*/0xC0DE, /*magic*/11, /*opaque*/(void*)12);
JSValue global = JS_GetGlobalObject(ctx);
JS_SetPropertyStr(ctx, global, "cfunc", cfunc);
JS_SetPropertyStr(ctx, global, "cfuncdata", cfuncdata);
JS_SetPropertyStr(ctx, global, "cclosure", cclosure);
JS_FreeValue(ctx, global);
ret = eval(ctx, "cfunc.name");
assert(!JS_IsException(ret));
assert(JS_IsString(ret));
s = JS_ToCString(ctx, ret);
JS_FreeValue(ctx, ret);
assert(s);
assert(!strcmp(s, "cfunc"));
JS_FreeCString(ctx, s);
ret = eval(ctx, "cfunc.length");
assert(!JS_IsException(ret));
assert(JS_IsNumber(ret));
assert(0 == JS_ToUint32(ctx, &length, ret));
assert(length == 42);
ret = eval(ctx, "cfuncdata.name");
assert(!JS_IsException(ret));
assert(JS_IsString(ret));
s = JS_ToCString(ctx, ret);
JS_FreeValue(ctx, ret);
assert(s);
assert(!strcmp(s, "cfuncdata"));
JS_FreeCString(ctx, s);
ret = eval(ctx, "cfuncdata.length");
assert(!JS_IsException(ret));
assert(JS_IsNumber(ret));
assert(0 == JS_ToUint32(ctx, &length, ret));
assert(length == 1337);
ret = eval(ctx, "cclosure.name");
assert(!JS_IsException(ret));
assert(JS_IsString(ret));
s = JS_ToCString(ctx, ret);
JS_FreeValue(ctx, ret);
assert(s);
assert(!strcmp(s, "cclosure"));
JS_FreeCString(ctx, s);
ret = eval(ctx, "cclosure.length");
assert(!JS_IsException(ret));
assert(JS_IsNumber(ret));
assert(0 == JS_ToUint32(ctx, &length, ret));
assert(length == 0xC0DE);
ret = eval(ctx, "cfunc()");
assert(JS_IsException(ret));
ret = JS_GetException(ctx);
assert(JS_IsError(ret));
stack = JS_GetPropertyStr(ctx, ret, "stack");
assert(JS_IsString(stack));
s = JS_ToCString(ctx, stack);
JS_FreeValue(ctx, stack);
assert(s);
assert(!strcmp(s, " at cfunc (native)\n at <eval> (<input>:1:1)\n"));
JS_FreeCString(ctx, s);
s = JS_ToCString(ctx, ret);
JS_FreeValue(ctx, ret);
assert(s);
assert(!strcmp(s, "TypeError: from cfunc"));
JS_FreeCString(ctx, s);
ret = eval(ctx, "cfuncdata()");
assert(JS_IsException(ret));
ret = JS_GetException(ctx);
assert(JS_IsError(ret));
stack = JS_GetPropertyStr(ctx, ret, "stack");
assert(JS_IsString(stack));
s = JS_ToCString(ctx, stack);
JS_FreeValue(ctx, stack);
assert(s);
assert(!strcmp(s, " at cfuncdata (native)\n at <eval> (<input>:1:1)\n"));
JS_FreeCString(ctx, s);
s = JS_ToCString(ctx, ret);
JS_FreeValue(ctx, ret);
assert(s);
assert(!strcmp(s, "TypeError: from cfuncdata"));
JS_FreeCString(ctx, s);
ret = eval(ctx, "cclosure()");
assert(JS_IsException(ret));
ret = JS_GetException(ctx);
assert(JS_IsError(ret));
stack = JS_GetPropertyStr(ctx, ret, "stack");
assert(JS_IsString(stack));
s = JS_ToCString(ctx, stack);
JS_FreeValue(ctx, stack);
assert(s);
assert(!strcmp(s, " at cclosure (native)\n at <eval> (<input>:1:1)\n"));
JS_FreeCString(ctx, s);
s = JS_ToCString(ctx, ret);
JS_FreeValue(ctx, ret);
assert(s);
assert(!strcmp(s, "TypeError: from cclosure"));
JS_FreeCString(ctx, s);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
assert(closure_finalized);
}
#define MAX_TIME 10
static int timeout_interrupt_handler(JSRuntime *rt, void *opaque)
{
int *time = (int *)opaque;
if (*time <= MAX_TIME)
*time += 1;
return *time > MAX_TIME;
}
static void sync_call(void)
{
static const char code[] =
"(function() { \
try { \
while (true) {} \
} catch (e) {} \
})();";
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
int time = 0;
JS_SetInterruptHandler(rt, timeout_interrupt_handler, &time);
JSValue ret = eval(ctx, code);
assert(time > MAX_TIME);
assert(JS_IsException(ret));
JS_FreeValue(ctx, ret);
assert(JS_HasException(ctx));
JSValue e = JS_GetException(ctx);
assert(JS_IsUncatchableError(e));
JS_FreeValue(ctx, e);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
static void async_call(void)
{
static const char code[] =
"(async function() { \
const loop = async () => { \
await Promise.resolve(); \
while (true) {} \
}; \
await loop().catch(() => {}); \
})();";
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
int time = 0;
JS_SetInterruptHandler(rt, timeout_interrupt_handler, &time);
JSValue ret = eval(ctx, code);
assert(!JS_IsException(ret));
JS_FreeValue(ctx, ret);
assert(JS_IsJobPending(rt));
int r = 0;
while (JS_IsJobPending(rt)) {
r = JS_ExecutePendingJob(rt, &ctx);
}
assert(time > MAX_TIME);
assert(r == -1);
assert(JS_HasException(ctx));
JSValue e = JS_GetException(ctx);
assert(JS_IsUncatchableError(e));
JS_FreeValue(ctx, e);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
static JSValue save_value(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
assert(argc == 1);
JSValue *p = (JSValue *)JS_GetContextOpaque(ctx);
*p = JS_DupValue(ctx, argv[0]);
return JS_UNDEFINED;
}
static void async_call_stack_overflow(void)
{
static const char code[] =
"(async function() { \
const f = () => f(); \
try { \
await Promise.resolve(); \
f(); \
} catch (e) { \
save_value(e); \
} \
})();";
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
JSValue value = JS_UNDEFINED;
JS_SetContextOpaque(ctx, &value);
JSValue global = JS_GetGlobalObject(ctx);
JS_SetPropertyStr(ctx, global, "save_value", JS_NewCFunction(ctx, save_value, "save_value", 1));
JS_FreeValue(ctx, global);
JSValue ret = eval(ctx, code);
assert(!JS_IsException(ret));
JS_FreeValue(ctx, ret);
assert(JS_IsJobPending(rt));
int r = 0;
while (JS_IsJobPending(rt)) {
r = JS_ExecutePendingJob(rt, &ctx);
}
assert(r == 1);
assert(!JS_HasException(ctx));
assert(JS_IsError(value)); // stack overflow should be caught
JS_FreeValue(ctx, value);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
// https://github.com/quickjs-ng/quickjs/issues/914
static void raw_context_global_var(void)
{
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContextRaw(rt);
JS_AddIntrinsicEval(ctx);
{
JSValue ret = eval(ctx, "globalThis");
assert(JS_IsException(ret));
JS_FreeValue(ctx, ret);
}
{
JSValue ret = eval(ctx, "var x = 42");
assert(JS_IsUndefined(ret));
JS_FreeValue(ctx, ret);
}
{
JSValue ret = eval(ctx, "function f() {}");
assert(JS_IsUndefined(ret));
JS_FreeValue(ctx, ret);
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
static void is_array(void)
{
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
{
JSValue ret = eval(ctx, "[]");
assert(!JS_IsException(ret));
assert(JS_IsArray(ret));
JS_FreeValue(ctx, ret);
}
{
JSValue ret = eval(ctx, "new Proxy([], {})");
assert(!JS_IsException(ret));
assert(!JS_IsArray(ret));
assert(JS_IsProxy(ret));
JSValue handler = JS_GetProxyHandler(ctx, ret);
JSValue target = JS_GetProxyTarget(ctx, ret);
assert(!JS_IsException(handler));
assert(!JS_IsException(target));
assert(!JS_IsProxy(handler));
assert(!JS_IsProxy(target));
assert(JS_IsObject(handler));
assert(JS_IsArray(target));
JS_FreeValue(ctx, handler);
JS_FreeValue(ctx, target);
JS_FreeValue(ctx, ret);
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
static int loader_calls;
static JSModuleDef *loader(JSContext *ctx, const char *name, void *opaque)
{
loader_calls++;
assert(!strcmp(name, "b"));
static const char code[] = "export function f(x){}";
JSValue ret = JS_Eval(ctx, code, strlen(code), "b",
JS_EVAL_TYPE_MODULE|JS_EVAL_FLAG_COMPILE_ONLY);
assert(!JS_IsException(ret));
JSModuleDef *m = JS_VALUE_GET_PTR(ret);
assert(m);
JS_FreeValue(ctx, ret);
return m;
}
static void module_serde(void)
{
JSRuntime *rt = JS_NewRuntime();
//JS_SetDumpFlags(rt, JS_DUMP_MODULE_RESOLVE);
JS_SetModuleLoaderFunc(rt, NULL, loader, NULL);
JSContext *ctx = JS_NewContext(rt);
static const char code[] = "import {f} from 'b'; f()";
assert(loader_calls == 0);
JSValue mod = JS_Eval(ctx, code, strlen(code), "a",
JS_EVAL_TYPE_MODULE|JS_EVAL_FLAG_COMPILE_ONLY);
assert(loader_calls == 1);
assert(!JS_IsException(mod));
assert(JS_IsModule(mod));
size_t len = 0;
uint8_t *buf = JS_WriteObject(ctx, &len, mod,
JS_WRITE_OBJ_BYTECODE|JS_WRITE_OBJ_REFERENCE);
assert(buf);
assert(len > 0);
JS_FreeValue(ctx, mod);
assert(loader_calls == 1);
mod = JS_ReadObject(ctx, buf, len, JS_READ_OBJ_BYTECODE);
js_free(ctx, buf);
assert(loader_calls == 1); // 'b' is returned from cache
assert(!JS_IsException(mod));
JSValue ret = JS_EvalFunction(ctx, mod);
assert(!JS_IsException(ret));
assert(JS_IsPromise(ret));
JSValue result = JS_PromiseResult(ctx, ret);
assert(!JS_IsException(result));
assert(JS_IsUndefined(result));
JS_FreeValue(ctx, result);
JS_FreeValue(ctx, ret);
JS_FreeValue(ctx, mod);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
static void runtime_cstring_free(void)
{
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
// string -> cstring + JS_FreeCStringRT
{
JSValue ret = eval(ctx, "\"testStringPleaseIgnore\"");
assert(JS_IsString(ret));
const char *s = JS_ToCString(ctx, ret);
assert(s);
assert(strcmp(s, "testStringPleaseIgnore") == 0);
JS_FreeCStringRT(rt, s);
JS_FreeValue(ctx, ret);
}
// string -> cstring + JS_FreeCStringRT, destroying the source value first
{
JSValue ret = eval(ctx, "\"testStringPleaseIgnore\"");
assert(JS_IsString(ret));
const char *s = JS_ToCString(ctx, ret);
assert(s);
JS_FreeValue(ctx, ret);
assert(strcmp(s, "testStringPleaseIgnore") == 0);
JS_FreeCStringRT(rt, s);
}
// number -> cstring + JS_FreeCStringRT
{
JSValue ret = eval(ctx, "123987");
assert(JS_IsNumber(ret));
const char *s = JS_ToCString(ctx, ret);
assert(s);
assert(strcmp(s, "123987") == 0);
JS_FreeCStringRT(rt, s);
JS_FreeValue(ctx, ret);
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
static void two_byte_string(void)
{
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
{
JSValue v = JS_NewTwoByteString(ctx, NULL, 0);
assert(!JS_IsException(v));
const char *s = JS_ToCString(ctx, v);
assert(s);
assert(!strcmp(s, ""));
JS_FreeCString(ctx, s);
JS_FreeValue(ctx, v);
}
{
JSValue v = JS_NewTwoByteString(ctx, (uint16_t[]){'o','k'}, 2);
assert(!JS_IsException(v));
const char *s = JS_ToCString(ctx, v);
assert(s);
assert(!strcmp(s, "ok"));
JS_FreeCString(ctx, s);
JS_FreeValue(ctx, v);
}
{
JSValue v = JS_NewTwoByteString(ctx, (uint16_t[]){0xD800}, 1);
assert(!JS_IsException(v));
const char *s = JS_ToCString(ctx, v);
assert(s);
// questionable but surrogates don't map to UTF-8 without WTF-8
assert(!strcmp(s, "\xED\xA0\x80"));
JS_FreeCString(ctx, s);
JS_FreeValue(ctx, v);
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
static void weak_map_gc_check(void)
{
static const char init_code[] =
"const map = new WeakMap(); \
function addItem() { \
const k = { \
text: 'a', \
}; \
map.set(k, {k}); \
}";
static const char test_code[] = "addItem()";
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
JSValue ret = eval(ctx, init_code);
assert(!JS_IsException(ret));
JSValue ret_test = eval(ctx, test_code);
assert(!JS_IsException(ret_test));
JS_RunGC(rt);
JSMemoryUsage memory_usage;
JS_ComputeMemoryUsage(rt, &memory_usage);
for (int i = 0; i < 3; i++) {
JSValue ret_test2 = eval(ctx, test_code);
assert(!JS_IsException(ret_test2));
JS_RunGC(rt);
JSMemoryUsage memory_usage2;
JS_ComputeMemoryUsage(rt, &memory_usage2);
assert(memory_usage.memory_used_count == memory_usage2.memory_used_count);
assert(memory_usage.memory_used_size == memory_usage2.memory_used_size);
JS_FreeValue(ctx, ret_test2);
}
JS_FreeValue(ctx, ret);
JS_FreeValue(ctx, ret_test);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
struct {
int hook_type_call_count[4];
} promise_hook_state;
static void promise_hook_cb(JSContext *ctx, JSPromiseHookType type,
JSValueConst promise, JSValueConst parent_promise,
void *opaque)
{
assert(type == JS_PROMISE_HOOK_INIT ||
type == JS_PROMISE_HOOK_BEFORE ||
type == JS_PROMISE_HOOK_AFTER ||
type == JS_PROMISE_HOOK_RESOLVE);
promise_hook_state.hook_type_call_count[type]++;
assert(opaque == (void *)&promise_hook_state);
if (!JS_IsUndefined(parent_promise)) {
JSValue global_object = JS_GetGlobalObject(ctx);
JS_SetPropertyStr(ctx, global_object, "actual",
JS_DupValue(ctx, parent_promise));
JS_FreeValue(ctx, global_object);
}
}
static void promise_hook(void)
{
int *cc = promise_hook_state.hook_type_call_count;
JSContext *unused;
JSRuntime *rt = JS_NewRuntime();
//JS_SetDumpFlags(rt, JS_DUMP_PROMISE);
JS_SetPromiseHook(rt, promise_hook_cb, &promise_hook_state);
JSContext *ctx = JS_NewContext(rt);
JSValue global_object = JS_GetGlobalObject(ctx);
{
// empty module; creates an outer and inner module promise;
// JS_Eval returns the outer promise
JSValue ret = JS_Eval(ctx, "", 0, "<input>", JS_EVAL_TYPE_MODULE);
assert(!JS_IsException(ret));
assert(JS_IsPromise(ret));
assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret));
JS_FreeValue(ctx, ret);
assert(2 == cc[JS_PROMISE_HOOK_INIT]);
assert(0 == cc[JS_PROMISE_HOOK_BEFORE]);
assert(0 == cc[JS_PROMISE_HOOK_AFTER]);
assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]);
assert(!JS_IsJobPending(rt));
}
memset(&promise_hook_state, 0, sizeof(promise_hook_state));
{
// module with unresolved promise; the outer and inner module promises
// are resolved but not the user's promise
static const char code[] = "new Promise(() => {})";
JSValue ret = JS_Eval(ctx, code, strlen(code), "<input>", JS_EVAL_TYPE_MODULE);
assert(!JS_IsException(ret));
assert(JS_IsPromise(ret));
assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise
JS_FreeValue(ctx, ret);
assert(3 == cc[JS_PROMISE_HOOK_INIT]);
assert(0 == cc[JS_PROMISE_HOOK_BEFORE]);
assert(0 == cc[JS_PROMISE_HOOK_AFTER]);
assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]); // outer and inner module promise
assert(!JS_IsJobPending(rt));
}
memset(&promise_hook_state, 0, sizeof(promise_hook_state));
{
// module with resolved promise
static const char code[] = "new Promise((resolve,reject) => resolve())";
JSValue ret = JS_Eval(ctx, code, strlen(code), "<input>", JS_EVAL_TYPE_MODULE);
assert(!JS_IsException(ret));
assert(JS_IsPromise(ret));
assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise
JS_FreeValue(ctx, ret);
assert(3 == cc[JS_PROMISE_HOOK_INIT]);
assert(0 == cc[JS_PROMISE_HOOK_BEFORE]);
assert(0 == cc[JS_PROMISE_HOOK_AFTER]);
assert(3 == cc[JS_PROMISE_HOOK_RESOLVE]);
assert(!JS_IsJobPending(rt));
}
memset(&promise_hook_state, 0, sizeof(promise_hook_state));
{
// module with rejected promise
static const char code[] = "new Promise((resolve,reject) => reject())";
JSValue ret = JS_Eval(ctx, code, strlen(code), "<input>", JS_EVAL_TYPE_MODULE);
assert(!JS_IsException(ret));
assert(JS_IsPromise(ret));
assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise
JS_FreeValue(ctx, ret);
assert(3 == cc[JS_PROMISE_HOOK_INIT]);
assert(0 == cc[JS_PROMISE_HOOK_BEFORE]);
assert(0 == cc[JS_PROMISE_HOOK_AFTER]);
assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]);
assert(!JS_IsJobPending(rt));
}
memset(&promise_hook_state, 0, sizeof(promise_hook_state));
{
// module with promise chain
static const char code[] =
"globalThis.count = 0;"
"globalThis.actual = undefined;" // set by promise_hook_cb
"globalThis.expected = new Promise(resolve => resolve());"
"expected.then(_ => count++)";
JSValue ret = JS_Eval(ctx, code, strlen(code), "<input>", JS_EVAL_TYPE_MODULE);
assert(!JS_IsException(ret));
assert(JS_IsPromise(ret));
assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise
JS_FreeValue(ctx, ret);
assert(4 == cc[JS_PROMISE_HOOK_INIT]);
assert(0 == cc[JS_PROMISE_HOOK_BEFORE]);
assert(0 == cc[JS_PROMISE_HOOK_AFTER]);
assert(3 == cc[JS_PROMISE_HOOK_RESOLVE]);
JSValue v = JS_GetPropertyStr(ctx, global_object, "count");
assert(!JS_IsException(v));
int32_t count;
assert(0 == JS_ToInt32(ctx, &count, v));
assert(0 == count);
JS_FreeValue(ctx, v);
assert(JS_IsJobPending(rt));
assert(1 == JS_ExecutePendingJob(rt, &unused));
assert(!JS_HasException(ctx));
assert(4 == cc[JS_PROMISE_HOOK_INIT]);
assert(0 == cc[JS_PROMISE_HOOK_BEFORE]);
assert(0 == cc[JS_PROMISE_HOOK_AFTER]);
assert(4 == cc[JS_PROMISE_HOOK_RESOLVE]);
assert(!JS_IsJobPending(rt));
v = JS_GetPropertyStr(ctx, global_object, "count");
assert(!JS_IsException(v));
assert(0 == JS_ToInt32(ctx, &count, v));
assert(1 == count);
JS_FreeValue(ctx, v);
JSValue actual = JS_GetPropertyStr(ctx, global_object, "actual");
JSValue expected = JS_GetPropertyStr(ctx, global_object, "expected");
assert(!JS_IsException(actual));
assert(!JS_IsException(expected));
assert(JS_IsSameValue(ctx, actual, expected));
JS_FreeValue(ctx, actual);
JS_FreeValue(ctx, expected);
}
memset(&promise_hook_state, 0, sizeof(promise_hook_state));
{
// module with thenable; fires before and after hooks
static const char code[] =
"new Promise(resolve => resolve({then(resolve){ resolve() }}))";
JSValue ret = JS_Eval(ctx, code, strlen(code), "<input>", JS_EVAL_TYPE_MODULE);
assert(!JS_IsException(ret));
assert(JS_IsPromise(ret));
assert(JS_PROMISE_FULFILLED == JS_PromiseState(ctx, ret)); // outer module promise
JS_FreeValue(ctx, ret);
assert(3 == cc[JS_PROMISE_HOOK_INIT]);
assert(0 == cc[JS_PROMISE_HOOK_BEFORE]);
assert(0 == cc[JS_PROMISE_HOOK_AFTER]);
assert(2 == cc[JS_PROMISE_HOOK_RESOLVE]);
assert(JS_IsJobPending(rt));
assert(1 == JS_ExecutePendingJob(rt, &unused));
assert(!JS_HasException(ctx));
assert(3 == cc[JS_PROMISE_HOOK_INIT]);
assert(1 == cc[JS_PROMISE_HOOK_BEFORE]);
assert(1 == cc[JS_PROMISE_HOOK_AFTER]);
assert(3 == cc[JS_PROMISE_HOOK_RESOLVE]);
assert(!JS_IsJobPending(rt));
}
JS_FreeValue(ctx, global_object);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
static void dump_memory_usage(void)
{
JSMemoryUsage stats;
JSRuntime *rt = NULL;
JSContext *ctx = NULL;
rt = JS_NewRuntime();
ctx = JS_NewContext(rt);
//JS_SetDumpFlags(rt, JS_DUMP_PROMISE);
static const char code[] =
"globalThis.count = 0;"
"globalThis.actual = undefined;" // set by promise_hook_cb
"globalThis.expected = new Promise(resolve => resolve());"
"expected.then(_ => count++)";
JSValue evalVal = JS_Eval(ctx, code, strlen(code), "<input>", 0);
JS_FreeValue(ctx, evalVal);
FILE *temp = tmpfile();
assert(temp != NULL);
JS_ComputeMemoryUsage(rt, &stats);
JS_DumpMemoryUsage(temp, &stats, rt);
// JS_DumpMemoryUsage(stdout, &stats, rt);
fclose(temp);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
static void new_errors(void)
{
typedef struct {
const char name[16];
JSValue (*func)(JSContext *, const char *, ...);
} Entry;
static const Entry entries[] = {
{"Error", JS_NewPlainError},
{"InternalError", JS_NewInternalError},
{"RangeError", JS_NewRangeError},
{"ReferenceError", JS_NewReferenceError},
{"SyntaxError", JS_NewSyntaxError},
{"TypeError", JS_NewTypeError},
};
const Entry *e;
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
for (e = entries; e < endof(entries); e++) {
JSValue obj = (*e->func)(ctx, "the %s", "needle");
assert(!JS_IsException(obj));
assert(JS_IsObject(obj));
assert(JS_IsError(obj));
const char *haystack = JS_ToCString(ctx, obj);
char needle[256];
snprintf(needle, sizeof(needle), "%s: the needle", e->name);
assert(strstr(haystack, needle));
JS_FreeCString(ctx, haystack);
JSValue stack = JS_GetPropertyStr(ctx, obj, "stack");
assert(!JS_IsException(stack));
assert(JS_IsString(stack));
JS_FreeValue(ctx, stack);
JS_FreeValue(ctx, obj);
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
static int gop_get_own_property(JSContext *ctx, JSPropertyDescriptor *desc,
JSValueConst obj, JSAtom prop)
{
const char *name;
int found;
found = 0;
name = JS_AtomToCString(ctx, prop);
if (!name)
return -1;
if (!strcmp(name, "answer")) {
found = 1;
*desc = (JSPropertyDescriptor){
.value = JS_NewInt32(ctx, 42),
.flags = JS_PROP_C_W_E | JS_PROP_HAS_VALUE,
};
}
JS_FreeCString(ctx, name);
return found;
}
static void global_object_prototype(void)
{
static const char code[] = "answer";
JSValue global_object, proto, ret;
JSClassID class_id;
JSRuntime *rt;
JSContext *ctx;
int32_t answer;
int res;
{
rt = JS_NewRuntime();
ctx = JS_NewContext(rt);
proto = JS_NewObject(ctx);
assert(JS_IsObject(proto));
JSCFunctionListEntry prop = JS_PROP_INT32_DEF("answer", 42, JS_PROP_C_W_E);
JS_SetPropertyFunctionList(ctx, proto, &prop, 1);
global_object = JS_GetGlobalObject(ctx);
res = JS_SetPrototype(ctx, global_object, proto);
assert(res == true);
JS_FreeValue(ctx, global_object);
JS_FreeValue(ctx, proto);
ret = eval(ctx, code);
assert(!JS_IsException(ret));
assert(JS_IsNumber(ret));
res = JS_ToInt32(ctx, &answer, ret);
assert(res == 0);
assert(answer == 42);
JS_FreeValue(ctx, ret);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
{
JSClassExoticMethods exotic = (JSClassExoticMethods){
.get_own_property = gop_get_own_property,
};
JSClassDef def = (JSClassDef){
.class_name = "Global Object",
.exotic = &exotic,
};
rt = JS_NewRuntime();
class_id = 0;
JS_NewClassID(rt, &class_id);
res = JS_NewClass(rt, class_id, &def);
assert(res == 0);
ctx = JS_NewContext(rt);
proto = JS_NewObjectClass(ctx, class_id);
assert(JS_IsObject(proto));
global_object = JS_GetGlobalObject(ctx);
res = JS_SetPrototype(ctx, global_object, proto);
assert(res == true);
JS_FreeValue(ctx, global_object);
JS_FreeValue(ctx, proto);
ret = eval(ctx, code);
assert(!JS_IsException(ret));
assert(JS_IsNumber(ret));
res = JS_ToInt32(ctx, &answer, ret);
assert(res == 0);
assert(answer == 42);
JS_FreeValue(ctx, ret);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
}
// https://github.com/quickjs-ng/quickjs/issues/1178
static void slice_string_tocstring(void)
{
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
JSValue ret = eval(ctx, "'.'.repeat(16384).slice(1, -1)");
assert(!JS_IsException(ret));
assert(JS_IsString(ret));
const char *str = JS_ToCString(ctx, ret);
assert(strlen(str) == 16382);
JS_FreeCString(ctx, str);
JS_FreeValue(ctx, ret);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
int main(void)
{
cfunctions();
sync_call();
async_call();
async_call_stack_overflow();
raw_context_global_var();
is_array();
module_serde();
runtime_cstring_free();
two_byte_string();
weak_map_gc_check();
promise_hook();
dump_memory_usage();
new_errors();
global_object_prototype();
slice_string_tocstring();
return 0;
}
@@ -0,0 +1,113 @@
/* File generated automatically by the QuickJS-ng compiler. */
#include <inttypes.h>
const uint32_t qjsc_builtin_array_fromasync_size = 826;
const uint8_t qjsc_builtin_array_fromasync[826] = {
0x15, 0x0d, 0x01, 0x1a, 0x61, 0x73, 0x79, 0x6e,
0x63, 0x49, 0x74, 0x65, 0x72, 0x61, 0x74, 0x6f,
0x72, 0x01, 0x10, 0x69, 0x74, 0x65, 0x72, 0x61,
0x74, 0x6f, 0x72, 0x01, 0x12, 0x61, 0x72, 0x72,
0x61, 0x79, 0x4c, 0x69, 0x6b, 0x65, 0x01, 0x0a,
0x6d, 0x61, 0x70, 0x46, 0x6e, 0x01, 0x0e, 0x74,
0x68, 0x69, 0x73, 0x41, 0x72, 0x67, 0x01, 0x0c,
0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x01, 0x02,
0x69, 0x01, 0x1a, 0x69, 0x73, 0x43, 0x6f, 0x6e,
0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x6f, 0x72,
0x01, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x01, 0x0c,
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x01, 0x08,
0x69, 0x74, 0x65, 0x72, 0x01, 0x1c, 0x6e, 0x6f,
0x74, 0x20, 0x61, 0x20, 0x66, 0x75, 0x6e, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x01, 0x08, 0x63, 0x61,
0x6c, 0x6c, 0x0c, 0x00, 0x02, 0x00, 0xa2, 0x01,
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x04, 0x01,
0xa4, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x43, 0x02,
0x01, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x01,
0x03, 0x05, 0xaa, 0x02, 0x00, 0x01, 0x40, 0xa0,
0x03, 0x00, 0x01, 0x40, 0xc6, 0x03, 0x00, 0x01,
0x40, 0xcc, 0x01, 0x00, 0x01, 0x40, 0xc8, 0x03,
0x00, 0x01, 0x40, 0x0c, 0x60, 0x02, 0x01, 0xf8,
0x01, 0x03, 0x0e, 0x01, 0x06, 0x05, 0x00, 0x86,
0x04, 0x11, 0xca, 0x03, 0x00, 0x01, 0x00, 0xcc,
0x03, 0x00, 0x01, 0x00, 0xce, 0x03, 0x00, 0x01,
0x00, 0xca, 0x03, 0x01, 0xff, 0xff, 0xff, 0xff,
0x0f, 0x20, 0xcc, 0x03, 0x01, 0x01, 0x20, 0xce,
0x03, 0x01, 0x02, 0x20, 0xd0, 0x03, 0x02, 0x00,
0x20, 0xd2, 0x03, 0x02, 0x04, 0x20, 0xd4, 0x03,
0x02, 0x05, 0x20, 0xd6, 0x03, 0x02, 0x06, 0x20,
0xd8, 0x03, 0x02, 0x07, 0x20, 0x64, 0x06, 0x08,
0x20, 0x82, 0x01, 0x07, 0x09, 0x20, 0xda, 0x03,
0x0a, 0x08, 0x30, 0x82, 0x01, 0x0d, 0x0b, 0x20,
0xd4, 0x01, 0x0d, 0x0c, 0x20, 0x10, 0x00, 0x01,
0x00, 0xa0, 0x03, 0x01, 0x03, 0xc6, 0x03, 0x02,
0x03, 0xc8, 0x03, 0x04, 0x03, 0xaa, 0x02, 0x00,
0x03, 0xcc, 0x01, 0x03, 0x03, 0x08, 0xc4, 0x0d,
0x62, 0x02, 0x00, 0x62, 0x01, 0x00, 0x62, 0x00,
0x00, 0xd3, 0xcb, 0xd4, 0x11, 0xf4, 0xec, 0x08,
0x0e, 0x39, 0x46, 0x00, 0x00, 0x00, 0xdc, 0xcc,
0xd5, 0x11, 0xf4, 0xec, 0x08, 0x0e, 0x39, 0x46,
0x00, 0x00, 0x00, 0xdd, 0xcd, 0x62, 0x07, 0x00,
0x62, 0x06, 0x00, 0x62, 0x05, 0x00, 0x62, 0x04,
0x00, 0x62, 0x03, 0x00, 0xd4, 0x39, 0x46, 0x00,
0x00, 0x00, 0xb0, 0xec, 0x16, 0xd4, 0x98, 0x04,
0x1b, 0x00, 0x00, 0x00, 0xb0, 0xec, 0x0c, 0xdf,
0x11, 0x04, 0xee, 0x00, 0x00, 0x00, 0x21, 0x01,
0x00, 0x30, 0x06, 0xce, 0xb6, 0xc4, 0x04, 0xc3,
0x0d, 0xf7, 0xc4, 0x05, 0x09, 0xc4, 0x06, 0xd3,
0xe0, 0x48, 0xc4, 0x07, 0x63, 0x07, 0x00, 0x07,
0xad, 0xec, 0x0f, 0x0a, 0x11, 0x64, 0x06, 0x00,
0x0e, 0xd3, 0xe1, 0x48, 0x11, 0x64, 0x07, 0x00,
0x0e, 0x63, 0x07, 0x00, 0x07, 0xad, 0x6a, 0xa6,
0x00, 0x00, 0x00, 0x62, 0x08, 0x00, 0x06, 0x11,
0xf4, 0xed, 0x0c, 0x71, 0x43, 0x32, 0x00, 0x00,
0x00, 0xc4, 0x08, 0x0e, 0xee, 0x05, 0x0e, 0xd3,
0xee, 0xf2, 0x63, 0x08, 0x00, 0x8e, 0x11, 0xed,
0x03, 0x0e, 0xb6, 0x11, 0x64, 0x08, 0x00, 0x0e,
0x63, 0x05, 0x00, 0xec, 0x0c, 0xc3, 0x0d, 0x11,
0x63, 0x08, 0x00, 0x21, 0x01, 0x00, 0xee, 0x06,
0xe2, 0x63, 0x08, 0x00, 0xf1, 0x11, 0x64, 0x03,
0x00, 0x0e, 0x63, 0x04, 0x00, 0x63, 0x08, 0x00,
0xa7, 0x6a, 0x2a, 0x01, 0x00, 0x00, 0x62, 0x09,
0x00, 0xd3, 0x63, 0x04, 0x00, 0x48, 0xc4, 0x09,
0x63, 0x06, 0x00, 0xec, 0x0a, 0x63, 0x09, 0x00,
0x8c, 0x11, 0x64, 0x09, 0x00, 0x0e, 0xd4, 0xec,
0x17, 0xd4, 0x43, 0xef, 0x00, 0x00, 0x00, 0xd5,
0x63, 0x09, 0x00, 0x63, 0x04, 0x00, 0x24, 0x03,
0x00, 0x8c, 0x11, 0x64, 0x09, 0x00, 0x0e, 0x5f,
0x04, 0x00, 0x63, 0x03, 0x00, 0x63, 0x04, 0x00,
0x92, 0x64, 0x04, 0x00, 0x0b, 0x63, 0x09, 0x00,
0x4d, 0x41, 0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3e,
0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3f, 0x00, 0x00,
0x00, 0xf3, 0x0e, 0xee, 0x9e, 0x62, 0x0a, 0x00,
0x63, 0x07, 0x00, 0x43, 0xef, 0x00, 0x00, 0x00,
0xd3, 0x24, 0x01, 0x00, 0xc4, 0x0a, 0x63, 0x05,
0x00, 0xec, 0x09, 0xc3, 0x0d, 0x11, 0x21, 0x00,
0x00, 0xee, 0x03, 0xe2, 0xf0, 0x11, 0x64, 0x03,
0x00, 0x0e, 0x6d, 0x8c, 0x00, 0x00, 0x00, 0x62,
0x0c, 0x00, 0x62, 0x0b, 0x00, 0x06, 0x11, 0xf4,
0xed, 0x13, 0x71, 0x43, 0x41, 0x00, 0x00, 0x00,
0xc4, 0x0b, 0x43, 0x6a, 0x00, 0x00, 0x00, 0xc4,
0x0c, 0x0e, 0xee, 0x10, 0x0e, 0x63, 0x0a, 0x00,
0x43, 0x6b, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00,
0x8c, 0xee, 0xe0, 0x63, 0x0c, 0x00, 0xed, 0x4e,
0x63, 0x06, 0x00, 0xec, 0x0a, 0x63, 0x0b, 0x00,
0x8c, 0x11, 0x64, 0x0b, 0x00, 0x0e, 0xd4, 0xec,
0x17, 0xd4, 0x43, 0xef, 0x00, 0x00, 0x00, 0xd5,
0x63, 0x0b, 0x00, 0x63, 0x04, 0x00, 0x24, 0x03,
0x00, 0x8c, 0x11, 0x64, 0x0b, 0x00, 0x0e, 0x5f,
0x04, 0x00, 0x63, 0x03, 0x00, 0x63, 0x04, 0x00,
0x92, 0x64, 0x04, 0x00, 0x0b, 0x63, 0x0b, 0x00,
0x4d, 0x41, 0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3e,
0x00, 0x00, 0x00, 0x0a, 0x4d, 0x3f, 0x00, 0x00,
0x00, 0xf3, 0x0e, 0xee, 0x83, 0x0e, 0x06, 0x6e,
0x0d, 0x00, 0x00, 0x00, 0x0e, 0xee, 0x1e, 0x6e,
0x05, 0x00, 0x00, 0x00, 0x30, 0x63, 0x0a, 0x00,
0x42, 0x06, 0x00, 0x00, 0x00, 0xec, 0x0d, 0x63,
0x0a, 0x00, 0x43, 0x06, 0x00, 0x00, 0x00, 0x24,
0x00, 0x00, 0x0e, 0x6f, 0x63, 0x03, 0x00, 0x63,
0x04, 0x00, 0x44, 0x32, 0x00, 0x00, 0x00, 0x63,
0x03, 0x00, 0x2f, 0xc1, 0x00, 0x28, 0xc1, 0x00,
0xcf, 0x28,
};
@@ -0,0 +1,36 @@
;(function(Array, TypeError, asyncIterator, defineProperty, iterator) {
"use strict" // result.length=i should throw if .length is not writable
return async function fromAsync(arrayLike, mapFn=undefined, thisArg=undefined) {
if (mapFn !== undefined && typeof mapFn !== "function") throw new TypeError("not a function")
let result, i = 0, isConstructor = typeof this === "function"
let sync = false, method = arrayLike[asyncIterator]
if (method == null) sync = true, method = arrayLike[iterator]
if (method == null) {
let {length} = arrayLike
length = +length || 0
result = isConstructor ? new this(length) : Array(length)
while (i < length) {
let value = arrayLike[i]
if (sync) value = await value
if (mapFn) value = await mapFn.call(thisArg, value, i)
defineProperty(result, i++, {value, configurable: true, writable: true})
}
} else {
const iter = method.call(arrayLike)
result = isConstructor ? new this() : Array()
try {
for (;;) {
let {value, done} = await iter.next()
if (done) break
if (sync) value = await value
if (mapFn) value = await mapFn.call(thisArg, value, i)
defineProperty(result, i++, {value, configurable: true, writable: true})
}
} finally {
if (iter.return) iter.return()
}
}
result.length = i
return result
}
})
+17
View File
@@ -0,0 +1,17 @@
// note: file is not actually compiled, only checked for C syntax errors
#include "quickjs.h"
int main(void)
{
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
JS_FreeValue(ctx, JS_NAN);
JS_FreeValue(ctx, JS_UNDEFINED);
JS_FreeValue(ctx, JS_NewFloat64(ctx, 42));
// not a legal way of using JS_MKPTR but this is here
// to have the compiler syntax-check its definition
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_UNINITIALIZED, 0));
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return 0;
}
File diff suppressed because it is too large Load Diff
+660
View File
@@ -0,0 +1,660 @@
/*
* C utilities
*
* Copyright (c) 2017 Fabrice Bellard
* Copyright (c) 2018 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef CUTILS_H
#define CUTILS_H
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <math.h>
#ifdef __cplusplus
extern "C" {
#endif
#if defined(_MSC_VER)
#include <winsock2.h>
#include <malloc.h>
#define alloca _alloca
#define ssize_t ptrdiff_t
#endif
#if defined(__APPLE__)
#include <malloc/malloc.h>
#elif defined(__linux__) || defined(__ANDROID__) || defined(__CYGWIN__) || defined(__GLIBC__)
#include <malloc.h>
#elif defined(__FreeBSD__)
#include <malloc_np.h>
#elif defined(_WIN32)
#include <windows.h>
#endif
#if !defined(_WIN32) && !defined(EMSCRIPTEN) && !defined(__wasi__)
#include <errno.h>
#include <pthread.h>
#endif
#if !defined(_WIN32)
#include <limits.h>
#include <unistd.h>
#endif
#if defined(_MSC_VER) && !defined(__clang__)
# define likely(x) (x)
# define unlikely(x) (x)
# define force_inline __forceinline
# define no_inline __declspec(noinline)
# define __maybe_unused
# define __attribute__(x)
# define __attribute(x)
#else
# define likely(x) __builtin_expect(!!(x), 1)
# define unlikely(x) __builtin_expect(!!(x), 0)
# define force_inline inline __attribute__((always_inline))
# define no_inline __attribute__((noinline))
# define __maybe_unused __attribute__((unused))
#endif
#if defined(_MSC_VER) && !defined(__clang__)
#include <math.h>
#define INF INFINITY
#define NEG_INF -INFINITY
#else
#define INF (1.0/0.0)
#define NEG_INF (-1.0/0.0)
#endif
#ifndef offsetof
#define offsetof(type, field) ((size_t) &((type *)0)->field)
#endif
#ifndef countof
#define countof(x) (sizeof(x) / sizeof((x)[0]))
#ifndef endof
#define endof(x) ((x) + countof(x))
#endif
#endif
#ifndef container_of
/* return the pointer of type 'type *' containing 'ptr' as field 'member' */
#define container_of(ptr, type, member) ((type *)((uint8_t *)(ptr) - offsetof(type, member)))
#endif
#if defined(_MSC_VER) || defined(__cplusplus)
#define minimum_length(n) n
#else
#define minimum_length(n) static n
#endif
/* Borrowed from Folly */
#ifndef JS_PRINTF_FORMAT
#ifdef _MSC_VER
#include <sal.h>
#define JS_PRINTF_FORMAT _Printf_format_string_
#define JS_PRINTF_FORMAT_ATTR(format_param, dots_param)
#else
#define JS_PRINTF_FORMAT
#if !defined(__clang__) && defined(__GNUC__)
#define JS_PRINTF_FORMAT_ATTR(format_param, dots_param) \
__attribute__((format(gnu_printf, format_param, dots_param)))
#else
#define JS_PRINTF_FORMAT_ATTR(format_param, dots_param) \
__attribute__((format(printf, format_param, dots_param)))
#endif
#endif
#endif
#if defined(PATH_MAX)
# define JS__PATH_MAX PATH_MAX
#elif defined(_WIN32)
# define JS__PATH_MAX 32767
#else
# define JS__PATH_MAX 8192
#endif
void js__pstrcpy(char *buf, int buf_size, const char *str);
char *js__pstrcat(char *buf, int buf_size, const char *s);
int js__strstart(const char *str, const char *val, const char **ptr);
int js__has_suffix(const char *str, const char *suffix);
static inline uint8_t is_be(void) {
union {
uint16_t a;
uint8_t b;
} u = { 0x100 };
return u.b;
}
static inline int max_int(int a, int b)
{
if (a > b)
return a;
else
return b;
}
static inline int min_int(int a, int b)
{
if (a < b)
return a;
else
return b;
}
static inline uint32_t max_uint32(uint32_t a, uint32_t b)
{
if (a > b)
return a;
else
return b;
}
static inline uint32_t min_uint32(uint32_t a, uint32_t b)
{
if (a < b)
return a;
else
return b;
}
static inline int64_t max_int64(int64_t a, int64_t b)
{
if (a > b)
return a;
else
return b;
}
static inline int64_t min_int64(int64_t a, int64_t b)
{
if (a < b)
return a;
else
return b;
}
/* WARNING: undefined if a = 0 */
static inline int clz32(unsigned int a)
{
#if defined(_MSC_VER) && !defined(__clang__)
unsigned long index;
_BitScanReverse(&index, a);
return 31 - index;
#else
return __builtin_clz(a);
#endif
}
/* WARNING: undefined if a = 0 */
static inline int clz64(uint64_t a)
{
#if defined(_MSC_VER) && !defined(__clang__)
#if INTPTR_MAX == INT64_MAX
unsigned long index;
_BitScanReverse64(&index, a);
return 63 - index;
#else
if (a >> 32)
return clz32((unsigned)(a >> 32));
else
return clz32((unsigned)a) + 32;
#endif
#else
return __builtin_clzll(a);
#endif
}
/* WARNING: undefined if a = 0 */
static inline int ctz32(unsigned int a)
{
#if defined(_MSC_VER) && !defined(__clang__)
unsigned long index;
_BitScanForward(&index, a);
return index;
#else
return __builtin_ctz(a);
#endif
}
/* WARNING: undefined if a = 0 */
static inline int ctz64(uint64_t a)
{
#if defined(_MSC_VER) && !defined(__clang__)
unsigned long index;
_BitScanForward64(&index, a);
return index;
#else
return __builtin_ctzll(a);
#endif
}
static inline uint64_t get_u64(const uint8_t *tab)
{
uint64_t v;
memcpy(&v, tab, sizeof(v));
return v;
}
static inline int64_t get_i64(const uint8_t *tab)
{
int64_t v;
memcpy(&v, tab, sizeof(v));
return v;
}
static inline void put_u64(uint8_t *tab, uint64_t val)
{
memcpy(tab, &val, sizeof(val));
}
static inline uint32_t get_u32(const uint8_t *tab)
{
uint32_t v;
memcpy(&v, tab, sizeof(v));
return v;
}
static inline int32_t get_i32(const uint8_t *tab)
{
int32_t v;
memcpy(&v, tab, sizeof(v));
return v;
}
static inline void put_u32(uint8_t *tab, uint32_t val)
{
memcpy(tab, &val, sizeof(val));
}
static inline uint32_t get_u16(const uint8_t *tab)
{
uint16_t v;
memcpy(&v, tab, sizeof(v));
return v;
}
static inline int32_t get_i16(const uint8_t *tab)
{
int16_t v;
memcpy(&v, tab, sizeof(v));
return v;
}
static inline void put_u16(uint8_t *tab, uint16_t val)
{
memcpy(tab, &val, sizeof(val));
}
static inline uint32_t get_u8(const uint8_t *tab)
{
return *tab;
}
static inline int32_t get_i8(const uint8_t *tab)
{
return (int8_t)*tab;
}
static inline void put_u8(uint8_t *tab, uint8_t val)
{
*tab = val;
}
#ifndef bswap16
static inline uint16_t bswap16(uint16_t x)
{
return (x >> 8) | (x << 8);
}
#endif
#ifndef bswap32
static inline uint32_t bswap32(uint32_t v)
{
return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >> 8) |
((v & 0x0000ff00) << 8) | ((v & 0x000000ff) << 24);
}
#endif
#ifndef bswap64
static inline uint64_t bswap64(uint64_t v)
{
return ((v & ((uint64_t)0xff << (7 * 8))) >> (7 * 8)) |
((v & ((uint64_t)0xff << (6 * 8))) >> (5 * 8)) |
((v & ((uint64_t)0xff << (5 * 8))) >> (3 * 8)) |
((v & ((uint64_t)0xff << (4 * 8))) >> (1 * 8)) |
((v & ((uint64_t)0xff << (3 * 8))) << (1 * 8)) |
((v & ((uint64_t)0xff << (2 * 8))) << (3 * 8)) |
((v & ((uint64_t)0xff << (1 * 8))) << (5 * 8)) |
((v & ((uint64_t)0xff << (0 * 8))) << (7 * 8));
}
#endif
static inline void inplace_bswap16(uint8_t *tab) {
put_u16(tab, bswap16(get_u16(tab)));
}
static inline void inplace_bswap32(uint8_t *tab) {
put_u32(tab, bswap32(get_u32(tab)));
}
static inline double fromfp16(uint16_t v) {
double d, s;
int e;
if ((v & 0x7C00) == 0x7C00) {
d = (v & 0x3FF) ? NAN : INFINITY;
} else {
d = (v & 0x3FF) / 1024.;
e = (v & 0x7C00) >> 10;
if (e == 0) {
e = -14;
} else {
d += 1;
e -= 15;
}
d = scalbn(d, e);
}
s = (v & 0x8000) ? -1.0 : 1.0;
return d * s;
}
static inline uint16_t tofp16(double d) {
uint16_t f, s;
double t;
int e;
s = 0;
if (copysign(1, d) < 0) { // preserve sign when |d| is negative zero
d = -d;
s = 0x8000;
}
if (isinf(d))
return s | 0x7C00;
if (isnan(d))
return s | 0x7C01;
if (d == 0)
return s | 0;
d = 2 * frexp(d, &e);
e--;
if (e > 15)
return s | 0x7C00; // out of range, return +/-infinity
if (e < -25) {
d = 0;
e = 0;
} else if (e < -14) {
d = scalbn(d, e + 14);
e = 0;
} else {
d -= 1;
e += 15;
}
d *= 1024.;
f = (uint16_t)d;
t = d - f;
if (t < 0.5)
goto done;
if (t == 0.5)
if ((f & 1) == 0)
goto done;
// adjust for rounding
if (++f == 1024) {
f = 0;
if (++e == 31)
return s | 0x7C00; // out of range, return +/-infinity
}
done:
return s | (e << 10) | f;
}
static inline int isfp16nan(uint16_t v) {
return (v & 0x7FFF) > 0x7C00;
}
static inline int isfp16zero(uint16_t v) {
return (v & 0x7FFF) == 0;
}
/* XXX: should take an extra argument to pass slack information to the caller */
typedef void *DynBufReallocFunc(void *opaque, void *ptr, size_t size);
typedef struct DynBuf {
uint8_t *buf;
size_t size;
size_t allocated_size;
bool error; /* true if a memory allocation error occurred */
DynBufReallocFunc *realloc_func;
void *opaque; /* for realloc_func */
} DynBuf;
void dbuf_init(DynBuf *s);
void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func);
int dbuf_realloc(DynBuf *s, size_t new_size);
int dbuf_write(DynBuf *s, size_t offset, const void *data, size_t len);
int dbuf_put(DynBuf *s, const void *data, size_t len);
int dbuf_put_self(DynBuf *s, size_t offset, size_t len);
int dbuf_putc(DynBuf *s, uint8_t c);
int dbuf_putstr(DynBuf *s, const char *str);
static inline int dbuf_put_u16(DynBuf *s, uint16_t val)
{
return dbuf_put(s, (uint8_t *)&val, 2);
}
static inline int dbuf_put_u32(DynBuf *s, uint32_t val)
{
return dbuf_put(s, (uint8_t *)&val, 4);
}
static inline int dbuf_put_u64(DynBuf *s, uint64_t val)
{
return dbuf_put(s, (uint8_t *)&val, 8);
}
int JS_PRINTF_FORMAT_ATTR(2, 3) dbuf_printf(DynBuf *s, JS_PRINTF_FORMAT const char *fmt, ...);
void dbuf_free(DynBuf *s);
static inline bool dbuf_error(DynBuf *s) {
return s->error;
}
static inline void dbuf_set_error(DynBuf *s)
{
s->error = true;
}
/*---- UTF-8 and UTF-16 handling ----*/
#define UTF8_CHAR_LEN_MAX 4
enum {
UTF8_PLAIN_ASCII = 0, // 7-bit ASCII plain text
UTF8_NON_ASCII = 1, // has non ASCII code points (8-bit or more)
UTF8_HAS_16BIT = 2, // has 16-bit code points
UTF8_HAS_NON_BMP1 = 4, // has non-BMP1 code points, needs UTF-16 surrogate pairs
UTF8_HAS_ERRORS = 8, // has encoding errors
};
int utf8_scan(const char *buf, size_t len, size_t *plen);
size_t utf8_encode_len(uint32_t c);
size_t utf8_encode(uint8_t buf[minimum_length(UTF8_CHAR_LEN_MAX)], uint32_t c);
uint32_t utf8_decode_len(const uint8_t *p, size_t max_len, const uint8_t **pp);
uint32_t utf8_decode(const uint8_t *p, const uint8_t **pp);
size_t utf8_decode_buf8(uint8_t *dest, size_t dest_len, const char *src, size_t src_len);
size_t utf8_decode_buf16(uint16_t *dest, size_t dest_len, const char *src, size_t src_len);
size_t utf8_encode_buf8(char *dest, size_t dest_len, const uint8_t *src, size_t src_len);
size_t utf8_encode_buf16(char *dest, size_t dest_len, const uint16_t *src, size_t src_len);
static inline bool is_surrogate(uint32_t c)
{
return (c >> 11) == (0xD800 >> 11); // 0xD800-0xDFFF
}
static inline bool is_hi_surrogate(uint32_t c)
{
return (c >> 10) == (0xD800 >> 10); // 0xD800-0xDBFF
}
static inline bool is_lo_surrogate(uint32_t c)
{
return (c >> 10) == (0xDC00 >> 10); // 0xDC00-0xDFFF
}
static inline uint32_t get_hi_surrogate(uint32_t c)
{
return (c >> 10) - (0x10000 >> 10) + 0xD800;
}
static inline uint32_t get_lo_surrogate(uint32_t c)
{
return (c & 0x3FF) | 0xDC00;
}
static inline uint32_t from_surrogate(uint32_t hi, uint32_t lo)
{
return 65536 + 1024 * (hi & 1023) + (lo & 1023);
}
static inline int from_hex(int c)
{
if (c >= '0' && c <= '9')
return c - '0';
else if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
else if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
else
return -1;
}
static inline uint8_t is_upper_ascii(uint8_t c) {
return c >= 'A' && c <= 'Z';
}
static inline uint8_t to_upper_ascii(uint8_t c) {
return c >= 'a' && c <= 'z' ? c - 'a' + 'A' : c;
}
void rqsort(void *base, size_t nmemb, size_t size,
int (*cmp)(const void *, const void *, void *),
void *arg);
static inline uint64_t float64_as_uint64(double d)
{
union {
double d;
uint64_t u64;
} u;
u.d = d;
return u.u64;
}
static inline double uint64_as_float64(uint64_t u64)
{
union {
double d;
uint64_t u64;
} u;
u.u64 = u64;
return u.d;
}
int64_t js__gettimeofday_us(void);
uint64_t js__hrtime_ns(void);
static inline size_t js__malloc_usable_size(const void *ptr)
{
#if defined(__APPLE__)
return malloc_size(ptr);
#elif defined(_WIN32)
return _msize((void *)ptr);
#elif defined(__linux__) || defined(__ANDROID__) || defined(__CYGWIN__) || defined(__FreeBSD__) || defined(__GLIBC__)
return malloc_usable_size((void *)ptr);
#else
return 0;
#endif
}
int js_exepath(char* buffer, size_t* size);
/* Cross-platform threading APIs. */
#if defined(EMSCRIPTEN) || defined(__wasi__)
#define JS_HAVE_THREADS 0
#else
#define JS_HAVE_THREADS 1
#if defined(_WIN32)
#define JS_ONCE_INIT INIT_ONCE_STATIC_INIT
typedef INIT_ONCE js_once_t;
typedef CRITICAL_SECTION js_mutex_t;
typedef CONDITION_VARIABLE js_cond_t;
typedef HANDLE js_thread_t;
#else
#define JS_ONCE_INIT PTHREAD_ONCE_INIT
typedef pthread_once_t js_once_t;
typedef pthread_mutex_t js_mutex_t;
typedef pthread_cond_t js_cond_t;
typedef pthread_t js_thread_t;
#endif
void js_once(js_once_t *guard, void (*callback)(void));
void js_mutex_init(js_mutex_t *mutex);
void js_mutex_destroy(js_mutex_t *mutex);
void js_mutex_lock(js_mutex_t *mutex);
void js_mutex_unlock(js_mutex_t *mutex);
void js_cond_init(js_cond_t *cond);
void js_cond_destroy(js_cond_t *cond);
void js_cond_signal(js_cond_t *cond);
void js_cond_broadcast(js_cond_t *cond);
void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex);
int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout);
enum {
JS_THREAD_CREATE_DETACHED = 1,
};
// creates threads with 2 MB stacks (glibc default)
int js_thread_create(js_thread_t *thrd, void (*start)(void *), void *arg,
int flags);
int js_thread_join(js_thread_t thrd);
#endif /* !defined(EMSCRIPTEN) && !defined(__wasi__) */
// JS requires strict rounding behavior. Turn on 64-bits double precision
// and disable x87 80-bits extended precision for intermediate floating-point
// results. 0x300 is extended precision, 0x200 is double precision.
// Note that `*&cw` in the asm constraints looks redundant but isn't.
#if defined(__i386__) && !defined(_MSC_VER)
#define JS_X87_FPCW_SAVE_AND_ADJUST(cw) \
unsigned short cw; \
__asm__ __volatile__("fnstcw %0" : "=m"(*&cw)); \
do { \
unsigned short t = 0x200 | (cw & ~0x300); \
__asm__ __volatile__("fldcw %0" : /*empty*/ : "m"(*&t)); \
} while (0)
#define JS_X87_FPCW_RESTORE(cw) \
__asm__ __volatile__("fldcw %0" : /*empty*/ : "m"(*&cw))
#else
#define JS_X87_FPCW_SAVE_AND_ADJUST(cw)
#define JS_X87_FPCW_RESTORE(cw)
#endif
#ifdef __cplusplus
} /* extern "C" { */
#endif
#endif /* CUTILS_H */
@@ -0,0 +1,2 @@
// note: file is not actually compiled, only checked for C++ syntax errors
#include "ctest.c"
@@ -0,0 +1,20 @@
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
@@ -0,0 +1 @@
18
@@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};
@@ -0,0 +1,63 @@
---
sidebar_position: 2
---
# Building
QuickJS uses [CMake] as its main build system, with an additional helper [Makefile].
:::note
Windows users will need to run the CMake commands manually.
:::
## Getting the source
```bash
git clone https://github.com/quickjs-ng/quickjs.git
cd quickjs
```
## Compiling everything
```bash
make
```
This will build the `qjs` and `qjsc` executables and other test tools. Head over [here](./cli) for
instructions on how to use them.
## Amalgamated builds
The amalgamated build rolls individual source files into a single big file.
It's a good choice for projects that want to use QuickJS without CMake.
Download quickjs-amalgam.zip from [Releases](https://github.com/quickjs-ng/quickjs/releases)
To enable the std, os and bjson modules, compile quickjs-amalgam.c with
`-DQJS_BUILD_LIBC`.
## Debug builds
```bash
make debug
```
This will produce a debug build without optimizations, suitable for developers.
## Running test262
```bash
make test262
```
This will run the test262 suite.
```bash
make test262-update
```
This will run the test262 suite and update the error / pass report, useful after
implementing a new feature that would alter the result of the test suite.
[CMake]: https://cmake.org
[Makefile]: https://www.gnu.org/software/make/
@@ -0,0 +1,130 @@
---
sidebar_position: 4
---
# The qjs and qjsc CLI tools
## `qjs` - The QuickJS JavaScript interpreter
The `qjs` executable runs the JavaScript interpreter. It includes a simple standard
library and REPL.
```
$ qjs
usage: qjs [options] [file [args]]
-h --help list options
-e --eval EXPR evaluate EXPR
-i --interactive go to interactive mode
-m --module load as ES6 module (default=autodetect)
--script load as ES6 script (default=autodetect)
-I --include file include an additional file
--std make 'std', 'os' and 'bjson' available to script
-T --trace trace memory allocation
-d --dump dump the memory usage stats
-D --dump-flags flags for dumping debug data (see DUMP_* defines)
-c --compile FILE compile the given JS file as a standalone executable
-o --out FILE output file for standalone executables
--exe select the executable to use as the base, defaults to the current one
--memory-limit n limit the memory usage to 'n' Kbytes
--stack-size n limit the stack size to 'n' Kbytes
--unhandled-rejection dump unhandled promise rejections
-q --quit just instantiate the interpreter and quit
```
The following dump flags are supported:
```
DUMP_BYTECODE_FINAL 0x01 /* dump pass 3 final byte code */
DUMP_BYTECODE_PASS2 0x02 /* dump pass 2 code */
DUMP_BYTECODE_PASS1 0x04 /* dump pass 1 code */
DUMP_BYTECODE_HEX 0x10 /* dump bytecode in hex */
DUMP_BYTECODE_PC2LINE 0x20 /* dump line number table */
DUMP_BYTECODE_STACK 0x40 /* dump compute_stack_size */
DUMP_BYTECODE_STEP 0x80 /* dump executed bytecode */
DUMP_READ_OBJECT 0x100 /* dump the marshalled objects at load time */
DUMP_FREE 0x200 /* dump every object free */
DUMP_GC 0x400 /* dump the occurrence of the automatic GC */
DUMP_GC_FREE 0x800 /* dump objects freed by the GC */
DUMP_MODULE_RESOLVE 0x1000 /* dump module resolution steps */
DUMP_PROMISE 0x2000 /* dump promise steps */
DUMP_LEAKS 0x4000 /* dump leaked objects and strings in JS_FreeRuntime */
DUMP_ATOM_LEAKS 0x8000 /* dump leaked atoms in JS_FreeRuntime */
DUMP_MEM 0x10000 /* dump memory usage in JS_FreeRuntime */
DUMP_OBJECTS 0x20000 /* dump objects in JS_FreeRuntime */
DUMP_ATOMS 0x40000 /* dump atoms in JS_FreeRuntime */
DUMP_SHAPES 0x80000 /* dump shapes in JS_FreeRuntime */
```
### Creating standalone executables
With the `qjs` CLI it's possible to create standalone executables that will bundle the given JavaScript file
alongside the binary.
```
$ qjs -c app.js -o app --exe qjs
```
The resulting `app` binary will have the same runtime dependencies as the `qjs` binary. This is acomplished
by compiling the target JavaScript file to bytecode and adding it a copy of the executable, with a little
trailer to help locate it. `--exe` expects the absolute path to `qjs`, e.g., `~/bin/qjs` or `$HOME/bin/qjs`.
Rather than using the current executable, it's possible to use the `--exe` switch to create standalone
executables for other platforms.
No JavaScript bundling is performed, the specified JS file cannot depend on other files. A bundler such
as `esbuild` can be used to generate an app bundle which can then be turned into the executable.
```
npx esbuild my-app/index.js \
--bundle \
--outfile=app.js \
--external:qjs:* \
--minify \
--target=es2023 \
--platform=neutral \
--format=esm \
--main-fields=main,module
```
## `qjsc` - The QuickJS JavaScript compiler
The `qjsc` executable runs the JavaScript compiler, it can generate bytecode from
source files which can then be embedded in an executable, or it can generate the necessary
scaffolding to build a C application which embeds QuickJS.
```
$ qjsc
usage: qjsc [options] [files]
options are:
-b output raw bytecode instead of C code
-e output main() and bytecode in a C file
-o output set the output filename
-n script_name set the script name (as used in stack traces)
-N cname set the C name of the generated data
-m compile as JavaScript module (default=autodetect)
-D module_name compile a dynamically loaded module or worker
-M module_name[,cname] add initialization code for an external C module
-p prefix set the prefix of the generated C names
-s strip the source code, specify twice to also strip debug info
-S n set the maximum stack size to 'n' bytes (default=262144)
```
Here is an example on how to create a standalone executable that embeds QuickJS
and the `examples/hello.js` JavaScript file:
```bash
# Make sure you are in the QuickJS source directory.
$ cc hello.c cutils.c dtoa.c libregexp.c libunicode.c quickjs.c quickjs-libc.c -I. -o hello
```
The resulting binary `hello` will be in the current directory.
```bash
$ ./hello
Hello World
```
:::note
See the ["Creating standalone executables"](#creating-standalone-executables) section for a simpler way.
:::
@@ -0,0 +1,7 @@
{
"label": "Developer Guide",
"position": 6,
"link": {
"type": "generated-index"
}
}
@@ -0,0 +1,3 @@
# API Reference
WIP.
@@ -0,0 +1,117 @@
# Internals
## Bytecode
The compiler generates bytecode directly with no intermediate
representation such as a parse tree, hence it is very fast. Several
optimizations passes are done over the generated bytecode.
A stack-based bytecode was chosen because it is simple and generates
compact code.
For each function, the maximum stack size is computed at compile time so that
no runtime stack overflow tests are needed.
A separate compressed line number table is maintained for the debug
information.
Access to closure variables is optimized and is almost as fast as local
variables.
Direct `eval` in strict mode is optimized.
## Runtime
### Strings
Strings are stored either as an 8 bit or a 16 bit array of
characters. Hence random access to characters is always fast.
The C API provides functions to convert JavaScript Strings to C UTF-8 encoded
strings. The most common case where the JavaScript string contains
only ASCII characters involves no copying.
### Objects
The object shapes (object prototype, property names and flags) are shared
between objects to save memory.
Arrays with no holes (except at the end of the array) are optimized.
TypedArray accesses are optimized.
### Atoms
Object property names and some strings are stored as Atoms (unique
strings) to save memory and allow fast comparison. Atoms are
represented as a 32 bit integer. Half of the atom range is reserved for
immediate integer literals from 0 to 2^31-1.
### Numbers
Numbers are represented either as 32-bit signed integers or 64-bit IEEE-754
floating point values. Most operations have fast paths for the 32-bit
integer case.
### Garbage collection
Reference counting is used to free objects automatically and
deterministically. A separate cycle removal pass is done when the allocated
memory becomes too large. The cycle removal algorithm only uses the
reference counts and the object content, so no explicit garbage
collection roots need to be manipulated in the C code.
### JSValue
It is a JavaScript value which can be a primitive type (such as
Number, String, ...) or an Object. NaN boxing is used in the 32-bit version
to store 64-bit floating point numbers. The representation is
optimized so that 32-bit integers and reference counted values can be
efficiently tested.
In 64-bit code, JSValue are 128-bit large and no NaN boxing is used. The
rationale is that in 64-bit code memory usage is less critical.
In both cases (32 or 64 bits), JSValue exactly fits two CPU registers,
so it can be efficiently returned by C functions.
### Function call
The engine is optimized so that function calls are fast. The system
stack holds the JavaScript parameters and local variables.
### RegExp
A specific regular expression engine was developed. It is both small
and efficient and supports all the ES2020+ features including the
Unicode properties. As the JavaScript compiler, it directly generates
bytecode without a parse tree.
Backtracking with an explicit stack is used so that there is no
recursion on the system stack. Simple quantifiers are specifically
optimized to avoid recursions.
Infinite recursions coming from quantifiers with empty terms are
avoided.
The full regexp library weighs about 15 KiB (x86 code), excluding the
Unicode library.
### Unicode
A specific Unicode library was developed so that there is no
dependency on an external large Unicode library such as ICU. All the
Unicode tables are compressed while keeping a reasonable access
speed.
The library supports case conversion, Unicode normalization, Unicode
script queries, Unicode general category queries and all Unicode
binary properties.
The full Unicode library weighs about 45 KiB (x86 code).
### BigInt
BigInt is implemented with the [libbf](https://bellard.org/libbf) library.
It weights about 90 KiB (x86 code) and provides arbitrary precision IEEE 754 floating
point operations and transcendental functions with exact rounding.
@@ -0,0 +1,113 @@
---
sidebar_position: 1
---
# The QuickJS C API
The C API was designed to be simple and efficient. The C API is
defined in the header `quickjs.h`.
## Runtime and contexts
`JSRuntime` represents a JavaScript runtime corresponding to an
object heap. Several runtimes can exist at the same time but they
cannot exchange objects. Inside a given runtime, no multi-threading is
supported.
`JSContext` represents a JavaScript context (or Realm). Each
JSContext has its own global objects and system objects. There can be
several JSContexts per JSRuntime and they can share objects, similar
to frames of the same origin sharing JavaScript objects in a
web browser.
## JSValue
`JSValue` represents a JavaScript value which can be a primitive
type or an object. Reference counting is used, so it is important to
explicitly duplicate (`JS_DupValue()`, increment the reference
count) or free (`JS_FreeValue()`, decrement the reference count)
JSValues.
## C functions
C functions can be created with
`JS_NewCFunction()`. `JS_SetPropertyFunctionList()` is a
shortcut to easily add functions, setters and getters properties to a
given object.
Unlike other embedded JavaScript engines, there is no implicit stack,
so C functions get their parameters as normal C parameters. As a
general rule, C functions take constant `JSValue`s as parameters
(so they don't need to free them) and return a newly allocated (=live)
`JSValue`.
## Exceptions
Most C functions can return a JavaScript exception. It
must be explicitly tested and handled by the C code. The specific
`JSValue` `JS_EXCEPTION` indicates that an exception
occurred. The actual exception object is stored in the
`JSContext` and can be retrieved with `JS_GetException()`.
## Script evaluation
Use `JS_Eval()` to evaluate a script or module source.
If the script or module was compiled to bytecode with `qjsc`, it
can be evaluated by calling `js_std_eval_binary()`. The advantage
is that no compilation is needed so it is faster and smaller because
the compiler can be removed from the executable if no `eval` is
required.
Note: the bytecode format is linked to a given QuickJS
version. Moreover, no security check is done before its
execution. Hence the bytecode should not be loaded from untrusted
sources.
## JS Classes
C opaque data can be attached to a JavaScript object. The type of the
C opaque data is determined with the class ID (`JSClassID`) of
the object. Hence the first step is to register a new class ID and JS
class (`JS_NewClassID()`, `JS_NewClass()`). Then you can
create objects of this class with `JS_NewObjectClass()` and get or
set the C opaque point with `JS_GetOpaque()` / `JS_SetOpaque()`.
When defining a new JS class, it is possible to declare a finalizer
which is called when the object is destroyed. The finalizer should be
used to release C resources. It is invalid to execute JS code from
it. A `gc_mark` method can be provided so that the cycle removal
algorithm can find the other objects referenced by this object. Other
methods are available to define exotic object behaviors.
The Class ID are allocated per-runtime. The
`JSClass` are allocated per `JSRuntime`. `JS_SetClassProto()`
is used to define a prototype for a given class in a given
`JSContext`. `JS_NewObjectClass()` sets this prototype in the
created object.
Examples are available in `quickjs-libc.c`.
## C Modules
Native ES6 modules are supported and can be dynamically or statically
linked. The standard library `quickjs-libc.c` is a good example
of a native module.
## Memory handling
Use `JS_SetMemoryLimit()` to set a global memory allocation limit
to a given `JSRuntime`.
Custom memory allocation functions can be provided with `JS_NewRuntime2()`.
The maximum system stack size can be set with `JS_SetMaxStackSize()`.
## Execution timeout and interrupts
Use `JS_SetInterruptHandler()` to set a callback which is
regularly called by the engine when it is executing code. This
callback can be used to implement an execution timeout.
It is used by the command line interpreter to implement a
`Ctrl-C` handler.
@@ -0,0 +1,68 @@
# Differences with bellard/quickjs
This project aims to be a drop-in replacement for those already using QuickJS.
Minimal API changes might be necessary.
## Community development
NG is developed in the open, interacting with the wider community and through
these interactions many improvements have already been made, including the incorporation
of patches previously maintained in other forks.
Each PR is reviewed, iterated on, and merged in GitHub.
To date, NG has had over 40 distinct contributors and over 400 PRs.
## Consistent release cadence
As the project moves forward, a steady cadence of releases has been maintained, with an
average of a new release every 2 months.
## Testing
Since its inception testing has been a focus. Each PR is tested in over 50 configurations,
involving different operating systems, build types and sanitizers.
The `test262` suite is also ran for every change.
## Cross-platform support
In order to better support other platforms such as Windows the build system was
changed to use [CMake].
In addition, Windows is treated as a first class citizen, with the addition of support
for the MSVC compiler.
[CMake]: https://cmake.org/
## Performance
While being an interpreter limits the performance in comparison with other engines which
use a JIT, several significant performance improvements have been made:
- Opcode fusion
- Polymorphic inline caching
- Memory allocation improvements
- Improved parse speeds
## New ECMAScript APIs
The main focus of NG is to deliver state-of-the-art JavaScript features. Typically once they
are stable (stage 4) but sometimes even at earlier stages. Here is a non-exhaustive list
of ES features present in NG:
- Resizable ArrayBuffer
- Float16Array
- WeakRef
- FinalizationRegistry
- Iterator Helpers
- Promise.try
- Error.isError
- Set operations
Some non-standard but widely used APIs have also been added:
- V8's [stack trace API](https://v8.dev/docs/stack-trace-api)
- `Error.captureStackTrace`
- `Error.prepareStackTrace`
- `Error.stackTraceLimit`
@@ -0,0 +1,11 @@
---
sidebar_position: 7
---
# ECMAScript Features
QuickJS aims to support the latest available ECMAScript features once they hit the spec.
Progress on _test262_ compliance can be checked [here](https://test262.fyi/#|qjs_ng).
Due to size constraints it is unlikely QuickJS will ever support the [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) APIs.
@@ -0,0 +1,34 @@
---
sidebar_position: 3
---
# Installation
Installing QuickJS is simple, and we provide several ways to do so.
## Build from source
If you built it from source as outlined in [building](./building) you can just run:
```bash
make install
```
and it will be installed in your system. The default installation path is `/usr/local`.
## Using a prebuilt binary
Each [release on GitHub] includes binaries for several systems and architectures.
## Using jsvu
As of version 2.2.0 of `jsvu`, QuickJS-ng will be installed when the `quickjs` engine is requested.
```bash
npm install jsvu -g
```
[release on GitHub]: https://github.com/quickjs-ng/quickjs/releases
@@ -0,0 +1,33 @@
---
slug: /
sidebar_position: 1
sidebar_label: Welcome
---
# Welcome to QuickJS-NG
QuickJS is a small and embeddable JavaScript engine. It aims to support the latest
[ECMAScript] specification.
This project is a _fork_ of the [original QuickJS project] by Fabrice Bellard and Charlie Gordon, after it went dormant, with the intent of reigniting its development.
This project is focused on (but not limited to):
- Community development
- Testing
- Cross-platform support
- ES features
You can check the differences with the original project [here.](./diff)
:::note
This site is under construction, the entire API is not yet documented.
:::
## Getting Started
Head over to [building](./building) if you want to build QuickJS from source or [installation](./installation)
for installing it from prebuilt binaries.
[ECMAScript]: https://tc39.es/ecma262/
[original QuickJS project]: https://bellard.org/quickjs
@@ -0,0 +1,44 @@
# Projects using NG
Here is a list of projects currently using, supporting or migrating to QuickJS-NG.
If you want yours to be listed here, please open a PR!
## [txiki.js](https://github.com/saghul/txiki.js)
A tiny JavaScript runtime.
## [radare2](https://github.com/radareorg/radare2)
Reverse engineering framework.
## [rquickjs](https://github.com/DelSkayn/rquickjs)
High level Rust bindings.
## [llrt](https://github.com/awslabs/llrt)
Lightweight JS runtime for serverless applications.
## [nx.js](https://github.com/TooTallNate/nx.js)
JavaScript runtime for Nintendo Switch homebrew applications.
## [quickjs-rusty](https://github.com/Icemic/quickjs-rusty)
Rust wrapper focus on embedding-ready and no-pain type conversion and interoperability.
## [GodotJS](https://github.com/godotjs/GodotJS)
This project adds TypeScript/JavaScript support for Godot 4.x. It supports multiple javascript runtimes, including QuickJS-NG.
## [quickjs_es_runtime](https://github.com/HiRoFa/quickjs_es_runtime)
Rust wrapper with typescript, modules, promises, async/await and much more.
## [Lua jsregexp](https://github.com/kmarius/jsregexp)
JavaScript regular expressions for Lua.
## [Edbrowse](https://edbrowse.org)
A combination editor, web-browser, and mail client that is 100% text based.
@@ -0,0 +1,577 @@
---
sidebar_position: 5
---
# Standard library
The standard libary is provided as part of the `qjs` executable and the `quickjs-libc.c` source file
and it's not part of the core engine.
## Globals
### `argv0`
Provides the executable path.
### `scriptArgs`
Provides the command line arguments. The first argument is the script name.
### `print(...args)`
Print the arguments separated by spaces and a trailing newline.
### `console.log(...args)`
Same as `print()`.
### `navigator.userAgent`
Returns `quickjs-ng/<version>`.
### `gc()`
Shorthand for `std.gc()`.
## `qjs:bjson` module
### `bjson.write(obj, [flags])`
Serializes the given object into the QuickJS internal serialization format.
Returns an ArrayBuffer with the serialized data.
Supported flags:
- `WRITE_OBJ_BYTECODE`: allow serializing functions and modules
- `WRITE_OBJ_REFERENCE`: allow serializing object references
- `WRITE_OBJ_SAB`: allow serializing SharedArrayBuffer instances
- `WRITE_OBJ_STRIP_DEBUG`: strip debugging information when serializing
- `WRITE_OBJ_STRIP_SOURCE`: strip the source information when serializing
### `bjson.read(buf, [pos], [len], [flags])`
De-serialize the given ArrayBuffer (in QuickJS internal serialization format) back into a JavaScript value.
Supported flags:
- `READ_OBJ_BYTECODE`: allow de-serializing functions and modules
- `READ_OBJ_REFERENCE`: allow de-serializing object references
- `READ_OBJ_SAB`: allow de-serializing SharedArrayBuffer instances
## `qjs:os` module
The `os` module provides Operating System specific functions:
- low level file access
- signals
- timers
- basic asynchronous I/O
- workers (threads)
The OS functions usually return 0 if OK or an OS specific negative
error code.
### `open(filename, flags, mode = 0o666)`
Open a file. Return a handle or < 0 if error.
Supported flags:
- `O_RDONLY`
- `O_WRONLY`
- `O_RDWR`
- `O_APPEND`
- `O_CREAT`
- `O_EXCL`
- `O_TRUNC`
POSIX open flags.
- `O_TEXT`
(Windows specific). Open the file in text mode. The default is binary mode.
### `close(fd)`
Close the file handle `fd`.
### `seek(fd, offset, whence)`
Seek in the file. Use `std.SEEK_*` for
`whence`. `offset` is either a number or a BigInt. If
`offset` is a BigInt, a BigInt is returned too.
### `read(fd, buffer, offset, length)`
Read `length` bytes from the file handle `fd` to the
ArrayBuffer `buffer` at byte position `offset`.
Return the number of read bytes or < 0 if error.
### `write(fd, buffer, offset, length)`
Write `length` bytes to the file handle `fd` from the
ArrayBuffer `buffer` at byte position `offset`.
Return the number of written bytes or < 0 if error.
### `isatty(fd)`
Return `true` is `fd` is a TTY (terminal) handle.
### `ttyGetWinSize(fd)`
Return the TTY size as `[width, height]` or `null` if not available.
### `ttySetRaw(fd)`
Set the TTY in raw mode.
### `remove(filename)`
Remove a file. Return 0 if OK or `-errno`.
### `rename(oldname, newname)`
Rename a file. Return 0 if OK or `-errno`.
### `realpath(path)`
Return `[str, err]` where `str` is the canonicalized absolute
pathname of `path` and `err` the error code.
### `getcwd()`
Return `[str, err]` where `str` is the current working directory
and `err` the error code.
### `exePath()`
Returns the full path of the current executable or `undefined` if not available / supported.
### `chdir(path)`
Change the current directory. Return 0 if OK or `-errno`.
### `mkdir(path, mode = 0o777)`
Create a directory at `path`. Return 0 if OK or `-errno`.
### `stat(path)` / `lstat(path)`
Return `[obj, err]` where `obj` is an object containing the
file status of `path`. `err` is the error code. The
following fields are defined in `obj`: `dev`, `ino`, `mode`, `nlink`,
`uid`, `gid`, `rdev`, `size`, `blocks`, `atime`, `mtime`, `ctime`. The times are
specified in milliseconds since 1970. `lstat()` is the same as
`stat()` excepts that it returns information about the link
itself.
- `S_IFMT`
- `S_IFIFO`
- `S_IFCHR`
- `S_IFDIR`
- `S_IFBLK`
- `S_IFREG`
- `S_IFSOCK`
- `S_IFLNK`
- `S_ISGID`
- `S_ISUID`
Constants to interpret the `mode` property returned by
`stat()`. They have the same value as in the C system header
`sys/stat.h`.
### `utimes(path, atime, mtime)`
Change the access and modification times of the file `path`. The
times are specified in milliseconds since 1970. Return 0 if OK or `-errno`.
### `symlink(target, linkpath)`
Create a link at `linkpath` containing the string `target`. Return 0 if OK or `-errno`.
### `readlink(path)`
Return `[str, err]` where `str` is the link target and `err`
the error code.
### `readdir(path)`
Return `[array, err]` where `array` is an array of strings
containing the filenames of the directory `path`. `err` is
the error code. `array` contains at least `"."` and `".."`
if successful.
### `setReadHandler(fd, func)`
Add a read handler to the file handle `fd`. `func` is called
each time there is data pending for `fd`. A single read handler
per file handle is supported. Use `func = null` to remove the
handler.
### `setWriteHandler(fd, func)`
Add a write handler to the file handle `fd`. `func` is
called each time data can be written to `fd`. A single write
handler per file handle is supported. Use `func = null` to remove
the handler.
### `signal(signal, func)`
Call the function `func` when the signal `signal`
happens. Only a single handler per signal number is supported. Use
`null` to set the default handler or `undefined` to ignore
the signal. Signal handlers can only be defined in the main thread.
- `SIGINT`
- `SIGABRT`
- `SIGFPE`
- `SIGILL`
- `SIGSEGV`
- `SIGTERM`
POSIX signal numbers.
### `kill(pid, sig)`
Send the signal `sig` to the process `pid`.
### `exec(args[, options])`
Execute a process with the arguments `args`. `options` is an
object containing optional parameters:
- `block` - Boolean (default = true). If true, wait until the process is
terminated. In this case, `exec` return the exit code if positive
or the negated signal number if the process was interrupted by a
signal. If false, do not block and return the process id of the child.
- `usePath` - Boolean (default = true). If true, the file is searched in the
`PATH` environment variable.
- `file` - String (default = `args[0]`). Set the file to be executed.
- `cwd` - String. If present, set the working directory of the new process.
- `stdin`, `stdout`, `stderr` - If present, set the handle in the child for stdin, stdout or stderr.
- `env` - Object. If present, set the process environment from the object
key-value pairs. Otherwise use the same environment as the current
process.
- `uid` - Integer. If present, the process uid with `setuid`.
- `gid` - Integer. If present, the process gid with `setgid`.
- `groups` - Array of integer. If present, the supplementary
group IDs with `setgroup`.
### `waitpid(pid, options)`
`waitpid` Unix system call. Return the array `[ret, status]`.
`ret` contains `-errno` in case of error.
- `WNOHANG`
Constant for the `options` argument of `waitpid`.
### `dup(fd)`
`dup` Unix system call.
### `dup2(oldfd, newfd)`
`dup2` Unix system call.
### `pipe()`
`pipe` Unix system call. Return two handles as `[read_fd, write_fd]` or null in case of error.
### `sleep(delay_ms)`
Sleep during `delay_ms` milliseconds.
### `sleepAsync(delay_ms)`
Asynchronouse sleep during `delay_ms` milliseconds. Returns a promise. Example:
```js
await os.sleepAsync(500);
```
### `setTimeout(func, delay)`
Call the function `func` after `delay` ms. Return a timer ID.
### `clearTimeout(id)`
Cancel a timer.
### `platform`
Return a string representing the platform: `"linux"`, `"darwin"`, `"win32"` or `"js"`.
### `Worker(module_filename)`
Constructor to create a new thread (worker) with an API close to that of
`WebWorkers`. `module_filename` is a string specifying the
module filename which is executed in the newly created thread. As for
dynamically imported module, it is relative to the current script or
module path. Threads normally don't share any data and communicate
between each other with messages. Nested workers are not supported. An
example is available in `tests/test_worker.js`.
The worker class has the following static properties:
- `parent` - In the created worker, `Worker.parent` represents the parent
worker and is used to send or receive messages.
The worker instances have the following properties:
- `postMessage(msg)` - Send a message to the corresponding worker. `msg` is cloned in
the destination worker using an algorithm similar to the `HTML`
structured clone algorithm. `SharedArrayBuffer` are shared
between workers.
- `onmessage` - Getter and setter. Set a function which is called each time a
message is received. The function is called with a single
argument. It is an object with a `data` property containing the
received message. The thread is not terminated if there is at least
one non `null` `onmessage` handler.
## `qjs:std` module
The `std` module provides wrappers to libc (`stdlib.h` and `stdio.h`) and a few other utilities.
### `exit(n)`
Exit the process.
### `evalScript(str, options = undefined)`
Evaluate the string `str` as a script (global
eval). `options` is an optional object containing the following
optional properties:
- `backtrace_barrier` - Boolean (default = false). If true, error backtraces do not list the
stack frames below the evalScript.
- `async` - Boolean (default = false). If true, `await` is accepted in the
script and a promise is returned. The promise is resolved with an
object whose `value` property holds the value returned by the
script.
### `loadScript(filename)`
Evaluate the file `filename` as a script (global eval).
### `loadFile(filename, [options])`
Load the file `filename` and return it as a string assuming UTF-8
encoding. Return `null` in case of I/O error.
If `options.binary` is set to `true` a `Uint8Array` is returned instead.
### `writeFile(filename, data)`
Create the file `filename` and write `data` into it.
`data` can be a string, a typed array or an `ArrayBuffer`. `undefined` is
treated as the empty string.
When `data` is a string, the file is opened in text mode; otherwise it is
opened in binary mode. (This distinction is only relevant on Windows.)
Throws an exception if the file cannot be created or written to.
### `open(filename, flags, errorObj = undefined)`
Open a file (wrapper to the libc `fopen()`). Return the FILE
object or `null` in case of I/O error. If `errorObj` is not
undefined, set its `errno` property to the error code or to 0 if
no error occurred.
### `popen(command, flags, errorObj = undefined)`
Open a process by creating a pipe (wrapper to the libc
`popen()`). Return the FILE
object or `null` in case of I/O error. If `errorObj` is not
undefined, set its `errno` property to the error code or to 0 if
no error occurred.
### `fdopen(fd, flags, errorObj = undefined)`
Open a file from a file handle (wrapper to the libc
`fdopen()`). Return the FILE
object or null` in case of I/O error. If `errorObj` is not
undefined, set its `errno` property to the error code or to 0 if
no error occurred.
### `tmpfile(errorObj = undefined)`
Open a temporary file. Return the FILE
object or `null` in case of I/O error. If `errorObj` is not
undefined, set its `errno` property to the error code or to 0 if
no error occurred.
### `puts(str)`
Equivalent to `std.out.puts(str)`.
### `printf(fmt, ...args)`
Equivalent to `std.out.printf(fmt, ...args)`.
### `sprintf(fmt, ...args)`
Equivalent to the libc sprintf().
### `in`, `out`, `err`
Wrappers to the libc file `stdin`, `stdout`, `stderr`.
### `Error`
Enumeration object containing the integer value of common errors
(additional error codes may be defined):
- `EINVAL`
- `EIO`
- `EACCES`
- `EEXIST`
- `ENOSPC`
- `ENOSYS`
- `EBUSY`
- `ENOENT`
- `EPERM`
- `EPIPE`
### `strerror(errno)`
Return a string that describes the error `errno`.
### `gc()`
Manually invoke the cycle removal algorithm. The cycle removal
algorithm is automatically started when needed, so this function is
useful in case of specific memory constraints or for testing.
### `getenv(name)`
Return the value of the environment variable `name` or
`undefined` if it is not defined.
### `setenv(name, value)`
Set the value of the environment variable `name` to the string
`value`.
### `unsetenv(name)`
Delete the environment variable `name`.
### `getenviron()`
Return an object containing the environment variables as key-value pairs.
### `urlGet(url, options = undefined)`
Download `url` using the `curl` command line
utility. `options` is an optional object containing the following
optional properties:
- `binary` - Boolean (default = false). If true, the response is an ArrayBuffer
instead of a string. When a string is returned, the data is assumed
to be UTF-8 encoded.
- `full` - Boolean (default = false). If true, return the an object contains
the properties `response` (response content),
`responseHeaders` (headers separated by CRLF), `status`
(status code). `response` is `null` is case of protocol or
network error. If `full` is false, only the response is
returned if the status is between 200 and 299. Otherwise `null`
is returned.
### `FILE`
File object.
#### `close()`
Close the file. Return 0 if OK or `-errno` in case of I/O error.
#### `puts(str)`
Outputs the string with the UTF-8 encoding.
#### `printf(fmt, ...args)`
Formatted printf.
The same formats as the standard C library `printf` are
supported. Integer format types (e.g. `%d`) truncate the Numbers
or BigInts to 32 bits. Use the `l` modifier (e.g. `%ld`) to
truncate to 64 bits.
#### `flush()`
Flush the buffered file.
#### `seek(offset, whence)`
Seek to a give file position (whence is
`std.SEEK_*`). `offset` can be a number or a BigInt. Return
0 if OK or `-errno` in case of I/O error.
- `SEEK_SET`
- `SEEK_CUR`
- `SEEK_END`
Constants for seek().
#### `tell()`
Return the current file position.
#### `tello()`
Return the current file position as a BigInt.
#### `eof()`
Return true if end of file.
#### `fileno()`
Return the associated OS handle.
#### `error()`
Return true if there was an error.
#### `clearerr()`
Clear the error indication.
#### `read(buffer, position, length)`
Read `length` bytes from the file to the ArrayBuffer `buffer` at byte
position `position` (wrapper to the libc `fread`).
#### `write(buffer, position, length)`
Write `length` bytes to the file from the ArrayBuffer `buffer` at byte
position `position` (wrapper to the libc `fwrite`).
#### `getline()`
Return the next line from the file, assuming UTF-8 encoding, excluding
the trailing line feed.
#### `readAsArrayBuffer(max_size = undefined)`
Read `max_size` bytes from the file and return them as an ArrayBuffer.
If `max_size` is not present, the file is read until its end.
#### `readAsString(max_size = undefined)`
Read `max_size` bytes from the file and return them as a string
assuming UTF-8 encoding. If `max_size` is not present, the file
is read until its end.
#### `getByte()`
Return the next byte from the file. Return -1 if the end of file is reached.
#### `putByte(c)`
Write one byte to the file.
@@ -0,0 +1,20 @@
---
sidebar_position: 8
---
# Supported Platforms
| System | Supported versions | Notes |
|---|---|---|
| GNU/Linux | * | glibc and musl are supported |
| macOS | macOS >= 11 | Currently supported macOS releases |
| Windows | >= Windows 7* | VS >= 2022 and Clang are supported; requires `<stdatomic.h>` |
| FreeBSD | * | Limited testing |
| OpenBSD | * | Limited testing |
| NetBSD | * | Limited testing |
| Android | NDK >= 26.0.10792818 | Limited testing |
| iOS | * | Limited testing |
| MinGW | MinGW-w64 | |
| Other | N/A | Missing? Open a PR! |
- `*`: Windows 7 is EOL and only supported in this project as long as it doesn't interfere with its progress.
@@ -0,0 +1,114 @@
// @ts-check
// `@type` JSDoc annotations allow editor autocompletion and type checking
// (when paired with `@ts-check`).
// There are various equivalent ways to declare your Docusaurus config.
// See: https://docusaurus.io/docs/api/docusaurus-config
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'QuickJS-NG',
tagline: 'QuickJS, the Next Generation: a mighty JavaScript engine',
favicon: 'img/favicon.ico',
// Set the production url of your site here
url: 'https://quickjs-ng.github.io',
// Set the /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: '/quickjs/',
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'quickjs-ng', // Usually your GitHub org/user name.
projectName: 'quickjs', // Usually your repo name.
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'throw',
// Even if you don't use internationalization, you can use this field to set
// useful metadata like html lang. For example, if your site is Chinese, you
// may want to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
routeBasePath: '/',
sidebarPath: './sidebars.js',
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl: 'https://github.com/quickjs-ng/quickjs/tree/master/docs/',
},
blog: false,
theme: {
customCss: './src/css/custom.css',
},
}),
],
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
// Replace with your project's social card
image: 'img/docusaurus-social-card.jpg',
navbar: {
title: 'QuickJS-NG',
items: [
{
type: 'docSidebar',
sidebarId: 'docsSidebar',
position: 'left',
label: 'Documentation',
},
{
href: 'https://github.com/quickjs-ng/quickjs',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Docs',
items: [
],
},
{
title: 'Community',
items: [
{
label: 'GitHub Discussions',
href: 'https://github.com/quickjs-ng/quickjs/discussions',
},
{
label: 'Matrix',
href: 'https://matrix.to/#/%23quickjs-ng%3Amatrix.org?via=matrix.org',
},
],
},
{
title: 'More',
items: [
{
label: 'GitHub',
href: 'https://github.com/quickjs-ng/quickjs',
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} QuickJS-NG project contributors.`,
},
prism: {
},
}),
};
export default config;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,42 @@
{
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "3.5.2",
"@docusaurus/preset-classic": "3.5.2",
"@mdx-js/react": "3.0.1",
"clsx": "2.1.1",
"prism-react-renderer": "2.4.0",
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.5.2",
"@docusaurus/types": "3.5.2"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 3 chrome version",
"last 3 firefox version",
"last 5 safari version"
]
},
"engines": {
"node": ">=18.0"
}
}
@@ -0,0 +1,20 @@
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
docsSidebar: [{type: 'autogenerated', dirName: '.'}],
};
export default sidebars;
@@ -0,0 +1,30 @@
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #2e8555;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

File diff suppressed because it is too large Load Diff
+87
View File
@@ -0,0 +1,87 @@
/*
* Tiny float64 printing and parsing library
*
* Copyright (c) 2024 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef DTOA_H
#define DTOA_H
//#define JS_DTOA_DUMP_STATS
/* maximum number of digits for fixed and frac formats */
#define JS_DTOA_MAX_DIGITS 101
/* radix != 10 is only supported with flags = JS_DTOA_FORMAT_FREE */
/* use as many digits as necessary */
#define JS_DTOA_FORMAT_FREE (0 << 0)
/* use n_digits significant digits (1 <= n_digits <= JS_DTOA_MAX_DIGITS) */
#define JS_DTOA_FORMAT_FIXED (1 << 0)
/* force fractional format: [-]dd.dd with n_digits fractional digits.
0 <= n_digits <= JS_DTOA_MAX_DIGITS */
#define JS_DTOA_FORMAT_FRAC (2 << 0)
#define JS_DTOA_FORMAT_MASK (3 << 0)
/* select exponential notation either in fixed or free format */
#define JS_DTOA_EXP_AUTO (0 << 2)
#define JS_DTOA_EXP_ENABLED (1 << 2)
#define JS_DTOA_EXP_DISABLED (2 << 2)
#define JS_DTOA_EXP_MASK (3 << 2)
#define JS_DTOA_MINUS_ZERO (1 << 4) /* show the minus sign for -0 */
/* only accepts integers (no dot, no exponent) */
#define JS_ATOD_INT_ONLY (1 << 0)
/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */
#define JS_ATOD_ACCEPT_BIN_OCT (1 << 1)
/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */
#define JS_ATOD_ACCEPT_LEGACY_OCTAL (1 << 2)
/* accept _ between digits as a digit separator */
#define JS_ATOD_ACCEPT_UNDERSCORES (1 << 3)
typedef struct {
uint64_t mem[37];
} JSDTOATempMem;
typedef struct {
uint64_t mem[27];
} JSATODTempMem;
/* return a maximum bound of the string length */
int js_dtoa_max_len(double d, int radix, int n_digits, int flags);
/* return the string length */
int js_dtoa(char *buf, double d, int radix, int n_digits, int flags,
JSDTOATempMem *tmp_mem);
double js_atod(const char *str, const char **pnext, int radix, int flags,
JSATODTempMem *tmp_mem);
#ifdef JS_DTOA_DUMP_STATS
void js_dtoa_dump_stats(void);
#endif
/* additional exported functions */
size_t u32toa(char *buf, uint32_t n);
size_t i32toa(char *buf, int32_t n);
size_t u64toa(char *buf, uint64_t n);
size_t i64toa(char *buf, int64_t n);
size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix);
size_t i64toa_radix(char *buf, int64_t n, unsigned int radix);
#endif /* DTOA_H */
@@ -0,0 +1,80 @@
/*
* QuickJS: Example of C module
*
* Copyright (c) 2017-2018 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "../quickjs.h"
#define countof(x) (sizeof(x) / sizeof((x)[0]))
static int fib(int n)
{
if (n <= 0)
return 0;
else if (n == 1)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
static JSValue js_fib(JSContext *ctx, JSValue this_val,
int argc, JSValue *argv)
{
int n, res;
if (JS_ToInt32(ctx, &n, argv[0]))
return JS_EXCEPTION;
res = fib(n);
return JS_NewInt32(ctx, res);
}
static const JSCFunctionListEntry js_fib_funcs[] = {
JS_CFUNC_DEF("fib", 1, js_fib ),
};
static int js_fib_init(JSContext *ctx, JSModuleDef *m)
{
return JS_SetModuleExportList(ctx, m, js_fib_funcs,
countof(js_fib_funcs));
}
#ifdef JS_SHARED_LIBRARY
#define JS_INIT_MODULE js_init_module
#else
#define JS_INIT_MODULE js_init_module_fib
#endif
#ifndef JS_EXTERN
#ifdef _WIN32
#define JS_EXTERN __declspec(dllexport)
#else
#define JS_EXTERN
#endif
#endif
JS_EXTERN JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name)
{
JSModuleDef *m;
m = JS_NewCModule(ctx, module_name, js_fib_init);
if (!m)
return NULL;
JS_AddModuleExportList(ctx, m, js_fib_funcs, countof(js_fib_funcs));
return m;
}
@@ -0,0 +1,10 @@
/* fib module */
export function fib(n)
{
if (n <= 0)
return 0;
else if (n == 1)
return 1;
else
return fib(n - 1) + fib(n - 2);
}

Some files were not shown because too many files have changed in this diff Show More