A `Bytes` value crossing a port carries its whole backing buffer, not the bytes it holds
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 48
- Forks
- 14
- Avg merge
- 4h 14m
- Merged PRs (30d)
- 1
Description
Found against: gren 0.6.6, gren-lang/core 7.4.2, node 22
Reproduction: in https://github.com/gilramir/gren-bug-reports, 2026-09-14-port-bytes-offset; ./run.sh prints the table
below.
Summary
A program decodes a message's body with Bytes.Decode.bytes and sends it to
the host through a port. The host receives the whole message, not the body.
In the other direction, a host that sends a Node Buffer into a port gives the
program 8192 bytes instead of what the Buffer held:
| direction | port | received | expected |
|---|---|---|---|
| program → host | toHost body |
5 bytes [0, 0, 7, 8, 9] |
3 bytes [7, 8, 9] |
| program → host | roundTrip body (task port input) |
5 bytes [0, 0, 7, 8, 9] |
3 bytes [7, 8, 9] |
| host → program | flags: a view of [4, 5] |
10 bytes [0, 1, 2, 3, 4, ...] |
2 bytes [4, 5] |
| host → program | roundTrip result: a view of [4, 5] |
10 bytes [0, 1, 2, 3, 4, ...] |
2 bytes [4, 5] |
| host → program | fromHost: a view of [4, 5] |
10 bytes [0, 1, 2, 3, 4, ...] |
2 bytes [4, 5] |
| host → program | fromHost: Buffer.from([4, 5]) |
8192 bytes [47, 0, 0, 0, 0, ...] |
2 bytes [4, 5] |
body is Bytes.Decode.bytes 3 after Bytes.Decode.bytes 2 over
[0, 0, 7, 8, 9], and Bytes.length body is 3. Inside the program it is
three bytes; it stops being three bytes when it crosses a port.
A Bytes value is a DataView, and a DataView is a window onto an
ArrayBuffer: byteOffset and byteLength are part of the value.
Bytes.Decode.bytes returns such a window (_Bytes_read_bytes), and so does
any host code built on a Node Buffer, since Node allocates small Buffers
out of a shared 8 KiB pool. Platform.js copies a port's bytes five times, and
each copy is of the whole backing buffer:
flags = new DataView(rawFlags.buffer.slice()); // flags
? new DataView(rawValue.buffer.slice()) // outgoing port
value = new DataView(incomingValue.buffer.slice()); // incoming port
? new DataView(input.buffer.slice()) // task port input
checkedValue = new DataView(value.buffer.slice()); // task port result
ArrayBuffer.prototype.slice() with no arguments copies the entire buffer, so
the window is lost and every byte around it comes along.
This is the mistake core#137
reports in Bytes.flatten, in a different file; fixing either leaves the other.
Reproduction
./run.sh builds the program and runs host.js, which sends [4, 5] into the
program three ways, receives the program's 3-byte body two ways, and prints
the table above:
$ node host.js
direction port received expected
program -> host toHost body 5 bytes [0, 0, 7, 8, 9] 3 bytes [7, 8, 9]
program -> host roundTrip body (input) 5 bytes [0, 0, 7, 8, 9] 3 bytes [7, 8, 9]
host -> program flags: a view of [4, 5] 10 bytes [0, 1, 2, 3, 4, ...] 2 bytes [4, 5]
host -> program roundTrip result: a view of [4, 5] 10 bytes [0, 1, 2, 3, 4, ...] 2 bytes [4, 5]
host -> program fromHost: a view of [4, 5] 10 bytes [0, 1, 2, 3, 4, ...] 2 bytes [4, 5]
host -> program fromHost: Buffer.from([4, 5]) 8192 bytes [47, 0, 0, 0, 0, ...] 2 bytes [4, 5]
host.js reads each DataView it receives through its own byteOffset and
byteLength, which is the correct way, so what it prints is what the port
handed over.
Suggested fix
Copy the window, not the buffer, in all five places.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in Platform.js, focusing on the five port-related DataView copies identified in the issue. Run ./run.sh or node host.js before and after the change; done means the reproduction preserves each view's byteOffset and byteLength, so the table reports 3-byte and 2-byte values rather than their backing buffers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100