Skip to content

Commit

Permalink
feat(ses): ArrayBuffer.prototype.transferToImmutable
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Aug 7, 2024
1 parent f531bc8 commit 86d38ba
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 3 deletions.
9 changes: 8 additions & 1 deletion packages/ses/NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
User-visible changes in `ses`:

# Next release

- Adds `ArrayBuffer.p.immutable` and `ArrayBuffer.p.transferToImmutable` as a shim for a future proposal. It makes an ArrayBuffer-like object whose contents cannot be mutated. However, due to limitations of the shim
- Unlike `ArrayBuffer` and `SharedArrayBuffer` this shim's ArrayBuffer-like object cannot be transfered or cloned between JS threads.
- Unlike `ArrayBuffer` and `SharedArrayBuffer`, this shim's ArrayBuffer-like object cannot be used as the backing store of TypeArrays or DataViews.
- The shim depends on the platform providing either `structuredClone` or `Array.prototype.transfer`. Node <= 16 provides neither, causing the shim to fail to initialize, and therefore SES to fail to initialize on such platforms.
- Even after the upcoming `transferToImmutable` proposal is implemented by the platform, the current code will still replace it with the shim implementation, in accord with shim best practices. See https://github.com/endojs/endo/pull/2311#discussion_r1632607527 . It will require a later manual step to delete the shim, after manual analysis of the compat implications.

# v1.6.0 (2024-07-30)

- *NOTICE*: This version introduces multiple features to converge upon a
Expand Down Expand Up @@ -60,7 +68,6 @@ User-visible changes in `ses`:
in TypeScript),
the stacktrace line-numbers point back into the original
source, as they do on Node without SES.

# v1.5.0 (2024-05-06)

- Adds `importNowHook` to the `Compartment` options. The compartment will invoke the hook whenever it encounters a missing dependency while running `compartmentInstance.importNow(specifier)`, which cannot use an asynchronous `importHook`.
Expand Down
3 changes: 2 additions & 1 deletion packages/ses/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@
"test:platform-compatibility": "node test/package/test.cjs"
},
"dependencies": {
"@endo/env-options": "^1.1.5"
"@endo/env-options": "^1.1.5",
"@endo/immutable-arraybuffer": "^0.1.0"
},
"devDependencies": {
"@endo/compartment-mapper": "^1.2.1",
Expand Down
1 change: 1 addition & 0 deletions packages/ses/src/commons.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export { universalThis as globalThis };

export const {
Array,
ArrayBuffer,
Date,
FinalizationRegistry,
Float32Array,
Expand Down
11 changes: 11 additions & 0 deletions packages/ses/src/get-anonymous-intrinsics.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
matchAllSymbol,
regexpPrototype,
globalThis,
ArrayBuffer,
} from './commons.js';
import { InertCompartment } from './compartment.js';

Expand Down Expand Up @@ -157,5 +158,15 @@ export const getAnonymousIntrinsics = () => {
);
}

const ab = new ArrayBuffer(0);
// @ts-expect-error TODO How do I add transferToImmutable to ArrayBuffer type?
// eslint-disable-next-line @endo/no-polymorphic-call
const iab = ab.transferToImmutable();
const iabProto = getPrototypeOf(iab);
if (iabProto !== ArrayBuffer.prototype) {
// In a native implementation, these will be the same prototype
intrinsics['%ImmutableArrayBufferPrototype%'] = iabProto;
}

return intrinsics;
};
1 change: 1 addition & 0 deletions packages/ses/src/lockdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// @ts-check

