Repository navigation
[JSC] A link that a termination cuts leaves no environment on a module that is linked again #976
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
1
commit into
robobun/85266cfa/termination-at-trap-check
Choose a base branch
from
robobun/85266cfa/module-environment-cleanup-scope
base: robobun/85266cfa/termination-at-trap-check
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 all commits
Commits
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| //@ runDefault("--useDollarVM=1", "--useJIT=0") | ||
|
|
||
| // A termination can begin inside the link of a module, behind the step that sets the environment of the module: | ||
| // in CyclicModuleRecord::initializeEnvironment(), and in SyntheticModuleRecord::initializeExports() for a module | ||
| // without text whose exports are made at the link. The module is then linked again by the next load, and that link | ||
| // sets an environment again. So the cut link has to leave none: ASSERT(!m_moduleEnvironment) in an assertion build. | ||
| // | ||
| // Each walk takes a termination at each trap check of an import() in turn ($vm.callWithTerminationAtTrapCheck()) and | ||
| // imports the module again. A cut inside the load is not the subject here: there the later import() can stay pending. | ||
|
|
||
| function shouldBe(actual, expected, what) { | ||
| if (actual !== expected) | ||
| throw new Error(`${what}: expected ${expected}, got ${actual}`); | ||
| } | ||
|
|
||
| const terminationValue = "JavaScript execution terminated."; | ||
| // The modules with text add their names here when they run. | ||
| globalThis.loadSyncLog = []; | ||
| // The number of modules that the loader asked the shell for. | ||
| let fetched = 0; | ||
| setModuleLoaderFetchHook(key => { | ||
| ++fetched; | ||
| if (key.endsWith("/imports-object.js")) | ||
| return `import { a, b, c } from "./object.js"; globalThis.loadSyncLog.push("imports-object"); export const value = a + b + c;`; | ||
| // A module without text. The shell reads its exports when the graph is about to be linked. | ||
| if (key.endsWith("/object.js")) { | ||
| return { | ||
| get a() { | ||
| exportsWereRead = true; | ||
| return 1; | ||
| }, | ||
| b: 2, | ||
| c: 38, | ||
| }; | ||
| } | ||
| }); | ||
| // Whether the exports of the module without text have been read. | ||
| let exportsWereRead = false; | ||
|
|
||
| function importRoot(loader, root) { | ||
| const load = { state: "pending", value: undefined }; | ||
| $vm.moduleLoaderImport(loader, root).then(namespace => { | ||
| load.value = namespace.value; | ||
| load.state = "fulfilled"; | ||
| }, error => { | ||
| load.value = error; | ||
| load.state = "rejected"; | ||
| }); | ||
| drainMicrotasks(); | ||
| return load; | ||
| } | ||
|
|
||
| // `inTheLink()` tells whether a cut import() that has loaded its graph and has run no module was cut in the link, where | ||
| // the test can tell. | ||
| function walk(what, root, modulesWithText, inTheLink = () => true) { | ||
| // A function has more trap checks while its inline caches are empty, so the walk imports the graph a few times first. | ||
| for (let i = 0; i < 10; ++i) | ||
| importRoot($vm.createModuleLoader(), root); | ||
|
|
||
| let linkedAgain = 0; | ||
| for (let n = 1; ; ++n) { | ||
| if (n > 100000) | ||
| throw new Error(`${what}: the import() has no end`); | ||
| const loader = $vm.createModuleLoader(); | ||
| loadSyncLog.length = 0; | ||
| exportsWereRead = false; | ||
| const wasCut = $vm.callWithTerminationAtTrapCheck(() => { importRoot(loader, root); }, n); | ||
| shouldBe($vm.hasPendingTermination(), false, `${what}, trap check ${n}: a termination is still pending`); | ||
| const ranBeforeTheCut = loadSyncLog.length; | ||
| const wasCutInTheLink = inTheLink(); | ||
|
|
||
| loadSyncLog.length = 0; | ||
| fetched = 0; | ||
| const later = importRoot(loader, root); | ||
| if (later.state == "fulfilled") { | ||
| shouldBe(later.value, 41, `${what}, trap check ${n}: the value of the later import()`); | ||
| // The later import() fetched nothing and ran each module. So the cut import() had loaded the graph and | ||
| // had run none of it: the termination began in the link or next to it, and the later import() linked | ||
| // the graph. | ||
| if (wasCut && !ranBeforeTheCut && wasCutInTheLink && !fetched && loadSyncLog.length == modulesWithText) | ||
| ++linkedAgain; | ||
| } else if (later.state == "rejected") { | ||
| // A module that the termination stopped in its evaluation keeps that as its error. | ||
| if (later.value !== null && later.value !== terminationValue) | ||
| throw new Error(`${what}, trap check ${n}: the later import() rejected with ${later.value}`); | ||
| } | ||
| if (!wasCut) { | ||
| shouldBe(later.state, "fulfilled", `${what}, trap check ${n}: the later import() of a call that was not cut`); | ||
| break; | ||
| } | ||
| } | ||
| if (!linkedAgain) | ||
| throw new Error(`${what}: no later import() linked a graph again whose link the termination had cut`); | ||
| } | ||
|
|
||
| walk("two modules", "./resources/module-loader-load-sync/root.js", 2); | ||
| // The exports of the module without text are read in the link, before SyntheticModuleRecord::initializeExports(). | ||
| walk("a module without text", "./module-link-cut-short-by-termination/imports-object.js", 1, () => exportsWereRead); |
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.
Uh oh!
There was an error while loading. Please reload this page.