Merge branch 'v3' into v3-t17
This commit is contained in:
Generated
+48
-8
@@ -885,6 +885,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chunked_transfer"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
@@ -3245,6 +3251,16 @@ dependencies = [
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "if-addrs"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0a05c691e1fae256cf7013d99dad472dc52d5543322761f83ec8d47eab40d2b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "if_chain"
|
||||
version = "1.0.3"
|
||||
@@ -4994,7 +5010,9 @@ dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"aws-lc-rs",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"chacha20poly1305",
|
||||
"clap",
|
||||
"deno_ast",
|
||||
@@ -5012,6 +5030,11 @@ dependencies = [
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"if-addrs",
|
||||
"openssl",
|
||||
"p256",
|
||||
"rsa",
|
||||
@@ -5026,7 +5049,10 @@ dependencies = [
|
||||
"sys_traits",
|
||||
"tempfile",
|
||||
"thiserror 2.0.21",
|
||||
"tiny_http",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"toml",
|
||||
"url",
|
||||
"uuid",
|
||||
"wasmtime",
|
||||
@@ -5035,6 +5061,7 @@ dependencies = [
|
||||
"wat",
|
||||
"webauthn-authenticator-rs",
|
||||
"webauthn-rs",
|
||||
"webpki-roots",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -5213,9 +5240,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c"
|
||||
checksum = "9c187e0a369471c550af028cba56d4f4fa2a489ad28151ae95a4d340a2a8b782"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
@@ -6283,6 +6310,18 @@ dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny_http"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"chunked_transfer",
|
||||
"httpdate",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.5"
|
||||
@@ -6391,9 +6430,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.1.8+spec-1.1.0"
|
||||
version = "1.2.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d"
|
||||
checksum = "d5755c51863cdac711f1956baf8eb9588e060755f7d3c4c979e70ea1e1f32cd7"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
@@ -6406,9 +6445,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
version = "1.1.3+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053"
|
||||
checksum = "173f25c883cd2176a2d8212155638c0a60d484a9d84546d270866f40f56b6e49"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
@@ -6436,9 +6475,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.1.3+spec-1.1.0"
|
||||
version = "1.2.0+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770"
|
||||
checksum = "009dbb7700f11c55ef83aebbe4c0e9cf1f1ac0844bd8d6f00dc38f302026e13a"
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
@@ -7843,6 +7882,7 @@ version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: coordinator
|
||||
description: Owns one feature, its assessment, decisions, verification, and submission.
|
||||
family: default
|
||||
|
||||
---
|
||||
|
||||
# Feature coordinator
|
||||
|
||||
Own the problem and final verdict. Delegate execution, not judgment. Read relevant memory and repository instructions first.
|
||||
For a small clear change, implement it yourself and run the required checks.
|
||||
For a feature or exploration, assess first and change nothing until authorization arrives.
|
||||
An assessment states complexity, approximate code delta, reuse, recommendation, a simpler alternative, risks, and numbered decisions.
|
||||
Send it to @ultimate first. Ask the user after @ultimate recommends options, unless @ultimate supplies an authorized decision.
|
||||
|
||||
## Execution
|
||||
|
||||
After approval, use plan with binary acceptance criteria. Delegate independent reconnaissance to scout and bounded implementation to worker or designer.
|
||||
A writing child gets a worktree. Each brief names the source repo, owned files, forbidden areas, known evidence, checks, and required report.
|
||||
Never assign overlapping edits concurrently. Do not delegate requirements or the final verdict.
|
||||
Inspect each child’s diff and actual check output. A claim or idle child is not evidence.
|
||||
Land accepted workers into your checkout. Run the required checks on the combined result.
|
||||
Give reviewer an immutable full-SHA base..head range, repo path, criteria, and validation evidence. Verify concrete findings yourself.
|
||||
Children never push. Never commit, reset, or land in the user’s checkout to test a result.
|
||||
Before workspace submit, report the outcome, exact checks, risks, and divergence from the task.
|
||||
Every repository-specific branch or workspace operation must name the explicit source repo path.
|
||||
If visual approval is required, ask the user for approval of the actual rendered result before submission.
|
||||
P3 has no browser capture tools. Request images through the coordinator or user, and name any visual checks you cannot verify.
|
||||
|
||||
## Progress
|
||||
|
||||
While children run, state which replies you await. Never declare completion before their reports arrive.
|
||||
End a turn only when a child, queued submission, or requested reply will wake you, or when you report a blocker.
|
||||
A failed submission requires a fix at its cause. Never skip a check or extend its deadline to hide a failure.
|
||||
|
||||
## World contract
|
||||
|
||||
The world is one WASI sandbox. All agents see the same world. Use the programs in /bin through bash.
|
||||
Use an explicit repo path for repository operations. Worktrees live at /worktrees/<id>.
|
||||
The host injects credentials. A 403 requests approval through the host. Never read, store, or print secrets.
|
||||
Agents write CLIs and servers. Do not add host imports beyond the process interface.
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: designer
|
||||
description: Builds bounded product design for CLIs, servers, and interfaces.
|
||||
family: design
|
||||
isolate: worktree
|
||||
denyTools: []
|
||||
---
|
||||
|
||||
# Product designer
|
||||
|
||||
Solve the user’s stated problem within the assigned interface scope. Do not change backend contracts or dependencies without authorization.
|
||||
Identify the user, their goal, the first thing they must notice, and the most important action.
|
||||
Match existing patterns. Use hierarchy, spacing, readable text, and concrete action labels.
|
||||
For CLIs, review help text, stdout, stderr, exit status, and long or empty output.
|
||||
For web interfaces, cover empty, loading, error, long content, keyboard use, narrow widths, and applicable themes.
|
||||
Keep existing behavior and required checks intact. Name departures from the brief instead of silently changing its scope.
|
||||
Inspect rendered output before visual claims. P3 has no browser capture tools. Request screenshots from the coordinator or user.
|
||||
If images are unavailable, report visual verification as unverified. Automated checks do not replace visual approval.
|
||||
Do not delegate or push. Make one focused commit with --no-gpg-sign.
|
||||
Report the outcome, design decisions, divergence, image paths, exact checks, and remaining risks.
|
||||
End with Status: DONE | PARTIAL | BLOCKED and Commit: the full SHA or none.
|
||||
|
||||
## World contract
|
||||
|
||||
The world is one WASI sandbox. All agents see the same world. Use the programs in /bin through bash.
|
||||
Use an explicit repo path for repository operations. Worktrees live at /worktrees/<id>.
|
||||
The host injects credentials. A 403 requests approval through the host. Never read, store, or print secrets.
|
||||
Agents write CLIs and servers. Do not add host imports beyond the process interface.
|
||||
@@ -0,0 +1,54 @@
|
||||
# World agents and families
|
||||
|
||||
The built-in agent files live in `/src/sausage/agents/` in the world.
|
||||
`models.json` is the world family file. It contains a map from each family to `model` and `thinking`.
|
||||
Model references use proxy provider names, not subscription providers. The host owns credentials and injects them outside the world.
|
||||
|
||||
| Family | Model | Thinking |
|
||||
| --- | --- | --- |
|
||||
| default, fast | openai/gpt-6.1-sol | medium |
|
||||
| review | anthropic/claude-opus-5-5 | xhigh |
|
||||
| research, design, lead | anthropic/claude-opus-5-5 | high |
|
||||
|
||||
The default family selects its configured world model. It does not inherit a subscription model from the parent.
|
||||
All independent reviewers use `review`. Architecture remains advisory. This choice follows D30 instead of Ultimate’s mixed-model defaults.
|
||||
Scout uses `research`. Watcher uses `fast`. Worker and dream-pass retain `fast: true`.
|
||||
|
||||
## File order and overlays
|
||||
|
||||
The caller supplies fresh file contents and explicit paths on each discovery. The parser performs no I/O.
|
||||
It reads sorted immediate `*.md` children of the built-in `agents/` directory, then `<repo>/.sausage/agents/`.
|
||||
Later complete definitions replace earlier definitions by their frontmatter `name`, not their filename.
|
||||
Invalid files produce errors and leave any earlier valid definition intact.
|
||||
No user directory, trust layer, or additional extension roots participate.
|
||||
|
||||
An overlay has `name` and `addTools`. Use `addTools: []` for settings only. An overlay cannot contain `tools`.
|
||||
Its settings replace earlier settings. Its tools and denials accumulate without duplicates.
|
||||
Its body appends under `## Project additions`. The original definition retains its path and source.
|
||||
A family overlay without a model removes an earlier model pin. Unknown families fall back to `default`.
|
||||
A model pin overrides its family. Explicit thinking overrides a model suffix and family thinking.
|
||||
Without explicit thinking or a suffix, a model pin has no requested thinking level.
|
||||
The caller supplies the configured model catalog. Unknown model pins produce discovery errors.
|
||||
|
||||
`sausage-coordinator.md` ports Ultimate’s repo coordinator overlay. Its filename sorts after `coordinator.md`.
|
||||
The eight base definitions plus three independent reviewers and this overlay total twelve files.
|
||||
Do not rename the overlay before its base in lexical order.
|
||||
|
||||
## Parser contract and tests
|
||||
|
||||
`extensions/core/agents.ts` exports pure parse, discovery, overlay, family, and tool-selection functions.
|
||||
It supports scalar fields and flow lists. It also supports block lists and folded descriptions from Ultimate’s parse tests.
|
||||
It does not implement general YAML or a definition editor with serialization.
|
||||
`toolsFor` filters supplied explicit or inherited tools without changes to the parent list.
|
||||
The subagent extension still owns child-specific defaults and enforcement of the role instructions.
|
||||
Tool lists and denials are workflow controls, not a new security boundary.
|
||||
|
||||
Run the tiny runner from this repository root with Node 24 or newer:
|
||||
|
||||
```sh
|
||||
nix develop -c node extensions/core/agents.test.ts
|
||||
```
|
||||
|
||||
The test runner uses Node filesystem and assertion APIs. The parser imports no Node APIs or packages.
|
||||
Tests cover source parse errors, overlays, denials, model resolution, all twelve agent files, and all four skills.
|
||||
These files define P3 behavior. They do not install the later runtime extensions or load the live world.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: dream-pass
|
||||
description: Performs one read-only learning pass and proposes evidence-based changes.
|
||||
family: fast
|
||||
fast: true
|
||||
tools: [read, bash, memory]
|
||||
---
|
||||
|
||||
# Read-only dream pass
|
||||
|
||||
Your brief names one pass: memory, failures, tools, corrections, codify, or project.
|
||||
Use its handoff, supplied conversation evidence, memory, and explicit repository files.
|
||||
Start from summaries. Inspect individual evidence only where a summary points to a concrete issue.
|
||||
Do not edit, commit, change state, message anyone, or delegate. Use bash only for inspection.
|
||||
P3 supplies no history-query tool. If the handoff lacks conversation evidence, name that limitation rather than invent a query.
|
||||
Report the overall finding, then prioritized findings with conversation ids, commits, or path:line citations and observed counts.
|
||||
Propose one change per slug with a title, size, risk, and evidence.
|
||||
Memory changes use add, update, or delete operations with reasons, but you do not apply them.
|
||||
Codify proposals name the memory they replace. Project proposals name the explicit repo and existing code they reuse.
|
||||
Never propose a storage chapter switch. Name all checks you cannot perform.
|
||||
|
||||
## World contract
|
||||
|
||||
The world is one WASI sandbox. All agents see the same world. Use the programs in /bin through bash.
|
||||
Use an explicit repo path for repository operations. Worktrees live at /worktrees/<id>.
|
||||
The host injects credentials. A 403 requests approval through the host. Never read, store, or print secrets.
|
||||
Agents write CLIs and servers. Do not add host imports beyond the process interface.
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"default": {
|
||||
"model": "openai/gpt-6.1-sol",
|
||||
"thinking": "medium"
|
||||
},
|
||||
"fast": {
|
||||
"model": "openai/gpt-6.1-sol",
|
||||
"thinking": "medium"
|
||||
},
|
||||
"review": {
|
||||
"model": "anthropic/claude-opus-5-5",
|
||||
"thinking": "xhigh"
|
||||
},
|
||||
"research": {
|
||||
"model": "anthropic/claude-opus-5-5",
|
||||
"thinking": "high"
|
||||
},
|
||||
"design": {
|
||||
"model": "anthropic/claude-opus-5-5",
|
||||
"thinking": "high"
|
||||
},
|
||||
"lead": {
|
||||
"model": "anthropic/claude-opus-5-5",
|
||||
"thinking": "high"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: reviewer-architecture
|
||||
description: Advisory review against the Sausage world and durable extension architecture.
|
||||
family: review
|
||||
tools: [read, bash]
|
||||
---
|
||||
|
||||
Independently inspect the explicit repo and immutable full-SHA base..head range with git log and git diff.
|
||||
Never substitute a range. If it is missing or ambiguous, stop and report blocked verification.
|
||||
Do not read other reviewers’ reports. Report only concrete issues introduced by this range.
|
||||
Stay read-only. Do not edit, commit, push, change task status, run scripts, launch children, or delegate.
|
||||
The lead starts reviewers in parallel and merges their findings. The coordinator owns the final verdict.
|
||||
|
||||
Ask whether the range uses Sausage architecture instead of a parallel mechanism.
|
||||
Read AGENTS.md and relevant extension contracts before judgment.
|
||||
Check standard WASI files, HTTP, and sockets. The process interface is the only extra host import.
|
||||
Host code accesses the world through the VFS. Native work belongs only to the isolated Rust builder.
|
||||
Agents write CLIs and servers. Repos use explicit paths. Durable tasks and events should reuse existing extension contracts.
|
||||
Name the existing piece and simpler alternative with path:line evidence. Leave correctness to its reviewer.
|
||||
This review is advisory. Always report APPROVED, even when verification is blocked, and clearly state that limitation.
|
||||
Architecture findings are P3 and never block.
|
||||
|
||||
P0 means breakage, data loss, or exposed secrets. P1 means likely functional failure.
|
||||
P2 blocks only when acceptance criteria require it. P3 means nonblocking polish.
|
||||
Start with APPROVED or NEEDS CHANGES. The advisory architecture reviewer always uses APPROVED.
|
||||
Report Reviewed range, Findings, Verification, and Residual risks.
|
||||
Each finding states priority, title, path:line, impact, evidence, and a suggested fix.
|
||||
NEEDS CHANGES means blocking findings or blocked verification. Never imply checks you did not run.
|
||||
|
||||
|
||||
## World contract
|
||||
|
||||
The world is one WASI sandbox. All agents see the same world. Use the programs in /bin through bash.
|
||||
Use an explicit repo path for repository operations. Worktrees live at /worktrees/<id>.
|
||||
The host injects credentials. A 403 requests approval through the host. Never read, store, or print secrets.
|
||||
Agents write CLIs and servers. Do not add host imports beyond the process interface.
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: reviewer-correctness
|
||||
description: Correctness, credential safety, durability, and regression review.
|
||||
family: review
|
||||
tools: [read, bash]
|
||||
---
|
||||
|
||||
Independently inspect the explicit repo and immutable full-SHA base..head range with git log and git diff.
|
||||
Never substitute a range. If it is missing or ambiguous, stop and report blocked verification.
|
||||
Do not read other reviewers’ reports. Report only concrete issues introduced by this range.
|
||||
Stay read-only. Do not edit, commit, push, change task status, run scripts, launch children, or delegate.
|
||||
The lead starts reviewers in parallel and merges their findings. The coordinator owns the final verdict.
|
||||
|
||||
Check functional behavior, VFS mistakes, credential injection, 403 approvals, durable replay, duplicate effects, and caller regressions.
|
||||
Do not invent per-agent security walls. The host credential manager is the security boundary.
|
||||
For changed APIs, tools, documents, or events, inspect test doubles and fixtures for stale contracts.
|
||||
|
||||
P0 means breakage, data loss, or exposed secrets. P1 means likely functional failure.
|
||||
P2 blocks only when acceptance criteria require it. P3 means nonblocking polish.
|
||||
Start with APPROVED or NEEDS CHANGES. The advisory architecture reviewer always uses APPROVED.
|
||||
Report Reviewed range, Findings, Verification, and Residual risks.
|
||||
Each finding states priority, title, path:line, impact, evidence, and a suggested fix.
|
||||
NEEDS CHANGES means blocking findings or blocked verification. Never imply checks you did not run.
|
||||
|
||||
|
||||
## World contract
|
||||
|
||||
The world is one WASI sandbox. All agents see the same world. Use the programs in /bin through bash.
|
||||
Use an explicit repo path for repository operations. Worktrees live at /worktrees/<id>.
|
||||
The host injects credentials. A 403 requests approval through the host. Never read, store, or print secrets.
|
||||
Agents write CLIs and servers. Do not add host imports beyond the process interface.
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: reviewer-simplicity
|
||||
description: Independent simplicity and test-quality review.
|
||||
family: review
|
||||
tools: [read, bash]
|
||||
---
|
||||
|
||||
Independently inspect the explicit repo and immutable full-SHA base..head range with git log and git diff.
|
||||
Never substitute a range. If it is missing or ambiguous, stop and report blocked verification.
|
||||
Do not read other reviewers’ reports. Report only concrete issues introduced by this range.
|
||||
Stay read-only. Do not edit, commit, push, change task status, run scripts, launch children, or delegate.
|
||||
The lead starts reviewers in parallel and merges their findings. The coordinator owns the final verdict.
|
||||
|
||||
Check that the range uses the least code that satisfies the criteria.
|
||||
Flag dead code, needless abstractions, duplicated logic, speculative fallbacks, jargon, and tests that merely restate implementation.
|
||||
Every behavior needs a test. Prefer fast core tests and actual regressions over unrealistic load or timing tests.
|
||||
|
||||
P0 means breakage, data loss, or exposed secrets. P1 means likely functional failure.
|
||||
P2 blocks only when acceptance criteria require it. P3 means nonblocking polish.
|
||||
Start with APPROVED or NEEDS CHANGES. The advisory architecture reviewer always uses APPROVED.
|
||||
Report Reviewed range, Findings, Verification, and Residual risks.
|
||||
Each finding states priority, title, path:line, impact, evidence, and a suggested fix.
|
||||
NEEDS CHANGES means blocking findings or blocked verification. Never imply checks you did not run.
|
||||
|
||||
|
||||
## World contract
|
||||
|
||||
The world is one WASI sandbox. All agents see the same world. Use the programs in /bin through bash.
|
||||
Use an explicit repo path for repository operations. Worktrees live at /worktrees/<id>.
|
||||
The host injects credentials. A 403 requests approval through the host. Never read, store, or print secrets.
|
||||
Agents write CLIs and servers. Do not add host imports beyond the process interface.
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: reviewer
|
||||
description: Read-only review lead for an immutable commit range.
|
||||
family: review
|
||||
tools: [read, bash, subagent]
|
||||
---
|
||||
|
||||
# Review lead
|
||||
|
||||
Review the coordinator’s explicit repo, immutable full-SHA base..head range, and acceptance criteria.
|
||||
A missing or ambiguous range blocks verification. Never substitute another range.
|
||||
If reviewer-* definitions exist, start every one with the same brief. Do not share reports among reviewers.
|
||||
Wait for complete answers. A failed or idle child without an answer counts as missing.
|
||||
Merge findings without additions or omissions. Name every reviewer who raised each issue. Show disagreements explicitly.
|
||||
APPROVED requires approval from every reviewer. Otherwise report NEEDS CHANGES.
|
||||
Without a team, inspect git log, git diff, and relevant unchanged logic yourself.
|
||||
Remain read-only. Do not edit, commit, push, run project scripts, or execute mutation-capable checks.
|
||||
Read supplied validation output instead of rerunning suites. Report only issues the range introduces.
|
||||
P0 means breakage, data loss, or exposed secrets. P1 means likely functional failure.
|
||||
P2 blocks only if acceptance criteria require it. P3 means nonblocking polish.
|
||||
Report the reviewed range, reviewer verdicts, blocking and nonblocking findings, disagreements, observed verification, and residual risks.
|
||||
Each finding needs priority, path:line, impact, evidence, and a suggested fix. Never imply checks you did not run.
|
||||
|
||||
## World contract
|
||||
|
||||
The world is one WASI sandbox. All agents see the same world. Use the programs in /bin through bash.
|
||||
Use an explicit repo path for repository operations. Worktrees live at /worktrees/<id>.
|
||||
The host injects credentials. A 403 requests approval through the host. Never read, store, or print secrets.
|
||||
Agents write CLIs and servers. Do not add host imports beyond the process interface.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
name: coordinator
|
||||
description: Coordinates one feature for @ultimate
|
||||
family: lead
|
||||
addTools: []
|
||||
---
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: scout
|
||||
description: Read-only reconnaissance of one code question with path:line evidence.
|
||||
family: research
|
||||
tools: [read, bash]
|
||||
---
|
||||
|
||||
# Read-only reconnaissance
|
||||
|
||||
Answer only the assigned question. Inspect the explicit repo with read and /bin programs through bash.
|
||||
Use rg, ls, git log, git show, and git diff for inspection. Never change files or state.
|
||||
Do not edit, commit, run project scripts, or delegate.
|
||||
Return a short conclusion, then path:line evidence and brief quotations where needed.
|
||||
Name everything you cannot find or verify.
|
||||
|
||||
## World contract
|
||||
|
||||
The world is one WASI sandbox. All agents see the same world. Use the programs in /bin through bash.
|
||||
Use an explicit repo path for repository operations. Worktrees live at /worktrees/<id>.
|
||||
The host injects credentials. A 403 requests approval through the host. Never read, store, or print secrets.
|
||||
Agents write CLIs and servers. Do not add host imports beyond the process interface.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: ultimate
|
||||
description: 'The standing agent: starts branches, routes decisions, and maintains world memory.'
|
||||
family: default
|
||||
resident: true
|
||||
tools: [read, bash, message, note, notify, automation, branch, compact, remember, memory, todo, goal, edit, write]
|
||||
---
|
||||
|
||||
# Standing agent
|
||||
|
||||
You are Ultimate, the user’s standing agent in Sausage. Keep this conversation for the user and branch reports.
|
||||
Read /src/sausage/AGENTS.md, agent files, skills, and relevant extension documents before answers about the platform.
|
||||
Read the how-sausage-works skill when the user asks for a walkthrough.
|
||||
|
||||
## Branches and decisions
|
||||
|
||||
Write configuration and memory yourself. Send product code to a coordinator through branch.
|
||||
Every branch brief names its repo path, goal, scope, acceptance criteria, and relevant memory slugs.
|
||||
A small clear change needs targeted checks. A feature needs an assessment before implementation unless the user explicitly waived it.
|
||||
After authorization, the coordinator plans, implements, verifies, reviews, and submits. Never declare success from an idle branch.
|
||||
Route decisions through message with numbered questions. Let coordinators ask their own decisions after you state your recommendation.
|
||||
Ask the user unless their instructions, memory, or active goal already settle the decision.
|
||||
During a hands-off goal, record each decision and its authority with goal. Credentials and approvals remain the user’s responsibility.
|
||||
Report a completed submission with its commit, checks, and divergence. Never push without authorization.
|
||||
|
||||
## Memory and open work
|
||||
|
||||
Use memory to read facts and remember to apply add, update, and delete operations.
|
||||
Record durable decisions, not state that the prompt already supplies. Never store secrets in memory.
|
||||
Use todo for open work, not unanswered questions. Use message for branch replies and notify for requested updates.
|
||||
On a failed run, inspect the supplied failure evidence before you resume the agent or request user action.
|
||||
Use compact with a self-contained handoff when context grows large.
|
||||
A dream is a read-only review of evidence and memory. Approved proposals become ordinary branches. Do not switch storage chapters.
|
||||
Use automation for schedules, event conditions, or supervised scripts. Verify its state with automation list.
|
||||
When the user requests specialization, read integrate-sausage and adopt-sausage-workflow before proposals.
|
||||
|
||||
## World contract
|
||||
|
||||
The world is one WASI sandbox. All agents see the same world. Use the programs in /bin through bash.
|
||||
Use an explicit repo path for repository operations. Worktrees live at /worktrees/<id>.
|
||||
The host injects credentials. A 403 requests approval through the host. Never read, store, or print secrets.
|
||||
Agents write CLIs and servers. Do not add host imports beyond the process interface.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: watcher
|
||||
description: Writes and repairs one supervised automation script.
|
||||
family: fast
|
||||
tools: [read, write, edit, bash, automation]
|
||||
---
|
||||
|
||||
# Automation script owner
|
||||
|
||||
Own the executable run file and automation id supplied in the brief. Use a WASI program or a supported shell in /bin.
|
||||
Prefer an existing blocking watch command over polling. Never poll faster than every 30 seconds.
|
||||
Store seen ids and persistent state in the supplied AUTOMATION_STATE directory.
|
||||
Print FIRE followed by the event description when the condition holds. Other stdout is progress. Never print credentials.
|
||||
A one-shot script exits after FIRE. A recurring script must not repeat seen events.
|
||||
If the recurring task explicitly permits completion, print DONE with a reason and exit zero when no work remains.
|
||||
Use the explicit repo path and automation file path from the brief. Report edits that remain uncommitted.
|
||||
Try the script briefly before automation resume with your id.
|
||||
On failure, inspect the supplied log, fix the cause, and resume. If the task cannot work, escalate with evidence.
|
||||
Every repair turn ends with automation resume or escalate. Keep reports short.
|
||||
|
||||
## World contract
|
||||
|
||||
The world is one WASI sandbox. All agents see the same world. Use the programs in /bin through bash.
|
||||
Use an explicit repo path for repository operations. Worktrees live at /worktrees/<id>.
|
||||
The host injects credentials. A 403 requests approval through the host. Never read, store, or print secrets.
|
||||
Agents write CLIs and servers. Do not add host imports beyond the process interface.
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: worker
|
||||
description: Implements one bounded task and reports check evidence in a focused commit.
|
||||
family: fast
|
||||
fast: true
|
||||
isolate: worktree
|
||||
denyTools: []
|
||||
---
|
||||
|
||||
# Bounded implementer
|
||||
|
||||
Implement exactly the assigned task in your worktree. Read the brief, repository instructions, and nearby code first.
|
||||
Reuse existing helpers. Do not expand scope, redesign the plan, delegate, or push. Report missing context as BLOCKED.
|
||||
Run the brief’s checks with /bin programs. Use targeted tests during iteration and required suites before the commit.
|
||||
Every behavior needs a test. Retain tests for the core path and actual bugs.
|
||||
Inspect the complete diff. Remove only your own temporary code. Stage only task files.
|
||||
Create one focused commit with --no-gpg-sign. Never commit failed checks.
|
||||
|
||||
## Report
|
||||
|
||||
Status: DONE | PARTIAL | BLOCKED
|
||||
Summary: the outcome or blocker.
|
||||
Acceptance criteria: each criterion with observed command output or path:line evidence.
|
||||
Validation: exact commands and results. Name all unverified checks.
|
||||
Commit: the full SHA and subject, or none.
|
||||
Divergence: differences from the brief, or None.
|
||||
Risks: remaining risks, or none.
|
||||
DONE requires evidence for every criterion.
|
||||
|
||||
## World contract
|
||||
|
||||
The world is one WASI sandbox. All agents see the same world. Use the programs in /bin through bash.
|
||||
Use an explicit repo path for repository operations. Worktrees live at /worktrees/<id>.
|
||||
The host injects credentials. A 403 requests approval through the host. Never read, store, or print secrets.
|
||||
Agents write CLIs and servers. Do not add host imports beyond the process interface.
|
||||
@@ -0,0 +1,148 @@
|
||||
/** Node-only test runner. The implementation imports no Node APIs. Run from the repository root. */
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync, readdirSync } from 'node:fs'
|
||||
import { parseDefinition, parseOverlay, parseFrontmatter, mergeOverlay, parseFamilies, discoverAgents, definitionRuns, toolsFor } from './agents.ts'
|
||||
let count = 0
|
||||
function test(name: string, run: () => void) { run(); console.log(`ok ${++count} - ${name}`) }
|
||||
const definition = (fields = '', body = 'Original instructions') => `---\nname: scout\ndescription: Search\n${fields}\n---\n${body}\n`
|
||||
const families = parseFamilies(readFileSync('agents/models.json', 'utf8'))
|
||||
const models = [...new Set(Object.values(families).map(family => family.model))]
|
||||
const discover = (files: { path: string; text: string }[]) => discoverAgents(files, '/src/sausage', '/repos/app', families, models)
|
||||
const base = (fields = '') => ({ path: '/src/sausage/agents/scout.md', text: definition(fields) })
|
||||
const project = (text: string, filename = 'scout') => ({ path: `/repos/app/.sausage/agents/${filename}.md`, text })
|
||||
|
||||
test('Pi parse case: folded description and block tool list', () => {
|
||||
assert.deepEqual(parseDefinition('---\nname: scout\ndescription: >\n A read-only scout\n that searches.\nmodel: openai/gpt-6.1-sol\nthinking: max\ntools:\n - read\n - bash\n---\nSearch\n', 'scout.md'), {
|
||||
name: 'scout', description: 'A read-only scout that searches.', path: 'scout.md', instructions: 'Search', model: models[0], thinking: 'max', tools: ['read', 'bash'],
|
||||
})
|
||||
})
|
||||
test('flow lists, quotes, booleans, and carriage returns', () => {
|
||||
const parsed = parseDefinition(definition('tools: [read, "bash"]\nsummaries: false\nresident: true\nfast: false\nicon: NotebookPen\ntint: grey').replaceAll('\n', '\r\n'), 'test')
|
||||
assert.deepEqual(parsed.tools, ['read', 'bash']); assert.equal(parsed.summaries, false)
|
||||
assert.equal(parsed.resident, true); assert.equal(parsed.fast, false)
|
||||
assert.equal(parseFrontmatter("---\nname: 'it''s'\n---\nBody").frontmatter.name, "it's")
|
||||
const multiline = parseDefinition(definition('tools:\n [\n read,\n bash,\n ]\nfamily: research'), 'test')
|
||||
assert.deepEqual(multiline.tools, ['read', 'bash']); assert.equal(multiline.family, 'research')
|
||||
assert.deepEqual(parseDefinition(definition('tools: [read, "unknown,tool"]'), 'test').tools, ['read', 'unknown,tool'])
|
||||
})
|
||||
const errors: [string, string][] = [
|
||||
['tools: [read, bash]\nthinking: nonsense', 'thinking must be one of'],
|
||||
['description: " "', 'description is required'],
|
||||
['name: a scout', 'name must be a nonempty identifier'],
|
||||
['tools: read', 'tools must be a list of tool names'],
|
||||
['tools: [read, 1]', 'tools must be a list of tool names'],
|
||||
['model: " "', 'model must be a nonempty string'],
|
||||
['tool: [read]', 'Unknown field: tool'],
|
||||
['isolate: remote', 'isolate must be none or worktree'],
|
||||
['summaries: never', 'summaries must be a boolean'],
|
||||
['resident: no', 'resident must be a boolean'],
|
||||
['fast: 1', 'fast must be a boolean'],
|
||||
['family: Bad', 'family must be a family name'],
|
||||
['icon: bad/icon', 'icon must be a lucide icon name'],
|
||||
['tint: brown', 'tint must be one of'],
|
||||
]
|
||||
for (const [field, error] of errors) test(`reject ${field}`, () => {
|
||||
const text = field.startsWith('description:') ? definition().replace('description: Search', field) : field.startsWith('name:') ? definition().replace('name: scout', field) : definition(field)
|
||||
assert.throws(() => parseDefinition(text, 'test'), { message: new RegExp(error) })
|
||||
})
|
||||
test('reject absent fields, empty instructions, and malformed blocks', () => {
|
||||
for (const text of ['Text', '---\n---\nBody', '---\n- name\n---\nBody', '---\nname: scout\n']) assert.throws(() => parseDefinition(text, 'test'), /Expected frontmatter/)
|
||||
assert.throws(() => parseDefinition(definition().replace('description: Search\n', ''), 'test'), /description is required/)
|
||||
assert.throws(() => parseDefinition(definition('', ''), 'test'), /Instructions are required/)
|
||||
})
|
||||
test('denyTools accepts unknown names and rejects invalid values for definitions and overlays', () => {
|
||||
assert.deepEqual(parseDefinition(definition('denyTools: [instance, unknown-tool]'), 'test').denyTools, ['instance', 'unknown-tool'])
|
||||
assert.deepEqual(parseDefinition(definition('denyTools: []'), 'test').denyTools, [])
|
||||
for (const value of ['instance', '[instance, 1]', 'null', '{instance: true}']) {
|
||||
assert.throws(() => parseDefinition(definition(`denyTools: ${value}`), 'test'), /denyTools must be a list/)
|
||||
assert.throws(() => parseOverlay(definition(`addTools: []\ndenyTools: ${value}`), 'test'), /denyTools must be a list/)
|
||||
}
|
||||
})
|
||||
test('ordered overlays add tools and append bodies without changing source', () => {
|
||||
const found = discover([project('---\nname: scout\naddTools: [write, read]\n---\nFirst', '01'), base('tools: [read, bash]'), project('---\nname: scout\naddTools: [message]\n---\nSecond', '02')])
|
||||
assert.deepEqual(found.errors, []); assert.deepEqual(found.replaced, [])
|
||||
const agent = found.definitions[0]
|
||||
assert.equal(agent.source, 'builtin'); assert.equal(agent.path, base().path)
|
||||
assert.deepEqual(agent.tools, ['read', 'bash', 'write', 'message'])
|
||||
assert.equal(agent.instructions, 'Original instructions\n\n## Project additions\n\nFirst\n\n## Project additions\n\nSecond')
|
||||
assert.deepEqual(agent.overlays, [project('', '01').path, project('', '02').path])
|
||||
})
|
||||
test('overlay settings replace while denials accumulate through empty overlay', () => {
|
||||
const found = discover([base('tools: [read]\ndenyTools: [unknown-tool]\nthinking: low'), project('---\nname: scout\naddTools: []\ndenyTools: [unknown-tool, bash]\nmodel: anthropic/claude-opus-5-5\nthinking: high\nfast: true\ndescription: Our scout\n---', '01'), project('---\nname: scout\naddTools: []\ndenyTools: []\n---', '02')])
|
||||
assert.deepEqual(found.errors, [])
|
||||
assert.deepEqual(found.definitions[0].denyTools, ['unknown-tool', 'bash'])
|
||||
assert.equal(found.definitions[0].thinking, 'high'); assert.equal(found.definitions[0].fast, true)
|
||||
assert.equal(found.definitions[0].description, 'Our scout')
|
||||
assert.deepEqual(found.definitions[0].tools, ['read'])
|
||||
})
|
||||
test('inherited tools retain deferred additions and do not mutate parent', () => {
|
||||
const parent = ['read', 'bash', 'write', 'subagent']
|
||||
const child = mergeOverlay(parseDefinition(definition('denyTools: [write, subagent]'), 'test'), parseOverlay('---\nname: scout\naddTools: [message]\n---', 'overlay'))
|
||||
assert.equal(child.tools, undefined); assert.deepEqual(child.addTools, ['message'])
|
||||
assert.deepEqual(toolsFor(child, parent), ['read', 'bash', 'message'])
|
||||
assert.deepEqual(toolsFor(parseDefinition(definition('tools: [read, write]\ndenyTools: [write]'), 'test'), parent), ['read'])
|
||||
assert.deepEqual(parent, ['read', 'bash', 'write', 'subagent'])
|
||||
})
|
||||
test('overlay errors do not replace a valid base', () => {
|
||||
const found = discover([base(), project('---\nname: scout\naddTools: [x]\ntools: [read]\n---', 'listed'), project('---\nname: scout\naddTools: []\ncolour: red\n---', 'unknown'), project('---\nname: nobody\naddTools: [x]\n---', 'orphan')])
|
||||
assert.deepEqual(Object.fromEntries(found.errors.map(error => [error.name, error.error])), {
|
||||
listed: 'Error: An overlay adds tools with addTools, not tools', unknown: 'Error: Unknown field: colour', orphan: 'Error: addTools needs an earlier definition named nobody',
|
||||
})
|
||||
assert.equal(found.definitions[0].instructions, 'Original instructions')
|
||||
assert.throws(() => parseOverlay('---\nname: scout\naddTools: read\n---', 'test'), /addTools must be a list/)
|
||||
})
|
||||
test('replacement uses name, not filename, and respects exact roots', () => {
|
||||
const found = discover([base(), project(definition('family: research', 'Replacement'), 'different'), { path: '/repos/other/.sausage/agents/scout.md', text: definition() }, { path: '/src/sausage/agents/nested/no.md', text: definition() }])
|
||||
assert.deepEqual(found.errors, []); assert.equal(found.definitions.length, 1)
|
||||
assert.equal(found.definitions[0].source, 'project'); assert.equal(found.definitions[0].instructions, 'Replacement')
|
||||
assert.deepEqual(found.replaced, [{ name: 'scout', path: project('', 'different').path, source: 'project' }])
|
||||
})
|
||||
test('family fallback, thinking override, model suffix, and unknown models', () => {
|
||||
const run = (fields: string) => definitionRuns(parseDefinition(definition(fields), 'test'), families, models)
|
||||
assert.equal(run('family: missing').family, 'default')
|
||||
assert.equal(run('family: constructor').family, 'default')
|
||||
assert.equal(run('family: research').thinking, 'high')
|
||||
assert.equal(run('family: research\nthinking: low').thinking, 'low')
|
||||
assert.equal(run('model: openai/gpt-6.1-sol').thinking, undefined)
|
||||
assert.equal(run('model: openai/gpt-6.1-sol:high').thinking, 'high')
|
||||
assert.equal(run('model: openai/gpt-6.1-sol:high\nthinking: low').thinking, 'low')
|
||||
for (const model of ['anthropic/unknown', 'unknown']) assert.match(discover([base(`model: ${model}`)]).errors[0].error, /model: Model .* not found/)
|
||||
const found = discover([base('model: openai/gpt-6.1-sol'), project('---\nname: scout\nfamily: lead\naddTools: []\n---')])
|
||||
assert.equal(found.definitions[0].model, undefined); assert.equal(found.definitions[0].runs.family, 'lead')
|
||||
const suffix = discover([base('thinking: low'), project('---\nname: scout\nmodel: openai/gpt-6.1-sol:high\naddTools: []\n---')])
|
||||
assert.equal(suffix.definitions[0].runs.thinking, 'high')
|
||||
})
|
||||
test('family world file requires valid refs and default', () => {
|
||||
for (const text of ['null', '[]', '{}', '{"default":{"model":"bad"}}', '{"Default":{"model":"openai/a"}}', '{"default":{"model":"openai/a","thinking":"bad"}}']) assert.throws(() => parseFamilies(text))
|
||||
assert.deepEqual(Object.keys(families), ['default', 'fast', 'review', 'research', 'design', 'lead'])
|
||||
assert.equal(families.review.thinking, 'xhigh')
|
||||
assert.equal(families.design.thinking, 'high')
|
||||
})
|
||||
test('all shipped definitions and overlays parse and discover', () => {
|
||||
const files = readdirSync('agents').filter(name => name.endsWith('.md')).map(name => ({ path: `/src/sausage/agents/${name}`, text: readFileSync(`agents/${name}`, 'utf8') }))
|
||||
assert.equal(files.length, 12)
|
||||
const available = ['read', 'bash', 'edit', 'write', 'message', 'note', 'notify', 'automation', 'branch', 'compact', 'remember', 'memory', 'todo', 'goal', 'subagent', 'plan', 'workspace']
|
||||
for (const file of files) {
|
||||
const agent = 'addTools' in parseFrontmatter(file.text).frontmatter ? parseOverlay(file.text, file.path) : parseDefinition(file.text, file.path)
|
||||
assert.ok(!/\b(pnpm|vitest|clone|instance|restart|web_search|mcp|setup|codemode|debug)\b|~\/\.ultimate/.test(agent.instructions), file.path)
|
||||
const fields = 'settings' in agent ? agent.settings : agent
|
||||
for (const tool of [...(fields.tools ?? []), ...(fields.denyTools ?? []), ...('addTools' in agent ? agent.addTools ?? [] : [])]) assert.ok(available.includes(tool), `${file.path}: ${tool}`)
|
||||
}
|
||||
const found = discover(files)
|
||||
assert.deepEqual(found.errors, []); assert.equal(found.definitions.length, 11)
|
||||
assert.equal(found.definitions.find(agent => agent.name === 'coordinator')!.runs.family, 'lead')
|
||||
for (const agent of found.definitions.filter(agent => agent.name.startsWith('reviewer'))) assert.equal(agent.runs.thinking, 'xhigh')
|
||||
})
|
||||
test('all applicable skills parse and avoid unavailable tools', () => {
|
||||
let skills = 0
|
||||
for (const name of readdirSync('skills')) {
|
||||
if (name === 'README.md') continue
|
||||
const parsed = parseFrontmatter(readFileSync(`skills/${name}/SKILL.md`, 'utf8'))
|
||||
assert.equal(parsed.frontmatter.name, name)
|
||||
assert.equal(typeof parsed.frontmatter.description, 'string'); assert.ok(parsed.body)
|
||||
assert.ok(!/\b(pnpm|vitest|clone|instance|restart|web_search|mcp|setup|codemode|debug)\b|~\/\.ultimate/.test(parsed.body), name)
|
||||
skills++
|
||||
}
|
||||
assert.equal(skills, 4)
|
||||
})
|
||||
console.log(`${count} checks passed`)
|
||||
@@ -0,0 +1,171 @@
|
||||
/** Pure parsing and ordered overlays for world agent files. The caller supplies all file contents and model configuration. */
|
||||
export const thinkingLevels = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const
|
||||
export type Thinking = typeof thinkingLevels[number]
|
||||
export type Family = { model: string; thinking?: Thinking }
|
||||
export type Families = Record<string, Family>
|
||||
export interface Definition {
|
||||
name: string
|
||||
description: string
|
||||
path: string
|
||||
instructions: string
|
||||
model?: string
|
||||
thinking?: Thinking
|
||||
family?: string
|
||||
tools?: string[]
|
||||
addTools?: string[]
|
||||
denyTools?: string[]
|
||||
isolate?: 'none' | 'worktree'
|
||||
resident?: boolean
|
||||
fast?: boolean
|
||||
summaries?: boolean
|
||||
icon?: string
|
||||
tint?: string
|
||||
overlays?: string[]
|
||||
}
|
||||
export type File = { path: string; text: string }
|
||||
const fields = ['name', 'description', 'model', 'thinking', 'family', 'tools', 'denyTools', 'isolate', 'resident', 'fast', 'summaries', 'icon', 'tint']
|
||||
const familyName = (name: string) => /^[a-z][a-z0-9-]{0,23}$/.test(name)
|
||||
const tools = (value: unknown): value is string[] => Array.isArray(value) && value.every(v => typeof v === 'string')
|
||||
|
||||
/** Scalars and flow lists, plus the block lists and folded description used in Ultimate's parser tests. Not general YAML. */
|
||||
function scalar(input: string): unknown {
|
||||
const text = input.trim()
|
||||
if (text.startsWith('[')) {
|
||||
if (!text.endsWith(']')) throw new Error('Unclosed flow list')
|
||||
const items = text.slice(1, -1).match(/\s*(?:"(?:\\.|[^"\\])*"|'(?:''|[^'])*'|[^,]+)/g) ?? []
|
||||
return items.filter(item => item.trim()).map(item => scalar(item))
|
||||
}
|
||||
if (text.startsWith('"')) return JSON.parse(text)
|
||||
if (text.startsWith("'")) {
|
||||
if (!text.endsWith("'")) throw new Error('Unclosed scalar')
|
||||
return text.slice(1, -1).replaceAll("''", "'")
|
||||
}
|
||||
if (text === 'true') return true
|
||||
if (text === 'false') return false
|
||||
if (text === 'null' || text === '~' || text === '') return null
|
||||
if (/^-?\d+(\.\d+)?$/.test(text)) return Number(text)
|
||||
if (text.startsWith('{')) return {} // Mapping values cannot satisfy any supported field.
|
||||
return text.replace(/\s+#.*$/, '')
|
||||
}
|
||||
export function parseFrontmatter(text: string): { frontmatter: Record<string, unknown>; body: string } {
|
||||
const lines = text.replaceAll('\r\n', '\n').split('\n')
|
||||
const end = lines.indexOf('---', 1)
|
||||
if (lines[0] !== '---' || end < 0) throw new Error('Expected frontmatter fields between --- lines')
|
||||
const frontmatter: Record<string, unknown> = {}
|
||||
for (let i = 1; i < end; i++) {
|
||||
if (!lines[i].trim() || lines[i].trimStart().startsWith('#')) continue
|
||||
const match = /^([A-Za-z][A-Za-z0-9]*):\s*(.*)$/.exec(lines[i])
|
||||
if (!match) throw new Error('Expected frontmatter fields between --- lines')
|
||||
const [, key, raw] = match
|
||||
let value = raw
|
||||
if (value.trim().startsWith('[') && !value.trim().endsWith(']')) {
|
||||
while (i + 1 < end && !value.trim().endsWith(']')) value += ' ' + lines[++i].trim()
|
||||
}
|
||||
if (raw === '>' || raw === '|' || raw === '') {
|
||||
const block: string[] = []
|
||||
while (i + 1 < end && /^\s+\S/.test(lines[i + 1])) block.push(lines[++i].trim())
|
||||
if (raw === '>') frontmatter[key] = block.join(' ')
|
||||
else if (raw === '|') frontmatter[key] = block.join('\n')
|
||||
else frontmatter[key] = block[0]?.startsWith('[') ? scalar(block.join(' ')) : block.length ? block.map(line => scalar(line.replace(/^-\s+/, ''))) : null
|
||||
} else frontmatter[key] = scalar(value)
|
||||
}
|
||||
if (!Object.keys(frontmatter).length) throw new Error('Expected frontmatter fields between --- lines')
|
||||
return { frontmatter, body: lines.slice(end + 1).join('\n').trim() }
|
||||
}
|
||||
function settings(data: Record<string, unknown>, overlay: boolean) {
|
||||
const unknown = Object.keys(data).find(key => !fields.includes(key) && !(overlay && key === 'addTools'))
|
||||
if (unknown) throw new Error(`Unknown field: ${unknown}`)
|
||||
if (typeof data.name !== 'string' || !/^[a-zA-Z0-9_-]+$/.test(data.name)) throw new Error('name must be a nonempty identifier')
|
||||
for (const key of ['description', 'model']) {
|
||||
if (data[key] !== undefined && (typeof data[key] !== 'string' || !data[key].trim())) throw new Error(key === 'description' ? 'description is required' : 'model must be a nonempty string')
|
||||
}
|
||||
if (data.family !== undefined && (typeof data.family !== 'string' || !familyName(data.family))) throw new Error('family must be a family name: lowercase letters, digits and -')
|
||||
if (data.thinking !== undefined && !thinkingLevels.includes(data.thinking as Thinking)) throw new Error(`thinking must be one of ${thinkingLevels.join(', ')}`)
|
||||
for (const key of ['tools', 'denyTools', 'addTools']) {
|
||||
if (data[key] !== undefined && !tools(data[key])) throw new Error(`${key} must be a list of tool names`)
|
||||
}
|
||||
if (data.isolate !== undefined && data.isolate !== 'none' && data.isolate !== 'worktree') throw new Error('isolate must be none or worktree')
|
||||
for (const key of ['resident', 'fast', 'summaries']) {
|
||||
if (data[key] !== undefined && typeof data[key] !== 'boolean') throw new Error(`${key} must be a boolean`)
|
||||
}
|
||||
if (data.icon !== undefined && (typeof data.icon !== 'string' || !/^[A-Za-z][A-Za-z0-9]*(-[a-z0-9]+)*$/.test(data.icon))) throw new Error('icon must be a lucide icon name, like Gem or notebook-pen')
|
||||
if (data.tint !== undefined && !['violet', 'blue', 'green', 'amber', 'red', 'pink', 'teal', 'grey'].includes(data.tint as string)) throw new Error('tint must be one of violet, blue, green, amber, red, pink, teal, grey')
|
||||
return { ...data, ...(typeof data.description === 'string' ? { description: data.description.trim() } : {}) }
|
||||
}
|
||||
export function parseDefinition(text: string, path: string): Definition {
|
||||
const { frontmatter, body } = parseFrontmatter(text)
|
||||
const parsed = settings(frontmatter, false)
|
||||
if (!parsed.description) throw new Error('description is required')
|
||||
if (!body) throw new Error('Instructions are required')
|
||||
return { ...parsed, path, instructions: body } as unknown as Definition
|
||||
}
|
||||
export function parseOverlay(text: string, path: string) {
|
||||
const { frontmatter, body } = parseFrontmatter(text)
|
||||
if ('tools' in frontmatter) throw new Error('An overlay adds tools with addTools, not tools')
|
||||
const parsed = settings(frontmatter, true)
|
||||
if (!tools(parsed.addTools)) throw new Error('addTools must be a list of tool names')
|
||||
const { name, addTools, ...rest } = parsed
|
||||
return { name: name as string, addTools, settings: rest as Partial<Definition>, path, instructions: body }
|
||||
}
|
||||
export function mergeOverlay(base: Definition, overlay: ReturnType<typeof parseOverlay>): Definition {
|
||||
const { model: _pin, ...unpinned } = base
|
||||
const suffix = overlay.settings.model?.match(/:(off|minimal|low|medium|high|xhigh|max)$/)
|
||||
const resolved = suffix ? { ...overlay.settings, model: overlay.settings.model!.slice(0, -suffix[0].length), thinking: overlay.settings.thinking ?? suffix[1] as Thinking } : overlay.settings
|
||||
return {
|
||||
...(overlay.settings.family && !overlay.settings.model ? unpinned : base),
|
||||
...resolved,
|
||||
...(overlay.settings.denyTools ? { denyTools: [...new Set([...(base.denyTools ?? []), ...overlay.settings.denyTools])] } : {}),
|
||||
...(base.tools ? { tools: [...new Set([...base.tools, ...overlay.addTools])] } : overlay.addTools.length ? { addTools: [...new Set([...(base.addTools ?? []), ...overlay.addTools])] } : {}),
|
||||
instructions: overlay.instructions ? `${base.instructions}\n\n## Project additions\n\n${overlay.instructions}` : base.instructions,
|
||||
overlays: [...(base.overlays ?? []), overlay.path],
|
||||
}
|
||||
}
|
||||
export function parseFamilies(text: string): Families {
|
||||
const value = JSON.parse(text)
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('families must be an object')
|
||||
for (const [name, spec] of Object.entries(value) as [string, Family][]) {
|
||||
if (!familyName(name) || !spec || typeof spec !== 'object' || !modelRef(spec.model)) throw new Error(`Invalid family: ${name}`)
|
||||
if (spec.thinking !== undefined && !thinkingLevels.includes(spec.thinking)) throw new Error(`Invalid thinking for family: ${name}`)
|
||||
}
|
||||
if (!value.default) throw new Error('default family is required')
|
||||
return value
|
||||
}
|
||||
function modelRef(model: unknown): model is string { return typeof model === 'string' && /^[^/\s]+\/\S+$/.test(model) }
|
||||
export function definitionRuns(definition: Definition, families: Families, models: readonly string[]) {
|
||||
const family = Object.hasOwn(families, definition.family ?? 'default') ? definition.family ?? 'default' : 'default'
|
||||
let model = definition.model ?? families[family].model
|
||||
let thinking = definition.thinking ?? (definition.model ? undefined : families[family].thinking)
|
||||
const suffix = /:(off|minimal|low|medium|high|xhigh|max)$/.exec(model)
|
||||
if (suffix) { model = model.slice(0, -suffix[0].length); thinking = definition.thinking ?? suffix[1] as Thinking }
|
||||
if (!modelRef(model) || !models.includes(model)) throw new Error(`model: Model "${model}" not found`)
|
||||
const slash = model.indexOf('/')
|
||||
return { model: { provider: model.slice(0, slash), modelId: model.slice(slash + 1) }, thinking, ...(definition.model ? { via: 'pin' as const } : { via: 'family' as const, family }) }
|
||||
}
|
||||
/** Sorted built-ins precede sorted project files. Invalid files produce errors without replacing an earlier definition. */
|
||||
export function discoverAgents(files: readonly File[], builtinRoot: string, repo: string, families: Families, models: readonly string[]) {
|
||||
const definitions = new Map<string, Definition & { source: 'builtin' | 'project' }>()
|
||||
const errors: { name: string; path: string; source: string; error: string }[] = []
|
||||
const replaced: { name: string; path: string; source: string }[] = []
|
||||
for (const [source, root] of [['builtin', builtinRoot + '/agents/'], ['project', repo + '/.sausage/agents/']] as const) {
|
||||
for (const file of files.filter(file => file.path.startsWith(root) && !file.path.slice(root.length).includes('/') && file.path.endsWith('.md')).toSorted((a, b) => a.path.localeCompare(b.path))) {
|
||||
try {
|
||||
let definition: Definition
|
||||
if ('addTools' in parseFrontmatter(file.text).frontmatter) {
|
||||
const overlay = parseOverlay(file.text, file.path)
|
||||
const base = definitions.get(overlay.name)
|
||||
if (!base) throw new Error(`addTools needs an earlier definition named ${overlay.name}`)
|
||||
definition = mergeOverlay(base, overlay)
|
||||
} else definition = parseDefinition(file.text, file.path)
|
||||
definitionRuns(definition, families, models)
|
||||
const previous = definitions.get(definition.name)
|
||||
if (source === 'project' && previous?.source === 'builtin' && !definition.overlays) replaced.push({ name: definition.name, path: file.path, source })
|
||||
definitions.set(definition.name, { ...definition, source: definition.overlays ? previous!.source : source })
|
||||
} catch (error) { errors.push({ name: file.path.split('/').at(-1)!.slice(0, -3), path: file.path, source, error: String(error) }) }
|
||||
}
|
||||
}
|
||||
return { definitions: [...definitions.values()].map(definition => ({ ...definition, runs: definitionRuns(definition, families, models) })), errors, replaced }
|
||||
}
|
||||
/** Tool denials also apply to inherited tools. Inputs stay unchanged. */
|
||||
export function toolsFor(definition: Definition, parent: readonly string[]): string[] {
|
||||
return [...new Set([...(definition.tools ?? parent), ...(definition.addTools ?? [])])].filter(tool => !definition.denyTools?.includes(tool))
|
||||
}
|
||||
+12
-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,17 @@ 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"
|
||||
tokio-rustls = "0.26.6"
|
||||
webpki-roots = "1.0.11"
|
||||
if-addrs = "0.15.0"
|
||||
toml = "1.2.1"
|
||||
http-body = "1.1.0"
|
||||
http-body-util = "0.1.5"
|
||||
hyper-util = { version = "0.1.21", features = ["tokio"] }
|
||||
hyper = { version = "1.12.0", features = ["client", "http1", "server"] }
|
||||
tiny_http = "0.12"
|
||||
|
||||
[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.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user