diff options
author | 2022-04-29 07:49:48 -0700 | |
---|---|---|
committer | 2022-04-29 07:49:48 -0700 | |
commit | d49ba5028949f726e68b31093921c2187759ab4b (patch) | |
tree | 91fdcf74bc342c872ee7047fe1bd6ca9d3a0fe93 | |
parent | 22f74756b4cf173749b2f72fdae438f8def24bd2 (diff) | |
download | bun-d49ba5028949f726e68b31093921c2187759ab4b.tar.gz bun-d49ba5028949f726e68b31093921c2187759ab4b.tar.zst bun-d49ba5028949f726e68b31093921c2187759ab4b.zip |
[bun.js] Implement unsafe.{`arrayBufferToPtr`, `arrayBufferFromPtr`, `bufferFromPtr`}
-rw-r--r-- | integration/bunjs-only-snippets/ffi.test.js | 9 | ||||
-rw-r--r-- | src/javascript/jsc/api/bun.zig | 102 | ||||
-rw-r--r-- | src/javascript/jsc/base.zig | 2 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/JSBuffer.cpp | 51 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/bindings.zig | 15 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/builtins/js/JSBufferPrototype.js | 90 |
6 files changed, 187 insertions, 82 deletions
diff --git a/integration/bunjs-only-snippets/ffi.test.js b/integration/bunjs-only-snippets/ffi.test.js index cd51a4594..16967fe51 100644 --- a/integration/bunjs-only-snippets/ffi.test.js +++ b/integration/bunjs-only-snippets/ffi.test.js @@ -1,4 +1,5 @@ import { describe, it, expect } from "bun:test"; +import { unsafe } from "bun"; it("ffi print", () => { Bun.dlprint({ @@ -168,7 +169,6 @@ it("ffi run", () => { // params: ["uint64_t", "uint64_t"], // }, }; - console.log(Bun.dlprint(types)[0]); const { symbols: { returns_true, @@ -253,7 +253,12 @@ it("ffi run", () => { expect(ptr != 0).toBe(true); expect(typeof ptr === "number").toBe(true); expect(does_pointer_equal_42_as_int32_t(ptr)).toBe(true); - // expect(add_uint64_t(1, 1)).toBe(2); + const buffer = unsafe.bufferFromPtr(ptr, 4); + expect(buffer.readInt32(0)).toBe(42); + expect( + new DataView(unsafe.arrayBufferFromPtr(ptr, 4), 0, 4).getInt32(0, true) + ).toBe(42); + expect(unsafe.arrayBufferToPtr(buffer)).toBe(ptr); close(); }); ``; diff --git a/src/javascript/jsc/api/bun.zig b/src/javascript/jsc/api/bun.zig index ff6e63724..34e9b7645 100644 --- a/src/javascript/jsc/api/bun.zig +++ b/src/javascript/jsc/api/bun.zig @@ -1862,10 +1862,112 @@ pub const Unsafe = struct { .arrayBufferToString = .{ .rfn = arrayBufferToString, }, + .arrayBufferToPtr = .{ + .rfn = JSC.wrapWithHasContainer(Unsafe, "arrayBufferToPtr", false, false), + }, + .arrayBufferFromPtr = .{ + .rfn = JSC.wrapWithHasContainer(Unsafe, "arrayBufferFromPtr", false, false), + }, + .bufferFromPtr = .{ + .rfn = JSC.wrapWithHasContainer(Unsafe, "bufferFromPtr", false, false), + }, }, .{}, ); + const ValueOrError = union(enum) { + err: JSValue, + slice: []u8, + }; + + pub fn arrayBufferToPtr(globalThis: *JSGlobalObject, value: JSValue) JSValue { + if (value.isEmpty()) { + return JSC.JSValue.jsNull(); + } + + const array_buffer = value.asArrayBuffer(globalThis) orelse { + return JSC.toInvalidArguments("Expected ArrayBufferView", .{}, globalThis.ref()); + }; + + if (array_buffer.len == 0) { + return JSC.toInvalidArguments("ArrayBufferView must have a length > 0. A pointer to empty memory doesn't work", .{}, globalThis.ref()); + } + + return JSC.JSValue.jsNumber(@bitCast(f64, @ptrToInt(array_buffer.ptr))); + } + + fn getPtrSlice(globalThis: *JSGlobalObject, value: JSValue, valueLength: JSValue) ValueOrError { + if (!value.isNumber()) { + return .{ .err = JSC.toInvalidArguments("ptr must be a number.", .{}, globalThis.ref()) }; + } + + const num = value.asNumber(); + if (num == 0) { + return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis.ref()) }; + } + + if (!std.math.isFinite(num)) { + return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis.ref()) }; + } + + const addr = @bitCast(usize, num); + + if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) { + return .{ .err = JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis.ref()) }; + } + + if (!valueLength.isNumber()) { + return .{ .err = JSC.toInvalidArguments("length must be a number.", .{}, globalThis.ref()) }; + } + + if (valueLength.asNumber() == 0.0) { + return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) }; + } + + const length_i = valueLength.toInt64(); + if (length_i < 0) { + return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) }; + } + + if (length_i > std.math.maxInt(u48)) { + return .{ .err = JSC.toInvalidArguments("length exceeds max addressable memory. This usually means a bug in your code.", .{}, globalThis.ref()) }; + } + + const length = @intCast(usize, length_i); + + return .{ .slice = @intToPtr([*]u8, addr)[0..length] }; + } + + pub fn arrayBufferFromPtr( + globalThis: *JSGlobalObject, + value: JSValue, + valueLength: JSValue, + ) JSC.JSValue { + switch (getPtrSlice(globalThis, value, valueLength)) { + .err => |erro| { + return erro; + }, + .slice => |slice| { + return JSC.ArrayBuffer.fromBytes(slice, JSC.JSValue.JSType.ArrayBuffer).toJSWithContext(globalThis.ref(), null, null, null); + }, + } + } + + pub fn bufferFromPtr( + globalThis: *JSGlobalObject, + value: JSValue, + valueLength: JSValue, + ) JSC.JSValue { + switch (getPtrSlice(globalThis, value, valueLength)) { + .err => |erro| { + return erro; + }, + .slice => |slice| { + return JSC.JSValue.createBuffer(globalThis, slice, null); + }, + } + } + // For testing the segfault handler pub fn __debug__doSegfault( _: void, diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig index d9b46f868..e2d57b9ca 100644 --- a/src/javascript/jsc/base.zig +++ b/src/javascript/jsc/base.zig @@ -2309,7 +2309,7 @@ pub const ArrayBuffer = extern struct { pub fn toJSWithContext( this: ArrayBuffer, ctx: JSC.C.JSContextRef, - deallocator: *anyopaque, + deallocator: ?*anyopaque, callback: JSC.C.JSTypedArrayBytesDeallocator, exception: JSC.C.ExceptionRef, ) JSC.JSValue { diff --git a/src/javascript/jsc/bindings/JSBuffer.cpp b/src/javascript/jsc/bindings/JSBuffer.cpp index d0278844d..afb4eebcd 100644 --- a/src/javascript/jsc/bindings/JSBuffer.cpp +++ b/src/javascript/jsc/bindings/JSBuffer.cpp @@ -69,7 +69,6 @@ static void toBuffer(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSUint8Array JSC::EncodedJSValue JSBuffer__bufferFromPointerAndLengthAndDeinit(JSC::JSGlobalObject* lexicalGlobalObject, char* ptr, unsigned int length, void* ctx, JSTypedArrayBytesDeallocator bytesDeallocator) { - JSC::JSUint8Array* uint8Array = nullptr; if (LIKELY(length > 0)) { @@ -352,36 +351,36 @@ static inline JSC::EncodedJSValue constructBufferFromStringAndEncoding(JSC::JSGl return JSC::JSValue::encode(buffer); } - case WebCore::BufferEncodingType::base64url: { - if (view.is8Bit()) { - result = Bun__encoding__constructFromLatin1AsURLSafeBase64(lexicalGlobalObject, view.characters8(), view.length()); - } else { - result = Bun__encoding__constructFromUTF16AsURLSafeBase64(lexicalGlobalObject, view.characters16(), view.length()); - } - break; +case WebCore::BufferEncodingType::base64url: { + if (view.is8Bit()) { + result = Bun__encoding__constructFromLatin1AsURLSafeBase64(lexicalGlobalObject, view.characters8(), view.length()); + } else { + result = Bun__encoding__constructFromUTF16AsURLSafeBase64(lexicalGlobalObject, view.characters16(), view.length()); } + break; +} - case WebCore::BufferEncodingType::hex: { - if (view.is8Bit()) { - result = Bun__encoding__constructFromLatin1AsHex(lexicalGlobalObject, view.characters8(), view.length()); - } else { - result = Bun__encoding__constructFromUTF16AsHex(lexicalGlobalObject, view.characters16(), view.length()); - } - break; - } - } - JSC::JSValue decoded = JSC::JSValue::decode(result); - if (UNLIKELY(!result)) { - throwTypeError(lexicalGlobalObject, scope, "An error occurred while decoding the string"_s); - return JSC::JSValue::encode(jsUndefined()); +case WebCore::BufferEncodingType::hex: { + if (view.is8Bit()) { + result = Bun__encoding__constructFromLatin1AsHex(lexicalGlobalObject, view.characters8(), view.length()); + } else { + result = Bun__encoding__constructFromUTF16AsHex(lexicalGlobalObject, view.characters16(), view.length()); } + break; +} +} +JSC::JSValue decoded = JSC::JSValue::decode(result); +if (UNLIKELY(!result)) { + throwTypeError(lexicalGlobalObject, scope, "An error occurred while decoding the string"_s); + return JSC::JSValue::encode(jsUndefined()); +} - if (decoded.isCell() && decoded.getObject()->isErrorInstance()) { - scope.throwException(lexicalGlobalObject, decoded); - return JSC::JSValue::encode(jsUndefined()); - } +if (decoded.isCell() && decoded.getObject()->isErrorInstance()) { + scope.throwException(lexicalGlobalObject, decoded); + return JSC::JSValue::encode(jsUndefined()); +} - RELEASE_AND_RETURN(scope, result); +RELEASE_AND_RETURN(scope, result); } template<> EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSBufferConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame) diff --git a/src/javascript/jsc/bindings/bindings.zig b/src/javascript/jsc/bindings/bindings.zig index 4d769642d..dbcfabf7b 100644 --- a/src/javascript/jsc/bindings/bindings.zig +++ b/src/javascript/jsc/bindings/bindings.zig @@ -2305,11 +2305,15 @@ pub const JSValue = enum(u64) { return cppFn("makeWithNameAndPrototype", .{ globalObject, class, instance, name_ }); } - /// Must come from globally-allocated memory! - pub fn createBuffer(globalObject: *JSGlobalObject, slice: []u8, allocator: std.mem.Allocator) JSValue { + /// Must come from globally-allocated memory if allocator is not null + pub fn createBuffer(globalObject: *JSGlobalObject, slice: []u8, allocator: ?std.mem.Allocator) JSValue { if (comptime JSC.is_bindgen) unreachable; @setRuntimeSafety(false); - return JSBuffer__bufferFromPointerAndLengthAndDeinit(globalObject, slice.ptr, slice.len, allocator.ptr, JSC.MarkedArrayBuffer_deallocator); + if (allocator) |alloc| { + return JSBuffer__bufferFromPointerAndLengthAndDeinit(globalObject, slice.ptr, slice.len, alloc.ptr, JSC.MarkedArrayBuffer_deallocator); + } else { + return JSBuffer__bufferFromPointerAndLengthAndDeinit(globalObject, slice.ptr, slice.len, null, null); + } } extern fn JSBuffer__bufferFromPointerAndLengthAndDeinit(*JSGlobalObject, [*]u8, usize, ?*anyopaque, JSC.C.JSTypedArrayBytesDeallocator) JSValue; @@ -2816,6 +2820,11 @@ pub const JSValue = enum(u64) { } pub inline fn asVoid(this: JSValue) *anyopaque { + if (comptime bun.Environment.allow_assert) { + if (@enumToInt(this) == 0) { + @panic("JSValue is null"); + } + } return @intToPtr(*anyopaque, @enumToInt(this)); } diff --git a/src/javascript/jsc/bindings/builtins/js/JSBufferPrototype.js b/src/javascript/jsc/bindings/builtins/js/JSBufferPrototype.js index b7563580d..ea1234b8e 100644 --- a/src/javascript/jsc/bindings/builtins/js/JSBufferPrototype.js +++ b/src/javascript/jsc/bindings/builtins/js/JSBufferPrototype.js @@ -26,185 +26,181 @@ // ^ that comment is required or the builtins generator will have a fit. // // - - - - // The fastest way as of April 2022 is to use DataView. // DataView has intrinsics that cause inlining function setBigUint64(offset, value, le) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigUint64(offset, value, le); + return this.dataView.setBigUint64(offset, value, le); } function readInt8(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt8(offset); + return this.dataView.getInt8(offset); } function readUInt8(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint8(offset); + return this.dataView.getUint8(offset); } function readInt16LE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt16(offset, true); + return this.dataView.getInt16(offset, true); } function readInt16BE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt16(offset); + return this.dataView.getInt16(offset); } function readUInt16LE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint16(offset, true); + return this.dataView.getUint16(offset, true); } function readUInt16BE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint16(offset); + return this.dataView.getUint16(offset); } function readInt32LE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt32(offset, true); + return this.dataView.getInt32(offset, true); } function readInt32BE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt32(offset); + return this.dataView.getInt32(offset); } function readUInt32LE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint32(offset, true); + return this.dataView.getUint32(offset, true); } function readUInt32BE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint32(offset); + return this.dataView.getUint32(offset); } function readFloatLE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getFloat32(offset, true); + return this.dataView.getFloat32(offset, true); } function readFloatBE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getFloat32(offset); + return this.dataView.getFloat32(offset); } function readDoubleLE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getFloat64(offset, true); + return this.dataView.getFloat64(offset, true); } function readDoubleBE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getFloat64(offset); + return this.dataView.getFloat64(offset); } function readBigInt64LE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigInt64(offset, true); + return this.dataView.getBigInt64(offset, true); } function readBigInt64BE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigInt64(offset); + return this.dataView.getBigInt64(offset); } function readBigUInt64LE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigUint64(offset, true); + return this.dataView.getBigUint64(offset, true); } function readBigUInt64BE(offset) { "use strict"; - return (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigUint64(offset); + return this.dataView.getBigUint64(offset); } function writeInt8(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt8(offset, value); + this.dataView.setInt8(offset, value); return offset + 1; } function writeUInt8(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint8(offset, value); + this.dataView.setUint8(offset, value); return offset + 1; } function writeInt16LE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt16(offset, value, true); + this.dataView.setInt16(offset, value, true); return offset + 2; } function writeInt16BE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt16(offset, value); + this.dataView.setInt16(offset, value); return offset + 2; } function writeUInt16LE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint16(offset, value, true); + this.dataView.setUint16(offset, value, true); return offset + 2; } function writeUInt16BE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint16(offset, value); + this.dataView.setUint16(offset, value); return offset + 2; } function writeInt32LE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt32(offset, value, true); + this.dataView.setInt32(offset, value, true); return offset + 4; } function writeInt32BE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt32(offset, value); + this.dataView.setInt32(offset, value); return offset + 4; } function writeUInt32LE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint32(offset, value, true); + this.dataView.setUint32(offset, value, true); return offset + 4; } function writeUInt32BE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint32(offset, value); + this.dataView.setUint32(offset, value); return offset + 4; } function writeFloatLE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setFloat32(offset, value, true); + this.dataView.setFloat32(offset, value, true); return offset + 4; } function writeFloatBE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setFloat32(offset, value); + this.dataView.setFloat32(offset, value); return offset + 4; } function writeDoubleLE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setFloat64(offset, value, true); + this.dataView.setFloat64(offset, value, true); return offset + 8; } function writeDoubleBE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setFloat64(offset, value); + this.dataView.setFloat64(offset, value); return offset + 8; } function writeBigInt64LE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigInt64(offset, value, true); + this.dataView.setBigInt64(offset, value, true); return offset + 8; } function writeBigInt64BE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigInt64(offset, value); + this.dataView.setBigInt64(offset, value); return offset + 8; } function writeBigUInt64LE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigUint64(offset, value, true); + this.dataView.setBigUint64(offset, value, true); return offset + 8; } function writeBigUInt64BE(value, offset) { "use strict"; - (this._view ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigUint64(offset, value); + this.dataView.setBigUint64(offset, value); return offset + 8; } @@ -219,8 +215,6 @@ function slice(start, end) { return new Buffer(this.buffer, this.byteOffset + (start || 0), (end || this.byteLength) - (start || 0)); } - - function utf8Write(text, offset, length) { "use strict"; return this.write(text, offset, length, "utf8"); @@ -282,15 +276,11 @@ function base64urlSlice(offset, length) { "use strict"; return this.toString(offset, length, "base64url"); } -function hexSlice(offset, length) { + +function subarray(start, end) { "use strict"; - var array = new @Uint8Array(this.buffer, this.byteOffset + (start || 0), (end || this.byteLength) - (start || 0)); - @setPrototypeDirect.@call( - array, - Buffer.prototype - ); - return array; + return new Buffer(this.buffer, this.byteOffset + (start || 0), (end || this.byteLength) - (start || 0)); } function toJSON() { |