Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 4 additions & 18 deletions src/jsc/bindings/BunObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "GeneratedBunObject.h"
#include "JavaScriptCore/BunV8HeapSnapshotBuilder.h"
#include "BunObjectModule.h"
#include "_NativeModule.h"
#include "JSCookie.h"
#include "JSCookieMap.h"
#include "Secrets.h"
Expand Down Expand Up @@ -1151,9 +1152,6 @@ JSC::JSObject* createBunObject(VM& vm, JSObject* globalObject)
} // namespace Bun

namespace Zig {
// Every export except `default` is declared without a value: JSC reads `Bun[name]` the first time
// something binds to it (SyntheticModuleRecord::materializeLazyExport), so importing the module does
// not run the PropertyCallbacks in bunObjectTable, most of which construct a class or load a builtin.
JSC::JSObject* generateNativeModule_BunObject(JSC::JSGlobalObject* lexicalGlobalObject,
JSC::Identifier moduleKey,
Vector<JSC::Identifier, 4>& exportNames,
Expand All @@ -1164,25 +1162,13 @@ JSC::JSObject* generateNativeModule_BunObject(JSC::JSGlobalObject* lexicalGlobal
auto scope = DECLARE_THROW_SCOPE(vm);
auto* object = globalObject->bunObject();

// Static table entries are listed whether or not they have been reified.
// Static table entries are listed whether or not they have been reified, so this is the same export
// list that reifying them all used to produce, minus the cost of constructing every one of them.
PropertyNameArrayBuilder propertyNames(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
object->getOwnNonIndexPropertyNames(globalObject, propertyNames, DontEnumPropertiesMode::Exclude);
RETURN_IF_EXCEPTION(scope, nullptr);

exportNames.reserveCapacity(propertyNames.size() + 1);
exportValues.ensureCapacity(propertyNames.size() + 1);

exportNames.append(vm.propertyNames->defaultKeyword);
exportValues.append(object);

for (const auto& propertyName : propertyNames) {
if (propertyName == vm.propertyNames->defaultKeyword) [[unlikely]]
continue;
exportNames.append(propertyName);
exportValues.append(JSValue());
}

return object;
return exportObjectProperties(vm, object, propertyNames, exportNames, exportValues);
}

} // namespace Zig
12 changes: 6 additions & 6 deletions src/jsc/modules/NativeModuleList.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
macro("abort-controller"_s, AbortControllerModule)

#define BUN_FOREACH_ESM_NATIVE_MODULE(macro) \
BUN_FOREACH_ESM_AND_CJS_NATIVE_MODULE(macro) \
macro("node:module"_s, NodeModule) \
macro("node:process"_s, NodeProcess)
BUN_FOREACH_ESM_AND_CJS_NATIVE_MODULE(macro)

// Generators with JSC::SyntheticSourceProvider::LazySyntheticSourceGenerator's signature: they may
// declare exports without a value and return the object JSC reads those exports from on first binding.
// src/codegen/internal-module-registry-scanner.ts numbers native modules by their order in this file,
// so these stay after the lists above.
// declare exports without a value and return the object JSC reads those exports from on first binding
// (see exportObjectProperties in _NativeModule.h). src/codegen/internal-module-registry-scanner.ts
// numbers native modules by their order in this file, so these stay after the list above, in this order.
#define BUN_FOREACH_LAZY_ESM_NATIVE_MODULE(macro) \
macro("node:module"_s, NodeModule) \
macro("node:process"_s, NodeProcess) \
macro("bun"_s, BunObject)

#define BUN_FOREACH_CJS_NATIVE_MODULE(macro) \
Expand Down
42 changes: 8 additions & 34 deletions src/jsc/modules/NodeModuleModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1219,48 +1219,22 @@ JSC::JSValue createStreamIterEnabledFlag(Zig::GlobalObject*)
} // namespace Bun

namespace Zig {
void generateNativeModule_NodeModule(JSC::JSGlobalObject* lexicalGlobalObject,
JSC::JSObject* generateNativeModule_NodeModule(JSC::JSGlobalObject* lexicalGlobalObject,
JSC::Identifier moduleKey,
Vector<JSC::Identifier, 4>& exportNames,
JSC::MarkedArgumentBuffer& exportValues)
{
Zig::GlobalObject* globalObject = defaultGlobalObject(lexicalGlobalObject);
auto& vm = JSC::getVM(globalObject);
auto topExceptionScope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
auto* constructor = globalObject->m_nodeModuleConstructor.getInitializedOnMainThread(globalObject);
// Don't bulk-reifyAllStaticProperties here. JSObject::reifyAllStaticProperties
// walks every PropertyCallbackAttribute back-to-back without an exception
// check between them, and several of our callbacks (getBuiltinModulesObject,
// getGlobalPathsObject, …) call constructArray/constructEmptyArray which
// open a ThrowScope at the same recursion depth as the next callback's
// ThrowScope — that trips the exception-check verifier on the synthetic
// ESM path (BUN_JSC_validateExceptionChecks=1). The loop below already
// does constructor->get(property) per-export, which lazy-reifies one entry
// at a time inside JSObject::get's own ThrowScope and is checked
// immediately after.

exportNames.reserveCapacity(Bun::countof(Bun::nodeModuleObjectTableValues) + 1);
exportValues.ensureCapacity(Bun::countof(Bun::nodeModuleObjectTableValues) + 1);

exportNames.append(vm.propertyNames->defaultKeyword);
exportValues.append(constructor);

for (unsigned i = 0; i < Bun::countof(Bun::nodeModuleObjectTableValues); ++i) {
const auto& entry = Bun::nodeModuleObjectTableValues[i];
const auto& property = Identifier::fromString(vm, entry.m_key);
JSValue value = constructor->get(globalObject, property);

if (topExceptionScope.exception()) [[unlikely]] {
// A termination (worker terminate() mid-import) cannot be cleared:
// stop the walk and leave it pending for the loader.
if (!topExceptionScope.tryClearException())
return;
value = jsUndefined();
}

exportNames.append(property);
exportValues.append(value);
}
// The exports are the static table's entries, not the constructor's own properties (`length`, `name`,
// whatever user code assigned onto Module).
PropertyNameArrayBuilder properties(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
for (const auto& entry : Bun::nodeModuleObjectTableValues)
properties.add(Identifier::fromString(vm, entry.m_key));

return exportObjectProperties(vm, constructor, properties, exportNames, exportValues);
}

} // namespace Zig
6 changes: 3 additions & 3 deletions src/jsc/modules/NodeModuleModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ JSC::JSValue resolveLookupPaths(JSC::JSGlobalObject* globalObject, String reques

namespace Zig {

void generateNativeModule_NodeModule(
JSC::JSGlobalObject *lexicalGlobalObject, JSC::Identifier moduleKey,
JSC::JSObject *generateNativeModule_NodeModule(
JSC::JSGlobalObject *lexicalGlobalObject, JSC::Identifier moduleKey,
Vector<JSC::Identifier, 4> &exportNames,
JSC::MarkedArgumentBuffer &exportValues);
JSC::MarkedArgumentBuffer &exportValues);


} // namespace Zig
35 changes: 4 additions & 31 deletions src/jsc/modules/NodeProcessModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,19 @@

namespace Zig {

DEFINE_NATIVE_MODULE(NodeProcess)
DEFINE_LAZY_NATIVE_MODULE(NodeProcess)
{
auto& vm = lexicalGlobalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);

Bun::Process* process = globalObject->processObject();
// Don't bulk-reifyAllStaticProperties here (see generateNativeModule_NodeModule
// for the long version). It runs every PropertyCallback back-to-back without an
// exception check in between, which trips BUN_JSC_validateExceptionChecks=1.
// The per-export get() below lazy-reifies one property at a time inside
// JSObject::get's own checked ThrowScope.

// The whole prototype chain: the EventEmitter methods are exports of this module too.
PropertyNameArrayBuilder properties(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
process->getPropertyNames(globalObject, properties, DontEnumPropertiesMode::Exclude);
RETURN_IF_EXCEPTION(scope, );

exportNames.append(vm.propertyNames->defaultKeyword);
exportValues.append(process);

for (auto& entry : properties.releaseData()->propertyNameVector()) {
if (entry == vm.propertyNames->defaultKeyword) {
// skip because it's already on the default
// export (the Process object itself)
continue;
}

auto topExceptionScope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
JSValue result = process->get(globalObject, entry);
if (topExceptionScope.exception()) {
// A getter that throws exports undefined; a termination (worker
// terminate() mid-import) cannot be cleared: stop, leave it pending.
if (!topExceptionScope.tryClearException())
return;
result = jsUndefined();
}
RETURN_IF_EXCEPTION(scope, nullptr);

exportNames.append(entry);
exportValues.append(result);
}
return exportObjectProperties(vm, process, properties, exportNames, exportValues);
}

} // namespace Zig
37 changes: 37 additions & 0 deletions src/jsc/modules/_NativeModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "JSBuffer.h"
#include <JavaScriptCore/JSGlobalObject.h>
#include <JavaScriptCore/ObjectConstructor.h>
#include <JavaScriptCore/PropertyNameArray.h>
#include "ZigGlobalObject.h"
#include "NativeModuleList.h"

Expand Down Expand Up @@ -57,6 +58,12 @@
JSC::JSGlobalObject *lexicalGlobalObject, JSC::Identifier moduleKey, \
Vector<JSC::Identifier, 4> &exportNames, \
JSC::MarkedArgumentBuffer &exportValues)
// For modules in BUN_FOREACH_LAZY_ESM_NATIVE_MODULE; the body usually ends in exportObjectProperties().
#define DEFINE_LAZY_NATIVE_MODULE(name) \
inline JSC::JSObject *generateNativeModule_##name( \
JSC::JSGlobalObject *lexicalGlobalObject, JSC::Identifier moduleKey, \
Vector<JSC::Identifier, 4> &exportNames, \
JSC::MarkedArgumentBuffer &exportValues)

#define INIT_NATIVE_MODULE(slot, numberOfExportNames) \
Zig::GlobalObject *globalObject = \
Expand Down Expand Up @@ -134,4 +141,34 @@ JSC::JSObject* generateNativeModule_##enumName( \
Vector<JSC::Identifier, 4> &exportNames, \
JSC::MarkedArgumentBuffer &exportValues);
BUN_FOREACH_LAZY_ESM_NATIVE_MODULE(FORWARD_DECL_LAZY_GENERATOR)

// The lazy modules each mirror an object that already exists (the Bun object, process, the Module
// constructor): `default` is the object and each of propertyNames is an export. A value that is
// already stored on the object is exported as is. Anything else, i.e. a static table entry nothing
// has read yet, an accessor, or an inherited property, is declared without a value, and JSC reads
// object[name] when something first binds to it, so loading the module does not construct the
// object's lazy properties. Returns the object, as the LazySyntheticSourceGenerator contract wants.
inline JSC::JSObject *exportObjectProperties(
JSC::VM &vm, JSC::JSObject *object,
const JSC::PropertyNameArrayBuilder &propertyNames,
Vector<JSC::Identifier, 4> &exportNames,
JSC::MarkedArgumentBuffer &exportValues) {
exportNames.reserveCapacity(propertyNames.size() + 1);
exportValues.ensureCapacity(propertyNames.size() + 1);

exportNames.append(vm.propertyNames->defaultKeyword);
exportValues.append(object);

for (const auto &propertyName : propertyNames) {
if (propertyName == vm.propertyNames->defaultKeyword) [[unlikely]]
continue;
JSC::JSValue stored = object->getDirect(vm, propertyName);
if (stored && (stored.isGetterSetter() || stored.isCustomGetterSetter()))
stored = JSC::JSValue();
exportNames.append(propertyName);
exportValues.append(stored);
}

return object;
}
} // namespace Zig
Loading