From 952c7d48a841ae369862e4d46d8f421d4fbd3639 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sat, 25 Jul 2026 17:57:45 +0000 Subject: [PATCH 1/3] SourceCodeKey: restore source string comparison in operator== 3186362fe1 removed the source string comparison as an optimization, leaving only the 24-bit StringImpl hash + length + flags. That lets two modules with colliding source hashes share one UnlinkedModuleProgramCodeBlock via CodeCache. When Bun hands JSC a JSModuleRecord built from the second module's import/export names, initializeEnvironment links it against the first module's symbol table, producing wrong export values or a null SymbolTableEntry deref in JSModuleNamespaceObject::getOwnPropertySlotCommon (release segfault). Restore upstream's behavior: fast-path on UnlinkedSourceCode equality (provider pointer + offsets), fall back to the source string compare. The string compare runs only on hash-collision bucket probes and legitimate cache hits across distinct providers. --- Source/JavaScriptCore/parser/SourceCodeKey.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Source/JavaScriptCore/parser/SourceCodeKey.h b/Source/JavaScriptCore/parser/SourceCodeKey.h index 4e1277a43589b..21f90b77e8d71 100644 --- a/Source/JavaScriptCore/parser/SourceCodeKey.h +++ b/Source/JavaScriptCore/parser/SourceCodeKey.h @@ -117,11 +117,7 @@ class SourceCodeKey { && m_functionConstructorParametersEndPosition == other.m_functionConstructorParametersEndPosition && m_name == other.m_name && host() == other.host() -#if USE(BUN_JSC_ADDITIONS) - ; -#else && (m_sourceCode == other.m_sourceCode || string() == other.string()); -#endif } struct HashTraits : SimpleClassHashTraits { From 3707e098d88f045af79a3d3c4b99e07bb72b0b6e Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sat, 25 Jul 2026 19:53:49 +0000 Subject: [PATCH 2/3] CachedStringSourceProvider: drop sourceType check when reusing runtime provider The decoded SourceCodeKey is only used for operator== against the runtime key, which does not read sourceType(). Bun encodes bytecode via makeSource() (StringSourceProvider, sourceType Program) but decodes against a Zig::SourceProvider whose sourceType is BunTranspiledModule or Module, so the sourceType check always failed and fell through to the empty-source fallback. That was harmless while operator== skipped the source compare; with it restored (previous commit) the fallback's empty string() causes every disk-cache lookup to miss. Keep the length check so a provider for a different file is still rejected; stale-source detection is the separate m_hash compare. --- Source/JavaScriptCore/runtime/CachedTypes.cpp | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/Source/JavaScriptCore/runtime/CachedTypes.cpp b/Source/JavaScriptCore/runtime/CachedTypes.cpp index a9afda484de10..4be2d94844cab 100644 --- a/Source/JavaScriptCore/runtime/CachedTypes.cpp +++ b/Source/JavaScriptCore/runtime/CachedTypes.cpp @@ -1647,10 +1647,12 @@ class CachedStringSourceProvider : public CachedSourceProviderShape provider = decoder.provider()) { - if (provider->sourceType() == sourceType && provider->source().length() == m_sourceLength) + if (provider->source().length() == m_sourceLength) return provider.leakRef(); } - // Fallback for callers that did not supply a provider: decode without source - // bytes. SourceCodeKey::operator== ignores string(), but length() is compared, - // so synthesize a provider whose source() is empty — length() will mismatch - // and the cache entry will be rejected, which is the conservative behaviour. + // Fallback for callers that did not supply a provider (isCachedBytecodeStillValid, + // decodeSourceCodeKey): decode without source bytes. The synthesised provider + // has empty source, so length() and string() both mismatch the runtime key and + // the cache entry is rejected, which is the conservative behaviour. String decodedSource; #else String decodedSource = m_source.decode(decoder); From e6251330eff7b06767e541c1b66777c3da05bb6b Mon Sep 17 00:00:00 2001 From: robobun Date: Sat, 25 Jul 2026 22:00:02 +0000 Subject: [PATCH 3/3] CachedStringSourceProvider: trim comments, drop spurious UNUSED_PARAM - Cap all BUN_JSC_ADDITIONS comments at 3 lines. - Remove UNUSED_PARAM(sourceType): it is still passed to StringSourceProvider::create in the fallback path. - Fix the fallback comment: length() still matches (offsets come from the encoded record); rejection relies on string(). --- Source/JavaScriptCore/runtime/CachedTypes.cpp | 36 +++++-------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/Source/JavaScriptCore/runtime/CachedTypes.cpp b/Source/JavaScriptCore/runtime/CachedTypes.cpp index 4be2d94844cab..d549ae9cc0e8b 100644 --- a/Source/JavaScriptCore/runtime/CachedTypes.cpp +++ b/Source/JavaScriptCore/runtime/CachedTypes.cpp @@ -1647,12 +1647,9 @@ class CachedStringSourceProvider : public CachedSourceProviderShape provider = decoder.provider()) { if (provider->source().length() == m_sourceLength) return provider.leakRef(); } - // Fallback for callers that did not supply a provider (isCachedBytecodeStillValid, - // decodeSourceCodeKey): decode without source bytes. The synthesised provider - // has empty source, so length() and string() both mismatch the runtime key and - // the cache entry is rejected, which is the conservative behaviour. + // No provider supplied (isCachedBytecodeStillValid, decodeSourceCodeKey): the + // empty-source provider makes string() mismatch the runtime key, so the cache + // entry is conservatively rejected. String decodedSource; #else String decodedSource = m_source.decode(decoder);