From 2d5807f1083550f912ba52919f217daca8acaa7d Mon Sep 17 00:00:00 2001 From: Suwei Chen Date: Thu, 4 May 2017 10:02:47 -0700 Subject: [PATCH] Dynamic Module Import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds support for import() dynamic module import sematic. Per https://github.com/tc39/proposal-dynamic-import: "A call to import(specifier) returns a promise for the module namespace object of the requested module, which is created after fetching, instantiating, and evaluating all of the module's dependencies, as well as the module itself. "Here specifier will be interpreted the same way as in an import declaration (i.e., the same strings will work in both places). However, while specifier is a string it is not necessarily a string literal; thus code like import(`./language-packs/${navigator.language}.js`) will work—something impossible to accomplish with the usual import declarations. "import() is proposed to work in both scripts and modules. This gives script code an easy asynchronous entry point into the module world, allowing it to start running module code." This PR includes following changes: - Update parser and bytecode generator to support import() sematic in module and in script - Add new bytecode 'ImportCall' - Add runtime function for import() that: ○ Uses caller from stack to look up module record or source context that are associated with the module or script from which 'import()' is called ○ Requests host to load target module source file (gets module record in return) ○ Creates promise unless the module record has one ○ Resolves/rejects promise if appropriates ○ Returns promise - Add new host callback ('FetchImportedModuleFromScript') for fetching imported module from script (accepts source context) - Add 'promiseCapability' field to module record class - Update SourceTextModuleRecord's methods to accept callback from host and to handle dynamically imported module and its promise capability - Update exception checks and assertions to cover new usage scenario of importing and evaluating module code with active script Add unit tests for dynamic import functionality --- bin/NativeTests/JsRTApiTest.cpp | 3 + bin/ch/WScriptJsrt.cpp | 71 +++- bin/ch/WScriptJsrt.h | 2 + lib/Backend/JnHelperMethodList.h | 2 + lib/Backend/Lower.cpp | 11 + lib/Common/ConfigFlagsList.h | 7 +- lib/Common/Exceptions/ExceptionCheck.cpp | 5 +- lib/Jsrt/ChakraCore.h | 18 +- lib/Jsrt/Core/JsrtContextCore.cpp | 22 ++ lib/Jsrt/Core/JsrtContextCore.h | 5 + lib/Jsrt/Core/JsrtCore.cpp | 3 + lib/Jsrt/JsrtInternal.h | 1 + lib/Parser/Parse.cpp | 37 +- lib/Parser/Parse.h | 2 +- lib/Parser/ptlist.h | 1 + lib/Runtime/Base/LeaveScriptObject.cpp | 4 +- lib/Runtime/Base/LeaveScriptObject.h | 1 + lib/Runtime/Base/ScriptContext.h | 1 + lib/Runtime/Base/ThreadContext.cpp | 28 +- lib/Runtime/Base/ThreadContext.h | 4 +- lib/Runtime/ByteCode/ByteCodeEmitter.cpp | 8 + lib/Runtime/ByteCode/OpCodes.h | 2 + lib/Runtime/Language/InterpreterHandler.inl | 1 + lib/Runtime/Language/JavascriptOperators.cpp | 76 ++++ lib/Runtime/Language/JavascriptOperators.h | 1 + .../Language/SourceTextModuleRecord.cpp | 127 ++++++- lib/Runtime/Language/SourceTextModuleRecord.h | 10 +- lib/Runtime/Language/i386/StackFrame.cpp | 10 +- lib/Runtime/Library/JavascriptLibrary.cpp | 6 +- test/UnitTestFramework/UnitTestFramework.js | 24 +- test/es6/ModuleCircularBar.js | 4 + test/es6/ModuleCircularFoo.js | 2 +- test/es6/ModuleComplexExports.js | 9 +- test/es6/dynamic-module-functionality.js | 359 ++++++++++++++++++ test/es6/es6_stable.baseline | 4 +- test/es6/es6_stable.enable_disable.baseline | 4 +- test/es6/rlexe.xml | 7 + 37 files changed, 806 insertions(+), 76 deletions(-) create mode 100644 test/es6/dynamic-module-functionality.js diff --git a/bin/NativeTests/JsRTApiTest.cpp b/bin/NativeTests/JsRTApiTest.cpp index 3a40a675b5a..7548e9c0a2e 100644 --- a/bin/NativeTests/JsRTApiTest.cpp +++ b/bin/NativeTests/JsRTApiTest.cpp @@ -1739,6 +1739,7 @@ namespace JsRTApiTest REQUIRE(JsInitializeModuleRecord(nullptr, specifier, &requestModule) == JsNoError); successTest.mainModule = requestModule; REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleCallback, Success_FIMC) == JsNoError); + REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, Success_FIMC) == JsNoError); REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_NotifyModuleReadyCallback, Succes_NMRC) == JsNoError); JsValueRef errorObject = JS_INVALID_REFERENCE; @@ -1834,6 +1835,7 @@ namespace JsRTApiTest REQUIRE(JsInitializeModuleRecord(nullptr, specifier, &requestModule) == JsNoError); reentrantParseData.mainModule = requestModule; REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleCallback, ReentrantParse_FIMC) == JsNoError); + REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, ReentrantParse_FIMC) == JsNoError); REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_NotifyModuleReadyCallback, ReentrantParse_NMRC) == JsNoError); JsValueRef errorObject = JS_INVALID_REFERENCE; @@ -1913,6 +1915,7 @@ namespace JsRTApiTest REQUIRE(JsInitializeModuleRecord(nullptr, specifier, &requestModule) == JsNoError); reentrantNoErrorParseData.mainModule = requestModule; REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleCallback, reentrantNoErrorParse_FIMC) == JsNoError); + REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, reentrantNoErrorParse_FIMC) == JsNoError); REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_NotifyModuleReadyCallback, reentrantNoErrorParse_NMRC) == JsNoError); JsValueRef errorObject = JS_INVALID_REFERENCE; diff --git a/bin/ch/WScriptJsrt.cpp b/bin/ch/WScriptJsrt.cpp index c6b3abaf259..7755b0f4980 100644 --- a/bin/ch/WScriptJsrt.cpp +++ b/bin/ch/WScriptJsrt.cpp @@ -304,14 +304,22 @@ JsErrorCode WScriptJsrt::InitializeModuleInfo(JsValueRef specifier, JsModuleReco { JsErrorCode errorCode = JsNoError; errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_FetchImportedModuleCallback, (void*)WScriptJsrt::FetchImportedModule); + if (errorCode == JsNoError) { - errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_NotifyModuleReadyCallback, (void*)WScriptJsrt::NotifyModuleReadyCallback); - } - if (errorCode == JsNoError) - { - errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_HostDefined, specifier); + errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, (void*)WScriptJsrt::FetchImportedModuleFromScript); + + if (errorCode == JsNoError) + { + errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_NotifyModuleReadyCallback, (void*)WScriptJsrt::NotifyModuleReadyCallback); + + if (errorCode == JsNoError) + { + errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_HostDefined, specifier); + } + } } + IfJsrtErrorFailLogAndRetErrorCode(errorCode); return JsNoError; } @@ -875,10 +883,24 @@ bool WScriptJsrt::Initialize() IfJsrtErrorFail(CreatePropertyIdFromString("console", &consoleName), false); IfJsrtErrorFail(ChakraRTInterface::JsSetProperty(global, consoleName, console, true), false); + IfJsrtErrorFail(InitializeModuleCallbacks(), false); + Error: return hr == S_OK; } +JsErrorCode WScriptJsrt::InitializeModuleCallbacks() +{ + JsModuleRecord moduleRecord = JS_INVALID_REFERENCE; + JsErrorCode errorCode = ChakraRTInterface::JsInitializeModuleRecord(nullptr, nullptr, &moduleRecord); + if (errorCode == JsNoError) + { + errorCode = InitializeModuleInfo(nullptr, moduleRecord); + } + + return errorCode; +} + bool WScriptJsrt::Uninitialize() { // moduleRecordMap is a global std::map, its destructor may access overrided @@ -1267,6 +1289,45 @@ JsErrorCode WScriptJsrt::FetchImportedModule(_In_ JsModuleRecord referencingModu return errorCode; } +// Callback from chakracore to fetch module dynamically during runtime. In the test harness, +// we are not doing any translation, just treat the specifier as fileName. +// While this call will come back directly from runtime script or module code, the additional +// task can be scheduled asynchronously that executed later. +JsErrorCode WScriptJsrt::FetchImportedModuleFromScript(_In_ DWORD_PTR dwReferencingSourceContext, + _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord) +{ + JsModuleRecord moduleRecord = JS_INVALID_REFERENCE; + AutoString specifierStr; + *dependentModuleRecord = nullptr; + + if (specifierStr.Initialize(specifier) != JsNoError) + { + return specifierStr.GetError(); + } + auto moduleEntry = moduleRecordMap.find(std::string(*specifierStr)); + if (moduleEntry != moduleRecordMap.end()) + { + *dependentModuleRecord = moduleEntry->second; + return JsNoError; + } + + JsErrorCode errorCode = ChakraRTInterface::JsInitializeModuleRecord(nullptr, specifier, &moduleRecord); + if (errorCode == JsNoError) + { + InitializeModuleInfo(specifier, moduleRecord); + moduleRecordMap[std::string(*specifierStr)] = moduleRecord; + ModuleMessage* moduleMessage = + WScriptJsrt::ModuleMessage::Create(nullptr, specifier); + if (moduleMessage == nullptr) + { + return JsErrorOutOfMemory; + } + WScriptJsrt::PushMessage(moduleMessage); + *dependentModuleRecord = moduleRecord; + } + return errorCode; +} + // Callback from chakraCore when the module resolution is finished, either successfuly or unsuccessfully. JsErrorCode WScriptJsrt::NotifyModuleReadyCallback(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar) { diff --git a/bin/ch/WScriptJsrt.h b/bin/ch/WScriptJsrt.h index eb4c18ca35c..fc06e300cb3 100644 --- a/bin/ch/WScriptJsrt.h +++ b/bin/ch/WScriptJsrt.h @@ -53,7 +53,9 @@ class WScriptJsrt static void PushMessage(MessageBase *message) { messageQueue->InsertSorted(message); } static JsErrorCode FetchImportedModule(_In_ JsModuleRecord referencingModule, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord); + static JsErrorCode FetchImportedModuleFromScript(_In_ DWORD_PTR dwReferencingSourceContext, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord); static JsErrorCode NotifyModuleReadyCallback(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar); + static JsErrorCode InitializeModuleCallbacks(); static void CALLBACK PromiseContinuationCallback(JsValueRef task, void *callbackState); static LPCWSTR ConvertErrorCodeToMessage(JsErrorCode errorCode) diff --git a/lib/Backend/JnHelperMethodList.h b/lib/Backend/JnHelperMethodList.h index d91bdd49cac..ef2b1c2f620 100644 --- a/lib/Backend/JnHelperMethodList.h +++ b/lib/Backend/JnHelperMethodList.h @@ -507,6 +507,8 @@ HELPERCALL(SetHomeObj, Js::JavascriptOperators::OP_SetHomeObj, HELPERCALL(LdHomeObjProto, Js::JavascriptOperators::OP_LdHomeObjProto, 0) HELPERCALL(LdFuncObjProto, Js::JavascriptOperators::OP_LdFuncObjProto, 0) +HELPERCALL(ImportCall, Js::JavascriptOperators::OP_ImportCall, 0) + HELPERCALL(ResumeYield, Js::JavascriptOperators::OP_ResumeYield, AttrCanThrow) #include "ExternalHelperMethodList.h" diff --git a/lib/Backend/Lower.cpp b/lib/Backend/Lower.cpp index c9d80026d27..0e036dd0f36 100644 --- a/lib/Backend/Lower.cpp +++ b/lib/Backend/Lower.cpp @@ -2844,6 +2844,17 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa break; } + case Js::OpCode::ImportCall: + { + IR::Opnd *src1Opnd = instr->UnlinkSrc1(); + + LoadScriptContext(instr); + m_lowererMD.LoadHelperArgument(instr, src1Opnd); + m_lowererMD.ChangeToHelperCall(instr, IR::HelperImportCall); + + break; + } + case Js::OpCode::SetComputedNameVar: { IR::Opnd *src2Opnd = instr->UnlinkSrc2(); diff --git a/lib/Common/ConfigFlagsList.h b/lib/Common/ConfigFlagsList.h index e7acb136db6..4136c4e22ee 100644 --- a/lib/Common/ConfigFlagsList.h +++ b/lib/Common/ConfigFlagsList.h @@ -531,7 +531,7 @@ PHASE(All) // If ES6Module needs to be disabled by compile flag, DEFAULT_CONFIG_ES6Module should be false #define DEFAULT_CONFIG_ES6Module (false) #else - #define DEFAULT_CONFIG_ES6Module (false) + #define DEFAULT_CONFIG_ES6Module (true) #endif #define DEFAULT_CONFIG_ES6Object (true) #define DEFAULT_CONFIG_ES6Number (true) @@ -997,10 +997,7 @@ FLAGPR (Boolean, ES6, ES7TrailingComma , "Enable ES7 trailing co FLAGPR (Boolean, ES6, ES6IsConcatSpreadable , "Enable ES6 isConcatSpreadable Symbol" , DEFAULT_CONFIG_ES6IsConcatSpreadable) FLAGPR (Boolean, ES6, ES6Math , "Enable ES6 Math extensions" , DEFAULT_CONFIG_ES6Math) -#ifndef COMPILE_DISABLE_ES6Module - #define COMPILE_DISABLE_ES6Module 0 -#endif -FLAGPR_REGOVR_EXP(Boolean, ES6, ES6Module , "Enable ES6 Modules" , DEFAULT_CONFIG_ES6Module) +FLAGPR (Boolean, ES6, ES6Module , "Enable ES6 Modules" , DEFAULT_CONFIG_ES6Module) FLAGPR (Boolean, ES6, ES6Object , "Enable ES6 Object extensions" , DEFAULT_CONFIG_ES6Object) FLAGPR (Boolean, ES6, ES6Number , "Enable ES6 Number extensions" , DEFAULT_CONFIG_ES6Number) FLAGPR (Boolean, ES6, ES6ObjectLiterals , "Enable ES6 Object literal extensions" , DEFAULT_CONFIG_ES6ObjectLiterals) diff --git a/lib/Common/Exceptions/ExceptionCheck.cpp b/lib/Common/Exceptions/ExceptionCheck.cpp index d1d9f5ad3ae..75398a5c41f 100644 --- a/lib/Common/Exceptions/ExceptionCheck.cpp +++ b/lib/Common/Exceptions/ExceptionCheck.cpp @@ -58,7 +58,8 @@ void ExceptionCheck::SetHandledExceptionType(ExceptionType e) #if DBG if(!(e == ExceptionType_None || e == ExceptionType_DisableCheck || - !JsUtil::ExternalApi::IsScriptActiveOnCurrentThreadContext() || + (e & ExceptionType_OutOfMemory) == ExceptionType_OutOfMemory || // InitializeModuleRecord handles OOM during dynamic import + !JsUtil::ExternalApi::IsScriptActiveOnCurrentThreadContext() || (e & ExceptionType_JavascriptException) == ExceptionType_JavascriptException || e == ExceptionType_HasStackProbe)) { @@ -84,6 +85,7 @@ AutoHandledExceptionType::AutoHandledExceptionType(ExceptionType e) AutoHandledExceptionType::~AutoHandledExceptionType() { Assert(ExceptionCheck::GetData().handledExceptionType == ExceptionType_DisableCheck || + (ExceptionCheck::GetData().handledExceptionType & ExceptionType_OutOfMemory) == ExceptionType_OutOfMemory || !JsUtil::ExternalApi::IsScriptActiveOnCurrentThreadContext() || ExceptionCheck::GetData().handledExceptionType == ExceptionType_HasStackProbe || (ExceptionCheck::GetData().handledExceptionType & ExceptionType_JavascriptException) == ExceptionType_JavascriptException); @@ -98,6 +100,7 @@ AutoNestedHandledExceptionType::AutoNestedHandledExceptionType(ExceptionType e) AutoNestedHandledExceptionType::~AutoNestedHandledExceptionType() { Assert(ExceptionCheck::GetData().handledExceptionType == ExceptionType_DisableCheck || + (ExceptionCheck::GetData().handledExceptionType & ExceptionType_OutOfMemory) == ExceptionType_OutOfMemory || !JsUtil::ExternalApi::IsScriptActiveOnCurrentThreadContext() || ExceptionCheck::GetData().handledExceptionType == ExceptionType_HasStackProbe || (ExceptionCheck::GetData().handledExceptionType & ExceptionType_JavascriptException) == ExceptionType_JavascriptException); diff --git a/lib/Jsrt/ChakraCore.h b/lib/Jsrt/ChakraCore.h index d999f287ab3..fc798cb10ed 100644 --- a/lib/Jsrt/ChakraCore.h +++ b/lib/Jsrt/ChakraCore.h @@ -38,7 +38,8 @@ typedef enum JsModuleHostInfoKind JsModuleHostInfo_Exception = 0x01, JsModuleHostInfo_HostDefined = 0x02, JsModuleHostInfo_NotifyModuleReadyCallback = 0x3, - JsModuleHostInfo_FetchImportedModuleCallback = 0x4 + JsModuleHostInfo_FetchImportedModuleCallback = 0x4, + JsModuleHostInfo_FetchImportedModuleFromScriptCallback = 0x5 } JsModuleHostInfoKind; /// @@ -70,6 +71,21 @@ typedef JsErrorCode(CHAKRA_CALLBACK * FetchImportedModuleCallBack)(_In_ JsModule /// /// true if the operation succeeded, false otherwise. /// +typedef JsErrorCode(CHAKRA_CALLBACK * FetchImportedModuleFromScriptCallBack)(_In_ DWORD_PTR dwReferencingSourceContext, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord); + +/// +/// User implemented callback to get notification when the module is ready. +/// +/// +/// Notify the host after ModuleDeclarationInstantiation step (15.2.1.1.6.4) is finished. If there was error in the process, exceptionVar +/// holds the exception. Otherwise the referencingModule is ready and the host should schedule execution afterwards. +/// +/// The referencing script that calls import() +/// If nullptr, the module is successfully initialized and host should queue the execution job +/// otherwise it's the exception object. +/// +/// true if the operation succeeded, false otherwise. +/// typedef JsErrorCode(CHAKRA_CALLBACK * NotifyModuleReadyCallback)(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar); /// diff --git a/lib/Jsrt/Core/JsrtContextCore.cpp b/lib/Jsrt/Core/JsrtContextCore.cpp index e297a71a72a..f00aa00ebfe 100644 --- a/lib/Jsrt/Core/JsrtContextCore.cpp +++ b/lib/Jsrt/Core/JsrtContextCore.cpp @@ -120,6 +120,28 @@ HRESULT ChakraCoreHostScriptContext::FetchImportedModule(Js::ModuleRecordBase* r return E_INVALIDARG; } +HRESULT ChakraCoreHostScriptContext::FetchImportedModuleFromScript(DWORD_PTR dwReferencingSourceContext, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) +{ + if (fetchImportedModuleFromScriptCallback == nullptr) + { + return E_INVALIDARG; + } + + Js::JavascriptString* specifierVar = Js::JavascriptString::NewCopySz(specifier, GetScriptContext()); + JsModuleRecord dependentRecord = JS_INVALID_REFERENCE; + { + AUTO_NO_EXCEPTION_REGION; + JsErrorCode errorCode = fetchImportedModuleFromScriptCallback(dwReferencingSourceContext, specifierVar, &dependentRecord); + if (errorCode == JsNoError) + { + *dependentModuleRecord = static_cast(dependentRecord); + return NOERROR; + } + } + + return E_INVALIDARG; +} + HRESULT ChakraCoreHostScriptContext::NotifyHostAboutModuleReady(Js::ModuleRecordBase* referencingModule, Js::Var exceptionVar) { if (notifyModuleReadyCallback == nullptr) diff --git a/lib/Jsrt/Core/JsrtContextCore.h b/lib/Jsrt/Core/JsrtContextCore.h index aaada307bb7..3534e478fa2 100644 --- a/lib/Jsrt/Core/JsrtContextCore.h +++ b/lib/Jsrt/Core/JsrtContextCore.h @@ -168,6 +168,7 @@ class ChakraCoreHostScriptContext sealed : public HostScriptContext } HRESULT FetchImportedModule(Js::ModuleRecordBase* referencingModule, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) override; + HRESULT FetchImportedModuleFromScript(DWORD_PTR dwReferencingSourceContext, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) override; HRESULT NotifyHostAboutModuleReady(Js::ModuleRecordBase* referencingModule, Js::Var exceptionVar) override; @@ -177,6 +178,9 @@ class ChakraCoreHostScriptContext sealed : public HostScriptContext void SetFetchImportedModuleCallback(FetchImportedModuleCallBack fetchCallback) { this->fetchImportedModuleCallback = fetchCallback ; } FetchImportedModuleCallBack GetFetchImportedModuleCallback() const { return this->fetchImportedModuleCallback; } + void SetFetchImportedModuleFromScriptCallback(FetchImportedModuleFromScriptCallBack fetchCallback) { this->fetchImportedModuleFromScriptCallback = fetchCallback; } + FetchImportedModuleFromScriptCallBack GetFetchImportedModuleFromScriptCallback() const { return this->fetchImportedModuleFromScriptCallback; } + #if DBG_DUMP || defined(PROFILE_EXEC) || defined(PROFILE_MEM) void EnsureParentInfo(Js::ScriptContext* scriptContext = NULL) override { @@ -187,5 +191,6 @@ class ChakraCoreHostScriptContext sealed : public HostScriptContext private: FetchImportedModuleCallBack fetchImportedModuleCallback; + FetchImportedModuleFromScriptCallBack fetchImportedModuleFromScriptCallback; NotifyModuleReadyCallback notifyModuleReadyCallback; }; diff --git a/lib/Jsrt/Core/JsrtCore.cpp b/lib/Jsrt/Core/JsrtCore.cpp index b2a6451beee..4e893e2cdc6 100644 --- a/lib/Jsrt/Core/JsrtCore.cpp +++ b/lib/Jsrt/Core/JsrtCore.cpp @@ -162,6 +162,9 @@ JsSetModuleHostInfo( case JsModuleHostInfo_FetchImportedModuleCallback: currentContext->GetHostScriptContext()->SetFetchImportedModuleCallback(reinterpret_cast(hostInfo)); break; + case JsModuleHostInfo_FetchImportedModuleFromScriptCallback: + currentContext->GetHostScriptContext()->SetFetchImportedModuleFromScriptCallback(reinterpret_cast(hostInfo)); + break; case JsModuleHostInfo_NotifyModuleReadyCallback: currentContext->GetHostScriptContext()->SetNotifyModuleReadyCallback(reinterpret_cast(hostInfo)); break; diff --git a/lib/Jsrt/JsrtInternal.h b/lib/Jsrt/JsrtInternal.h index 5ec6f7ea008..e30fe0ec22c 100644 --- a/lib/Jsrt/JsrtInternal.h +++ b/lib/Jsrt/JsrtInternal.h @@ -369,6 +369,7 @@ JsErrorCode SetContextAPIWrapper(JsrtContext* newContext, Fn fn) return JsErrorOutOfMemory; } CATCH_OTHER_EXCEPTIONS(errorCode) + AUTO_NESTED_HANDLED_EXCEPTION_TYPE((ExceptionType)(ExceptionType_OutOfMemory | ExceptionType_StackOverflow)); JsrtContext::TrySetCurrent(oldContext); return errorCode; } diff --git a/lib/Parser/Parse.cpp b/lib/Parser/Parse.cpp index c66e03077f8..023ae31acbc 100644 --- a/lib/Parser/Parse.cpp +++ b/lib/Parser/Parse.cpp @@ -2436,19 +2436,26 @@ bool Parser::IsImportOrExportStatementValidHere() } template -ParseNodePtr Parser::ParseImportDeclaration() +ParseNodePtr Parser::ParseImport() { Assert(m_scriptContext->GetConfig()->IsES6ModuleEnabled()); Assert(m_token.tk == tkIMPORT); + m_pscan->Scan(); + + // import() + if (m_token.tk == tkLParen) + { + ParseNodePtr specifier = ParseTerm(); + return CreateCallNode(knopCall, CreateNodeWithScanner(), specifier); + } + if (!IsImportOrExportStatementValidHere()) { Error(ERRInvalidModuleImportOrExport); } // We just parsed an import token. Next valid token is *, {, string constant, or binding identifier. - m_pscan->Scan(); - if (m_token.tk == tkStrCon) { // This import declaration has no import clause. @@ -3227,6 +3234,23 @@ LFunction : } break; + case tkIMPORT: + if (m_scriptContext->GetConfig()->IsES6ModuleEnabled()) + { + m_pscan->Scan(); + if (m_token.tk != tkLParen) + { + Error(ERRsyntax); + } + ParseNodePtr specifier = ParseTerm(); + return CreateCallNode(knopCall, CreateNodeWithScanner(), specifier); + } + else + { + goto LUnknown; + } + break; + case tkCASE: { if (!m_doingFastScan) @@ -10112,12 +10136,7 @@ ParseNodePtr Parser::ParseStatement() goto LNeedTerminator; case tkIMPORT: - if (!(m_grfscr & fscrIsModuleCode)) - { - goto LDefaultToken; - } - - pnode = ParseImportDeclaration(); + pnode = ParseImport(); goto LNeedTerminator; diff --git a/lib/Parser/Parse.h b/lib/Parser/Parse.h index efe1a957382..f68bd15fb96 100644 --- a/lib/Parser/Parse.h +++ b/lib/Parser/Parse.h @@ -861,7 +861,7 @@ class Parser bool IsImportOrExportStatementValidHere(); - template ParseNodePtr ParseImportDeclaration(); + template ParseNodePtr ParseImport(); template void ParseImportClause(ModuleImportOrExportEntryList* importEntryList, bool parsingAfterComma = false); template ParseNodePtr ParseExportDeclaration(); diff --git a/lib/Parser/ptlist.h b/lib/Parser/ptlist.h index d521ce27dcc..310cb4b5ade 100644 --- a/lib/Parser/ptlist.h +++ b/lib/Parser/ptlist.h @@ -23,6 +23,7 @@ PTNODE(knopNone , "" , Nop , None , fnopNone ***************************************************************************/ PTNODE(knopName , "name" , Nop , Pid , fnopLeaf , "NameExpr" ) PTNODE(knopInt , "int const" , Nop , Int , fnopLeaf|fnopConst , "NumberLit" ) +PTNODE(knopImport , "import" , Nop , None , fnopLeaf , "ImportExpr" ) PTNODE(knopFlt , "flt const" , Nop , Flt , fnopLeaf|fnopConst , "NumberLit" ) PTNODE(knopStr , "str const" , Nop , Pid , fnopLeaf|fnopConst , "StringLit" ) PTNODE(knopRegExp , "reg expr" , Nop , Pid , fnopLeaf|fnopConst , "RegExprLit" ) diff --git a/lib/Runtime/Base/LeaveScriptObject.cpp b/lib/Runtime/Base/LeaveScriptObject.cpp index 5fd53139491..eb4ec15672d 100644 --- a/lib/Runtime/Base/LeaveScriptObject.cpp +++ b/lib/Runtime/Base/LeaveScriptObject.cpp @@ -63,7 +63,7 @@ namespace Js // not run and we might be in an inconsistent state // Put any code that may raise an exception in OnScriptStart - scriptContext->GetThreadContext()->EnterScriptStart(entryExitRecord, doCleanup); + scriptContext->GetThreadContext()->EnterScriptStart(entryExitRecord, doCleanup, &this->isScriptActive); } END_NO_EXCEPTION } @@ -84,7 +84,7 @@ namespace Js { scriptContext->GetThreadContext()->PopHostScriptContext(); } - scriptContext->GetThreadContext()->EnterScriptEnd(entryExitRecord, doCleanup); + scriptContext->GetThreadContext()->EnterScriptEnd(entryExitRecord, doCleanup, this->isScriptActive); #ifdef PROFILE_EXEC scriptContext->ProfileEnd(Js::RunPhase); #endif diff --git a/lib/Runtime/Base/LeaveScriptObject.h b/lib/Runtime/Base/LeaveScriptObject.h index beb48f08409..0cb915d49f8 100644 --- a/lib/Runtime/Base/LeaveScriptObject.h +++ b/lib/Runtime/Base/LeaveScriptObject.h @@ -132,6 +132,7 @@ namespace Js bool doCleanup; bool isCallRoot; bool hasForcedEnter; // due to debugging. + bool isScriptActive; ScriptContext* scriptContext; HRESULT hr; // we need to throw outside of constructor JavascriptLibrary* library; // stack pin the library. diff --git a/lib/Runtime/Base/ScriptContext.h b/lib/Runtime/Base/ScriptContext.h index edef4a829bd..a04f012cf25 100644 --- a/lib/Runtime/Base/ScriptContext.h +++ b/lib/Runtime/Base/ScriptContext.h @@ -153,6 +153,7 @@ class HostScriptContext virtual HRESULT EnqueuePromiseTask(Js::Var varTask) = 0; virtual HRESULT FetchImportedModule(Js::ModuleRecordBase* referencingModule, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) = 0; + virtual HRESULT FetchImportedModuleFromScript(DWORD_PTR dwReferencingSourceContext, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) = 0; virtual HRESULT NotifyHostAboutModuleReady(Js::ModuleRecordBase* referencingModule, Js::Var exceptionVar) = 0; Js::ScriptContext* GetScriptContext() { return scriptContext; } diff --git a/lib/Runtime/Base/ThreadContext.cpp b/lib/Runtime/Base/ThreadContext.cpp index 971c5b4fd44..ad829616e8a 100644 --- a/lib/Runtime/Base/ThreadContext.cpp +++ b/lib/Runtime/Base/ThreadContext.cpp @@ -1433,7 +1433,7 @@ ThreadContext::UnregisterCodeGenRecyclableData(Js::CodeGenRecyclableData *const uint -ThreadContext::EnterScriptStart(Js::ScriptEntryExitRecord * record, bool doCleanup) +ThreadContext::EnterScriptStart(Js::ScriptEntryExitRecord * record, __in bool doCleanup, __out bool *isScriptActive) { Recycler * recycler = this->GetRecycler(); Assert(recycler->IsReentrantState()); @@ -1469,10 +1469,16 @@ ThreadContext::EnterScriptStart(Js::ScriptEntryExitRecord * record, bool doClean this->PushEntryExitRecord(record); - AssertMsg(!this->IsScriptActive(), - "Missing EnterScriptEnd or LeaveScriptStart"); - this->isScriptActive = true; - recycler->SetIsScriptActive(true); + if (isScriptActive != nullptr) + { + *isScriptActive = this->isScriptActive; + } + + if (!this->IsScriptActive()) + { + this->isScriptActive = true; + recycler->SetIsScriptActive(true); + } #if DBG_DUMP if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::RunPhase)) @@ -1487,7 +1493,7 @@ ThreadContext::EnterScriptStart(Js::ScriptEntryExitRecord * record, bool doClean void -ThreadContext::EnterScriptEnd(Js::ScriptEntryExitRecord * record, bool doCleanup) +ThreadContext::EnterScriptEnd(Js::ScriptEntryExitRecord * record, bool doCleanup, bool isScriptActive) { #if DBG_DUMP if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::RunPhase)) @@ -1499,8 +1505,12 @@ ThreadContext::EnterScriptEnd(Js::ScriptEntryExitRecord * record, bool doCleanup this->PopEntryExitRecord(record); AssertMsg(this->IsScriptActive(), "Missing EnterScriptStart or LeaveScriptEnd"); - this->isScriptActive = false; - this->GetRecycler()->SetIsScriptActive(false); + if (this->isScriptActive != isScriptActive) + { + this->isScriptActive = isScriptActive; + this->GetRecycler()->SetIsScriptActive(isScriptActive); + } + this->callRootLevel--; #ifdef EXCEPTION_CHECK ExceptionCheck::SetHandledExceptionType(record->handledExceptionType); @@ -4042,7 +4052,6 @@ bool ThreadContext::AsyncHostOperationStart(void * suspendRecord) { bool wasInAsync = false; - Assert(!this->IsScriptActive()); Js::ScriptEntryExitRecord * lastRecord = this->entryExitRecord; if (lastRecord != NULL) { @@ -4067,7 +4076,6 @@ ThreadContext::AsyncHostOperationStart(void * suspendRecord) void ThreadContext::AsyncHostOperationEnd(bool wasInAsync, void * suspendRecord) { - Assert(!this->IsScriptActive()); Js::ScriptEntryExitRecord * lastRecord = this->entryExitRecord; if (lastRecord != NULL) { diff --git a/lib/Runtime/Base/ThreadContext.h b/lib/Runtime/Base/ThreadContext.h index 543ce0e5169..1020fdd17c7 100644 --- a/lib/Runtime/Base/ThreadContext.h +++ b/lib/Runtime/Base/ThreadContext.h @@ -1208,8 +1208,8 @@ class ThreadContext sealed : void ResetFunctionCount() { Assert(this->GetScriptSiteHolderCount() == 0); this->functionCount = 0; } void PushEntryExitRecord(Js::ScriptEntryExitRecord *); void PopEntryExitRecord(Js::ScriptEntryExitRecord *); - uint EnterScriptStart(Js::ScriptEntryExitRecord *, bool doCleanup); - void EnterScriptEnd(Js::ScriptEntryExitRecord *, bool doCleanup); + uint EnterScriptStart(Js::ScriptEntryExitRecord *, __in bool doCleanup, __out bool *isScriptActive = nullptr); + void EnterScriptEnd(Js::ScriptEntryExitRecord *, bool doCleanup, bool isScriptActive = false); template void LeaveScriptStart(void *); diff --git a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp index 5d41c3d5c2d..b99b20b5159 100644 --- a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp +++ b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp @@ -10803,6 +10803,14 @@ void Emit(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator, FuncInfo *func { byteCodeGenerator->EmitSuperCall(funcInfo, pnode, fReturnValue); } + else if (pnode->sxCall.pnodeTarget->nop == knopImport) + { + ParseNodePtr args = pnode->sxCall.pnodeArgs; + Emit(args, byteCodeGenerator, funcInfo, false); + funcInfo->ReleaseLoc(args); + funcInfo->AcquireLoc(pnode); + byteCodeGenerator->Writer()->Reg2(Js::OpCode::ImportCall, pnode->location, args->location); + } else { if (pnode->sxCall.isApplyCall && funcInfo->GetApplyEnclosesArgs()) diff --git a/lib/Runtime/ByteCode/OpCodes.h b/lib/Runtime/ByteCode/OpCodes.h index 1a3ced80d0b..524a3b22be8 100755 --- a/lib/Runtime/ByteCode/OpCodes.h +++ b/lib/Runtime/ByteCode/OpCodes.h @@ -734,6 +734,8 @@ MACRO_EXTEND_WMS( LdHomeObjProto, Reg2, OpSideEffect) MACRO_EXTEND_WMS( LdFuncObjProto, Reg2, OpSideEffect) MACRO_EXTEND_WMS( SetHomeObj, Reg2, OpSideEffect) +MACRO_EXTEND_WMS( ImportCall, Reg2, OpSideEffect) + MACRO_BACKEND_ONLY( BrFncCachedScopeEq, Reg2, None) MACRO_BACKEND_ONLY( BrFncCachedScopeNeq,Reg2, None) diff --git a/lib/Runtime/Language/InterpreterHandler.inl b/lib/Runtime/Language/InterpreterHandler.inl index e8f6e0eb649..c9bed5457cd 100644 --- a/lib/Runtime/Language/InterpreterHandler.inl +++ b/lib/Runtime/Language/InterpreterHandler.inl @@ -300,6 +300,7 @@ EXDEF2_WMS(XXtoA1Mem, ScopedLdHomeObj, OP_ScopedLdHomeO EXDEF2_WMS(XXtoA1Mem, ScopedLdFuncObj, OP_ScopedLdFuncObj) EXDEF2_WMS(A1toA1Mem, LdHomeObjProto, JavascriptOperators::OP_LdHomeObjProto) EXDEF2_WMS(A1toA1Mem, LdFuncObjProto, JavascriptOperators::OP_LdFuncObjProto) +EXDEF2_WMS(A1toA1Mem, ImportCall, JavascriptOperators::OP_ImportCall) EXDEF2_WMS(A2toXX, SetHomeObj, JavascriptOperators::OP_SetHomeObj) DEF2_WMS(A1toA1Mem, StrictLdThis, JavascriptOperators::OP_StrictGetThis) DEF2_WMS(A1I1toA1Mem, ProfiledLdThis, PROFILEDOP(OP_ProfiledLdThis, JavascriptOperators::OP_GetThisNoFastPath)) diff --git a/lib/Runtime/Language/JavascriptOperators.cpp b/lib/Runtime/Language/JavascriptOperators.cpp index 205c1dda12a..db8ef935663 100644 --- a/lib/Runtime/Language/JavascriptOperators.cpp +++ b/lib/Runtime/Language/JavascriptOperators.cpp @@ -9639,6 +9639,82 @@ namespace Js return superCtor; } + Var JavascriptOperators::OP_ImportCall(Var specifier, ScriptContext* scriptContext) + { + ModuleRecordBase *moduleRecordBase = nullptr; + SourceTextModuleRecord *moduleRecord = nullptr; + + JavascriptFunction* pfuncCaller; + if (JavascriptStackWalker::GetCaller(&pfuncCaller, scriptContext) && pfuncCaller && pfuncCaller->IsScriptFunction()) + { + FunctionBody* parentFuncBody = pfuncCaller->GetFunctionBody(); + ModuleID id = parentFuncBody->GetModuleID(); + + LPCOLESTR moduleName = Js::JavascriptString::FromVar(specifier)->GetSz(); + + HRESULT hr = 0; + if (id != kmodGlobal) + { + SourceTextModuleRecord *referenceModuleRecord = parentFuncBody->GetScriptContext()->GetLibrary()->GetModuleRecord(id); + + ASYNC_HOST_OPERATION_START(scriptContext->GetThreadContext()); + hr = scriptContext->GetHostScriptContext()->FetchImportedModule(referenceModuleRecord, moduleName, &moduleRecordBase); + ASYNC_HOST_OPERATION_END(scriptContext->GetThreadContext()); + } + else + { + DWORD_PTR dwReferencingSourceContext = parentFuncBody->GetFunctionInfo()->GetFunctionProxy()->GetSourceContextInfo()->dwHostSourceContext; + + ASYNC_HOST_OPERATION_START(scriptContext->GetThreadContext()); + hr = scriptContext->GetHostScriptContext()->FetchImportedModuleFromScript(dwReferencingSourceContext, moduleName, &moduleRecordBase); + ASYNC_HOST_OPERATION_END(scriptContext->GetThreadContext()); + } + + if (FAILED(hr)) + { + Js::JavascriptError * error = scriptContext->GetLibrary()->CreateURIError(); + Var value = JavascriptString::NewCopySz(moduleName, scriptContext); + JavascriptOperators::OP_SetProperty(error, PropertyIds::message, value, scriptContext); + return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, error, scriptContext); + } + + moduleRecord = SourceTextModuleRecord::FromHost(moduleRecordBase); + + if (moduleRecord->WasEvaluated()) + { + return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(true, moduleRecord->GetNamespace(), scriptContext, moduleRecord); + } + else if (moduleRecord->GetErrorObject() != nullptr) + { + return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, moduleRecord->GetErrorObject(), scriptContext, moduleRecord); + } + else + { + JavascriptPromiseCapability *promiseCapability = moduleRecord->GetPromiseCapability(); + if (promiseCapability == nullptr) + { + BEGIN_TRANSLATE_TO_HRESULT(static_cast(ExceptionType_OutOfMemory | ExceptionType_StackOverflow)); + promiseCapability = JavascriptPromise::NewPromiseCapability(scriptContext->GetLibrary()->GetPromiseConstructor(), scriptContext); + END_TRANSLATE_EXCEPTION_TO_HRESULT(hr); + + moduleRecord->SetPromiseCapability(promiseCapability); + } + + if (moduleRecord->ParentsNotified()) + { + moduleRecord->InstantiateAndNotifyHostAboutDynamicModuleReady(); + } + + return promiseCapability->GetPromise(); + } + } + else + { + Assert(false); + Throw::InternalError(); + } + } + Var JavascriptOperators::ScopedLdHomeObjFuncObjHelper(Var scriptFunction, Js::PropertyId propertyId, ScriptContext * scriptContext) { ScriptFunction *instance = ScriptFunction::FromVar(scriptFunction); diff --git a/lib/Runtime/Language/JavascriptOperators.h b/lib/Runtime/Language/JavascriptOperators.h index 53983a8404f..7c9b0958eef 100644 --- a/lib/Runtime/Language/JavascriptOperators.h +++ b/lib/Runtime/Language/JavascriptOperators.h @@ -567,6 +567,7 @@ namespace Js static Var ScopedLdHomeObjFuncObjHelper(Var scriptFunction, Js::PropertyId propertyId, ScriptContext * scriptContext); static Var OP_LdHomeObjProto(Var aRight, ScriptContext* scriptContext); static Var OP_LdFuncObjProto(Var aRight, ScriptContext* scriptContext); + static Var OP_ImportCall(Var specifier, ScriptContext* scriptContext); static Var OP_ResumeYield(ResumeYieldData* yieldData, RecyclableObject* iterator); diff --git a/lib/Runtime/Language/SourceTextModuleRecord.cpp b/lib/Runtime/Language/SourceTextModuleRecord.cpp index df7fb64f27c..9971a332f62 100644 --- a/lib/Runtime/Language/SourceTextModuleRecord.cpp +++ b/lib/Runtime/Language/SourceTextModuleRecord.cpp @@ -7,6 +7,7 @@ #include "Types/SimpleDictionaryPropertyDescriptor.h" #include "Types/SimpleDictionaryTypeHandler.h" #include "ModuleNamespace.h" +#include "Library/JavascriptPromise.h" namespace Js { @@ -36,15 +37,14 @@ namespace Js resolvedExportMap(nullptr), wasParsed(false), wasDeclarationInitialized(false), -#if DBG parentsNotified(false), -#endif isRootModule(false), hadNotifyHostReady(false), localExportSlots(nullptr), numPendingChildrenModule(0), moduleId(InvalidModuleIndex), localSlotCount(InvalidSlotCount), + promiseCapability(nullptr), localExportCount(0) { namespaceRecord.module = this; @@ -61,7 +61,7 @@ namespace Js // in practice the modulerecord lifetime should be the same as the scriptcontext so it could be retrieved for the same // site. Host might hold a reference to the module as well after initializing the module. // In our implementation, we'll use the moduleId in bytecode to identify the module. - childModuleRecord->moduleId = scriptContext->GetLibrary()->EnsureModuleRecordList()->Add(childModuleRecord); + childModuleRecord->moduleId = scriptContext->GetLibrary()->EnsureModuleRecordList()->Add(childModuleRecord) + 1; // 0 == kmodGlobal return childModuleRecord; } @@ -91,6 +91,7 @@ namespace Js OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("ParseSource(%s)\n"), this->GetSpecifierSz()); Assert(!wasParsed); Assert(parser == nullptr); + Assert(exceptionVar != nullptr); HRESULT hr = NOERROR; ScriptContext* scriptContext = GetScriptContext(); CompileScriptException se; @@ -98,6 +99,9 @@ namespace Js *exceptionVar = nullptr; if (!scriptContext->GetConfig()->IsES6ModuleEnabled()) { + JavascriptError *pError = scriptContext->GetLibrary()->CreateError(); + JavascriptError::SetErrorMessageProperties(pError, hr, _u("ES6Module not supported"), scriptContext); + *exceptionVar = pError; return E_NOTIMPL; } // Host indicates that the current module failed to load. @@ -105,8 +109,8 @@ namespace Js { Assert(sourceLength == 0); hr = E_FAIL; - JavascriptError *pError = scriptContext->GetLibrary()->CreateError(); - JavascriptError::SetErrorMessageProperties(pError, hr, _u("host failed to download module"), scriptContext); + JavascriptError *pError = scriptContext->GetLibrary()->CreateURIError(); + JavascriptError::SetErrorMessageProperties(pError, hr, this->GetSpecifierSz(), scriptContext); *exceptionVar = pError; } else @@ -175,6 +179,11 @@ namespace Js } OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("\t>NotifyParentAsNeeded\n")); + if (this->promiseCapability != nullptr) + { + SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->GetErrorObject(), this->GetScriptContext(), this); + } + NotifyParentsAsNeeded(); } return hr; @@ -190,9 +199,7 @@ namespace Js parentModule->OnChildModuleReady(this, this->errorObject); }); } -#if DBG SetParentsNotified(); -#endif } void SourceTextModuleRecord::ImportModuleListsFromParser() @@ -220,6 +227,28 @@ namespace Js return hr; } + void SourceTextModuleRecord::InstantiateAndNotifyHostAboutDynamicModuleReady() + { + if (!WasDeclarationInitialized()) + { + ScriptContext* scriptContext = GetScriptContext(); + ModuleDeclarationInstantiation(); + + if (this->errorObject != nullptr) + { + SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->GetErrorObject(), this->GetScriptContext(), this); + } + else + { + if (!hadNotifyHostReady && !WasEvaluated()) + { + scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject); + hadNotifyHostReady = true; + } + } + } + } + HRESULT SourceTextModuleRecord::PrepareForModuleDeclarationInitialization() { OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("PrepareForModuleDeclarationInitialization(%s)\n"), this->GetSpecifierSz()); @@ -230,15 +259,15 @@ namespace Js OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("\t>NotifyParentsAsNeeded\n")); NotifyParentsAsNeeded(); - if (!WasDeclarationInitialized() && isRootModule) + if (this->promiseCapability != nullptr || (!WasDeclarationInitialized() && isRootModule)) { // TODO: move this as a promise call? if parser is called from a different thread // We'll need to call the bytecode gen in the main thread as we are accessing GC. ScriptContext* scriptContext = GetScriptContext(); - Assert(!scriptContext->GetThreadContext()->IsScriptActive()); + Assert(!scriptContext->GetThreadContext()->IsScriptActive() || this->promiseCapability != nullptr); ModuleDeclarationInstantiation(); - if (!hadNotifyHostReady) + if (!hadNotifyHostReady && !WasEvaluated()) { hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject); hadNotifyHostReady = true; @@ -268,7 +297,14 @@ namespace Js OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("\t>NotifyParentAsNeeded (childException)\n"), this->GetSpecifierSz()); NotifyParentsAsNeeded(); - if (isRootModule && !hadNotifyHostReady) + + if (this->promiseCapability != nullptr) + { + SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->errorObject, this->GetScriptContext(), this); + hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject); + hadNotifyHostReady = true; + } + else if (isRootModule && !hadNotifyHostReady) { hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject); hadNotifyHostReady = true; @@ -586,7 +622,7 @@ namespace Js void SourceTextModuleRecord::SetParent(SourceTextModuleRecord* parentRecord, LPCOLESTR moduleName) { Assert(parentRecord != nullptr); - Assert(parentRecord->childrenModuleSet != nullptr); + parentRecord->EnsureChildModuleSet(GetScriptContext()); if (!parentRecord->childrenModuleSet->ContainsKey(moduleName)) { parentRecord->childrenModuleSet->AddNew(moduleName, this); @@ -609,6 +645,15 @@ namespace Js } } + void SourceTextModuleRecord::EnsureChildModuleSet(ScriptContext *scriptContext) + { + if (nullptr == this->childrenModuleSet) + { + ArenaAllocator* allocator = scriptContext->GeneralAllocator(); + this->childrenModuleSet = (ChildModuleRecordSet*)AllocatorNew(ArenaAllocator, allocator, ChildModuleRecordSet, allocator); + } + } + HRESULT SourceTextModuleRecord::ResolveExternalModuleDependencies() { OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("ResolveExternalModuleDependencies(%s)\n"), this->GetSpecifierSz()); @@ -618,11 +663,7 @@ namespace Js HRESULT hr = NOERROR; if (requestedModuleList != nullptr) { - if (nullptr == childrenModuleSet) - { - ArenaAllocator* allocator = scriptContext->GeneralAllocator(); - childrenModuleSet = (ChildModuleRecordSet*)AllocatorNew(ArenaAllocator, allocator, ChildModuleRecordSet, allocator); - } + EnsureChildModuleSet(scriptContext); requestedModuleList->MapUntil([&](IdentPtr specifier) { ModuleRecordBase* moduleRecordBase = nullptr; SourceTextModuleRecord* moduleRecord = nullptr; @@ -742,7 +783,14 @@ namespace Js Assert(this->errorObject == nullptr); if (this->errorObject != nullptr) { - JavascriptExceptionOperators::Throw(errorObject, scriptContext); + if (this->promiseCapability != nullptr) + { + SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->GetErrorObject(), this->GetScriptContext(), this); + } + else + { + JavascriptExceptionOperators::Throw(errorObject, scriptContext); + } } Assert(!WasEvaluated()); SetWasEvaluated(); @@ -765,7 +813,11 @@ namespace Js CleanupBeforeExecution(); Arguments outArgs(CallInfo(CallFlags_Value, 0), nullptr); - return rootFunction->CallRootFunction(outArgs, scriptContext, true); + Var ret = rootFunction->CallRootFunction(outArgs, scriptContext, true); + + SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(true, this->GetNamespace(), this->GetScriptContext(), this); + + return ret; } HRESULT SourceTextModuleRecord::OnHostException(void* errorVar) @@ -983,4 +1035,41 @@ namespace Js } return slotIndex; } + + // static + Var SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(bool toResolve, Var value, ScriptContext *scriptContext, SourceTextModuleRecord *moduleRecord) + { + JavascriptPromiseCapability *promiseCapability = nullptr; + if (moduleRecord != nullptr) + { + promiseCapability = moduleRecord->GetPromiseCapability(); + } + + if (promiseCapability == nullptr) + { + promiseCapability = JavascriptPromise::NewPromiseCapability(scriptContext->GetLibrary()->GetPromiseConstructor(), scriptContext); + } + + Var resolveOrRejectVar = toResolve ? promiseCapability->GetResolve() : promiseCapability->GetReject(); + + if (!JavascriptConversion::IsCallable(resolveOrRejectVar)) + { + JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedFunction); + } + + RecyclableObject* resolveOrRejectFunc = RecyclableObject::FromVar(resolveOrRejectVar); + + ASYNC_HOST_OPERATION_START(scriptContext->GetThreadContext()); + BEGIN_ENTER_SCRIPT(scriptContext, true, false, false); + CALL_FUNCTION(scriptContext->GetThreadContext(), resolveOrRejectFunc, Js::CallInfo(CallFlags_Value, 2), promiseCapability->GetPromise(), value); + END_ENTER_SCRIPT + ASYNC_HOST_OPERATION_END(scriptContext->GetThreadContext()); + + if (moduleRecord != nullptr) + { + moduleRecord->SetPromiseCapability(nullptr); + } + + return promiseCapability->GetPromise(); + } } \ No newline at end of file diff --git a/lib/Runtime/Language/SourceTextModuleRecord.h b/lib/Runtime/Language/SourceTextModuleRecord.h index 7beab815b94..ee1706eb8a6 100644 --- a/lib/Runtime/Language/SourceTextModuleRecord.h +++ b/lib/Runtime/Language/SourceTextModuleRecord.h @@ -41,6 +41,7 @@ namespace Js void Mark(Recycler * recycler) override { return; } HRESULT ResolveExternalModuleDependencies(); + void EnsureChildModuleSet(ScriptContext *scriptContext); void* GetHostDefined() const { return hostDefined; } void SetHostDefined(void* hostObj) { hostDefined = hostObj; } @@ -55,11 +56,11 @@ namespace Js void SetWasParsed() { wasParsed = true; } bool WasDeclarationInitialized() const { return wasDeclarationInitialized; } void SetWasDeclarationInitialized() { wasDeclarationInitialized = true; } -#if DBG bool ParentsNotified() const { return parentsNotified; } void SetParentsNotified() { parentsNotified = true; } -#endif void SetIsRootModule() { isRootModule = true; } + JavascriptPromiseCapability *GetPromiseCapability() { return this->promiseCapability; } + void SetPromiseCapability(JavascriptPromiseCapability *value) { this->promiseCapability = value; } void SetImportRecordList(ModuleImportOrExportEntryList* importList) { importRecordList = importList; } void SetLocalExportRecordList(ModuleImportOrExportEntryList* localExports) { localExportRecordList = localExports; } @@ -103,6 +104,8 @@ namespace Js void SetParent(SourceTextModuleRecord* parentRecord, LPCOLESTR moduleName); Utf8SourceInfo* GetSourceInfo() { return this->pSourceInfo; } + static Var ResolveOrRejectDynamicImportPromise(bool toResolve, Var value, ScriptContext *scriptContext, SourceTextModuleRecord *mr = nullptr); + void InstantiateAndNotifyHostAboutDynamicModuleReady(); private: const static uint InvalidModuleIndex = 0xffffffff; @@ -112,9 +115,7 @@ namespace Js // This is the parsed tree resulted from compilation. Field(bool) wasParsed; Field(bool) wasDeclarationInitialized; -#if DBG Field(bool) parentsNotified; -#endif Field(bool) isRootModule; Field(bool) hadNotifyHostReady; Field(ParseNodePtr) parseTree; @@ -148,6 +149,7 @@ namespace Js Field(uint) moduleId; Field(ModuleNameRecord) namespaceRecord; + Field(JavascriptPromiseCapability*) promiseCapability; HRESULT PostParseProcess(); HRESULT PrepareForModuleDeclarationInitialization(); diff --git a/lib/Runtime/Language/i386/StackFrame.cpp b/lib/Runtime/Language/i386/StackFrame.cpp index e30774be3fa..90bc1047348 100644 --- a/lib/Runtime/Language/i386/StackFrame.cpp +++ b/lib/Runtime/Language/i386/StackFrame.cpp @@ -51,9 +51,13 @@ X86StackFrame::InitializeByReturnAddress(void * returnAddress, ScriptContext* sc bool X86StackFrame::Next() { - this->addressOfCodeAddr = this->GetAddressOfReturnAddress(); - this->codeAddr = this->GetReturnAddress(); - this->frame = (void **)this->frame[0]; + if (frame != nullptr) + { + this->addressOfCodeAddr = this->GetAddressOfReturnAddress(); + this->codeAddr = this->GetReturnAddress(); + this->frame = (void **)this->frame[0]; + } + return frame != nullptr; } diff --git a/lib/Runtime/Library/JavascriptLibrary.cpp b/lib/Runtime/Library/JavascriptLibrary.cpp index 61b960915b6..2a2f360d17d 100644 --- a/lib/Runtime/Library/JavascriptLibrary.cpp +++ b/lib/Runtime/Library/JavascriptLibrary.cpp @@ -7134,12 +7134,12 @@ namespace Js SourceTextModuleRecord* JavascriptLibrary::GetModuleRecord(uint moduleId) { - Assert((moduleRecordList->Count() >= 0) && (moduleId < (uint)moduleRecordList->Count())); - if (moduleId >= (uint)moduleRecordList->Count()) + Assert((moduleRecordList->Count() > 0) && (moduleId <= (uint)moduleRecordList->Count())); + if (moduleId > (uint)moduleRecordList->Count()) { Js::Throw::FatalInternalError(); } - return moduleRecordList->Item(moduleId); + return moduleRecordList->Item(moduleId - 1); // 0 == kmodGlobal } void JavascriptLibrary::BindReference(void * addr) diff --git a/test/UnitTestFramework/UnitTestFramework.js b/test/UnitTestFramework/UnitTestFramework.js index 2990cbf90e9..520fc290676 100644 --- a/test/UnitTestFramework/UnitTestFramework.js +++ b/test/UnitTestFramework/UnitTestFramework.js @@ -253,6 +253,7 @@ var testRunner = function testRunner() { ++passedTestCount; } else { helpers.writeln("FAILED"); + testRunner.asyncTestErr(testIndex, "RUN FAILED"); } ++executedTestCount; }, @@ -268,7 +269,7 @@ var testRunner = function testRunner() { asyncTest.resolve[testIndex][testCount] = 0; }, - prepareAsyncCode: function prepareAsyncCode(source, shouldFail) { + prepareAsyncCode: function prepareAsyncCode(source, shouldFail, explicitAsyncTestExit) { var testIndex = asyncTest.testIndex; if (typeof shouldFail == "undefined" || shouldFail == false) { _hasAsyncTestPromise = true; @@ -277,14 +278,29 @@ var testRunner = function testRunner() { asyncTest.resolve[testIndex].push(resolve); }); asyncTest.promise[testIndex].push(promise); - return `testRunner.asyncTestBegin(${testIndex}, ${testCount}); ${source};\ntestRunner.asyncTestEnd(${testIndex}, ${testCount});`; + return explicitAsyncTestExit ? + ` + var _asyncEnter = ()=>{ testRunner.asyncTestBegin(${testIndex}, ${testCount}); }; + var _asyncExit = ()=>{ testRunner.asyncTestEnd(${testIndex}, ${testCount}); }; + _asyncEnter(); + ${source}; + ` : + ` + testRunner.asyncTestBegin(${testIndex}, ${testCount}); + ${source}; + testRunner.asyncTestEnd(${testIndex}, ${testCount}); + `; } else { return source; } }, - LoadModule : function LoadModule(source, context, shouldFail) { - return WScript.LoadModule(testRunner.prepareAsyncCode(source, shouldFail), context); + LoadModule : function LoadModule(source, context, shouldFail, explicitAsyncTestExit) { + return WScript.LoadModule(testRunner.prepareAsyncCode(source, shouldFail, explicitAsyncTestExit), context); + }, + + LoadScript : function LoadScript(source, context, shouldFail, explicitAsyncTestExit) { + return WScript.LoadScript(testRunner.prepareAsyncCode(source, shouldFail, explicitAsyncTestExit)); } }; return testRunner; diff --git a/test/es6/ModuleCircularBar.js b/test/es6/ModuleCircularBar.js index 9a32f61267e..1640ad682e0 100644 --- a/test/es6/ModuleCircularBar.js +++ b/test/es6/ModuleCircularBar.js @@ -12,3 +12,7 @@ export function increment() { counter++; } export var counter = 0; + +export function reset() { + counter = 0; +} diff --git a/test/es6/ModuleCircularFoo.js b/test/es6/ModuleCircularFoo.js index eedc7f377a0..006da313a3f 100644 --- a/test/es6/ModuleCircularFoo.js +++ b/test/es6/ModuleCircularFoo.js @@ -12,4 +12,4 @@ export function circular_foo() { return counter; } } -export { circular_bar as rexportbar } from "ModuleCircularBar.js" +export { circular_bar as rexportbar, reset } from "ModuleCircularBar.js" diff --git a/test/es6/ModuleComplexExports.js b/test/es6/ModuleComplexExports.js index 5148a0f0bba..17d2b6edc49 100644 --- a/test/es6/ModuleComplexExports.js +++ b/test/es6/ModuleComplexExports.js @@ -41,13 +41,18 @@ export { genfoo as genfoo2, genbar, genbar as genbar2 }; export default function () { return 'default'; }; -var mutatingExportTarget = function() { return 'before'; }; +var mutatingExportTarget; +function resetMutatingExportTarget() { + mutatingExportTarget = function() { return 'before'; }; + return 'ok'; +} function changeMutatingExportTarget() { mutatingExportTarget = function() { return 'after'; }; return 'ok'; } +resetMutatingExportTarget(); -export { mutatingExportTarget as target, changeMutatingExportTarget as changeTarget }; +export { mutatingExportTarget as target, changeMutatingExportTarget as changeTarget, resetMutatingExportTarget as reset}; var exportedAsKeyword = 'ModuleComplexExports'; export { exportedAsKeyword as export }; diff --git a/test/es6/dynamic-module-functionality.js b/test/es6/dynamic-module-functionality.js new file mode 100644 index 00000000000..1d07c80073b --- /dev/null +++ b/test/es6/dynamic-module-functionality.js @@ -0,0 +1,359 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +// ES6 Module functionality tests -- verifies functionality of import and export statements + +WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); + +function testScript(source, message, shouldFail = false, explicitAsync = false) { + message += " (script)"; + let testfunc = () => testRunner.LoadScript(source, undefined, shouldFail, explicitAsync); + + if (shouldFail) { + let caught = false; + + assert.throws(testfunc, SyntaxErrr, message); + assert.isTrue(caught, `Expected error not thrown: ${message}`); + } else { + assert.doesNotThrow(testfunc, message); + } +} + +function testModuleScript(source, message, shouldFail = false, explicitAsync = false) { + message += " (module)"; + let testfunc = () => testRunner.LoadModule(source, 'samethread', shouldFail, explicitAsync); + + if (shouldFail) { + let caught = false; + + // We can't use assert.throws here because the SyntaxError used to construct the thrown error + // is from a different context so it won't be strictly equal to our SyntaxError. + try { + testfunc(); + } catch(e) { + caught = true; + + // Compare toString output of SyntaxError and other context SyntaxError constructor. + assert.areEqual(e.constructor.toString(), SyntaxError.toString(), message); + } + + assert.isTrue(caught, `Expected error not thrown: ${message}`); + } else { + assert.doesNotThrow(testfunc, message); + } +} + +function testDynamicImport(importFunc, thenFunc, catchFunc, _asyncEnter, _asyncExit) { + var promise = importFunc(); + assert.isTrue(promise instanceof Promise); + promise.then((v)=>{ + _asyncEnter(); + thenFunc(v); + _asyncExit(); + }).catch((err)=>{ + _asyncEnter(); + catchFunc(err); + _asyncExit(); + }); +} + +var tests = [ + { + name: "Runtime evaluation of module specifier", + body: function () { + let functionBody = + `testDynamicImport( + ()=>{ + var getName = ()=>{ return 'ModuleSimpleExport'; }; + return import( getName() + '.js'); + }, + (v)=>{ + assert.areEqual('ModuleSimpleExport', v.ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js'); + }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit + )`; + testScript(functionBody, "Test importing a simple exported function", false, true); + testModuleScript(functionBody, "Test importing a simple exported function", false, true); + } + }, + { + name: "Validate a simple module export", + body: function () { + let functionBody = + `testDynamicImport( + ()=>{ return import('ModuleSimpleExport.js'); }, + (v)=>{ assert.areEqual('ModuleSimpleExport', v.ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js'); }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit + )`; + testScript(functionBody, "Test importing a simple exported function", false, true); + testModuleScript(functionBody, "Test importing a simple exported function", false, true); + } + }, + { + name: "Validate importing from multiple modules", + body: function () { + let functionBody = + `testDynamicImport( + ()=>import('ModuleComplexExports.js'), + (v)=>{ + assert.areEqual('foo', v.foo2(), 'Failed to import foo2 from ModuleComplexExports.js'); + }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit + )`; + testScript(functionBody, "Test importing from multiple modules", false, true); + testModuleScript(functionBody, "Test importing from multiple modules", false, true); + } + }, + { + name: "Validate a variety of more complex exports", + body: function () { + let functionBody = + `testDynamicImport( + ()=>import('ModuleComplexExports.js'), + (v)=>{ + assert.areEqual('foo', v.foo(), 'Failed to import foo from ModuleComplexExports.js'); + assert.areEqual('foo', v.foo2(), 'Failed to import foo2 from ModuleComplexExports.js'); + assert.areEqual('bar', v.bar(), 'Failed to import bar from ModuleComplexExports.js'); + assert.areEqual('bar', v.bar2(), 'Failed to import bar2 from ModuleComplexExports.js'); + assert.areEqual('let2', v.let2, 'Failed to import let2 from ModuleComplexExports.js'); + assert.areEqual('let3', v.let3, 'Failed to import let3 from ModuleComplexExports.js'); + assert.areEqual('let2', v.let4, 'Failed to import let4 from ModuleComplexExports.js'); + assert.areEqual('let3', v.let5, 'Failed to import let5 from ModuleComplexExports.js'); + assert.areEqual('const2', v.const2, 'Failed to import const2 from ModuleComplexExports.js'); + assert.areEqual('const3', v.const3, 'Failed to import const3 from ModuleComplexExports.js'); + assert.areEqual('const2', v.const4, 'Failed to import const4 from ModuleComplexExports.js'); + assert.areEqual('const3', v.const5, 'Failed to import const5 from ModuleComplexExports.js'); + assert.areEqual('var2', v.var2, 'Failed to import var2 from ModuleComplexExports.js'); + assert.areEqual('var3', v.var3, 'Failed to import var3 from ModuleComplexExports.js'); + assert.areEqual('var2', v.var4, 'Failed to import var4 from ModuleComplexExports.js'); + assert.areEqual('var3', v.var5, 'Failed to import var5 from ModuleComplexExports.js'); + assert.areEqual('class2', v.class2.static_member(), 'Failed to import class2 from ModuleComplexExports.js'); + assert.areEqual('class2', new v.class2().member(), 'Failed to create intance of class2 from ModuleComplexExports.js'); + assert.areEqual('class2', v.class3.static_member(), 'Failed to import class3 from ModuleComplexExports.js'); + assert.areEqual('class2', new v.class3().member(), 'Failed to create intance of class3 from ModuleComplexExports.js'); + assert.areEqual('class4', v.class4.static_member(), 'Failed to import class4 from ModuleComplexExports.js'); + assert.areEqual('class4', new v.class4().member(), 'Failed to create intance of class4 from ModuleComplexExports.js'); + assert.areEqual('class4', v.class5.static_member(), 'Failed to import class4 from ModuleComplexExports.js'); + assert.areEqual('class4', new v.class5().member(), 'Failed to create intance of class4 from ModuleComplexExports.js'); + assert.areEqual('default', v.default(), 'Failed to import default from ModuleComplexExports.js'); + assert.areEqual('ModuleComplexExports', v.function, 'Failed to import v.function from ModuleComplexExports.js'); + assert.areEqual('ModuleComplexExports', v.export, 'Failed to import v.export from ModuleComplexExports.js'); + }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit); + `; + testScript(functionBody, "Test importing a variety of exports", false, true); + testModuleScript(functionBody, "Test importing a variety of exports", false, true); + } + }, + { + name: "Exporting module changes exported value", + body: function () { + let functionBody = + `testDynamicImport( + ()=>import('ModuleComplexExports.js'), + (v)=>{ + v.reset(); + assert.areEqual('before', v.target(), 'Failed to import target from ModuleComplexExports.js'); + assert.areEqual('ok', v.changeTarget(), 'Failed to import changeTarget from ModuleComplexExports.js'); + assert.areEqual('after', v.target(), 'changeTarget failed to change export value'); + }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit); + `; + testScript(functionBody, "Changing exported value", false, true); + testModuleScript(functionBody, "Changing exported value", false, true); + } + }, + { + name: "Simple re-export forwards import to correct slot", + body: function () { + let functionBody = + `testDynamicImport( + ()=>import('ModuleSimpleReexport.js'), + (v)=>{ + assert.areEqual('ModuleSimpleExport', v.ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleReexport.js'); + }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit); + `; + testScript(functionBody, "Simple re-export from one module to another", false, true); + testModuleScript(functionBody, "Simple re-export from one module to another", false, true); + } + }, + { + name: "Renamed re-export and dynamic import", + body: function () { + let functionBody = + `testDynamicImport( + ()=>import('ModuleComplexReexports.js'), + (v)=>{ + assert.areEqual('bar', v.ModuleComplexReexports_foo(), 'Failed to import ModuleComplexReexports_foo from ModuleComplexReexports.js'); + }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit); + `; + testScript(functionBody, "Rename already renamed re-export", false, true); + testModuleScript(functionBody, "Rename already renamed re-export", false, true); + } + }, + { + name: "Explicit export/import to default binding", + body: function () { + let functionBody = + `testDynamicImport( + ()=>import('ModuleDefaultExport1.js'), + (v)=>{ + assert.areEqual('ModuleDefaultExport1', v.default(), 'Failed to import default from ModuleDefaultExport1.js'); + }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit); + `; + testScript(functionBody, "Explicitly export and import a local name to the default binding", false, true); + testModuleScript(functionBody, "Explicitly export and import a local name to the default binding", false, true); + } + }, + { + name: "Explicit import of default binding", + body: function () { + let functionBody = + `testDynamicImport( + ()=>import('ModuleDefaultExport2.js'), + (v)=>{ + assert.areEqual('ModuleDefaultExport2', v.default(), 'Failed to import default from ModuleDefaultExport2.js'); + }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit); + `; + testScript(functionBody, "Explicitly import the default export binding", false, true); + testModuleScript(functionBody, "Explicitly import the default export binding", false, true); + } + }, + { + name: "Exporting module changes value of default export", + body: function () { + let functionBody = + `testDynamicImport( + ()=>import('ModuleDefaultExport3.js'), + (v)=>{ + assert.areEqual(2, v.default, 'Failed to import default from ModuleDefaultExport3.js'); + }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit); + `; + testScript(functionBody, "Exported value incorrectly bound", false, true); + testModuleScript(functionBody, "Exported value incorrectly bound", false, true); + + functionBody = + `testDynamicImport( + ()=>import('ModuleDefaultExport4.js'), + (v)=>{ + assert.areEqual(1, v.default, 'Failed to import default from ModuleDefaultExport4.js'); + }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit); + `; + testScript(functionBody, "Exported value incorrectly bound", false, true); + testModuleScript(functionBody, "Exported value incorrectly bound", false, true); + } + }, + { + name: "Import bindings used in a nested function", + body: function () { + let functionBody = + `function test(func) { + assert.areEqual('ModuleDefaultExport2', func(), 'Failed to import default from ModuleDefaultExport2.js'); + } + testDynamicImport( + ()=>import('ModuleDefaultExport2.js'), + (v)=>test(v.default), + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit); + `; + testScript(functionBody, "Failed to find imported name correctly in nested function", false, true); + testModuleScript(functionBody, "Failed to find imported name correctly in nested function", false, true); + } + }, + { + name: "Exported name may be any keyword", + body: function () { + let functionBody = + `testDynamicImport( + ()=>import('ModuleComplexExports.js'), + (v)=>{ + assert.areEqual('ModuleComplexExports', v.export, 'Failed to import export from ModuleDefaultExport2.js'); + assert.areEqual('ModuleComplexExports', v.function, 'Failed to import function from ModuleDefaultExport2.js'); + }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit); + `; + testScript(functionBody, "Exported name may be a keyword (import binding must be binding identifier)", false, true); + testModuleScript(functionBody, "Exported name may be a keyword (import binding must be binding identifier)", false, true); + } + }, + { + name: "Odd case of 'export { as as as }; import { as as as };'", + body: function () { + let functionBody = + `testDynamicImport( + ()=>import('ModuleComplexExports.js'), + (v)=>{ + assert.areEqual('as', v.as(), 'String "as" is not reserved word'); + }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit); + `; + testScript(functionBody, "Test 'import { as as as}'", false, true); + testModuleScript(functionBody, "Test 'import { as as as}'", false, true); + } + }, + { + name: "Typeof a module export", + body: function () { + let functionBody = + `testDynamicImport( + ()=>import('ModuleDefaultExport2.js'), + (v)=>{ + assert.areEqual('function', typeof v.default, 'typeof default export from ModuleDefaultExport2.js is function'); + }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit); + `; + + testScript(functionBody, "Typeof a module export", false, true); + testModuleScript(functionBody, "Typeof a module export", false, true); + } + }, + { + name: "Circular module dependency", + body: function () { + let functionBody = + `testDynamicImport( + ()=>import('ModuleCircularFoo.js'), + (v)=>{ + v.reset(); + assert.areEqual(2, v.circular_foo(), 'This function calls between both modules in the circular dependency incrementing a counter in each'); + assert.areEqual(4, v.rexportbar(), 'Second call originates in the other module but still increments the counter twice'); + }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit); + `; + + testScript(functionBody, "Circular module dependency", false, true); + testModuleScript(functionBody, "Circular module dependency", false, true); + + } + }, + { + name: "Implicitly re-exporting an import binding (import { foo } from ''; export { foo };)", + body: function () { + let functionBody = + `testDynamicImport( + ()=>import('ModuleComplexReexports.js'), + (v)=>{ + assert.areEqual('foo', v.foo(), 'Simple implicit re-export'); + assert.areEqual('foo', v.baz(), 'Renamed export imported and renamed during implicit re-export'); + assert.areEqual('foo', v.localfoo(), 'Export renamed as import and implicitly re-exported'); + assert.areEqual('foo', v.bar(), 'Renamed export renamed as import and renamed again during implicit re-exported'); + assert.areEqual('foo', v.localfoo2(), 'Renamed export renamed as import and implicitly re-exported'); + assert.areEqual('foo', v.bar2(), 'Renamed export renamed as import and renamed again during implicit re-export'); + }, + (err)=>{ assert.isTrue(false, err.message); }, _asyncEnter, _asyncExit); + `; + + testScript(functionBody, "Implicitly re-exporting an import binding (import { foo } from ''; export { foo };)", false, true); + testModuleScript(functionBody, "Implicitly re-exporting an import binding (import { foo } from ''; export { foo };)", false, true); + } + }, +]; + +testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); diff --git a/test/es6/es6_stable.baseline b/test/es6/es6_stable.baseline index 9e8eccdcaa0..c1f3b6d22f0 100644 --- a/test/es6/es6_stable.baseline +++ b/test/es6/es6_stable.baseline @@ -31,8 +31,8 @@ FLAG ES6 = 1 - setting child flag ES6IsConcatSpreadable = 1 FLAG ES6IsConcatSpreadable = 1 FLAG ES6 = 1 - setting child flag ES6Math = 1 FLAG ES6Math = 1 -FLAG ES6 = 1 - setting child flag ES6Module = 0 -FLAG ES6Module = 0 +FLAG ES6 = 1 - setting child flag ES6Module = 1 +FLAG ES6Module = 1 FLAG ES6 = 1 - setting child flag ES6Object = 1 FLAG ES6Object = 1 FLAG ES6 = 1 - setting child flag ES6Number = 1 diff --git a/test/es6/es6_stable.enable_disable.baseline b/test/es6/es6_stable.enable_disable.baseline index 044f97b6c0e..4af5766aa10 100644 --- a/test/es6/es6_stable.enable_disable.baseline +++ b/test/es6/es6_stable.enable_disable.baseline @@ -31,8 +31,8 @@ FLAG ES6 = 1 - setting child flag ES6IsConcatSpreadable = 1 FLAG ES6IsConcatSpreadable = 1 FLAG ES6 = 1 - setting child flag ES6Math = 1 FLAG ES6Math = 1 -FLAG ES6 = 1 - setting child flag ES6Module = 0 -FLAG ES6Module = 0 +FLAG ES6 = 1 - setting child flag ES6Module = 1 +FLAG ES6Module = 1 FLAG ES6 = 1 - setting child flag ES6Object = 1 FLAG ES6Object = 1 FLAG ES6 = 1 - setting child flag ES6Number = 1 diff --git a/test/es6/rlexe.xml b/test/es6/rlexe.xml index 3247f1035b3..1c52a0e318f 100644 --- a/test/es6/rlexe.xml +++ b/test/es6/rlexe.xml @@ -1361,6 +1361,13 @@ exclude_dynapogo,exclude_sanitize_address + + + dynamic-module-functionality.js + -ES6Module -args summary -endargs + exclude_sanitize_address + + module-syntax.js