mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-02 07:43:42 +08:00
Set a trusted build aside while a getter runs outside a visit session
A getter or proxy trap that runs during a trusted build outside any visit session now runs with the build's state set aside, as inside a session. A conversion the getter starts, such as a recursive union that opens and ends its own session, no longer clears the mark that keeps the rest of the outer build from trusting its earlier match.
This commit is contained in:
@@ -10932,9 +10932,9 @@ static void scr_dyn_visit_exit(void) {
|
||||
}
|
||||
|
||||
/* Getters and proxy traps run user code, which may start conversions of
|
||||
* its own and catch their failures. It runs with the session set aside,
|
||||
* so its conversions use their own tables and leave the session's
|
||||
* entries alone. */
|
||||
* its own and catch their failures. It runs with the session and any
|
||||
* trusted build set aside, so its conversions use their own tables and
|
||||
* leave the session's entries and the trusted build's state alone. */
|
||||
typedef struct {
|
||||
size_t depth;
|
||||
ScrDynVisitSet matching, built, viewed;
|
||||
@@ -10977,12 +10977,10 @@ static void scr_dyn_visit_resume(const ScrDynVisitState *saved) {
|
||||
scr_dyn_session_ran_code = true;
|
||||
}
|
||||
|
||||
/* Calls a getter for a member read, outside any visit session. */
|
||||
/* Calls a getter for a member read, outside any visit session or trusted
|
||||
* build. */
|
||||
static ScrDyn *scr_dyn_visit_call_getter(ScrDyn *getter) {
|
||||
if (scr_dyn_visit_depth == 0) {
|
||||
if (scr_dyn_trusted) scr_dyn_session_ran_code = true;
|
||||
return scr_dyn_call(getter, NULL, 0, "getter");
|
||||
}
|
||||
if (scr_dyn_visit_depth == 0 && !scr_dyn_trusted) return scr_dyn_call(getter, NULL, 0, "getter");
|
||||
ScrDynVisitState saved;
|
||||
scr_dyn_visit_suspend(&saved);
|
||||
ScrDyn *result = scr_dyn_call(getter, NULL, 0, "getter");
|
||||
@@ -10990,12 +10988,10 @@ static ScrDyn *scr_dyn_visit_call_getter(ScrDyn *getter) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/* A proxy's get trap for a member read, outside any visit session. */
|
||||
/* A proxy's get trap for a member read, outside any visit session or
|
||||
* trusted build. */
|
||||
static ScrDyn *scr_dyn_visit_proxy_get(const ScrDyn *d, ScrStr *name) {
|
||||
if (scr_dyn_visit_depth == 0) {
|
||||
if (scr_dyn_trusted) scr_dyn_session_ran_code = true;
|
||||
return scr_dyn_proxy_get(d, name);
|
||||
}
|
||||
if (scr_dyn_visit_depth == 0 && !scr_dyn_trusted) return scr_dyn_proxy_get(d, name);
|
||||
ScrDynVisitState saved;
|
||||
scr_dyn_visit_suspend(&saved);
|
||||
ScrDyn *result = scr_dyn_proxy_get(d, name);
|
||||
|
||||
@@ -686,6 +686,46 @@ console.log(JSON.stringify(plain));
|
||||
]);
|
||||
});
|
||||
|
||||
test("nested unions: a getter that converts a recursive union keeps a non-recursive build untrusted", async () => {
|
||||
// The getter converts a recursive union, which opens and ends a visit
|
||||
// session of its own, before it answers. The outer build still matches
|
||||
// the getter's second answer in full, and the failure names that union.
|
||||
const r = await compileAndRun(
|
||||
"flat-discriminated-getter-session",
|
||||
`type Tree = { kind: "leaf"; n: number } | { kind: "node"; left: Tree; right: Tree };
|
||||
type Inner = { tag: "x"; n: number } | { tag: "y"; s: string };
|
||||
type Outer = { kind: "a"; inner: Inner } | { kind: "b"; other: number };
|
||||
const v = JSON.parse('{"kind":"a"}') as unknown;
|
||||
let calls = 0;
|
||||
let leaves = 0;
|
||||
Object.defineProperty(v, "inner", {
|
||||
enumerable: true,
|
||||
get() {
|
||||
calls++;
|
||||
const t = JSON.parse('{"kind":"node","left":{"kind":"leaf","n":1},"right":{"kind":"leaf","n":2}}') as Tree;
|
||||
if (t.kind === "node") leaves += 2;
|
||||
return calls === 1 ? JSON.parse('{"tag":"x","n":1}') : JSON.parse('{"tag":"x","n":"bad"}');
|
||||
},
|
||||
});
|
||||
try {
|
||||
const o = v as Outer;
|
||||
console.log("ok", JSON.stringify(o));
|
||||
} catch (err) {
|
||||
console.log((err as Error).message);
|
||||
}
|
||||
console.log("getter calls", calls, "leaves", leaves);
|
||||
const plain = JSON.parse('{"kind":"a","inner":{"tag":"y","s":"z"}}') as Outer;
|
||||
console.log(JSON.stringify(plain));
|
||||
`,
|
||||
);
|
||||
expect(r.exitCode).toBe(0);
|
||||
expect(r.stdout.trimEnd().split("\n")).toEqual([
|
||||
"expected object | object at $.inner, got object",
|
||||
"getter calls 2 leaves 4",
|
||||
'{"kind":"a","inner":{"tag":"y","s":"z"}}',
|
||||
]);
|
||||
});
|
||||
|
||||
test("array where a record is expected names the mismatch at the root", async () => {
|
||||
const r = await compileAndRun(
|
||||
"root-kind",
|
||||
|
||||
Reference in New Issue
Block a user