Merge v3-p2-fix: security review fixes for the credentials manager (T27, T28)
Typed passkey proofs checked inside mutation transactions, ports and normalized paths, overrides on GrantCreate, payload digests, key file checks, retention (D57), scripts in QuickJS inside Wasmtime (D58), RSA on aws-lc-rs, OAuth2 token URL checks. 85 credential tests plus durable and harness tests pass.
This commit is contained in:
Generated
+3
@@ -4994,7 +4994,9 @@ dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"aws-lc-rs",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"chacha20poly1305",
|
||||
"clap",
|
||||
"deno_ast",
|
||||
@@ -7843,6 +7845,7 @@ version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
|
||||
+3
-1
@@ -34,7 +34,7 @@ serde = { version = "1.0.229", features = ["derive"] }
|
||||
serde_yaml_ng = "0.10.0"
|
||||
rustls = "0.23"
|
||||
chacha20poly1305 = "0.10"
|
||||
zeroize = "1"
|
||||
zeroize = { version = "1", features = ["serde"] }
|
||||
hex = "0.4"
|
||||
http = "1"
|
||||
url = "2"
|
||||
@@ -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"
|
||||
|
||||
Generated
+584
@@ -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"
|
||||
@@ -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" }
|
||||
@@ -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.
|
||||
@@ -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.
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
Vendored
+50
@@ -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
@@ -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"
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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("."))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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");
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,24 @@
|
||||
# rquickjs-sys
|
||||
|
||||
[](https://github.com/DelSkayn/rquickjs)
|
||||
[](https://crates.io/crates/rquickjs-sys)
|
||||
[](https://docs.rs/rquickjs-sys)
|
||||
[](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
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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})
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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/
|
||||
@@ -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)
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
@@ -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;
|
||||
}
|
||||
+1342
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
:::
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"label": "Developer Guide",
|
||||
"position": 6,
|
||||
"link": {
|
||||
"type": "generated-index"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# API Reference
|
||||
|
||||
WIP.
|
||||
+117
@@ -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.
|
||||
+113
@@ -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.
|
||||
+20
@@ -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;
|
||||
+14656
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 |
+1619
File diff suppressed because it is too large
Load Diff
@@ -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
Reference in New Issue
Block a user