@agoric/marshal
Advanced tools
Comparing version 0.4.19 to 0.4.20
@@ -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 @@ |
{ | ||
"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 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
94986
1791
Yes
6
Updated@agoric/assert@^0.3.7
Updated@agoric/promise-kit@^0.2.21