Skip to content

Commit fc569ce

Browse files
authored
refactor: insert await null;s needed for await safety (#2347)
Closes: #XXXX Refs: #XXXX ## Description Prior to this PR, `yarn lint` warns about many violations of our `await` safety rules. In vscode, I was very pleasantly surprised to learn about the automated fixes offered interactively for these: ![image](https://github.com/user-attachments/assets/6be443de-dcd8-4e1a-b714-6a1f1106bd61) For each of these, I accepted the "insert `await null;` before..." option. Uniquely for the one shown in the above screenshot I needed to do an additional manual readjustment to move the `// eslint-disable-next-line ...` so it would still be about the next line. Caveats: - I applied the automated fix manually, without reading the code in question or in any other way validating that this change is correctness preserving. My only evidence at the moment is that all the tests still pass. - I would have loved to mark this PR with `refactor:` or at least `fix:`. But I marked it with `fix!` because there's no reason yet to think these changes are either correctness preserving or compat. - I applied the automated change blindly even to the test files, even though we're not normally concerned about `await` safety there. Indeed, we should also change our lint configuration so `await` safety is unenforced for those. After this PR, we should also change our `await` safety enforcement for non-test files from "warning" to "error", to keep these from creeping back in. - I applied the automated fix to the test files anyway since test code still often serves as precedent-by-example for production code. Since the automated fix made it easy to do this, I decided it was better to improve our test-code-as-precedent. Obviously, this should not be merged until we're confident that these changes are correctness preserving. I don't know of any way to do so other than careful manual review. Perhaps as part of the same review, we might also conclude that it is compat, in which case we can change back from `fix!` to `fix:` or even hopefully `refactor:`. For the various "considerations" below, I'll answer under the assumption that we have already reviewed these changes for correctness, and deemed them all to be correctness preserving. Obviously, until that happens, this PR is fatal re all the considerations below. Update: As suggested by review comments, I moved those inserted `await null;`s to the top of their respective functions. Update: Between @kriskowal 's evaluation comments below and my own inspection, I'm reasonably confident that this PR should count as a refactor, so I'm changing the title from `fix!...` to `refactor:...`. ### Security Considerations Assuming the changes are correctness preserving, `await` safety aids security. ### Scaling Considerations none ### Documentation Considerations we already need to explain `await` safety. This PR doesn't change that, but repairs our code so that we practice what we will preach. ### Testing Considerations A bit distressing that all tests pass without any further intervention. This perhaps indicates a failure to test for the semantics that are changed by these extra awaits. ### Compatibility Considerations If this change is correctness preserving, then it is likely also compat. But not necessarily. The extra awaits introduce extra turn delays and extra interleaving points. ### Upgrade Considerations If this PR is correctness preserving and compat, then there are unlikely to be any upgrade considerations. But I don't know that for sure, so we should also worry about this during review.
1 parent 1a140d5 commit fc569ce

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+90
-4
lines changed

packages/base64/test/bench-main.js

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ async function main() {
1616
const log = (typeof console !== 'undefined' && console.log) || print;
1717
/** @type {typeof Date.now} */
1818
const now = await (async () => {
19+
await null;
1920
try {
2021
const { performance } = await import('perf_hooks');
2122
if (performance.now) {

packages/captp/test/export-hook.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Far } from '@endo/marshal';
66
import { E, makeLoopback } from '../src/loopback.js';
77

88
test('exportHook', async t => {
9+
await null;
910
const exports = [];
1011

1112
const { makeFar } = makeLoopback('us', {

packages/captp/test/gc.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { detectEngineGC } from './engine-gc.js';
77
import { makeGcAndFinalize } from './gc-and-finalize.js';
88

99
const isolated = async (t, makeFar) => {
10+
await null;
1011
const local = Far('local', {
1112
method: () => 'local',
1213
});

packages/captp/test/trap.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
const dirname = url.fileURLToPath(new URL('./', import.meta.url));
1717

1818
const makeWorkerTests = isHost => async t => {
19+
await null;
1920
const initFn = isHost ? makeHost : makeGuest;
2021
for (let len = 0; len < MIN_TRANSFER_BUFFER_LENGTH; len += 1) {
2122
t.throws(() => initFn(() => {}, new SharedArrayBuffer(len)), {

packages/captp/test/traplib.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const createHostBootstrap = makeTrapHandler => {
2424
};
2525

2626
export const runTrapTests = async (t, Trap, bs, unwrapsPromises) => {
27+
await null;
2728
// Demonstrate async compatibility of traps.
2829
const pn = E(E(bs).getTraps(3)).getN();
2930
t.is(Promise.resolve(pn), pn);

packages/check-bundle/lite.js

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const checkBundle = async (
2222
computeSha512,
2323
bundleName = '<unknown-bundle>',
2424
) => {
25+
await null;
2526
assert.typeof(
2627
bundle,
2728
'object',

packages/cli/src/client.js

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const provideEndoClient = async (
1313
cancelled,
1414
bootstrap,
1515
) => {
16+
await null;
1617
try {
1718
// It is okay to fail to connect because the daemon is not running.
1819
return await makeEndoClient(name, sockPath, cancelled, bootstrap);

packages/cli/src/commands/accept.js

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const fromAsync = async iterable => {
1212
};
1313

1414
export const accept = async ({ guestName, agentNames }) => {
15+
await null;
1516
process.stdin.setEncoding('utf-8');
1617
const invitationLocator = (await fromAsync(process.stdin)).join('').trim();
1718
return withEndoAgent(agentNames, { os, process }, async ({ agent }) => {

packages/cli/src/commands/list.js

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const pad = (fieldVal, width, minPad = 2) => {
3636

3737
export const list = async ({ directory, follow, json, verbose }) =>
3838
withEndoHost({ os, process }, async ({ host: agent }) => {
39+
await null;
3940
if (directory !== undefined) {
4041
const directoryPath = parsePetNamePath(directory);
4142
agent = E(agent).lookup(...directoryPath);

packages/cli/src/commands/log.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const delay = async (ms, cancelled) => {
2424

2525
export const log = async ({ follow, ping }) =>
2626
withInterrupt(async ({ cancelled }) => {
27+
await null;
2728
const logCheckIntervalMs = ping !== undefined ? Number(ping) : 5_000;
2829

2930
const { username, homedir } = os.userInfo();

packages/cli/src/commands/make.js

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const makeCommand = async ({
2222
agentNames,
2323
powersName,
2424
}) => {
25+
await null;
2526
if (filePath !== undefined && importPath !== undefined) {
2627
console.error('Specify only one of [file] or --UNCONFINED <file>');
2728
process.exitCode = 1;
@@ -61,6 +62,7 @@ export const makeCommand = async ({
6162
}
6263

6364
await withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
65+
await null;
6466
// Prepare a bundle, with the given name.
6567
if (bundleReaderRef !== undefined) {
6668
await E(agent).storeBlob(bundleReaderRef, bundleName);

packages/cli/src/commands/run.js

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export const run = async ({
4444
agentNames,
4545
{ os, process },
4646
async ({ bootstrap, agent }) => {
47+
await null;
4748
let powersP;
4849
if (powersName === 'NONE') {
4950
powersP = E(bootstrap).leastAuthority();

packages/cli/src/commands/store.js

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export const store = async ({
7676
const parsedName = parsePetNamePath(name);
7777

7878
await withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
79+
await null;
7980
if (storeText !== undefined) {
8081
await E(agent).storeValue(storeText, parsedName);
8182
} else if (storeJson !== undefined) {

packages/cli/src/context.js

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { provideEndoClient } from './client.js';
77
import { parsePetNamePath } from './pet-name.js';
88

99
export const withInterrupt = async callback => {
10+
await null;
1011
const { promise: cancelled, reject: cancel } = makePromiseKit();
1112
cancelled.catch(() => {});
1213
process.once('SIGINT', () => cancel(Error('SIGINT')));

packages/common/test/apply-labeling-error.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Fail } from '@endo/errors';
44
import { applyLabelingError } from '../apply-labeling-error.js';
55

66
test('test applyLabelingError', async t => {
7+
await null;
78
t.is(
89
applyLabelingError(x => x * 2, [8]),
910
16,

packages/compartment-mapper/src/archive-lite.js

+1
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ const renameSources = (sources, compartmentRenames) => {
226226
* @param {Sources} sources
227227
*/
228228
const addSourcesToArchive = async (archive, sources) => {
229+
await null;
229230
for (const compartment of keys(sources).sort()) {
230231
const modules = sources[compartment];
231232
const compartmentLocation = resolveLocation(`${compartment}/`, 'file:///');

packages/compartment-mapper/src/import-archive-lite.js

+2
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ const makeArchiveImportHookMaker = (
105105
const { modules } = compartmentDescriptor;
106106
/** @type {ImportHook} */
107107
const importHook = async moduleSpecifier => {
108+
await null;
108109
// per-module:
109110
const module = modules[moduleSpecifier];
110111
if (module === undefined) {
@@ -280,6 +281,7 @@ export const parseArchive = async (
280281
archiveLocation = '<unknown>',
281282
options = {},
282283
) => {
284+
await null;
283285
const {
284286
computeSha512 = undefined,
285287
expectedSha512 = undefined,

packages/compartment-mapper/src/import-hook.js

+1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ export const makeImportHookMaker = (
209209

210210
/** @type {ImportHook} */
211211
const importHook = async moduleSpecifier => {
212+
await null;
212213
compartmentDescriptor.retained = true;
213214

214215
// per-module:

packages/compartment-mapper/src/link.js

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ const makeExtensionParser = (
8484
moduleTransforms,
8585
) => {
8686
return async (bytes, specifier, location, packageLocation, options) => {
87+
await null;
8788
let language;
8889
const extension = parseExtension(location);
8990

packages/compartment-mapper/src/node-modules.js

+1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ const readDescriptorWithMemo = async (memo, maybeRead, packageLocation) => {
152152
* } | undefined>}
153153
*/
154154
const findPackage = async (readDescriptor, canonical, directory, name) => {
155+
await null;
155156
for (;;) {
156157
// eslint-disable-next-line no-await-in-loop
157158
const packageLocation = await canonical(

packages/compartment-mapper/src/node-powers.js

+2
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ const makeReadPowersSloppy = ({ fs, url = undefined, crypto = undefined }) => {
104104
* @param {string} location
105105
*/
106106
const canonical = async location => {
107+
await null;
107108
try {
108109
if (location.endsWith('/')) {
109110
const realPath = await fs.promises.realpath(
@@ -157,6 +158,7 @@ const makeWritePowersSloppy = ({ fs, url = undefined }) => {
157158
* @param {Uint8Array} data
158159
*/
159160
const write = async (location, data) => {
161+
await null;
160162
try {
161163
return await fs.promises.writeFile(fileURLToPath(location), data);
162164
} catch (error) {

packages/compartment-mapper/src/search.js

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const resolveLocation = (rel, abs) => new URL(rel, abs).toString();
3333
* @returns {Promise<{data:T, directory: string, location:string, packageDescriptorLocation: string}>}
3434
*/
3535
export const searchDescriptor = async (location, readDescriptor) => {
36+
await null;
3637
let directory = resolveLocation('./', location);
3738
for (;;) {
3839
const packageDescriptorLocation = resolveLocation(

packages/compartment-mapper/test/policy.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { moduleify, scaffold, sanitizePaths } from './scaffold.js';
55

66
function combineAssertions(...assertionFunctions) {
77
return async (...args) => {
8+
await null;
89
for (const assertion of assertionFunctions) {
910
// eslint-disable-next-line no-await-in-loop
1011
await assertion(...args);

packages/compartment-mapper/test/scaffold.js

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const builtinLocation = new URL(
6262
// application package.
6363

6464
export async function setup() {
65+
await null;
6566
if (modules === undefined) {
6667
const utility = await loadLocation(readPowers, builtinLocation);
6768
const { namespace } = await utility.import({ globals });
@@ -98,6 +99,7 @@ export function scaffold(
9899
testFunc = testFunc.failing || testFunc;
99100
}
100101
return testFunc(title, async t => {
102+
await null;
101103
const compartmentInstrumentation = compartmentInstrumentationFactory();
102104
let namespace;
103105
try {

packages/compartment-mapper/test/snapshots/error-handling.test.js.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,23 @@ Generated by [AVA](https://avajs.dev).
8484
8585
`Error: Cannot find external module "missing" in package file://.../compartment-mapper/test/fixtures-error-handling/node_modules/cjs/␊
8686
at importHook (file://.../compartment-mapper/src/import-hook.js:…)␊
87-
at async asyncTrampoline (file://.../ses/src/module-load.js:…)`
87+
at async asyncTrampoline (file://.../ses/src/module-load.js:…)␊
88+
at async drainQueue (file://.../ses/src/module-load.js:…)␊
89+
at async load (file://.../ses/src/module-load.js:…)␊
90+
at async file://.../compartment-mapper/test/scaffold.js:…␊
91+
at async file://.../compartment-mapper/test/scaffold.js:…`
8892

8993
## fixtures-error-handling / cjs / importLocation
9094

9195
> Snapshot 1
9296
9397
`Error: Cannot find external module "missing" in package file://.../compartment-mapper/test/fixtures-error-handling/node_modules/cjs/␊
9498
at importHook (file://.../compartment-mapper/src/import-hook.js:…)␊
95-
at async asyncTrampoline (file://.../ses/src/module-load.js:…)`
99+
at async asyncTrampoline (file://.../ses/src/module-load.js:…)␊
100+
at async drainQueue (file://.../ses/src/module-load.js:…)␊
101+
at async load (file://.../ses/src/module-load.js:…)␊
102+
at async file://.../compartment-mapper/test/scaffold.js:…␊
103+
at async file://.../compartment-mapper/test/scaffold.js:…`
96104

97105
## fixtures-error-handling / cjs / makeArchive / parseArchive
98106

Binary file not shown.

packages/compartment-mapper/test/stack.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ test('archive stack trace source', async t => {
4141
// Whereas, we expect the same program executed directly from local files to
4242
// have a fully qualified file URL in the stack trace.
4343
test('disk stack trace source', async t => {
44+
await null;
4445
let error;
4546
try {
4647
await importLocation(readPowers, fixtureLocation);

packages/daemon/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ const enoentOk = error => {
144144
};
145145

146146
export const clean = async (config = defaultConfig) => {
147+
await null;
147148
if (process.platform !== 'win32') {
148149
await removePath(config.sockPath).catch(enoentOk);
149150
}

packages/daemon/src/daemon-node-powers.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,8 @@ export const makeDaemonicPersistencePowers = (
367367
return filePowers.readFileText(storagePath);
368368
};
369369
const json = async () => {
370-
return JSON.parse(await text());
370+
const jsonSrc = await text();
371+
return JSON.parse(jsonSrc);
371372
};
372373
return harden({
373374
sha512: () => sha512,

packages/daemon/src/serve-private-port-http.js

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const servePrivatePortHttp = (
1818
const connectionClosedPromises = new Set();
1919

2020
const respond = async request => {
21+
await null;
2122
if (request.method === 'GET') {
2223
if (request.url === '/') {
2324
return {

packages/daemon/src/web-server-node-powers.js

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export const makeHttpPowers = ({ http, ws }) => {
7979

8080
server.on('request', (req, res) => {
8181
(async () => {
82+
await null;
8283
if (req.method === undefined || req.url === undefined) {
8384
return;
8485
}

packages/daemon/src/web-server-node.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export const make = async (_powers, context) => {
110110
}
111111

112112
(async () => {
113+
await null;
113114
const {
114115
reader: frameReader,
115116
writer: frameWriter,
@@ -137,7 +138,6 @@ export const make = async (_powers, context) => {
137138
// TODO set up heart monitor
138139
E.sendOnly(webletBootstrap).ping();
139140

140-
// eslint-disable-next-line no-use-before-define
141141
E(webletBootstrap)
142142
.makeBundle(
143143
await E(/** @type {any} */ (webletBundle)).json(),

packages/daemon/test/context-consumer.js

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const ContextConsumerInterface = M.interface(
1111
export const make = async (_powers, context) => {
1212
return makeExo('Context consumer', ContextConsumerInterface, {
1313
async awaitCancellation() {
14+
await null;
1415
try {
1516
await E(context).whenCancelled();
1617
} catch {

packages/eventual-send/test/e.test.js

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ test('E.when', async t => {
3939
});
4040

4141
test('E method calls', async t => {
42+
await null;
4243
const x = {
4344
double(n) {
4445
return 2 * n;
@@ -144,6 +145,7 @@ test('E method call undefined receiver', async t => {
144145
});
145146

146147
test('E shortcuts', async t => {
148+
await null;
147149
const x = {
148150
name: 'buddy',
149151
val: 123,
@@ -166,6 +168,7 @@ test('E shortcuts', async t => {
166168
});
167169

168170
test('E.get', async t => {
171+
await null;
169172
const x = {
170173
name: 'buddy',
171174
val: 123,

packages/eventual-send/test/eventual-send.test.js

+3
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ test('handlers are always async', async t => {
155155
});
156156

157157
test('new HandledPromise expected errors', async t => {
158+
await null;
158159
const handler = {
159160
get(o, _key) {
160161
return o;
@@ -259,6 +260,7 @@ test('new HandledPromise expected errors', async t => {
259260
});
260261

261262
test('new HandledPromise(executor, undefined)', async t => {
263+
await null;
262264
const handledP = new HandledPromise((_, _2, resolveWithPresence) => {
263265
setTimeout(() => {
264266
const o = {
@@ -308,6 +310,7 @@ test('handled promises are promises', t => {
308310
});
309311

310312
test('eventual send expected errors', async t => {
313+
await null;
311314
t.is(
312315
await HandledPromise.get(true, 'toString'),
313316
true.toString,

packages/eventual-send/test/thenable.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ test(
2020
);
2121

2222
const verifyThenAttack = async (t, resolve) => {
23+
await null;
2324
const p = new Promise(_ => {});
2425
let getHappened = false;
2526
let callHappened = false;

packages/exo/test/exo-only-throwables.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const thrower = makeExo(
2424
);
2525

2626
test('exo only throwables', async t => {
27+
await null;
2728
const e = makeError('test error', undefined, {
2829
sanitize: false,
2930
});

packages/exo/test/legacy-guard-tolerance.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import { makeExo } from '../src/exo-makers.js';
1717

1818
test('legacy guard tolerance', async t => {
19+
await null;
1920
const aag = M.await(88);
2021
const laag = makeLegacyAwaitArgGuard({
2122
argGuard: 88,

0 commit comments

Comments
 (0)