Skip to content
This repository has been archived by the owner on Oct 15, 2020. It is now read-only.

Commit

Permalink
[Merge chakra-core/ChakraCore@6a23c85c43] [1.6>1.7] [MERGE #3533 @jia…
Browse files Browse the repository at this point in the history
…nchun] module circular reference GetExportedNames/ResolveExport bugs

Merge pull request #3533 from jianchun:modfix

GetExportedNames(): module namespace binds all names from
`GetExportedNames(nullptr)`. However we incorrectly cached result from any
`GetExportedNames(exportStarSet)` call. This causes module namespace
having incomplete names, because a previous intermediate call with non-null
exportStarSet did not include names from that set. This fails 3250-ext-a.
Found this problem while investigating #3250. Fixed by only cache/reuse the
result if exportStarSet is nullptr (root call).

ResolveExport(): parameter `exportStarSet` were used to prevent infinite
loop when looking up name under module circular references. However it
broke the case that we lookup this module record recursively for a
different name (indirect). This causes #3250 to throw SyntaxError for
failing to resolve a binding (resolving f_indirectUninit for dep2, reenter
to resolve c_localUninit1 but rejected by `exportStarSet`). Fixed by
removing it -- another parameter 'resolveSet' already does the job well. It
prevents recursion for the same {moduleRecord, name}, but allows looking up
a different name.

Finally, moved a fragment into try/catch and added an ENTER_SCRIPT_IF. The
given call may throw JavaScriptException which requires in script.
  • Loading branch information
chakrabot authored and MSLaguana committed Sep 25, 2017
1 parent b076970 commit 359274f
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ namespace Js
exportedNames->Map([&](PropertyId propertyId) {
JavascriptString* propertyString = scriptContext->GetPropertyString(propertyId);
sortedExportedNames->Add(propertyString);
if (!moduleRecord->ResolveExport(propertyId, nullptr, nullptr, &moduleNameRecord))
if (!moduleRecord->ResolveExport(propertyId, nullptr, &moduleNameRecord))
{
// ignore ambigious resolution.
#if DBG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ namespace Js
virtual ExportedNames* GetExportedNames(ExportModuleRecordList* exportStarSet) = 0;
// return false when "ambiguous".
// otherwise nullptr means "null" where we have circular reference/cannot resolve.
virtual bool ResolveExport(PropertyId exportName, ResolveSet* resolveSet, ExportModuleRecordList* exportStarSet, ModuleNameRecord** exportRecord) = 0;
virtual bool ResolveExport(PropertyId exportName, ResolveSet* resolveSet, ModuleNameRecord** exportRecord) = 0;
virtual void ModuleDeclarationInstantiation() = 0;
virtual Var ModuleEvaluation() = 0;
virtual bool IsSourceTextModuleRecord() { return false; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,11 +400,16 @@ namespace Js

ExportedNames* SourceTextModuleRecord::GetExportedNames(ExportModuleRecordList* exportStarSet)
{
if (exportedNames != nullptr)
const bool isRootGetExportedNames = (exportStarSet == nullptr);

// this->exportedNames only caches root "GetExportedNames(nullptr)"
if (isRootGetExportedNames && exportedNames != nullptr)
{
return exportedNames;
}

ArenaAllocator* allocator = scriptContext->GeneralAllocator();

if (exportStarSet == nullptr)
{
exportStarSet = Anew(allocator, ExportModuleRecordList, allocator);
Expand All @@ -414,6 +419,7 @@ namespace Js
return nullptr;
}
exportStarSet->Prepend(this);

ExportedNames* tempExportedNames = nullptr;
if (this->localExportRecordList != nullptr)
{
Expand Down Expand Up @@ -468,7 +474,13 @@ namespace Js
#endif
});
}
exportedNames = tempExportedNames;

// this->exportedNames only caches root "GetExportedNames(nullptr)"
if (isRootGetExportedNames)
{
exportedNames = tempExportedNames;
}

return tempExportedNames;
}

Expand All @@ -488,7 +500,7 @@ namespace Js
}
else
{
childModule->ResolveExport(importName, nullptr, nullptr, importRecord);
childModule->ResolveExport(importName, nullptr, importRecord);
}
return true;
}
Expand All @@ -500,7 +512,7 @@ namespace Js

