aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/bindings/CommonJSModuleRecord.cpp
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-06-15 01:18:23 -0700
committerGravatar GitHub <noreply@github.com> 2023-06-15 01:18:23 -0700
commitdc06caccaa6bd8fd273e16cff2c2e0c10f32c58e (patch)
treeee62eff2b60d567a337d3442ed703cf6f547bd76 /src/bun.js/bindings/CommonJSModuleRecord.cpp
parente6d4b3a89ac6631f54276a21d82d41f91fd41c76 (diff)
downloadbun-dc06caccaa6bd8fd273e16cff2c2e0c10f32c58e.tar.gz
bun-dc06caccaa6bd8fd273e16cff2c2e0c10f32c58e.tar.zst
bun-dc06caccaa6bd8fd273e16cff2c2e0c10f32c58e.zip
Tweak CommonJS output (#3320)
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Diffstat (limited to 'src/bun.js/bindings/CommonJSModuleRecord.cpp')
-rw-r--r--src/bun.js/bindings/CommonJSModuleRecord.cpp497
1 files changed, 235 insertions, 262 deletions
diff --git a/src/bun.js/bindings/CommonJSModuleRecord.cpp b/src/bun.js/bindings/CommonJSModuleRecord.cpp
index 31c24bb66..1cee1091b 100644
--- a/src/bun.js/bindings/CommonJSModuleRecord.cpp
+++ b/src/bun.js/bindings/CommonJSModuleRecord.cpp
@@ -68,9 +68,6 @@
namespace Bun {
using namespace JSC;
-static Structure* internalCreateCommonJSModuleStructure(
- Zig::GlobalObject* globalObject);
-
class JSCommonJSModule final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
@@ -78,15 +75,13 @@ public:
mutable JSC::WriteBarrier<JSC::Unknown> m_exportsObject;
mutable JSC::WriteBarrier<JSC::JSString> m_id;
- mutable JSC::WriteBarrier<JSC::EvalExecutable> m_executable;
- void finishCreation(JSC::VM& vm, JSC::JSValue exportsObject, JSC::JSString* id, JSC::JSString* filename, JSC::JSValue requireFunction, JSC::EvalExecutable* executable)
+ void finishCreation(JSC::VM& vm, JSC::JSValue exportsObject, JSC::JSString* id, JSC::JSString* filename, JSC::JSString* dirname, JSC::JSValue requireFunction)
{
Base::finishCreation(vm);
ASSERT(inherits(vm, info()));
m_exportsObject.set(vm, this, exportsObject);
m_id.set(vm, this, id);
- m_executable.set(vm, this, executable);
this->putDirectOffset(
vm,
@@ -101,15 +96,83 @@ public:
this->putDirectOffset(
vm,
2,
- id);
+ filename);
+
this->putDirectOffset(
vm,
3,
- filename);
+ jsBoolean(false));
+
this->putDirectOffset(
vm,
4,
- requireFunction);
+ dirname);
+
+ this->putDirectOffset(
+ vm,
+ 5,
+ jsUndefined());
+ }
+
+ static JSC::Structure* createStructure(
+ JSC::JSGlobalObject* globalObject)
+ {
+ auto& vm = globalObject->vm();
+ JSC::Structure* structure = JSC::Structure::create(
+ vm,
+ globalObject,
+ globalObject->objectPrototype(),
+ JSC::TypeInfo(JSC::ObjectType, JSCommonJSModule::StructureFlags),
+ JSCommonJSModule::info(),
+ JSC::NonArray,
+ 6);
+
+ JSC::PropertyOffset offset;
+ auto clientData = WebCore::clientData(vm);
+
+ structure = structure->addPropertyTransition(
+ vm,
+ structure,
+ JSC::Identifier::fromString(vm, "exports"_s),
+ 0,
+ offset);
+
+ structure = structure->addPropertyTransition(
+ vm,
+ structure,
+ JSC::Identifier::fromString(vm, "id"_s),
+ 0,
+ offset);
+
+ structure = structure->addPropertyTransition(
+ vm,
+ structure,
+ JSC::Identifier::fromString(vm, "filename"_s),
+ 0,
+ offset);
+
+ structure = structure->addPropertyTransition(
+ vm,
+ structure,
+ JSC::Identifier::fromString(vm, "loaded"_s),
+ 0,
+ offset);
+
+ structure = structure->addPropertyTransition(
+ vm,
+ structure,
+ JSC::Identifier::fromString(vm, "path"_s),
+ 0,
+ offset);
+
+ structure = structure->addPropertyTransition(
+ vm,
+ structure,
+ JSC::Identifier::fromString(vm, "require"_s),
+ 0,
+ offset);
+
+ return structure;
}
static JSCommonJSModule* create(
@@ -118,11 +181,11 @@ public:
JSC::JSValue exportsObject,
JSC::JSString* id,
JSC::JSString* filename,
- JSC::JSValue requireFunction,
- JSC::EvalExecutable* executable)
+ JSC::JSString* dirname,
+ JSC::JSValue requireFunction)
{
JSCommonJSModule* cell = new (NotNull, JSC::allocateCell<JSCommonJSModule>(vm)) JSCommonJSModule(vm, structure);
- cell->finishCreation(vm, exportsObject, id, filename, requireFunction, executable);
+ cell->finishCreation(vm, exportsObject, id, filename, dirname, requireFunction);
return cell;
}
@@ -145,34 +208,34 @@ public:
JSC::JSValue value,
JSC::PutPropertySlot& slot)
{
- JSCommonJSModule* thisObject = jsCast<JSCommonJSModule*>(cell);
- ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+
auto& vm = globalObject->vm();
+ auto* clientData = WebCore::clientData(vm);
auto throwScope = DECLARE_THROW_SCOPE(vm);
- auto* clientData = WebCore::clientData(vm);
- bool result = Base::put(thisObject, globalObject, propertyName, value, slot);
- if (result) {
- // Whenever you call module.exports = ... in a module, we need to:
- //
- // - Update the internal exports object
- // - Update the require map
- //
- if (propertyName == clientData->builtinNames().exportsPublicName()) {
- thisObject->m_exportsObject.set(vm, thisObject, value);
- Zig::GlobalObject* zigGlobalObject = jsCast<Zig::GlobalObject*>(globalObject);
- zigGlobalObject->requireMap()->set(globalObject, thisObject->id(), value);
- RETURN_IF_EXCEPTION(throwScope, false);
+ if (propertyName == clientData->builtinNames().exportsPublicName()) {
+ JSCommonJSModule* thisObject = jsCast<JSCommonJSModule*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+
+ // It will crash if we attempt to assign Object.defineProperty() result to a JSMap*.
+ if (UNLIKELY(slot.thisValue() != thisObject))
+ RELEASE_AND_RETURN(throwScope, JSObject::definePropertyOnReceiver(globalObject, propertyName, value, slot));
+
+ JSValue prevValue = thisObject->m_exportsObject.get();
+
+ // TODO: refactor this to not go through ESM path and we don't need to do this check.
+ // IF we do this on every call, it causes GC to happen in a place that it may not be able to.
+ // This breaks loading Bluebird in some cases, for example.
+ // We need to update the require map "live" because otherwise the code in Discord.js will break
+ // The bug is something to do with exception handling which causes GC to happen in the error path and then boom.
+ if (prevValue != value && (!prevValue.isCell() || !value.isCell() || prevValue.asCell()->type() != value.asCell()->type())) {
+ jsCast<Zig::GlobalObject*>(globalObject)->requireMap()->set(globalObject, thisObject->id(), value);
}
+
+ thisObject->m_exportsObject.set(vm, thisObject, value);
}
- RELEASE_AND_RETURN(throwScope, result);
- }
-
- static JSC::Structure* createStructure(
- JSC::JSGlobalObject* globalObject)
- {
- return internalCreateCommonJSModuleStructure(reinterpret_cast<Zig::GlobalObject*>(globalObject));
+ RELEASE_AND_RETURN(throwScope, Base::put(cell, globalObject, propertyName, value, slot));
}
DECLARE_INFO;
@@ -200,53 +263,6 @@ Structure* createCommonJSModuleStructure(
return JSCommonJSModule::createStructure(globalObject);
}
-static Structure* internalCreateCommonJSModuleStructure(
- Zig::GlobalObject* globalObject)
-{
- auto& vm = globalObject->vm();
- JSC::Structure* structure = JSC::Structure::create(
- vm,
- globalObject,
- globalObject->objectPrototype(),
- JSC::TypeInfo(JSC::ObjectType, JSCommonJSModule::StructureFlags),
- JSCommonJSModule::info(),
- JSC::NonArray,
- 4);
-
- JSC::PropertyOffset offset;
- auto clientData = WebCore::clientData(vm);
-
- structure = structure->addPropertyTransition(
- vm,
- structure,
- JSC::Identifier::fromString(vm, "exports"_s),
- 0,
- offset);
-
- structure = structure->addPropertyTransition(
- vm,
- structure,
- JSC::Identifier::fromString(vm, "id"_s),
- 0,
- offset);
-
- structure = structure->addPropertyTransition(
- vm,
- structure,
- JSC::Identifier::fromString(vm, "filename"_s),
- 0,
- offset);
-
- structure = structure->addPropertyTransition(
- vm,
- structure,
- JSC::Identifier::fromString(vm, "require"_s),
- JSC::PropertyAttribute::Builtin | JSC::PropertyAttribute::Function | 0,
- offset);
-
- return structure;
-}
-
template<typename Visitor>
void JSCommonJSModule::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
@@ -255,34 +271,11 @@ void JSCommonJSModule::visitChildrenImpl(JSCell* cell, Visitor& visitor)
Base::visitChildren(thisObject, visitor);
visitor.append(thisObject->m_exportsObject);
visitor.append(thisObject->m_id);
- visitor.append(thisObject->m_executable);
}
DEFINE_VISIT_CHILDREN(JSCommonJSModule);
const JSC::ClassInfo JSCommonJSModule::s_info = { "Module"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCommonJSModule) };
-JSCommonJSModule* createCommonJSModuleObject(
- Zig::GlobalObject* globalObject,
- const ResolvedSource& source,
- const WTF::String& sourceURL,
- JSC::JSValue exportsObjectValue,
- JSC::JSValue requireFunctionValue,
- JSC::EvalExecutable* executable,
- JSC::JSString* filename)
-{
- auto& vm = globalObject->vm();
- auto scope = DECLARE_THROW_SCOPE(vm);
- auto* jsSourceURL = JSC::jsString(vm, sourceURL);
-
- JSCommonJSModule* moduleObject = JSCommonJSModule::create(
- vm,
- globalObject->CommonJSModuleObjectStructure(),
- exportsObjectValue,
- jsSourceURL, filename, requireFunctionValue, executable);
-
- return moduleObject;
-}
-
static bool canPerformFastEnumeration(Structure* s)
{
if (s->typeInfo().overridesGetOwnPropertySlot())
@@ -300,172 +293,155 @@ static bool canPerformFastEnumeration(Structure* s)
return true;
}
-JSC::SourceCode createCommonJSModule(
+JSValue evaluateCommonJSModule(
Zig::GlobalObject* globalObject,
+ Ref<Zig::SourceProvider> sourceProvider,
+ const WTF::String& sourceURL,
ResolvedSource source)
{
- auto sourceURL = Zig::toStringCopy(source.source_url);
- auto sourceProvider = Zig::SourceProvider::create(globalObject, source, JSC::SourceProviderSourceType::Program);
+ auto& vm = globalObject->vm();
- return JSC::SourceCode(
- JSC::SyntheticSourceProvider::create(
- [source, sourceProvider = WTFMove(sourceProvider), sourceURL](JSC::JSGlobalObject* lexicalGlobalObject,
- JSC::Identifier moduleKey,
- Vector<JSC::Identifier, 4>& exportNames,
- JSC::MarkedArgumentBuffer& exportValues) -> void {
- auto* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
- auto& vm = globalObject->vm();
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ auto* requireMapKey = jsString(vm, sourceURL);
+
+ JSC::JSObject* exportsObject = source.commonJSExportsLen < 64
+ ? JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), source.commonJSExportsLen)
+ : JSC::constructEmptyObject(globalObject, globalObject->objectPrototype());
+ auto index = sourceURL.reverseFind('/', sourceURL.length());
+ JSString* dirname = jsEmptyString(vm);
+ JSString* filename = requireMapKey;
+ if (index != WTF::notFound) {
+ dirname = JSC::jsSubstring(globalObject, requireMapKey, 0, index);
+ }
- auto throwScope = DECLARE_THROW_SCOPE(vm);
- auto* requireMapKey = jsString(vm, sourceURL);
-
- JSC::JSObject* exportsObject = source.commonJSExportsLen < 64
- ? JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), source.commonJSExportsLen)
- : JSC::constructEmptyObject(globalObject, globalObject->objectPrototype());
- auto index = sourceURL.reverseFind('/', sourceURL.length());
- JSString* dirname = jsEmptyString(vm);
- JSString* filename = requireMapKey;
- if (index != WTF::notFound) {
- dirname = JSC::jsSubstring(globalObject, requireMapKey, 0, index);
- }
+ globalObject->requireMap()->set(globalObject, requireMapKey, exportsObject);
+ auto* requireFunction = Zig::ImportMetaObject::createRequireFunction(vm, globalObject, sourceURL);
- globalObject->requireMap()->set(globalObject, requireMapKey, exportsObject);
- JSC::SourceCode inputSource(
- WTFMove(sourceProvider));
+ JSC::SourceCode inputSource(
+ WTFMove(sourceProvider));
- JSC::Structure* scopeExtensionObjectStructure = globalObject->commonJSFunctionArgumentsStructure();
- JSC::JSObject* scopeExtensionObject = JSC::constructEmptyObject(
- vm,
- scopeExtensionObjectStructure);
+ auto* moduleObject = JSCommonJSModule::create(
+ vm,
+ globalObject->CommonJSModuleObjectStructure(),
+ exportsObject,
+ requireMapKey, filename, dirname, requireFunction);
- auto* requireFunction = Zig::ImportMetaObject::createRequireFunction(vm, globalObject, sourceURL);
- auto* executable = JSC::DirectEvalExecutable::create(
- globalObject, inputSource, DerivedContextType::None, NeedsClassFieldInitializer::No, PrivateBrandRequirement::None,
- false, false, EvalContextType::None, nullptr, nullptr, ECMAMode::sloppy());
+ if (UNLIKELY(throwScope.exception())) {
+ globalObject->requireMap()->remove(globalObject, requireMapKey);
+ RELEASE_AND_RETURN(throwScope, JSValue());
+ }
- if (UNLIKELY(!executable && !throwScope.exception())) {
- // I'm not sure if this case happens, but it's better to be safe than sorry.
- throwSyntaxError(globalObject, throwScope, "Failed to compile CommonJS module."_s);
- }
+ JSC::Structure* thisObjectStructure = globalObject->commonJSFunctionArgumentsStructure();
+ JSC::JSObject* thisObject = JSC::constructEmptyObject(
+ vm,
+ thisObjectStructure);
+ thisObject->putDirectOffset(
+ vm,
+ 0,
+ moduleObject);
- if (UNLIKELY(throwScope.exception())) {
- globalObject->requireMap()->remove(globalObject, requireMapKey);
- throwScope.release();
- return;
- }
+ thisObject->putDirectOffset(
+ vm,
+ 1,
+ exportsObject);
- auto* moduleObject = createCommonJSModuleObject(globalObject,
- source,
- sourceURL,
- exportsObject,
- requireFunction, executable, filename);
-
- scopeExtensionObject->putDirectOffset(
- vm,
- 0,
- moduleObject);
-
- scopeExtensionObject->putDirectOffset(
- vm,
- 1,
- exportsObject);
-
- scopeExtensionObject->putDirectOffset(
- vm,
- 2,
- dirname);
-
- scopeExtensionObject->putDirectOffset(
- vm,
- 3,
- filename);
-
- scopeExtensionObject->putDirectOffset(
- vm,
- 4,
- requireFunction);
-
- if (UNLIKELY(throwScope.exception())) {
- globalObject->requireMap()->remove(globalObject, requireMapKey);
- throwScope.release();
- return;
- }
+ thisObject->putDirectOffset(
+ vm,
+ 2,
+ dirname);
- auto catchScope = DECLARE_CATCH_SCOPE(vm);
-
- // Where the magic happens.
- //
- // A `with` scope is created containing { module, exports, require }.
- // We eval() the CommonJS module code
- // with that scope.
- //
- // Doing it that way saves us a roundtrip through C++ <> JS.
- //
- // Sidenote: another implementation could use
- // FunctionExecutable. It looks like there are lots of arguments
- // to pass to that and it isn't used directly much, so that
- // seems harder to do correctly.
- {
- // We must use a global scope extension or else the JSWithScope will be collected unexpectedly.
- // https://github.com/oven-sh/bun/issues/3161
- globalObject->clearGlobalScopeExtension();
-
- JSWithScope* withScope = JSWithScope::create(vm, globalObject, globalObject->globalScope(), scopeExtensionObject);
- globalObject->setGlobalScopeExtension(withScope);
- vm.interpreter.executeEval(executable, globalObject, globalObject->globalScope());
- globalObject->clearGlobalScopeExtension();
-
- if (UNLIKELY(catchScope.exception())) {
- auto returnedException = catchScope.exception();
- catchScope.clearException();
- JSC::throwException(globalObject, throwScope, returnedException);
- }
- }
+ thisObject->putDirectOffset(
+ vm,
+ 3,
+ filename);
- if (throwScope.exception()) {
- globalObject->requireMap()->remove(globalObject, requireMapKey);
- throwScope.release();
- return;
- }
+ thisObject->putDirectOffset(
+ vm,
+ 4,
+ requireFunction);
- JSValue result = moduleObject->exportsObject();
-
- // The developer can do something like:
- //
- // Object.defineProperty(module, 'exports', {get: getter})
- //
- // In which case, the exports object is now a GetterSetter object.
- //
- // We can't return a GetterSetter object to ESM code, so we need to call it.
- if (!result.isEmpty() && (result.isGetterSetter() || result.isCustomGetterSetter())) {
- auto* clientData = WebCore::clientData(vm);
-
- // TODO: is there a faster way to call these getters? We shouldn't need to do a full property lookup.
- //
- // we use getIfPropertyExists just incase a pathological devleoper did:
- //
- // - Object.defineProperty(module, 'exports', {get: getter})
- // - delete module.exports
- //
- if (result.isGetterSetter()) {
- JSC::GetterSetter* getter = jsCast<JSC::GetterSetter*>(result);
- result = getter->callGetter(globalObject, moduleObject);
- } else {
- result = moduleObject->getIfPropertyExists(globalObject, clientData->builtinNames().exportsPublicName());
- }
+ {
+ WTF::NakedPtr<Exception> exception;
+ globalObject->m_BunCommonJSModuleValue.set(vm, globalObject, thisObject);
+ JSC::evaluate(globalObject, inputSource, globalObject->globalThis(), exception);
+
+ if (exception.get()) {
+ throwScope.throwException(globalObject, exception->value());
+ exception.clear();
+ RELEASE_AND_RETURN(throwScope, JSValue());
+ }
+ }
- if (UNLIKELY(throwScope.exception())) {
- // Unlike getters on properties of the exports object
- // When the exports object itself is a getter and it throws
- // There's not a lot we can do
- // so we surface that error
- globalObject->requireMap()->remove(globalObject, requireMapKey);
- throwScope.release();
- return;
- }
+ if (UNLIKELY(throwScope.exception())) {
+ globalObject->requireMap()->remove(globalObject, requireMapKey);
+ RELEASE_AND_RETURN(throwScope, JSValue());
+ }
+
+ JSValue result = moduleObject->exportsObject();
+
+ // The developer can do something like:
+ //
+ // Object.defineProperty(module, 'exports', {get: getter})
+ //
+ // In which case, the exports object is now a GetterSetter object.
+ //
+ // We can't return a GetterSetter object to ESM code, so we need to call it.
+ if (!result.isEmpty() && (result.isGetterSetter() || result.isCustomGetterSetter())) {
+ auto* clientData = WebCore::clientData(vm);
+
+ // TODO: is there a faster way to call these getters? We shouldn't need to do a full property lookup.
+ //
+ // we use getIfPropertyExists just incase a pathological devleoper did:
+ //
+ // - Object.defineProperty(module, 'exports', {get: getter})
+ // - delete module.exports
+ //
+ if (result.isGetterSetter()) {
+ JSC::GetterSetter* getter = jsCast<JSC::GetterSetter*>(result);
+ result = getter->callGetter(globalObject, moduleObject);
+ } else {
+ result = moduleObject->getIfPropertyExists(globalObject, clientData->builtinNames().exportsPublicName());
+ }
+
+ if (UNLIKELY(throwScope.exception())) {
+ // Unlike getters on properties of the exports object
+ // When the exports object itself is a getter and it throws
+ // There's not a lot we can do
+ // so we surface that error
+ globalObject->requireMap()->remove(globalObject, requireMapKey);
+ RELEASE_AND_RETURN(throwScope, JSValue());
+ }
+ }
+
+ globalObject->requireMap()->set(globalObject, requireMapKey, result);
+
+ return result;
+}
+
+JSC::SourceCode createCommonJSModule(
+ Zig::GlobalObject* globalObject,
+ ResolvedSource source)
+{
+ auto sourceURL = Zig::toStringCopy(source.source_url);
+ auto sourceProvider = Zig::SourceProvider::create(globalObject, source, JSC::SourceProviderSourceType::Program);
+
+ return JSC::SourceCode(
+ JSC::SyntheticSourceProvider::create(
+ [source, sourceProvider = WTFMove(sourceProvider), sourceURL](JSC::JSGlobalObject* globalObject,
+ JSC::Identifier moduleKey,
+ Vector<JSC::Identifier, 4>& exportNames,
+ JSC::MarkedArgumentBuffer& exportValues) -> void {
+ JSValue result = evaluateCommonJSModule(
+ jsCast<Zig::GlobalObject*>(globalObject),
+ WTFMove(sourceProvider),
+ sourceURL,
+ source);
+
+ if (!result) {
+ return;
}
- globalObject->requireMap()->set(globalObject, requireMapKey, result);
+ auto& vm = globalObject->vm();
exportNames.append(vm.propertyNames->defaultKeyword);
exportValues.append(result);
@@ -474,9 +450,8 @@ JSC::SourceCode createCommonJSModule(
exportNames.append(Identifier::fromUid(vm.symbolRegistry().symbolForKey("CommonJS"_s)));
exportValues.append(jsNumber(0));
- moduleObject->m_executable.clear();
-
if (result.isObject()) {
+ DeferGCForAWhile deferGC(vm);
auto* exports = asObject(result);
auto* structure = exports->structure();
@@ -498,22 +473,20 @@ JSC::SourceCode createCommonJSModule(
return true;
});
} else {
+ auto catchScope = DECLARE_CATCH_SCOPE(vm);
JSC::PropertyNameArray properties(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude);
exports->methodTable()->getOwnPropertyNames(exports, globalObject, properties, DontEnumPropertiesMode::Exclude);
- if (throwScope.exception()) {
- throwScope.release();
+ if (catchScope.exception()) {
+ catchScope.clearExceptionExceptTermination();
return;
}
for (auto property : properties) {
- if (UNLIKELY(property.isEmpty() || property.isNull()))
+ if (UNLIKELY(property.isEmpty() || property.isNull() || property.isPrivateName() || property.isSymbol()))
continue;
// ignore constructor
- if (property == vm.propertyNames->constructor)
- continue;
-
- if (property.isSymbol() || property.isPrivateName() || property == vm.propertyNames->defaultKeyword)
+ if (property == vm.propertyNames->constructor || property == vm.propertyNames->defaultKeyword)
continue;
JSC::PropertySlot slot(exports, PropertySlot::InternalMethodType::Get);