Skip to content

Commit

Permalink
feat(evasive-transform): expose evadeCensorSync (#2332)
Browse files Browse the repository at this point in the history
## Description

This change replaces [source-map](https://npm.im/source-map) with
[source-map-js](https://npm.im/source-map-js), which is a fork of the
former. Crucially, `source-map-js` is a synchronous, pure-JS
implementation.

A consequence of this is that `makeLocationUnmapper` is now synchronous.
It is internal, however, and thus does not break a public API.

In the `makeLocationUnmapper` implementation, an assertion for the
truthiness of `ast.loc` has been moved _before_ instantiation of
`SourceMapConsumer`, where it maybe should have been in the first place.

### Security Considerations

The dependency switch incurs risk.

### Scaling Considerations

None.

### Documentation Considerations

New public API should be documented

### Testing Considerations

`evadeCensor` now just wraps `evadeCensorSync` in a `Promise`. Tests
have been changed to use `evadeCensorSync` directly.

### Compatibility Considerations

Allows use as a module transform for dynamically-required modules, which
must be loaded synchronously.

### Upgrade Considerations

None.
  • Loading branch information
kriskowal authored Jul 2, 2024
2 parents 6712b52 + 2f141cd commit 201ceeb
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 70 deletions.
5 changes: 4 additions & 1 deletion packages/evasive-transform/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@
"@agoric/babel-generator": "^7.17.6",
"@babel/parser": "^7.23.6",
"@babel/traverse": "^7.23.6",
"source-map": "0.7.4"
"source-map-js": "^1.2.0"
},
"resolutions": {
"@babel/types": "7.23.0"
}
}
36 changes: 28 additions & 8 deletions packages/evasive-transform/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
* @module
*/

/**
* @import {TransformedResult} from './generate.js'
*/

import { transformAst } from './transform-ast.js';
import { parseAst } from './parse-ast.js';
import { generate } from './generate.js';

/**
* Options for {@link evadeCensor}
* Options for {@link evadeCensorSync}
*
* @typedef EvadeCensorOptions
* @property {string|import('source-map').RawSourceMap} [sourceMap] - Original source map in JSON string or object form
Expand All @@ -31,17 +35,14 @@ import { generate } from './generate.js';
* @template {EvadeCensorOptions} T
* @param {string} source - Source code to transform
* @param {T} [options] - Options for the transform
* @returns {Promise<import('./generate.js').TransformedResult<T['sourceUrl']>>} Object containing new code and optionally source map object (ready for stringification)
* @returns {TransformedResult<T['sourceUrl']>} Object containing new code and optionally source map object (ready for stringification)
* @public
*/
export async function evadeCensor(source, options) {
export function evadeCensorSync(source, options) {
// TODO Use options ?? {} when resolved:
// https://github.com/Agoric/agoric-sdk/issues/8671
const { sourceMap, sourceUrl, useLocationUnmap, sourceType } = options || {};

// See "Chesterton's Fence"
await null;

// Parse the rolled-up chunk with Babel.
// We are prepared for different module systems.
const ast = parseAst(source, {
Expand All @@ -52,13 +53,32 @@ export async function evadeCensor(source, options) {
typeof sourceMap === 'string' ? sourceMap : JSON.stringify(sourceMap);

if (sourceMap && useLocationUnmap) {
await transformAst(ast, { sourceMap: sourceMapJson, useLocationUnmap });
transformAst(ast, { sourceMap: sourceMapJson, useLocationUnmap });
} else {
await transformAst(ast);
transformAst(ast);
}

if (sourceUrl) {
return generate(ast, { sourceUrl });
}
return generate(ast);
}

/**
* Apply SES censorship evasion transforms on the given code `source`
*
* If the `sourceUrl` option is provided, the `map` property of the fulfillment
* value will be a source map object; otherwise it will be `undefined`.
*
* If the `sourceMap` option is _not_ provided, the `useLocationUnmap` option
* will have no effect.
*
* @template {EvadeCensorOptions} T
* @param {string} source - Source code to transform
* @param {T} [options] - Options for the transform
* @returns {Promise<TransformedResult<T['sourceUrl']>>} Object containing new code and optionally source map object (ready for stringification)
* @public
*/
export async function evadeCensor(source, options) {
return evadeCensorSync(source, options);
}
73 changes: 36 additions & 37 deletions packages/evasive-transform/src/location-unmapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* @module
*/

import { SourceMapConsumer } from 'source-map';
import { SourceMapConsumer } from 'source-map-js';

/**
* A function which modifies an AST Node's source location
Expand All @@ -21,55 +21,54 @@ import { SourceMapConsumer } from 'source-map';
* @internal
* @param {string} sourceMap - Source map
* @param {import('@babel/types').File} ast - AST as created by Babel
* @returns {Promise<LocationUnmapper>}
* @returns {LocationUnmapper}
*/
export async function makeLocationUnmapper(sourceMap, ast) {
export function makeLocationUnmapper(sourceMap, ast) {
if (!sourceMap) {
throw new TypeError('Invalid arguments; expected sourceMap');
}
if (!ast || typeof ast !== 'object') {
throw new TypeError('Invalid arguments; expected AST ast');
}
if (!ast.loc) {
throw new TypeError('No SourceLocation found in AST');
}
try {
// We rearrange the rolled-up chunk according to its sourcemap to move
// its source lines back to the right place.
return await SourceMapConsumer.with(sourceMap, null, async consumer => {
if (!ast.loc) {
throw new TypeError('No SourceLocation found in AST');
const consumer = new SourceMapConsumer(JSON.parse(sourceMap));
const unmapped = new WeakSet();
/**
* Change this type to `import('@babel/types').Position` if we assign the
* `index` prop below
* @type {any}
*/
let lastPos = {
...ast.loc.start,
};
return loc => {
if (!loc || unmapped.has(loc)) {
return;
}
const unmapped = new WeakSet();
/**
* Change this type to `import('@babel/types').Position` if we assign the
* `index` prop below
* @type {any}
*/
let lastPos = {
...ast.loc.start,
};
return loc => {
if (!loc || unmapped.has(loc)) {
return;
}
// Make sure things start at least at the right place.
loc.end = { ...loc.start };
for (const pos of /** @type {const} */ (['start', 'end'])) {
if (loc[pos]) {
const newPos = consumer.originalPositionFor(loc[pos]);
if (newPos.source !== null) {
// This assumes that if source is non-null, then line and column are
// also non-null
lastPos = {
line: /** @type {number} */ (newPos.line),
column: /** @type {number} */ (newPos.column),
// XXX: what of the `index` prop?
};
}
loc[pos] = lastPos;
// Make sure things start at least at the right place.
loc.end = { ...loc.start };
for (const pos of /** @type {const} */ (['start', 'end'])) {
if (loc[pos]) {
const newPos = consumer.originalPositionFor(loc[pos]);
if (newPos.source !== null) {
// This assumes that if source is non-null, then line and column are
// also non-null
lastPos = {
line: /** @type {number} */ (newPos.line),
column: /** @type {number} */ (newPos.column),
// XXX: what of the `index` prop?
};
}
loc[pos] = lastPos;
}
unmapped.add(loc);
};
});
}
unmapped.add(loc);
};
} catch (err) {
// A source map string should be valid JSON, and if `JSON.parse()` fails, a
// SyntaxError is thrown
Expand Down
6 changes: 3 additions & 3 deletions packages/evasive-transform/src/transform-ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ const traverse = /** @type {typeof import('@babel/traverse')['default']} */ (
* @internal
* @param {import('@babel/types').File} ast - AST, as generated by Babel
* @param {TransformAstOptions} [opts]
* @returns {Promise<void>}
* @returns {void}
*/
export async function transformAst(ast, { sourceMap, useLocationUnmap } = {}) {
export function transformAst(ast, { sourceMap, useLocationUnmap } = {}) {
/** @type {import('./location-unmapper.js').LocationUnmapper|undefined} */
let unmapLoc;
if (sourceMap && useLocationUnmap) {
unmapLoc = await makeLocationUnmapper(sourceMap, ast);
unmapLoc = makeLocationUnmapper(sourceMap, ast);
}
traverse(ast, {
enter(p) {
Expand Down
16 changes: 8 additions & 8 deletions packages/evasive-transform/test/evade-censor.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { evadeCensor } from '../src/index.js';
import { evadeCensorSync } from '../src/index.js';
import { test } from './prepare-test-env-ava-fixture.js';

/**
Expand All @@ -15,20 +15,20 @@ function stripLinefeeds(str) {

test('evadeCensor() - missing "source" arg', async t => {
// @ts-expect-error - intentional missing args
await t.throwsAsync(evadeCensor());
t.throws(evadeCensorSync);
});

test('evadeCensor() - successful source transform', async t => {
const { source } = t.context;
const { code, map } = await evadeCensor(source);
const { code, map } = evadeCensorSync(source);

t.snapshot(stripLinefeeds(code));
t.is(map, undefined);
});

test('evadeCensor() - successful source transform w/ source map', async t => {
const { source, sourceMap } = t.context;
const { code, map } = await evadeCensor(source, {
const { code, map } = evadeCensorSync(source, {
sourceMap,
});

Expand All @@ -38,7 +38,7 @@ test('evadeCensor() - successful source transform w/ source map', async t => {

test('evadeCensor() - successful source transform w/ source map & source URL', async t => {
const { sourceMap, sourceUrl, source } = t.context;
const { code, map } = await evadeCensor(source, {
const { code, map } = evadeCensorSync(source, {
sourceMap,
sourceUrl,
});
Expand All @@ -49,7 +49,7 @@ test('evadeCensor() - successful source transform w/ source map & source URL', a

test('evadeCensor() - successful source transform w/ source URL', async t => {
const { sourceUrl, source } = t.context;
const { code, map } = await evadeCensor(source, {
const { code, map } = evadeCensorSync(source, {
sourceUrl,
});

Expand All @@ -59,7 +59,7 @@ test('evadeCensor() - successful source transform w/ source URL', async t => {

test('evadeCensor() - successful source transform w/ source map & unmapping', async t => {
const { sourceMap, source } = t.context;
const { code, map } = await evadeCensor(source, {
const { code, map } = evadeCensorSync(source, {
sourceMap,
useLocationUnmap: true,
});
Expand All @@ -70,7 +70,7 @@ test('evadeCensor() - successful source transform w/ source map & unmapping', as

test('evadeCensor() - successful source transform w/ source map, source URL & unmapping', async t => {
const { sourceMap, sourceUrl, source } = t.context;
const { code, map } = await evadeCensor(source, {
const { code, map } = evadeCensorSync(source, {
sourceMap,
sourceUrl,
useLocationUnmap: true,
Expand Down
10 changes: 5 additions & 5 deletions packages/evasive-transform/test/location-unmapper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const { parse: parseBabel } = babelParser;

test('makeLocationUnmapper() - missing source map', async t => {
// @ts-expect-error - wrong number of args
await t.throwsAsync(() => makeLocationUnmapper(), {
t.throws(makeLocationUnmapper, {
message: 'Invalid arguments; expected sourceMap',
});
});
Expand All @@ -16,7 +16,7 @@ test('makeLocationUnmapper() - invalid source map', async t => {
const sourceMap = '26 sons and she named them all dave';
const ast = parseBabel(source, { sourceType: 'module' });

await t.throwsAsync(() => makeLocationUnmapper(sourceMap, ast), {
t.throws(() => makeLocationUnmapper(sourceMap, ast), {
message: /^Invalid source map:/,
});
});
Expand All @@ -25,7 +25,7 @@ test('makeLocationUnmapper() - missing AST', async t => {
const { sourceMap } = t.context;

// @ts-expect-error - wrong number of args
await t.throwsAsync(() => makeLocationUnmapper(sourceMap), {
t.throws(() => makeLocationUnmapper(sourceMap), {
message: 'Invalid arguments; expected AST ast',
});
});
Expand All @@ -37,15 +37,15 @@ test('makeLocationUnmapper() - invalid AST', async t => {
};

// @ts-expect-error - the AST is invalid, as you may have guessed
await t.throwsAsync(() => makeLocationUnmapper(sourceMap, ast), {
t.throws(() => makeLocationUnmapper(sourceMap, ast), {
message: 'No SourceLocation found in AST',
});
});

test('makeLocationUnmapper() - success', async t => {
const { source, sourceMap } = t.context;
const ast = parseBabel(source, { sourceType: 'module' });
const unmap = await makeLocationUnmapper(sourceMap, ast);
const unmap = makeLocationUnmapper(sourceMap, ast);

t.true(typeof unmap === 'function');
});
16 changes: 8 additions & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ __metadata:
c8: "npm:^7.14.0"
eslint: "npm:^8.57.0"
rollup: "npm:^2.79.1"
source-map: "npm:0.7.4"
source-map-js: "npm:^1.2.0"
tsd: "npm:^0.30.7"
typescript: "npm:5.5.2"
languageName: unknown
Expand Down Expand Up @@ -10698,6 +10698,13 @@ __metadata:
languageName: node
linkType: hard

"source-map-js@npm:^1.2.0":
version: 1.2.0
resolution: "source-map-js@npm:1.2.0"
checksum: 10c0/7e5f896ac10a3a50fe2898e5009c58ff0dc102dcb056ed27a354623a0ece8954d4b2649e1a1b2b52ef2e161d26f8859c7710350930751640e71e374fe2d321a4
languageName: node
linkType: hard

"source-map-resolve@npm:^0.5.0":
version: 0.5.3
resolution: "source-map-resolve@npm:0.5.3"
Expand Down Expand Up @@ -10728,13 +10735,6 @@ __metadata:
languageName: node
linkType: hard

"source-map@npm:0.7.4":
version: 0.7.4
resolution: "source-map@npm:0.7.4"
checksum: 10c0/dc0cf3768fe23c345ea8760487f8c97ef6fca8a73c83cd7c9bf2fde8bc2c34adb9c0824d6feb14bc4f9e37fb522e18af621543f1289038a66ac7586da29aa7dc
languageName: node
linkType: hard

"source-map@npm:^0.5.0, source-map@npm:^0.5.6":
version: 0.5.7
resolution: "source-map@npm:0.5.7"
Expand Down

0 comments on commit 201ceeb

Please sign in to comment.