diff --git a/Source/JavaScriptCore/bytecode/ModuleNamespaceAccessCase.cpp b/Source/JavaScriptCore/bytecode/ModuleNamespaceAccessCase.cpp index b1ebda2c09d4d..76acc0e953ef8 100644 --- a/Source/JavaScriptCore/bytecode/ModuleNamespaceAccessCase.cpp +++ b/Source/JavaScriptCore/bytecode/ModuleNamespaceAccessCase.cpp @@ -34,6 +34,9 @@ #include "JSModuleEnvironment.h" #include "JSModuleNamespaceObject.h" #include "PropertyInlineCache.h" +#if USE(BUN_JSC_ADDITIONS) +#include "SyntheticModuleRecord.h" +#endif namespace JSC { @@ -43,6 +46,10 @@ ModuleNamespaceAccessCase::ModuleNamespaceAccessCase(VM& vm, JSCell* owner, Cach { m_moduleNamespaceObject.set(vm, owner, moduleNamespaceObject); m_moduleEnvironment.set(vm, owner, moduleEnvironment); +#if USE(BUN_JSC_ADDITIONS) + if (auto* synthetic = dynamicDowncast(moduleEnvironment->moduleRecord())) + m_additionalSet = synthetic->liveExportsSourceWatchpointSet().inflate(); +#endif } Ref ModuleNamespaceAccessCase::create(VM& vm, JSCell* owner, CacheableIdentifier identifier, JSModuleNamespaceObject* moduleNamespaceObject, JSModuleEnvironment* moduleEnvironment, ScopeOffset scopeOffset) diff --git a/Source/JavaScriptCore/bytecode/ModuleNamespaceAccessCase.h b/Source/JavaScriptCore/bytecode/ModuleNamespaceAccessCase.h index af7a278ff68bb..30ebf9b6f9cfd 100644 --- a/Source/JavaScriptCore/bytecode/ModuleNamespaceAccessCase.h +++ b/Source/JavaScriptCore/bytecode/ModuleNamespaceAccessCase.h @@ -47,12 +47,19 @@ class ModuleNamespaceAccessCase final : public AccessCase { static Ref create(VM&, JSCell* owner, CacheableIdentifier, JSModuleNamespaceObject*, JSModuleEnvironment*, ScopeOffset); +#if USE(BUN_JSC_ADDITIONS) + WatchpointSet* additionalSetImpl() const { return m_additionalSet.get(); } +#endif + private: ModuleNamespaceAccessCase(VM&, JSCell* owner, CacheableIdentifier, JSModuleNamespaceObject*, JSModuleEnvironment*, ScopeOffset); WriteBarrier m_moduleNamespaceObject; WriteBarrier m_moduleEnvironment; ScopeOffset m_scopeOffset; +#if USE(BUN_JSC_ADDITIONS) + RefPtr m_additionalSet; +#endif }; } // namespace JSC diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp index 569e91bc95207..a4acd00feeb38 100644 --- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp +++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp @@ -77,6 +77,9 @@ #include "JSMapIterator.h" #include "JSModuleEnvironment.h" #include "JSModuleNamespaceObject.h" +#if USE(BUN_JSC_ADDITIONS) +#include "SyntheticModuleRecord.h" +#endif #include "JSPromise.h" #include "JSPromiseConstructor.h" #include "JSPromiseCombinatorsContext.h" @@ -6036,6 +6039,18 @@ bool ByteCodeParser::handleModuleNamespaceLoad(VirtualRegister result, Speculate { if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadConstantValue)) return false; +#if USE(BUN_JSC_ADDITIONS) + // Bun may later install a live-exports source on a SyntheticModuleRecord + // (mock.module re-mock with an accessor), after which namespace reads must + // go back through getOwnPropertySlotCommon. Watch the record's invalidation + // set so this lowering jettisons instead of continuing to read the raw slot. + if (auto* synthetic = dynamicDowncast(getById.moduleEnvironment()->moduleRecord())) { + InlineWatchpointSet& set = synthetic->liveExportsSourceWatchpointSet(); + if (!set.isStillValid()) + return false; + m_graph.watchpoints().addLazily(set); + } +#endif addToGraph(CheckIsConstant, OpInfo(m_graph.freeze(getById.moduleNamespaceObject())), Edge(base, CellUse)); addToGraph(FilterGetByStatus, OpInfo(m_graph.m_plan.recordedStatuses().addGetByStatus(currentCodeOrigin(), getById)), base); diff --git a/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp b/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp index 18c7e9afd5e31..2e63e5c104ba2 100644 --- a/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleNamespaceObject.cpp @@ -31,6 +31,9 @@ #include "JSCInlines.h" #include "JSModuleEnvironment.h" #include "JSModuleRecord.h" +#if USE(BUN_JSC_ADDITIONS) +#include "SyntheticModuleRecord.h" +#endif namespace JSC { @@ -195,6 +198,39 @@ bool JSModuleNamespaceObject::getOwnPropertySlotCommon(JSGlobalObject* globalObj return false; } +#if USE(BUN_JSC_ADDITIONS) + // Bun's mock.module / loader:"object" may back a synthetic module with the + // factory-returned object so that accessor exports stay live. The module + // environment slots still hold the first-read snapshot for static imports + // (which read slots directly), but dynamic import namespace access + // re-evaluates through the source object on every read. Keys absent from + // the source fall through to the environment slot so partial re-mocks keep + // un-overridden exports. Returning a plain uncacheable value keeps the + // JIT's module-namespace IC (which would inline the raw slot) from being + // installed; once the record's watchpoint has fired, the env-slot + // fallthrough is also returned uncacheable so the IC is never + // re-installed on a record that has ever had a live source. + if (auto* synthetic = dynamicDowncast(exportEntry.moduleRecord.get())) { + if (JSObject* source = synthetic->liveExportsSource()) [[unlikely]] { + PropertySlot sourceSlot(source, PropertySlot::InternalMethodType::GetOwnProperty); + bool hasOwn = source->methodTable()->getOwnPropertySlot(source, globalObject, propertyName, sourceSlot); + RETURN_IF_EXCEPTION(scope, false); + if (hasOwn) { + slot.disableCaching(); + JSValue liveValue = sourceSlot.getValue(globalObject, propertyName); + RETURN_IF_EXCEPTION(scope, false); + slot.setValue(this, static_cast(PropertyAttribute::DontDelete), liveValue); + return true; + } + } + if (!synthetic->liveExportsSourceWatchpointSet().isStillValid()) [[unlikely]] { + slot.disableCaching(); + slot.setValue(this, static_cast(PropertyAttribute::DontDelete), value); + return true; + } + } +#endif + slot.setValueModuleNamespace(this, static_cast(PropertyAttribute::DontDelete), value, environment, scopeOffset); return true; } @@ -452,6 +488,17 @@ bool JSModuleNamespaceObject::overrideExportValue(JSGlobalObject* globalObject, putResult = moduleNamespaceObject->put(moduleNamespaceObject, globalObject, name, value, putter); RETURN_IF_EXCEPTION(scope, {}); moduleNamespaceObject->m_isOverridingValue = false; + + // Keep the live-exports backing object (if any) consistent with the env + // slot so spyOn / re-mock writes are observed by namespace reads that + // forward through it. + if (auto* synthetic = dynamicDowncast(record)) { + if (JSObject* source = synthetic->liveExportsSource()) { + source->putDirect(vm, name, value, 0); + RETURN_IF_EXCEPTION(scope, {}); + } + } + return putResult; } diff --git a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp index 895711336e910..d0c91f45d87a6 100644 --- a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.cpp @@ -73,6 +73,9 @@ void SyntheticModuleRecord::visitChildrenImpl(JSCell* cell, Visitor& visitor) SyntheticModuleRecord* thisObject = uncheckedDowncast(cell); ASSERT_GC_OBJECT_INHERITS(thisObject, info()); Base::visitChildren(thisObject, visitor); +#if USE(BUN_JSC_ADDITIONS) + visitor.append(thisObject->m_liveExportsSource); +#endif } DEFINE_VISIT_CHILDREN(SyntheticModuleRecord); @@ -92,7 +95,19 @@ SyntheticModuleRecord* SyntheticModuleRecord::tryCreateWithExportNamesAndValues( VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); +#if USE(BUN_JSC_ADDITIONS) + // A trailing value with no matching name carries the live-exports backing + // object (Bun's mock.module / loader:"object" path). + JSObject* liveExportsSource = nullptr; + if (exportValues.size() == exportNames.size() + 1) { + JSValue extra = exportValues.at(exportNames.size()); + if (extra.isObject()) + liveExportsSource = asObject(extra); + } + ASSERT(exportNames.size() == exportValues.size() || liveExportsSource); +#else ASSERT(exportNames.size() == exportValues.size()); +#endif auto* moduleRecord = create(globalObject, vm, globalObject->syntheticModuleRecordStructure(), moduleKey); SymbolTable* exportSymbolTable = SymbolTable::create(vm); @@ -121,10 +136,28 @@ SyntheticModuleRecord* SyntheticModuleRecord::tryCreateWithExportNamesAndValues( ASSERT(putResult); } +#if USE(BUN_JSC_ADDITIONS) + if (liveExportsSource) + moduleRecord->setLiveExportsSource(vm, liveExportsSource); +#endif + return moduleRecord; } +#if USE(BUN_JSC_ADDITIONS) +void SyntheticModuleRecord::setLiveExportsSource(VM& vm, JSObject* source) +{ + m_liveExportsSource.setMayBeNull(vm, this, source); + // Any ModuleNamespaceAccessCase / DFG GetClosureVar compiled while no live + // source was installed reads the environment slot directly. Invalidate them + // the first time a source is installed so those sites re-enter + // getOwnPropertySlotCommon and observe the live-source forwarding. + if (source && m_liveExportsSourceWatchpointSet.isStillValid()) + m_liveExportsSourceWatchpointSet.fireAll(vm, "SyntheticModuleRecord live-exports source installed"); +} +#endif + SyntheticModuleRecord* SyntheticModuleRecord::tryCreateDefaultExportSyntheticModule(JSGlobalObject* globalObject, const Identifier& moduleKey, JSValue defaultExport) { VM& vm = globalObject->vm(); diff --git a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h index 47ac7856ebf15..be131b54d0763 100644 --- a/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h +++ b/Source/JavaScriptCore/runtime/SyntheticModuleRecord.h @@ -28,6 +28,9 @@ #include "AbstractModuleRecord.h" #include "ArgList.h" #include "SourceCode.h" +#if USE(BUN_JSC_ADDITIONS) +#include "Watchpoint.h" +#endif namespace JSC { @@ -61,12 +64,23 @@ class SyntheticModuleRecord final : public AbstractModuleRecord { JS_EXPORT_PRIVATE static SyntheticModuleRecord* tryCreateWithExportNamesAndValues(JSGlobalObject*, const Identifier& moduleKey, const Vector& exportNames, const MarkedArgumentBuffer& exportValues); +#if USE(BUN_JSC_ADDITIONS) + JSObject* liveExportsSource() const { return m_liveExportsSource.get(); } + JS_EXPORT_PRIVATE void setLiveExportsSource(VM&, JSObject* source); + InlineWatchpointSet& liveExportsSourceWatchpointSet() { return m_liveExportsSourceWatchpointSet; } +#endif + private: SyntheticModuleRecord(VM&, Structure*, const Identifier& moduleKey); static SyntheticModuleRecord* tryCreateDefaultExportSyntheticModule(JSGlobalObject*, const Identifier& moduleKey, JSValue); void finishCreation(JSGlobalObject*, VM&); + +#if USE(BUN_JSC_ADDITIONS) + WriteBarrier m_liveExportsSource; + InlineWatchpointSet m_liveExportsSourceWatchpointSet { IsWatched }; +#endif }; } // namespace JSC