Skip to content

Commit

Permalink
[MERGE #4707 @rhuanjl] Implement JsGetModuleNamespace API fixes #3616
Browse files Browse the repository at this point in the history
Merge pull request #4707 from rhuanjl:moduleNamespace

Implement a new Jsrt API to get a Module Namespace from an evaluated module record. As discussed in #3616

This is intended for use by any embedder who wishes to access module exports from native code e.g. to use one as an entry point - I note that node's implementation of ESModules with v8 uses an equivalent v8 facility and so this is likely one of the required steps towards implementing ESModules in node-chakracore.

For testing purposes only a WScript method has been added to ch that wraps this method to allow fetching a namespace object from within Javascript - this has then been used to create a test case.

*cc* @liminzhu @boingoing
  • Loading branch information
boingoing committed Feb 20, 2018
2 parents 592d42b + ee9daa6 commit a259b3c
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 4 deletions.
1 change: 1 addition & 0 deletions bin/ChakraCore/ChakraCore.def
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ JsParseModuleSource
JsModuleEvaluation
JsSetModuleHostInfo
JsGetModuleHostInfo
JsGetModuleNamespace
JsInitializeJITServer

JsCreateSharedArrayBufferWithSharedContent
Expand Down
1 change: 1 addition & 0 deletions bin/ch/ChakraRtInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ bool ChakraRTInterface::LoadChakraDll(ArgInfo* argInfo, HINSTANCE *outLibrary)
m_jsApiHooks.pfJsrtSetModuleHostInfo = (JsAPIHooks::JsSetModuleHostInfoPtr)GetChakraCoreSymbol(library, "JsSetModuleHostInfo");
m_jsApiHooks.pfJsrtGetModuleHostInfo = (JsAPIHooks::JsGetModuleHostInfoPtr)GetChakraCoreSymbol(library, "JsGetModuleHostInfo");
m_jsApiHooks.pfJsrtModuleEvaluation = (JsAPIHooks::JsModuleEvaluationPtr)GetChakraCoreSymbol(library, "JsModuleEvaluation");
m_jsApiHooks.pfJsrtGetModuleNamespace = (JsAPIHooks::JsGetModuleNamespacePtr)GetChakraCoreSymbol(library, "JsGetModuleNamespace");
m_jsApiHooks.pfJsrtDiagStartDebugging = (JsAPIHooks::JsrtDiagStartDebugging)GetChakraCoreSymbol(library, "JsDiagStartDebugging");
m_jsApiHooks.pfJsrtDiagStopDebugging = (JsAPIHooks::JsrtDiagStopDebugging)GetChakraCoreSymbol(library, "JsDiagStopDebugging");
m_jsApiHooks.pfJsrtDiagGetSource = (JsAPIHooks::JsrtDiagGetSource)GetChakraCoreSymbol(library, "JsDiagGetSource");
Expand Down
3 changes: 3 additions & 0 deletions bin/ch/ChakraRtInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ struct JsAPIHooks
typedef JsErrorCode (WINAPI *JsInitializeModuleRecordPtr)(JsModuleRecord referencingModule, JsValueRef normalizedSpecifier, JsModuleRecord* moduleRecord);
typedef JsErrorCode (WINAPI *JsParseModuleSourcePtr)(JsModuleRecord requestModule, JsSourceContext sourceContext, byte* sourceText, unsigned int sourceLength, JsParseModuleSourceFlags sourceFlag, JsValueRef* exceptionValueRef);
typedef JsErrorCode (WINAPI *JsModuleEvaluationPtr)(JsModuleRecord requestModule, JsValueRef* result);
typedef JsErrorCode (WINAPI *JsGetModuleNamespacePtr)(JsModuleRecord requestModule, JsValueRef *moduleNamespace);
typedef JsErrorCode (WINAPI *JsSetModuleHostInfoPtr)(JsModuleRecord requestModule, JsModuleHostInfoKind moduleHostInfo, void* hostInfo);
typedef JsErrorCode (WINAPI *JsGetModuleHostInfoPtr)(JsModuleRecord requestModule, JsModuleHostInfoKind moduleHostInfo, void** hostInfo);
typedef JsErrorCode (WINAPI *JsrtCallFunctionPtr)(JsValueRef function, JsValueRef* arguments, unsigned short argumentCount, JsValueRef *result);
Expand Down Expand Up @@ -129,6 +130,7 @@ struct JsAPIHooks
JsParseModuleSourcePtr pfJsrtParseModuleSource;
JsInitializeModuleRecordPtr pfJsrtInitializeModuleRecord;
JsModuleEvaluationPtr pfJsrtModuleEvaluation;
JsGetModuleNamespacePtr pfJsrtGetModuleNamespace;
JsSetModuleHostInfoPtr pfJsrtSetModuleHostInfo;
JsGetModuleHostInfoPtr pfJsrtGetModuleHostInfo;
JsrtCallFunctionPtr pfJsrtCallFunction;
Expand Down Expand Up @@ -381,6 +383,7 @@ class ChakraRTInterface
return HOOK_JS_API(ParseModuleSource(requestModule, sourceContext, sourceText, sourceLength, sourceFlag, exceptionValueRef));
}
static JsErrorCode WINAPI JsModuleEvaluation(JsModuleRecord requestModule, JsValueRef* result) { return HOOK_JS_API(ModuleEvaluation(requestModule, result)); }
static JsErrorCode WINAPI JsGetModuleNamespace(JsModuleRecord requestModule, JsValueRef *moduleNamespace) { return HOOK_JS_API(GetModuleNamespace(requestModule, moduleNamespace)); }
static JsErrorCode WINAPI JsInitializeModuleRecord(JsModuleRecord referencingModule, JsValueRef normalizedSpecifier, JsModuleRecord* moduleRecord) {
return HOOK_JS_API(InitializeModuleRecord(referencingModule, normalizedSpecifier, moduleRecord));
}
Expand Down
62 changes: 62 additions & 0 deletions bin/ch/WScriptJsrt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,67 @@ JsValueRef WScriptJsrt::LoadScriptFileHelper(JsValueRef callee, JsValueRef *argu
return returnValue;
}

