aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-04-29 07:49:48 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-04-29 07:49:48 -0700
commitd49ba5028949f726e68b31093921c2187759ab4b (patch)
tree91fdcf74bc342c872ee7047fe1bd6ca9d3a0fe93
parent22f74756b4cf173749b2f72fdae438f8def24bd2 (diff)
downloadbun-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.js9
-rw-r--r--src/javascript/jsc/api/bun.zig102
-rw-r--r--src/javascript/jsc/base.zig2
-rw-r--r--src/javascript/jsc/bindings/JSBuffer.cpp51
-rw-r--r--src/javascript/jsc/bindings/bindings.zig15
-rw-r--r--src/javascript/jsc/bindings/builtins/js/JSBufferPrototype.js90
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() {