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/Helpers.cpp b/bin/ch/Helpers.cpp index f139048af5b..6083f393f31 100644 --- a/bin/ch/Helpers.cpp +++ b/bin/ch/Helpers.cpp @@ -139,26 +139,30 @@ HRESULT Helpers::LoadScriptFromFile(LPCSTR filename, LPCSTR& contents, UINT* len // if (fopen_s(&file, filename, "rb") != 0) { -#ifdef _WIN32 - DWORD lastError = GetLastError(); - char16 wszBuff[512]; - fprintf(stderr, "Error in opening file '%s' ", filename); - wszBuff[0] = 0; - if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, - nullptr, - lastError, - 0, - wszBuff, - _countof(wszBuff), - nullptr)) + if (!HostConfigFlags::flags.AsyncModuleLoadIsEnabled) { - fwprintf(stderr, _u(": %s"), wszBuff); - } - fwprintf(stderr, _u("\n")); +#ifdef _WIN32 + DWORD lastError = GetLastError(); + char16 wszBuff[512]; + fprintf(stderr, "Error in opening file '%s' ", filename); + wszBuff[0] = 0; + if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, + lastError, + 0, + wszBuff, + _countof(wszBuff), + nullptr)) + { + fwprintf(stderr, _u(": %s"), wszBuff); + } + fwprintf(stderr, _u("\n")); #elif defined(_POSIX_VERSION) - fprintf(stderr, "Error in opening file: "); - perror(filename); + fprintf(stderr, "Error in opening file: "); + perror(filename); #endif + } + IfFailGo(E_FAIL); } diff --git a/bin/ch/HostConfigFlagsList.h b/bin/ch/HostConfigFlagsList.h index 3f73f2beee8..538cf793311 100644 --- a/bin/ch/HostConfigFlagsList.h +++ b/bin/ch/HostConfigFlagsList.h @@ -11,5 +11,6 @@ FLAG(int, InspectMaxStringLength, "Max string length to dump in locals FLAG(BSTR, Serialized, "If source is UTF8, deserializes from bytecode file", NULL) FLAG(bool, OOPJIT, "Run JIT in a separate process", false) FLAG(bool, EnsureCloseJITServer, "JIT process will be force closed when ch is terminated", true) +FLAG(bool, AsyncModuleLoad, "Silence host error output for module load failures to enable promise testing", false) #undef FLAG #endif diff --git a/bin/ch/WScriptJsrt.cpp b/bin/ch/WScriptJsrt.cpp index c6b3abaf259..28db362c258 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; } @@ -351,9 +359,10 @@ JsErrorCode WScriptJsrt::LoadModuleFromString(LPCSTR fileName, LPCSTR fileConten JsValueRef errorObject = JS_INVALID_REFERENCE; // ParseModuleSource is sync, while additional fetch & evaluation are async. + unsigned int fileContentLength = (fileContent == nullptr) ? 0 : (unsigned int)strlen(fileContent); errorCode = ChakraRTInterface::JsParseModuleSource(requestModule, dwSourceCookie, (LPBYTE)fileContent, - (unsigned int)strlen(fileContent), JsParseModuleSourceFlags_DataIsUTF8, &errorObject); - if ((errorCode != JsNoError) && errorObject != JS_INVALID_REFERENCE) + fileContentLength, JsParseModuleSourceFlags_DataIsUTF8, &errorObject); + if ((errorCode != JsNoError) && errorObject != JS_INVALID_REFERENCE && fileContent != nullptr) { ChakraRTInterface::JsSetException(errorObject); return errorCode; @@ -875,10 +884,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 @@ -1217,7 +1240,12 @@ HRESULT WScriptJsrt::ModuleMessage::Call(LPCSTR fileName) hr = Helpers::LoadScriptFromFile(specifierStr.GetString(), fileContent); if (FAILED(hr)) { - fprintf(stderr, "Couldn't load file.\n"); + if (!HostConfigFlags::flags.AsyncModuleLoadIsEnabled) + { + fprintf(stderr, "Couldn't load file.\n"); + } + + LoadScript(nullptr, specifierStr.GetString(), nullptr, "module", true, WScriptJsrt::FinalizeFree); } else { @@ -1228,12 +1256,8 @@ HRESULT WScriptJsrt::ModuleMessage::Call(LPCSTR fileName) return errorCode; } -// Callback from chakracore to fetch dependent module. In the test harness, -// we are not doing any translation, just treat the specifier as fileName. -// While this call will come back directly from ParseModuleSource, the additional -// task are treated as Promise that will be executed later. -JsErrorCode WScriptJsrt::FetchImportedModule(_In_ JsModuleRecord referencingModule, - _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord) +JsErrorCode WScriptJsrt::FetchImportedModuleHelper(JsModuleRecord referencingModule, + JsValueRef specifier, __out JsModuleRecord* dependentModuleRecord) { JsModuleRecord moduleRecord = JS_INVALID_REFERENCE; AutoString specifierStr; @@ -1267,6 +1291,27 @@ JsErrorCode WScriptJsrt::FetchImportedModule(_In_ JsModuleRecord referencingModu return errorCode; } +// Callback from chakracore to fetch dependent module. In the test harness, +// we are not doing any translation, just treat the specifier as fileName. +// While this call will come back directly from ParseModuleSource, the additional +// task are treated as Promise that will be executed later. +JsErrorCode WScriptJsrt::FetchImportedModule(_In_ JsModuleRecord referencingModule, + _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord) +{ + return FetchImportedModuleHelper(referencingModule, specifier, dependentModuleRecord); +} + +// 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_ JsSourceContext dwReferencingSourceContext, + _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord) +{ + // ch.exe assumes all imported source files are located at . + return FetchImportedModuleHelper(nullptr, specifier, dependentModuleRecord); +} + // 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..c6f1b9cb8ca 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) @@ -116,6 +118,8 @@ class WScriptJsrt static JsValueRef CALLBACK LoadTextFileCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); static JsValueRef CALLBACK FlagCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); + static JsErrorCode FetchImportedModuleHelper(JsModuleRecord referencingModule, JsValueRef specifier, __out JsModuleRecord* dependentModuleRecord); + static MessageQueue *messageQueue; static DWORD_PTR sourceContext; static std::map moduleRecordMap; 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..716d3bac5f8 100644 --- a/lib/Backend/Lower.cpp +++ b/lib/Backend/Lower.cpp @@ -2844,6 +2844,20 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa break; } + case Js::OpCode::ImportCall: + { + IR::Opnd *src1Opnd = instr->UnlinkSrc1(); + IR::Opnd *functionObjOpnd = nullptr; + m_lowererMD.LoadFunctionObjectOpnd(instr, functionObjOpnd); + + LoadScriptContext(instr); + m_lowererMD.LoadHelperArgument(instr, src1Opnd); + m_lowererMD.LoadHelperArgument(instr, functionObjOpnd); + 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 d3add86da5c..cd1dfe8d36d 100644 --- a/lib/Common/ConfigFlagsList.h +++ b/lib/Common/ConfigFlagsList.h @@ -532,7 +532,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) @@ -998,10 +998,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/Memory/ArenaAllocator.h b/lib/Common/Memory/ArenaAllocator.h index 18830c33dfa..e4ce0478b29 100644 --- a/lib/Common/Memory/ArenaAllocator.h +++ b/lib/Common/Memory/ArenaAllocator.h @@ -21,6 +21,12 @@ namespace Memory #define Adelete(alloc, obj) AllocatorDelete(ArenaAllocator, alloc, obj) #define AdeletePlus(alloc, size, obj) AllocatorDeletePlus(ArenaAllocator, alloc, size, obj) #define AdeleteArray(alloc, count, obj) AllocatorDeleteArray(ArenaAllocator, alloc, count, obj) +#define AdeleteUnlessNull(alloc, obj) \ + if (obj != nullptr) \ + { \ + Adelete(alloc, obj); \ + obj = nullptr; \ + } #define AnewNoThrow(alloc,T,...) AllocatorNewNoThrow(ArenaAllocator, alloc, T, __VA_ARGS__) diff --git a/lib/Jsrt/ChakraCore.h b/lib/Jsrt/ChakraCore.h index d999f287ab3..b03197df15c 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_ JsSourceContext 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..c604b86e3b2 100644 --- a/lib/Jsrt/Core/JsrtContextCore.cpp +++ b/lib/Jsrt/Core/JsrtContextCore.cpp @@ -101,6 +101,25 @@ void JsrtContextCore::OnScriptLoad(Js::JavascriptFunction * scriptFunction, Js:: } HRESULT ChakraCoreHostScriptContext::FetchImportedModule(Js::ModuleRecordBase* referencingModule, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) +{ + return FetchImportedModuleHelper( + [=](Js::JavascriptString *specifierVar, JsModuleRecord *dependentRecord) -> JsErrorCode + { + return fetchImportedModuleCallback(referencingModule, specifierVar, dependentRecord); + }, specifier, dependentModuleRecord); +} + +HRESULT ChakraCoreHostScriptContext::FetchImportedModuleFromScript(JsSourceContext dwReferencingSourceContext, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) +{ + return FetchImportedModuleHelper( + [=](Js::JavascriptString *specifierVar, JsModuleRecord *dependentRecord) -> JsErrorCode + { + return fetchImportedModuleFromScriptCallback(dwReferencingSourceContext, specifierVar, dependentRecord); + }, specifier, dependentModuleRecord); +} + +template +HRESULT ChakraCoreHostScriptContext::FetchImportedModuleHelper(Fn fetch, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) { if (fetchImportedModuleCallback == nullptr) { @@ -110,7 +129,7 @@ HRESULT ChakraCoreHostScriptContext::FetchImportedModule(Js::ModuleRecordBase* r JsModuleRecord dependentRecord = JS_INVALID_REFERENCE; { AUTO_NO_EXCEPTION_REGION; - JsErrorCode errorCode = fetchImportedModuleCallback(referencingModule, specifierVar, &dependentRecord); + JsErrorCode errorCode = fetch(specifierVar, &dependentRecord); if (errorCode == JsNoError) { *dependentModuleRecord = static_cast(dependentRecord); diff --git a/lib/Jsrt/Core/JsrtContextCore.h b/lib/Jsrt/Core/JsrtContextCore.h index aaada307bb7..8fee8b7b1ad 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(JsSourceContext 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 { @@ -186,6 +190,9 @@ class ChakraCoreHostScriptContext sealed : public HostScriptContext #endif private: + template + HRESULT FetchImportedModuleHelper(Fn fetch, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord); FetchImportedModuleCallBack fetchImportedModuleCallback; + FetchImportedModuleFromScriptCallBack fetchImportedModuleFromScriptCallback; NotifyModuleReadyCallback notifyModuleReadyCallback; }; diff --git a/lib/Jsrt/Core/JsrtCore.cpp b/lib/Jsrt/Core/JsrtCore.cpp index b2a6451beee..df078d128bc 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; @@ -203,6 +206,9 @@ JsGetModuleHostInfo( case JsModuleHostInfo_FetchImportedModuleCallback: *hostInfo = reinterpret_cast(currentContext->GetHostScriptContext()->GetFetchImportedModuleCallback()); break; + case JsModuleHostInfo_FetchImportedModuleFromScriptCallback: + *hostInfo = reinterpret_cast(currentContext->GetHostScriptContext()->GetFetchImportedModuleFromScriptCallback()); + break; case JsModuleHostInfo_NotifyModuleReadyCallback: *hostInfo = reinterpret_cast(currentContext->GetHostScriptContext()->GetNotifyModuleReadyCallback()); 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..2054d2a5eab 100644 --- a/lib/Parser/Parse.cpp +++ b/lib/Parser/Parse.cpp @@ -2435,12 +2435,37 @@ bool Parser::IsImportOrExportStatementValidHere() && this->m_tryCatchOrFinallyDepth == 0; } +template ParseNodePtr Parser::ParseImportCall() +{ + m_pscan->Scan(); + ParseNodePtr specifier = ParseExpr(koplCma, nullptr, /* fAllowIn */FALSE, /* fAllowEllipsis */FALSE); + if (m_token.tk != tkRParen) + { + Error(ERRnoRparen); + } + + m_pscan->Scan(); + return CreateCallNode(knopCall, CreateNodeWithScanner(), specifier); +} + template -ParseNodePtr Parser::ParseImportDeclaration() +ParseNodePtr Parser::ParseImport() { Assert(m_scriptContext->GetConfig()->IsES6ModuleEnabled()); Assert(m_token.tk == tkIMPORT); + RestorePoint parsedImport; + m_pscan->Capture(&parsedImport); + m_pscan->Scan(); + + // import() + if (m_token.tk == tkLParen) + { + return ParseImportCall(); + } + + m_pscan->SeekTo(parsedImport); + if (!IsImportOrExportStatementValidHere()) { Error(ERRInvalidModuleImportOrExport); @@ -3227,6 +3252,23 @@ LFunction : } break; + case tkIMPORT: + if (m_scriptContext->GetConfig()->IsES6ModuleEnabled()) + { + m_pscan->Scan(); + if (m_token.tk == tkLParen) + { + return ParseImportCall(); + } + + Error(ERRnoLparen); + } + else + { + goto LUnknown; + } + break; + case tkCASE: { if (!m_doingFastScan) @@ -10112,12 +10154,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..1e97fbb58a6 100644 --- a/lib/Parser/Parse.h +++ b/lib/Parser/Parse.h @@ -861,8 +861,9 @@ class Parser bool IsImportOrExportStatementValidHere(); - template ParseNodePtr ParseImportDeclaration(); + template ParseNodePtr ParseImport(); template void ParseImportClause(ModuleImportOrExportEntryList* importEntryList, bool parsingAfterComma = false); + template ParseNodePtr ParseImportCall(); template ParseNodePtr ParseExportDeclaration(); template ParseNodePtr ParseDefaultExportClause(); 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/FunctionBody.cpp b/lib/Runtime/Base/FunctionBody.cpp index b157bf48398..c38b82becb7 100644 --- a/lib/Runtime/Base/FunctionBody.cpp +++ b/lib/Runtime/Base/FunctionBody.cpp @@ -203,6 +203,11 @@ namespace Js scriptContext->GetDebugContext()->RegisterFunction(this, pszTitle); } + bool ParseableFunctionInfo::IsES6ModuleCode() const + { + return (GetGrfscr() & fscrIsModuleCode) == fscrIsModuleCode; + } + // Given an offset into the source buffer, determine if the end of this SourceInfo // lies after the given offset. bool @@ -3171,7 +3176,7 @@ namespace Js Js::RootObjectBase * FunctionBody::LoadRootObject() const { - if ((this->GetGrfscr() & fscrIsModuleCode) == fscrIsModuleCode || this->GetModuleID() == kmodGlobal) + if (this->IsES6ModuleCode() || this->GetModuleID() == kmodGlobal) { return JavascriptOperators::OP_LdRoot(this->GetScriptContext()); } diff --git a/lib/Runtime/Base/FunctionBody.h b/lib/Runtime/Base/FunctionBody.h index af14d150421..50ea086f1a1 100644 --- a/lib/Runtime/Base/FunctionBody.h +++ b/lib/Runtime/Base/FunctionBody.h @@ -2094,6 +2094,7 @@ namespace Js DeferredFunctionStub *GetDeferredStubs() const { return static_cast(this->GetAuxPtr(AuxPointerType::DeferredStubs)); } void SetDeferredStubs(DeferredFunctionStub *stub) { this->SetAuxPtr(AuxPointerType::DeferredStubs, stub); } void RegisterFuncToDiag(ScriptContext * scriptContext, char16 const * pszTitle); + bool IsES6ModuleCode() const; protected: static HRESULT MapDeferredReparseError(HRESULT& hrParse, const CompileScriptException& se); 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.h b/lib/Runtime/Base/ThreadContext.h index 543ce0e5169..2793fe6fae3 100644 --- a/lib/Runtime/Base/ThreadContext.h +++ b/lib/Runtime/Base/ThreadContext.h @@ -133,6 +133,30 @@ extern "C" void* MarkerForExternalDebugStep(); }\ } +#define LEAVE_SCRIPT_IF(scriptContext, condition, block) \ + if (condition) \ + { \ + BEGIN_LEAVE_SCRIPT(scriptContext); \ + block \ + END_LEAVE_SCRIPT(scriptContext); \ + } \ + else \ + { \ + block \ + } + +#define ENTER_SCRIPT_IF(scriptContext, doCleanup, isCallRoot, hasCaller, condition, block) \ + if (condition) \ + { \ + BEGIN_ENTER_SCRIPT(scriptContext, doCleanup, isCallRoot, hasCaller); \ + block \ + END_ENTER_SCRIPT(scriptContext, doCleanup, isCallRoot, hasCaller); \ + } \ + else \ + { \ + block \ + } + #define BEGIN_LEAVE_SCRIPT(scriptContext) \ LEAVE_SCRIPT_START_EX(scriptContext, /* stackProbe */ true, /* leaveForHost */ true, /* isFPUControlRestoreNeeded */ false) diff --git a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp index 5d41c3d5c2d..2545d265662 100644 --- a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp +++ b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp @@ -10803,6 +10803,15 @@ 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; + Assert(CountArguments(args) == 2); // import() takes one argument + 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..4dad21b1310 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, 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/InterpreterStackFrame.cpp b/lib/Runtime/Language/InterpreterStackFrame.cpp index 154a698b4b1..727ada672ce 100644 --- a/lib/Runtime/Language/InterpreterStackFrame.cpp +++ b/lib/Runtime/Language/InterpreterStackFrame.cpp @@ -7555,6 +7555,11 @@ const byte * InterpreterStackFrame::OP_ProfiledLoopBodyStart(const byte * ip) return JavascriptOperators::OP_ScopedLdFuncObj(function, scriptContext); } + Var InterpreterStackFrame::OP_ImportCall(Var specifier, ScriptContext *scriptContext) + { + return JavascriptOperators::OP_ImportCall(function, specifier, scriptContext); + } + void InterpreterStackFrame::ValidateRegValue(Var value, bool allowStackVar, bool allowStackVarOnDisabledStackNestedFunc) const { #if DBG diff --git a/lib/Runtime/Language/InterpreterStackFrame.h b/lib/Runtime/Language/InterpreterStackFrame.h index ac506a57e36..1cb804d65d2 100644 --- a/lib/Runtime/Language/InterpreterStackFrame.h +++ b/lib/Runtime/Language/InterpreterStackFrame.h @@ -727,6 +727,7 @@ namespace Js template const byte * OP_ProfiledLoopBodyStart(const byte *ip); template void OP_ApplyArgs(const unaligned OpLayoutT_Reg5 * playout); template void OP_EmitTmpRegCount(const unaligned OpLayoutT_Unsigned1 * ip); + Var OP_ImportCall(Var specifier, ScriptContext *scriptContext); HeapArgumentsObject * CreateEmptyHeapArgumentsObject(ScriptContext* scriptContext); void TrySetFrameObjectInHeapArgObj(ScriptContext * scriptContext, bool hasNonSimpleParam, bool isScopeObjRestored); diff --git a/lib/Runtime/Language/JavascriptOperators.cpp b/lib/Runtime/Language/JavascriptOperators.cpp index 205c1dda12a..ed905237130 100644 --- a/lib/Runtime/Language/JavascriptOperators.cpp +++ b/lib/Runtime/Language/JavascriptOperators.cpp @@ -9639,6 +9639,112 @@ namespace Js return superCtor; } + Var JavascriptOperators::OP_ImportCall(__in JavascriptFunction *function, __in Var specifier, __in ScriptContext* scriptContext) + { + ModuleRecordBase *moduleRecordBase = nullptr; + SourceTextModuleRecord *moduleRecord = nullptr; + + FunctionBody* parentFuncBody = function->GetFunctionBody(); + JavascriptString *specifierString = nullptr; + + try + { + specifierString = JavascriptConversion::ToString(specifier, scriptContext); + } + catch (const JavascriptException &err) + { + Var errorObject = err.GetAndClear()->GetThrownObject(scriptContext); + AssertMsg(errorObject != nullptr, "OP_ImportCall: null error object thrown by ToString(specifier)"); + if (errorObject != nullptr) + { + return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, errorObject, scriptContext); + } + + Throw::InternalError(); + } + + DWORD_PTR dwReferencingSourceContext = parentFuncBody->GetHostSourceContext(); + if (!parentFuncBody->IsES6ModuleCode() && dwReferencingSourceContext == Js::Constants::NoHostSourceContext) + { + // import() called from eval + if (parentFuncBody->GetUtf8SourceInfo()->GetCallerUtf8SourceInfo() == nullptr) + { + JavascriptError *error = scriptContext->GetLibrary()->CreateError(); + JavascriptError::SetErrorMessageProperties(error, E_FAIL, _u("Unable to locate active script or module that calls import()"), scriptContext); + return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, error, scriptContext); + } + + dwReferencingSourceContext = parentFuncBody->GetUtf8SourceInfo()->GetCallerUtf8SourceInfo()->GetSourceContextInfo()->dwHostSourceContext; + + if (dwReferencingSourceContext == Js::Constants::NoHostSourceContext) + { + // Walk the call stack if caller function is neither module code nor having host source context + + JavascriptFunction* caller = nullptr; + Js::JavascriptStackWalker walker(scriptContext); + walker.GetCaller(&caller); + + do + { + if (walker.GetCaller(&caller) && caller != nullptr && caller->IsScriptFunction()) + { + parentFuncBody = caller->GetFunctionBody(); + dwReferencingSourceContext = parentFuncBody->GetHostSourceContext(); + } + else + { + JavascriptError *error = scriptContext->GetLibrary()->CreateError(); + JavascriptError::SetErrorMessageProperties(error, E_FAIL, _u("Unable to locate active script or module that calls import()"), scriptContext); + return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, error, scriptContext); + } + + } while (!parentFuncBody->IsES6ModuleCode() && dwReferencingSourceContext == Js::Constants::NoHostSourceContext); + } + } + + LPCOLESTR moduleName = specifierString->GetSz(); + HRESULT hr = 0; + + if (parentFuncBody->IsES6ModuleCode()) + { + SourceTextModuleRecord *referenceModuleRecord = parentFuncBody->GetScriptContext()->GetLibrary()->GetModuleRecord(parentFuncBody->GetModuleID()); + BEGIN_LEAVE_SCRIPT(scriptContext); + BEGIN_TRANSLATE_TO_HRESULT(static_cast(ExceptionType_OutOfMemory | ExceptionType_StackOverflow)); + hr = scriptContext->GetHostScriptContext()->FetchImportedModule(referenceModuleRecord, moduleName, &moduleRecordBase); + END_TRANSLATE_EXCEPTION_TO_HRESULT(hr); + END_LEAVE_SCRIPT(scriptContext); + } + else + { + Assert(dwReferencingSourceContext != Js::Constants::NoHostSourceContext); + BEGIN_LEAVE_SCRIPT(scriptContext); + BEGIN_TRANSLATE_TO_HRESULT(static_cast(ExceptionType_OutOfMemory | ExceptionType_StackOverflow)); + hr = scriptContext->GetHostScriptContext()->FetchImportedModuleFromScript(dwReferencingSourceContext, moduleName, &moduleRecordBase); + END_TRANSLATE_EXCEPTION_TO_HRESULT(hr); + END_LEAVE_SCRIPT(scriptContext); + } + + if (FAILED(hr)) + { + Js::JavascriptError *error = scriptContext->GetLibrary()->CreateURIError(); + JavascriptError::SetErrorMessageProperties(error, hr, moduleName, scriptContext); + return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, error, scriptContext); + } + + moduleRecord = SourceTextModuleRecord::FromHost(moduleRecordBase); + + if (moduleRecord->GetErrorObject() != nullptr) + { + return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, moduleRecord->GetErrorObject(), scriptContext, moduleRecord); + } + else if (moduleRecord->WasEvaluated()) + { + return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(true, moduleRecord->GetNamespace(), scriptContext, moduleRecord); + } + + return moduleRecord->PostProcessDynamicModuleImport(); + } + 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..09b0ac355c7 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(__in JavascriptFunction *function, __in Var specifier, __in 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..e534ae0f4b7 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), + promise(nullptr), localExportCount(0) { namespaceRecord.module = this; @@ -74,15 +74,11 @@ namespace Js localExportRecordList = nullptr; indirectExportRecordList = nullptr; starExportRecordList = nullptr; - childrenModuleSet = nullptr; parentModuleList = nullptr; if (!isShutdown) { - if (parser != nullptr) - { - AllocatorDelete(ArenaAllocator, scriptContext->GeneralAllocator(), parser); - parser = nullptr; - } + AdeleteUnlessNull(scriptContext->GeneralAllocator(), parser); + AdeleteUnlessNull(scriptContext->GeneralAllocator(), childrenModuleSet); } } @@ -91,6 +87,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 +95,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 +105,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 @@ -114,7 +114,7 @@ namespace Js try { AUTO_NESTED_HANDLED_EXCEPTION_TYPE((ExceptionType)(ExceptionType_OutOfMemory | ExceptionType_StackOverflow)); - this->parser = (Parser*)AllocatorNew(ArenaAllocator, allocator, Parser, scriptContext); + this->parser = Anew(allocator, Parser, scriptContext); srcInfo->moduleID = moduleId; LoadScriptFlag loadScriptFlag = (LoadScriptFlag)(LoadScriptFlag_Expression | LoadScriptFlag_Module | @@ -166,7 +166,7 @@ namespace Js if (this->parser) { this->parseTree = nullptr; - AllocatorDelete(ArenaAllocator, allocator, this->parser); + Adelete(allocator, this->parser); this->parser = nullptr; } if (this->errorObject == nullptr) @@ -175,6 +175,11 @@ namespace Js } OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("\t>NotifyParentAsNeeded\n")); + if (this->promise != nullptr) + { + SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->errorObject, this->scriptContext, this); + } + NotifyParentsAsNeeded(); } return hr; @@ -190,9 +195,7 @@ namespace Js parentModule->OnChildModuleReady(this, this->errorObject); }); } -#if DBG SetParentsNotified(); -#endif } void SourceTextModuleRecord::ImportModuleListsFromParser() @@ -220,6 +223,55 @@ namespace Js return hr; } + Var SourceTextModuleRecord::PostProcessDynamicModuleImport() + { + JavascriptPromise *promise = this->GetPromise(); + ScriptContext* scriptContext = GetScriptContext(); + AnalysisAssert(scriptContext != nullptr); + if (promise == nullptr) + { + promise = JavascriptPromise::CreateEnginePromise(scriptContext); + this->SetPromise(promise); + } + + if (this->ParentsNotified()) + { + HRESULT hr = NOERROR; + if (!WasDeclarationInitialized()) + { + ModuleDeclarationInstantiation(); + + if (this->errorObject != nullptr) + { + SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->errorObject, scriptContext, this); + } + else + { + if (!hadNotifyHostReady && !WasEvaluated()) + { + bool isScriptActive = scriptContext->GetThreadContext()->IsScriptActive(); + + LEAVE_SCRIPT_IF(scriptContext, isScriptActive, + { + hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject); + }); + + hadNotifyHostReady = true; + } + } + } + + if (FAILED(hr)) + { + Js::JavascriptError * error = scriptContext->GetLibrary()->CreateURIError(); + JavascriptError::SetErrorMessageProperties(error, hr, this->GetSpecifierSz(), scriptContext); + return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, error, scriptContext, this); + } + } + + return this->promise; + } + HRESULT SourceTextModuleRecord::PrepareForModuleDeclarationInitialization() { OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("PrepareForModuleDeclarationInitialization(%s)\n"), this->GetSpecifierSz()); @@ -230,17 +282,22 @@ namespace Js OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("\t>NotifyParentsAsNeeded\n")); NotifyParentsAsNeeded(); - if (!WasDeclarationInitialized() && isRootModule) + if (this->promise != 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()); + bool isScriptActive = scriptContext->GetThreadContext()->IsScriptActive(); + Assert(!isScriptActive || this->promise != nullptr); ModuleDeclarationInstantiation(); - if (!hadNotifyHostReady) + if (!hadNotifyHostReady && !WasEvaluated()) { - hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject); + LEAVE_SCRIPT_IF(scriptContext, isScriptActive, + { + hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject); + }); + hadNotifyHostReady = true; } } @@ -268,9 +325,21 @@ namespace Js OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("\t>NotifyParentAsNeeded (childException)\n"), this->GetSpecifierSz()); NotifyParentsAsNeeded(); - if (isRootModule && !hadNotifyHostReady) + + bool isScriptActive = scriptContext->GetThreadContext()->IsScriptActive(); + + if (this->promise != nullptr) + { + SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->errorObject, this->scriptContext, this); + } + + if (this->promise != nullptr || (isRootModule && !hadNotifyHostReady)) { - hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject); + LEAVE_SCRIPT_IF(scriptContext, isScriptActive, + { + hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject); + }); + hadNotifyHostReady = true; } } @@ -311,7 +380,7 @@ namespace Js ArenaAllocator* allocator = scriptContext->GeneralAllocator(); if (exportStarSet == nullptr) { - exportStarSet = (ExportModuleRecordList*)AllocatorNew(ArenaAllocator, allocator, ExportModuleRecordList, allocator); + exportStarSet = Anew(allocator, ExportModuleRecordList, allocator); } if (exportStarSet->Has(this)) { @@ -321,7 +390,7 @@ namespace Js ExportedNames* tempExportedNames = nullptr; if (this->localExportRecordList != nullptr) { - tempExportedNames = (ExportedNames*)AllocatorNew(ArenaAllocator, allocator, ExportedNames, allocator); + tempExportedNames = Anew(allocator, ExportedNames, allocator); this->localExportRecordList->Map([=](ModuleImportOrExportEntry exportEntry) { PropertyId exportNameId = EnsurePropertyIdForIdentifier(exportEntry.exportName); tempExportedNames->Prepend(exportNameId); @@ -331,7 +400,7 @@ namespace Js { if (tempExportedNames == nullptr) { - tempExportedNames = (ExportedNames*)AllocatorNew(ArenaAllocator, allocator, ExportedNames, allocator); + tempExportedNames = Anew(allocator, ExportedNames, allocator); } this->indirectExportRecordList->Map([=](ModuleImportOrExportEntry exportEntry) { PropertyId exportedNameId = EnsurePropertyIdForIdentifier(exportEntry.exportName); @@ -342,7 +411,7 @@ namespace Js { if (tempExportedNames == nullptr) { - tempExportedNames = (ExportedNames*)AllocatorNew(ArenaAllocator, allocator, ExportedNames, allocator); + tempExportedNames = Anew(allocator, ExportedNames, allocator); } this->starExportRecordList->Map([=](ModuleImportOrExportEntry exportEntry) { Assert(exportEntry.moduleRequest != nullptr); @@ -409,7 +478,7 @@ namespace Js ArenaAllocator* allocator = scriptContext->GeneralAllocator(); if (resolvedExportMap == nullptr) { - resolvedExportMap = AllocatorNew(ArenaAllocator, allocator, ResolvedExportMap, allocator); + resolvedExportMap = Anew(allocator, ResolvedExportMap, allocator); } if (resolvedExportMap->TryGetReference(exportName, exportRecord)) { @@ -418,11 +487,11 @@ namespace Js // TODO: use per-call/loop allocator? if (exportStarSet == nullptr) { - exportStarSet = (ExportModuleRecordList*)AllocatorNew(ArenaAllocator, allocator, ExportModuleRecordList, allocator); + exportStarSet = Anew(allocator, ExportModuleRecordList, allocator); } if (resolveSet == nullptr) { - resolveSet = (ResolveSet*)AllocatorNew(ArenaAllocator, allocator, ResolveSet, allocator); + resolveSet = Anew(allocator, ResolveSet, allocator); } *exportRecord = nullptr; @@ -586,7 +655,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 +678,15 @@ namespace Js } } + void SourceTextModuleRecord::EnsureChildModuleSet(ScriptContext *scriptContext) + { + if (nullptr == this->childrenModuleSet) + { + ArenaAllocator* allocator = scriptContext->GeneralAllocator(); + this->childrenModuleSet = Anew(allocator, ChildModuleRecordSet, allocator); + } + } + HRESULT SourceTextModuleRecord::ResolveExternalModuleDependencies() { OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("ResolveExternalModuleDependencies(%s)\n"), this->GetSpecifierSz()); @@ -618,11 +696,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; @@ -739,19 +813,23 @@ namespace Js { return nullptr; } - Assert(this->errorObject == nullptr); + if (this->errorObject != nullptr) { - JavascriptExceptionOperators::Throw(errorObject, scriptContext); + if (this->promise != nullptr) + { + SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->errorObject, this->scriptContext, this); + return scriptContext->GetLibrary()->GetUndefined(); + } + else + { + JavascriptExceptionOperators::Throw(errorObject, this->scriptContext); + } } - Assert(!WasEvaluated()); + + Assert(this->errorObject == nullptr); SetWasEvaluated(); - // we shouldn't evaluate if there are existing failure. This is defense in depth as the host shouldn't - // call into evaluation if there was previous failure on the module. - if (this->errorObject) - { - return this->errorObject; - } + if (childrenModuleSet != nullptr) { childrenModuleSet->EachValue([=](SourceTextModuleRecord* childModuleRecord) @@ -765,7 +843,41 @@ namespace Js CleanupBeforeExecution(); Arguments outArgs(CallInfo(CallFlags_Value, 0), nullptr); - return rootFunction->CallRootFunction(outArgs, scriptContext, true); + + Var ret = nullptr; + JavascriptExceptionObject *exception = nullptr; + try + { + AUTO_NESTED_HANDLED_EXCEPTION_TYPE((ExceptionType)(ExceptionType_OutOfMemory | ExceptionType_JavascriptException)); + ENTER_SCRIPT_IF(scriptContext, true, false, false, !scriptContext->GetThreadContext()->IsScriptActive(), + { + ret = rootFunction->CallRootFunction(outArgs, scriptContext, true); + }); + } + catch (const Js::JavascriptException &err) + { + exception = err.GetAndClear(); + Var errorObject = exception->GetThrownObject(scriptContext); + AssertOrFailFastMsg(errorObject != nullptr, "ModuleEvaluation: null error object thrown from root function"); + this->errorObject = errorObject; + if (this->promise != nullptr) + { + ResolveOrRejectDynamicImportPromise(false, errorObject, scriptContext, this); + return scriptContext->GetLibrary()->GetUndefined(); + } + } + + if (exception != nullptr) + { + JavascriptExceptionOperators::DoThrowCheckClone(exception, scriptContext); + } + + if (this->promise != nullptr) + { + SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(true, this->GetNamespace(), this->GetScriptContext(), this); + } + + return ret; } HRESULT SourceTextModuleRecord::OnHostException(void* errorVar) @@ -836,9 +948,9 @@ namespace Js if (localExportRecordList != nullptr) { ArenaAllocator* allocator = scriptContext->GeneralAllocator(); - localExportMapByExportName = AllocatorNew(ArenaAllocator, allocator, LocalExportMap, allocator); - localExportMapByLocalName = AllocatorNew(ArenaAllocator, allocator, LocalExportMap, allocator); - localExportIndexList = AllocatorNew(ArenaAllocator, allocator, LocalExportIndexList, allocator); + localExportMapByExportName = Anew(allocator, LocalExportMap, allocator); + localExportMapByLocalName = Anew(allocator, LocalExportMap, allocator); + localExportIndexList = Anew(allocator, LocalExportIndexList, allocator); localExportRecordList->Map([&](ModuleImportOrExportEntry exportEntry) { Assert(exportEntry.moduleRequest == nullptr); @@ -983,4 +1095,39 @@ namespace Js } return slotIndex; } + + // static + Var SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(bool isResolve, Var value, ScriptContext *scriptContext, SourceTextModuleRecord *moduleRecord) + { + bool isScriptActive = scriptContext->GetThreadContext()->IsScriptActive(); + JavascriptPromise *promise = nullptr; + if (moduleRecord != nullptr) + { + promise = moduleRecord->GetPromise(); + } + + if (promise == nullptr) + { + promise = JavascriptPromise::CreateEnginePromise(scriptContext); + } + + ENTER_SCRIPT_IF(scriptContext, true, false, false, !isScriptActive, + { + if (isResolve) + { + promise->Resolve(value, scriptContext); + } + else + { + promise->Reject(value, scriptContext); + } + }); + + if (moduleRecord != nullptr) + { + moduleRecord->SetPromise(nullptr); + } + + return promise; + } } \ No newline at end of file diff --git a/lib/Runtime/Language/SourceTextModuleRecord.h b/lib/Runtime/Language/SourceTextModuleRecord.h index 7beab815b94..49beaf1a7ad 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,9 @@ 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; } + JavascriptPromise *GetPromise() { return this->promise; } + void SetPromise(JavascriptPromise *value) { this->promise = value; } void SetImportRecordList(ModuleImportOrExportEntryList* importList) { importRecordList = importList; } void SetLocalExportRecordList(ModuleImportOrExportEntryList* localExports) { localExportRecordList = localExports; } @@ -103,6 +102,8 @@ namespace Js void SetParent(SourceTextModuleRecord* parentRecord, LPCOLESTR moduleName); Utf8SourceInfo* GetSourceInfo() { return this->pSourceInfo; } + static Var ResolveOrRejectDynamicImportPromise(bool isResolve, Var value, ScriptContext *scriptContext, SourceTextModuleRecord *mr = nullptr); + Var PostProcessDynamicModuleImport(); private: const static uint InvalidModuleIndex = 0xffffffff; @@ -112,9 +113,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 +147,7 @@ namespace Js Field(uint) moduleId; Field(ModuleNameRecord) namespaceRecord; + Field(JavascriptPromise*) promise; HRESULT PostParseProcess(); HRESULT PrepareForModuleDeclarationInitialization(); @@ -158,6 +158,8 @@ namespace Js void InitializeLocalImports(); void InitializeLocalExports(); void InitializeIndirectExports(); + bool ParentsNotified() const { return parentsNotified; } + void SetParentsNotified() { parentsNotified = true; } PropertyId EnsurePropertyIdForIdentifier(IdentPtr pid); LocalExportMap* GetLocalExportMap() const { return localExportMapByExportName; } LocalExportIndexList* GetLocalExportIndexList() const { return localExportIndexList; } diff --git a/lib/Runtime/Library/JavascriptPromise.cpp b/lib/Runtime/Library/JavascriptPromise.cpp index b8d0b794996..6de84251243 100644 --- a/lib/Runtime/Library/JavascriptPromise.cpp +++ b/lib/Runtime/Library/JavascriptPromise.cpp @@ -1863,4 +1863,16 @@ namespace Js return args[0]; } + + //static + JavascriptPromise* JavascriptPromise::CreateEnginePromise(ScriptContext *scriptContext) + { + JavascriptPromiseResolveOrRejectFunction *resolve = nullptr; + JavascriptPromiseResolveOrRejectFunction *reject = nullptr; + + JavascriptPromise *promise = scriptContext->GetLibrary()->CreatePromise(); + JavascriptPromise::InitializePromise(promise, &resolve, &reject, scriptContext); + + return promise; + } } // namespace Js diff --git a/lib/Runtime/Library/JavascriptPromise.h b/lib/Runtime/Library/JavascriptPromise.h index 18fe35fee8a..ef85ea853e4 100644 --- a/lib/Runtime/Library/JavascriptPromise.h +++ b/lib/Runtime/Library/JavascriptPromise.h @@ -442,6 +442,8 @@ namespace Js static Var TryCallResolveOrRejectHandler(Var handler, Var value, ScriptContext* scriptContext); static Var TryRejectWithExceptionObject(JavascriptExceptionObject* exceptionObject, Var handler, ScriptContext* scriptContext); + static JavascriptPromise* CreateEnginePromise(ScriptContext *scriptContext); + Var Resolve(Var resolution, ScriptContext* scriptContext); Var Reject(Var resolution, ScriptContext* scriptContext); 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..d69dfa4cb4b --- /dev/null +++ b/test/es6/dynamic-module-functionality.js @@ -0,0 +1,372 @@ +//------------------------------------------------------------------------------------------------------- +// 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.fail(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.fail(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.fail(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.fail(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.fail(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.fail(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.fail(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.fail(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.fail(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.fail(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.fail(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.fail(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.fail(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.fail(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.fail(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.fail(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.fail(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); + } + }, + { + name: "Validate a simple module export inside eval()", + body: function () { + let functionBody = + `testDynamicImport( + ()=>{ return eval("import('ModuleSimpleExport.js')"); }, + (v)=>{ assert.areEqual('ModuleSimpleExport', v.ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js'); }, + (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit + )`; + testScript(functionBody, "Test importing a simple exported function", false, true); + testModuleScript(functionBody, "Test importing a simple exported function", false, true); + } + }, +]; + +testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); diff --git a/test/es6/dynamic-module-import-specifier.js b/test/es6/dynamic-module-import-specifier.js new file mode 100644 index 00000000000..b0a2e4dbe36 --- /dev/null +++ b/test/es6/dynamic-module-import-specifier.js @@ -0,0 +1,142 @@ +//------------------------------------------------------------------------------------------------------- +// 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: "Valid cases for import()", + body: function () { + let functionBody = + ` + assert.doesNotThrow(function () { eval("import(undefined)"); }, "undefined"); + assert.doesNotThrow(function () { eval("import(null)"); }, "null"); + assert.doesNotThrow(function () { eval("import(true)"); }, "boolean - true"); + assert.doesNotThrow(function () { eval("import(false)"); }, "boolean - false"); + assert.doesNotThrow(function () { eval("import(1234567890)"); }, "number"); + assert.doesNotThrow(function () { eval("import('abc789cde')"); }, "string literal"); + assert.doesNotThrow(function () { eval("import('number' + 100 + 0.4 * 18)"); }, "expression"); + assert.doesNotThrow(function () { eval("import(import(true))"); }, "nested import"); + assert.doesNotThrow(function () { eval("import(import(Infinity) + import(undefined))"); }, "nested import expression"); + `; + + testScript(functionBody, "Test importing a simple exported function"); + testModuleScript(functionBody, "Test importing a simple exported function"); + } + }, + { + name: "Syntax errors for import() call", + body: function () { + let functionBody = + ` + assert.throws(function () { eval("import()"); }, SyntaxError, "no argument"); + assert.throws(function () { eval("import(1, 2)"); }, SyntaxError, "more than one arguments"); + assert.throws(function () { eval("import('abc.js', 'def.js')"); }, SyntaxError, "more than one argument - case 2"); + assert.throws(function () { eval("import(...['abc.js', 'def.js'])"); }, SyntaxError, "spread argument"); + `; + + testScript(functionBody, "Test importing a simple exported function"); + testModuleScript(functionBody, "Test importing a simple exported function"); + } + }, + { + name: "Module specifier that are not string", + body: function () { + var testNonStringSpecifier = function (specifier, expectedType, expectedErrMsg, message) { + if (typeof message === "undefined" ) { + message = specifier; + } + + let functionBody = + `testDynamicImport( + ()=>{ + return import(${specifier}); + }, + (v)=>{ + assert.fail('Expected: promise rejected; actual: promise resolved: ' + '${message}'); + }, + (err)=>{ + assert.isTrue(err instanceof Error, '${message}'); + assert.areEqual(${expectedType}, err.constructor, '${message}'); + assert.areEqual("${expectedErrMsg}", err.message, '${message}'); + }, _asyncEnter, _asyncExit + )`; + + testScript(functionBody, "Test importing a simple exported function", false, true); + testModuleScript(functionBody, "Test importing a simple exported function", false, true); + }; + + testNonStringSpecifier("undefined", "URIError", "undefined"); + testNonStringSpecifier("null", "URIError", "null"); + testNonStringSpecifier("true", "URIError", "true"); + testNonStringSpecifier("false", "URIError", "false"); + testNonStringSpecifier("NaN", "URIError", "NaN"); + testNonStringSpecifier("+0", "URIError", "0"); + testNonStringSpecifier("-0", "URIError", "0"); + testNonStringSpecifier("-12345", "URIError", "-12345"); + testNonStringSpecifier("1/0", "URIError", "Infinity"); + testNonStringSpecifier("1.123456789012345678901", "URIError", "1.1234567890123457"); + testNonStringSpecifier("-1.123456789012345678901", "URIError", "-1.1234567890123457"); + testNonStringSpecifier('Symbol("abc")', "TypeError", "Object doesn't support property or method 'ToString'"); + testNonStringSpecifier("{}", "URIError", "[object Object]"); + } + }, +]; + +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..4f623eac594 100644 --- a/test/es6/rlexe.xml +++ b/test/es6/rlexe.xml @@ -1361,6 +1361,20 @@ exclude_dynapogo,exclude_sanitize_address + + + dynamic-module-functionality.js + -ES6Module -args summary -endargs + exclude_sanitize_address + + + + + dynamic-module-import-specifier.js + -AsyncModuleLoad -ES6Module -args summary -endargs + exclude_sanitize_address + + module-syntax.js