@endo/eventual-send
Advanced tools
Comparing version 0.16.8 to 0.16.9
{ | ||
"name": "@endo/eventual-send", | ||
"version": "0.16.8", | ||
"version": "0.16.9", | ||
"description": "Extend a Promise class to implement the eventual-send API", | ||
@@ -15,3 +15,3 @@ "type": "module", | ||
"prepack": "tsc --build tsconfig.build.json", | ||
"postpack": "yarn clean", | ||
"postpack": "git clean -f '*.d.ts*'", | ||
"lint-fix": "yarn lint:eslint --fix && yarn lint:types", | ||
@@ -34,5 +34,5 @@ "lint-check": "yarn lint", | ||
"devDependencies": { | ||
"@endo/lockdown": "^0.1.24", | ||
"@endo/ses-ava": "^0.2.36", | ||
"ava": "^3.12.1", | ||
"@endo/lockdown": "^0.1.25", | ||
"@endo/ses-ava": "^0.2.37", | ||
"ava": "^5.1.0", | ||
"c8": "^7.7.3", | ||
@@ -58,2 +58,3 @@ "tsd": "^0.24.1" | ||
"prettier": { | ||
"arrowParens": "avoid", | ||
"trailingComma": "all", | ||
@@ -71,3 +72,3 @@ "singleQuote": true | ||
}, | ||
"gitHead": "da16a94856482e36296b7cae16d715aa63344928" | ||
"gitHead": "ab8d64ae6fc9c628a2d1c02d16bf9ef249f5c8dc" | ||
} |
@@ -1,2 +0,1 @@ | ||
// @ts-check | ||
import { trackTurns } from './track-turns.js'; | ||
@@ -3,0 +2,0 @@ |
@@ -1,2 +0,1 @@ | ||
// @ts-check | ||
/// <reference types="ses" /> | ||
@@ -8,3 +7,3 @@ import { trackTurns } from './track-turns.js'; | ||
localGet, | ||
sortedOwnKeys, | ||
getMethodNames, | ||
} from './local.js'; | ||
@@ -195,3 +194,3 @@ import { makePostponedHandler } from './postponed.js'; | ||
operation, | ||
)} (has ${q(sortedOwnKeys(handler))})`, | ||
)} (has ${q(getMethodNames(handler))})`, | ||
TypeError, | ||
@@ -278,5 +277,4 @@ ); | ||
// local handled Promise. | ||
[pendingHandler, continueForwarding] = makePostponedHandler( | ||
HandledPromise, | ||
); | ||
[pendingHandler, continueForwarding] = | ||
makePostponedHandler(HandledPromise); | ||
} | ||
@@ -372,3 +370,23 @@ | ||
const isFrozenPromiseThen = p => { | ||
/** | ||
* If the promise `p` is safe, then during the evaluation of the | ||
* expressopns `p.then` and `await p`, `p` cannot mount a reentrancy attack. | ||
* Unfortunately, due to limitations of the current JavaScript standard, | ||
* it seems impossible to prevent `p` from mounting a reentrancy attack | ||
* during the evaluation of `isSafePromise(p)`, and therefore during | ||
* operations like `HandledPromise.resolve(p)` that call | ||
* `isSafePromise(p)` synchronously. | ||
* | ||
* The `@endo/marshal` package defines a related notion of a passable | ||
* promise, i.e., one for which which `passStyleOf(p) === 'promise'`. All | ||
* passable promises are also safe. But not vice versa because the | ||
* requirements for a promise to be passable are slightly greater. A safe | ||
* promise must not override `then` or `constructor`. A passable promise | ||
* must not have any own properties. The requirements are otherwise | ||
* identical. | ||
* | ||
* @param {Promise} p | ||
* @returns {boolean} | ||
*/ | ||
const isSafePromise = p => { | ||
return ( | ||
@@ -378,3 +396,4 @@ isFrozen(p) && | ||
Promise.resolve(p) === p && | ||
getOwnPropertyDescriptor(p, 'then') === undefined | ||
getOwnPropertyDescriptor(p, 'then') === undefined && | ||
getOwnPropertyDescriptor(p, 'constructor') === undefined | ||
); | ||
@@ -423,3 +442,3 @@ }; | ||
harden(resolvedPromise); | ||
if (isFrozenPromiseThen(resolvedPromise)) { | ||
if (isSafePromise(resolvedPromise)) { | ||
// We can use the `resolvedPromise` directly, since it is guaranteed to | ||
@@ -555,3 +574,5 @@ // have a `then` which is actually `Promise.prototype.then`. | ||
const unknownBaseHandledPromise = baseHandledPromise; | ||
HandledPromise = /** @type {typeof HandledPromise} */ (unknownBaseHandledPromise); | ||
HandledPromise = /** @type {typeof HandledPromise} */ ( | ||
unknownBaseHandledPromise | ||
); | ||
@@ -558,0 +579,0 @@ // We cannot harden(HandledPromise) because we're a vetted shim which |
@@ -1,7 +0,5 @@ | ||
export function priorityValueCompare<T>([aIsPriority, aValue]: PriorityValue<T>, [bIsPriority, bValue]: PriorityValue<T>): -1 | 0 | 1; | ||
export function sortedOwnKeys(specimen: any): (string | number | symbol)[]; | ||
export function getMethodNames(val: any): (string | symbol)[]; | ||
export function localApplyFunction(t: any, args: any): any; | ||
export function localApplyMethod(t: any, method: any, args: any): any; | ||
export function localGet(t: any, key: any): any; | ||
export type PriorityValue<T> = [boolean, T]; | ||
//# sourceMappingURL=local.d.ts.map |
111
src/local.js
@@ -1,5 +0,4 @@ | ||
// @ts-check | ||
const { details: X, quote: q } = assert; | ||
const { getOwnPropertyDescriptors } = Object; | ||
const { getOwnPropertyDescriptors, getPrototypeOf, freeze } = Object; | ||
const { apply, ownKeys } = Reflect; | ||
@@ -10,81 +9,59 @@ | ||
/** | ||
* @template T | ||
* @typedef {[boolean, T]} PriorityValue | ||
* TODO Consolidate with `isObject` that's currently in `@endo/marshal` | ||
* | ||
* @param {any} val | ||
* @returns {boolean} | ||
*/ | ||
const isObject = val => Object(val) === val; | ||
/** | ||
* Compare two pairs of priority + string. | ||
* Prioritize symbols as earlier than strings. | ||
* | ||
* @template T | ||
* @param {PriorityValue<T>} param0 | ||
* @param {PriorityValue<T>} param1 | ||
* @param {string|symbol} a | ||
* @param {string|symbol} b | ||
* @returns {-1 | 0 | 1} | ||
*/ | ||
export const priorityValueCompare = ( | ||
[aIsPriority, aValue], | ||
[bIsPriority, bValue], | ||
) => { | ||
if (aIsPriority && !bIsPriority) { | ||
return -1; | ||
const compareStringified = (a, b) => { | ||
if (typeof a === typeof b) { | ||
const left = String(a); | ||
const right = String(b); | ||
// eslint-disable-next-line no-nested-ternary | ||
return left < right ? -1 : left > right ? 1 : 0; | ||
} | ||
if (!aIsPriority && bIsPriority) { | ||
return 1; | ||
} | ||
// Same priority, so compare by value. | ||
if (aValue < bValue) { | ||
if (typeof a === 'symbol') { | ||
assert(typeof b === 'string'); | ||
return -1; | ||
} | ||
if (aValue > bValue) { | ||
return 1; | ||
} | ||
return 0; | ||
assert(typeof a === 'string'); | ||
assert(typeof b === 'symbol'); | ||
return 1; | ||
}; | ||
/** | ||
* Return an ordered array of own keys of a value. | ||
* | ||
* @todo This is only useful as a diagnostic if we don't have prototype | ||
* inheritance. | ||
* @param {any} specimen value to get ownKeys of | ||
* @param {any} val | ||
* @returns {(string|symbol)[]} | ||
*/ | ||
export const sortedOwnKeys = specimen => { | ||
/** | ||
* Get the own keys of the specimen, no matter what type it is. We don't want | ||
* `ownKeys` to fail on non-objects. | ||
* | ||
* @type {(string | number | symbol)[]} | ||
*/ | ||
const keys = ownKeys(getOwnPropertyDescriptors(specimen)); | ||
/** | ||
* Symbols are higher priority than strings, regardless of stringification. | ||
* | ||
* @type {PriorityValue<string>[]} | ||
*/ | ||
const priorityValues = keys.map(key => [ | ||
typeof key === 'symbol', | ||
String(key), | ||
]); | ||
/** | ||
* Get the sorted-by-priorityValue indices into the keys array. | ||
* | ||
* @type {number[]} | ||
*/ | ||
const sortedIndices = new Array(priorityValues.length); | ||
for (let i = 0; i < priorityValues.length; i += 1) { | ||
sortedIndices[i] = i; | ||
export const getMethodNames = val => { | ||
let layer = val; | ||
const names = new Set(); // Set to deduplicate | ||
while (layer !== null && layer !== Object.prototype) { | ||
// be tolerant of non-objects | ||
const descs = getOwnPropertyDescriptors(layer); | ||
for (const name of ownKeys(descs)) { | ||
// In case a method is overridden by a non-method, | ||
// test `val[name]` rather than `layer[name]` | ||
if (typeof val[name] === 'function') { | ||
names.add(name); | ||
} | ||
} | ||
if (!isObject(val)) { | ||
break; | ||
} | ||
layer = getPrototypeOf(layer); | ||
} | ||
sortedIndices.sort((ai, bi) => | ||
priorityValueCompare(priorityValues[ai], priorityValues[bi]), | ||
); | ||
/** | ||
* Return the sorted keys. | ||
* | ||
* @type {(string | symbol)[]} | ||
*/ | ||
return sortedIndices.map(i => keys[i]); | ||
return harden([...names].sort(compareStringified)); | ||
}; | ||
// The top level of the eventual send modules can be evaluated before | ||
// ses creates `harden`, and so cannot rely on `harden` at top level. | ||
freeze(getMethodNames); | ||
@@ -116,3 +93,3 @@ export const localApplyFunction = (t, args) => { | ||
assert.fail( | ||
X`target has no method ${q(method)}, has ${q(sortedOwnKeys(t))}`, | ||
X`target has no method ${q(method)}, has ${q(getMethodNames(t))}`, | ||
TypeError, | ||
@@ -119,0 +96,0 @@ ); |
@@ -1,2 +0,1 @@ | ||
// @ts-check | ||
/// <reference types="ses" /> | ||
@@ -3,0 +2,0 @@ |
@@ -44,30 +44,32 @@ /* global globalThis */ | ||
const wrapFunction = (func, sendingError, X) => (...args) => { | ||
hiddenPriorError = sendingError; | ||
hiddenCurrentTurn += 1; | ||
hiddenCurrentEvent = 0; | ||
try { | ||
let result; | ||
const wrapFunction = | ||
(func, sendingError, X) => | ||
(...args) => { | ||
hiddenPriorError = sendingError; | ||
hiddenCurrentTurn += 1; | ||
hiddenCurrentEvent = 0; | ||
try { | ||
result = func(...args); | ||
} catch (err) { | ||
if (err instanceof Error) { | ||
assert.note( | ||
err, | ||
X`Thrown from: ${hiddenPriorError}:${hiddenCurrentTurn}.${hiddenCurrentEvent}`, | ||
); | ||
let result; | ||
try { | ||
result = func(...args); | ||
} catch (err) { | ||
if (err instanceof Error) { | ||
assert.note( | ||
err, | ||
X`Thrown from: ${hiddenPriorError}:${hiddenCurrentTurn}.${hiddenCurrentEvent}`, | ||
); | ||
} | ||
if (VERBOSE) { | ||
console.log('THROWN to top of event loop', err); | ||
} | ||
throw err; | ||
} | ||
if (VERBOSE) { | ||
console.log('THROWN to top of event loop', err); | ||
} | ||
throw err; | ||
// Must capture this now, not when the catch triggers. | ||
const detailsNote = X`Rejection from: ${hiddenPriorError}:${hiddenCurrentTurn}.${hiddenCurrentEvent}`; | ||
Promise.resolve(result).catch(addRejectionNote(detailsNote)); | ||
return harden(result); | ||
} finally { | ||
hiddenPriorError = undefined; | ||
} | ||
// Must capture this now, not when the catch triggers. | ||
const detailsNote = X`Rejection from: ${hiddenPriorError}:${hiddenCurrentTurn}.${hiddenCurrentEvent}`; | ||
Promise.resolve(result).catch(addRejectionNote(detailsNote)); | ||
return harden(result); | ||
} finally { | ||
hiddenPriorError = undefined; | ||
} | ||
}; | ||
}; | ||
@@ -74,0 +76,0 @@ /** |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
62902
28