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