Skip to content

Commit 341721e

Browse files
author
Dylan Stewart
authored
Merge pull request #23 from NASA-AMMOS/fix/throws-in-library-code
Fix/throws in library code
2 parents 45c9e3b + ca5cd75 commit 341721e

File tree

2 files changed

+81
-7
lines changed

2 files changed

+81
-7
lines changed

src/UserCodeRunner.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import path from 'path';
44
import { defaultErrorCodeMessageMappers } from './defaultErrorCodeMessageMappers.js';
55
import { createMapDiagnosticMessage } from './utils/errorMessageMapping.js';
66
import ts from 'typescript';
7-
import { parse } from 'stack-trace';
7+
import { parse, StackFrame } from 'stack-trace';
88
import { BasicSourceMapConsumer, IndexedSourceMapConsumer, SourceMapConsumer } from 'source-map';
99
import LRUCache from 'lru-cache';
1010
import { Result } from './utils/monads.js';
@@ -348,21 +348,27 @@ export class UserCodeRuntimeError extends UserCodeError {
348348
private readonly error: Error;
349349
private readonly sourceMap: SourceMapConsumer;
350350
private readonly tsFileCache: Map<string, ts.SourceFile>;
351+
private readonly stackFrames: StackFrame[];
351352

352353
protected constructor(error: Error, sourceMap: SourceMapConsumer, tsFileCache: Map<string, ts.SourceFile>) {
353354
super();
354355
this.error = error;
355356
this.sourceMap = sourceMap;
356357
this.tsFileCache = tsFileCache;
358+
this.stackFrames = parse(this.error);
359+
const userCodeFrame = this.stackFrames.find(frame => frame.getFileName() === USER_CODE_FILENAME);
360+
if (userCodeFrame === undefined) {
361+
this.error.message = 'Error: Runtime error detected outside of user code execution path. This is most likely a bug in the additional library source.\nInherited from:\n' + this.error.message;
362+
throw this.error;
363+
}
357364
}
358365

359366
public get message(): string {
360367
return 'Error: ' + this.error.message;
361368
}
362369

363370
public get stack(): string {
364-
const stack = parse(this.error);
365-
const stackWithoutHarness = stack
371+
const stackWithoutHarness = this.stackFrames
366372
.filter(callSite => callSite.getFileName()?.endsWith(USER_CODE_FILENAME))
367373
.filter(callSite => {
368374
if (callSite.getFileName() === undefined) {
@@ -390,10 +396,7 @@ export class UserCodeRuntimeError extends UserCodeError {
390396

391397
public get location(): { line: number; column: number } {
392398
const stack = parse(this.error);
393-
const userFileStackFrame = stack.find(callSite => callSite.getFileName() === USER_CODE_FILENAME);
394-
if (userFileStackFrame === undefined) {
395-
throw new Error('Runtime error detected outside of user code execution path. This is most likely a bug in the additional library source.');
396-
}
399+
const userFileStackFrame = stack.find(callSite => callSite.getFileName() === USER_CODE_FILENAME)!;
397400
const originalPosition = this.sourceMap.originalPositionFor({
398401
line: userFileStackFrame.getLineNumber()!,
399402
column: userFileStackFrame.getColumnNumber()!,

test/UserCodeRunner.spec.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,77 @@ it('should handle unnamed arrow function default exports assignment', async () =
10891089
expect(result.isOk()).toBeTruthy();
10901090
});
10911091

1092+
it('should handle throws in user code but outside default function execution path', async () => {
1093+
const userCode = `
1094+
export default function MyDSLFunction(thing: string): string {
1095+
return thing + ' world';
1096+
}
1097+
1098+
throw new Error('This is a test error');
1099+
`.trimTemplate();
1100+
1101+
const runner = new UserCodeRunner();
1102+
1103+
const result = await runner.executeUserCode(
1104+
userCode,
1105+
['hello'],
1106+
'string',
1107+
['string'],
1108+
);
1109+
1110+
expect(result.isErr()).toBeTruthy();
1111+
expect(result.unwrapErr().length).toBe(1);
1112+
expect(result.unwrapErr()[0].message).toBe(`
1113+
Error: This is a test error
1114+
`.trimTemplate());
1115+
expect(result.unwrapErr()[0].stack).toBe(`
1116+
at null(5:6)
1117+
`.trimTemplate())
1118+
expect(result.unwrapErr()[0].location).toMatchObject({
1119+
line: 5,
1120+
column: 6,
1121+
});
1122+
});
1123+
1124+
it('should handle throws in library code outside default function execution path with an explicit error', async () => {
1125+
const userCode = `
1126+
export default function MyDSLFunction(thing: string): string {
1127+
return thing + ' world';
1128+
}
1129+
`.trimTemplate();
1130+
1131+
const runner = new UserCodeRunner();
1132+
1133+
try {
1134+
await runner.executeUserCode(
1135+
userCode,
1136+
['hello'],
1137+
'string',
1138+
['string'],
1139+
1000,
1140+
[
1141+
ts.createSourceFile('additionalFile.ts', `
1142+
export {}
1143+
throw new Error('This is a test error');
1144+
`.trimTemplate(), ts.ScriptTarget.ESNext, true),
1145+
],
1146+
);
1147+
} catch (err: any) {
1148+
expect(err.message).toBe(`
1149+
Error: Runtime error detected outside of user code execution path. This is most likely a bug in the additional library source.
1150+
Inherited from:
1151+
This is a test error
1152+
`.trimTemplate());
1153+
expect(err.stack).toBe(`
1154+
Error: This is a test error
1155+
at additionalFile:1:7
1156+
at SourceTextModule.evaluate (node:internal/vm/module:224:23)
1157+
at UserCodeRunner.executeUserCode (/Users/jdstewar/gitRepos/jpl/mpcs/aerie/aerie-ts-user-code-runner/src/UserCodeRunner.ts:234:24)
1158+
at Object.<anonymous> (/Users/jdstewar/gitRepos/jpl/mpcs/aerie/aerie-ts-user-code-runner/test/UserCodeRunner.spec.ts:1090:5)
1159+
`.trimTemplate());
1160+
}
1161+
});
1162+
10921163
test('Aerie incorrect stack frame assumption regression test', async () => {
10931164
const userCode = `
10941165
export default () => {

0 commit comments

Comments
 (0)