JsValueRef __stdcall WScriptJsrt::GetModuleNamespace(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState)
{
JsErrorCode errorCode = JsNoError;
JsValueRef returnValue = JS_INVALID_REFERENCE;
LPCWSTR errorMessage = _u("");
char fullPath[_MAX_PATH];

if (argumentCount < 2)
{
errorCode = JsErrorInvalidArgument;
errorMessage = _u("Need an argument for WScript.GetModuleNamespace");
}
else
{
AutoString specifierStr(arguments[1]);
errorCode = specifierStr.GetError();

if (errorCode == JsNoError)
{
if (_fullpath(fullPath, specifierStr.GetString(), _MAX_PATH) == nullptr)
{
errorCode = JsErrorInvalidArgument;
}
else
{
auto moduleEntry = moduleRecordMap.find(fullPath);
if (moduleEntry == moduleRecordMap.end())
{
errorCode = JsErrorInvalidArgument;
errorMessage = _u("Need to supply a path for an already loaded module for WScript.GetModuleNamespace");
}
else
{
errorCode = ChakraRTInterface::JsGetModuleNamespace(moduleEntry->second, &returnValue);
if (errorCode == JsErrorModuleNotEvaluated)
{
errorMessage = _u("GetModuleNamespace called with un-evaluated module");
}
}
}
}
}

if (errorCode != JsNoError)
{
JsValueRef errorObject;
JsValueRef errorMessageString;

if (wcscmp(errorMessage, _u("")) == 0)
{
errorMessage = ConvertErrorCodeToMessage(errorCode);
}

ERROR_MESSAGE_TO_STRING(errCode, errorMessage, errorMessageString);

ChakraRTInterface::JsCreateError(errorMessageString, &errorObject);
ChakraRTInterface::JsSetException(errorObject);
}
return returnValue;
}

JsValueRef __stdcall WScriptJsrt::LoadScriptCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState)
{
return LoadScriptHelper(callee, isConstructCall, arguments, argumentCount, callbackState, false);
Expand Down Expand Up @@ -859,6 +920,7 @@ bool WScriptJsrt::Initialize()
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "LoadTextFile", LoadTextFileCallback));
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "Flag", FlagCallback));
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "RegisterModuleSource", RegisterModuleSourceCallback));
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "GetModuleNamespace", GetModuleNamespace));

