From 0a60e0c9d27b85199d7fdda494e2bb45e9c8878e Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Sat, 8 Feb 2025 00:51:47 -0800 Subject: [PATCH] fix test-buffer-inspect.js --- src/bun.js/ConsoleObject.zig | 1 - src/bun.js/api/BunObject.zig | 25 +++++- src/bun.js/bindings/JSBuffer.cpp | 78 ++++++++++++++++++- src/bun.js/modules/NodeBufferModule.h | 2 +- src/js/builtins/JSBufferPrototype.ts | 4 - .../node/test/parallel/test-buffer-inspect.js | 70 +++++++++++++++++ 6 files changed, 170 insertions(+), 10 deletions(-) create mode 100644 test/js/node/test/parallel/test-buffer-inspect.js diff --git a/src/bun.js/ConsoleObject.zig b/src/bun.js/ConsoleObject.zig index b78c9afe7d22ef..3a8f469150f51e 100644 --- a/src/bun.js/ConsoleObject.zig +++ b/src/bun.js/ConsoleObject.zig @@ -784,7 +784,6 @@ pub const FormatOptions = struct { if (try arg1.getBooleanLoose(globalThis, "sorted")) |opt| { formatOptions.ordered_properties = opt; } - if (try arg1.getBooleanLoose(globalThis, "compact")) |opt| { formatOptions.single_line = opt; } diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index e51a3535f174f0..387b3a8c5b2e2b 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -500,7 +500,6 @@ pub fn inspect(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.J if (arguments.len > 1) { try formatOptions.fromJS(globalThis, arguments[1..]); } - const value = arguments[0]; // very stable memory address var array = MutableString.init(getAllocator(globalThis), 0) catch unreachable; @@ -509,13 +508,13 @@ pub fn inspect(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.J var buffered_writer = &buffered_writer_; const writer = buffered_writer.writer(); - const Writer = @TypeOf(writer); + const Writer = MutableString.BufferedWriter.Writer; // we buffer this because it'll almost always be < 4096 // when it's under 4096, we want to avoid the dynamic allocation try ConsoleObject.format2( .Debug, globalThis, - @as([*]const JSValue, @ptrCast(&value)), + arguments.ptr, 1, Writer, Writer, @@ -546,6 +545,26 @@ export fn Bun__inspect(globalThis: *JSGlobalObject, value: JSValue) ZigString { return ZigString.init(array.slice()).withEncoding(); } +export fn Bun__inspect_singleline(globalThis: *JSGlobalObject, value: JSValue) bun.String { + var array = MutableString.init(getAllocator(globalThis), 0) catch unreachable; + defer array.deinit(); + var buffered_writer = MutableString.BufferedWriter{ .context = &array }; + const writer = buffered_writer.writer(); + const Writer = MutableString.BufferedWriter.Writer; + ConsoleObject.format2(.Debug, globalThis, (&value)[0..1].ptr, 1, Writer, Writer, writer, .{ + .enable_colors = false, + .add_newline = false, + .flush = false, + .max_depth = std.math.maxInt(u16), + .quote_strings = true, + .ordered_properties = false, + .single_line = true, + }) catch return .empty; + if (globalThis.hasException()) return .empty; + buffered_writer.flush() catch return .empty; + return bun.String.createUTF8(array.slice()); +} + pub fn getInspect(globalObject: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue { const fun = JSC.createCallback(globalObject, ZigString.static("inspect"), 2, inspect); var str = ZigString.init("nodejs.util.inspect.custom"); diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index ecd0e243edd2fc..361c605da0ec4a 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -80,6 +80,9 @@ extern "C" bool Bun__Node__ZeroFillBuffers; +// export fn Bun__inspect_singleline(globalThis: *JSGlobalObject, value: JSValue) bun.String +extern "C" BunString Bun__inspect_singleline(JSC::JSGlobalObject* globalObject, JSC::JSValue value); + using namespace JSC; using namespace WebCore; @@ -105,6 +108,7 @@ static JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_equals); static JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_fill); static JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_includes); static JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_indexOf); +static JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_inspect); static JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_lastIndexOf); static JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_swap16); static JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_swap32); @@ -1470,6 +1474,71 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_indexOfBody(JSC::JSG return JSC::JSValue::encode(jsNumber(index)); } +static JSC::EncodedJSValue jsBufferPrototypeFunction_inspectBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis) +{ + auto globalObject = defaultGlobalObject(lexicalGlobalObject); + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto recurseTimes = callFrame->argument(0); + UNUSED_PARAM(recurseTimes); + auto ctx = callFrame->argument(1); + + WTF::StringBuilder result; + auto data = castedThis->span(); + auto alphabet = "0123456789abcdef"_s; + + result.append("INSPECT_MAX_BYTES; + auto actualMaxD = std::min(max, data.size()); + size_t actualMax = actualMaxD; + + for (auto item : data.first(actualMax)) { + result.append(' '); + result.append(alphabet[item / 16]); + result.append(alphabet[item % 16]); + } + if (data.size() > max) { + auto remaining = data.size() - max; + result.append(makeString(" ... "_s, remaining, " more byte"_s)); + if (remaining > 1) result.append('s'); + } + + // Inspect special properties as well, if possible. + if (ctx.toBoolean(globalObject)) { + auto showHidden = ctx.get(globalObject, Identifier::fromString(vm, "showHidden"_s)); + RETURN_IF_EXCEPTION(scope, {}); + JSC::PropertyNameArray array(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude); + + auto extras = false; + auto filter = showHidden.toBoolean(globalObject) ? DontEnumPropertiesMode::Include : DontEnumPropertiesMode::Exclude; + + if (UNLIKELY(castedThis->hasNonReifiedStaticProperties())) { + castedThis->reifyAllStaticProperties(globalObject); + } + castedThis->getOwnNonIndexPropertyNames(globalObject, array, filter); + RETURN_IF_EXCEPTION(scope, {}); + auto obj = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 0); + + for (auto ident : array) { + extras = true; + auto value = castedThis->get(globalObject, ident); + RETURN_IF_EXCEPTION(scope, {}); + obj->putDirect(vm, ident, value); + } + if (extras) { + if (data.size() > 0) { + result.append(","_s); + } + result.append(' '); + auto inspected = Bun__inspect_singleline(globalObject, obj).transferToWTFString(); + result.append(inspected.substring(2, inspected.length() - 4)); + } + } + result.append('>'); + return JSValue::encode(JSC::jsString(vm, result.toString())); +} + static inline JSC::EncodedJSValue jsBufferPrototypeFunction_lastIndexOfBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis) { auto index = indexOf(lexicalGlobalObject, callFrame, castedThis, true); @@ -2119,6 +2188,11 @@ JSC_DEFINE_HOST_FUNCTION(jsBufferPrototypeFunction_indexOf, (JSGlobalObject * le return IDLOperation::call(*lexicalGlobalObject, *callFrame, "indexOf"); } +JSC_DEFINE_HOST_FUNCTION(jsBufferPrototypeFunction_inspect, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation::call(*lexicalGlobalObject, *callFrame, "inspect"); +} + JSC_DEFINE_HOST_FUNCTION(jsBufferPrototypeFunction_lastIndexOf, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { return IDLOperation::call(*lexicalGlobalObject, *callFrame, "lastIndexOf"); @@ -2239,7 +2313,7 @@ static const HashTableValue JSBufferPrototypeTableValues[] { "hexWrite"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_hexWrite, 3 } }, { "includes"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_includes, 3 } }, { "indexOf"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_indexOf, 3 } }, - { "inspect"_s, static_cast(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeInspectCodeGenerator, 2 } }, + { "inspect"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_inspect, 2 } }, { "lastIndexOf"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_lastIndexOf, 3 } }, { "latin1Slice"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_latin1Slice, 2 } }, { "latin1Write"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_latin1Write, 3 } }, @@ -2355,6 +2429,8 @@ void JSBufferPrototype::finishCreation(VM& vm, JSC::JSGlobalObject* globalThis) ALIAS("writeUint32LE", "writeUInt32LE"); ALIAS("writeBigUint64BE", "writeBigUInt64BE"); ALIAS("writeBigUint64LE", "writeBigUInt64LE"); + + this->putDirect(vm, Identifier::fromUid(vm.symbolRegistry().symbolForKey("nodejs.util.inspect.custom"_s)), this->getDirect(vm, Identifier::fromString(vm, "inspect"_s)), PropertyAttribute::Builtin | 0); } const ClassInfo JSBufferPrototype::s_info = { diff --git a/src/bun.js/modules/NodeBufferModule.h b/src/bun.js/modules/NodeBufferModule.h index 2f791ab677386f..ab6bf16326e123 100644 --- a/src/bun.js/modules/NodeBufferModule.h +++ b/src/bun.js/modules/NodeBufferModule.h @@ -140,7 +140,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionNotImplemented, JSC_DEFINE_CUSTOM_GETTER(jsGetter_INSPECT_MAX_BYTES, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) { auto globalObject = reinterpret_cast(lexicalGlobalObject); - return JSValue::encode(jsNumber(globalObject->INSPECT_MAX_BYTES)); + return JSValue::encode(jsDoubleNumber(globalObject->INSPECT_MAX_BYTES)); } JSC_DEFINE_CUSTOM_SETTER(jsSetter_INSPECT_MAX_BYTES, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, PropertyName propertyName)) diff --git a/src/js/builtins/JSBufferPrototype.ts b/src/js/builtins/JSBufferPrototype.ts index 99ca59405aa5ac..a1a3be8c524262 100644 --- a/src/js/builtins/JSBufferPrototype.ts +++ b/src/js/builtins/JSBufferPrototype.ts @@ -511,7 +511,3 @@ $getter; export function offset(this: BufferExt) { return $isObject(this) && this instanceof $Buffer ? this.byteOffset : undefined; } - -export function inspect(this: BufferExt, recurseTimes, ctx) { - return Bun.inspect(this); -} diff --git a/test/js/node/test/parallel/test-buffer-inspect.js b/test/js/node/test/parallel/test-buffer-inspect.js new file mode 100644 index 00000000000000..1e8212e876344a --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-inspect.js @@ -0,0 +1,70 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const util = require('util'); +const buffer = require('buffer'); + +buffer.INSPECT_MAX_BYTES = 2; + +let b = Buffer.allocUnsafe(4); +b.fill('1234'); + +let s = buffer.SlowBuffer(4); +s.fill('1234'); + +let expected = ''; + +assert.strictEqual(util.inspect(b), expected); +assert.strictEqual(util.inspect(s), expected); + +b = Buffer.allocUnsafe(2); +b.fill('12'); + +s = buffer.SlowBuffer(2); +s.fill('12'); + +expected = ''; + +assert.strictEqual(util.inspect(b), expected); +assert.strictEqual(util.inspect(s), expected); + +buffer.INSPECT_MAX_BYTES = Infinity; + +assert.strictEqual(util.inspect(b), expected); +assert.strictEqual(util.inspect(s), expected); + +b.inspect = undefined; +b.prop = new Uint8Array(0); +assert.strictEqual( + util.inspect(b), + '' +); + +b = Buffer.alloc(0); +b.prop = 123; + +assert.strictEqual( + util.inspect(b), + '' +);