Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@agoric/marshal

Package Overview
Dependencies
Maintainers
5
Versions
156
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@agoric/marshal - npm Package Compare versions

Comparing version 0.4.19 to 0.4.20

8

CHANGELOG.md

@@ -6,2 +6,10 @@ # Change Log

### [0.4.20](https://github.com/Agoric/agoric-sdk/compare/@agoric/marshal@0.4.19...@agoric/marshal@0.4.20) (2021-07-28)
**Note:** Version bump only for package @agoric/marshal
### [0.4.19](https://github.com/Agoric/agoric-sdk/compare/@agoric/marshal@0.4.18...@agoric/marshal@0.4.19) (2021-07-01)

@@ -8,0 +16,0 @@

26

package.json
{
"name": "@agoric/marshal",
"version": "0.4.19",
"version": "0.4.20",
"description": "marshal",
"parsers": {
"js": "mjs"
},
"type": "module",
"main": "index.js",

@@ -15,2 +13,3 @@ "directories": {

"test": "ava",
"test:c8": "c8 $C8_OPTIONS ava --config=ava-nesm.config.js",
"test:xs": "exit 0",

@@ -39,12 +38,14 @@ "pretty-fix": "prettier --write '**/*.js'",

"dependencies": {
"@agoric/assert": "^0.3.6",
"@agoric/eventual-send": "^0.13.22",
"@agoric/assert": "^0.3.7",
"@agoric/eventual-send": "^0.13.23",
"@agoric/nat": "^4.1.0",
"@agoric/promise-kit": "^0.2.20"
"@agoric/promise-kit": "^0.2.21"
},
"devDependencies": {
"@agoric/install-ses": "^0.5.20",
"@agoric/swingset-vat": "^0.18.6",
"@agoric/install-ses": "^0.5.21",
"@agoric/swingset-vat": "^0.19.0",
"@endo/ses-ava": "^0.2.4",
"ava": "^3.12.1",
"esm": "agoric-labs/esm#Agoric-built"
"c8": "^7.7.2",
"ses": "^0.13.4"
},

@@ -72,8 +73,5 @@ "files": [

],
"require": [
"esm"
],
"timeout": "2m"
},
"gitHead": "6410fbc01abf1f0d0fa3d6a1275fe8f117b98b0a"
"gitHead": "835ce6579a257107f363e29a234643679844cc0e"
}

@@ -31,2 +31,4 @@ // @ts-check

const { prototype: functionPrototype } = Function;
const { ownKeys } = Reflect;

@@ -41,7 +43,6 @@