// ToDo Remove
IfFalseGo(WScriptJsrt::InstallObjectsOnObject(wscript, "Edit", EmptyCallback));
Expand Down
1 change: 1 addition & 0 deletions bin/ch/WScriptJsrt.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class WScriptJsrt
static JsValueRef CALLBACK LoadScriptFileCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState);
static JsValueRef CALLBACK LoadScriptCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState);
static JsValueRef CALLBACK LoadModuleCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState);
static JsValueRef CALLBACK GetModuleNamespace(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState);
static JsValueRef CALLBACK SetTimeoutCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState);
static JsValueRef CALLBACK ClearTimeoutCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState);
static JsValueRef CALLBACK AttachCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState);
Expand Down
4 changes: 4 additions & 0 deletions lib/Jsrt/ChakraCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ typedef unsigned short uint16_t;
/// </summary>
JsNoWeakRefRequired,
/// <summary>
/// Module was not yet evaluated when JsGetModuleNamespace was called.
/// </summary>
JsErrorModuleNotEvaluated,
/// <summary>
/// Category of errors that relates to errors occurring within the engine itself.
/// </summary>
JsErrorCategoryEngine = 0x20000,
Expand Down
16 changes: 16 additions & 0 deletions lib/Jsrt/ChakraCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -1036,5 +1036,21 @@ CHAKRA_API
JsSetHostPromiseRejectionTracker(
_In_ JsHostPromiseRejectionTrackerCallback promiseRejectionTrackerCallback,
_In_opt_ void *callbackState);

/// <summary>
/// Provides the namespace object for a module.
/// </summary>
/// <remarks>
/// Requires an active script context and that the module has already been evaluated.
/// </remarks>
/// <param name="requestModule">The JsModuleRecord for which the namespace is being requested.</param>
/// <param name="moduleNamespace">A JsValueRef - the requested namespace object.</param>
/// <returns>
/// The code <c>JsNoError</c> if the operation succeeded, a failure code otherwise.
/// </returns>
CHAKRA_API
JsGetModuleNamespace(
_In_ JsModuleRecord requestModule,
_Outptr_result_maybenull_ JsValueRef *moduleNamespace);
#endif // _CHAKRACOREBUILD
#endif // _CHAKRACORE_H_
17 changes: 17 additions & 0 deletions lib/Jsrt/Core/JsrtCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,20 @@ JsGetModuleHostInfo(
});
return errorCode;
}

