fix(marshal): Return a special error message from passStyleOf(typedArray)
Fixes
MODIFY
packages/marshal/src/helpers/passStyle-helpers.js
packages/marshal/src/helpers/passStyle-helpers.js
/** @typedef {import('../types.js').PassStyle} PassStyle */
const { details: X, quote: q } = assert;
+const { isArray } = Array;
const {
getOwnPropertyDescriptor,
+ getPrototypeOf,
hasOwnProperty: objectHasOwnProperty,
isFrozen,
} = Object;
const { apply } = Reflect;
-const { isArray } = Array;
+const { toStringTag: toStringTagSymbol } = Symbol;
+
+const typedArrayPrototype = getPrototypeOf(Uint8Array.prototype);
+const typedArrayToStringTagDesc = getOwnPropertyDescriptor(
+ typedArrayPrototype,
+ toStringTagSymbol,
+);
+assert(typedArrayToStringTagDesc);
+const getTypedArrayToStringTag = typedArrayToStringTagDesc.get;
+assert(typeof getTypedArrayToStringTag === 'function');
export const hasOwnPropertyOf = (obj, prop) =>
apply(objectHasOwnProperty, obj, [prop]);
@@ -21,6 +32,18 @@ harden(hasOwnPropertyOf);
export const isObject = val => Object(val) === val;
harden(isObject);
+/**
+ * Duplicates packages/ses/src/make-hardener.js to avoid a dependency.
+ *
+ * @param {unknown} object
+ */
+export const isTypedArray = object => {
+ // The object must pass a brand check or toStringTag will return undefined.
+ const tag = apply(getTypedArrayToStringTag, object, []);
+ return tag !== undefined;
+};
+harden(isTypedArray);
+
export const PASS_STYLE = Symbol.for('passStyle');
/**

MODIFY
packages/marshal/src/passStyleOf.js
packages/marshal/src/passStyleOf.js
/// <reference types="ses"/>
import { isPromise } from '@endo/promise-kit';
-import { isObject, PASS_STYLE } from './helpers/passStyle-helpers.js';
+import {
+ isObject,
+ isTypedArray,
+ PASS_STYLE,
+} from './helpers/passStyle-helpers.js';
import { CopyArrayHelper } from './helpers/copyArray.js';
import { CopyRecordHelper } from './helpers/copyRecord.js';
@@ -141,10 +145,15 @@ const makePassStyleOf = passStyleHelpers => {
if (inner === null) {
return 'null';
}
- (isFrozen(inner)) ||
+ if (!isFrozen(inner)) {
assert.fail(
- X`Cannot pass non-frozen objects like ${inner}. Use harden()`,
+ // TypedArrays get special treatment in harden()
+ // and a corresponding special error message here.
+ isTypedArray(inner)
+ ? X`Cannot pass mutable typed arrays like ${inner}.`
+ : X`Cannot pass non-frozen objects like ${inner}. Use harden()`,
);
+ }
if (isPromise(inner)) {
assertSafePromise(inner);
return 'promise';

MODIFY
packages/marshal/test/test-marshal-stringify.js
packages/marshal/test/test-marshal-stringify.js
@@ -32,6 +32,12 @@ test('marshal stringify errors', t => {
t.throws(() => stringify({}), {
message: /Cannot pass non-frozen objects like .*. Use harden()/,
});
+ t.throws(() => stringify(harden(new Uint8Array(1))), {
+ message: 'Cannot pass mutable typed arrays like "[Uint8Array]".',
+ });
+ t.throws(() => stringify(harden(new Int16Array(1))), {
+ message: 'Cannot pass mutable typed arrays like "[Int16Array]".',
+ });
t.throws(() => stringify(harden(Promise.resolve(8))), {
message: /Marshal's stringify rejects presences and promises .*/,

MODIFY
packages/ses/src/make-hardener.js
packages/ses/src/make-hardener.js
@@ -66,7 +66,10 @@ const getTypedArrayToStringTag = typedArrayToStringTag.get;
assert(getTypedArrayToStringTag);
// Exported for tests.
-/** @param {unknown} object */
+/**
+ * Duplicates packages/marshal/src/helpers/passStyle-helpers.js to avoid a dependency.
+ * @param {unknown} object
+ */
export const isTypedArray = object => {
// The object must pass a brand check or toStringTag will return undefined.
const tag = apply(getTypedArrayToStringTag, object, []);

fix(marshal): Return a special error message from passStyleOf(typedArray)

Fixes #1326