Skip to content

Commit

Permalink
more pass
Browse files Browse the repository at this point in the history
  • Loading branch information
paperclover committed Jan 10, 2025
1 parent 928e9e4 commit eadc1b4
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 20 deletions.
4 changes: 4 additions & 0 deletions src/bun.js/ConsoleObject.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3262,6 +3262,10 @@ pub const Formatter = struct {
else
bun.asByteSlice(@tagName(arrayBuffer.typed_array_type)),
);
if (arrayBuffer.len == 0) {
writer.print("({d}) []", .{arrayBuffer.len});
return;
}
writer.print("({d}) [ ", .{arrayBuffer.len});

if (slice.len > 0) {
Expand Down
52 changes: 32 additions & 20 deletions src/bun.js/node/node_fs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2412,8 +2412,8 @@ pub const Arguments = struct {

arguments.eat();

if (arguments.next()) |current_| parse: {
var current = current_;
parse: {
var current = arguments.next() orelse break :parse;
switch (buffer) {
// fs.write(fd, string[, position[, encoding]], callback)
else => {
Expand All @@ -2430,31 +2430,39 @@ pub const Arguments = struct {
},
// fs.write(fd, buffer[, offset[, length[, position]]], callback)
.buffer => {
if (!current.isNumber()) {
break :parse;
}

if (!(current.isNumber() or current.isBigInt())) break :parse;
args.offset = current.to(u52);
args.offset = @intCast(try JSC.Node.validators.validateInteger(ctx, current, "offset", .{}, 0, 9007199254740991));
arguments.eat();
current = arguments.next() orelse break :parse;

if (!(current.isNumber() or current.isBigInt())) break :parse;
args.length = current.to(u52);
const buf_len = args.buffer.slice().len;
if (args.length > 0) {
if (args.length > buf_len) {
return ctx.throwRangeError(
@as(f64, @floatFromInt(args.length)),
.{ .field_name = "length", .max = @intCast(@min(buf_len, std.math.maxInt(i64))) },
);
}
const length = current.to(i52);
const buf_len = args.buffer.buffer.slice().len;
if (args.offset > buf_len) {
return ctx.throwRangeError(
@as(f64, @floatFromInt(args.length)),
.{ .field_name = "offset", .max = @intCast(@min(buf_len, std.math.maxInt(i64))) },
);
}
if (length > buf_len - args.offset) {
return ctx.throwRangeError(
@as(f64, @floatFromInt(args.length)),
.{ .field_name = "length", .max = @intCast(@min(buf_len, std.math.maxInt(i64))) },
);
}
if (length < 0) {
return ctx.throwRangeError(
@as(f64, @floatFromInt(args.length)),
.{ .field_name = "length", .min = 0 },
);
}
args.length = @intCast(length);

arguments.eat();
current = arguments.next() orelse break :parse;

if (!(current.isNumber() or current.isBigInt())) break :parse;
args.position = current.to(i52);
const position = current.to(i52);
if (position >= 0) args.position = position;
arguments.eat();
},
}
Expand Down Expand Up @@ -2521,7 +2529,9 @@ pub const Arguments = struct {
if (arguments.next()) |arg_position| {
arguments.eat();
if (arg_position.isNumber() or arg_position.isBigInt()) {
args.position = @as(ReadPosition, @intCast(arg_position.to(i52)));
const num = arg_position.to(i52);
if (num > 0)
args.position = @as(ReadPosition, @intCast(num));
}
}
} else if (current.isObject()) {
Expand All @@ -2540,7 +2550,9 @@ pub const Arguments = struct {

if (try current.getTruthy(ctx, "position")) |num| {
if (num.isNumber() or num.isBigInt()) {
args.position = num.to(i52);
const n = num.to(i52);
if (n > 0)
args.position = num.to(i52);
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions src/js/node/fs.promises.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,8 +467,21 @@ function asyncWrap(fn: any) {

if (buffer?.byteLength === 0) return { __proto__: null, bytesWritten: 0, buffer };

isArrayBufferView ??= require("node:util/types").isArrayBufferView;
if (isArrayBufferView(buffer)) {
if (typeof offset === "object") {
({ offset = 0, length = buffer.byteLength - offset, position = null } = offset ?? kEmptyObject);
}

if (offset == null) {
offset = 0;
}
if (typeof length !== "number") length = buffer.byteLength - offset;
if (typeof position !== "number") position = null;
}
try {
this[kRef]();
console.log({ fd, buffer, offset, length, position });
return { buffer, bytesWritten: await write(fd, buffer, offset, length, position) };
} finally {
this[kUnref]();
Expand Down
111 changes: 111 additions & 0 deletions test/js/node/test/parallel/test-fs-promises-write-optional-params.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
'use strict';

const common = require('../common');

// This test ensures that filehandle.write accepts "named parameters" object
// and doesn't interpret objects as strings

const assert = require('assert');
const fsPromises = require('fs').promises;
const tmpdir = require('../common/tmpdir');

tmpdir.refresh();

const dest = tmpdir.resolve('tmp.txt');
const buffer = Buffer.from('zyx');

async function testInvalid(dest, expectedCode, ...params) {
if (params.length >= 2) {
params[1] = common.mustNotMutateObjectDeep(params[1]);
}
let fh;
try {
fh = await fsPromises.open(dest, 'w+');
console.log({ expectedCode, params });
await assert.rejects(
fh.write(...params),
{ code: expectedCode });
} finally {
await fh?.close();
}
}

async function testValid(dest, buffer, options) {
const length = options?.length;
const offset = options?.offset;
let fh, writeResult, writeBufCopy, readResult, readBufCopy;

try {
fh = await fsPromises.open(dest, 'w');
writeResult = await fh.write(buffer, options);
writeBufCopy = Uint8Array.prototype.slice.call(writeResult.buffer);
} finally {
await fh?.close();
}

try {
fh = await fsPromises.open(dest, 'r');
readResult = await fh.read(buffer, options);
readBufCopy = Uint8Array.prototype.slice.call(readResult.buffer);
} finally {
await fh?.close();
}

assert.ok(writeResult.bytesWritten >= readResult.bytesRead);
if (length !== undefined && length !== null) {
assert.strictEqual(writeResult.bytesWritten, length);
assert.strictEqual(readResult.bytesRead, length);
}
if (offset === undefined || offset === 0) {
assert.deepStrictEqual(writeBufCopy, readBufCopy);
}
assert.deepStrictEqual(writeResult.buffer, readResult.buffer);
}

(async () => {
// Test if first argument is not wrongly interpreted as ArrayBufferView|string
for (const badBuffer of [
undefined, null, true, 42, 42n, Symbol('42'), NaN, [], () => {},
common.mustNotCall(),
common.mustNotMutateObjectDeep({}),
Promise.resolve(new Uint8Array(1)),
{},
{ buffer: 'amNotParam' },
{ string: 'amNotParam' },
{ buffer: new Uint8Array(1).buffer },
new Date(),
new String('notPrimitive'),
{ toString() { return 'amObject'; } },
{ [Symbol.toPrimitive]: (hint) => 'amObject' },
]) {
await testInvalid(dest, 'ERR_INVALID_ARG_TYPE', common.mustNotMutateObjectDeep(badBuffer), {});
}

// First argument (buffer or string) is mandatory
await testInvalid(dest, 'ERR_INVALID_ARG_TYPE');

// Various invalid options
await testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { length: 5 });
await testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { offset: 5 });
await testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { length: 1, offset: 3 });
await testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { length: -1 });
await testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { offset: -1 });
await testInvalid(dest, 'ERR_INVALID_ARG_TYPE', buffer, { offset: false });
await testInvalid(dest, 'ERR_INVALID_ARG_TYPE', buffer, { offset: true });

// Test compatibility with filehandle.read counterpart
for (const options of [
undefined,
null,
{},
{ length: 1 },
{ position: 5 },
{ length: 1, position: 5 },
{ length: 1, position: -1, offset: 2 },
{ length: null },
{ position: null },
{ offset: 1 },
]) {
await testValid(dest, buffer, common.mustNotMutateObjectDeep(options));
}
})().then(common.mustCall());
41 changes: 41 additions & 0 deletions test/js/node/test/parallel/test-fs-read-empty-buffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use strict';
require('../common');
const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const fs = require('fs');
const filepath = fixtures.path('x.txt');
const fd = fs.openSync(filepath, 'r');
const fsPromises = fs.promises;

const buffer = new Uint8Array();

assert.throws(
() => fs.readSync(fd, buffer, 0, 10, 0),
{
code: 'ERR_INVALID_ARG_VALUE',
message: 'The argument \'buffer\' is empty and cannot be written. ' +
'Received Uint8Array(0) []'
}
);

assert.throws(
() => fs.read(fd, buffer, 0, 1, 0, common.mustNotCall()),
{
code: 'ERR_INVALID_ARG_VALUE',
message: 'The argument \'buffer\' is empty and cannot be written. ' +
'Received Uint8Array(0) []'
}
);

(async () => {
const filehandle = await fsPromises.open(filepath, 'r');
assert.rejects(
() => filehandle.read(buffer, 0, 1, 0),
{
code: 'ERR_INVALID_ARG_VALUE',
message: 'The argument \'buffer\' is empty and cannot be written. ' +
'Received Uint8Array(0) []'
}
).then(common.mustCall());
})().then(common.mustCall());

0 comments on commit eadc1b4

Please sign in to comment.