From be88da5741ebad4ed19a8b9e6a572b7b1900cd3e Mon Sep 17 00:00:00 2001 From: LeoTM <1881059+leotm@users.noreply.github.com> Date: Mon, 30 Sep 2024 16:43:18 +0100 Subject: [PATCH 01/11] feat(ses): add AsyncGeneratorFunctionInstance to commons Co-authored-by: Saleh Abdel Motaal <849613+SMotaal@users.noreply.github.com> --- packages/ses/src/commons.js | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/packages/ses/src/commons.js b/packages/ses/src/commons.js index 5925c17eae..3520b6a637 100644 --- a/packages/ses/src/commons.js +++ b/packages/ses/src/commons.js @@ -386,3 +386,49 @@ export const FERAL_STACK_GETTER = feralStackGetter; * @type {((newValue: any) => void) | undefined} */ export const FERAL_STACK_SETTER = feralStackSetter; + +const getAsyncGeneratorFunctionInstance = () => { + // Test for async generator function syntax support. + try { + // Wrapping one in an new Function lets the `hermesc` binary file + // parse the Metro js bundle without SyntaxError, to generate the + // optimised Hermes bytecode bundle, when `gradlew` is called to + // assemble the release build APK for React Native prod Android apps. + // Delaying the error until runtime lets us customise lockdown behaviour. + return new FERAL_FUNCTION( + 'return (async function* AsyncGeneratorFunctionInstance() {})', + )(); + } catch (error) { + // Note: `Error.prototype.jsEngine` is only set by React Native runtime, not Hermes: + // /~https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.cpp#L224-L230 + if (error.name === 'SyntaxError') { + // Swallows Hermes error `async generators are unsupported` at runtime. + if (globalThis.console !== undefined) { + // eslint-disable-next-line @endo/no-polymorphic-call + console.warn('SES Skipping async generators'); + } + if (globalThis.print !== undefined) { + // @ts-expect-error Hermes defines a print fn that accepts one or more arguments. + // eslint-disable-next-line no-undef + print('SES Skipping async generators'); + } + // Note: `console` is not a JS built-in, so Hermes engine throws: + // Uncaught ReferenceError: Property 'console' doesn't exist + // See: /~https://github.com/facebook/hermes/issues/675 + // However React Native provides a `console` implementation when setting up error handling: + // /~https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/InitializeCore.js + return undefined; + } else { + throw error; + } + } +}; + +/** + * If the platform supports async generator functions, this will be an + * async generator function instance. Otherwise, it will be `undefined`. + * + * @type {AsyncGeneratorFunction | undefined} + */ +export const AsyncGeneratorFunctionInstance = + getAsyncGeneratorFunctionInstance(); From fa68e5649cf26f27c342b3552e80f2a4ad166ae8 Mon Sep 17 00:00:00 2001 From: LeoTM <1881059+leotm@users.noreply.github.com> Date: Mon, 30 Sep 2024 17:06:33 +0100 Subject: [PATCH 02/11] feat(ses): include async generators in anonymous intrinsics if supported --- packages/ses/src/get-anonymous-intrinsics.js | 41 +++++++++++--------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/packages/ses/src/get-anonymous-intrinsics.js b/packages/ses/src/get-anonymous-intrinsics.js index f7fcd37118..9e8eaf8fb9 100644 --- a/packages/ses/src/get-anonymous-intrinsics.js +++ b/packages/ses/src/get-anonymous-intrinsics.js @@ -14,6 +14,8 @@ import { matchAllSymbol, regexpPrototype, globalThis, + assign, + AsyncGeneratorFunctionInstance, } from './commons.js'; import { InertCompartment } from './compartment.js'; @@ -95,20 +97,6 @@ export const getAnonymousIntrinsics = () => { const Generator = GeneratorFunction.prototype; - // 25.3.1 The AsyncGeneratorFunction Constructor - - // eslint-disable-next-line no-empty-function - async function* AsyncGeneratorFunctionInstance() {} - const AsyncGeneratorFunction = getConstructorOf( - AsyncGeneratorFunctionInstance, - ); - - // 25.3.2.2 AsyncGeneratorFunction.prototype - const AsyncGenerator = AsyncGeneratorFunction.prototype; - // 25.5.1 Properties of the AsyncGenerator Prototype Object - const AsyncGeneratorPrototype = AsyncGenerator.prototype; - const AsyncIteratorPrototype = getPrototypeOf(AsyncGeneratorPrototype); - // 25.7.1 The AsyncFunction Constructor // eslint-disable-next-line no-empty-function @@ -119,10 +107,6 @@ export const getAnonymousIntrinsics = () => { '%InertFunction%': InertFunction, '%ArrayIteratorPrototype%': ArrayIteratorPrototype, '%InertAsyncFunction%': AsyncFunction, - '%AsyncGenerator%': AsyncGenerator, - '%InertAsyncGeneratorFunction%': AsyncGeneratorFunction, - '%AsyncGeneratorPrototype%': AsyncGeneratorPrototype, - '%AsyncIteratorPrototype%': AsyncIteratorPrototype, '%Generator%': Generator, '%InertGeneratorFunction%': GeneratorFunction, '%IteratorPrototype%': IteratorPrototype, @@ -135,6 +119,27 @@ export const getAnonymousIntrinsics = () => { '%InertCompartment%': InertCompartment, }; + if (AsyncGeneratorFunctionInstance !== undefined) { + // 25.3.1 The AsyncGeneratorFunction Constructor + + const AsyncGeneratorFunction = getConstructorOf( + AsyncGeneratorFunctionInstance, + ); + + // 25.3.2.2 AsyncGeneratorFunction.prototype + const AsyncGenerator = AsyncGeneratorFunction.prototype; + // 25.5.1 Properties of the AsyncGenerator Prototype Object + const AsyncGeneratorPrototype = AsyncGenerator.prototype; + const AsyncIteratorPrototype = getPrototypeOf(AsyncGeneratorPrototype); + + assign(intrinsics, { + '%AsyncGenerator%': AsyncGenerator, + '%InertAsyncGeneratorFunction%': AsyncGeneratorFunction, + '%AsyncGeneratorPrototype%': AsyncGeneratorPrototype, + '%AsyncIteratorPrototype%': AsyncIteratorPrototype, + }); + } + if (globalThis.Iterator) { intrinsics['%IteratorHelperPrototype%'] = getPrototypeOf( // eslint-disable-next-line @endo/no-polymorphic-call From 8c7e6d1ad4d3005efe30e1ae10ec854e1aba2694 Mon Sep 17 00:00:00 2001 From: LeoTM <1881059+leotm@users.noreply.github.com> Date: Mon, 30 Sep 2024 17:07:23 +0100 Subject: [PATCH 03/11] feat(ses): tame async generator function constructors if supported --- packages/ses/src/tame-function-constructors.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/ses/src/tame-function-constructors.js b/packages/ses/src/tame-function-constructors.js index 4380ae4d4e..402b1b3b82 100644 --- a/packages/ses/src/tame-function-constructors.js +++ b/packages/ses/src/tame-function-constructors.js @@ -6,6 +6,7 @@ import { getPrototypeOf, setPrototypeOf, freeze, + AsyncGeneratorFunctionInstance, } from './commons.js'; // This module replaces the original `Function` constructor, and the original @@ -126,11 +127,14 @@ export default function tameFunctionConstructors() { '%InertAsyncFunction%', '(async function(){})', ); - repairFunction( - 'AsyncGeneratorFunction', - '%InertAsyncGeneratorFunction%', - '(async function*(){})', - ); + + if (AsyncGeneratorFunctionInstance !== undefined) { + repairFunction( + 'AsyncGeneratorFunction', + '%InertAsyncGeneratorFunction%', + '(async function*(){})', + ); + } return newIntrinsics; } From 1cdbea8c16f40869d6928918ce1c87ba681d9b01 Mon Sep 17 00:00:00 2001 From: LeoTM <1881059+leotm@users.noreply.github.com> Date: Mon, 30 Sep 2024 17:10:18 +0100 Subject: [PATCH 04/11] feat(ses): create async arrow function transform with Babel for Hermes bundle --- packages/ses/scripts/hermes-transforms.js | 83 +++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 packages/ses/scripts/hermes-transforms.js diff --git a/packages/ses/scripts/hermes-transforms.js b/packages/ses/scripts/hermes-transforms.js new file mode 100644 index 0000000000..b7b4486d53 --- /dev/null +++ b/packages/ses/scripts/hermes-transforms.js @@ -0,0 +1,83 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { parse } from '@babel/parser'; +import babelGenerate from '@agoric/babel-generator'; +import babelTraverse from '@babel/traverse'; +import * as t from '@babel/types'; + +// TODO The following is sufficient on Node.js, but for compatibility with +// `node -r esm`, we must use the pattern below. +// Remove after /~https://github.com/Agoric/agoric-sdk/issues/8671. +// OR, upgrading to Babel 8 probably addresses this defect. +const traverse = babelTraverse.default || babelTraverse; +const generate = babelGenerate.default || babelGenerate; + +const decoder = new TextDecoder(); +const encoder = new TextEncoder(); + +const asyncArrowEliminator = { + ArrowFunctionExpression(path) { + if (path.node.async) { + let body = path.node.body; + + path.traverse({ + ThisExpression(innerPath) { + const { start } = innerPath.node.loc; + // throw path.buildCodeFrameError("..."); // /~https://github.com/babel/babel/issues/8617 + throw Error( + `Hermes makeBundle Babel transform doesn't support 'this' keyword in async arrow functions. + at this (${path.state.filename}:${start.line}:${start.column})`, + ); + }, + // No need for an Identifier traversal on nodes matching 'arguments' to error on + // Since only non-arrow functions can access the `arguments` array-like object + }); + + // In case it's a ()=>expression style arrow function + if (!t.isBlockStatement(body)) { + body = t.blockStatement([t.returnStatement(body)]); + } + + const functionExpression = t.functionExpression( + undefined, + path.node.params, + body, + path.node.generator, + path.node.async, + ); + + path.replaceWith(functionExpression); + } + }, +}; + +export const hermesTransforms = { + mjs: async ( + sourceBytes, + specifier, + location, + _packageLocation, + { sourceMap }, + ) => { + const transforms = { + ...asyncArrowEliminator, + // Some transforms might be added based on the specifier later + }; + + const sourceString = decoder.decode(sourceBytes); + + const ast = parse(sourceString, { + sourceType: 'module', + }); + + traverse(ast, transforms, undefined, { filename: location }); + + const { code } = generate(ast, { + // Nothing being done with sourcemaps as this point + retainLines: true, + compact: true, + verbatim: true, + }); + + return { bytes: encoder.encode(code), parser: 'mjs', sourceMap }; + }, +}; From fa520caa22fb10319cb63dd241d287edabef42a0 Mon Sep 17 00:00:00 2001 From: LeoTM <1881059+leotm@users.noreply.github.com> Date: Mon, 30 Sep 2024 17:19:50 +0100 Subject: [PATCH 05/11] feat(ses): bundle and export shim compatible with Hermes compiler --- packages/ses/package.json | 10 +++++- packages/ses/scripts/bundle.js | 61 ++++++++++++++++++++++++---------- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/packages/ses/package.json b/packages/ses/package.json index e552bda739..ba9a44ca15 100644 --- a/packages/ses/package.json +++ b/packages/ses/package.json @@ -56,6 +56,12 @@ "default": "./dist/ses.cjs" } }, + "./hermes": { + "require": { + "types": "./dist/types.d.cts", + "default": "./dist/ses-hermes.cjs" + } + }, "./tools.js": "./tools.js", "./assert-shim.js": "./assert-shim.js", "./lockdown-shim.js": { @@ -70,7 +76,9 @@ "./package.json": "./package.json" }, "scripts": { - "build": "node scripts/bundle.js", + "build:vanilla": "node scripts/bundle.js", + "build:hermes": "node scripts/bundle.js hermes", + "build": "yarn build:vanilla && yarn build:hermes", "clean": "rm -rf dist", "cover": "c8 ava", "demo": "python3 -m http.server", diff --git a/packages/ses/scripts/bundle.js b/packages/ses/scripts/bundle.js index 9b330299f1..05b9212848 100644 --- a/packages/ses/scripts/bundle.js +++ b/packages/ses/scripts/bundle.js @@ -4,6 +4,7 @@ import fs from 'fs'; import { makeBundle } from '@endo/compartment-mapper/bundle.js'; import { minify } from 'terser'; import { fileURLToPath, pathToFileURL } from 'url'; +import { hermesTransforms } from './hermes-transforms.js'; lockdown(); @@ -16,7 +17,11 @@ const write = async (target, content) => { await fs.promises.writeFile(location, content); }; -const main = async () => { +/** + * @param {object} [options] + * @param {string} [options.buildType] Suffix used to build special bundles (e.g. 'hermes') + */ +const writeBundle = async ({ buildType } = {}) => { const text = await fs.promises.readFile( fileURLToPath(`${root}/package.json`), 'utf8', @@ -24,32 +29,47 @@ const main = async () => { const packageJson = JSON.parse(text); const version = packageJson.version; + let moduleTransforms; + + let bundleFilePaths = [ + 'dist/ses.cjs', + 'dist/ses.mjs', + 'dist/ses.umd.js', + 'dist/lockdown.cjs', + 'dist/lockdown.mjs', + 'dist/lockdown.umd.js', + ]; + let terseFilePaths = ['dist/ses.umd.min.js', 'dist/lockdown.umd.min.js']; + + if (buildType === 'hermes') { + bundleFilePaths = ['dist/ses-hermes.cjs']; + terseFilePaths = []; + moduleTransforms = hermesTransforms; + } + const bundle = await makeBundle( read, pathToFileURL(resolve('../index.js', import.meta.url)).toString(), + { moduleTransforms }, ); const versionedBundle = `// ses@${version}\n${bundle}`; - const { code: terse } = await minify(versionedBundle, { - mangle: false, - keep_classnames: true, - }); - assert.string(terse); - + console.log(`-- Building '${buildType}' version of SES --`); console.log(`Bundle size: ${versionedBundle.length} bytes`); - console.log(`Minified bundle size: ${terse.length} bytes`); - await fs.promises.mkdir('dist', { recursive: true }); + /** @type {string|undefined} */ + let terse; + if (terseFilePaths.length) { + const { code } = await minify(versionedBundle, { + mangle: false, + keep_classnames: true, + }); + terse = code; + assert.string(terse); + console.log(`Minified bundle size: ${terse.length} bytes`); + } - const bundleFilePaths = [ - 'dist/ses.cjs', - 'dist/ses.mjs', - 'dist/ses.umd.js', - 'dist/lockdown.cjs', - 'dist/lockdown.mjs', - 'dist/lockdown.umd.js', - ]; - const terseFilePaths = ['dist/ses.umd.min.js', 'dist/lockdown.umd.min.js']; + await fs.promises.mkdir('dist', { recursive: true }); await Promise.all([ ...bundleFilePaths.map(dest => write(dest, versionedBundle)), @@ -82,6 +102,11 @@ const main = async () => { console.log(`Copied ${sourceDTS} to ${destDTS}`); }; +const main = async () => { + const buildType = process.argv[2]; + await writeBundle({ buildType }); +}; + main().catch(err => { console.error('Error running main:', err); process.exitCode = 1; From 58dc9a470f31d5ff181b026e0d76ab8028bd0dce Mon Sep 17 00:00:00 2001 From: LeoTM <1881059+leotm@users.noreply.github.com> Date: Tue, 29 Oct 2024 18:45:50 +0000 Subject: [PATCH 06/11] test: Introduce Hermes CI workflow --- .github/workflows/ci.yml | 35 ++++++++++++++++++ packages/ses/package.json | 2 ++ packages/ses/scripts/hermes-test.sh | 55 +++++++++++++++++++++++++++++ packages/ses/test/_hermes-smoke.js | 49 +++++++++++++++++++++++++ yarn.lock | 8 +++++ 5 files changed, 149 insertions(+) create mode 100755 packages/ses/scripts/hermes-test.sh create mode 100644 packages/ses/test/_hermes-smoke.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f72d76da0..ad428c6a6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -213,6 +213,41 @@ jobs: - name: Run yarn test262 run: exit 0 # TODO remove test262 from required tests for CI + test-hermes: + name: test-hermes + + # begin macro + + runs-on: ${{ matrix.platform }} + strategy: + fail-fast: false + matrix: + platform: [ubuntu-latest] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + # without this, setup-node errors on mismatched yarn versions + - run: corepack enable + + - name: Use Node.js 22.x + uses: actions/setup-node@v4 + with: + node-version: 22.x + cache: yarn + + - name: Install dependencies + run: yarn install --immutable + + # end macro + + - name: Run yarn build + run: yarn build + + - name: Run SES/Hermes smoke test + run: yarn lerna run test:hermes + viable-release: name: viable-release diff --git a/packages/ses/package.json b/packages/ses/package.json index ba9a44ca15..99509144f0 100644 --- a/packages/ses/package.json +++ b/packages/ses/package.json @@ -89,6 +89,7 @@ "prepare": "npm run clean && npm run build", "qt": "ava", "test": "tsd && ava", + "test:hermes": "./scripts/hermes-test.sh", "test:xs": "xst dist/ses.umd.js test/_lockdown-safe.js && node scripts/generate-test-xs.js && xst tmp/test-xs.js && rm -rf tmp", "postpack": "git clean -f '*.d.ts*' '*.tsbuildinfo'" }, @@ -108,6 +109,7 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.29.1", + "hermes-engine-cli": "^0.12.0", "prettier": "^3.3.3", "terser": "^5.16.6", "tsd": "^0.31.2", diff --git a/packages/ses/scripts/hermes-test.sh b/packages/ses/scripts/hermes-test.sh new file mode 100755 index 0000000000..2fdfa1688b --- /dev/null +++ b/packages/ses/scripts/hermes-test.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# Creates a JS smoke test to be compiled into Hermes bytecode (hbc). +# +# Usage: scripts/hermes-test.sh + +set -ueo pipefail + +DIR=$(dirname -- "${BASH_SOURCE[0]}") +cd "$DIR/.." + +OS="$(uname -s)" + +case "$OS" in + Linux*) + OS_DIR="linux64-bin" + ;; + Darwin*) + OS_DIR="osx-bin" + ;; + CYGWIN*|MINGW*|MSYS*) + OS_DIR="win64-bin" + ;; + *) + echo "Unsupported OS: $OS" + exit 1 + ;; +esac + +HERMESC="../../node_modules/hermes-engine-cli/$OS_DIR/hermesc" +HERMES="../../node_modules/hermes-engine-cli/$OS_DIR/hermes" + +echo "Concatenating: dist/ses-hermes.cjs + test/_hermes-smoke.js" +cat dist/ses-hermes.cjs test/_hermes-smoke.js > test/_hermes-smoke-dist.js +echo "Generated: test/_hermes-smoke-dist.js" + +# Errors on async arrow functions and async generators +# Both are unsupported on Hermes +echo "Executing: test/_hermes-smoke-dist.js on Hermes compiler" +$HERMESC test/_hermes-smoke-dist.js -emit-binary -out test/_hermes-smoke-dist.hbc +echo "Generated: test/_hermes-smoke-dist.hbc" +echo "Hermes compiler done" + +# TODO: Disabled until /~https://github.com/endojs/endo/issues/1891 complete +# echo "Executing generated bytecode file on Hermes VM" +# $HERMES -b test/_hermes-smoke-dist.hbc +# echo "Hermes VM done" +echo "Skipping: Hermes VM" + +echo "Hermes tests complete" + +echo "Removing: test/_hermes-smoke-dist.js" +rm test/_hermes-smoke-dist.js +echo "Removing: test/_hermes-smoke-dist.hbc" +rm test/_hermes-smoke-dist.hbc diff --git a/packages/ses/test/_hermes-smoke.js b/packages/ses/test/_hermes-smoke.js new file mode 100644 index 0000000000..0663956080 --- /dev/null +++ b/packages/ses/test/_hermes-smoke.js @@ -0,0 +1,49 @@ +// Hermes doesn't support native I/O, +// so we concat the SES shim above, +// when running this test on Hermes. + +/** + * Test calling SES lockdown. + */ +const testLockdown = () => { + lockdown(); +}; + +/** + * TODO: Test creating a new Compartment. + */ +// eslint-disable-next-line no-unused-vars +const testCompartment = () => { + // eslint-disable-next-line no-unused-vars + const c = new Compartment(); +}; + +/** + * TODO: Test Compartment import hook and resolve hook. + */ +// eslint-disable-next-line no-unused-vars +async function testCompartmentHooks() { + const resolveHook = a => a; + + async function importHook() { + return { + imports: [], + exports: ['meaning'], + execute(exports) { + exports.meaning = 42; + }, + }; + } + + const compartment = new Compartment({}, {}, { resolveHook, importHook }); + + const module = compartment.module('.'); + + const { + namespace: { _meaning }, + } = await compartment.import('.'); + + assert(module); +} + +testLockdown(); diff --git a/yarn.lock b/yarn.lock index a838a69480..e7ced69cd5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5539,6 +5539,13 @@ __metadata: languageName: node linkType: hard +"hermes-engine-cli@npm:^0.12.0": + version: 0.12.0 + resolution: "hermes-engine-cli@npm:0.12.0" + checksum: 10c0/53a00336632cc7a743e9a88a5199865cf922d118f42f15bed4d2ed2fee635acd0d4d8563803b47e7c1bc2d17650281eb9188be8cfdaa25887243cfee840523be + languageName: node + linkType: hard + "hosted-git-info@npm:^2.1.4": version: 2.8.9 resolution: "hosted-git-info@npm:2.8.9" @@ -8959,6 +8966,7 @@ __metadata: eslint-config-prettier: "npm:^9.1.0" eslint-plugin-eslint-comments: "npm:^3.2.0" eslint-plugin-import: "npm:^2.29.1" + hermes-engine-cli: "npm:^0.12.0" prettier: "npm:^3.3.3" terser: "npm:^5.16.6" tsd: "npm:^0.31.2" From e3cbe039c18e9e156068171f94af8d601f2da033 Mon Sep 17 00:00:00 2001 From: LeoTM <1881059+leotm@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:58:40 +0000 Subject: [PATCH 07/11] refactor(ses): compartmentImport async arrow fn `this` to a var for Hermes transform --- packages/ses/src/compartment.js | 36 +++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/ses/src/compartment.js b/packages/ses/src/compartment.js index c72bc6dde9..5c5dfa05ff 100644 --- a/packages/ses/src/compartment.js +++ b/packages/ses/src/compartment.js @@ -47,6 +47,7 @@ import { assign, defineProperties, identity, + functionBind, promiseThen, toStringTagSymbol, weakmapGet, @@ -359,24 +360,29 @@ export const makeCompartmentConstructor = ( * The method `compartment.import` accepts a full specifier, but dynamic * import accepts an import specifier and resolves it to a full specifier * relative to the calling module's full specifier. + * @param compartment - The `compartment` reference, to avoid relying on upper-scope `this` * @returns {Promise} */ - const compartmentImport = async fullSpecifier => { - if (typeof resolveHook !== 'function') { - throw TypeError( - `Compartment does not support dynamic import: no configured resolveHook for compartment ${q(name)}`, + const compartmentImport = functionBind( + async (compartment, fullSpecifier) => { + if (typeof resolveHook !== 'function') { + throw TypeError( + `Compartment does not support dynamic import: no configured resolveHook for compartment ${q(name)}`, + ); + } + await load(privateFields, moduleAliases, compartment, fullSpecifier); + const { execute, exportsProxy } = link( + privateFields, + moduleAliases, + compartment, + fullSpecifier, ); - } - await load(privateFields, moduleAliases, this, fullSpecifier); - const { execute, exportsProxy } = link( - privateFields, - moduleAliases, - this, - fullSpecifier, - ); - execute(); - return exportsProxy; - }; + execute(); + return exportsProxy; + }, + null, + this, + ); weakmapSet(privateFields, this, { name: `${name}`, From 3137c9aa5f6b253a99f324a51a4890ad31394956 Mon Sep 17 00:00:00 2001 From: LeoTM <1881059+leotm@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:13:11 +0000 Subject: [PATCH 08/11] feat(ses): support async generators in Hermes transform for CSP --- packages/ses/scripts/hermes-transforms.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/ses/scripts/hermes-transforms.js b/packages/ses/scripts/hermes-transforms.js index b7b4486d53..1c0d7a299a 100644 --- a/packages/ses/scripts/hermes-transforms.js +++ b/packages/ses/scripts/hermes-transforms.js @@ -50,6 +50,17 @@ const asyncArrowEliminator = { }, }; +const destroyAsyncGenerators = path => { + if (path.node.async && path.node.generator) { + path.replaceWith(t.identifier('undefined')); + } +}; + +const asyncGeneratorDestroyer = { + FunctionExpression: destroyAsyncGenerators, + FunctionDeclaration: destroyAsyncGenerators, +}; + export const hermesTransforms = { mjs: async ( sourceBytes, @@ -60,6 +71,7 @@ export const hermesTransforms = { ) => { const transforms = { ...asyncArrowEliminator, + ...asyncGeneratorDestroyer, // Some transforms might be added based on the specifier later }; From f35f394efb42f524225e4988c279863203eb025e Mon Sep 17 00:00:00 2001 From: LeoTM <1881059+leotm@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:14:22 +0000 Subject: [PATCH 09/11] feat(ses): support CSP in commons AsyncGeneratorFunctionInstance --- packages/ses/src/commons.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/ses/src/commons.js b/packages/ses/src/commons.js index 3520b6a637..aa1b55cb52 100644 --- a/packages/ses/src/commons.js +++ b/packages/ses/src/commons.js @@ -418,6 +418,9 @@ const getAsyncGeneratorFunctionInstance = () => { // However React Native provides a `console` implementation when setting up error handling: // /~https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/InitializeCore.js return undefined; + } else if (error.name === 'EvalError') { + // eslint-disable-next-line no-empty-function + return async function* AsyncGeneratorFunctionInstance() {}; } else { throw error; } From 43aece975a750759dc05a5ed1a29a665af91d67f Mon Sep 17 00:00:00 2001 From: LeoTM <1881059+leotm@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:25:16 +0000 Subject: [PATCH 10/11] fix(compartment-mapper): sync module transforms in bundle.js Co-authored-by: naugtur --- packages/compartment-mapper/src/bundle.js | 2 ++ packages/ses/scripts/bundle.js | 6 +++--- packages/ses/scripts/hermes-transforms.js | 8 +------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/compartment-mapper/src/bundle.js b/packages/compartment-mapper/src/bundle.js index bea4f6a657..05b1384186 100644 --- a/packages/compartment-mapper/src/bundle.js +++ b/packages/compartment-mapper/src/bundle.js @@ -233,6 +233,7 @@ export const makeBundle = async (readPowers, moduleLocation, options) => { const { moduleTransforms, + syncModuleTransforms, dev, tags: tagsOption, conditions: conditionsOption = tagsOption, @@ -333,6 +334,7 @@ export const makeBundle = async (readPowers, moduleLocation, options) => { resolve, makeImportHook, moduleTransforms, + syncModuleTransforms, parserForLanguage, }); await compartment.load(entryModuleSpecifier); diff --git a/packages/ses/scripts/bundle.js b/packages/ses/scripts/bundle.js index 05b9212848..73fa964c61 100644 --- a/packages/ses/scripts/bundle.js +++ b/packages/ses/scripts/bundle.js @@ -29,7 +29,7 @@ const writeBundle = async ({ buildType } = {}) => { const packageJson = JSON.parse(text); const version = packageJson.version; - let moduleTransforms; + let syncModuleTransforms; let bundleFilePaths = [ 'dist/ses.cjs', @@ -44,13 +44,13 @@ const writeBundle = async ({ buildType } = {}) => { if (buildType === 'hermes') { bundleFilePaths = ['dist/ses-hermes.cjs']; terseFilePaths = []; - moduleTransforms = hermesTransforms; + syncModuleTransforms = hermesTransforms; } const bundle = await makeBundle( read, pathToFileURL(resolve('../index.js', import.meta.url)).toString(), - { moduleTransforms }, + { syncModuleTransforms }, ); const versionedBundle = `// ses@${version}\n${bundle}`; diff --git a/packages/ses/scripts/hermes-transforms.js b/packages/ses/scripts/hermes-transforms.js index 1c0d7a299a..8142fcbf53 100644 --- a/packages/ses/scripts/hermes-transforms.js +++ b/packages/ses/scripts/hermes-transforms.js @@ -62,13 +62,7 @@ const asyncGeneratorDestroyer = { }; export const hermesTransforms = { - mjs: async ( - sourceBytes, - specifier, - location, - _packageLocation, - { sourceMap }, - ) => { + mjs: (sourceBytes, specifier, location, _packageLocation, { sourceMap }) => { const transforms = { ...asyncArrowEliminator, ...asyncGeneratorDestroyer, From cad46eb9532ed5989cc7dbcc3a6913c658e25e4d Mon Sep 17 00:00:00 2001 From: LeoTM <1881059+leotm@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:21:56 +0000 Subject: [PATCH 11/11] chore(ses): prevent lerna from swallowing Hermes VM logs --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad428c6a6a..3884a8012c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -246,7 +246,7 @@ jobs: run: yarn build - name: Run SES/Hermes smoke test - run: yarn lerna run test:hermes + run: yarn test:hermes viable-release: name: viable-release