CHAKRA_API JsGetModuleNamespace(_In_ JsModuleRecord requestModule, _Outptr_result_maybenull_ JsValueRef *moduleNamespace)
{
PARAM_NOT_NULL(moduleNamespace);
*moduleNamespace = nullptr;
if (!Js::SourceTextModuleRecord::Is(requestModule))
{
return JsErrorInvalidArgument;
}
Js::SourceTextModuleRecord* moduleRecord = Js::SourceTextModuleRecord::FromHost(requestModule);
if (!moduleRecord->WasEvaluated())
{
return JsErrorModuleNotEvaluated;
}
*moduleNamespace = static_cast<JsValueRef>(moduleRecord->GetNamespace());
return JsNoError;
}
8 changes: 4 additions & 4 deletions lib/Jsrt/Jsrt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5336,10 +5336,10 @@ CHAKRA_API JsGetDataViewInfo(

CHAKRA_API JsSetHostPromiseRejectionTracker(_In_ JsHostPromiseRejectionTrackerCallback promiseRejectionTrackerCallback, _In_opt_ void *callbackState)
{
return ContextAPINoScriptWrapper_NoRecord([&](Js::ScriptContext *scriptContext) -> JsErrorCode {
scriptContext->GetLibrary()->SetNativeHostPromiseRejectionTrackerCallback((Js::JavascriptLibrary::HostPromiseRejectionTrackerCallback) promiseRejectionTrackerCallback, callbackState);
return JsNoError;
},
return ContextAPINoScriptWrapper_NoRecord([&](Js::ScriptContext *scriptContext) -> JsErrorCode {
scriptContext->GetLibrary()->SetNativeHostPromiseRejectionTrackerCallback((Js::JavascriptLibrary::HostPromiseRejectionTrackerCallback) promiseRejectionTrackerCallback, callbackState);
return JsNoError;
},
/*allowInObjectBeforeCollectCallback*/true);
}

Expand Down
82 changes: 82 additions & 0 deletions test/es6module/GetModuleNamespace.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");

WScript.RegisterModuleSource("mod0.js", `
export const export1 = 5;
export default function export2 ()
{
return true;
}
export class export3
{
}
export async function export4 ()
{
return await null;
}
export let export5 = "exporting";
const notExported1 = "foo";
let notExported2 = "bar";
var notExported3 = 5;
`);

WScript.RegisterModuleSource("mod1.js",`
export let export1 = 10;
export default function export2 ()
{
return false;
}
export class export3
{
}
export async function export4 ()
{
return await null;
}
export const export5 = "exported";
export {export6} from "mod2.js";
`);

WScript.RegisterModuleSource("mod2.js",`
export function export6 ()
{
return true;
}
`);

let test1 = import("mod0.js").then(namespaceFromImport => {
let mod0Namespace = WScript.GetModuleNamespace("mod0.js");
assert.areEqual(mod0Namespace.export1, 5, "mod0: export1 should equal 5");
assert.isTrue(mod0Namespace.default(), "mod0: export2 should return true");
assert.areEqual(typeof mod0Namespace.export3.constructor, "function", "mod0: export3 should have constructor function");
assert.areEqual(mod0Namespace.export4.constructor.name, "AsyncFunction", "mod0: export4 should be an async function");
assert.areEqual(mod0Namespace.export5, "exporting", "mod0: export5 should be a string, exporting");
assert.isUndefined(mod0Namespace.notExported1, "Not exported module const should not be property of namespace");
assert.isUndefined(mod0Namespace.notExported2, "Not exported module let should not be property of namespace");
assert.isUndefined(mod0Namespace.notExported3, "Not exported module var should not be property of namespace");
assert.areEqual(namespaceFromImport, mod0Namespace, "ModuleNamespace object should match resolved value of import");
});

let test2 = import("mod1.js").then(namespaceFromImport => {
let mod1Namespace = WScript.GetModuleNamespace("mod1.js");
assert.areEqual(mod1Namespace.export1, 10);
assert.isFalse(mod1Namespace.default());
assert.isUndefined(mod1Namespace.export2, "mod1: Name of default export should be default.")
assert.areEqual(typeof mod1Namespace.export3.constructor, "function");
assert.areEqual(mod1Namespace.export4.constructor.name, "AsyncFunction");
assert.areEqual(mod1Namespace.export5, "exported");
assert.areEqual(mod1Namespace.export6(), true);
assert.areEqual(namespaceFromImport, mod1Namespace,"ModuleNamespace object should match resolved value of import");
});

assert.throws(()=>WScript.GetModuleNamespace("mod0.js"), Error, "Expected error for un-evaluated module", "GetModuleNamespace called with un-evaluated module");
assert.throws(()=>WScript.GetModuleNamespace("mod3.js", Error, "Expected error for un-loaded module", "Need to supply a path for an already loaded module for WScript.GetModuleNamespace"));
assert.throws(()=>WScript.GetModuleNamespace(), Error, "Expected error for no-argument", "Need an argument for WScript.GetModuleNamespace");

Promise.all([test1,test2]).then(()=>print("pass")).catch((e)=>print("fail: " + e));
7 changes: 7 additions & 0 deletions test/es6module/rlexe.xml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@
<tags>BugFix,exclude_sanitize_address</tags>
</default>
</test>
<test>
<default>
<files>GetModuleNamespace.js</files>
<compile-flags>-ESDynamicImport</compile-flags>
<tags>exclude_jshost,exclude_sanitize_address</tags>
</default>
</test>
<test>
<default>
<files>module-load-twice.js</files>
Expand Down

0 comments on commit a259b3c

Please sign in to comment.