* @param {T} val input value. NOTE: Must be hardened!
* @param {WeakMap<any,any>} [already=new WeakMap()]
* @returns {T} pure, hardened copy
*/
function pureCopy(val, already = new WeakMap()) {
// eslint-disable-next-line no-use-before-define
export const pureCopy = val => {
// passStyleOf now asserts that val has no pass-by-copy cycles.
const passStyle = passStyleOf(val);

@@ -61,5 +62,2 @@ switch (passStyle) {

const obj = /** @type {Object} */ (val);
if (already.has(obj)) {
return already.get(obj);
}

@@ -69,5 +67,2 @@ // Create a new identity.

// Prevent recursion.
already.set(obj, copy);
// Make a deep copy on the new identity.

@@ -80,3 +75,3 @@ // Object.entries(obj) takes a snapshot (even if a Proxy).

Object.entries(obj).forEach(([prop, value]) => {
copy[prop] = pureCopy(value, already);
copy[prop] = pureCopy(value);
});

@@ -87,15 +82,20 @@ return harden(copy);

case 'copyError': {
// passStyleOf is currently not fully validating of error objects,
// in order to tolerate malformed error objects to preserve the initial
// complaint, rather than complain about the form of the complaint.
// However, pureCopy(error) must be safe. We should obtain nothing from
// the error object other than the `name` and `message` and we should
// only copy stringified forms of these, where `name` must be an
// error constructor name.
const unk = /** @type {unknown} */ (val);
const err = /** @type {Error} */ (unk);
if (already.has(err)) {
return already.get(err);
}
const { name, message } = err;
// eslint-disable-next-line no-use-before-define
const EC = getErrorConstructor(`${name}`) || Error;
const copy = harden(new EC(`${message}`));
already.set(err, copy);
// Even the cleaned up error copy, if sent to the console, should
// cause hidden diagnostic information of the original error
// to be logged.
assert.note(copy, X`copied from error ${err}`);

@@ -125,16 +125,26 @@ const unk2 = /** @type {unknown} */ (harden(copy));

}
}
};
harden(pureCopy);
export { pureCopy };
/**
* @param {Object|null} oldProto
* @param {Object} remotable
* @param {InterfaceSpec} iface
* @returns {Object}
*/
const makeRemotableProto = (oldProto, iface) => {
assert(
oldProto === objectPrototype || oldProto === null,
X`For now, remotables cannot inherit from anything unusual`,
);
const makeRemotableProto = (remotable, iface) => {
const oldProto = getPrototypeOf(remotable);
if (typeof remotable === 'object') {
assert(
oldProto === objectPrototype || oldProto === null,
X`For now, remotables cannot inherit from anything unusual, in ${remotable}`,
);
} else if (typeof remotable === 'function') {
assert(
oldProto === functionPrototype ||
getPrototypeOf(oldProto) === functionPrototype,
X`Far functions must originally inherit from Function.prototype, in ${remotable}`,
);
} else {
assert.fail(X`unrecognized typeof ${remotable}`);
}
// Assign the arrow function to a variable to set its .name.

@@ -158,11 +168,3 @@ const toString = () => `[${iface}]`;

/**
* @template Slot
* @type {ConvertValToSlot<Slot>}
*/
const defaultValToSlotFn = x => x;
/**
* @template Slot
* @type {ConvertSlotToVal<Slot>}
*/
const defaultSlotToValFn = (x, _) => x;

@@ -227,20 +229,2 @@

slotMap.set(val, slotIndex);
/*
if (iface === undefined && passStyleOf(val) === 'remotable') {
// iface = `Alleged: remotable at slot ${slotIndex}`;
if (
getPrototypeOf(val) === objectPrototype &&
ownKeys(val).length === 0
) {
// For now, skip the diagnostic if we have a pure empty object
} else {
try {
assert.fail(X`Serialize ${val} generates needs iface`);
} catch (err) {
console.info(err);
}
}
}
*/
}

@@ -667,3 +651,3 @@

assert(!isFrozen(remotable), X`Remotable ${remotable} is already frozen`);
const remotableProto = makeRemotableProto(getPrototypeOf(remotable), iface);
const remotableProto = makeRemotableProto(remotable, iface);

@@ -670,0 +654,0 @@ // Take a static copy of the enumerable own properties as data properties.

@@ -19,3 +19,5 @@ // @ts-check

// flag entirely and fix code that uses it (as if it were always `false`).
const ALLOW_IMPLICIT_REMOTABLES = true;
//
// Exported only for testing during the transition
export const ALLOW_IMPLICIT_REMOTABLES = true;

@@ -29,2 +31,4 @@ const {

const { prototype: functionPrototype } = Function;
const { ownKeys } = Reflect;

@@ -34,2 +38,5 @@

// TODO: Maintenance hazard: Coordinate with the list of errors in the SES
// whilelist. Currently, both omit AggregateError, which is now standard. Both
// must eventually include it.
const errorConstructors = new Map([

@@ -45,5 +52,4 @@ ['Error', Error],

export function getErrorConstructor(name) {
return errorConstructors.get(name);
}
export const getErrorConstructor = name => errorConstructors.get(name);
harden(getErrorConstructor);

@@ -58,6 +64,9 @@ /**

*
* TODO: BUG: SECURITY: Making this tolerant of malformed errors conflicts with
* having passStyleOf be validating.
*
* @param {Passable} val
* @returns {boolean}
*/
function isPassByCopyError(val) {
const isPassByCopyError = val => {
// TODO: Need a better test than instanceof

@@ -104,9 +113,10 @@ if (!(val instanceof Error)) {

return true;
}
};
/**
* @param {Passable} val
* @param { Set<Passable> } inProgress
* @returns {boolean}
*/
function isPassByCopyArray(val) {
const isPassByCopyArray = (val, inProgress) => {
if (!Array.isArray(val)) {

@@ -131,7 +141,2 @@ return false;

assert(
typeof desc.value !== 'function',
X`Arrays must not contain methods: ${q(i)}`,
TypeError,
);
assert(
desc.enumerable,

@@ -141,2 +146,5 @@ X`Array elements must be enumerable: ${q(i)}`,

);
// Recursively validate that each member is passable.
// eslint-disable-next-line no-use-before-define
passStyleOfRecur(desc.value, inProgress);
}

@@ -149,11 +157,27 @@ assert(

return true;
}
};
/**
* For a function to be a valid method, it must not be passable.
* Otherwise, we risk confusing pass-by-copy data carrying
* far functions with attempts at far objects with methods.
*
* TODO HAZARD Because we check this on the way to hardening a remotable,
* we cannot yet check that `func` is hardened. However, without
* doing so, it's inheritance might change after the `PASS_STYLE`
* check below.
*
* @param {*} func
* @returns {boolean}
*/
const canBeMethod = func => typeof func === 'function' && !(PASS_STYLE in func);
/**
* @param {Passable} val
* @param { Set<Passable> } inProgress
* @returns {boolean}
*/
function isPassByCopyRecord(val) {
const isPassByCopyRecord = (val, inProgress) => {
const proto = getPrototypeOf(val);
if (proto !== objectPrototype) {
if (proto !== objectPrototype && proto !== null) {
return false;

@@ -165,7 +189,8 @@ }

for (const descKey of descKeys) {
if (typeof descKey === 'symbol') {
if (typeof descKey !== 'string') {
// Pass by copy records can only have string-named own properties
return false;
}
const desc = descs[descKey];
if (typeof desc.value === 'function') {
if (canBeMethod(desc.value)) {
return false;

@@ -175,8 +200,3 @@ }

for (const descKey of descKeys) {
assert.typeof(
descKey,
'string',
X`Pass by copy records can only have string-named own properties`,
);
const desc = descs[descKey];
const desc = descs[/** @type {string} */ (descKey)];
assert(

@@ -192,5 +212,8 @@ !('get' in desc),

);
// Recursively validate that each member is passable.
// eslint-disable-next-line no-use-before-define
passStyleOfRecur(desc.value, inProgress);
}
return true;
}
};

@@ -238,26 +261,41 @@ // Below we have a series of predicate functions and their (curried) assertion

*/
const assertIface = iface => checkIface(iface, assertChecker);
export const assertIface = iface => checkIface(iface, assertChecker);
harden(assertIface);
export { assertIface };
/**
* TODO: It would be nice to typedef this shape, but we can't declare a type
* with PASS_STYLE from JSDoc.
*
* @param {{
* [PASS_STYLE]: string,
* [Symbol.toStringTag]: string,
* toString: () => void }} val the value to verify
* @param {any} original
* @param {Checker} [check]
* @returns {boolean}
*/
const checkRemotableProto = (val, check = x => x) => {
const checkRemotableProtoOf = (original, check = x => x) => {
/**
* TODO: It would be nice to typedef this shape, but we can't declare a type
* with PASS_STYLE from JSDoc.
*
* @type {{ [PASS_STYLE]: string,
* [Symbol.toStringTag]: string,
* toString: () => void
* }}
*/
const proto = getPrototypeOf(original);
if (
!(
check(
typeof val === 'object',
X`cannot serialize non-objects like ${val}`,
typeof proto === 'object',
X`cannot serialize non-objects like ${proto}`,
) &&
check(!Array.isArray(val), X`Arrays cannot be pass-by-remote`) &&
check(val !== null, X`null cannot be pass-by-remote`)
check(isFrozen(proto), X`The Remotable proto must be frozen`) &&
check(!Array.isArray(proto), X`Arrays cannot be pass-by-remote`) &&
check(proto !== null, X`null cannot be pass-by-remote`) &&
check(
// Since we're working with TypeScript's unsound type system, mostly
// to catch accidents and to provide IDE support, we type arguments
// like `val` according to what they are supposed to be. The following
// tests for a particular violation. However, TypeScript complains
// because *if the declared type were accurate*, then the condition
// would always return true.
// @ts-ignore TypeScript assumes what we're trying to check
proto !== objectPrototype,
X`Remotables must now be explicitly declared: ${q(original)}`,
)
)

@@ -268,12 +306,25 @@ ) {

const protoProto = getPrototypeOf(val);
if (
!(
check(
const protoProto = getPrototypeOf(proto);
if (typeof original === 'object') {
if (
!check(
protoProto === objectPrototype || protoProto === null,
X`The Remotable Proto marker cannot inherit from anything unusual`,
) && check(isFrozen(val), X`The Remotable proto must be frozen`)
)
) {
return false;
)
) {
return false;
}
} else if (typeof original === 'function') {
if (
!check(
protoProto === functionPrototype ||
getPrototypeOf(protoProto) === functionPrototype,
X`For far functions, the Remotable Proto marker must inherit from Function.prototype, in ${original}`,
)
) {
return false;
}
} else {
assert.fail(X`unrecognized typeof ${original}`);
}

@@ -287,3 +338,3 @@

...rest
} = getOwnPropertyDescriptors(val);
} = getOwnPropertyDescriptors(proto);

@@ -316,7 +367,7 @@ return (

*/
function checkCanBeRemotable(val, check = x => x) {
const checkCanBeRemotable = (val, check = x => x) => {
if (
!(
check(
typeof val === 'object',
typeof val === 'object' || typeof val === 'function',
X`cannot serialize non-objects like ${val}`,

@@ -332,34 +383,55 @@ ) &&

const descs = getOwnPropertyDescriptors(val);
const keys = ownKeys(descs); // enumerable-and-not, string-or-Symbol
return keys.every(
key =>
// Typecast needed due to https://github.com/microsoft/TypeScript/issues/1863
if (typeof val === 'object') {
const keys = ownKeys(descs); // enumerable-and-not, string-or-Symbol
return keys.every(
key =>
// Typecast needed due to https://github.com/microsoft/TypeScript/issues/1863
check(
!('get' in descs[/** @type {string} */ (key)]),
X`cannot serialize Remotables with accessors like ${q(
String(key),
)} in ${val}`,
) &&
check(
canBeMethod(val[key]),
X`cannot serialize Remotables with non-methods like ${q(
String(key),
)} in ${val}`,
) &&
check(
key !== PASS_STYLE,
X`A pass-by-remote cannot shadow ${q(PASS_STYLE)}`,
),
);
} else if (typeof val === 'function') {
// Far functions cannot be methods, and cannot have methods.
// They must have exactly expected `.name` and `.length` properties
const { name: nameDesc, length: lengthDesc, ...restDescs } = descs;
const restKeys = ownKeys(restDescs);
return (
check(
!('get' in descs[/** @type {string} */ (key)]),
X`cannot serialize objects with getters like ${q(
String(key),
)} in ${val}`,
nameDesc && typeof nameDesc.value === 'string',
X`Far function name must be a string, in ${val}`,
) &&
check(
typeof val[key] === 'function',
X`cannot serialize objects with non-methods like ${q(
String(key),
)} in ${val}`,
lengthDesc && typeof lengthDesc.value === 'number',
X`Far function length must be a number, in ${val}`,
) &&
check(
key !== PASS_STYLE,
X`A pass-by-remote cannot shadow ${q(PASS_STYLE)}`,
),
);
}
restKeys.length === 0,
X`Far functions unexpected properties besides .name and .length ${restKeys}`,
)
);
} else {
assert.fail(X`unrecognized typeof ${val}`);
}
};
const canBeRemotable = val => checkCanBeRemotable(val);
export const canBeRemotable = val => checkCanBeRemotable(val);
harden(canBeRemotable);
export { canBeRemotable };
const assertCanBeRemotable = val => {
export const assertCanBeRemotable = val => {
checkCanBeRemotable(val, assertChecker);
};
harden(assertCanBeRemotable);
export { assertCanBeRemotable };

@@ -371,3 +443,3 @@ /**

*/
function checkRemotable(val, check = x => x) {
const checkRemotable = (val, check = x => x) => {
const not = (cond, details) => !check(cond, details);

@@ -383,6 +455,10 @@ if (not(isFrozen(val), X`cannot serialize non-frozen objects like ${val}`)) {

if (ALLOW_IMPLICIT_REMOTABLES && (p === null || p === objectPrototype)) {
const err = assert.error(
X`Remotables should be explicitly declared: ${q(val)}`,
);
console.warn('Missing Far:', err);
return true;
}
return checkRemotableProto(p, check);
}
return checkRemotableProtoOf(val, check);
};

@@ -397,5 +473,5 @@ /**

/** @type {MarshalGetInterfaceOf} */
const getInterfaceOf = val => {
export const getInterfaceOf = val => {
if (
typeof val !== 'object' ||
(typeof val !== 'object' && typeof val !== 'function') ||
val === null ||

@@ -410,38 +486,9 @@ val[PASS_STYLE] !== 'remotable' ||

harden(getInterfaceOf);
export { getInterfaceOf };
/**
* objects can only be passed in one of two/three forms:
* 1: pass-by-remote: all properties (own and inherited) are methods,
* the object itself is of type object, not function
* 2: pass-by-copy: all string-named own properties are data, not methods
* the object must inherit from objectPrototype or null
* 3: the empty object is pass-by-remote, for identity comparison
*
* all objects must be frozen
*
* anything else will throw an error if you try to serialize it
* with these restrictions, our remote call/copy protocols expose all useful
* behavior of these objects: pass-by-remote objects have no other data (so
* there's nothing else to copy), and pass-by-copy objects have no other
* behavior (so there's nothing else to invoke)
*
* How would val be passed? For primitive values, the answer is
* * 'null' for null
* * throwing an error for a symbol, whether registered or not.
* * that value's typeof string for all other primitive values
* For frozen objects, the possible answers
* * 'copyRecord' for non-empty records with only data properties
* * 'copyArray' for arrays with only data properties
* * 'copyError' for instances of Error with only data properties
* * 'remotable' for non-array objects with only method properties
* * 'promise' for genuine promises only
* * throwing an error on anything else, including thenables.
* We export passStyleOf so other algorithms can use this module's
* classification.
*
* @param {Passable} val
* @returns {PassStyle}
* @param { Passable } val
* @param { Set<Passable> } inProgress
* @returns { PassStyle }
*/
export function passStyleOf(val) {
const passStyleOfInternal = (val, inProgress) => {
const typestr = typeof val;

@@ -470,15 +517,21 @@ switch (typestr) {

}
if (isPassByCopyArray(val)) {
if (isPassByCopyArray(val, inProgress)) {
return 'copyArray';
}
if (isPassByCopyRecord(val)) {
if (isPassByCopyRecord(val, inProgress)) {
return 'copyRecord';
}
assertRemotable(val);
// console.log(`--- @@marshal: pass-by-ref object without Far/Remotable`);
// assert.fail(X`pass-by-ref object without Far/Remotable`);
return 'remotable';
}
case 'function': {
assert.fail(X`Bare functions like ${val} are disabled for now`);
if (getInterfaceOf(val)) {
return 'remotable';
}
assert(
isFrozen(val),
X`Cannot pass non-frozen objects like ${val}. Use harden()`,
);
assertRemotable(val);
return 'remotable';
}

@@ -497,2 +550,80 @@ case 'undefined':

}
}
};
/**
* Purely for performance. However it is mutable static state, and
* it does have some observability on proxies. TODO need to assess
* whether this creates a static communications channel.
*
* passStyleOf does a full recursive walk of pass-by-copy
* structures, in order to validate that they are acyclic. In addition
* it is used by other algorithms to recursively walk these pass-by-copy
* structures, so without this cache, these algorithms could be
* O(N**2) or worse.
*
* @type {WeakMap<Passable, PassStyle>}
*/
const passStyleOfCache = new WeakMap();
/**
* @param { Passable } val
* @param { Set<Passable> } inProgress
* @returns { PassStyle }
*/
const passStyleOfRecur = (val, inProgress) => {
const isObject = Object(val) === val;
if (isObject) {
if (passStyleOfCache.has(val)) {
// @ts-ignore
return passStyleOfCache.get(val);
}
assert(!inProgress.has(val), X`Pass-by-copy data cannot be cyclic ${val}`);
inProgress.add(val);
}
const passStyle = passStyleOfInternal(val, inProgress);
if (isObject) {
passStyleOfCache.set(val, passStyle);
inProgress.delete(val);
}
return passStyle;
};
/**
* objects can only be passed in one of two/three forms:
* 1: pass-by-remote: all properties (own and inherited) are methods,
* the object itself is of type object, not function
* 2: pass-by-copy: all string-named own properties are data, not methods
* the object must inherit from objectPrototype or null
* 3: the empty object is pass-by-remote, for identity comparison
*
* all objects must be frozen
*
* anything else will throw an error if you try to serialize it
* with these restrictions, our remote call/copy protocols expose all useful
* behavior of these objects: pass-by-remote objects have no other data (so
* there's nothing else to copy), and pass-by-copy objects have no other
* behavior (so there's nothing else to invoke)
*
* How would val be passed? For primitive values, the answer is
* * 'null' for null
* * throwing an error for a symbol, whether registered or not.
* * that value's typeof string for all other primitive values
* For frozen objects, the possible answers
* * 'copyRecord' for non-empty records with only data properties
* * 'copyArray' for arrays with only data properties
* * 'copyError' for instances of Error with only data properties
* * 'remotable' for non-array objects with only method properties
* * 'promise' for genuine promises only
* * throwing an error on anything else, including thenables.
* We export passStyleOf so other algorithms can use this module's
* classification.
*
* @param {Passable} val
* @returns {PassStyle}
*/
export const passStyleOf = val =>
// Even when a WeakSet is correct, when the set has a shorter lifetime
// than its keys, we prefer a Set due to expected implementation
// tradeoffs.
passStyleOfRecur(val, new Set());
harden(passStyleOf);

@@ -148,6 +148,6 @@ // eslint-disable-next-line spaced-comment

* @callback MakeMarshal
* @param {ConvertValToSlot=} convertValToSlot
* @param {ConvertSlotToVal=} convertSlotToVal
* @param {ConvertValToSlot<Slot>=} convertValToSlot
* @param {ConvertSlotToVal<Slot>=} convertSlotToVal
* @param {MakeMarshalOptions=} options
* @returns {Marshal}
* @returns {Marshal<Slot>}
*/

@@ -154,0 +154,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc