Skip to content

Commit b71d35f

Browse files
committed
Correct module load order. Fixes: #5171
And fix logic for circular dependencies to handle to multiple root modules.
1 parent 938e7e4 commit b71d35f

File tree

5 files changed

+97
-32
lines changed

5 files changed

+97
-32
lines changed

lib/Runtime/Language/SourceTextModuleRecord.cpp

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ namespace Js
4242
isRootModule(false),
4343
hadNotifyHostReady(false),
4444
localExportSlots(nullptr),
45-
numPendingChildrenModule(0),
4645
moduleId(InvalidModuleIndex),
4746
localSlotCount(InvalidSlotCount),
4847
promise(nullptr),
@@ -225,6 +224,11 @@ namespace Js
225224

226225
void SourceTextModuleRecord::NotifyParentsAsNeeded()
227226
{
227+
if (notifying)
228+
{
229+
return;
230+
}
231+
notifying = true;
228232
// Notify the parent modules that this child module is either in fault state or finished.
229233
if (this->parentModuleList != nullptr)
230234
{
@@ -233,6 +237,7 @@ namespace Js
233237
parentModule->OnChildModuleReady(this, this->errorObject);
234238
});
235239
}
240+
notifying = false;
236241
SetParentsNotified();
237242
}
238243

@@ -326,7 +331,7 @@ namespace Js
326331
OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("PrepareForModuleDeclarationInitialization(%s)\n"), this->GetSpecifierSz());
327332
HRESULT hr = NO_ERROR;
328333

329-
if (numPendingChildrenModule == 0)
334+
if (ConfirmChildrenParsed())
330335
{
331336
OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("\t>NotifyParentsAsNeeded\n"));
332337
NotifyParentsAsNeeded();
@@ -355,12 +360,6 @@ namespace Js
355360
}
356361
}
357362
}
358-
#if DBG
359-
else
360-
{
361-
OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("\tnumPendingChildrenModule=(%d)\n"), numPendingChildrenModule);
362-
}
363-
#endif
364363
return hr;
365364
}
366365

@@ -399,12 +398,6 @@ namespace Js
399398
}
400399
else
401400
{
402-
if (numPendingChildrenModule == 0)
403-
{
404-
return NOERROR; // this is only in case of recursive module reference. Let the higher stack frame handle this module.
405-
}
406-
numPendingChildrenModule--;
407-
408401
hr = PrepareForModuleDeclarationInitialization();
409402
}
410403
return hr;
@@ -715,21 +708,52 @@ namespace Js
715708
{
716709
parentRecord->childrenModuleSet->AddNew(moduleName, this);
717710

718-
if (!this->WasParsed())
711+
if (this->parentModuleList == nullptr)
719712
{
720-
if (this->parentModuleList == nullptr)
713+
Recycler* recycler = GetScriptContext()->GetRecycler();
714+
this->parentModuleList = RecyclerNew(recycler, ModuleRecordList, recycler);
715+
}
716+
717+
if (!this->parentModuleList->Contains(parentRecord))
718+
{
719+
this->parentModuleList->Add(parentRecord);
720+
}
721+
}
722+
}
723+
724+
bool SourceTextModuleRecord::ConfirmChildrenParsed()
725+
{
726+
if (!this->WasParsed())
727+
{
728+
return false;
729+
}
730+
if (confirmedReady || this->ParentsNotified())
731+
{
732+
return true;
733+
}
734+
bool result = true;
735+
confirmedReady = true;
736+
EnsureChildModuleSet(GetScriptContext());
737+
childrenModuleSet->EachValue([&](SourceTextModuleRecord* childModuleRecord) {
738+
if (childModuleRecord->ParentsNotified())
739+
{
740+
return false;
741+
}
742+
else
743+
{
744+
if (childModuleRecord->ConfirmChildrenParsed())
721745
{
722-
Recycler* recycler = GetScriptContext()->GetRecycler();
723-
this->parentModuleList = RecyclerNew(recycler, ModuleRecordList, recycler);
746+
return false;
724747
}
725-
726-
if (!this->parentModuleList->Contains(parentRecord))
748+
else
727749
{
728-
this->parentModuleList->Add(parentRecord);
729-
parentRecord->numPendingChildrenModule++;
750+
result = false;
751+
return true;
730752
}
731753
}
732-
}
754+
});
755+
confirmedReady = false;
756+
return result;
733757
}
734758