import { getEnvironmentOption as getenv } from '@endo/env-options';
import '@endo/immutable-arraybuffer/shim.js';
import {
FERAL_FUNCTION,
FERAL_EVAL,
Expand Down
25 changes: 25 additions & 0 deletions packages/ses/src/permits.js
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,31 @@ export const permitted = {
// https://github.com/tc39/proposal-arraybuffer-transfer
transferToFixedLength: fn,
detached: getter,
// https://github.com/endojs/endo/pull/2309#issuecomment-2155513240
// to be proposed
transferToImmutable: fn,
immutable: getter,
},

// If this exists, it is purely an artifact of how we currently shim
// `transferToImmutable`. As natively implemented, there would be no
// such extra prototype.
'%ImmutableArrayBufferPrototype%': {
'[[Proto]]': '%ArrayBufferPrototype%',
byteLength: getter,
slice: fn,
// See https://github.com/tc39/proposal-resizablearraybuffer
transfer: fn,
resize: fn,
resizable: getter,
maxByteLength: getter,
// https://github.com/tc39/proposal-arraybuffer-transfer
transferToFixedLength: fn,
detached: getter,
// https://github.com/endojs/endo/pull/2309#issuecomment-2155513240
// to be proposed
transferToImmutable: fn,
immutable: getter,
},

// SharedArrayBuffer Objects
Expand Down
91 changes: 91 additions & 0 deletions packages/ses/test/immutable-array-buffer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import test from 'ava';
import '../index.js';

const { isFrozen, getPrototypeOf } = Object;

lockdown();

test('ses Immutable ArrayBuffer shim installed and hardened', t => {
const ab1 = new ArrayBuffer(0);
const iab = ab1.transferToImmutable();
const iabProto = getPrototypeOf(iab);
t.true(isFrozen(iabProto));
t.true(isFrozen(iabProto.slice));
});

test('ses Immutable ArrayBuffer shim ops', t => {
// Absent on Node <= 18
const canResize = 'maxByteLength' in ArrayBuffer.prototype;

const ab1 = new ArrayBuffer(2, { maxByteLength: 7 });
const ta1 = new Uint8Array(ab1);
ta1[0] = 3;
ta1[1] = 4;
const iab = ab1.transferToImmutable();
t.true(iab instanceof ArrayBuffer);
ta1[1] = 5;
const ab2 = iab.slice(0);
const ta2 = new Uint8Array(ab2);
t.is(ta1[1], undefined);
t.is(ta2[1], 4);
ta2[1] = 6;

const ab3 = iab.slice(0);
t.true(ab3 instanceof ArrayBuffer);

const ta3 = new Uint8Array(ab3);
t.is(ta1[1], undefined);
t.is(ta2[1], 6);
t.is(ta3[1], 4);

t.is(ab1.byteLength, 0);
t.is(iab.byteLength, 2);
t.is(ab2.byteLength, 2);

t.is(iab.maxByteLength, 2);
if (canResize) {
t.is(ab1.maxByteLength, 0);
t.is(ab2.maxByteLength, 2);
}

if ('detached' in ab1) {
t.true(ab1.detached);
t.false(ab2.detached);
t.false(ab3.detached);
}
t.false(iab.detached);
t.false(iab.resizable);
});

// This could have been written as a test.failing as compared to
// the immutable ArrayBuffer we'll propose. However, I'd rather test what
// the shim purposely does instead.
test('ses Immutable ArrayBuffer shim limitations', t => {
const ab1 = new ArrayBuffer(2);
const dv1 = new DataView(ab1);
t.is(dv1.buffer, ab1);
t.is(dv1.byteLength, 2);
const ta1 = new Uint8Array(ab1);
ta1[0] = 3;
ta1[1] = 4;
t.is(ta1.byteLength, 2);

t.throws(() => new DataView({}), { instanceOf: TypeError });
// Unfortutanely, calling a TypeArray constructor with an object that
// is not a TypeArray, ArrayBuffer, or Iterable just creates a useless
// empty TypedArray, rather than throwing.
const ta2 = new Uint8Array({});
t.is(ta2.byteLength, 0);

const iab = ab1.transferToImmutable();
t.throws(() => new DataView(iab), {
instanceOf: TypeError,
});
// Unfortunately, unlike the immutable ArrayBuffer to be proposed,
// calling a TypedArray constructor with the shim implementation of
// an immutable ArrayBuffer as argument treats it as an unrecognized object,
// rather than throwing an error.
t.is(iab.byteLength, 2);
const ta3 = new Uint8Array(iab);
t.is(ta3.byteLength, 0);
});
3 changes: 2 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ __metadata:
languageName: unknown
linkType: soft

"@endo/immutable-arraybuffer@workspace:packages/immutable-arraybuffer":
"@endo/immutable-arraybuffer@npm:^0.1.0, @endo/immutable-arraybuffer@workspace:packages/immutable-arraybuffer":
version: 0.0.0-use.local
resolution: "@endo/immutable-arraybuffer@workspace:packages/immutable-arraybuffer"
dependencies:
Expand Down Expand Up @@ -10554,6 +10554,7 @@ __metadata:
dependencies:
"@endo/compartment-mapper": "npm:^1.2.1"
"@endo/env-options": "npm:^1.1.5"
"@endo/immutable-arraybuffer": "npm:^0.1.0"
"@endo/module-source": "npm:^1.0.1"
"@endo/test262-runner": "npm:^0.1.39"
ava: "npm:^6.1.3"
Expand Down

0 comments on commit 86d38ba

Please sign in to comment.