Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion lib/internal/encoding.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const { FastBuffer } = require('internal/buffer');
const {
ERR_ENCODING_NOT_SUPPORTED,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_THIS,
ERR_NO_ICU,
} = require('internal/errors').codes;
Expand Down Expand Up @@ -363,7 +364,11 @@ class TextEncoder {
}

#encodeInto(input, dest) {
encodeInto(input, dest);
const accepted = encodeInto(input, dest) !== false;
if (!accepted) {
const reason = 'must not be backed by an immutable ArrayBuffer';
throw new ERR_INVALID_ARG_VALUE('dest', dest, reason);
}
// We need to read from the binding here since the buffer gets refreshed
// from the snapshot.
const { 0: read, 1: written } = encodeIntoResults;
Expand Down
6 changes: 6 additions & 0 deletions src/encoding_binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ void BindingData::EncodeInto(const FunctionCallbackInfo<Value>& args) {
Local<Uint8Array> dest = args[1].As<Uint8Array>();
Local<ArrayBuffer> buf = dest->Buffer();

// Read-only backing store: return false so the JS caller throws instead.
if (buf->IsImmutable()) {
args.GetReturnValue().Set(false);
return;
}

// Handle detached buffers - return {read: 0, written: 0}
if (buf->Data() == nullptr) {
binding_data->encode_into_results_buffer_[0] = 0;
Expand Down
54 changes: 54 additions & 0 deletions test/parallel/test-whatwg-encoding-custom-textencoder-immutable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Flags: --js-immutable-arraybuffer
'use strict';
const common = require('../common');
const assert = require('assert');

if (typeof ArrayBuffer.prototype.transferToImmutable !== 'function')
common.skip('ArrayBuffer.prototype.transferToImmutable is not available');

const encoder = new TextEncoder();
const original = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];

function immutable() {
const ab = new ArrayBuffer(original.length);
new Uint8Array(ab).set(original);
return ab.transferToImmutable();
}

// Strings longer than 32 code units take the fast path in the binding, so
// both write paths are exercised.
const inputs = [
'',
'hi',
'héllo wörld',
'a'.repeat(64),
'日本語のテキスト'.repeat(8),
'\uD800 lone surrogate',
];

const views = [
(ab) => new Uint8Array(ab),
(ab) => new Uint8Array(ab, 4, 8),
(ab) => new Uint8Array(ab, 0, 0),
(ab) => new Uint8Array(ab, ab.byteLength, 0),
];

for (const input of inputs) {
for (const makeView of views) {
const ab = immutable();
const dest = makeView(ab);
assert.throws(() => encoder.encodeInto(input, dest), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_VALUE',
message: /'dest' must not be backed by an immutable ArrayBuffer/,
});
assert.deepStrictEqual([...new Uint8Array(ab)], original);
}
}

// Mutable buffers are unaffected.
{
const dest = new Uint8Array(4);
assert.deepStrictEqual(encoder.encodeInto('hi', dest), { read: 2, written: 2 });
assert.deepStrictEqual([...dest], [104, 105, 0, 0]);
}