735759
void SourceTextModuleRecord::EnsureChildModuleSet(ScriptContext *scriptContext)
@@ -751,16 +775,9 @@ namespace Js
751775
if (requestedModuleList != nullptr)
752776
{
753777
EnsureChildModuleSet(scriptContext);
754-
ArenaAllocator* allocator = scriptContext->GeneralAllocator();
755-
SList<LPCOLESTR> * moduleRecords = Anew(allocator, SList<LPCOLESTR>, allocator);
756778

757-
// Reverse the order for the host. So, host can read the files top-down
758779
requestedModuleList->MapUntil([&](IdentPtr specifier) {
759780
LPCOLESTR moduleName = specifier->Psz();
760-
return !moduleRecords->Prepend(moduleName);
761-
});
762-
763-
moduleRecords->MapUntil([&](LPCOLESTR moduleName) {
764781
ModuleRecordBase* moduleRecordBase = nullptr;
765782
SourceTextModuleRecord* moduleRecord = nullptr;
766783
bool itemFound = childrenModuleSet->TryGetValue(moduleName, &moduleRecord);
@@ -787,7 +804,6 @@ namespace Js
787804
}
788805
return false;
789806
});
790-
moduleRecords->Clear();
791807

792808
if (FAILED(hr))
793809
{

lib/Runtime/Language/SourceTextModuleRecord.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ namespace Js
4343

4444
HRESULT ResolveExternalModuleDependencies();
4545
void EnsureChildModuleSet(ScriptContext *scriptContext);
46+
bool ConfirmChildrenParsed();
4647

4748
void* GetHostDefined() const { return hostDefined; }
4849
void SetHostDefined(void* hostObj) { hostDefined = hostObj; }
@@ -116,6 +117,8 @@ namespace Js
116117
const static uint InvalidSlotIndex = 0xffffffff;
117118
// TODO: move non-GC fields out to avoid false reference?
118119
// This is the parsed tree resulted from compilation.
120+
Field(bool) confirmedReady = false;
121+
Field(bool) notifying = false;
119122
Field(bool) wasParsed;
120123
Field(bool) wasDeclarationInitialized;
121124
Field(bool) parentsNotified;
@@ -136,7 +139,6 @@ namespace Js
136139
Field(LocalExportMap*) localExportMapByExportName; // from propertyId to index map: for bytecode gen.
137140
Field(LocalExportMap*) localExportMapByLocalName; // from propertyId to index map: for bytecode gen.
138141
Field(LocalExportIndexList*) localExportIndexList; // from index to propertyId: for typehandler.
139-
Field(uint) numPendingChildrenModule;
140142
Field(ExportedNames*) exportedNames;
141143
Field(ResolvedExportMap*) resolvedExportMap;
142144

test/es6module/module-bugfixes.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,32 @@ var tests = [
6767
let functionBody = "import 'module_4570_dep1.js';"
6868
testRunner.LoadModule(functionBody);
6969
}
70+
},
71+
{
72+
name: "Issue 5171: Incorrect module load order",
73+
body: function() {
74+
WScript.RegisterModuleSource("obj.js", `export const obj = {a:false, b: false, c: false};`);
75+
WScript.RegisterModuleSource("a.js",`
76+
import {obj} from "obj.js";
77+
assert.isTrue(obj.b);
78+
assert.isFalse(obj.c);
79+
assert.isFalse(obj.a);
80+
obj.a = true;`);
81+
WScript.RegisterModuleSource("b.js",`
82+
import {obj} from "obj.js";
83+
assert.isFalse(obj.b);
84+
assert.isFalse(obj.c);
85+
assert.isFalse(obj.a);
86+
obj.b = true;`);
87+
WScript.RegisterModuleSource("c.js",`
88+
import {obj} from "obj.js";
89+
assert.isTrue(obj.b);
90+
assert.isFalse(obj.c);
91+
assert.isTrue(obj.a);
92+
obj.c = true;`);
93+
const start = 'import "b.js"; import "a.js"; import "c.js";';
94+
testRunner.LoadModule(start);
95+
}
7096
}
7197
];
7298

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//-------------------------------------------------------------------------------------------------------
2+
// Copyright (C) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
4+
//-------------------------------------------------------------------------------------------------------
5+
6+
// Tests that circular overlapping module dependencies are all loaded before execution
7+
8+
WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
9+
10+
WScript.RegisterModuleSource("mod0.js", "print('pass')");
11+
WScript.RegisterModuleSource("mod1.js", "import 'mod2.js';");
12+
WScript.RegisterModuleSource("mod2.js", "import 'mod0.js';");
13+
14+
WScript.LoadScriptFile("mod2.js", "module");
15+
WScript.LoadScriptFile("mod1.js", "module");

test/es6module/rlexe.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@
139139
<baseline>bug_issue_3257.baseline</baseline>
140140
<tags>exclude_sanitize_address</tags>
141141
</default>
142+
</test>
143+
<test>
144+
<default>
145+
<files>multiple-roots-circular.js</files>
146+
<tags>exclude_sanitize_address</tags>
147+
</default>
142148
</test>
143149
<test>
144150
<default>

0 commit comments

Comments
 (0)