// return false when "ambiguous".
// otherwise nullptr means "null" where we have circular reference/cannot resolve.
bool SourceTextModuleRecord::ResolveExport(PropertyId exportName, ResolveSet* resolveSet, ExportModuleRecordList* exportStarSet, ModuleNameRecord** exportRecord)
bool SourceTextModuleRecord::ResolveExport(PropertyId exportName, ResolveSet* resolveSet, ModuleNameRecord** exportRecord)
{
ArenaAllocator* allocator = scriptContext->GeneralAllocator();
if (resolvedExportMap == nullptr)
Expand All @@ -512,10 +524,6 @@ namespace Js
return true;
}
// TODO: use per-call/loop allocator?
if (exportStarSet == nullptr)
{
exportStarSet = Anew(allocator, ExportModuleRecordList, allocator);
}
if (resolveSet == nullptr)
{
resolveSet = Anew(allocator, ResolveSet, allocator);
Expand Down Expand Up @@ -589,7 +597,7 @@ namespace Js
}
else
{
isAmbiguous = !childModuleRecord->ResolveExport(importNameId, resolveSet, exportStarSet, exportRecord);
isAmbiguous = !childModuleRecord->ResolveExport(importNameId, resolveSet, exportRecord);
if (isAmbiguous)
{
// ambiguous; don't need to search further
Expand Down Expand Up @@ -624,13 +632,6 @@ namespace Js
return false;
}

if (exportStarSet->Has(this))
{
*exportRecord = nullptr;
return true;
}

exportStarSet->Prepend(this);
bool ambiguousResolution = false;
if (this->starExportRecordList != nullptr)
{
Expand All @@ -648,7 +649,7 @@ namespace Js
}

// if ambigious, return "ambigious"
if (!childModule->ResolveExport(exportName, resolveSet, exportStarSet, &currentResolution))
if (!childModule->ResolveExport(exportName, resolveSet, &currentResolution))
{
ambiguousResolution = true;
return true;
Expand Down Expand Up @@ -796,6 +797,21 @@ namespace Js
InitializeLocalImports();

InitializeIndirectExports();

SetWasDeclarationInitialized();
if (childrenModuleSet != nullptr)
{
childrenModuleSet->Map([](LPCOLESTR specifier, SourceTextModuleRecord* moduleRecord)
{
Assert(moduleRecord->WasParsed());
moduleRecord->ModuleDeclarationInstantiation();
});
}

ENTER_SCRIPT_IF(scriptContext, true, false, false, !scriptContext->GetThreadContext()->IsScriptActive(),
{
ModuleNamespace::GetModuleNamespace(this);
});
}
catch (const JavascriptException& err)
{
Expand All @@ -809,17 +825,6 @@ namespace Js
return;
}

SetWasDeclarationInitialized();
if (childrenModuleSet != nullptr)
{
childrenModuleSet->Map([](LPCOLESTR specifier, SourceTextModuleRecord* moduleRecord)
{
Assert(moduleRecord->WasParsed());
moduleRecord->ModuleDeclarationInstantiation();
});
}

ModuleNamespace::GetModuleNamespace(this);
Js::AutoDynamicCodeReference dynamicFunctionReference(scriptContext);
Assert(this == scriptContext->GetLibrary()->GetModuleRecord(this->pSourceInfo->GetSrcInfo()->moduleID));
CompileScriptException se;
Expand Down Expand Up @@ -965,7 +970,7 @@ namespace Js
// We don't need to initialize anything for * import.
if (importName != Js::PropertyIds::star_)
{
if (!childModule->ResolveExport(importName, nullptr, nullptr, &importRecord)
if (!childModule->ResolveExport(importName, nullptr, &importRecord)
|| importRecord == nullptr)
{
JavascriptError* errorObj = scriptContext->GetLibrary()->CreateSyntaxError();
Expand Down Expand Up @@ -1097,7 +1102,7 @@ namespace Js
this->errorObject = errorObj;
return;
}
if (!childModuleRecord->ResolveExport(propertyId, nullptr, nullptr, &exportRecord) ||
if (!childModuleRecord->ResolveExport(propertyId, nullptr, &exportRecord) ||
(exportRecord == nullptr))
{
JavascriptError* errorObj = scriptContext->GetLibrary()->CreateSyntaxError();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace Js

// return false when "ambiguous".
// otherwise nullptr means "null" where we have circular reference/cannot resolve.
bool ResolveExport(PropertyId exportName, ResolveSet* resolveSet, ExportModuleRecordList* exportStarSet, ModuleNameRecord** exportRecord) override;
bool ResolveExport(PropertyId exportName, ResolveSet* resolveSet, ModuleNameRecord** exportRecord) override;
bool ResolveImport(PropertyId localName, ModuleNameRecord** importRecord);
void ModuleDeclarationInstantiation() override;
Var ModuleEvaluation() override;
Expand Down
13 changes: 13 additions & 0 deletions deps/chakrashim/core/test/es6/module-3250-bug-dep.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

// module-3250-bug-dep.js
import * as ns from './module-3250-bug-dep.js';

export let c_localUninit1;
export { c_localUninit1 as f_indirectUninit } from './module-3250-bug-dep2.js';

var stringKeys = Object.getOwnPropertyNames(ns);
assert.areEqual('["c_localUninit1","f_indirectUninit"]', JSON.stringify(stringKeys));
7 changes: 7 additions & 0 deletions deps/chakrashim/core/test/es6/module-3250-bug-dep2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

// module-3250-bug-dep2.js
export * from './module-3250-bug-dep.js';
13 changes: 13 additions & 0 deletions deps/chakrashim/core/test/es6/module-3250-ext-a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

// module-3250-ext-a.js
import * as ns from './module-3250-ext-a.js';

export let a;
export * from './module-3250-ext-b.js';

var stringKeys = Object.getOwnPropertyNames(ns);
assert.areEqual('["a","b"]', JSON.stringify(stringKeys));
8 changes: 8 additions & 0 deletions deps/chakrashim/core/test/es6/module-3250-ext-b.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

// module-3250-ext-b.js
export let b;
export * from './module-3250-ext-a.js';
16 changes: 16 additions & 0 deletions deps/chakrashim/core/test/es6/module-functionality.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,22 @@ var tests = [
testRunner.LoadModule(functionBody, 'samethread');
}
},
{
name: "Circular module dependency: starExportList should allow multiple/recursive visits to re-resolve different names",
body: function () {
let functionBody =
`import './module-3250-bug-dep.js';`;
testModuleScript(functionBody);
}
},
{
name: "Circular module dependency: non-root GetExportNames should not be cached",
body: function () {
let functionBody =
`import './module-3250-ext-a.js';`;
testModuleScript(functionBody);
}
},
{
name: "Implicitly re-exporting an import binding (import { foo } from ''; export { foo };)",
body: function () {
Expand Down

0 comments on commit 359274f

Please sign in to comment.