fix(platform): snapshot session fault errors safely
This commit is contained in:
@@ -226,16 +226,65 @@ test('GameSessionHost preserves attach then dispose failures by identity and occ
|
||||
assert.equal(sessions.state, 'idle');
|
||||
});
|
||||
|
||||
test('open fault guard recognizes the global brand across realms without reading accessors', () => {
|
||||
test('GameSessionOpenFault locks its brand and frozen error tuple against later mutation', () => {
|
||||
const primary = new Error('primary');
|
||||
const cleanup = new Error('cleanup');
|
||||
const replacement = new Error('replacement');
|
||||
const fault = new GameSessionOpenFault(primary, cleanup);
|
||||
const brand = Symbol.for(OPEN_FAULT_BRAND_KEY);
|
||||
const originalErrors = fault.errors;
|
||||
|
||||
assert.deepEqual(Object.getOwnPropertyDescriptor(fault, brand), {
|
||||
value: true,
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
});
|
||||
assert.deepEqual(Object.getOwnPropertyDescriptor(fault, 'errors'), {
|
||||
value: originalErrors,
|
||||
writable: false,
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
});
|
||||
assert.equal(Object.isFrozen(originalErrors), true);
|
||||
|
||||
assert.throws(() => {
|
||||
(fault as unknown as { errors: unknown[] }).errors = [replacement, cleanup];
|
||||
}, TypeError);
|
||||
assert.throws(() => {
|
||||
(originalErrors as unknown as unknown[])[0] = replacement;
|
||||
}, TypeError);
|
||||
assert.throws(() => {
|
||||
Object.defineProperty(originalErrors, Symbol.iterator, {
|
||||
value: function* hostileIterator() { yield replacement; },
|
||||
});
|
||||
}, TypeError);
|
||||
assert.throws(() => {
|
||||
(fault as unknown as { [key: symbol]: unknown })[brand] = false;
|
||||
}, TypeError);
|
||||
|
||||
assert.equal(fault.errors, originalErrors);
|
||||
assert.equal(fault.errors[0], primary);
|
||||
assert.equal(fault.errors[1], cleanup);
|
||||
assert.equal((fault as unknown as { [key: symbol]: unknown })[brand], true);
|
||||
});
|
||||
|
||||
test('open fault extractor snapshots branded faults across realms without reading hostile hooks', () => {
|
||||
type OpenFaultShape = { readonly errors: readonly unknown[] };
|
||||
type OpenFaultGuard = (value: unknown) => value is OpenFaultShape;
|
||||
const exported = (gameSessionRuntime as unknown as Record<string, unknown>)
|
||||
.isGameSessionOpenFault;
|
||||
assert.equal(typeof exported, 'function');
|
||||
const isGameSessionOpenFault = exported as OpenFaultGuard;
|
||||
type OpenFaultExtractor = (value: unknown) => readonly [unknown, unknown] | null;
|
||||
const runtimeExports = gameSessionRuntime as unknown as Record<string, unknown>;
|
||||
const guardExport = runtimeExports.isGameSessionOpenFault;
|
||||
const extractorExport = runtimeExports.extractGameSessionOpenFaultErrors;
|
||||
assert.equal(typeof guardExport, 'function');
|
||||
assert.equal(typeof extractorExport, 'function');
|
||||
const isGameSessionOpenFault = guardExport as OpenFaultGuard;
|
||||
const extractGameSessionOpenFaultErrors = extractorExport as OpenFaultExtractor;
|
||||
const primary = new Error('primary');
|
||||
const cleanup = new Error('cleanup');
|
||||
const sameRealm = new GameSessionOpenFault(primary, cleanup);
|
||||
class OpenFaultSubclass extends GameSessionOpenFault {}
|
||||
const subclass = new OpenFaultSubclass(primary, cleanup);
|
||||
const foreignRealm = runInNewContext(`(() => {
|
||||
const fault = new Error('foreign open fault');
|
||||
Object.defineProperties(fault, {
|
||||
@@ -244,6 +293,11 @@ test('open fault guard recognizes the global brand across realms without reading
|
||||
});
|
||||
return fault;
|
||||
})()`, { primary, cleanup });
|
||||
const duplicateModuleLike = new Error('duplicate module open fault');
|
||||
Object.defineProperties(duplicateModuleLike, {
|
||||
[Symbol.for(OPEN_FAULT_BRAND_KEY)]: { value: true },
|
||||
errors: { value: [primary, cleanup] },
|
||||
});
|
||||
const ordinaryAggregate = runInNewContext(
|
||||
'new AggregateError([primary, cleanup], "user aggregate")',
|
||||
{ primary, cleanup },
|
||||
@@ -270,14 +324,64 @@ test('open fault guard recognizes the global brand across realms without reading
|
||||
},
|
||||
errors: { value: [primary, cleanup] },
|
||||
});
|
||||
const iteratorReplacement = new Error('iterator replacement');
|
||||
let iteratorReads = 0;
|
||||
const hostileErrors = [primary, cleanup];
|
||||
Object.defineProperty(hostileErrors, Symbol.iterator, {
|
||||
configurable: true,
|
||||
get() {
|
||||
iteratorReads += 1;
|
||||
hostileErrors[0] = iteratorReplacement;
|
||||
throw new Error('source iterator must not run');
|
||||
},
|
||||
});
|
||||
const hostileIteratorFault = new Error('hostile iterator fault');
|
||||
Object.defineProperties(hostileIteratorFault, {
|
||||
[Symbol.for(OPEN_FAULT_BRAND_KEY)]: { value: true },
|
||||
errors: { value: hostileErrors },
|
||||
});
|
||||
const descriptorTarget = new Error('descriptor failure');
|
||||
Object.defineProperties(descriptorTarget, {
|
||||
[Symbol.for(OPEN_FAULT_BRAND_KEY)]: { value: true },
|
||||
errors: { value: [primary, cleanup] },
|
||||
});
|
||||
let descriptorReads = 0;
|
||||
const descriptorFailure = new Proxy(descriptorTarget, {
|
||||
getOwnPropertyDescriptor(target, key) {
|
||||
descriptorReads += 1;
|
||||
if (key === 'errors') throw new Error('descriptor trap failed');
|
||||
return Reflect.getOwnPropertyDescriptor(target, key);
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(foreignRealm instanceof GameSessionOpenFault, false);
|
||||
assert.equal(isGameSessionOpenFault(sameRealm), true);
|
||||
assert.equal(isGameSessionOpenFault(foreignRealm), true);
|
||||
assert.equal(isGameSessionOpenFault(ordinaryAggregate), false);
|
||||
assert.equal(isGameSessionOpenFault(unbranded), false);
|
||||
assert.equal(isGameSessionOpenFault(accessorSpoof), false);
|
||||
assert.equal(isGameSessionOpenFault(brandAccessorSpoof), false);
|
||||
for (const candidate of [sameRealm, subclass, foreignRealm, duplicateModuleLike]) {
|
||||
const snapshot = extractGameSessionOpenFaultErrors(candidate);
|
||||
assert.notEqual(snapshot, null);
|
||||
assert.equal(Object.isFrozen(snapshot), true);
|
||||
assert.equal(snapshot?.[0], primary);
|
||||
assert.equal(snapshot?.[1], cleanup);
|
||||
assert.equal(isGameSessionOpenFault(candidate), true);
|
||||
}
|
||||
const hostileSnapshot = extractGameSessionOpenFaultErrors(hostileIteratorFault);
|
||||
assert.notEqual(hostileSnapshot, null);
|
||||
assert.equal(hostileSnapshot?.[0], primary);
|
||||
assert.equal(hostileSnapshot?.[1], cleanup);
|
||||
assert.equal(Object.isFrozen(hostileSnapshot), true);
|
||||
assert.equal(iteratorReads, 0);
|
||||
assert.equal(hostileErrors[0], primary);
|
||||
|
||||
for (const candidate of [
|
||||
ordinaryAggregate,
|
||||
unbranded,
|
||||
accessorSpoof,
|
||||
brandAccessorSpoof,
|
||||
]) {
|
||||
assert.equal(extractGameSessionOpenFaultErrors(candidate), null);
|
||||
assert.equal(isGameSessionOpenFault(candidate), false);
|
||||
}
|
||||
assert.equal(extractGameSessionOpenFaultErrors(descriptorFailure), null);
|
||||
assert.equal(descriptorReads, 2);
|
||||
assert.equal(accessorReads, 0);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user