@sproutboat/toolchain 0.4.16 → 0.4.17
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/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/acquire.test.ts +5 -0
- package/src/patch.ts +28 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @sproutboat/toolchain
|
|
2
2
|
|
|
3
|
+
## 0.4.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2cda218: Fix async handlers failing permanently with `TypeError: Cannot get property of null` after a few requests (baronunread/sproutboat#168). Porffor represents `null` as an object-typed value with pointer 0, so a promise resolving to `null` went through the `then` probe and looked `then` up at memory offset 0. Once `Date.prototype.toISOString` had written there, the probe matched, read `null.then` and rejected, and every later async function returning `null` failed the same way. A patch to `compiler/builtins/promise.ts` keeps `null` out of the probe.
|
|
8
|
+
|
|
3
9
|
## 0.4.16
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/package.json
CHANGED
package/src/acquire.test.ts
CHANGED
|
@@ -119,6 +119,7 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
|
|
|
119
119
|
"compiler/builtins/promise.ts":
|
|
120
120
|
"export const __Porffor_promise_resolve = (value: any, promise: any): void => {\n" +
|
|
121
121
|
" if (Porffor.type(value) == Porffor.TYPES.object) {\n" +
|
|
122
|
+
" // cheap prototype-chain probe for 'then' before the expensive Get below, does not invoke getters\n" +
|
|
122
123
|
" const thenHash: i32 = __Porffor_object_hash('then');\n" +
|
|
123
124
|
" let probe: any = value;\n" +
|
|
124
125
|
" while (Porffor.type(probe) == Porffor.TYPES.object) {\n" +
|
|
@@ -197,6 +198,10 @@ test("the default call (no url/expectedSha256 override) never touches the networ
|
|
|
197
198
|
},
|
|
198
199
|
});
|
|
199
200
|
expect(await readFile(join(dir, "runtime/index.js"), "utf8")).not.toBe("");
|
|
201
|
+
// #168: the real vendored source takes the null guard on the `then` probe.
|
|
202
|
+
expect(await readFile(join(dir, "compiler/builtins/promise.ts"), "utf8")).toContain(
|
|
203
|
+
"if (Porffor.type(value) == Porffor.TYPES.object && value != null) {",
|
|
204
|
+
);
|
|
200
205
|
});
|
|
201
206
|
|
|
202
207
|
test("a corrupted warm cache is replaced from the verified archive", async () => {
|
package/src/patch.ts
CHANGED
|
@@ -192,6 +192,21 @@ const CFLAGS_INJECT =
|
|
|
192
192
|
" ...(process.env.SB_EXTRA_CFLAGS ? process.env.SB_EXTRA_CFLAGS.split(' ').filter(Boolean) : []),\n";
|
|
193
193
|
const CFLAGS_MARKER = "SB_EXTRA_CFLAGS";
|
|
194
194
|
|
|
195
|
+
// baronunread/sproutboat#168: Porffor represents null as an object-typed value
|
|
196
|
+
// with pointer 0, so __Porffor_promise_resolve's cheap `then` probe treated a
|
|
197
|
+
// promise resolving to null as an object and looked `then` up at memory offset
|
|
198
|
+
// 0. Once something (Date.prototype.toISOString's scratch writes, in practice)
|
|
199
|
+
// left bytes there that hash-match, the probe "found" a then, read null.then,
|
|
200
|
+
// and rejected with "Cannot get property of null". Every later async function
|
|
201
|
+
// resolving to null then failed, permanently. Only non-null objects can be
|
|
202
|
+
// thenables (ECMA-262 Promise Resolve Functions step 8), so exclude null.
|
|
203
|
+
const PROMISE_NULL_ANCHOR =
|
|
204
|
+
" if (Porffor.type(value) == Porffor.TYPES.object) {\n // cheap prototype-chain probe for 'then'";
|
|
205
|
+
const PROMISE_NULL_INJECT =
|
|
206
|
+
" // sproutboat #168: null is object-typed with pointer 0, never a thenable.\n" +
|
|
207
|
+
" if (Porffor.type(value) == Porffor.TYPES.object && value != null) {\n // cheap prototype-chain probe for 'then'";
|
|
208
|
+
const PROMISE_NULL_MARKER = "sproutboat #168: null is object-typed";
|
|
209
|
+
|
|
195
210
|
// Porffor's TypedArray.from only handles iterables. An array-like input such
|
|
196
211
|
// as { length: 16 } silently becomes an empty typed array, including HMAC
|
|
197
212
|
// keys made with Uint8Array.from({ length: 16 }, mapFn). The compiler uses a
|
|
@@ -977,6 +992,16 @@ export async function patchDateParser(root: string): Promise<void> {
|
|
|
977
992
|
await writeFile(file, src.slice(0, start) + parser.replace(DATE_PARSER_ANCHOR, DATE_PARSER_INJECT) + src.slice(end));
|
|
978
993
|
}
|
|
979
994
|
|
|
995
|
+
/** #168: keep a promise resolving to null out of the `then` probe. Precompiled by patchTypedArrayFrom. */
|
|
996
|
+
export async function patchPromiseResolveNull(root: string): Promise<void> {
|
|
997
|
+
const file = resolve(root, "compiler/builtins/promise.ts");
|
|
998
|
+
const src = await readFile(file, "utf8");
|
|
999
|
+
if (src.includes(PROMISE_NULL_MARKER)) return;
|
|
1000
|
+
if (!src.includes(PROMISE_NULL_ANCHOR))
|
|
1001
|
+
throw new Error(`could not patch Porffor's promise resolve for null: anchor not found in ${file}`);
|
|
1002
|
+
await writeFile(file, src.replace(PROMISE_NULL_ANCHOR, PROMISE_NULL_INJECT));
|
|
1003
|
+
}
|
|
1004
|
+
|
|
980
1005
|
export async function patchTypedArrayFrom(root: string): Promise<void> {
|
|
981
1006
|
const file = resolve(root, "compiler/builtins/typedarray.js");
|
|
982
1007
|
const src = await readFile(file, "utf8");
|
|
@@ -1052,6 +1077,9 @@ export async function ensurePorfforPatched(root: string): Promise<void> {
|
|
|
1052
1077
|
await patchUwebsockets(root);
|
|
1053
1078
|
await patchRenderJs(root);
|
|
1054
1079
|
await patchDateParser(root);
|
|
1080
|
+
await patchPromiseResolveNull(root);
|
|
1081
|
+
// Last of the builtin edits: its precompile picks up the date and promise
|
|
1082
|
+
// patches above too.
|
|
1055
1083
|
await patchTypedArrayFrom(root);
|
|
1056
1084
|
await patchUnicode(root);
|
|
1057
1085
|
done.add(root);
|