From c3be6732d1e96c4ef78de09111314f0f6dd49af2 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Mon, 31 Mar 2025 02:15:27 -0700 Subject: [PATCH] Fixes #18572 (#18578) Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com> Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> --- .cursor/rules/javascriptcore-class.mdc | 30 +- ...odeFSBinding.cpp => NodeFSStatBinding.cpp} | 0 .../{NodeFSBinding.h => NodeFSStatBinding.h} | 0 src/bun.js/bindings/NodeFSStatFSBinding.cpp | 460 ++++++++++++++++++ src/bun.js/bindings/NodeFSStatFSBinding.h | 8 + src/bun.js/bindings/ZigGlobalObject.cpp | 15 +- src/bun.js/bindings/ZigGlobalObject.h | 2 + .../bindings/generated_classes_list.zig | 3 - src/bun.js/node/Stat.zig | 207 ++++++++ src/bun.js/node/StatFS.zig | 142 ++++++ src/bun.js/node/node.classes.ts | 68 --- src/bun.js/node/node_fs.zig | 13 +- src/bun.js/node/node_fs_stat_watcher.zig | 8 +- src/bun.js/node/types.zig | 350 +------------ test/internal/ban-words.test.ts | 4 +- test/js/node/test/parallel/test-fs-statfs.js | 59 +++ 16 files changed, 933 insertions(+), 436 deletions(-) rename src/bun.js/bindings/{NodeFSBinding.cpp => NodeFSStatBinding.cpp} (100%) rename src/bun.js/bindings/{NodeFSBinding.h => NodeFSStatBinding.h} (100%) create mode 100644 src/bun.js/bindings/NodeFSStatFSBinding.cpp create mode 100644 src/bun.js/bindings/NodeFSStatFSBinding.h create mode 100644 src/bun.js/node/Stat.zig create mode 100644 src/bun.js/node/StatFS.zig create mode 100644 test/js/node/test/parallel/test-fs-statfs.js diff --git a/.cursor/rules/javascriptcore-class.mdc b/.cursor/rules/javascriptcore-class.mdc index de5a088f7b..e8eab9f0f9 100644 --- a/.cursor/rules/javascriptcore-class.mdc +++ b/.cursor/rules/javascriptcore-class.mdc @@ -1,13 +1,14 @@ --- description: JavaScript class implemented in C++ globs: *.cpp +alwaysApply: false --- # Implementing JavaScript classes in C++ If there is a publicly accessible Constructor and Prototype, then there are 3 classes: -- IF there are C++ class members we need a destructor, so `class Foo : public JSC::DestructibleObject`, if no C++ class fields (only JS properties) then we don't need a class at all usually. We can instead use JSC::constructEmptyObject(vm, structure) and `putDirectOffset` like in [NodeFSBinding.cpp](mdc:src/bun.js/bindings/NodeFSBinding.cpp). +- IF there are C++ class members we need a destructor, so `class Foo : public JSC::DestructibleObject`, if no C++ class fields (only JS properties) then we don't need a class at all usually. We can instead use JSC::constructEmptyObject(vm, structure) and `putDirectOffset` like in [NodeFSStatBinding.cpp](mdc:src/bun.js/bindings/NodeFSStatBinding.cpp). - class FooPrototype : public JSC::JSNonFinalObject - class FooConstructor : public JSC::InternalFunction @@ -18,6 +19,7 @@ If there are C++ fields on the Foo class, the Foo class will need an iso subspac Usually you'll need to #include "root.h" at the top of C++ files or you'll get lint errors. Generally, defining the subspace looks like this: + ```c++ class Foo : public JSC::DestructibleObject { @@ -45,6 +47,7 @@ It's better to put it in the .cpp file instead of the .h file, when possible. ## Defining properties Define properties on the prototype. Use a const HashTableValues like this: + ```C++ static JSC_DECLARE_HOST_FUNCTION(jsX509CertificateProtoFuncCheckEmail); static JSC_DECLARE_HOST_FUNCTION(jsX509CertificateProtoFuncCheckHost); @@ -158,6 +161,7 @@ void JSX509CertificatePrototype::finishCreation(VM& vm) ``` ### Getter definition: + ```C++ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_ca, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) @@ -212,7 +216,6 @@ JSC_DEFINE_HOST_FUNCTION(jsX509CertificateProtoFuncToJSON, (JSGlobalObject * glo } ``` - ### Constructor definition ```C++ @@ -259,7 +262,6 @@ private: }; ``` - ### Structure caching If there's a class, prototype, and constructor: @@ -279,6 +281,7 @@ void GlobalObject::finishCreation(VM& vm) { ``` Then, implement the function that creates the structure: + ```c++ void setupX509CertificateClassStructure(LazyClassStructure::Initializer& init) { @@ -301,11 +304,12 @@ If there's only a class, use `JSC::LazyProperty` inst 1. Add the `JSC::LazyProperty` to @ZigGlobalObject.h 2. Initialize the class structure in @ZigGlobalObject.cpp in `void GlobalObject::finishCreation(VM& vm)` 3. Visit the lazy property in visitChildren in @ZigGlobalObject.cpp in `void GlobalObject::visitChildrenImpl` -void GlobalObject::finishCreation(VM& vm) { -// ... + void GlobalObject::finishCreation(VM& vm) { + // ... this.m_myLazyProperty.initLater([](const JSC::LazyProperty::Initializer& init) { - init.set(Bun::initMyStructure(init.vm, reinterpret_cast(init.owner))); - }); + init.set(Bun::initMyStructure(init.vm, reinterpret_cast(init.owner))); + }); + ``` Then, implement the function that creates the structure: @@ -316,7 +320,7 @@ Structure* setupX509CertificateStructure(JSC::VM &vm, Zig::GlobalObject* globalO auto* prototypeStructure = JSX509CertificatePrototype::createStructure(init.vm, init.global, init.global->objectPrototype()); auto* prototype = JSX509CertificatePrototype::create(init.vm, init.global, prototypeStructure); - // If there is no prototype or it only has + // If there is no prototype or it only has auto* structure = JSX509Certificate::createStructure(init.vm, init.global, prototype); init.setPrototype(prototype); @@ -325,7 +329,6 @@ Structure* setupX509CertificateStructure(JSC::VM &vm, Zig::GlobalObject* globalO } ``` - Then, use the structure by calling `globalObject.m_myStructureName.get(globalObject)` ```C++ @@ -378,12 +381,14 @@ extern "C" JSC::EncodedJSValue Bun__JSBigIntStatsObjectConstructor(Zig::GlobalOb ``` Zig: + ```zig extern "c" fn Bun__JSBigIntStatsObjectConstructor(*JSC.JSGlobalObject) JSC.JSValue; pub const getBigIntStatsConstructor = Bun__JSBigIntStatsObjectConstructor; ``` -To create an object (instance) of a JS class defined in C++ from Zig, follow the __toJS convention like this: +To create an object (instance) of a JS class defined in C++ from Zig, follow the \_\_toJS convention like this: + ```c++ // X509* is whatever we need to create the object extern "C" EncodedJSValue Bun__X509__toJS(Zig::GlobalObject* globalObject, X509* cert) @@ -395,12 +400,13 @@ extern "C" EncodedJSValue Bun__X509__toJS(Zig::GlobalObject* globalObject, X509* ``` And from Zig: + ```zig const X509 = opaque { - // ... class + // ... class extern fn Bun__X509__toJS(*JSC.JSGlobalObject, *X509) JSC.JSValue; - + pub fn toJS(this: *X509, globalObject: *JSC.JSGlobalObject) JSC.JSValue { return Bun__X509__toJS(globalObject, this); } diff --git a/src/bun.js/bindings/NodeFSBinding.cpp b/src/bun.js/bindings/NodeFSStatBinding.cpp similarity index 100% rename from src/bun.js/bindings/NodeFSBinding.cpp rename to src/bun.js/bindings/NodeFSStatBinding.cpp diff --git a/src/bun.js/bindings/NodeFSBinding.h b/src/bun.js/bindings/NodeFSStatBinding.h similarity index 100% rename from src/bun.js/bindings/NodeFSBinding.h rename to src/bun.js/bindings/NodeFSStatBinding.h diff --git a/src/bun.js/bindings/NodeFSStatFSBinding.cpp b/src/bun.js/bindings/NodeFSStatFSBinding.cpp new file mode 100644 index 0000000000..e4256e613d --- /dev/null +++ b/src/bun.js/bindings/NodeFSStatFSBinding.cpp @@ -0,0 +1,460 @@ +#include "root.h" + +#include "JavaScriptCore/FunctionPrototype.h" +#include "JavaScriptCore/LazyClassStructure.h" +#include "JavaScriptCore/LazyClassStructureInlines.h" +#include "JavaScriptCore/VMTrapsInlines.h" +#include "BunBuiltinNames.h" +#include "JavaScriptCore/ArgList.h" +#include "JavaScriptCore/JSType.h" +#include "JavaScriptCore/ObjectInitializationScope.h" + +#include "JavaScriptCore/ObjectConstructor.h" +#include +#include +#include +#include +#include +#include +#include +#include "ZigGlobalObject.h" + +namespace Bun { + +class JSStatFSPrototype; +class JSBigIntStatFSPrototype; +class JSStatFSConstructor; +class JSBigIntStatFSConstructor; +using namespace JSC; +using namespace WebCore; + +JSC_DECLARE_HOST_FUNCTION(callStatFS); +JSC_DECLARE_HOST_FUNCTION(callBigIntStatFS); +JSC_DECLARE_HOST_FUNCTION(constructStatFS); +JSC_DECLARE_HOST_FUNCTION(constructBigIntStatFS); + +template +Structure* getStatFSStructure(Zig::GlobalObject* globalObject) +{ + if (isBigInt) { + return globalObject->m_JSStatFSBigIntClassStructure.getInitializedOnMainThread(globalObject); + } + + return globalObject->m_JSStatFSClassStructure.getInitializedOnMainThread(globalObject); +} + +template +JSObject* getStatFSPrototype(Zig::GlobalObject* globalObject) +{ + if (isBigInt) { + return globalObject->m_JSStatFSBigIntClassStructure.prototypeInitializedOnMainThread(globalObject); + } + + return globalObject->m_JSStatFSClassStructure.prototypeInitializedOnMainThread(globalObject); +} + +template +JSObject* getStatFSConstructor(Zig::GlobalObject* globalObject) +{ + if (isBigInt) { + return globalObject->m_JSStatFSBigIntClassStructure.constructorInitializedOnMainThread(globalObject); + } + + return globalObject->m_JSStatFSClassStructure.constructorInitializedOnMainThread(globalObject); +} + +class JSStatFSPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSStatFSPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSStatFSPrototype* prototype = new (NotNull, JSC::allocateCell(vm)) JSStatFSPrototype(vm, structure); + prototype->finishCreation(vm); + return prototype; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSStatFSPrototype, Base); + return &vm.plainObjectSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + structure->setMayBePrototype(true); + return structure; + } + +private: + JSStatFSPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM& vm); +}; + +class JSBigIntStatFSPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSBigIntStatFSPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSBigIntStatFSPrototype* prototype = new (NotNull, JSC::allocateCell(vm)) JSBigIntStatFSPrototype(vm, structure); + prototype->finishCreation(vm); + return prototype; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSBigIntStatFSPrototype, Base); + return &vm.plainObjectSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + structure->setMayBePrototype(true); + return structure; + } + +private: + JSBigIntStatFSPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM& vm); +}; + +class JSStatFSConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSStatFSConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype) + { + JSStatFSConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSStatFSConstructor(vm, structure); + constructor->finishCreation(vm, prototype); + return constructor; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.internalFunctionSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + +private: + JSStatFSConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, callStatFS, constructStatFS) + { + } + + void finishCreation(JSC::VM& vm, JSC::JSObject* prototype) + { + Base::finishCreation(vm, 0, "StatFs"_s); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); + } +}; + +class JSBigIntStatFSConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSBigIntStatFSConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype) + { + JSBigIntStatFSConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSBigIntStatFSConstructor(vm, structure); + constructor->finishCreation(vm, prototype); + return constructor; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.internalFunctionSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + +private: + JSBigIntStatFSConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, callBigIntStatFS, constructBigIntStatFS) + { + } + + void finishCreation(JSC::VM& vm, JSC::JSObject* prototype) + { + Base::finishCreation(vm, 0, "BigIntStatFs"_s); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); + } +}; + +JSC::Structure* createJSStatFSObjectStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + auto* prototype = JSStatFSPrototype::create(vm, globalObject, JSStatFSPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); + auto structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::FinalObjectType, 0), JSFinalObject::info(), NonArray, 7); + + // Add property transitions for all statfs fields + PropertyOffset offset = 0; + structure = structure->addPropertyTransition(vm, structure, vm.propertyNames->type, 0, offset); + structure = structure->addPropertyTransition(vm, structure, JSC::Identifier::fromString(vm, "bsize"_s), 0, offset); + structure = structure->addPropertyTransition(vm, structure, JSC::Identifier::fromString(vm, "blocks"_s), 0, offset); + structure = structure->addPropertyTransition(vm, structure, JSC::Identifier::fromString(vm, "bfree"_s), 0, offset); + structure = structure->addPropertyTransition(vm, structure, JSC::Identifier::fromString(vm, "bavail"_s), 0, offset); + structure = structure->addPropertyTransition(vm, structure, JSC::Identifier::fromString(vm, "files"_s), 0, offset); + structure = structure->addPropertyTransition(vm, structure, JSC::Identifier::fromString(vm, "ffree"_s), 0, offset); + + return structure; +} + +JSC::Structure* createJSBigIntStatFSObjectStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + auto prototype = JSBigIntStatFSPrototype::create(vm, globalObject, JSBigIntStatFSPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); + auto structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::FinalObjectType, 0), JSFinalObject::info(), NonArray, 7); + + // Add property transitions for all bigint statfs fields + PropertyOffset offset = 0; + structure = structure->addPropertyTransition(vm, structure, vm.propertyNames->type, 0, offset); + structure = structure->addPropertyTransition(vm, structure, JSC::Identifier::fromString(vm, "bsize"_s), 0, offset); + structure = structure->addPropertyTransition(vm, structure, JSC::Identifier::fromString(vm, "blocks"_s), 0, offset); + structure = structure->addPropertyTransition(vm, structure, JSC::Identifier::fromString(vm, "bfree"_s), 0, offset); + structure = structure->addPropertyTransition(vm, structure, JSC::Identifier::fromString(vm, "bavail"_s), 0, offset); + structure = structure->addPropertyTransition(vm, structure, JSC::Identifier::fromString(vm, "files"_s), 0, offset); + structure = structure->addPropertyTransition(vm, structure, JSC::Identifier::fromString(vm, "ffree"_s), 0, offset); + + return structure; +} + +extern "C" JSC::EncodedJSValue Bun__createJSStatFSObject(Zig::GlobalObject* globalObject, + int64_t fstype, + int64_t bsize, + int64_t blocks, + int64_t bfree, + int64_t bavail, + int64_t files, + int64_t ffree) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSC::JSValue js_fstype = JSC::jsNumber(fstype); + JSC::JSValue js_bsize = JSC::jsNumber(bsize); + JSC::JSValue js_blocks = JSC::jsNumber(blocks); + JSC::JSValue js_bfree = JSC::jsNumber(bfree); + JSC::JSValue js_bavail = JSC::jsNumber(bavail); + JSC::JSValue js_files = JSC::jsNumber(files); + JSC::JSValue js_ffree = JSC::jsNumber(ffree); + + auto* structure = getStatFSStructure(globalObject); + auto* object = JSC::JSFinalObject::create(vm, structure); + + object->putDirectOffset(vm, 0, js_fstype); + object->putDirectOffset(vm, 1, js_bsize); + object->putDirectOffset(vm, 2, js_blocks); + object->putDirectOffset(vm, 3, js_bfree); + object->putDirectOffset(vm, 4, js_bavail); + object->putDirectOffset(vm, 5, js_files); + object->putDirectOffset(vm, 6, js_ffree); + + RELEASE_AND_RETURN(scope, JSC::JSValue::encode(object)); +} + +extern "C" JSC::EncodedJSValue Bun__createJSBigIntStatFSObject(Zig::GlobalObject* globalObject, + int64_t fstype, + int64_t bsize, + int64_t blocks, + int64_t bfree, + int64_t bavail, + int64_t files, + int64_t ffree) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* structure = getStatFSStructure(globalObject); + JSC::JSValue js_fstype = JSC::JSBigInt::createFrom(globalObject, fstype); + JSC::JSValue js_bsize = JSC::JSBigInt::createFrom(globalObject, bsize); + JSC::JSValue js_blocks = JSC::JSBigInt::createFrom(globalObject, blocks); + JSC::JSValue js_bfree = JSC::JSBigInt::createFrom(globalObject, bfree); + JSC::JSValue js_bavail = JSC::JSBigInt::createFrom(globalObject, bavail); + JSC::JSValue js_files = JSC::JSBigInt::createFrom(globalObject, files); + JSC::JSValue js_ffree = JSC::JSBigInt::createFrom(globalObject, ffree); + + auto* object = JSC::JSFinalObject::create(vm, structure); + + object->putDirectOffset(vm, 0, js_fstype); + object->putDirectOffset(vm, 1, js_bsize); + object->putDirectOffset(vm, 2, js_blocks); + object->putDirectOffset(vm, 3, js_bfree); + object->putDirectOffset(vm, 4, js_bavail); + object->putDirectOffset(vm, 5, js_files); + object->putDirectOffset(vm, 6, js_ffree); + + RELEASE_AND_RETURN(scope, JSC::JSValue::encode(object)); +} + +const JSC::ClassInfo JSStatFSPrototype::s_info = { "StatFs"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSStatFSPrototype) }; +const JSC::ClassInfo JSBigIntStatFSPrototype::s_info = { "BigIntStatFs"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBigIntStatFSPrototype) }; +const JSC::ClassInfo JSStatFSConstructor::s_info = { "StatFs"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSStatFSConstructor) }; +const JSC::ClassInfo JSBigIntStatFSConstructor::s_info = { "BigIntStatFs"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBigIntStatFSConstructor) }; + +template +inline JSValue callJSStatFSFunction(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* structure = getStatFSStructure(defaultGlobalObject(globalObject)); + + JSValue type = callFrame->argument(0); + JSValue bsize = callFrame->argument(1); + JSValue blocks = callFrame->argument(2); + JSValue bfree = callFrame->argument(3); + JSValue bavail = callFrame->argument(4); + JSValue files = callFrame->argument(5); + JSValue ffree = callFrame->argument(6); + + auto* object = JSC::JSFinalObject::create(vm, structure); + + object->putDirectOffset(vm, 0, type); + object->putDirectOffset(vm, 1, bsize); + object->putDirectOffset(vm, 2, blocks); + object->putDirectOffset(vm, 3, bfree); + object->putDirectOffset(vm, 4, bavail); + object->putDirectOffset(vm, 5, files); + object->putDirectOffset(vm, 6, ffree); + + return object; +} + +template +inline JSValue constructJSStatFSObject(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) +{ + auto& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + Zig::GlobalObject* globalObject = defaultGlobalObject(lexicalGlobalObject); + + auto* structure = getStatFSStructure(globalObject); + auto* constructor = getStatFSConstructor(globalObject); + JSObject* newTarget = asObject(callFrame->newTarget()); + + if (constructor != newTarget) { + auto scope = DECLARE_THROW_SCOPE(vm); + auto* functionGlobalObject = reinterpret_cast( + // ShadowRealm functions belong to a different global object. + getFunctionRealm(lexicalGlobalObject, newTarget)); + RETURN_IF_EXCEPTION(scope, {}); + structure = InternalFunction::createSubclassStructure( + lexicalGlobalObject, + newTarget, + getStatFSStructure(functionGlobalObject)); + } + + JSValue type = callFrame->argument(0); + JSValue bsize = callFrame->argument(1); + JSValue blocks = callFrame->argument(2); + JSValue bfree = callFrame->argument(3); + JSValue bavail = callFrame->argument(4); + JSValue files = callFrame->argument(5); + JSValue ffree = callFrame->argument(6); + + JSFinalObject* object = JSC::JSFinalObject::create(vm, structure); + object->putDirect(vm, vm.propertyNames->type, type, 0); + object->putDirect(vm, Identifier::fromString(vm, "bsize"_s), bsize, 0); + object->putDirect(vm, Identifier::fromString(vm, "blocks"_s), blocks, 0); + object->putDirect(vm, Identifier::fromString(vm, "bfree"_s), bfree, 0); + object->putDirect(vm, Identifier::fromString(vm, "bavail"_s), bavail, 0); + object->putDirect(vm, Identifier::fromString(vm, "files"_s), files, 0); + object->putDirect(vm, Identifier::fromString(vm, "ffree"_s), ffree, 0); + + return object; +} + +JSC_DEFINE_HOST_FUNCTION(constructStatFS, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + return JSValue::encode(constructJSStatFSObject(lexicalGlobalObject, callFrame)); +} + +JSC_DEFINE_HOST_FUNCTION(constructBigIntStatFS, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + return JSValue::encode(constructJSStatFSObject(lexicalGlobalObject, callFrame)); +} + +JSC_DEFINE_HOST_FUNCTION(callStatFS, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + return JSValue::encode(callJSStatFSFunction(lexicalGlobalObject, callFrame)); +} + +JSC_DEFINE_HOST_FUNCTION(callBigIntStatFS, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + return JSValue::encode(callJSStatFSFunction(lexicalGlobalObject, callFrame)); +} + +extern "C" JSC::EncodedJSValue Bun__JSBigIntStatFSObjectConstructor(Zig::GlobalObject* globalobject) +{ + return JSValue::encode(globalobject->m_JSStatFSBigIntClassStructure.constructor(globalobject)); +} + +extern "C" JSC::EncodedJSValue Bun__JSStatFSObjectConstructor(Zig::GlobalObject* globalobject) +{ + return JSValue::encode(globalobject->m_JSStatFSClassStructure.constructor(globalobject)); +} + +void JSStatFSPrototype::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +void JSBigIntStatFSPrototype::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +void initJSStatFSClassStructure(JSC::LazyClassStructure::Initializer& init) +{ + auto* prototype = JSStatFSPrototype::create(init.vm, init.global, JSStatFSPrototype::createStructure(init.vm, init.global, init.global->objectPrototype())); + auto* structure = createJSStatFSObjectStructure(init.vm, init.global); + auto* constructor = JSStatFSConstructor::create(init.vm, JSStatFSConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()), prototype); + init.setPrototype(prototype); + init.setStructure(structure); + init.setConstructor(constructor); +} + +void initJSBigIntStatFSClassStructure(JSC::LazyClassStructure::Initializer& init) +{ + auto* prototype = JSBigIntStatFSPrototype::create(init.vm, init.global, JSBigIntStatFSPrototype::createStructure(init.vm, init.global, init.global->objectPrototype())); + auto* structure = createJSBigIntStatFSObjectStructure(init.vm, init.global); + auto* constructor = JSBigIntStatFSConstructor::create(init.vm, JSBigIntStatFSConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()), prototype); + init.setPrototype(prototype); + init.setStructure(structure); + init.setConstructor(constructor); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/NodeFSStatFSBinding.h b/src/bun.js/bindings/NodeFSStatFSBinding.h new file mode 100644 index 0000000000..083fef3aa4 --- /dev/null +++ b/src/bun.js/bindings/NodeFSStatFSBinding.h @@ -0,0 +1,8 @@ +#include "root.h" + +namespace Bun { + +void initJSStatFSClassStructure(JSC::LazyClassStructure::Initializer& init); +void initJSBigIntStatFSClassStructure(JSC::LazyClassStructure::Initializer& init); + +} diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 1df8ff2512..ba4a8ae814 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -180,7 +180,8 @@ #include "JavaScriptCore/RemoteInspectorServer.h" #endif -#include "NodeFSBinding.h" +#include "NodeFSStatBinding.h" +#include "NodeFSStatFSBinding.h" #include "NodeDirent.h" #if !OS(WINDOWS) @@ -3016,6 +3017,16 @@ void GlobalObject::finishCreation(VM& vm) Bun::initJSBigIntStatsClassStructure(init); }); + m_JSStatFSClassStructure.initLater( + [](LazyClassStructure::Initializer& init) { + Bun::initJSStatFSClassStructure(init); + }); + + m_JSStatFSBigIntClassStructure.initLater( + [](LazyClassStructure::Initializer& init) { + Bun::initJSBigIntStatFSClassStructure(init); + }); + m_memoryFootprintStructure.initLater( [](const JSC::LazyProperty::Initializer& init) { init.set( @@ -4063,6 +4074,8 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_memoryFootprintStructure.visit(visitor); thisObject->m_JSStatsClassStructure.visit(visitor); thisObject->m_JSStatsBigIntClassStructure.visit(visitor); + thisObject->m_JSStatFSClassStructure.visit(visitor); + thisObject->m_JSStatFSBigIntClassStructure.visit(visitor); thisObject->m_JSDirentClassStructure.visit(visitor); thisObject->m_NapiClassStructure.visit(visitor); thisObject->m_NapiExternalStructure.visit(visitor); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 7fa6cf7da1..594e7bca5f 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -494,6 +494,8 @@ public: JSC::LazyClassStructure m_JSStatsClassStructure; JSC::LazyClassStructure m_JSStatsBigIntClassStructure; + JSC::LazyClassStructure m_JSStatFSClassStructure; + JSC::LazyClassStructure m_JSStatFSBigIntClassStructure; JSC::LazyClassStructure m_JSDirentClassStructure; JSObject* cryptoObject() const { return m_cryptoObject.getInitializedOnMainThread(this); } diff --git a/src/bun.js/bindings/generated_classes_list.zig b/src/bun.js/bindings/generated_classes_list.zig index 58705a5330..a4eea5ebae 100644 --- a/src/bun.js/bindings/generated_classes_list.zig +++ b/src/bun.js/bindings/generated_classes_list.zig @@ -82,7 +82,4 @@ pub const Classes = struct { pub const S3Client = JSC.WebCore.S3Client; pub const S3Stat = JSC.WebCore.S3Stat; pub const HTMLBundle = JSC.API.HTMLBundle; - - pub const StatFs = JSC.Node.StatFSSmall; - pub const BigIntStatFs = JSC.Node.StatFSBig; }; diff --git a/src/bun.js/node/Stat.zig b/src/bun.js/node/Stat.zig new file mode 100644 index 0000000000..a705fdceb1 --- /dev/null +++ b/src/bun.js/node/Stat.zig @@ -0,0 +1,207 @@ +/// Stats and BigIntStats classes from node:fs +pub fn StatType(comptime big: bool) type { + return struct { + pub usingnamespace bun.New(@This()); + value: bun.Stat, + + const StatTimespec = if (Environment.isWindows) bun.windows.libuv.uv_timespec_t else std.posix.timespec; + const Float = if (big) i64 else f64; + + inline fn toNanoseconds(ts: StatTimespec) u64 { + if (ts.sec < 0) { + return @intCast(@max(bun.timespec.nsSigned(&bun.timespec{ + .sec = @intCast(ts.sec), + .nsec = @intCast(ts.nsec), + }), 0)); + } + + return bun.timespec.ns(&bun.timespec{ + .sec = @intCast(ts.sec), + .nsec = @intCast(ts.nsec), + }); + } + + fn toTimeMS(ts: StatTimespec) Float { + // On windows, Node.js purposefully mis-interprets time values + // > On win32, time is stored in uint64_t and starts from 1601-01-01. + // > libuv calculates tv_sec and tv_nsec from it and converts to signed long, + // > which causes Y2038 overflow. On the other platforms it is safe to treat + // > negative values as pre-epoch time. + const tv_sec = if (Environment.isWindows) @as(u32, @bitCast(ts.sec)) else ts.sec; + const tv_nsec = if (Environment.isWindows) @as(u32, @bitCast(ts.nsec)) else ts.nsec; + if (big) { + const sec: i64 = tv_sec; + const nsec: i64 = tv_nsec; + return @as(i64, sec * std.time.ms_per_s) +| + @as(i64, @divTrunc(nsec, std.time.ns_per_ms)); + } else { + return @floatFromInt(bun.timespec.ms(&bun.timespec{ + .sec = @intCast(tv_sec), + .nsec = @intCast(tv_nsec), + })); + } + } + + pub fn toJS(this: *const @This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { + return statToJS(&this.value, globalObject); + } + + pub fn getConstructor(globalObject: *JSC.JSGlobalObject) JSC.JSValue { + return if (big) Bun__JSBigIntStatsObjectConstructor(globalObject) else Bun__JSStatsObjectConstructor(globalObject); + } + + fn clampedInt64(value: anytype) i64 { + return @intCast(@min(@max(value, 0), std.math.maxInt(i64))); + } + + fn statToJS(stat_: *const bun.Stat, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + const aTime = stat_.atime(); + const mTime = stat_.mtime(); + const cTime = stat_.ctime(); + const dev: i64 = clampedInt64(stat_.dev); + const ino: i64 = clampedInt64(stat_.ino); + const mode: i64 = clampedInt64(stat_.mode); + const nlink: i64 = clampedInt64(stat_.nlink); + const uid: i64 = clampedInt64(stat_.uid); + const gid: i64 = clampedInt64(stat_.gid); + const rdev: i64 = clampedInt64(stat_.rdev); + const size: i64 = clampedInt64(stat_.size); + const blksize: i64 = clampedInt64(stat_.blksize); + const blocks: i64 = clampedInt64(stat_.blocks); + const atime_ms: Float = toTimeMS(aTime); + const mtime_ms: Float = toTimeMS(mTime); + const ctime_ms: Float = toTimeMS(cTime); + const atime_ns: u64 = if (big) toNanoseconds(aTime) else 0; + const mtime_ns: u64 = if (big) toNanoseconds(mTime) else 0; + const ctime_ns: u64 = if (big) toNanoseconds(cTime) else 0; + const birthtime_ms: Float = if (Environment.isLinux) 0 else toTimeMS(stat_.birthtime()); + const birthtime_ns: u64 = if (big and !Environment.isLinux) toNanoseconds(stat_.birthtime()) else 0; + + if (big) { + return Bun__createJSBigIntStatsObject( + globalObject, + dev, + ino, + mode, + nlink, + uid, + gid, + rdev, + size, + blksize, + blocks, + atime_ms, + mtime_ms, + ctime_ms, + birthtime_ms, + atime_ns, + mtime_ns, + ctime_ns, + birthtime_ns, + ); + } + + return Bun__createJSStatsObject( + globalObject, + dev, + ino, + mode, + nlink, + uid, + gid, + rdev, + size, + blksize, + blocks, + atime_ms, + mtime_ms, + ctime_ms, + birthtime_ms, + ); + } + + pub fn init(stat_: *const bun.Stat) @This() { + return @This(){ + .value = stat_.*, + }; + } + }; +} +extern fn Bun__JSBigIntStatsObjectConstructor(*JSC.JSGlobalObject) JSC.JSValue; +extern fn Bun__JSStatsObjectConstructor(*JSC.JSGlobalObject) JSC.JSValue; + +extern fn Bun__createJSStatsObject( + globalObject: *JSC.JSGlobalObject, + dev: i64, + ino: i64, + mode: i64, + nlink: i64, + uid: i64, + gid: i64, + rdev: i64, + size: i64, + blksize: i64, + blocks: i64, + atimeMs: f64, + mtimeMs: f64, + ctimeMs: f64, + birthtimeMs: f64, +) JSC.JSValue; + +extern fn Bun__createJSBigIntStatsObject( + globalObject: *JSC.JSGlobalObject, + dev: i64, + ino: i64, + mode: i64, + nlink: i64, + uid: i64, + gid: i64, + rdev: i64, + size: i64, + blksize: i64, + blocks: i64, + atimeMs: i64, + mtimeMs: i64, + ctimeMs: i64, + birthtimeMs: i64, + atimeNs: u64, + mtimeNs: u64, + ctimeNs: u64, + birthtimeNs: u64, +) JSC.JSValue; + +pub const StatsSmall = StatType(false); +pub const StatsBig = StatType(true); + +/// Union between `Stats` and `BigIntStats` where the type can be decided at runtime +pub const Stats = union(enum) { + big: StatsBig, + small: StatsSmall, + + pub inline fn init(stat_: *const bun.Stat, big: bool) Stats { + if (big) { + return .{ .big = StatsBig.init(stat_) }; + } else { + return .{ .small = StatsSmall.init(stat_) }; + } + } + + pub fn toJSNewlyCreated(this: *const Stats, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + return switch (this.*) { + .big => this.big.toJS(globalObject), + .small => this.small.toJS(globalObject), + }; + } + + pub inline fn toJS(this: *Stats, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + _ = this; + _ = globalObject; + + @compileError("Only use Stats.toJSNewlyCreated() or Stats.toJS() directly on a StatsBig or StatsSmall"); + } +}; + +const bun = @import("root").bun; +const JSC = bun.JSC; +const Environment = bun.Environment; +const std = @import("std"); diff --git a/src/bun.js/node/StatFS.zig b/src/bun.js/node/StatFS.zig new file mode 100644 index 0000000000..7783634459 --- /dev/null +++ b/src/bun.js/node/StatFS.zig @@ -0,0 +1,142 @@ +/// StatFS and BigIntStatFS classes from node:fs +pub fn StatFSType(comptime big: bool) type { + const Int = if (big) i64 else i32; + + return struct { + + // Common fields between Linux and macOS + _fstype: Int, + _bsize: Int, + _blocks: Int, + _bfree: Int, + _bavail: Int, + _files: Int, + _ffree: Int, + + const This = @This(); + + pub fn toJS(this: *const This, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + return statfsToJS(this, globalObject); + } + + fn statfsToJS(this: *const This, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + if (big) { + return Bun__createJSBigIntStatFSObject( + globalObject, + this._fstype, + this._bsize, + this._blocks, + this._bfree, + this._bavail, + this._files, + this._ffree, + ); + } + + return Bun__createJSStatFSObject( + globalObject, + this._fstype, + this._bsize, + this._blocks, + this._bfree, + this._bavail, + this._files, + this._ffree, + ); + } + + pub fn init(statfs_: *const bun.StatFS) This { + const fstype_, const bsize_, const blocks_, const bfree_, const bavail_, const files_, const ffree_ = switch (comptime Environment.os) { + .linux, .mac => .{ + statfs_.f_type, + statfs_.f_bsize, + statfs_.f_blocks, + statfs_.f_bfree, + statfs_.f_bavail, + statfs_.f_files, + statfs_.f_ffree, + }, + .windows => .{ + statfs_.f_type, + statfs_.f_bsize, + statfs_.f_blocks, + statfs_.f_bfree, + statfs_.f_bavail, + statfs_.f_files, + statfs_.f_ffree, + }, + else => @compileError("Unsupported OS"), + }; + return .{ + ._fstype = @truncate(@as(i64, @intCast(fstype_))), + ._bsize = @truncate(@as(i64, @intCast(bsize_))), + ._blocks = @truncate(@as(i64, @intCast(blocks_))), + ._bfree = @truncate(@as(i64, @intCast(bfree_))), + ._bavail = @truncate(@as(i64, @intCast(bavail_))), + ._files = @truncate(@as(i64, @intCast(files_))), + ._ffree = @truncate(@as(i64, @intCast(ffree_))), + }; + } + }; +} + +extern fn Bun__JSBigIntStatFSObjectConstructor(*JSC.JSGlobalObject) JSC.JSValue; +extern fn Bun__JSStatFSObjectConstructor(*JSC.JSGlobalObject) JSC.JSValue; + +extern fn Bun__createJSStatFSObject( + globalObject: *JSC.JSGlobalObject, + fstype: i64, + bsize: i64, + blocks: i64, + bfree: i64, + bavail: i64, + files: i64, + ffree: i64, +) JSC.JSValue; + +extern fn Bun__createJSBigIntStatFSObject( + globalObject: *JSC.JSGlobalObject, + fstype: i64, + bsize: i64, + blocks: i64, + bfree: i64, + bavail: i64, + files: i64, + ffree: i64, +) JSC.JSValue; + +pub const StatFSSmall = StatFSType(false); +pub const StatFSBig = StatFSType(true); + +/// Union between `Stats` and `BigIntStats` where the type can be decided at runtime +pub const StatFS = union(enum) { + big: StatFSBig, + small: StatFSSmall, + + pub inline fn init(stat_: *const bun.StatFS, big: bool) StatFS { + if (big) { + return .{ .big = StatFSBig.init(stat_) }; + } else { + return .{ .small = StatFSSmall.init(stat_) }; + } + } + + pub fn toJSNewlyCreated(this: *const StatFS, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + return switch (this.*) { + .big => |*big| big.toJS(globalObject), + .small => |*small| small.toJS(globalObject), + }; + } + + pub inline fn toJS(this: *StatFS, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + _ = this; + _ = globalObject; + + @compileError("Only use Stats.toJSNewlyCreated() or Stats.toJS() directly on a StatsBig or StatsSmall"); + } +}; + +const bun = @import("root").bun; +const JSC = bun.JSC; +const Environment = bun.Environment; +const std = @import("std"); diff --git a/src/bun.js/node/node.classes.ts b/src/bun.js/node/node.classes.ts index 07b230411f..72872c1822 100644 --- a/src/bun.js/node/node.classes.ts +++ b/src/bun.js/node/node.classes.ts @@ -314,72 +314,4 @@ export default [ Stats: { getter: "getStats" }, }, }), - define({ - name: "StatFs", - construct: true, - finalize: true, - klass: {}, - JSType: "0b11101110", - - supportsObjectCreate: true, - - // TODO: make these own properties to pass test-fs-statfs.js - proto: { - type: { - getter: "fstype", - }, - bsize: { - getter: "bsize", - }, - blocks: { - getter: "blocks", - }, - bfree: { - getter: "bfree", - }, - bavail: { - getter: "bavail", - }, - files: { - getter: "files", - }, - ffree: { - getter: "ffree", - }, - }, - }), - define({ - name: "BigIntStatFs", - construct: true, - finalize: true, - klass: {}, - JSType: "0b11101110", - - supportsObjectCreate: true, - - // TODO: make these own properties to pass test-fs-statfs.js - proto: { - type: { - getter: "fstype", - }, - bsize: { - getter: "bsize", - }, - blocks: { - getter: "blocks", - }, - bfree: { - getter: "bfree", - }, - bavail: { - getter: "bavail", - }, - files: { - getter: "files", - }, - ffree: { - getter: "ffree", - }, - }, - }), ]; diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig index 06bdb667f0..d0e65e7409 100644 --- a/src/bun.js/node/node_fs.zig +++ b/src/bun.js/node/node_fs.zig @@ -3850,7 +3850,7 @@ pub const NodeFS = struct { pub fn fstat(_: *NodeFS, args: Arguments.Fstat, _: Flavor) Maybe(Return.Fstat) { return switch (Syscall.fstat(args.fd)) { - .result => |result| .{ .result = .init(result, args.big_int) }, + .result => |*result| .{ .result = .init(result, args.big_int) }, .err => |err| .{ .err = err }, }; } @@ -3927,7 +3927,7 @@ pub const NodeFS = struct { pub fn lstat(this: *NodeFS, args: Arguments.Lstat, _: Flavor) Maybe(Return.Lstat) { return switch (Syscall.lstat(args.path.sliceZ(&this.sync_error_buf))) { - .result => |result| Maybe(Return.Lstat){ .result = .{ .stats = .init(result, args.big_int) } }, + .result => |*result| Maybe(Return.Lstat){ .result = .{ .stats = .init(result, args.big_int) } }, .err => |err| brk: { if (!args.throw_if_no_entry and err.getErrno() == .NOENT) { return Maybe(Return.Lstat){ .result = .{ .not_found = {} } }; @@ -4234,7 +4234,8 @@ pub const NodeFS = struct { .from_libuv = true, } }; } - return Maybe(Return.StatFS).initResult(Return.StatFS.init(req.ptrAs(*align(1) bun.StatFS).*, args.big_int)); + const statfs_ = req.ptrAs(*align(1) bun.StatFS).*; + return Maybe(Return.StatFS).initResult(Return.StatFS.init(&statfs_, args.big_int)); } pub fn openDir(_: *NodeFS, _: Arguments.OpenDir, _: Flavor) Maybe(Return.OpenDir) { @@ -5748,7 +5749,7 @@ pub const NodeFS = struct { pub fn statfs(this: *NodeFS, args: Arguments.StatFS, _: Flavor) Maybe(Return.StatFS) { return switch (Syscall.statfs(args.path.sliceZ(&this.sync_error_buf))) { - .result => |result| Maybe(Return.StatFS){ .result = Return.StatFS.init(result, args.big_int) }, + .result => |*result| Maybe(Return.StatFS){ .result = Return.StatFS.init(result, args.big_int) }, .err => |err| Maybe(Return.StatFS){ .err = err }, }; } @@ -5756,13 +5757,13 @@ pub const NodeFS = struct { pub fn stat(this: *NodeFS, args: Arguments.Stat, _: Flavor) Maybe(Return.Stat) { const path = args.path.sliceZ(&this.sync_error_buf); if (bun.StandaloneModuleGraph.get()) |graph| { - if (graph.stat(path)) |result| { + if (graph.stat(path)) |*result| { return .{ .result = .{ .stats = .init(result, args.big_int) } }; } } return switch (Syscall.stat(path)) { - .result => |result| .{ + .result => |*result| .{ .result = .{ .stats = .init(result, args.big_int) }, }, .err => |err| brk: { diff --git a/src/bun.js/node/node_fs_stat_watcher.zig b/src/bun.js/node/node_fs_stat_watcher.zig index b059898636..646571c18c 100644 --- a/src/bun.js/node/node_fs_stat_watcher.zig +++ b/src/bun.js/node/node_fs_stat_watcher.zig @@ -24,7 +24,7 @@ const StatsBig = bun.JSC.Node.StatsBig; const log = bun.Output.scoped(.StatWatcher, false); -fn statToJSStats(globalThis: *JSC.JSGlobalObject, stats: bun.Stat, bigint: bool) JSC.JSValue { +fn statToJSStats(globalThis: *JSC.JSGlobalObject, stats: *const bun.Stat, bigint: bool) JSC.JSValue { if (bigint) { return StatsBig.init(stats).toJS(globalThis); } else { @@ -376,7 +376,7 @@ pub const StatWatcher = struct { return; } - const jsvalue = statToJSStats(this.globalThis, this.last_stat, this.bigint); + const jsvalue = statToJSStats(this.globalThis, &this.last_stat, this.bigint); this.last_jsvalue = JSC.Strong.create(jsvalue, this.globalThis); const vm = this.globalThis.bunVM(); @@ -389,7 +389,7 @@ pub const StatWatcher = struct { return; } - const jsvalue = statToJSStats(this.globalThis, this.last_stat, this.bigint); + const jsvalue = statToJSStats(this.globalThis, &this.last_stat, this.bigint); this.last_jsvalue = JSC.Strong.create(jsvalue, this.globalThis); const vm = this.globalThis.bunVM(); @@ -428,7 +428,7 @@ pub const StatWatcher = struct { /// After a restat found the file changed, this calls the listener function. pub fn swapAndCallListenerOnMainThread(this: *StatWatcher) void { const prev_jsvalue = this.last_jsvalue.swap(); - const current_jsvalue = statToJSStats(this.globalThis, this.last_stat, this.bigint); + const current_jsvalue = statToJSStats(this.globalThis, &this.last_stat, this.bigint); this.last_jsvalue.set(this.globalThis, current_jsvalue); _ = StatWatcher.listenerGetCached(this.js_this).?.call( diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index b4facc1e52..8c0b58f3c6 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -1604,209 +1604,6 @@ pub const FileSystemFlags = enum(c_int) { } }; -/// Stats and BigIntStats classes from node:fs -pub fn StatType(comptime big: bool) type { - return struct { - pub usingnamespace bun.New(@This()); - value: bun.Stat, - - const StatTimespec = if (Environment.isWindows) bun.windows.libuv.uv_timespec_t else std.posix.timespec; - const Float = if (big) i64 else f64; - - inline fn toNanoseconds(ts: StatTimespec) u64 { - if (ts.sec < 0) { - return @intCast(@max(bun.timespec.nsSigned(&bun.timespec{ - .sec = @intCast(ts.sec), - .nsec = @intCast(ts.nsec), - }), 0)); - } - - return bun.timespec.ns(&bun.timespec{ - .sec = @intCast(ts.sec), - .nsec = @intCast(ts.nsec), - }); - } - - fn toTimeMS(ts: StatTimespec) Float { - // On windows, Node.js purposefully mis-interprets time values - // > On win32, time is stored in uint64_t and starts from 1601-01-01. - // > libuv calculates tv_sec and tv_nsec from it and converts to signed long, - // > which causes Y2038 overflow. On the other platforms it is safe to treat - // > negative values as pre-epoch time. - const tv_sec = if (Environment.isWindows) @as(u32, @bitCast(ts.sec)) else ts.sec; - const tv_nsec = if (Environment.isWindows) @as(u32, @bitCast(ts.nsec)) else ts.nsec; - if (big) { - const sec: i64 = tv_sec; - const nsec: i64 = tv_nsec; - return @as(i64, sec * std.time.ms_per_s) +| - @as(i64, @divTrunc(nsec, std.time.ns_per_ms)); - } else { - return @floatFromInt(bun.timespec.ms(&bun.timespec{ - .sec = @intCast(tv_sec), - .nsec = @intCast(tv_nsec), - })); - } - } - - pub fn toJS(this: *const @This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - return statToJS(&this.value, globalObject); - } - - pub fn getConstructor(globalObject: *JSC.JSGlobalObject) JSC.JSValue { - return if (big) Bun__JSBigIntStatsObjectConstructor(globalObject) else Bun__JSStatsObjectConstructor(globalObject); - } - - fn clampedInt64(value: anytype) i64 { - return @intCast(@min(@max(value, 0), std.math.maxInt(i64))); - } - - fn statToJS(stat_: *const bun.Stat, globalObject: *JSC.JSGlobalObject) JSC.JSValue { - const aTime = stat_.atime(); - const mTime = stat_.mtime(); - const cTime = stat_.ctime(); - const dev: i64 = clampedInt64(stat_.dev); - const ino: i64 = clampedInt64(stat_.ino); - const mode: i64 = clampedInt64(stat_.mode); - const nlink: i64 = clampedInt64(stat_.nlink); - const uid: i64 = clampedInt64(stat_.uid); - const gid: i64 = clampedInt64(stat_.gid); - const rdev: i64 = clampedInt64(stat_.rdev); - const size: i64 = clampedInt64(stat_.size); - const blksize: i64 = clampedInt64(stat_.blksize); - const blocks: i64 = clampedInt64(stat_.blocks); - const atime_ms: Float = toTimeMS(aTime); - const mtime_ms: Float = toTimeMS(mTime); - const ctime_ms: Float = toTimeMS(cTime); - const atime_ns: u64 = if (big) toNanoseconds(aTime) else 0; - const mtime_ns: u64 = if (big) toNanoseconds(mTime) else 0; - const ctime_ns: u64 = if (big) toNanoseconds(cTime) else 0; - const birthtime_ms: Float = if (Environment.isLinux) 0 else toTimeMS(stat_.birthtime()); - const birthtime_ns: u64 = if (big and !Environment.isLinux) toNanoseconds(stat_.birthtime()) else 0; - - if (big) { - return Bun__createJSBigIntStatsObject( - globalObject, - dev, - ino, - mode, - nlink, - uid, - gid, - rdev, - size, - blksize, - blocks, - atime_ms, - mtime_ms, - ctime_ms, - birthtime_ms, - atime_ns, - mtime_ns, - ctime_ns, - birthtime_ns, - ); - } - - return Bun__createJSStatsObject( - globalObject, - dev, - ino, - mode, - nlink, - uid, - gid, - rdev, - size, - blksize, - blocks, - atime_ms, - mtime_ms, - ctime_ms, - birthtime_ms, - ); - } - - pub fn init(stat_: bun.Stat) @This() { - return @This(){ - .value = stat_, - }; - } - }; -} -extern fn Bun__JSBigIntStatsObjectConstructor(*JSC.JSGlobalObject) JSC.JSValue; -extern fn Bun__JSStatsObjectConstructor(*JSC.JSGlobalObject) JSC.JSValue; - -extern fn Bun__createJSStatsObject( - globalObject: *JSC.JSGlobalObject, - dev: i64, - ino: i64, - mode: i64, - nlink: i64, - uid: i64, - gid: i64, - rdev: i64, - size: i64, - blksize: i64, - blocks: i64, - atimeMs: f64, - mtimeMs: f64, - ctimeMs: f64, - birthtimeMs: f64, -) JSC.JSValue; - -extern fn Bun__createJSBigIntStatsObject( - globalObject: *JSC.JSGlobalObject, - dev: i64, - ino: i64, - mode: i64, - nlink: i64, - uid: i64, - gid: i64, - rdev: i64, - size: i64, - blksize: i64, - blocks: i64, - atimeMs: i64, - mtimeMs: i64, - ctimeMs: i64, - birthtimeMs: i64, - atimeNs: u64, - mtimeNs: u64, - ctimeNs: u64, - birthtimeNs: u64, -) JSC.JSValue; - -pub const StatsSmall = StatType(false); -pub const StatsBig = StatType(true); - -/// Union between `Stats` and `BigIntStats` where the type can be decided at runtime -pub const Stats = union(enum) { - big: StatsBig, - small: StatsSmall, - - pub inline fn init(stat_: bun.Stat, big: bool) Stats { - if (big) { - return .{ .big = StatsBig.init(stat_) }; - } else { - return .{ .small = StatsSmall.init(stat_) }; - } - } - - pub fn toJSNewlyCreated(this: *const Stats, globalObject: *JSC.JSGlobalObject) JSC.JSValue { - return switch (this.*) { - .big => this.big.toJS(globalObject), - .small => this.small.toJS(globalObject), - }; - } - - pub inline fn toJS(this: *Stats, globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = this; - _ = globalObject; - - @compileError("Only use Stats.toJSNewlyCreated() or Stats.toJS() directly on a StatsBig or StatsSmall"); - } -}; - /// A class representing a directory stream. /// /// Created by {@link opendir}, {@link opendirSync}, or `fsPromises.opendir()`. @@ -2185,142 +1982,15 @@ comptime { std.testing.refAllDecls(Process); } -/// StatFS and BigIntStatFS classes from node:fs -pub fn StatFSType(comptime big: bool) type { - const Int = if (big) i64 else i32; - - return extern struct { - pub usingnamespace if (big) JSC.Codegen.JSBigIntStatFs else JSC.Codegen.JSStatFs; - pub usingnamespace bun.New(@This()); - - // Common fields between Linux and macOS - _fstype: Int, - _bsize: Int, - _blocks: Int, - _bfree: Int, - _bavail: Int, - _files: Int, - _ffree: Int, - - const This = @This(); - - const PropertyGetter = fn (this: *This, globalObject: *JSC.JSGlobalObject) JSC.JSValue; - - fn getter(comptime field: std.meta.FieldEnum(This)) PropertyGetter { - return struct { - pub fn callback(this: *This, globalObject: *JSC.JSGlobalObject) JSC.JSValue { - const value = @field(this, @tagName(field)); - const Type = @TypeOf(value); - if (comptime big and @typeInfo(Type) == .int) { - return JSC.JSValue.fromInt64NoTruncate(globalObject, value); - } - - const result = JSC.JSValue.jsDoubleNumber(@as(f64, @floatFromInt(value))); - if (Environment.isDebug) { - bun.assert_eql(result.asNumber(), @as(f64, @floatFromInt(value))); - } - return result; - } - }.callback; - } - - pub const fstype = getter(._fstype); - pub const bsize = getter(._bsize); - pub const blocks = getter(._blocks); - pub const bfree = getter(._bfree); - pub const bavail = getter(._bavail); - pub const files = getter(._files); - pub const ffree = getter(._ffree); - - pub fn finalize(this: *This) void { - this.destroy(); - } - - pub fn init(statfs_: bun.StatFS) This { - const fstype_, const bsize_, const blocks_, const bfree_, const bavail_, const files_, const ffree_ = switch (comptime Environment.os) { - .linux, .mac => .{ - statfs_.f_type, - statfs_.f_bsize, - statfs_.f_blocks, - statfs_.f_bfree, - statfs_.f_bavail, - statfs_.f_files, - statfs_.f_ffree, - }, - .windows => .{ - statfs_.f_type, - statfs_.f_bsize, - statfs_.f_blocks, - statfs_.f_bfree, - statfs_.f_bavail, - statfs_.f_files, - statfs_.f_ffree, - }, - else => @compileError("Unsupported OS"), - }; - return .{ - ._fstype = @truncate(@as(i64, @intCast(fstype_))), - ._bsize = @truncate(@as(i64, @intCast(bsize_))), - ._blocks = @truncate(@as(i64, @intCast(blocks_))), - ._bfree = @truncate(@as(i64, @intCast(bfree_))), - ._bavail = @truncate(@as(i64, @intCast(bavail_))), - ._files = @truncate(@as(i64, @intCast(files_))), - ._ffree = @truncate(@as(i64, @intCast(ffree_))), - }; - } - - pub fn constructor(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!*This { - if (big) { - return globalObject.throwInvalidArguments("BigIntStatFS is not a constructor", .{}); - } - - var args = callFrame.arguments(); - - const this = This.new(.{ - ._fstype = if (args.len > 0 and args[0].isNumber()) args[0].toInt32() else 0, - ._bsize = if (args.len > 1 and args[1].isNumber()) args[1].toInt32() else 0, - ._blocks = if (args.len > 2 and args[2].isNumber()) args[2].toInt32() else 0, - ._bfree = if (args.len > 3 and args[3].isNumber()) args[3].toInt32() else 0, - ._bavail = if (args.len > 4 and args[4].isNumber()) args[4].toInt32() else 0, - ._files = if (args.len > 5 and args[5].isNumber()) args[5].toInt32() else 0, - ._ffree = if (args.len > 6 and args[6].isNumber()) args[6].toInt32() else 0, - }); - - return this; - } - }; -} - -pub const StatFSSmall = StatFSType(false); -pub const StatFSBig = StatFSType(true); - -/// Union between `Stats` and `BigIntStats` where the type can be decided at runtime -pub const StatFS = union(enum) { - big: StatFSBig, - small: StatFSSmall, - - pub inline fn init(stat_: bun.StatFS, big: bool) StatFS { - if (big) { - return .{ .big = StatFSBig.init(stat_) }; - } else { - return .{ .small = StatFSSmall.init(stat_) }; - } - } - - pub fn toJSNewlyCreated(this: *const StatFS, globalObject: *JSC.JSGlobalObject) JSC.JSValue { - return switch (this.*) { - .big => StatFSBig.new(this.big).toJS(globalObject), - .small => StatFSSmall.new(this.small).toJS(globalObject), - }; - } - - pub inline fn toJS(this: *StatFS, globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = this; - _ = globalObject; - - @compileError("Only use Stats.toJSNewlyCreated() or Stats.toJS() directly on a StatsBig or StatsSmall"); - } -}; - pub const uid_t = if (Environment.isPosix) std.posix.uid_t else bun.windows.libuv.uv_uid_t; pub const gid_t = if (Environment.isPosix) std.posix.gid_t else bun.windows.libuv.uv_gid_t; + +const stat = @import("./Stat.zig"); +pub const Stats = stat.Stats; +pub const StatsBig = stat.StatsBig; +pub const StatsSmall = stat.StatsSmall; + +const statfs = @import("./StatFS.zig"); +pub const StatFSSmall = statfs.StatFSSmall; +pub const StatFSBig = statfs.StatFSBig; +pub const StatFS = statfs.StatFS; diff --git a/test/internal/ban-words.test.ts b/test/internal/ban-words.test.ts index 5d84fc16fb..9334d99768 100644 --- a/test/internal/ban-words.test.ts +++ b/test/internal/ban-words.test.ts @@ -27,8 +27,8 @@ const words: Record "alloc.ptr !=": { reason: "The std.mem.Allocator context pointer can be undefined, which makes this comparison undefined behavior" }, "== alloc.ptr": { reason: "The std.mem.Allocator context pointer can be undefined, which makes this comparison undefined behavior" }, "!= alloc.ptr": { reason: "The std.mem.Allocator context pointer can be undefined, which makes this comparison undefined behavior" }, - [String.raw`: [a-zA-Z0-9_\.\*\?\[\]\(\)]+ = undefined,`]: { reason: "Do not default a struct field to undefined", limit: 246, regex: true }, - "usingnamespace": { reason: "This brings Bun away from incremental / faster compile times.", limit: 494 }, + [String.raw`: [a-zA-Z0-9_\.\*\?\[\]\(\)]+ = undefined,`]: { reason: "Do not default a struct field to undefined", limit: 244, regex: true }, + "usingnamespace": { reason: "This brings Bun away from incremental / faster compile times.", limit: 492 }, }; const words_keys = [...Object.keys(words)]; diff --git a/test/js/node/test/parallel/test-fs-statfs.js b/test/js/node/test/parallel/test-fs-statfs.js new file mode 100644 index 0000000000..5fd34f215b --- /dev/null +++ b/test/js/node/test/parallel/test-fs-statfs.js @@ -0,0 +1,59 @@ +'use strict'; +const common = require('../common'); +const assert = require('node:assert'); +const fs = require('node:fs'); + +function verifyStatFsObject(statfs, isBigint = false) { + const valueType = isBigint ? 'bigint' : 'number'; + + [ + 'type', 'bsize', 'blocks', 'bfree', 'bavail', 'files', 'ffree', + ].forEach((k) => { + assert.ok(Object.hasOwn(statfs, k)); + assert.strictEqual(typeof statfs[k], valueType, + `${k} should be a ${valueType}`); + }); +} + +fs.statfs(__filename, common.mustSucceed(function(stats) { + verifyStatFsObject(stats); + assert.strictEqual(this, undefined); +})); + +fs.statfs(__filename, { bigint: true }, function(err, stats) { + assert.ifError(err); + verifyStatFsObject(stats, true); + assert.strictEqual(this, undefined); +}); + +// Synchronous +{ + const statFsObj = fs.statfsSync(__filename); + verifyStatFsObject(statFsObj); +} + +// Synchronous Bigint +{ + const statFsBigIntObj = fs.statfsSync(__filename, { bigint: true }); + verifyStatFsObject(statFsBigIntObj, true); +} + +[false, 1, {}, [], null, undefined].forEach((input) => { + assert.throws( + () => fs.statfs(input, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.throws( + () => fs.statfsSync(input), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); +}); + +// Should not throw an error +fs.statfs(__filename, undefined, common.mustCall());