1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

fix(node/fs): node:fs.read and write should accept typed arrays other than Uint8Array (#25030)

Part of #25028.

Our underlying read/write operations in `io` assume the buffer is a
Uint8Array, but we were passing in other typed arrays (in the case above
it was `Int8Array`).
This commit is contained in:
Nathan Whitaker 2024-08-14 09:42:31 -07:00 committed by GitHub
parent c765d9ad2f
commit 1f2d48cd97
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 106 additions and 19 deletions

View file

@ -38,7 +38,7 @@ export type BinaryOptionsArgument =
export type FileOptionsArgument = Encodings | FileOptions; export type FileOptionsArgument = Encodings | FileOptions;
export type ReadOptions = { export type ReadOptions = {
buffer: Buffer | Uint8Array; buffer: Buffer | ArrayBufferView;
offset: number; offset: number;
length: number; length: number;
position: number | null; position: number | null;

View file

@ -9,6 +9,7 @@ import * as io from "ext:deno_io/12_io.js";
import * as fs from "ext:deno_fs/30_fs.js"; import * as fs from "ext:deno_fs/30_fs.js";
import { ReadOptions } from "ext:deno_node/_fs/_fs_common.ts"; import { ReadOptions } from "ext:deno_node/_fs/_fs_common.ts";
import { import {
arrayBufferViewToUint8Array,
validateOffsetLengthRead, validateOffsetLengthRead,
validatePosition, validatePosition,
} from "ext:deno_node/internal/fs/utils.mjs"; } from "ext:deno_node/internal/fs/utils.mjs";
@ -16,6 +17,7 @@ import {
validateBuffer, validateBuffer,
validateInteger, validateInteger,
} from "ext:deno_node/internal/validators.mjs"; } from "ext:deno_node/internal/validators.mjs";
import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts";
type readSyncOptions = { type readSyncOptions = {
offset: number; offset: number;
@ -38,7 +40,7 @@ export function read(
): void; ): void;
export function read( export function read(
fd: number, fd: number,
buffer: Buffer | Uint8Array, buffer: ArrayBufferView,
offset: number, offset: number,
length: number, length: number,
position: number | null, position: number | null,
@ -46,7 +48,7 @@ export function read(
): void; ): void;
export function read( export function read(
fd: number, fd: number,
optOrBufferOrCb?: Buffer | Uint8Array | ReadOptions | Callback, optOrBufferOrCb?: ArrayBufferView | ReadOptions | Callback,
offsetOrCallback?: number | Callback, offsetOrCallback?: number | Callback,
length?: number, length?: number,
position?: number | null, position?: number | null,
@ -75,9 +77,9 @@ export function read(
} }
if ( if (
optOrBufferOrCb instanceof Buffer || optOrBufferOrCb instanceof Uint8Array isArrayBufferView(optOrBufferOrCb)
) { ) {
buffer = optOrBufferOrCb; buffer = arrayBufferViewToUint8Array(optOrBufferOrCb);
} else if (typeof optOrBufferOrCb === "function") { } else if (typeof optOrBufferOrCb === "function") {
offset = 0; offset = 0;
buffer = Buffer.alloc(16384); buffer = Buffer.alloc(16384);
@ -86,7 +88,7 @@ export function read(
} else { } else {
const opt = optOrBufferOrCb as ReadOptions; const opt = optOrBufferOrCb as ReadOptions;
if ( if (
!(opt.buffer instanceof Buffer) && !(opt.buffer instanceof Uint8Array) !isArrayBufferView(opt.buffer)
) { ) {
throw new ERR_INVALID_ARG_TYPE("buffer", [ throw new ERR_INVALID_ARG_TYPE("buffer", [
"Buffer", "Buffer",
@ -94,8 +96,12 @@ export function read(
"DataView", "DataView",
], optOrBufferOrCb); ], optOrBufferOrCb);
} }
if (opt.buffer === undefined) {
buffer = Buffer.alloc(16384);
} else {
buffer = arrayBufferViewToUint8Array(opt.buffer);
}
offset = opt.offset ?? 0; offset = opt.offset ?? 0;
buffer = opt.buffer ?? Buffer.alloc(16384);
length = opt.length ?? buffer.byteLength; length = opt.length ?? buffer.byteLength;
position = opt.position ?? null; position = opt.position ?? null;
} }
@ -131,19 +137,19 @@ export function read(
export function readSync( export function readSync(
fd: number, fd: number,
buffer: Buffer | Uint8Array, buffer: ArrayBufferView,
offset: number, offset: number,
length: number, length: number,
position: number | null, position: number | null,
): number; ): number;
export function readSync( export function readSync(
fd: number, fd: number,
buffer: Buffer | Uint8Array, buffer: ArrayBufferView,
opt: readSyncOptions, opt: readSyncOptions,
): number; ): number;
export function readSync( export function readSync(
fd: number, fd: number,
buffer: Buffer | Uint8Array, buffer: ArrayBufferView,
offsetOrOpt?: number | readSyncOptions, offsetOrOpt?: number | readSyncOptions,
length?: number, length?: number,
position?: number | null, position?: number | null,
@ -156,6 +162,8 @@ export function readSync(
validateBuffer(buffer); validateBuffer(buffer);
buffer = arrayBufferViewToUint8Array(buffer);
if (length == null) { if (length == null) {
length = 0; length = 0;
} }

View file

@ -12,6 +12,7 @@ import {
import * as io from "ext:deno_io/12_io.js"; import * as io from "ext:deno_io/12_io.js";
import * as fs from "ext:deno_fs/30_fs.js"; import * as fs from "ext:deno_fs/30_fs.js";
import { import {
arrayBufferViewToUint8Array,
getValidatedFd, getValidatedFd,
validateOffsetLengthWrite, validateOffsetLengthWrite,
validateStringAfterArrayBufferView, validateStringAfterArrayBufferView,
@ -23,9 +24,7 @@ export function writeSync(fd, buffer, offset, length, position) {
fd = getValidatedFd(fd); fd = getValidatedFd(fd);
const innerWriteSync = (fd, buffer, offset, length, position) => { const innerWriteSync = (fd, buffer, offset, length, position) => {
if (buffer instanceof DataView) { buffer = arrayBufferViewToUint8Array(buffer);
buffer = new Uint8Array(buffer.buffer);
}
if (typeof position === "number") { if (typeof position === "number") {
fs.seekSync(fd, position, io.SeekMode.Start); fs.seekSync(fd, position, io.SeekMode.Start);
} }
@ -69,9 +68,7 @@ export function write(fd, buffer, offset, length, position, callback) {
fd = getValidatedFd(fd); fd = getValidatedFd(fd);
const innerWrite = async (fd, buffer, offset, length, position) => { const innerWrite = async (fd, buffer, offset, length, position) => {
if (buffer instanceof DataView) { buffer = arrayBufferViewToUint8Array(buffer);
buffer = new Uint8Array(buffer.buffer);
}
if (typeof position === "number") { if (typeof position === "number") {
await fs.seek(fd, position, io.SeekMode.Start); await fs.seek(fd, position, io.SeekMode.Start);
} }

View file

@ -986,6 +986,16 @@ export const validatePosition = hideStackFrames((position) => {
} }
}); });
/** @type {(buffer: ArrayBufferView) => Uint8Array} */
export const arrayBufferViewToUint8Array = hideStackFrames(
(buffer) => {
if (!(buffer instanceof Uint8Array)) {
return new Uint8Array(buffer.buffer);
}
return buffer;
},
);
export const realpathCacheKey = Symbol("realpathCacheKey"); export const realpathCacheKey = Symbol("realpathCacheKey");
export const constants = { export const constants = {
kIoMaxLength, kIoMaxLength,

View file

@ -1,4 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
/// <reference types="npm:@types/node" />
import { import {
assertEquals, assertEquals,
assertFalse, assertFalse,
@ -13,21 +14,21 @@ import { closeSync } from "node:fs";
async function readTest( async function readTest(
testData: string, testData: string,
buffer: Buffer, buffer: NodeJS.ArrayBufferView,
offset: number, offset: number,
length: number, length: number,
position: number | null = null, position: number | null = null,
expected: ( expected: (
fd: number, fd: number,
bytesRead: number | null, bytesRead: number | null,
data: Buffer | undefined, data: ArrayBufferView | undefined,
) => void, ) => void,
) { ) {
let fd1 = 0; let fd1 = 0;
await new Promise<{ await new Promise<{
fd: number; fd: number;
bytesRead: number | null; bytesRead: number | null;
data: Buffer | undefined; data: ArrayBufferView | undefined;
}>((resolve, reject) => { }>((resolve, reject) => {
open(testData, "r", (err, fd) => { open(testData, "r", (err, fd) => {
if (err) reject(err); if (err) reject(err);
@ -320,3 +321,35 @@ Deno.test({
closeSync(fd); closeSync(fd);
}, },
}); });
Deno.test({
name: "accepts non Uint8Array buffer",
async fn() {
const moduleDir = path.dirname(path.fromFileUrl(import.meta.url));
const testData = path.resolve(moduleDir, "testdata", "hello.txt");
const buffer = new ArrayBuffer(1024);
const buf = new Int8Array(buffer);
await readTest(
testData,
buf,
buf.byteOffset,
buf.byteLength,
null,
(_fd, bytesRead, data) => {
assertStrictEquals(bytesRead, 11);
assertEquals(data instanceof Buffer, true);
assertMatch((data as Buffer).toString(), /hello world/);
},
);
const fd = openSync(testData, "r");
try {
const nRead = readSync(fd, buf);
const expected = new TextEncoder().encode("hello world");
assertEquals(buf.slice(0, nRead), new Int8Array(expected.buffer));
} finally {
closeSync(fd);
}
},
});

View file

@ -73,3 +73,42 @@ Deno.test({
assertEquals(decoder.decode(data), "\x00\x00\x00\x00hello world"); assertEquals(decoder.decode(data), "\x00\x00\x00\x00hello world");
}, },
}); });
Deno.test({
name: "Accepts non Uint8Array buffer",
async fn() {
const tempFile: string = Deno.makeTempFileSync();
using file = Deno.openSync(tempFile, {
create: true,
write: true,
read: true,
});
const bytes = [0, 1, 2, 3, 4];
const buffer = new Int8Array(bytes);
for (let i = 0; i < buffer.length; i++) {
buffer[i] = i;
}
let nWritten = writeSync(file.rid, buffer);
const data = Deno.readFileSync(tempFile);
assertEquals(nWritten, 5);
assertEquals(data, new Uint8Array(bytes));
nWritten = await new Promise((resolve, reject) =>
write(
file.rid,
buffer,
0,
5,
(err: unknown, nwritten: number) => {
if (err) return reject(err);
resolve(nwritten);
},
)
);
assertEquals(nWritten, 5);
assertEquals(data, new Uint8Array(bytes));
},
});