-
Notifications
You must be signed in to change notification settings - Fork 73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(ses): ImmutableArrayBuffer #2309
Conversation
ebc5040
to
dbc18f7
Compare
@phoddie @patrick-soquet I cannot add you as official reviewers. But please consider yourself reviewers anyway. I am eager for your comments. Would this as a proposal, and eventually in the language, help XS in the way I explain above? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am very skeptical of "shimming" something that has very little current agreement in committee. I would much rather we explore approaches that do not require us to modify the ses shim for now, as I stated in #1331 (comment)
* concurrency. Unlike `ArrayBuffer`, sharing an `ImmutableArrayBuffer` | ||
* does not detach anything. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, only "transferred" ArrayBuffer
become detached on the web. If not transferring, the AB is simply copied, which is for an immutable buffer is observably indistinguishable from sharing. Basically an ImmutableArrayBuffer
would simply not be "transferable" on the web, but it could remain "cloneable".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I did not realize that structured clone has a distinct knob for clone vs transfer. I'll clarify that the advantage is sharing immutable data without copying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still view copying or not as an optimization that engines could do regardless. There is no guarantee that they would not copy a cloned immutable array buffer, and if the engine was implementing copy-on-write, it could avoid copying a cloned array buffer already.
@gibson042 @FUDCo @phoddie @patrick-soquet , if you'll be in Helsinki and have time, try shopping this around informally. Once I realized that it's a natural sibling of |
It's not clear to me that immutability is best expressed by a sibling class rather than a detail of any particular ArrayBuffer (like resizeability but in some ways its opposite). One could imagine |
Yes, agreed. We worked to avoid having resizable ArrayBuffers use a separate constructor. Similarly, one of our objections to Records & Tuples is creating new constructors/classes for immutable versions of existing objects. |
As long as So please shop that around as well. Or instead, if you're so inclined. |
Oh yuck, the shim will need to replace all the non-generic builtins on ArrayBuffer.prototype with wrappers that test what flavor of |
Setting aside the shim for a moment, it seems like small extensions to the
|
I agree, and am currently working on a modified shim to try to emulate this as best as I practically can. I just noticed another problem in shimming this though. Node 20, which we still support, has no way within the language to detach an ArrayBuffer. Therefore, on Node 20, |
Not within the language, but it does have extensions that are sufficient, specifically node -e '
console.log("node", process.version);
const buf = new ArrayBuffer(10);
console.log("initial byteLength", buf.byteLength);
structuredClone(new ArrayBuffer(0), { transfer: [buf] });
console.log("post-transfer byteLength", buf.byteLength);
try {
buf.slice();
console.error("detach failed");
} catch (err) {
console.log("detach worked!", err.message);
}
'
node v20.10.0
initial byteLength 10
post-transfer byteLength 0
detach worked! Cannot perform ArrayBuffer.prototype.slice on a detached ArrayBuffer So you could do something like const detachArrayBuffer = ArrayBuffer.prototype.transfer
? buf => void buf.transfer()
: buf => void structuredClone(buf, { transfer: [buf] }); |
…2399) Closes: #XXXX Refs: #1538 #1331 #2309 #2311 ## Description Introduces the `@endo/immutable-arraybuffer` package, the ponyfill exports of `@endo/immutable-arraybuffer`, and the shim obtained by importing `@endo/immutable-arraybuffer/shim.js`. Alternative to #2309 as suggested by @phoddie at #2309 (comment) We plan to fix #1331 in a stack of PRs starting with this one - This PR implements a ponyfill and shim for an upcoming *Immutable ArrayBuffer* proposal, along the lines suggested by @phoddie at #2309 (comment) + the suggestions on an earlier state of #2311 . This is a pure JavaScript ponyfill/shim, leaving it to #2311 to bring it into Hardened JavaScript. - #2311 imports the #2399 shim, treating the new objects it introduces as if they are new primordials, to be permitted and hardened. - #1538 uses the Hardened JavaScript Immutable ArrayBuffers to define a new `Passable` type, `ByteArray`, corresponding to the [OCapN](https://ocapn.org/) `ByteArray`. - Some future PR extending the various marshal formats to encode and decode the `ByteArray` objects. See the README.md in this PR for more. ### Security Considerations Better support for immutability generally helps security. The imperfections of the shim are a leaky abstraction in all the ways explained in the Caveats section of the README.md. For example, objects that are purely artifacts of the emulation, like the `immutableArrayBufferPrototype`, are easily discoverable, revealing the emulation's mechanisms. As a pure JavaScript polyfill/shim, this `@endo/immutable-arraybuffer` package does not harden the objects it exposes. Thus, by itself it does not provide much security -- like the initial state of JavaScript does not by itself provide much security. Rather, both provide securability, depending on Hardened JavaScript to harden early as needed to provide the security. See #2311 Once hardened early, the abstraction will still be leaky as above, but the immutability of the buffer contents are robustly enforced. ### Scaling Considerations This ponyfill/shim is a zero-copy implementation, meaning that it does no more buffer copying than expected of a native implementation. ### Compatibility and Documentation Considerations This ponyfill/shim implements zero-copy by relying on the platform to provide one of two primitives: `structuredClone` or `ArrayBuffer.prototype.transfer`. Neither exist on Node <= 16. Without either, this ponyfill/shim will fail to initialize. This PR sets the stage for writing an Immutable ArrayBuffer proposal, proposing it to tc39, and including it in our own documentation. ### Testing Considerations Ideally, we should identify the subset of test262 `ArrayBuffer` tests that should be applicable to immutable ArrayBuffers, and duplicate them for that purpose. ### Upgrade Considerations Nothing breaking.
Closes: #XXXX
Refs: #1538 #1331 #2311
Description
A step towards fixing #1331 , likely by restaging #1538 on this one and then fixing it.
By making
ImmutableArrayBuffer
as-if part of the language, in #1538passStyleOf
will be able to recognize one even though it carries its own methods, avoiding the eval-twin problems that otherwise thwart such plans. This is one of the candidates explained at #1331 . ThatpassStyleOf
behavior opens the door formarshal
to serialize and unserialize these as additional ocapn Passables.Additionally, by proposing it to tc39 as explained below, we'd enable immutable TypedArrays and DataViews as well, and XS could place all these in ROM cheaply, while conforming to the language spec. When also hardened, XS could judge these to be pure. Attn @phoddie @patrick-soquet
From the initial class comment:
ImmutableArrayBuffer
is intended to be a peer ofArrayBuffer
andSharedArrayBuffer
, but only with the non-mutating methods they have incommon. We're adding this to ses as if it was already part of the
language, and we consider this implementation to be a shim for an
upcoming tc39 proposal.
As a proposal it would take additional steps that would the shim does not:
ImmutableArrayBuffer
be shared betweenthreads (in spec speak, "agent") in exactly the same way
SharedArrayBuffer
is shared between agents. UnlikeSharedArrayBuffer
,sharing an
ImmutableArrayBuffer
does not introduce any observableconcurrency. Unlike
ArrayBuffer
, sharing anImmutableArrayBuffer
does not detach anything.
TypedArray
orDataView
, all the querymethods would work, but the mutating methods would throw. In this sense,
the wrapping
TypedArray
orDataView
would also be immutable.Note that the class isn't immutable until hardened by lockdown.
Even then, the instances are not immutable until hardened.
This class does not harden its instances itself to preserve similarity
with
ArrayBuffer
andSharedArrayBuffer
.Security Considerations
The eval-twin problem explained at #1331 is a security problem. This PR is one candidate for solving that problem, unblocking #1538 so it can fix #1331. Further, if accepted into a future version of the language, the immutability it provides will generally help security.
Scaling Considerations
This shim implementation likely does more copying than even a naive native implementation would. A native implementation may even engage in copy-on-write tricks that this shim cannot. Use of the shim should beware of these "extra" copying costs.
Compatibility and Documentation Considerations
Generally we've kept hardened JS close to standard JS, and we've kept the ses-shim close to hardened JS. With this PR, we'd need to explain
ImmutableArrayBuffer
as part of the hardened JS implemented by the ses-shim, even though it is not yet proposed to tc39.Testing Considerations
Ideally, we should identify the subset of test262
ArrayBuffer
tests that should be applicable toImmutableArrayBuffer
, and duplicate them for that purpose.Upgrade Considerations
Nothing breaking.
NEWS.md updated