-
Notifications
You must be signed in to change notification settings - Fork 77
/
Copy pathindex.js
204 lines (179 loc) · 6.68 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/* global globalThis */
const { setPrototypeOf, getOwnPropertyDescriptors } = Object;
const { apply } = Reflect;
const { prototype: arrayBufferPrototype } = ArrayBuffer;
const {
slice,
// TODO used to be a-ts-expect-error, but my local IDE's TS server
// seems to use a more recent definition of the `ArrayBuffer` type.
// @ts-ignore At the time of this writing, the `ArrayBuffer` type built
// into TypeScript does not know about the recent standard `transfer` method.
// Indeed, the `transfer` method is absent from Node <= 20.
transfer,
} = arrayBufferPrototype;
/**
* Enforces that `arrayBuffer` is a genuine `ArrayBuffer` exotic object.
*
* @param {ArrayBuffer} arrayBuffer
* @param {number} [start]
* @param {number} [end]
* @returns {ArrayBuffer}
*/
const arrayBufferSlice = (arrayBuffer, start = undefined, end = undefined) =>
apply(slice, arrayBuffer, [start, end]);
/**
* Enforces that `arrayBuffer` is a genuine `ArrayBuffer` exotic object.
* Return a new fresh `ArrayBuffer` exotic object, where the contents of the
* original `arrayBuffer` has been moved into the new one, and the original
* `arrayBuffer` has been detached.
*
* @param {ArrayBuffer} arrayBuffer
* @returns {ArrayBuffer}
*/
let arrayBufferTransfer;
if (transfer) {
arrayBufferTransfer = arrayBuffer => apply(transfer, arrayBuffer, []);
} else if (globalThis.structuredClone) {
arrayBufferTransfer = arrayBuffer => {
// Hopefully, a zero-length slice is cheap, but still enforces that
// `arrayBuffer` is a genuine `ArrayBuffer` exotic object.
arrayBufferSlice(arrayBuffer, 0, 0);
return globalThis.structuredClone(arrayBuffer, { transfer: [arrayBuffer] });
};
} else {
// Indeed, Node <= 16 has neither.
throw TypeError(
`Can only emulate immutable ArrayBuffer on a platform with either "structuredClone" or "ArrayBuffer.prototype.transfer"`,
);
}
/**
* This class only exists as an artifact of this ponyfill and shim,
* as a convience for imperfectly emulating the
* *Immutable ArrayBuffer* proposal, which would not have this class.
* In the proposal,
* `transferToImmutable` makes a new `ArrayBuffer` that inherits directly from
* `ArrayBuffer.prototype` as you'd expect. In the ponyfill and shim,
* `transferToImmutable` makes a normal object that inherits directly from
* `immutableArrayBufferPrototype`, which has been surgically
* altered to inherit directly from `ArrayBuffer.prototype`. The constructor is
* captured for use internal to this module, and is made otherwise inaccessible.
* Therefore, `immutableArrayBufferPrototype` and all its methods
* and accessor functions effectively become hidden intrinsics.
* They are not encapsulated. Rather, they are trivially discoverable if you
* know how, but are not discoverable merely by enumerating naming paths.
*/
class ImmutableArrayBufferInternal {
/** @type {ArrayBuffer} */
#buffer;
constructor(buffer) {
// This constructor is deleted from the prototype below.
this.#buffer = arrayBufferTransfer(buffer);
}
get byteLength() {
return this.#buffer.byteLength;
}
get detached() {
this.#buffer; // shim brand check
return false;
}
get maxByteLength() {
// Not underlying maxByteLength, which is irrelevant
return this.#buffer.byteLength;
}
get resizable() {
this.#buffer; // shim brand check
return false;
}
get immutable() {
this.#buffer; // shim brand check
return true;
}
slice(start = undefined, end = undefined) {
return arrayBufferSlice(this.#buffer, start, end);
}
sliceToImmutable(start = undefined, end = undefined) {
// eslint-disable-next-line no-use-before-define
return sliceBufferToImmutable(this.#buffer, start, end);
}
resize(_newByteLength = undefined) {
this.#buffer; // shim brand check
throw TypeError('Cannot resize an immutable ArrayBuffer');
}
transfer(_newLength = undefined) {
this.#buffer; // shim brand check
throw TypeError('Cannot detach an immutable ArrayBuffer');
}
transferToFixedLength(_newLength = undefined) {
this.#buffer; // shim brand check
throw TypeError('Cannot detach an immutable ArrayBuffer');
}
transferToImmutable(_newLength = undefined) {
this.#buffer; // shim brand check
throw TypeError('Cannot detach an immutable ArrayBuffer');
}
}
const immutableArrayBufferPrototype = ImmutableArrayBufferInternal.prototype;
// @ts-expect-error can only delete optionals
delete immutableArrayBufferPrototype.constructor;
const {
slice: { value: sliceOfImmutable },
immutable: { get: isImmutableGetter },
} = getOwnPropertyDescriptors(immutableArrayBufferPrototype);
setPrototypeOf(immutableArrayBufferPrototype, arrayBufferPrototype);
export const transferBufferToImmutable = (buffer, newLength = undefined) => {
if (newLength !== undefined) {
if (transfer) {
buffer = apply(transfer, buffer, [newLength]);
} else {
buffer = arrayBufferTransfer(buffer);
const oldLength = buffer.byteLength;
// eslint-disable-next-line @endo/restrict-comparison-operands
if (newLength <= oldLength) {
buffer = arrayBufferSlice(buffer, 0, newLength);
} else {
const oldTA = new Uint8Array(buffer);
const newTA = new Uint8Array(newLength);
newTA.set(oldTA);
buffer = newTA.buffer;
}
}
}
return new ImmutableArrayBufferInternal(buffer);
};
export const isBufferImmutable = buffer => {
try {
// TODO The following directive line should either be removed or
// turned back into an at-ts-expect-error. We made it into an
// at-ts-ignore because we were getting inconsistent reports.
// See https://github.com/endojs/endo/pull/2673#issuecomment-2566711810
// @ts-ignore Getter should be typed as this-sensitive
return apply(isImmutableGetter, buffer, []);
} catch (err) {
if (err instanceof TypeError) {
// Enforce that `buffer` is a genuine ArrayBuffer before returning.
arrayBufferSlice(buffer, 0, 0);
return false;
}
throw err;
}
};
const sliceBuffer = (buffer, start = undefined, end = undefined) => {
try {
// TODO The following directive line should either be removed or
// turned back into an at-ts-expect-error. We made it into an
// at-ts-ignore because we were getting inconsistent reports.
// See https://github.com/endojs/endo/pull/2673#issuecomment-2566711810
// @ts-ignore We know it is really there
return apply(sliceOfImmutable, buffer, [start, end]);
} catch (err) {
if (err instanceof TypeError) {
return arrayBufferSlice(buffer, start, end);
}
throw err;
}
};
export const sliceBufferToImmutable = (
buffer,
start = undefined,
end = undefined,
) => transferBufferToImmutable(sliceBuffer(buffer, start, end));