Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e807308

Browse files
committedDec 30, 2024·
feat(no-trapping-shim): Ponyfill and shim for noTrapping integrity level
1 parent 215eaf4 commit e807308

File tree

6 files changed

+182
-6
lines changed

6 files changed

+182
-6
lines changed
 

‎packages/no-trapping-shim/package.json

-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
"test:c8": "c8 $C8_OPTIONS ava --config=ava-nesm.config.js",
3636
"test:xs": "exit 0"
3737
},
38-
"dependencies": {},
3938
"devDependencies": {
4039
"@endo/lockdown": "workspace:^",
4140
"@endo/ses-ava": "workspace:^",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
const OriginalProxy = Proxy;
2+
const { freeze } = Object;
3+
const { apply } = Reflect;
4+
5+
const noTrappingSet = new WeakSet();
6+
7+
const proxyHandlerMap = new WeakMap();
8+
9+
/**
10+
* In the shim, this should also be on `Reflect`.
11+
* TODO always return boolean vs sometimes throw
12+
*
13+
* @param {any} specimen
14+
* @returns {boolean}
15+
*/
16+
export const isNoTrapping = specimen => {
17+
if (noTrappingSet.has(specimen)) {
18+
return true;
19+
}
20+
if (!proxyHandlerMap.has(specimen)) {
21+
return false;
22+
}
23+
const [target, handler] = proxyHandlerMap.get(specimen);
24+
if (isNoTrapping(target)) {
25+
noTrappingSet.add(specimen);
26+
return true;
27+
}
28+
const trap = handler.isNoTrapping;
29+
if (trap === undefined) {
30+
return false;
31+
}
32+
const result = apply(trap, handler, [target]);
33+
const ofTarget = isNoTrapping(target);
34+
if (result !== ofTarget) {
35+
throw TypeError(
36+
`'isNoTrapping' proxy trap does not reflect 'isNoTrapping' of proxy target (which is '${ofTarget}')`,
37+
);
38+
}
39+
if (result) {
40+
noTrappingSet.add(specimen);
41+
}
42+
return result;
43+
};
44+
45+
/**
46+
* In the shim, this should also be on `Reflect`.
47+
* TODO always return boolean vs sometimes throw
48+
*
49+
* @param {any} specimen
50+
* @returns {boolean}
51+
*/
52+
export const suppressTrapping = specimen => {
53+
if (noTrappingSet.has(specimen)) {
54+
return true;
55+
}
56+
freeze(specimen);
57+
if (!proxyHandlerMap.has(specimen)) {
58+
noTrappingSet.add(specimen);
59+
return true;
60+
}
61+
const [target, handler] = proxyHandlerMap.get(specimen);
62+
if (isNoTrapping(target)) {
63+
noTrappingSet.add(specimen);
64+
return true;
65+
}
66+
const trap = handler.suppressTrapping;
67+
if (trap === undefined) {
68+
const result = suppressTrapping(target);
69+
if (result) {
70+
noTrappingSet.add(specimen);
71+
}
72+
return result;
73+
}
74+
const result = apply(trap, handler, [target]);
75+
const ofTarget = isNoTrapping(target);
76+
if (result !== ofTarget) {
77+
throw TypeError(
78+
`'suppressTrapping' proxy trap does not reflect 'isNoTrapping' of proxy target (which is '${ofTarget}')`,
79+
);
80+
}
81+
if (result) {
82+
noTrappingSet.add(specimen);
83+
}
84+
return result;
85+
};
86+
87+
const makeMetaHandler = handler =>
88+
freeze({
89+
get(_, trapName, _receiver) {
90+
return freeze((target, ...rest) => {
91+
if (isNoTrapping(target) || handler[trapName] === undefined) {
92+
return Reflect[trapName](target, ...rest);
93+
} else {
94+
return handler[trapName](target, ...rest);
95+
}
96+
});
97+
},
98+
});
99+
100+
const makeSafeHandler = handler =>
101+
new OriginalProxy({}, makeMetaHandler(handler));
102+
103+
/**
104+
* In the shim, `SafeProxy` should replace the global `Proxy`.
105+
*
106+
* @param {any} target
107+
* @param {object} handler
108+
*/
109+
const SafeProxy = function Proxy(target, handler) {
110+
if (new.target !== SafeProxy) {
111+
if (new.target === undefined) {
112+
throw TypeError('Proxy constructor requires "new"');
113+
}
114+
throw TypeError('Safe Proxy shim does not support subclassing');
115+
}
116+
const safeHandler = makeSafeHandler(handler);
117+
const proxy = new OriginalProxy(target, safeHandler);
118+
proxyHandlerMap.set(proxy, [target, handler]);
119+
return proxy;
120+
};
121+
SafeProxy.revocable = (target, handler) => {
122+
const safeHandler = makeSafeHandler(handler);
123+
const { proxy, revoke } = OriginalProxy.revocable(target, safeHandler);
124+
proxyHandlerMap.set(proxy, [target, handler]);
125+
return { proxy, revoke };
126+
};
127+
128+
export { SafeProxy };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/* global globalThis */
2+
import {
3+
isNoTrapping,
4+
suppressTrapping,
5+
SafeProxy,
6+
} from './no-trapping-pony.js';
7+
8+
Reflect.isNoTrapping = isNoTrapping;
9+
Reflect.suppressTrapping = suppressTrapping;
10+
// @ts-expect-error Something about the type of Proxy?
11+
globalThis.Proxy = SafeProxy;

‎packages/no-trapping-shim/test/index.test.js

-5
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import test from '@endo/ses-ava/prepare-endo.js';
2+
import {
3+
isNoTrapping,
4+
SafeProxy,
5+
suppressTrapping,
6+
} from '../src/no-trapping-pony.js';
7+
8+
const { freeze, isFrozen } = Object;
9+
10+
test('no-trapping-pony', async t => {
11+
const specimen = { foo: 8 };
12+
13+
const sillyHandler = freeze({
14+
get(target, prop, receiver) {
15+
return [target, prop, receiver];
16+
},
17+
});
18+
19+
const safeProxy = new SafeProxy(specimen, sillyHandler);
20+
21+
t.false(isNoTrapping(specimen));
22+
t.false(isFrozen(specimen));
23+
t.deepEqual(safeProxy.foo, [specimen, 'foo', safeProxy]);
24+
25+
suppressTrapping(specimen);
26+
27+
t.true(isNoTrapping(specimen));
28+
t.true(isFrozen(specimen));
29+
t.deepEqual(safeProxy.foo, 8);
30+
});

‎yarn.lock

+13
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,19 @@ __metadata:
702702
languageName: unknown
703703
linkType: soft
704704

705+
"@endo/no-trapping-shim@workspace:packages/no-trapping-shim":
706+
version: 0.0.0-use.local
707+
resolution: "@endo/no-trapping-shim@workspace:packages/no-trapping-shim"
708+
dependencies:
709+
"@endo/lockdown": "workspace:^"
710+
"@endo/ses-ava": "workspace:^"
711+
ava: "npm:^6.1.3"
712+
c8: "npm:^7.14.0"
713+
tsd: "npm:^0.31.2"
714+
typescript: "npm:~5.6.3"
715+
languageName: unknown
716+
linkType: soft
717+
705718
"@endo/pass-style@workspace:^, @endo/pass-style@workspace:packages/pass-style":
706719
version: 0.0.0-use.local
707720
resolution: "@endo/pass-style@workspace:packages/pass-style"

0 commit comments

Comments
 (0)
Please sign in to comment.