forked from WebKit/WebKit
-
Notifications
You must be signed in to change notification settings - Fork 52
SyntheticModuleRecord: support a live exports source for namespace reads #380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
robobun
wants to merge
5
commits into
main
Choose a base branch
from
farm/24ce6e23/synthetic-module-live-exports
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
a59646c
SyntheticModuleRecord: support a live exports source for namespace reads
robobun 1ee717b
overrideExportValue: write through to the live-exports source
robobun 3cf0ca7
Fall back to the env slot when the live source lacks the property
robobun abed2db
Invalidate the module-namespace IC when a live-exports source is inst…
robobun 1b4afe5
setLiveExportsSource: accept null and fire the watchpoint once
robobun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 The live-source lookup uses
propertyName(the export name on this namespace) instead ofexportEntry.localName(the binding name in the target synthetic module). When another module doesexport { foo as bar } from './mocked',propertyNameisbarbut the source object only hasfoo, so the read returnsundefined— a regression vs. the environment-slot fallback. Additionally, this branch must be skipped whenexportEntry.localName == vm.propertyNames->starNamespacePrivateName: forexport * as X from './mocked'the resolved local name is the private star-namespace symbol, which the user's factory object cannot have, sonsB.Xalso becomesundefinedinstead of the mocked module's namespace object.Extended reasoning...
What the bug is
m_exportson aJSModuleNamespaceObjectmaps this namespace's export names toExportEntry { localName, moduleRecord }, wheremoduleRecord/localNameare the resolved binding after following re-export chains (see the constructor, which storesresolution.localNameandresolution.moduleRecord). The new live-source branch reads the source object withpropertyName— the key on this namespace — instead ofexportEntry.localName— the key in the target synthetic module's environment (and thus on its backing object).For direct access on the synthetic module's own namespace these happen to coincide, because
tryCreateWithExportNamesAndValuescallsaddExportEntry(ExportEntry::createLocal(exportName, exportName)). They diverge as soon as another module re-exports from the mocked module.Step-by-step: renamed re-export
./mockedis aSyntheticModuleRecordcreated viamock.module(id, () => ({ get foo() { ... } }))with aliveExportsSourcethat has afooaccessor.export { foo as bar } from './mocked'.resolveExportImplwalks theIndirectentry on B, enqueues(mockedRecord, 'foo'), and resolves at the synthetic module'sLocalentry toResolution{ Resolved, moduleRecord: mockedSyntheticRecord, localName: 'foo' }.m_exports['bar'] = { localName: 'foo', moduleRecord: mockedSyntheticRecord }.nsB.barentersgetOwnPropertySlotCommonwithpropertyName == 'bar'andexportEntry.localName == 'foo'.dynamicDowncast<SyntheticModuleRecord>(exportEntry.moduleRecord.get())succeeds,liveExportsSource()is non-null, and the code executessource->get(globalObject, propertyName)→source.bar→undefined.Before this PR, step 6 fell through to
setValueModuleNamespacewith the environment slot value (the snapshot offoo), so this is a regression, not merely an incomplete feature. Note that the immediately preceding line already uses the correct key:getValue(environment, exportEntry.localName, scopeOffset).Step-by-step:
export * as Xexport * as X from './mocked'.resolveExport/getModuleNamespaceproduceResolution{ Resolved, moduleRecord: mockedSyntheticRecord, localName: starNamespacePrivateName }(AbstractModuleRecord.cpp handling for star-namespace bindings).m_exports['X'] = { localName: starNamespacePrivateName, moduleRecord: mockedSyntheticRecord }.nsB.X: the earlierif (exportEntry.localName == starNamespacePrivateName)block materializes the namespace, andgetValue(environment, starNamespacePrivateName, ...)correctly fetches the mocked module'sJSModuleNamespaceObjectfrom the slot.exportEntry.moduleRecordis the synthetic record with a live source) and callssource->get(globalObject, 'X')on the user's plain factory object →undefined.Fixing case 1 by switching to
exportEntry.localNamedoes not fix case 2: it would callsource->get(starNamespacePrivateName), a private symbol the user object cannot have, still yieldingundefined.Why nothing prevents it
The branch is guarded only by
dynamicDowncast<SyntheticModuleRecord>(exportEntry.moduleRecord)andliveExportsSource(). It does not check whether this namespace is the synthetic module's own namespace, nor whether the resolved local name is the star-namespace sentinel. BecauseexportEntry.moduleRecordis the resolved target record (notm_moduleRecord), any namespace whose export chain terminates at the mocked module hits this path.Impact
Both cases return
undefinedwhere the pre-PR code returned the correct value (snapshot for the rename case, the namespace object for the star case). This is user-visible incorrect behavior for anyone who re-exports from amock.module'd module — a common pattern when mocking a package that is itself re-exported through a barrel file.Suggested fix
i.e. (a) look up by
exportEntry.localName, and (b) skip the branch entirely for the star-namespace sentinel so it falls through tosetValueModuleNamespacewith the already-correct slot value.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
hasOwnfallback in 3cf0ca7 removes theundefinedregression for both the renamed-re-export andexport * as Xcases (the source lacksbar/X, so we drop through to the env slot), but the lookup key is stillpropertyNamerather thanexportEntry.localName, so the underlying wrong-key issue remains:export { foo as bar } from './mocked'→nsB.barmisses onsource.barand returns the env-slot snapshot, whilensMocked.fooreturns the live value. The feature silently stops working across a barrel/re-export.bar, the source does ownbar, sohasOwnis true andnsB.barreturnssource.bar— but the resolved binding isfoo. That's a correctness bug, not just a stale snapshot.Switching both
getOwnPropertySlotandgetValuecalls toexportEntry.localNamefixes both. With that change thehasOwnfallback also happens to cover the star-namespace case (the source can't ownstarNamespacePrivateName), so the explicit sentinel guard from the original suggestion becomes optional — though keeping it saves a pointless method-table dispatch on everyexport * as Xread.