diff --git a/sdk/nodejs/runtime/closure/v8.ts b/sdk/nodejs/runtime/closure/v8.ts index cb52ac616368..12c49580bd5d 100644 --- a/sdk/nodejs/runtime/closure/v8.ts +++ b/sdk/nodejs/runtime/closure/v8.ts @@ -22,14 +22,39 @@ import * as v8 from "v8"; v8.setFlagsFromString("--allow-natives-syntax"); +import * as inspector from "inspector"; +import * as util from "util"; +import * as vm from "vm"; import * as v8Hooks from "./v8Hooks"; -import * as v8_v10andLower from "./v8_v10andLower"; -import * as v8_v11andHigher from "./v8_v11andHigher"; +/** + * Given a function, returns the file, line and column number in the file where this function was + * defined. Returns { "", 0, 0 } if the location cannot be found or if the given function has no Script. + * @internal + */ +export async function getFunctionLocationAsync(func: Function) { + // First, find the runtime's internal id for this function. + const functionId = await getRuntimeIdForFunctionAsync(func); + + // Now, query for the internal properties the runtime sets up for it. + const { internalProperties } = await runtimeGetPropertiesAsync(functionId, /*ownProperties:*/ false); -// Node majorly changed their introspection apis between 10.0 and 11.0 (removing outright some -// of the APIs we use). Detect if we're before or after this change and delegate to the -const versionSpecificV8Module = v8Hooks.isNodeAtLeastV11 ? v8_v11andHigher : v8_v10andLower; + // There should normally be an internal property called [[FunctionLocation]]: + // https://chromium.googlesource.com/v8/v8.git/+/3f99afc93c9ba1ba5df19f123b93cc3079893c9b/src/inspector/v8-debugger.cc#793 + const functionLocation = internalProperties.find(p => p.name === "[[FunctionLocation]]"); + if (!functionLocation || !functionLocation.value || !functionLocation.value.value) { + return { file: "", line: 0, column: 0 }; + } + + const value = functionLocation.value.value; + + // Map from the scriptId the value has to a file-url. + const file = v8Hooks.getScriptUrl(value.scriptId) || ""; + const line = value.lineNumber || 0; + const column = value.columnNumber || 0; + + return { file, line, column }; +} /** * Given a function and a free variable name, lookupCapturedVariableValue looks up the value of that free variable @@ -42,11 +67,221 @@ const versionSpecificV8Module = v8Hooks.isNodeAtLeastV11 ? v8_v11andHigher : v8 * @returns The value of the free variable. If `throwOnFailure` is false, returns `undefined` if not found. * @internal */ -export const lookupCapturedVariableValueAsync = versionSpecificV8Module.lookupCapturedVariableValueAsync; +export async function lookupCapturedVariableValueAsync( + func: Function, freeVariable: string, throwOnFailure: boolean): Promise { -/** - * Given a function, returns the file, line and column number in the file where this function was - * defined. Returns { "", 0, 0 } if the location cannot be found or if the given function has no Script. - * @internal - */ -export const getFunctionLocationAsync = versionSpecificV8Module.getFunctionLocationAsync; + // First, find the runtime's internal id for this function. + const functionId = await getRuntimeIdForFunctionAsync(func); + + // Now, query for the internal properties the runtime sets up for it. + const { internalProperties } = await runtimeGetPropertiesAsync(functionId, /*ownProperties:*/ false); + + // There should normally be an internal property called [[Scopes]]: + // https://chromium.googlesource.com/v8/v8.git/+/3f99afc93c9ba1ba5df19f123b93cc3079893c9b/src/inspector/v8-debugger.cc#820 + const scopes = internalProperties.find(p => p.name === "[[Scopes]]"); + if (!scopes) { + throw new Error("Could not find [[Scopes]] property"); + } + + if (!scopes.value) { + throw new Error("[[Scopes]] property did not have [value]"); + } + + if (!scopes.value.objectId) { + throw new Error("[[Scopes]].value have objectId"); + } + + // This is sneaky, but we can actually map back from the [[Scopes]] object to a real in-memory + // v8 array-like value. Note: this isn't actually a real array. For example, it cannot be + // iterated. Nor can any actual methods be called on it. However, we can directly index into + // it, and we can. Similarly, the 'object' type it optionally points at is not a true JS + // object. So we can't call things like .hasOwnProperty on it. However, the values pointed to + // by 'object' are the real in-memory JS objects we are looking for. So we can find and return + // those successfully to our caller. + const scopesArray: { object?: Record }[] = await getValueForObjectId(scopes.value.objectId); + + // scopesArray is ordered from innermost to outermost. + for (let i = 0, n = scopesArray.length; i < n; i++) { + const scope = scopesArray[i]; + if (scope.object) { + if (freeVariable in scope.object) { + const val = scope.object[freeVariable]; + return val; + } + } + } + + if (throwOnFailure) { + throw new Error("Unexpected missing variable in closure environment: " + freeVariable); + } + + return undefined; +} + +// We want to call util.promisify on inspector.Session.post. However, due to all the overloads of +// that method, promisify gets confused. To prevent this, we cast our session object down to an +// interface containing only the single overload we care about. +type PostSession = { + post(method: TMethod, params?: TParams, callback?: (err: Error | null, params: TReturn) => void): void; +}; + +type EvaluationSession = PostSession<"Runtime.evaluate", inspector.Runtime.EvaluateParameterType, inspector.Runtime.EvaluateReturnType>; +type GetPropertiesSession = PostSession<"Runtime.getProperties", inspector.Runtime.GetPropertiesParameterType, inspector.Runtime.GetPropertiesReturnType>; +type CallFunctionSession = PostSession<"Runtime.callFunctionOn", inspector.Runtime.CallFunctionOnParameterType, inspector.Runtime.CallFunctionOnReturnType>; +type ContextSession = { + post(method: "Runtime.disable" | "Runtime.enable", callback?: (err: Error | null) => void): void; + once(event: "Runtime.executionContextCreated", listener: (message: inspector.InspectorNotification) => void): void; +}; + +type InflightContext = { + contextId: number; + functions: Record; + currentFunctionId: number; + calls: Record; + currentCallId: number; +}; +// Isolated singleton context accessible from the inspector. +// Used instead of `global` object to support executions with multiple V8 vm contexts as, e.g., done by Jest. +let inflightContextCache: Promise | undefined; +function inflightContext() { + if (inflightContextCache) { + return inflightContextCache; + } + inflightContextCache = createContext(); + return inflightContextCache; +} +async function createContext(): Promise { + const context: InflightContext = { + contextId: 0, + functions: {}, + currentFunctionId: 0, + calls: {}, + currentCallId: 0, + }; + const session = await v8Hooks.getSessionAsync(); + const post = util.promisify(session.post); + + // Create own context with known context id and functionsContext as `global` + await post.call(session, "Runtime.enable"); + const contextIdAsync = new Promise(resolve => { + session.once("Runtime.executionContextCreated", event => { + resolve(event.params.context.id); + }); + }); + vm.createContext(context); + context.contextId = await contextIdAsync; + await post.call(session, "Runtime.disable"); + + return context; +} + +async function getRuntimeIdForFunctionAsync(func: Function): Promise { + // In order to get information about an object, we need to put it in a well known location so + // that we can call Runtime.evaluate and find it. To do this, we use a special map on the + // 'global' object of a vm context only used for this purpose, and map from a unique-id to that + // object. We then call Runtime.evaluate with an expression that then points to that unique-id + // in that global object. The runtime will then find the object and give us back an internal id + // for it. We can then query for information about the object through that internal id. + // + // Note: the reason for the mapping object and the unique-id we create is so that we don't run + // into any issues when being called asynchronously. We don't want to place the object in a + // location that might be overwritten by another call while we're asynchronously waiting for our + // original call to complete. + + const session = await v8Hooks.getSessionAsync(); + const post = util.promisify(session.post); + + // Place the function in a unique location + const context = await inflightContext(); + const currentFunctionName = "id" + context.currentFunctionId++; + context.functions[currentFunctionName] = func; + const contextId = context.contextId; + const expression = `functions.${currentFunctionName}`; + + try { + const retType = await post.call(session, "Runtime.evaluate", { contextId, expression }); + + if (retType.exceptionDetails) { + throw new Error(`Error calling "Runtime.evaluate(${expression})" on context ${contextId}: ` + retType.exceptionDetails.text); + } + + const remoteObject = retType.result; + if (remoteObject.type !== "function") { + throw new Error("Remote object was not 'function': " + JSON.stringify(remoteObject)); + } + + if (!remoteObject.objectId) { + throw new Error("Remote function does not have 'objectId': " + JSON.stringify(remoteObject)); + } + + return remoteObject.objectId; + } + finally { + delete context.functions[currentFunctionName]; + } +} + +async function runtimeGetPropertiesAsync( + objectId: inspector.Runtime.RemoteObjectId, + ownProperties: boolean | undefined) { + const session = await v8Hooks.getSessionAsync(); + const post = util.promisify(session.post); + + // This cast will become unnecessary when we move to TS 3.1.6 or above. In that version they + // support typesafe '.call' calls. + const retType = await post.call( + session, "Runtime.getProperties", { objectId, ownProperties }); + + if (retType.exceptionDetails) { + throw new Error(`Error calling "Runtime.getProperties(${objectId}, ${ownProperties})": ` + + retType.exceptionDetails.text); + } + + return { internalProperties: retType.internalProperties || [], properties: retType.result }; +} + +async function getValueForObjectId(objectId: inspector.Runtime.RemoteObjectId): Promise { + // In order to get the raw JS value for the *remote wrapper* of the [[Scopes]] array, we use + // Runtime.callFunctionOn on it passing in a fresh function-declaration. The Node runtime will + // then compile that function, invoking it with the 'real' underlying scopes-array value in + // memory as the bound 'this' value. Inside that function declaration, we can then access + // 'this' and assign it to a unique-id in a well known mapping table we have set up. As above, + // the unique-id is to prevent any issues with multiple in-flight asynchronous calls. + + const session = await v8Hooks.getSessionAsync(); + const post = util.promisify(session.post); + const context = await inflightContext(); + + // Get an id for an unused location in the global table. + const tableId = "id" + context.currentCallId++; + + // Now, ask the runtime to call a fictitious method on the scopes-array object. When it + // does, it will get the actual underlying value for the scopes array and bind it to the + // 'this' value inside the function. Inside the function we then just grab 'this' and + // stash it in our global table. After this completes, we'll then have access to it. + + // This cast will become unnecessary when we move to TS 3.1.6 or above. In that version they + // support typesafe '.call' calls. + const retType = await post.call( + session, "Runtime.callFunctionOn", { + objectId, + functionDeclaration: `function () { + calls["${tableId}"] = this; + }`, + }); + + if (retType.exceptionDetails) { + throw new Error(`Error calling "Runtime.callFunction(${objectId})": ` + + retType.exceptionDetails.text); + } + + if (!context.calls.hasOwnProperty(tableId)) { + throw new Error(`Value was not stored into table after calling "Runtime.callFunctionOn(${objectId})"`); + } + + // Extract value and clear our table entry. + const val = context.calls[tableId]; + delete context.calls[tableId]; + + return val; +} \ No newline at end of file diff --git a/sdk/nodejs/runtime/closure/v8Hooks.ts b/sdk/nodejs/runtime/closure/v8Hooks.ts index c7194997e69f..f4c9e5358f49 100644 --- a/sdk/nodejs/runtime/closure/v8Hooks.ts +++ b/sdk/nodejs/runtime/closure/v8Hooks.ts @@ -20,22 +20,12 @@ import * as v8 from "v8"; v8.setFlagsFromString("--allow-natives-syntax"); -import * as semver from "semver"; - -// On node11 and above, create an 'inspector session' that can be used to keep track of what is -// happening through a supported API. Pre-11 we can just call into % intrinsics for the same data. -/** @internal */ -export const isNodeAtLeastV11 = semver.gte(process.version, "11.0.0"); - let session: Promise | undefined = undefined; function getSession() { if (session !== undefined) { return session; } - if (!isNodeAtLeastV11) { - return Promise.resolve(undefined); - } session = createInspectorSessionAsync(); return session; } @@ -69,10 +59,6 @@ async function createInspectorSessionAsync(): Promise; -} - -/** @internal */ -export async function getFunctionLocationAsync(func: Function) { - const script = getScript(func); - const { line, column } = getLineColumn(); - - return { file: script ? script.name : "", line, column }; - - function getLineColumn() { - if (script) { - const pos = getSourcePosition(func); - - try { - if (isNodeAtLeastV10) { - return scriptPositionInfo(script, pos); - } else { - return script.locationFromPosition(pos); - } - } catch (err) { - // Be resilient to native functions not being available. In this case, we just return - // '0,0'. That's not great, but it at least lets us run, and it isn't a terrible - // experience. - // - // Specifically, we only need these locations when we're printing out an error about not - // being able to serialize something. In that case, we still print out the names of the - // functions (as well as the call-tree that got us there), *and* we print out the body - // of the function. With both of these, it is generally not too difficult to find out - // where the code actually lives. - } - } - - return { line: 0, column: 0 }; - } -} - -function getScript(func: Function): V8Script | undefined { - // The use of the Function constructor here and elsewhere in this file is because - // because V8 intrinsics are not valid JavaScript identifiers; they all begin with '%', - // which means that the TypeScript compiler issues errors for them. - const scriptFunc = new Function("func", "return %FunctionGetScript(func);") as any; - return scriptFunc(func); -} - - -// The second intrinsic is `FunctionGetScriptSourcePosition`, which does about what you'd -// expect. It returns a `V8SourcePosition`, which can be passed to `V8Script::locationFromPosition` -// to produce a `V8SourceLocation`. -const getSourcePosition: (func: Function) => V8SourcePosition = - new Function("func", "return %FunctionGetScriptSourcePosition(func);") as any; - -function scriptPositionInfo(script: V8Script, pos: V8SourcePosition): {line: number; column: number} { - if (isNodeAtLeastV10) { - const scriptPositionInfoFunc = - new Function("script", "pos", "return %ScriptPositionInfo(script, pos, false);") as any; - - return scriptPositionInfoFunc(script, pos); - } - - // Should not be called if running on Node<10.0.0. - return undefined; -} - -/** @internal */ -export async function lookupCapturedVariableValueAsync( - func: Function, freeVariable: string, throwOnFailure: boolean): Promise { - - // The implementation of this function is now very straightforward since the intrinsics do all of the - // difficult work. - const count = getFunctionScopeCount(func); - for (let i = 0; i < count; i++) { - const scope = getScopeForFunction(func, i); - if (freeVariable in scope.scopeObject) { - return scope.scopeObject[freeVariable]; - } - } - - if (throwOnFailure) { - throw new Error("Unexpected missing variable in closure environment: " + freeVariable); - } - - return undefined; -} - -// The last two intrinsics are `GetFunctionScopeCount` and `GetFunctionScopeDetails`. -// The former function returns the number of scopes in a given function's scope chain, while -// the latter function returns the i'th entry in a function's scope chain, given a function and -// index i. -function getFunctionScopeDetails(func: Function, index: number): any[] { - const getFunctionScopeDetailsFunc = - new Function("func", "index", "return %GetFunctionScopeDetails(func, index);") as any; - - return getFunctionScopeDetailsFunc(func, index); -} - -function getFunctionScopeCount(func: Function): number { - const getFunctionScopeCountFunc = new Function("func", "return %GetFunctionScopeCount(func);") as any; - return getFunctionScopeCountFunc(func); -} - -// getScopeForFunction extracts a V8ScopeDetails for the index'th element in the scope chain for the -// given function. -function getScopeForFunction(func: Function, index: number): V8ScopeDetails { - const scopeDetails = getFunctionScopeDetails(func, index); - return { - scopeObject: scopeDetails[V8ScopeDetailsFields.kScopeDetailsObjectIndex] as Record, - }; -} - -// All of these functions contain syntax that is not legal TS/JS (i.e. "%Whatever"). As such, -// we cannot serialize them. In case they somehow get captured, just block them from closure -// serialization entirely. -(getScript).doNotCapture = true; -(getSourcePosition).doNotCapture = true; -(getFunctionScopeDetails).doNotCapture = true; -(getFunctionScopeCount).doNotCapture = true; diff --git a/sdk/nodejs/runtime/closure/v8_v11andHigher.ts b/sdk/nodejs/runtime/closure/v8_v11andHigher.ts deleted file mode 100644 index 5da13e4ec818..000000000000 --- a/sdk/nodejs/runtime/closure/v8_v11andHigher.ts +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright 2016-2018, Pulumi Corporation. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/* eslint-disable max-len */ - -import * as inspector from "inspector"; -import * as util from "util"; -import * as vm from "vm"; -import * as v8Hooks from "./v8Hooks"; - -/** @internal */ -export async function getFunctionLocationAsync(func: Function) { - // First, find the runtime's internal id for this function. - const functionId = await getRuntimeIdForFunctionAsync(func); - - // Now, query for the internal properties the runtime sets up for it. - const { internalProperties } = await runtimeGetPropertiesAsync(functionId, /*ownProperties:*/ false); - - // There should normally be an internal property called [[FunctionLocation]]: - // https://chromium.googlesource.com/v8/v8.git/+/3f99afc93c9ba1ba5df19f123b93cc3079893c9b/src/inspector/v8-debugger.cc#793 - const functionLocation = internalProperties.find(p => p.name === "[[FunctionLocation]]"); - if (!functionLocation || !functionLocation.value || !functionLocation.value.value) { - return { file: "", line: 0, column: 0 }; - } - - const value = functionLocation.value.value; - - // Map from the scriptId the value has to a file-url. - const file = v8Hooks.getScriptUrl(value.scriptId) || ""; - const line = value.lineNumber || 0; - const column = value.columnNumber || 0; - - return { file, line, column }; -} - -/** @internal */ -export async function lookupCapturedVariableValueAsync( - func: Function, freeVariable: string, throwOnFailure: boolean): Promise { - - // First, find the runtime's internal id for this function. - const functionId = await getRuntimeIdForFunctionAsync(func); - - // Now, query for the internal properties the runtime sets up for it. - const { internalProperties } = await runtimeGetPropertiesAsync(functionId, /*ownProperties:*/ false); - - // There should normally be an internal property called [[Scopes]]: - // https://chromium.googlesource.com/v8/v8.git/+/3f99afc93c9ba1ba5df19f123b93cc3079893c9b/src/inspector/v8-debugger.cc#820 - const scopes = internalProperties.find(p => p.name === "[[Scopes]]"); - if (!scopes) { - throw new Error("Could not find [[Scopes]] property"); - } - - if (!scopes.value) { - throw new Error("[[Scopes]] property did not have [value]"); - } - - if (!scopes.value.objectId) { - throw new Error("[[Scopes]].value have objectId"); - } - - // This is sneaky, but we can actually map back from the [[Scopes]] object to a real in-memory - // v8 array-like value. Note: this isn't actually a real array. For example, it cannot be - // iterated. Nor can any actual methods be called on it. However, we can directly index into - // it, and we can. Similarly, the 'object' type it optionally points at is not a true JS - // object. So we can't call things like .hasOwnProperty on it. However, the values pointed to - // by 'object' are the real in-memory JS objects we are looking for. So we can find and return - // those successfully to our caller. - const scopesArray: { object?: Record }[] = await getValueForObjectId(scopes.value.objectId); - - // scopesArray is ordered from innermost to outermost. - for (let i = 0, n = scopesArray.length; i < n; i++) { - const scope = scopesArray[i]; - if (scope.object) { - if (freeVariable in scope.object) { - const val = scope.object[freeVariable]; - return val; - } - } - } - - if (throwOnFailure) { - throw new Error("Unexpected missing variable in closure environment: " + freeVariable); - } - - return undefined; -} - -// We want to call util.promisify on inspector.Session.post. However, due to all the overloads of -// that method, promisify gets confused. To prevent this, we cast our session object down to an -// interface containing only the single overload we care about. -type PostSession = { - post(method: TMethod, params?: TParams, callback?: (err: Error | null, params: TReturn) => void): void; -}; - -type EvaluationSession = PostSession<"Runtime.evaluate", inspector.Runtime.EvaluateParameterType, inspector.Runtime.EvaluateReturnType>; -type GetPropertiesSession = PostSession<"Runtime.getProperties", inspector.Runtime.GetPropertiesParameterType, inspector.Runtime.GetPropertiesReturnType>; -type CallFunctionSession = PostSession<"Runtime.callFunctionOn", inspector.Runtime.CallFunctionOnParameterType, inspector.Runtime.CallFunctionOnReturnType>; -type ContextSession = { - post(method: "Runtime.disable" | "Runtime.enable", callback?: (err: Error | null) => void): void; - once(event: "Runtime.executionContextCreated", listener: (message: inspector.InspectorNotification) => void): void; -}; - -type InflightContext = { - contextId: number; - functions: Record; - currentFunctionId: number; - calls: Record; - currentCallId: number; -}; -// Isolated singleton context accessible from the inspector. -// Used instead of `global` object to support executions with multiple V8 vm contexts as, e.g., done by Jest. -let inflightContextCache: Promise | undefined; -function inflightContext() { - if (inflightContextCache) { - return inflightContextCache; - } - inflightContextCache = createContext(); - return inflightContextCache; -} -async function createContext(): Promise { - const context: InflightContext = { - contextId: 0, - functions: {}, - currentFunctionId: 0, - calls: {}, - currentCallId: 0, - }; - const session = await v8Hooks.getSessionAsync(); - const post = util.promisify(session.post); - - // Create own context with known context id and functionsContext as `global` - await post.call(session, "Runtime.enable"); - const contextIdAsync = new Promise(resolve => { - session.once("Runtime.executionContextCreated", event => { - resolve(event.params.context.id); - }); - }); - vm.createContext(context); - context.contextId = await contextIdAsync; - await post.call(session, "Runtime.disable"); - - return context; -} - -async function getRuntimeIdForFunctionAsync(func: Function): Promise { - // In order to get information about an object, we need to put it in a well known location so - // that we can call Runtime.evaluate and find it. To do this, we use a special map on the - // 'global' object of a vm context only used for this purpose, and map from a unique-id to that - // object. We then call Runtime.evaluate with an expression that then points to that unique-id - // in that global object. The runtime will then find the object and give us back an internal id - // for it. We can then query for information about the object through that internal id. - // - // Note: the reason for the mapping object and the unique-id we create is so that we don't run - // into any issues when being called asynchronously. We don't want to place the object in a - // location that might be overwritten by another call while we're asynchronously waiting for our - // original call to complete. - - const session = await v8Hooks.getSessionAsync(); - const post = util.promisify(session.post); - - // Place the function in a unique location - const context = await inflightContext(); - const currentFunctionName = "id" + context.currentFunctionId++; - context.functions[currentFunctionName] = func; - const contextId = context.contextId; - const expression = `functions.${currentFunctionName}`; - - try { - const retType = await post.call(session, "Runtime.evaluate", { contextId, expression }); - - if (retType.exceptionDetails) { - throw new Error(`Error calling "Runtime.evaluate(${expression})" on context ${contextId}: ` + retType.exceptionDetails.text); - } - - const remoteObject = retType.result; - if (remoteObject.type !== "function") { - throw new Error("Remote object was not 'function': " + JSON.stringify(remoteObject)); - } - - if (!remoteObject.objectId) { - throw new Error("Remote function does not have 'objectId': " + JSON.stringify(remoteObject)); - } - - return remoteObject.objectId; - } - finally { - delete context.functions[currentFunctionName]; - } -} - -async function runtimeGetPropertiesAsync( - objectId: inspector.Runtime.RemoteObjectId, - ownProperties: boolean | undefined) { - const session = await v8Hooks.getSessionAsync(); - const post = util.promisify(session.post); - - // This cast will become unnecessary when we move to TS 3.1.6 or above. In that version they - // support typesafe '.call' calls. - const retType = await post.call( - session, "Runtime.getProperties", { objectId, ownProperties }); - - if (retType.exceptionDetails) { - throw new Error(`Error calling "Runtime.getProperties(${objectId}, ${ownProperties})": ` - + retType.exceptionDetails.text); - } - - return { internalProperties: retType.internalProperties || [], properties: retType.result }; -} - -async function getValueForObjectId(objectId: inspector.Runtime.RemoteObjectId): Promise { - // In order to get the raw JS value for the *remote wrapper* of the [[Scopes]] array, we use - // Runtime.callFunctionOn on it passing in a fresh function-declaration. The Node runtime will - // then compile that function, invoking it with the 'real' underlying scopes-array value in - // memory as the bound 'this' value. Inside that function declaration, we can then access - // 'this' and assign it to a unique-id in a well known mapping table we have set up. As above, - // the unique-id is to prevent any issues with multiple in-flight asynchronous calls. - - const session = await v8Hooks.getSessionAsync(); - const post = util.promisify(session.post); - const context = await inflightContext(); - - // Get an id for an unused location in the global table. - const tableId = "id" + context.currentCallId++; - - // Now, ask the runtime to call a fictitious method on the scopes-array object. When it - // does, it will get the actual underlying value for the scopes array and bind it to the - // 'this' value inside the function. Inside the function we then just grab 'this' and - // stash it in our global table. After this completes, we'll then have access to it. - - // This cast will become unnecessary when we move to TS 3.1.6 or above. In that version they - // support typesafe '.call' calls. - const retType = await post.call( - session, "Runtime.callFunctionOn", { - objectId, - functionDeclaration: `function () { - calls["${tableId}"] = this; - }`, - }); - - if (retType.exceptionDetails) { - throw new Error(`Error calling "Runtime.callFunction(${objectId})": ` - + retType.exceptionDetails.text); - } - - if (!context.calls.hasOwnProperty(tableId)) { - throw new Error(`Value was not stored into table after calling "Runtime.callFunctionOn(${objectId})"`); - } - - // Extract value and clear our table entry. - const val = context.calls[tableId]; - delete context.calls[tableId]; - - return val; -} diff --git a/sdk/nodejs/tsconfig.json b/sdk/nodejs/tsconfig.json index 70aa28fdb76d..6e596411597e 100644 --- a/sdk/nodejs/tsconfig.json +++ b/sdk/nodejs/tsconfig.json @@ -52,8 +52,6 @@ "runtime/closure/serializeClosure.ts", "runtime/closure/utils.ts", "runtime/closure/v8.ts", - "runtime/closure/v8_v10andLower.ts", - "runtime/closure/v8_v11andHigher.ts", "runtime/asyncIterableUtil.ts", "runtime/config.ts",