xbintsc 0.5.52 → 0.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/README.zh-CN.md +9 -3
- package/dist/scripts/fetch-tarball.d.ts +28 -0
- package/dist/scripts/fetch-tarball.js +57 -0
- package/dist/scripts/fetch-tarball.js.map +1 -0
- package/dist/scripts/gui-build-options.d.ts +77 -0
- package/dist/scripts/gui-build-options.js +134 -0
- package/dist/scripts/gui-build-options.js.map +1 -0
- package/dist/src/cli/help.d.ts +1 -1
- package/dist/src/cli/hints.d.ts +6 -2
- package/dist/src/cli/hints.js +16 -2
- package/dist/src/cli/hints.js.map +1 -1
- package/dist/src/cli/main.js +9 -0
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/driver/app-artifacts.d.ts +2 -0
- package/dist/src/driver/app-artifacts.js +3 -3
- package/dist/src/driver/app-artifacts.js.map +1 -1
- package/dist/src/driver/compiler.js +147 -52
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/icon.d.ts +1 -1
- package/dist/src/driver/icon.js +3 -3
- package/dist/src/driver/icon.js.map +1 -1
- package/dist/src/driver/macos-sdk.d.ts +32 -0
- package/dist/src/driver/macos-sdk.js +50 -0
- package/dist/src/driver/macos-sdk.js.map +1 -0
- package/dist/src/driver/options.d.ts +6 -0
- package/dist/src/driver/platform-support.d.ts +45 -0
- package/dist/src/driver/platform-support.js +66 -0
- package/dist/src/driver/platform-support.js.map +1 -0
- package/dist/src/driver/prebuilt.d.ts +26 -0
- package/dist/src/driver/prebuilt.js +58 -0
- package/dist/src/driver/prebuilt.js.map +1 -0
- package/dist/src/driver/runtime-objects.d.ts +11 -0
- package/dist/src/driver/runtime-objects.js +14 -5
- package/dist/src/driver/runtime-objects.js.map +1 -1
- package/dist/src/driver/toolchain-provider.d.ts +8 -0
- package/dist/src/driver/toolchain-provider.js +8 -0
- package/dist/src/driver/toolchain-provider.js.map +1 -1
- package/dist/src/driver/toolchain.d.ts +20 -1
- package/dist/src/driver/toolchain.js +19 -0
- package/dist/src/driver/toolchain.js.map +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/driver/version.js.map +1 -1
- package/dist/src/driver/win-entry.d.ts +1 -1
- package/dist/src/driver/win-entry.js +4 -4
- package/dist/src/driver/win-entry.js.map +1 -1
- package/dist/src/extensions/gui/html.d.ts +10 -7
- package/dist/src/extensions/gui/html.js +53 -29
- package/dist/src/extensions/gui/html.js.map +1 -1
- package/dist/src/extensions/node/child_process/index.d.ts +4 -3
- package/dist/src/extensions/node/child_process/index.js +4 -3
- package/dist/src/extensions/node/child_process/index.js.map +1 -1
- package/dist/tests/cli/hints.test.js +9 -1
- package/dist/tests/cli/hints.test.js.map +1 -1
- package/dist/tests/cli/main.test.js +9 -0
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/driver/compiler.test.js +107 -0
- package/dist/tests/driver/compiler.test.js.map +1 -1
- package/dist/tests/driver/cxx-compile-flags.test.d.ts +1 -0
- package/dist/tests/driver/cxx-compile-flags.test.js +24 -0
- package/dist/tests/driver/cxx-compile-flags.test.js.map +1 -0
- package/dist/tests/driver/macos-sdk.test.d.ts +1 -0
- package/dist/tests/driver/macos-sdk.test.js +83 -0
- package/dist/tests/driver/macos-sdk.test.js.map +1 -0
- package/dist/tests/driver/platform-support.test.d.ts +1 -0
- package/dist/tests/driver/platform-support.test.js +41 -0
- package/dist/tests/driver/platform-support.test.js.map +1 -0
- package/dist/tests/driver/prebuilt.test.d.ts +1 -0
- package/dist/tests/driver/prebuilt.test.js +44 -0
- package/dist/tests/driver/prebuilt.test.js.map +1 -0
- package/dist/tests/driver/toolchain-provider.test.js +24 -0
- package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
- package/dist/tests/driver/toolchain.test.js +23 -0
- package/dist/tests/driver/toolchain.test.js.map +1 -1
- package/dist/tests/e2e/async-await.test.js +4 -2
- package/dist/tests/e2e/async-await.test.js.map +1 -1
- package/dist/tests/e2e/compile-node.test.js +21 -0
- package/dist/tests/e2e/compile-node.test.js.map +1 -1
- package/dist/tests/e2e/fetch.test.js +34 -6
- package/dist/tests/e2e/fetch.test.js.map +1 -1
- package/dist/tests/e2e/gui-example-pelican3d.test.d.ts +10 -0
- package/dist/tests/e2e/gui-example-pelican3d.test.js +94 -0
- package/dist/tests/e2e/gui-example-pelican3d.test.js.map +1 -0
- package/dist/tests/e2e/gui-script.test.js +48 -0
- package/dist/tests/e2e/gui-script.test.js.map +1 -1
- package/dist/tests/e2e/unhandled-rejection.test.d.ts +14 -0
- package/dist/tests/e2e/unhandled-rejection.test.js +107 -0
- package/dist/tests/e2e/unhandled-rejection.test.js.map +1 -0
- package/dist/tests/extensions/gui.test.js +44 -0
- package/dist/tests/extensions/gui.test.js.map +1 -1
- package/dist/tests/scripts/fetch-tarball.test.d.ts +1 -0
- package/dist/tests/scripts/fetch-tarball.test.js +70 -0
- package/dist/tests/scripts/fetch-tarball.test.js.map +1 -0
- package/doc/ai/cli.md +6 -0
- package/doc/ai/language-support.md +1 -0
- package/doc/ai/troubleshooting.md +32 -2
- package/doc/ai/zh-CN/cli.md +5 -0
- package/doc/ai/zh-CN/language-support.md +1 -0
- package/doc/ai/zh-CN/troubleshooting.md +27 -2
- package/doc/gui/build.md +7 -3
- package/doc/gui/dom.md +3 -1
- package/doc/gui/webgl.md +4 -0
- package/doc/gui-scripts.md +33 -17
- package/doc/gui.md +17 -1
- package/doc/implemented/async.md +1 -0
- package/doc/implemented/modules.md +9 -6
- package/doc/implemented/node/child-process.md +12 -4
- package/doc/implemented/runtime-core.md +1 -1
- package/doc/npm-package.md +23 -2
- package/doc/post/make-js-great-again.md +30 -0
- package/doc/requirements.md +31 -0
- package/doc/unimplemented/deviations.md +1 -0
- package/doc/zh-CN/gui/build.md +4 -2
- package/doc/zh-CN/gui/dom.md +3 -1
- package/doc/zh-CN/gui/webgl.md +1 -0
- package/doc/zh-CN/gui-scripts.md +23 -11
- package/doc/zh-CN/gui.md +11 -1
- package/doc/zh-CN/implemented/async.md +1 -0
- package/doc/zh-CN/implemented/modules.md +8 -4
- package/doc/zh-CN/implemented/node/child-process.md +10 -3
- package/doc/zh-CN/implemented/runtime-core.md +1 -1
- package/doc/zh-CN/npm-package.md +18 -1
- package/doc/zh-CN/post/make-js-great-again.md +32 -0
- package/doc/zh-CN/requirements.md +25 -0
- package/doc/zh-CN/unimplemented/deviations.md +1 -0
- package/package.json +7 -7
- package/runtime/ext_node/child_process/child_process.c +149 -7
- package/runtime/rt.h +4 -0
- package/runtime/xt_async.c +4 -0
- package/runtime/xt_generator.c +2 -0
- package/runtime/xt_loop.c +8 -4
- package/runtime/xt_promise.c +103 -0
- package/scripts/build-gui.ts +17 -5
- package/scripts/build-media.ts +89 -10
- package/scripts/build-runtime.ts +7 -2
- package/scripts/fetch-tarball.ts +71 -0
- package/scripts/gui-build-cache.ts +28 -0
- package/scripts/gui-build-options.ts +26 -2
- package/scripts/package-release.ts +8 -0
- package/src/cli/hints.ts +19 -2
- package/src/cli/main.ts +11 -0
- package/src/driver/app-artifacts.ts +5 -3
- package/src/driver/compiler.ts +167 -56
- package/src/driver/icon.ts +3 -2
- package/src/driver/macos-sdk.ts +52 -0
- package/src/driver/options.ts +6 -0
- package/src/driver/platform-support.ts +69 -0
- package/src/driver/prebuilt.ts +61 -0
- package/src/driver/runtime-objects.ts +26 -5
- package/src/driver/toolchain-provider.ts +11 -0
- package/src/driver/toolchain.ts +26 -1
- package/src/driver/version.ts +1 -1
- package/src/driver/win-entry.ts +4 -3
- package/src/extensions/gui/html.ts +61 -31
- package/src/extensions/node/child_process/index.ts +4 -3
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# 这可能是今年最让JS开发者激动的事情了
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
上面是我用JS+HTML+WebGL2做的鹈鹕3D骑自行车,FPS也非常不错。
|
|
6
|
+
|
|
7
|
+
你可能会说“这不是用Ai有手就行了么?”
|
|
8
|
+
|
|
9
|
+
但是,如果我说我把这上面用xbintsc编译成原生二进制,还不到10MB,还能在没有WebView环境上跑,还能支持Node.js和HTML混写,如下:
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<html>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="out">loading...</div>
|
|
15
|
+
<script lang="ts">
|
|
16
|
+
import { readFileSync } from "fs";
|
|
17
|
+
|
|
18
|
+
const out = document.getElementById("out");
|
|
19
|
+
const world = readFileSync("data.txt", "utf8");
|
|
20
|
+
out.textContent = "hello " + world;
|
|
21
|
+
</script>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
要知道以前光chrome最少也得有100MB,用xbintsc编译产物10MB还不需要WebView环境,这可不仅仅代表桌面端开发体积极大减小和更加正确的实现路径的问题。
|
|
27
|
+
|
|
28
|
+
更加具有划时代意义的是,JS开发者在嵌入式设备跑你想要做的应用成为了可能。
|
|
29
|
+
|
|
30
|
+
[xbintsc开源地址](https://github.com/zy445566/xbintsc)
|
|
31
|
+
|
|
32
|
+
|
|
@@ -14,6 +14,27 @@ xbintsc 不再自带编译器,因此每个平台都需要安装一个兼容 cl
|
|
|
14
14
|
如果通过 npm 安装编译器而不是使用发布压缩包,Node.js 22+ 会直接运行编译器本身;
|
|
15
15
|
见[通过 npm 包使用 xbintsc](./npm-package.md)。
|
|
16
16
|
|
|
17
|
+
## 支持的平台
|
|
18
|
+
|
|
19
|
+
发布二进制和预编译运行库归档面向 xbintsc 支持的最低平台版本。CI 使用**各宿主仍
|
|
20
|
+
能产出发布包的最老工具链**构建它们,因此你机器上更新的 Xcode 或 Visual Studio 可以链接
|
|
21
|
+
这些产物;反过来(旧工具链链接新产物)才会出现 `LNK2038`、`Undefined symbols` 或
|
|
22
|
+
`was built for newer macOS version`。
|
|
23
|
+
|
|
24
|
+
| 平台 | 生成的二进制可运行于 | 构建基线(CI) |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| macOS x64 / arm64 | **macOS 11.0**(Big Sur)及以上 | 镜像中最老的 clang 16+ Xcode,`MACOSX_DEPLOYMENT_TARGET=11.0` |
|
|
27
|
+
| Windows x64 | **Windows 11** 及以上 | `windows-2025` 镜像自带的工具链(当前为 VS 2026) |
|
|
28
|
+
| Windows arm64 | **Windows 11** | `windows-11-arm` 镜像自带的工具链(当前为 VS 2026;没有更老的 arm64 镜像) |
|
|
29
|
+
| Linux x64 / arm64 | **glibc 2.39**(Ubuntu 24.04)及以上 | `ubuntu-24.04` 上的 clang 16 + lld 16 |
|
|
30
|
+
|
|
31
|
+
编译器工具链下限为:macOS 上的 Xcode 16(Apple clang 16)、Windows 上 `windows-2025`
|
|
32
|
+
镜像自带的 Visual Studio(当前为 VS 2026)、Linux 上的 clang 16。因此构建机的要求比生成二进制的
|
|
33
|
+
运行要求更高:Xcode 16 本身需要 macOS 14.5+,macOS 11–14 的构建机需要另外安装
|
|
34
|
+
LLVM 16+(例如 Homebrew 的 `llvm`)。`xbintsc doctor` 会打印当前宿主的支持下限。如果归档是用比本机更新的工具链构建的,`build` 会自动改用随包 C 源码
|
|
35
|
+
重试(见 [npm 包指南](./npm-package.md#首次构建与缓存));随包的 `gui.a`/`media.a`
|
|
36
|
+
无法这样重编,因为 SDL3/FFmpeg 的源码不在包里。
|
|
37
|
+
|
|
17
38
|
clang 15 及更旧版本会在任何程序的第一个字符串字面量处失败:
|
|
18
39
|
|
|
19
40
|
```
|
|
@@ -104,6 +125,10 @@ xcode-select -p # 打印当前开发者目录
|
|
|
104
125
|
xcrun --show-sdk-path # 打印 SDK 路径
|
|
105
126
|
```
|
|
106
127
|
|
|
128
|
+
xbintsc 会自己执行第二条命令,并把结果以 `-isysroot` 传给 clang,因此即使 clang
|
|
129
|
+
是用绝对路径选中的(CI 的基线 Xcode,或 `xbintsc_CLANG`),也仍能找到 SDK 里的
|
|
130
|
+
C 头文件与链接桩。自己设置 `SDKROOT` 优先,可用它固定某个 SDK。
|
|
131
|
+
|
|
107
132
|
## Linux —— 安装 clang 与 lld
|
|
108
133
|
|
|
109
134
|
常规的 glibc 发行版加上 `clang`、`lld` 即可:
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
| 内存管理 | 非移动标记-清扫回收器(在 `xt_alloc` 之后):显式根、子系统根提供者与保守 C 栈扫描。单线程、stop-the-world;无移动/压缩与分代 |
|
|
17
17
|
| 函数 `arity` / 调用参数个数 | 对本地可见函数的直接调用由默认[语义检查](../implemented/checker.md)校验(`TS4004`);通过属性、导入、展开实参或重新赋值后的绑定发起的调用不校验。`fn.length` 报告声明的 arity |
|
|
18
18
|
| `async` / `await` | 已在栈式协程上真实挂起;`await` 总会排入一次微任务(非 Promise 与已 settle 的 Promise 也一样),因此与 pending `.then` 回调的交错顺序符合规范。顶层 `await` 会阻塞事件循环等待,而不是让出模块主体。`fs/promises` 与 `fetch` 把阻塞工作 offload 到 4 线程池并在 reactor 上 settle。异步生成器可在 `await` 与 `yield` 处挂起,`for await...of` 每次迭代等待一步 |
|
|
19
|
+
| 未处理的 rejection | 在程序结束时才报告,而不是在当轮 tick 结束时:无人观察的 rejection 会在事件循环排空后打印并使进程以状态码 1 退出,因此长期运行的进程(如服务端)会一直运行到退出,稍后补挂的处理函数也仍会抑制该报告。Node 在微任务队列排空后立即抛出致命错误 |
|
|
19
20
|
| 异步生成器清理 | `for await...of` 提前退出时不会调用迭代器的 `return()`。异步生成器中的 `yield*` 只委托同步可迭代对象。在挂起的 `yield` 处调用 `.return()` 会直接完成而不执行 `finally`(与同步生成器相同的限制) |
|
|
20
21
|
| `super` | `super.x` / `super[x]` 从当前执行类的父级持有者读取(实例方法中是父原型,`static` 方法或静态块中是父构造函数),因此任意继承深度都正确 |
|
|
21
22
|
| `Error.stack` | 仅函数级帧:每帧为 `at 名称 (文件:行)`,行号取自函数声明(最内层帧会更新为错误构造处);没有列号,生成器 / `async` 函数体不产生帧(它们会切换栈) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xbintsc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.8",
|
|
4
4
|
"description": "xbintsc - a TypeScript binary compiler (TypeScript -> LLVM IR -> native binary)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -72,12 +72,12 @@
|
|
|
72
72
|
"vitest": "^2.1.8"
|
|
73
73
|
},
|
|
74
74
|
"optionalDependencies": {
|
|
75
|
-
"@xbintsc/linux-x64": "0.
|
|
76
|
-
"@xbintsc/linux-arm64": "0.
|
|
77
|
-
"@xbintsc/darwin-x64": "0.
|
|
78
|
-
"@xbintsc/darwin-arm64": "0.
|
|
79
|
-
"@xbintsc/win32-x64": "0.
|
|
80
|
-
"@xbintsc/win32-arm64": "0.
|
|
75
|
+
"@xbintsc/linux-x64": "0.6.8",
|
|
76
|
+
"@xbintsc/linux-arm64": "0.6.8",
|
|
77
|
+
"@xbintsc/darwin-x64": "0.6.8",
|
|
78
|
+
"@xbintsc/darwin-arm64": "0.6.8",
|
|
79
|
+
"@xbintsc/win32-x64": "0.6.8",
|
|
80
|
+
"@xbintsc/win32-arm64": "0.6.8"
|
|
81
81
|
},
|
|
82
82
|
"engines": {
|
|
83
83
|
"node": ">=22"
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* const result = spawnSync("clang", ["--version"], { cwd, encoding: "utf8" });
|
|
8
8
|
* result.status / result.stdout / result.stderr
|
|
9
9
|
*
|
|
10
|
-
* `options.cwd`
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* `options.cwd` and `options.env` are honoured (the latter with Node's replace
|
|
11
|
+
* semantics); stdout/stderr are always captured as UTF-8 strings. On Windows
|
|
12
|
+
* the child runs under `CreateProcess` with anonymous pipes drained by reader
|
|
13
|
+
* threads (which also avoids pipe-buffer deadlocks).
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
#include "rt.h"
|
|
@@ -60,6 +61,58 @@ static int option_stdio_inherit(xt_value options) {
|
|
|
60
61
|
return stdio && XT_IS_STRING(stdio) && strcmp(xt_string_data(stdio), "inherit") == 0;
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
/*
|
|
65
|
+
* `options.env` replaces the child's environment, exactly like Node: an absent
|
|
66
|
+
* option inherits the parent's environment, while a present one is the complete
|
|
67
|
+
* environment (spread `process.env` into it to extend rather than replace).
|
|
68
|
+
*
|
|
69
|
+
* The runtime used to ignore this option, which made every `spawnSync` inherit:
|
|
70
|
+
* a caller passing `SDKROOT` or `INCLUDE` silently got nothing, and a
|
|
71
|
+
* self-hosted compiler could not hand the macOS SDK to clang.
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
/** The `env` object, or `XT_UNDEFINED` when the child should inherit. */
|
|
75
|
+
static xt_value option_env(xt_value options) {
|
|
76
|
+
if (!XT_IS_OBJECT(options)) return XT_UNDEFINED;
|
|
77
|
+
xt_value env = xt_object_get(options, xt_string_from_cstr("env"));
|
|
78
|
+
return XT_IS_OBJECT(env) ? env : XT_UNDEFINED;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The number of `KEY=VALUE` entries the environment object provides. */
|
|
82
|
+
static int32_t env_size(xt_value env) {
|
|
83
|
+
xt_value keys = xt_object_keys(env);
|
|
84
|
+
return XT_IS_ARRAY(keys) ? xt_array_size(keys) : 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static void free_env_entries(char **entries, int32_t count) {
|
|
88
|
+
if (entries == NULL) return;
|
|
89
|
+
for (int32_t i = 0; i < count; i++) free(entries[i]);
|
|
90
|
+
free(entries);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** `KEY=VALUE` entries for the environment object, in object key order. */
|
|
94
|
+
static char **env_entries(xt_value env, int32_t count) {
|
|
95
|
+
char **entries = (char **)calloc((size_t)(count > 0 ? count : 1), sizeof(char *));
|
|
96
|
+
if (entries == NULL) return NULL;
|
|
97
|
+
xt_value keys = xt_object_keys(env);
|
|
98
|
+
xt_value *items = XT_IS_ARRAY(keys) ? xt_array_items(keys) : NULL;
|
|
99
|
+
for (int32_t i = 0; i < count; i++) {
|
|
100
|
+
xt_value key = items[i];
|
|
101
|
+
xt_value raw = xt_object_get(env, key);
|
|
102
|
+
/* Node ignores `undefined` values instead of passing "undefined". */
|
|
103
|
+
if (XT_IS_UNDEFINED(raw)) continue;
|
|
104
|
+
const char *name = xt_string_data(xt_to_string(key));
|
|
105
|
+
const char *value = xt_string_data(xt_to_string(raw));
|
|
106
|
+
if (name == NULL || value == NULL) continue;
|
|
107
|
+
size_t length = strlen(name) + strlen(value) + 2;
|
|
108
|
+
char *entry = (char *)malloc(length);
|
|
109
|
+
if (entry == NULL) continue;
|
|
110
|
+
snprintf(entry, length, "%s=%s", name, value);
|
|
111
|
+
entries[i] = entry;
|
|
112
|
+
}
|
|
113
|
+
return entries;
|
|
114
|
+
}
|
|
115
|
+
|
|
63
116
|
static xt_value make_result(double status, const char *out, const char *err) {
|
|
64
117
|
xt_value result = xt_object_new();
|
|
65
118
|
xt_set(result, xt_string_from_cstr("status"), xt_number(status));
|
|
@@ -181,18 +234,60 @@ static char *build_command_line(char **child_argv, int count) {
|
|
|
181
234
|
return line;
|
|
182
235
|
}
|
|
183
236
|
|
|
237
|
+
/* `CreateProcessA` wants one ANSI block, entries sorted case-insensitively. */
|
|
238
|
+
static int compare_env_entries(const void *left, const void *right) {
|
|
239
|
+
const char *a = *(const char *const *)left;
|
|
240
|
+
const char *b = *(const char *const *)right;
|
|
241
|
+
if (a == NULL) return b == NULL ? 0 : 1;
|
|
242
|
+
if (b == NULL) return -1;
|
|
243
|
+
return _stricmp(a, b);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** The environment block for `CreateProcessA`, or NULL to inherit. */
|
|
247
|
+
static char *build_env_block(xt_value env) {
|
|
248
|
+
if (!XT_IS_OBJECT(env)) return NULL;
|
|
249
|
+
int32_t count = env_size(env);
|
|
250
|
+
char **entries = env_entries(env, count);
|
|
251
|
+
if (entries == NULL) return NULL;
|
|
252
|
+
qsort(entries, (size_t)count, sizeof(char *), compare_env_entries);
|
|
253
|
+
size_t total = 1;
|
|
254
|
+
for (int32_t i = 0; i < count; i++) total += entries[i] != NULL ? strlen(entries[i]) + 1 : 0;
|
|
255
|
+
char *block = (char *)malloc(total + 1);
|
|
256
|
+
if (block == NULL) {
|
|
257
|
+
free_env_entries(entries, count);
|
|
258
|
+
return NULL;
|
|
259
|
+
}
|
|
260
|
+
char *cursor = block;
|
|
261
|
+
for (int32_t i = 0; i < count; i++) {
|
|
262
|
+
if (entries[i] == NULL) continue;
|
|
263
|
+
size_t length = strlen(entries[i]) + 1;
|
|
264
|
+
memcpy(cursor, entries[i], length);
|
|
265
|
+
cursor += length;
|
|
266
|
+
}
|
|
267
|
+
/* The block ends with an extra terminator; an empty environment is "\0\0". */
|
|
268
|
+
*cursor++ = 0;
|
|
269
|
+
*cursor = 0;
|
|
270
|
+
free_env_entries(entries, count);
|
|
271
|
+
return block;
|
|
272
|
+
}
|
|
273
|
+
|
|
184
274
|
xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
185
275
|
const char *command = argc > 0 ? xt_string_data(xt_to_string(argv[0])) : NULL;
|
|
186
276
|
if (!command) command = "";
|
|
187
277
|
xt_value args = argc > 1 ? argv[1] : XT_UNDEFINED;
|
|
188
278
|
const char *cwd = argc > 2 ? option_cwd(argv[2]) : NULL;
|
|
279
|
+
char *env_block = build_env_block(option_env(argc > 2 ? argv[2] : XT_UNDEFINED));
|
|
189
280
|
|
|
190
281
|
int count = 0;
|
|
191
282
|
char **child_argv = (char **)build_argv(command, args, &count);
|
|
192
|
-
if (!child_argv)
|
|
283
|
+
if (!child_argv) {
|
|
284
|
+
free(env_block);
|
|
285
|
+
return make_result(-1, "", "out of memory");
|
|
286
|
+
}
|
|
193
287
|
char *command_line = build_command_line(child_argv, count);
|
|
194
288
|
if (!command_line) {
|
|
195
289
|
free(child_argv);
|
|
290
|
+
free(env_block);
|
|
196
291
|
return make_result(-1, "", "out of memory");
|
|
197
292
|
}
|
|
198
293
|
|
|
@@ -204,9 +299,10 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
204
299
|
|
|
205
300
|
if (option_stdio_inherit(argc > 2 ? argv[2] : XT_UNDEFINED)) {
|
|
206
301
|
BOOL created = CreateProcessA(
|
|
207
|
-
NULL, command_line, NULL, NULL, TRUE, 0,
|
|
302
|
+
NULL, command_line, NULL, NULL, TRUE, 0, env_block, cwd, &startup, &process);
|
|
208
303
|
free(command_line);
|
|
209
304
|
free(child_argv);
|
|
305
|
+
free(env_block);
|
|
210
306
|
if (!created) return make_result(-1, "", "failed to start process");
|
|
211
307
|
WaitForSingleObject(process.hProcess, INFINITE);
|
|
212
308
|
DWORD exit_code = 0;
|
|
@@ -229,6 +325,7 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
229
325
|
close_capture_pipe(&err_pipe);
|
|
230
326
|
free(command_line);
|
|
231
327
|
free(child_argv);
|
|
328
|
+
free(env_block);
|
|
232
329
|
return make_result(-1, "", "could not create pipes");
|
|
233
330
|
}
|
|
234
331
|
startup.dwFlags = STARTF_USESTDHANDLES;
|
|
@@ -237,9 +334,10 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
237
334
|
startup.hStdError = err_write;
|
|
238
335
|
|
|
239
336
|
BOOL created = CreateProcessA(
|
|
240
|
-
NULL, command_line, NULL, NULL, TRUE, 0,
|
|
337
|
+
NULL, command_line, NULL, NULL, TRUE, 0, env_block, cwd, &startup, &process);
|
|
241
338
|
free(command_line);
|
|
242
339
|
free(child_argv);
|
|
340
|
+
free(env_block);
|
|
243
341
|
CloseHandle(out_write);
|
|
244
342
|
CloseHandle(err_write);
|
|
245
343
|
if (!created) {
|
|
@@ -285,30 +383,68 @@ static void append_chunk(char **buffer, size_t *length, size_t *capacity, const
|
|
|
285
383
|
(*buffer)[*length] = 0;
|
|
286
384
|
}
|
|
287
385
|
|
|
386
|
+
/** A NULL-terminated `envp` for `exec`, or NULL when the child inherits. */
|
|
387
|
+
static char **build_envp(xt_value env) {
|
|
388
|
+
if (!XT_IS_OBJECT(env)) return NULL;
|
|
389
|
+
int32_t count = env_size(env);
|
|
390
|
+
char **entries = env_entries(env, count);
|
|
391
|
+
if (entries == NULL) return NULL;
|
|
392
|
+
char **envp = (char **)calloc((size_t)count + 1, sizeof(char *));
|
|
393
|
+
if (envp == NULL) {
|
|
394
|
+
free_env_entries(entries, count);
|
|
395
|
+
return NULL;
|
|
396
|
+
}
|
|
397
|
+
int32_t kept = 0;
|
|
398
|
+
for (int32_t i = 0; i < count; i++) {
|
|
399
|
+
if (entries[i] != NULL) envp[kept++] = entries[i];
|
|
400
|
+
}
|
|
401
|
+
envp[kept] = NULL;
|
|
402
|
+
/* The strings now belong to `envp`; only the index array is released here. */
|
|
403
|
+
free(entries);
|
|
404
|
+
return envp;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** Release an `envp` built by {@link build_envp}. */
|
|
408
|
+
static void free_envp(char **envp) {
|
|
409
|
+
if (envp == NULL) return;
|
|
410
|
+
for (char **entry = envp; *entry != NULL; entry++) free(*entry);
|
|
411
|
+
free(envp);
|
|
412
|
+
}
|
|
413
|
+
|
|
288
414
|
xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
289
415
|
const char *command = argc > 0 ? xt_string_data(xt_to_string(argv[0])) : NULL;
|
|
290
416
|
if (!command) command = "";
|
|
291
417
|
xt_value args = argc > 1 ? argv[1] : XT_UNDEFINED;
|
|
292
418
|
const char *cwd = argc > 2 ? option_cwd(argv[2]) : NULL;
|
|
419
|
+
char **envp = build_envp(option_env(argc > 2 ? argv[2] : XT_UNDEFINED));
|
|
293
420
|
|
|
294
421
|
int count = 0;
|
|
295
422
|
char **child_argv = (char **)build_argv(command, args, &count);
|
|
296
|
-
if (!child_argv)
|
|
423
|
+
if (!child_argv) {
|
|
424
|
+
free_envp(envp);
|
|
425
|
+
return make_result(-1, "", "out of memory");
|
|
426
|
+
}
|
|
297
427
|
|
|
298
428
|
if (option_stdio_inherit(argc > 2 ? argv[2] : XT_UNDEFINED)) {
|
|
299
429
|
pid_t inherit_pid = fork();
|
|
300
430
|
if (inherit_pid < 0) {
|
|
301
431
|
free(child_argv);
|
|
432
|
+
free_envp(envp);
|
|
302
433
|
return make_result(-1, "", "failed to fork");
|
|
303
434
|
}
|
|
304
435
|
if (inherit_pid == 0) {
|
|
305
436
|
if (cwd) chdir(cwd);
|
|
437
|
+
if (envp != NULL) {
|
|
438
|
+
extern char **environ;
|
|
439
|
+
environ = envp;
|
|
440
|
+
}
|
|
306
441
|
execvp(command, child_argv);
|
|
307
442
|
_exit(127);
|
|
308
443
|
}
|
|
309
444
|
int inherit_status = 0;
|
|
310
445
|
waitpid(inherit_pid, &inherit_status, 0);
|
|
311
446
|
free(child_argv);
|
|
447
|
+
free_envp(envp);
|
|
312
448
|
return make_result(
|
|
313
449
|
WIFEXITED(inherit_status) ? (double)WEXITSTATUS(inherit_status) : -1.0, "", "");
|
|
314
450
|
}
|
|
@@ -317,6 +453,7 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
317
453
|
int err_pipe[2];
|
|
318
454
|
if (pipe(out_pipe) != 0 || pipe(err_pipe) != 0) {
|
|
319
455
|
free(child_argv);
|
|
456
|
+
free_envp(envp);
|
|
320
457
|
return make_result(-1, "", "could not create pipes");
|
|
321
458
|
}
|
|
322
459
|
|
|
@@ -329,6 +466,10 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
329
466
|
close(err_pipe[0]);
|
|
330
467
|
close(err_pipe[1]);
|
|
331
468
|
if (cwd) chdir(cwd);
|
|
469
|
+
if (envp != NULL) {
|
|
470
|
+
extern char **environ;
|
|
471
|
+
environ = envp;
|
|
472
|
+
}
|
|
332
473
|
execvp(command, child_argv);
|
|
333
474
|
_exit(127);
|
|
334
475
|
}
|
|
@@ -371,6 +512,7 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
371
512
|
free(buffers[0]);
|
|
372
513
|
free(buffers[1]);
|
|
373
514
|
free(child_argv);
|
|
515
|
+
free_envp(envp);
|
|
374
516
|
return result;
|
|
375
517
|
}
|
|
376
518
|
|
package/runtime/rt.h
CHANGED
|
@@ -334,6 +334,10 @@ xt_value xt_promise_static(xt_value name, int32_t argc, xt_value *argv);
|
|
|
334
334
|
xt_value xt_await(xt_value value);
|
|
335
335
|
/** Run queued promise reactions; called once the program body finishes. */
|
|
336
336
|
void xt_drain_microtasks(void);
|
|
337
|
+
/** Mark `value` (a promise) as observed, so its rejection is not reported. */
|
|
338
|
+
void xt_promise_observe_value(xt_value value);
|
|
339
|
+
/** Print rejected promises that no handler ever observed; returns how many. */
|
|
340
|
+
int xt_promise_report_unhandled(void);
|
|
337
341
|
|
|
338
342
|
/* -- host event loop ------------------------------------------------------ */
|
|
339
343
|
/* A small select(2) reactor for host extensions (node's net/dgram/http). The
|
package/runtime/xt_async.c
CHANGED
|
@@ -122,6 +122,10 @@ void xt_async_wake(void *asyncPointer, xt_value value, int rejected) {
|
|
|
122
122
|
* queues a microtask before resuming, so an await always interleaves with
|
|
123
123
|
* pending `.then` callbacks exactly like the specification. */
|
|
124
124
|
xt_value xt_await(xt_value value) {
|
|
125
|
+
/* The awaiting code observes the outcome, so a rejection here is handled by
|
|
126
|
+
* definition — it is rethrown inside the coroutine, and the promise of the
|
|
127
|
+
* enclosing `async` function is what gets reported if nobody catches it. */
|
|
128
|
+
xt_promise_observe_value(value);
|
|
125
129
|
/* An `await` inside an async generator suspends that generator's coroutine
|
|
126
130
|
* while leaving the current `.next()` promise pending. */
|
|
127
131
|
xt_value generatorResult;
|
package/runtime/xt_generator.c
CHANGED
|
@@ -395,6 +395,8 @@ int xt_generator_await(xt_value value, xt_value *result) {
|
|
|
395
395
|
/* A nested async function inside the generator runs on its own coroutine;
|
|
396
396
|
* its awaits must not suspend the generator. */
|
|
397
397
|
if (!xt_coroutine_is_current(gen->coroutine)) return 0;
|
|
398
|
+
/* The generator's body observes the outcome, so this rejection is handled. */
|
|
399
|
+
xt_promise_observe_value(value);
|
|
398
400
|
int state = xt_promise_state_of(value);
|
|
399
401
|
if (state == XT_PROMISE_PENDING) {
|
|
400
402
|
gen->await_threw = 0;
|
package/runtime/xt_loop.c
CHANGED
|
@@ -449,11 +449,15 @@ void xt_run_event_loop(void) {
|
|
|
449
449
|
responsible for calling `xt_loop_poll` while it runs. */
|
|
450
450
|
if (xt_loop_main != NULL) {
|
|
451
451
|
xt_loop_main();
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
452
|
+
} else {
|
|
453
|
+
while (xt_loop_has_work()) {
|
|
454
|
+
if (xt_loop_poll(-1) < 0) break;
|
|
455
|
+
}
|
|
456
456
|
}
|
|
457
|
+
/* A rejection that no handler ever observed is fatal, like Node's default
|
|
458
|
+
* `--unhandled-rejections=throw`. Without this an `async main()` that throws
|
|
459
|
+
* exits 0 with no output at all, which hides the failure completely. */
|
|
460
|
+
if (xt_promise_report_unhandled() > 0) exit(1);
|
|
457
461
|
}
|
|
458
462
|
|
|
459
463
|
int xt_loop_has_work(void) {
|
package/runtime/xt_promise.c
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
#include "rt_internal.h"
|
|
13
13
|
|
|
14
|
+
#include <stdio.h>
|
|
14
15
|
#include <stdlib.h>
|
|
15
16
|
#include <string.h>
|
|
16
17
|
|
|
@@ -33,6 +34,12 @@ struct xt_promise {
|
|
|
33
34
|
xt_reaction **reactions;
|
|
34
35
|
uint32_t reaction_count;
|
|
35
36
|
uint32_t reaction_capacity;
|
|
37
|
+
/* Unhandled-rejection tracking. `observed` is set once something can see
|
|
38
|
+
* this promise's outcome: a `.then`/`.catch` registration, an `await`, or
|
|
39
|
+
* adoption by another promise. `report_index` is its slot in `xt_unhandled`
|
|
40
|
+
* (`XT_PROMISE_UNLISTED` while it is not queued). */
|
|
41
|
+
int observed;
|
|
42
|
+
uint32_t report_index;
|
|
36
43
|
};
|
|
37
44
|
|
|
38
45
|
/* -- microtask queue ------------------------------------------------------ */
|
|
@@ -64,6 +71,58 @@ static xt_value xt_promise_value(xt_promise *promise) {
|
|
|
64
71
|
return XT_FROM_PTR(XT_TAG_OBJECT, promise);
|
|
65
72
|
}
|
|
66
73
|
|
|
74
|
+
/* -- unhandled rejections ------------------------------------------------- */
|
|
75
|
+
/*
|
|
76
|
+
* A promise that rejects without anybody observing the outcome is a bug, and
|
|
77
|
+
* an invisible one here: a compiled program's entry point is usually `async`,
|
|
78
|
+
* so an escaping exception rejects *its* promise and nothing else looks at it.
|
|
79
|
+
* The process used to print nothing and exit 0, which hides the failure.
|
|
80
|
+
*
|
|
81
|
+
* A rejected promise joins this queue and leaves it the moment a handler (or
|
|
82
|
+
* an `await`) is registered — including one attached later, from a timer. What
|
|
83
|
+
* is still queued once the event loop drains is reported on stderr and the
|
|
84
|
+
* process exits non-zero, like Node's default `unhandled-rejections=throw`.
|
|
85
|
+
*/
|
|
86
|
+
#define XT_PROMISE_UNLISTED 0xFFFFFFFFu
|
|
87
|
+
|
|
88
|
+
static xt_promise **xt_unhandled = NULL;
|
|
89
|
+
static uint32_t xt_unhandled_count = 0;
|
|
90
|
+
static uint32_t xt_unhandled_capacity = 0;
|
|
91
|
+
|
|
92
|
+
static void xt_unhandled_remove(xt_promise *promise) {
|
|
93
|
+
uint32_t index = promise->report_index;
|
|
94
|
+
if (index == XT_PROMISE_UNLISTED) return;
|
|
95
|
+
uint32_t last = xt_unhandled_count - 1;
|
|
96
|
+
if (index != last) {
|
|
97
|
+
xt_promise *moved = xt_unhandled[last];
|
|
98
|
+
xt_unhandled[index] = moved;
|
|
99
|
+
moved->report_index = index;
|
|
100
|
+
}
|
|
101
|
+
xt_unhandled_count = last;
|
|
102
|
+
promise->report_index = XT_PROMISE_UNLISTED;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
static void xt_unhandled_add(xt_promise *promise) {
|
|
106
|
+
if (promise->observed || promise->report_index != XT_PROMISE_UNLISTED) return;
|
|
107
|
+
if (xt_unhandled_count >= xt_unhandled_capacity) {
|
|
108
|
+
xt_unhandled_capacity = xt_unhandled_capacity == 0 ? 8 : xt_unhandled_capacity * 2;
|
|
109
|
+
xt_unhandled = (xt_promise **)realloc(xt_unhandled, sizeof(xt_promise *) * xt_unhandled_capacity);
|
|
110
|
+
if (!xt_unhandled) abort();
|
|
111
|
+
}
|
|
112
|
+
promise->report_index = xt_unhandled_count;
|
|
113
|
+
xt_unhandled[xt_unhandled_count++] = promise;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Something is listening now, so this promise can never be "unhandled". */
|
|
117
|
+
static void xt_promise_observe(xt_promise *promise) {
|
|
118
|
+
if (!promise) return;
|
|
119
|
+
promise->observed = 1;
|
|
120
|
+
xt_unhandled_remove(promise);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Mark `value` (when it is a promise) as observed; see `xt_promise_observe`. */
|
|
124
|
+
void xt_promise_observe_value(xt_value value) { xt_promise_observe(xt_as_promise(value)); }
|
|
125
|
+
|
|
67
126
|
static void xt_promise_gc_register(void);
|
|
68
127
|
|
|
69
128
|
static xt_promise *xt_promise_new(void) {
|
|
@@ -73,6 +132,8 @@ static xt_promise *xt_promise_new(void) {
|
|
|
73
132
|
promise->reactions = NULL;
|
|
74
133
|
promise->reaction_count = 0;
|
|
75
134
|
promise->reaction_capacity = 0;
|
|
135
|
+
promise->observed = 0;
|
|
136
|
+
promise->report_index = XT_PROMISE_UNLISTED;
|
|
76
137
|
xt_promise_gc_register();
|
|
77
138
|
return promise;
|
|
78
139
|
}
|
|
@@ -90,6 +151,9 @@ static void xt_promise_gc_scan_roots(void) {
|
|
|
90
151
|
xt_gc_mark_header((xt_header *)reaction->result);
|
|
91
152
|
if (reaction->async) xt_gc_mark_header((xt_header *)reaction->async);
|
|
92
153
|
}
|
|
154
|
+
/* Pending rejections are reported by identity, and the reason must survive
|
|
155
|
+
* long enough to be printed. */
|
|
156
|
+
for (uint32_t i = 0; i < xt_unhandled_count; i++) xt_gc_mark_header((xt_header *)xt_unhandled[i]);
|
|
93
157
|
}
|
|
94
158
|
|
|
95
159
|
static void xt_promise_gc_register(void) {
|
|
@@ -115,6 +179,8 @@ void xt_promise_gc_trace(xt_header *header) {
|
|
|
115
179
|
|
|
116
180
|
void xt_promise_gc_release(xt_header *header) {
|
|
117
181
|
xt_promise *promise = (xt_promise *)header;
|
|
182
|
+
/* A listed rejection is a GC root, so this is only a safety net. */
|
|
183
|
+
xt_unhandled_remove(promise);
|
|
118
184
|
/* Pending reactions are owned solely by the promise; queued ones live in
|
|
119
185
|
* `xt_microtasks` and are freed as they run. */
|
|
120
186
|
for (uint32_t i = 0; i < promise->reaction_count; i++) free(promise->reactions[i]);
|
|
@@ -129,6 +195,9 @@ static void xt_promise_resolve_into(xt_promise *result, xt_value value) {
|
|
|
129
195
|
xt_promise_settle(result, XT_PROMISE_FULFILLED, value);
|
|
130
196
|
return;
|
|
131
197
|
}
|
|
198
|
+
/* `result` follows `other`, so `other`'s rejection is consumed here even if
|
|
199
|
+
* `result` ends up unhandled itself (Node reports only the outermost). */
|
|
200
|
+
xt_promise_observe(other);
|
|
132
201
|
if (other->state == XT_PROMISE_PENDING) {
|
|
133
202
|
xt_reaction *reaction = (xt_reaction *)malloc(sizeof(xt_reaction));
|
|
134
203
|
if (!reaction) abort();
|
|
@@ -152,6 +221,7 @@ static void xt_promise_settle(xt_promise *promise, int state, xt_value value) {
|
|
|
152
221
|
if (promise->state != XT_PROMISE_PENDING) return;
|
|
153
222
|
promise->state = state;
|
|
154
223
|
promise->value = value;
|
|
224
|
+
if (state == XT_PROMISE_REJECTED) xt_unhandled_add(promise);
|
|
155
225
|
for (uint32_t i = 0; i < promise->reaction_count; i++) xt_microtask_push(promise->reactions[i]);
|
|
156
226
|
promise->reaction_count = 0;
|
|
157
227
|
}
|
|
@@ -206,6 +276,8 @@ void xt_promise_queue_async_wake(void *asyncPointer, xt_value settled, int rejec
|
|
|
206
276
|
xt_promise *promise = xt_promise_new();
|
|
207
277
|
promise->state = rejected ? XT_PROMISE_REJECTED : XT_PROMISE_FULFILLED;
|
|
208
278
|
promise->value = settled;
|
|
279
|
+
/* An internal handoff to a coroutine, never a rejection anyone can see. */
|
|
280
|
+
promise->observed = 1;
|
|
209
281
|
xt_reaction *reaction = (xt_reaction *)malloc(sizeof(xt_reaction));
|
|
210
282
|
if (!reaction) abort();
|
|
211
283
|
reaction->promise = promise;
|
|
@@ -239,6 +311,8 @@ xt_value xt_promise_value_of(xt_value value) {
|
|
|
239
311
|
void xt_promise_add_async_waiter(xt_value promiseValue, void *async) {
|
|
240
312
|
xt_promise *promise = xt_as_promise(promiseValue);
|
|
241
313
|
if (!promise || promise->state != XT_PROMISE_PENDING) return;
|
|
314
|
+
/* The coroutine sees the rejection, so this promise is handled. */
|
|
315
|
+
xt_promise_observe(promise);
|
|
242
316
|
xt_reaction *reaction = (xt_reaction *)malloc(sizeof(xt_reaction));
|
|
243
317
|
if (!reaction) abort();
|
|
244
318
|
reaction->promise = promise;
|
|
@@ -346,6 +420,9 @@ xt_value xt_promise_static(xt_value name, int32_t argc, xt_value *argv) {
|
|
|
346
420
|
for (uint32_t i = 0; i < array->length; i++) {
|
|
347
421
|
xt_value item = array->items[i];
|
|
348
422
|
xt_promise *inner = xt_is_promise(item) ? xt_as_promise(item) : NULL;
|
|
423
|
+
/* The combinator reads every element's outcome, including a rejection
|
|
424
|
+
* it forwards to its own result. */
|
|
425
|
+
xt_promise_observe(inner);
|
|
349
426
|
int rejected = inner && inner->state == XT_PROMISE_REJECTED;
|
|
350
427
|
if (strcmp(fn, "allSettled") == 0) {
|
|
351
428
|
xt_value record = xt_object_new();
|
|
@@ -400,6 +477,10 @@ xt_value xt_promise_method(xt_value target, const char *method, int32_t argc, xt
|
|
|
400
477
|
return XT_UNDEFINED;
|
|
401
478
|
}
|
|
402
479
|
if (strcmp(method, "then") == 0) {
|
|
480
|
+
/* `.then` with no rejection handler still consumes this promise's
|
|
481
|
+
* rejection: the derivative carries it on and is reported if nobody
|
|
482
|
+
* handles *that* one, exactly like Node. */
|
|
483
|
+
xt_promise_observe(promise);
|
|
403
484
|
xt_promise *result = xt_promise_new();
|
|
404
485
|
xt_reaction *reaction = (xt_reaction *)malloc(sizeof(xt_reaction));
|
|
405
486
|
if (!reaction) abort();
|
|
@@ -448,3 +529,25 @@ xt_value xt_queue_microtask(int32_t argc, xt_value *argv) {
|
|
|
448
529
|
xt_promise_method(promise, "then", 1, &callback, &handled);
|
|
449
530
|
return XT_UNDEFINED;
|
|
450
531
|
}
|
|
532
|
+
|
|
533
|
+
/* -- reporting ------------------------------------------------------------ */
|
|
534
|
+
|
|
535
|
+
int xt_promise_report_unhandled(void) {
|
|
536
|
+
int reported = 0;
|
|
537
|
+
for (uint32_t i = 0; i < xt_unhandled_count; i++) {
|
|
538
|
+
xt_promise *promise = xt_unhandled[i];
|
|
539
|
+
if (!promise || promise->observed) continue;
|
|
540
|
+
reported++;
|
|
541
|
+
/* `Error` (and its subclasses) carry a captured stack; everything else is
|
|
542
|
+
* stringified the way Node prints a rejection reason. */
|
|
543
|
+
xt_value text = xt_error_stack(promise->value);
|
|
544
|
+
if (text == XT_UNDEFINED) text = xt_to_string(promise->value);
|
|
545
|
+
xt_string *bytes = xt_as_string(text);
|
|
546
|
+
if (bytes) {
|
|
547
|
+
fprintf(stderr, "Unhandled promise rejection\n%s\n", xt_string_bytes(bytes));
|
|
548
|
+
} else {
|
|
549
|
+
fprintf(stderr, "Unhandled promise rejection\n");
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return reported;
|
|
553
|
+
}
|