Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com>
This commit is contained in:
Jarred Sumner
2025-03-31 02:15:27 -07:00
committed by GitHub
parent c3e2bf0fc4
commit c3be6732d1
16 changed files with 933 additions and 436 deletions

View File

@@ -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<JSGlobalObject, Structure>` inst
1. Add the `JSC::LazyProperty<JSGlobalObject, Structure>` 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<JSC::JSGlobalObject, JSC::Structure>::Initializer& init) {
init.set(Bun::initMyStructure(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.owner)));
});
init.set(Bun::initMyStructure(init.vm, reinterpret_cast<Zig::GlobalObject\*>(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);
}

View File

@@ -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 <JavaScriptCore/InternalFunction.h>
#include <JavaScriptCore/JSGlobalObject.h>
#include <JavaScriptCore/Identifier.h>
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/JSObject.h>
#include <JavaScriptCore/Structure.h>
#include <JavaScriptCore/PropertyNameArray.h>
#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<bool isBigInt>
Structure* getStatFSStructure(Zig::GlobalObject* globalObject)
{
if (isBigInt) {
return globalObject->m_JSStatFSBigIntClassStructure.getInitializedOnMainThread(globalObject);
}
return globalObject->m_JSStatFSClassStructure.getInitializedOnMainThread(globalObject);
}
template<bool isBigInt>
JSObject* getStatFSPrototype(Zig::GlobalObject* globalObject)
{
if (isBigInt) {
return globalObject->m_JSStatFSBigIntClassStructure.prototypeInitializedOnMainThread(globalObject);
}
return globalObject->m_JSStatFSClassStructure.prototypeInitializedOnMainThread(globalObject);
}
template<bool isBigInt>
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<JSStatFSPrototype>(vm)) JSStatFSPrototype(vm, structure);
prototype->finishCreation(vm);
return prototype;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
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<JSBigIntStatFSPrototype>(vm)) JSBigIntStatFSPrototype(vm, structure);
prototype->finishCreation(vm);
return prototype;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
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<JSStatFSConstructor>(vm)) JSStatFSConstructor(vm, structure);
constructor->finishCreation(vm, prototype);
return constructor;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
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<JSBigIntStatFSConstructor>(vm)) JSBigIntStatFSConstructor(vm, structure);
constructor->finishCreation(vm, prototype);
return constructor;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
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<false>(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<true>(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<bool isBigInt>
inline JSValue callJSStatFSFunction(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame)
{
auto& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* structure = getStatFSStructure<isBigInt>(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<bool isBigInt>
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<isBigInt>(globalObject);
auto* constructor = getStatFSConstructor<isBigInt>(globalObject);
JSObject* newTarget = asObject(callFrame->newTarget());
if (constructor != newTarget) {
auto scope = DECLARE_THROW_SCOPE(vm);
auto* functionGlobalObject = reinterpret_cast<Zig::GlobalObject*>(
// ShadowRealm functions belong to a different global object.
getFunctionRealm(lexicalGlobalObject, newTarget));
RETURN_IF_EXCEPTION(scope, {});
structure = InternalFunction::createSubclassStructure(
lexicalGlobalObject,
newTarget,
getStatFSStructure<isBigInt>(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<false>(lexicalGlobalObject, callFrame));
}
JSC_DEFINE_HOST_FUNCTION(constructBigIntStatFS, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
return JSValue::encode(constructJSStatFSObject<true>(lexicalGlobalObject, callFrame));
}
JSC_DEFINE_HOST_FUNCTION(callStatFS, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
return JSValue::encode(callJSStatFSFunction<false>(lexicalGlobalObject, callFrame));
}
JSC_DEFINE_HOST_FUNCTION(callBigIntStatFS, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
return JSValue::encode(callJSStatFSFunction<true>(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

View File

@@ -0,0 +1,8 @@
#include "root.h"
namespace Bun {
void initJSStatFSClassStructure(JSC::LazyClassStructure::Initializer& init);
void initJSBigIntStatFSClassStructure(JSC::LazyClassStructure::Initializer& init);
}

View File

@@ -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<JSC::JSGlobalObject, Structure>::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);

View File

@@ -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); }

View File

@@ -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;
};

207
src/bun.js/node/Stat.zig Normal file
View File

@@ -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");

142
src/bun.js/node/StatFS.zig Normal file
View File

@@ -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");

View File

@@ -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",
},
},
}),
];

View File

@@ -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: {

View File

@@ -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(

View File

@@ -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;

View File

@@ -27,8 +27,8 @@ const words: Record<string, { reason: string; limit?: number; regex?: boolean }>
"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)];

View File

@@ -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());