Initial work to support serializing proxies
This commit is contained in:
parent
97803a6591
commit
e99cdc07a4
|
@ -20,6 +20,7 @@ build_package::
|
|||
./node_modules/.bin/tsc
|
||||
cp tests/runtime/jsClosureCases_8.js bin/tests/runtime
|
||||
cp tests/runtime/jsClosureCases_10_4.js bin/tests/runtime
|
||||
cp tests/runtime/jsClosureCases_11.js bin/tests/runtime
|
||||
cp README.md ../../LICENSE package.json ./dist/* bin/
|
||||
node ../../scripts/reversion.js bin/package.json ${VERSION}
|
||||
node ../../scripts/reversion.js bin/version.js ${VERSION}
|
||||
|
@ -44,7 +45,7 @@ install_plugin::
|
|||
install:: install_package install_plugin
|
||||
|
||||
istanbul_tests::
|
||||
istanbul test --print none _mocha -- --timeout 15000 'bin/tests/**/*.spec.js'
|
||||
istanbul test --print none _mocha -- --timeout 15000 'bin/tests/**/closureLoader.spec.js'
|
||||
istanbul report text-summary
|
||||
istanbul report text
|
||||
|
||||
|
|
|
@ -345,8 +345,19 @@ async function analyzeFunctionInfoAsync(
|
|||
|
||||
// logInfo = logInfo || func.name === "addHandler";
|
||||
|
||||
const { file, line, column } = await v8.getFunctionLocationAsync(func);
|
||||
const functionString = func.toString();
|
||||
// Some libraries may have wrapped a function up in a proxy (for example 'sequelize' does this).
|
||||
// That's problematic if they then also provide their own static toString. For example:
|
||||
//
|
||||
// return new Proxy(class C { static toString() { ... } }, {})
|
||||
//
|
||||
// In order to support this, we need to first unwrap the proxy to get at the underlying function
|
||||
// that has been wrapped. Then, we also need to ensure that we call Function's toString method,
|
||||
// not any particular static derivation this function may have provided itself. We need the
|
||||
// original user code code for this
|
||||
const unwrappedFunction = await v8.unwrapIfProxyAsync(func);
|
||||
const functionString = Function.prototype.toString.call(unwrappedFunction);
|
||||
|
||||
const { file, line, column } = await v8.getFunctionLocationAsync(unwrappedFunction);
|
||||
const frame = { functionLocation: { func, file, line, column, functionString, isArrowFunction: false } };
|
||||
|
||||
context.frames.push(frame);
|
||||
|
|
|
@ -49,3 +49,9 @@ export const lookupCapturedVariableValueAsync = versionSpecificV8Module.lookupCa
|
|||
* defined. Returns { "", 0, 0 } if the location cannot be found or if the given function has no Script.
|
||||
*/
|
||||
export const getFunctionLocationAsync = versionSpecificV8Module.getFunctionLocationAsync;
|
||||
|
||||
/**
|
||||
* Given a function that is possibly a proxy wrapping a function, just return the true function
|
||||
* being wrapped.
|
||||
*/
|
||||
export const unwrapIfProxyAsync = versionSpecificV8Module.unwrapIfProxyAsync;
|
|
@ -133,6 +133,10 @@ export async function lookupCapturedVariableValueAsync(
|
|||
return undefined;
|
||||
}
|
||||
|
||||
export async function unwrapIfProxyAsync(func: Function): Promise<Function> {
|
||||
return func;
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
@ -93,6 +93,47 @@ export async function lookupCapturedVariableValueAsync(
|
|||
return undefined;
|
||||
}
|
||||
|
||||
export async function unwrapIfProxyAsync(func: Function): Promise<Function> {
|
||||
console.log("trying to unwrap proxy: " + func.toString());
|
||||
console.log("util.types: " + (util.types !== undefined));
|
||||
console.log("util.types.isProxy: " + (util.types.isProxy !== undefined));
|
||||
console.log("util.types.isProxy(f): " + util.types.isProxy(func));
|
||||
while (util.types && util.types.isProxy && util.types.isProxy(func)) {
|
||||
console.log("Got proxy");
|
||||
// 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);
|
||||
|
||||
const target = internalProperties.find(p => p.name === "[[Target]]");
|
||||
if (!target) {
|
||||
throw new Error("Could not find [[Target]] property on proxied function");
|
||||
}
|
||||
|
||||
if (!target.value) {
|
||||
throw new Error("[[Target]] property did not have [value]");
|
||||
}
|
||||
|
||||
if (!target.value.objectId) {
|
||||
throw new Error("[[Target]].value have objectId");
|
||||
}
|
||||
|
||||
const result = await getValueForObjectId(target.value.objectId);
|
||||
if (!result) {
|
||||
throw new Error("Could not retrieve target function of proxy.");
|
||||
}
|
||||
|
||||
if (!(result instanceof Function)) {
|
||||
throw new Error("Target of proxy was not a Function.");
|
||||
}
|
||||
|
||||
func = result;
|
||||
}
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -161,8 +202,8 @@ async function getRuntimeIdForFunctionAsync(func: Function): Promise<inspector.R
|
|||
}
|
||||
|
||||
async function runtimeGetPropertiesAsync(
|
||||
objectId: inspector.Runtime.RemoteObjectId,
|
||||
ownProperties: boolean | undefined) {
|
||||
objectId: inspector.Runtime.RemoteObjectId,
|
||||
ownProperties: boolean | undefined) {
|
||||
const session = <GetPropertiesSession>await v8Hooks.getSessionAsync();
|
||||
const post = util.promisify(session.post);
|
||||
|
||||
|
@ -210,11 +251,11 @@ async function getValueForObjectId(objectId: inspector.Runtime.RemoteObjectId):
|
|||
// support typesafe '.call' calls.
|
||||
const retType = <inspector.Runtime.CallFunctionOnReturnType>await post.call(
|
||||
session, "Runtime.callFunctionOn", {
|
||||
objectId,
|
||||
functionDeclaration: `function () {
|
||||
objectId,
|
||||
functionDeclaration: `function () {
|
||||
global.__inflightCalls["${tableId}"] = this;
|
||||
}`,
|
||||
});
|
||||
});
|
||||
|
||||
if (retType.exceptionDetails) {
|
||||
throw new Error(`Error calling "Runtime.callFunction(${objectId})": `
|
||||
|
|
56
sdk/nodejs/tests/runtime/jsClosureCases_11.js
Normal file
56
sdk/nodejs/tests/runtime/jsClosureCases_11.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
"use strict";
|
||||
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
|
||||
|
||||
const cases = [];
|
||||
|
||||
{
|
||||
class C {
|
||||
toString() { return "x"; }
|
||||
}
|
||||
|
||||
const proxy = new Proxy(C, {
|
||||
apply(Target, thisArg, args) {
|
||||
return new Target(...args);
|
||||
},
|
||||
construct(Target, args) {
|
||||
return new Target(...args);
|
||||
},
|
||||
get(target, p) {
|
||||
return target[p];
|
||||
}
|
||||
})
|
||||
|
||||
cases.push({
|
||||
title: "Proxied class",
|
||||
// tslint:disable-next-line
|
||||
func: function () { return proxy; },
|
||||
expectText: ` `,
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
class C {
|
||||
static toString() { return "y"; }
|
||||
}
|
||||
|
||||
const proxy = new Proxy(C, {
|
||||
apply(Target, thisArg, args) {
|
||||
return new Target(...args);
|
||||
},
|
||||
construct(Target, args) {
|
||||
return new Target(...args);
|
||||
},
|
||||
get(target, p) {
|
||||
return target[p];
|
||||
}
|
||||
})
|
||||
|
||||
cases.push({
|
||||
title: "Proxied class with static toString",
|
||||
// tslint:disable-next-line
|
||||
func: function () { return proxy; },
|
||||
expectText: ` `,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.cases = cases;
|
|
@ -39,7 +39,7 @@ export const exportedValue = 42;
|
|||
|
||||
// This group of tests ensure that we serialize closures properly.
|
||||
describe("closure", () => {
|
||||
const cases: ClosureCase[] = [];
|
||||
let cases: ClosureCase[] = [];
|
||||
|
||||
cases.push({
|
||||
title: "Empty function closure",
|
||||
|
@ -6523,17 +6523,24 @@ return function () { console.log(regex); foo(); };
|
|||
});
|
||||
}
|
||||
|
||||
cases = [];
|
||||
|
||||
// Run a bunch of direct checks on async js functions if we're in node 8 or above.
|
||||
// We can't do this inline as node6 doesn't understand 'async functions'. And we
|
||||
// can't do this in TS as TS will convert the async-function to be a normal non-async
|
||||
// function.
|
||||
if (semver.gte(process.version, "8.0.0")) {
|
||||
const jsCases = require("./jsClosureCases_8");
|
||||
cases.push(...jsCases.cases);
|
||||
}
|
||||
// if (semver.gte(process.version, "8.0.0")) {
|
||||
// const jsCases = require("./jsClosureCases_8");
|
||||
// cases.push(...jsCases.cases);
|
||||
// }
|
||||
|
||||
if (semver.gte(process.version, "10.4.0")) {
|
||||
const jsCases = require("./jsClosureCases_10_4");
|
||||
// if (semver.gte(process.version, "10.4.0")) {
|
||||
// const jsCases = require("./jsClosureCases_10_4");
|
||||
// cases.push(...jsCases.cases);
|
||||
// }
|
||||
|
||||
if (semver.gte(process.version, "11.0.0")) {
|
||||
const jsCases = require("./jsClosureCases_11");
|
||||
cases.push(...jsCases.cases);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue