From 885af4d693779ef4e38621b73898093c3ef1e657 Mon Sep 17 00:00:00 2001 From: David Beitey Date: Thu, 21 Mar 2024 16:23:40 +0000 Subject: [PATCH] Retain original sandbox errors (from different JavaScript realms) without coercion (#355) This PR retains original errors being returned from different realms. Currently, this module [wraps any such errors](https://github.com/guigrpa/docx-templates/blob/master/src/jsSandbox.ts#L54) as `new Error(`${err}`)`, coercing their name/message into a string, losing these as separate fields, and losing the stack trace entirely (it gets effectively replaced when the error is re-thrown). The reason it does this is because `instanceof Error` returns false for sandboxed errors when the prototype chain doesn't match; these errors are still Error objects, just that they can't be detected with `instanceof`. This change affects the formatting of `CommandExeuctionError`, since the string coercion no longer happens automatically, so the `${err.name}` has been added back in to its message. Most tests continue to work unchanged, but several have required updating now that this messaging is consistent between sandboxing and unsandboxed execution. --- .../__snapshots__/error_handling.test.ts.snap | 4 +- .../__snapshots__/images.test.ts.snap | 2 +- .../__snapshots__/templating.test.ts.snap | 10 +-- src/__tests__/error_handling.test.ts | 73 ++++++++++++++++++ src/__tests__/fixtures/referenceError.docx | Bin 0 -> 12053 bytes src/errors.ts | 9 ++- src/jsSandbox.ts | 8 +- src/processTemplate.ts | 5 +- 8 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 src/__tests__/fixtures/referenceError.docx diff --git a/src/__tests__/__snapshots__/error_handling.test.ts.snap b/src/__tests__/__snapshots__/error_handling.test.ts.snap index e5c10a3d..81580da2 100644 --- a/src/__tests__/__snapshots__/error_handling.test.ts.snap +++ b/src/__tests__/__snapshots__/error_handling.test.ts.snap @@ -262,7 +262,7 @@ exports[`sandbox custom ErrorHandler properly handles InvalidCommandError 1`] = - Error: SyntaxError: Unexpected identifier 'foo' + SyntaxError: Unexpected identifier 'foo' @@ -284,7 +284,7 @@ exports[`sandbox custom ErrorHandler properly handles InvalidCommandError 1`] = exports[`sandbox custom ErrorHandler properly handles InvalidCommandError 2`] = ` [ - [Error: SyntaxError: Unexpected identifier 'foo'], + [SyntaxError: Unexpected identifier 'foo'], ] `; diff --git a/src/__tests__/__snapshots__/images.test.ts.snap b/src/__tests__/__snapshots__/images.test.ts.snap index 5e50b5f3..a591eac4 100644 --- a/src/__tests__/__snapshots__/images.test.ts.snap +++ b/src/__tests__/__snapshots__/images.test.ts.snap @@ -1187,7 +1187,7 @@ exports[`001: Issue #61 Correctly renders an SVG image 1`] = ` } `; -exports[`002: throws when thumbnail is incorrectly provided when inserting an SVG 1`] = `[Error: Error executing command 'IMAGE svgImgFile()': An extension (one of .png,.gif,.jpg,.jpeg,.svg) needs to be provided when providing an image or a thumbnail.]`; +exports[`002: throws when thumbnail is incorrectly provided when inserting an SVG 1`] = `[Error: Error executing command 'IMAGE svgImgFile()': Error: An extension (one of .png,.gif,.jpg,.jpeg,.svg) needs to be provided when providing an image or a thumbnail.]`; exports[`003: can inject an svg without a thumbnail 1`] = ` { diff --git a/src/__tests__/__snapshots__/templating.test.ts.snap b/src/__tests__/__snapshots__/templating.test.ts.snap index b23571f9..1982f318 100644 --- a/src/__tests__/__snapshots__/templating.test.ts.snap +++ b/src/__tests__/__snapshots__/templating.test.ts.snap @@ -19868,7 +19868,7 @@ exports[`noSandbox Template processing 39 Processes LINK commands 1`] = ` } `; -exports[`noSandbox Template processing 40 Throws on invalid command 1`] = `[Error: Error executing command 'TTT foo': Unexpected identifier 'foo']`; +exports[`noSandbox Template processing 40 Throws on invalid command 1`] = `[Error: Error executing command 'TTT foo': SyntaxError: Unexpected identifier 'foo']`; exports[`noSandbox Template processing 41 Throws on invalid for logic 1`] = `[Error: Invalid command: END-FOR company]`; @@ -26172,14 +26172,14 @@ exports[`noSandbox Template processing 107b non-alphanumeric INS commands (e.g. exports[`noSandbox Template processing 112a failFast: false lists all errors in the document before failing. 1`] = ` [ - [Error: Error executing command 'notavailable': notavailable is not defined], - [Error: Error executing command 'something': something is not defined], + [Error: Error executing command 'notavailable': ReferenceError: notavailable is not defined], + [Error: Error executing command 'something': ReferenceError: something is not defined], [Error: Invalid command: END-FOR company], [Error: Unterminated FOR-loop ('FOR company'). Make sure each FOR loop has a corresponding END-FOR command.], ] `; -exports[`noSandbox Template processing 112b failFast: true has the same behaviour as when failFast is undefined 1`] = `[Error: Error executing command 'notavailable': notavailable is not defined]`; +exports[`noSandbox Template processing 112b failFast: true has the same behaviour as when failFast is undefined 1`] = `[Error: Error executing command 'notavailable': ReferenceError: notavailable is not defined]`; exports[`noSandbox Template processing 131 correctly handles Office 365 .docx files 1`] = ` { @@ -30290,7 +30290,7 @@ exports[`noSandbox Template processing avoids confusion between variable name an } `; -exports[`noSandbox Template processing fixSmartQuotes flag (see PR #152) 1`] = `"Error executing command 'reverse(‘aubergine’)': Invalid or unexpected token"`; +exports[`noSandbox Template processing fixSmartQuotes flag (see PR #152) 1`] = `"Error executing command 'reverse(‘aubergine’)': SyntaxError: Invalid or unexpected token"`; exports[`noSandbox Template processing iterate over object properties and keys in FOR loop 1`] = ` { diff --git a/src/__tests__/error_handling.test.ts b/src/__tests__/error_handling.test.ts index 01538bf5..3e79729e 100644 --- a/src/__tests__/error_handling.test.ts +++ b/src/__tests__/error_handling.test.ts @@ -4,6 +4,7 @@ import QR from 'qrcode'; import { createReport } from '../index'; import { setDebugLogSink } from '../debug'; import { + isError, NullishCommandResultError, CommandExecutionError, InvalidCommandError, @@ -11,6 +12,17 @@ import { if (process.env.DEBUG) setDebugLogSink(console.log); +class NoErrorThrownError extends Error {} + +const getError = async (call: () => unknown): Promise => { + try { + await call(); + throw new NoErrorThrownError(); + } catch (error: unknown) { + return error as TError; + } +}; + ['noSandbox', 'sandbox'].forEach(sbStatus => { const noSandbox = sbStatus === 'sandbox' ? false : true; @@ -325,3 +337,64 @@ if (process.env.DEBUG) setDebugLogSink(console.log); ); }); }); + +describe('errors from different realms', () => { + it('sandbox', async () => { + const template = await fs.promises.readFile( + path.join(__dirname, 'fixtures', 'referenceError.docx') + ); + + const error = await getError(() => + createReport({ noSandbox: false, template, data: {} }) + ); + expect(error).toBeInstanceOf(CommandExecutionError); + + // We cannot check with instanceof as this Error is from another realm despite still being an error + const commandExecutionError = error as CommandExecutionError; + expect(commandExecutionError.err).not.toBeInstanceOf(ReferenceError); + expect(commandExecutionError.err).not.toBeInstanceOf(Error); + expect(commandExecutionError.err.name).toBe('ReferenceError'); + expect(commandExecutionError.err.message).toBe( + 'nonExistentVariable is not defined' + ); + }); + + it('noSandbox', async () => { + const template = await fs.promises.readFile( + path.join(__dirname, 'fixtures', 'referenceError.docx') + ); + + const error = await getError(() => + createReport({ noSandbox: true, template, data: {} }) + ); + expect(error).toBeInstanceOf(CommandExecutionError); + + // Without sandboxing, the error is from the same realm + const commandExecutionError = error as CommandExecutionError; + expect(commandExecutionError.err).toBeInstanceOf(ReferenceError); + expect(commandExecutionError.err).toBeInstanceOf(Error); + expect(commandExecutionError.err.name).toBe('ReferenceError'); + expect(commandExecutionError.err.message).toBe( + 'nonExistentVariable is not defined' + ); + }); +}); + +describe('isError', () => { + it('Error is an error', () => { + expect(isError(new Error())).toBeTruthy(); + }); + + it('error-like object is an error', () => { + expect( + isError({ + name: 'ReferenceError', + message: 'nonExistentVariable is not defined', + }) + ).toBeTruthy(); + }); + + it('primitive is not an error', () => { + expect(isError(1)).toBeFalsy(); + }); +}); diff --git a/src/__tests__/fixtures/referenceError.docx b/src/__tests__/fixtures/referenceError.docx new file mode 100644 index 0000000000000000000000000000000000000000..d44a549081b7b9ddbfad6273ca7df56b680dda88 GIT binary patch literal 12053 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 literal 0 HcmV?d00001 diff --git a/src/errors.ts b/src/errors.ts index 036a4f28..24fc7a0b 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,5 +1,12 @@ import { LoopStatus } from './types'; +export function isError(err: unknown): err is Error { + return ( + err instanceof Error || + (typeof err === 'object' && !!err && 'name' in err && 'message' in err) + ); +} + /** * Thrown when `rejectNullish` is set to `true` and a command returns `null` or `undefined`. */ @@ -50,7 +57,7 @@ export class CommandExecutionError extends Error { command: string; err: Error; constructor(err: Error, command: string) { - super(`Error executing command '${command}': ${err.message}`); + super(`Error executing command '${command}': ${err.name}: ${err.message}`); Object.setPrototypeOf(this, CommandExecutionError.prototype); this.command = command; this.err = err; diff --git a/src/jsSandbox.ts b/src/jsSandbox.ts index 6f8329eb..34ae3654 100755 --- a/src/jsSandbox.ts +++ b/src/jsSandbox.ts @@ -1,7 +1,11 @@ import vm from 'vm'; import { getCurLoop } from './reportUtils'; import { ReportData, Context, SandBox } from './types'; -import { CommandExecutionError, NullishCommandResultError } from './errors'; +import { + isError, + CommandExecutionError, + NullishCommandResultError, +} from './errors'; import { logger } from './debug'; // Runs a user snippet in a sandbox, and returns the result. @@ -51,7 +55,7 @@ export async function runUserJsAndGetRaw( result = await script.runInContext(context); } } catch (err) { - const e = err instanceof Error ? err : new Error(`${err}`); + const e = isError(err) ? err : new Error(`${err}`); if (ctx.options.errorHandler != null) { context = sandbox; result = await ctx.options.errorHandler(e, code); diff --git a/src/processTemplate.ts b/src/processTemplate.ts index 91cfe5b7..b96c458e 100755 --- a/src/processTemplate.ts +++ b/src/processTemplate.ts @@ -25,6 +25,7 @@ import { NonTextNode, } from './types'; import { + isError, CommandSyntaxError, InternalError, InvalidCommandError, @@ -628,7 +629,7 @@ const processCmd: CommandProcessor = async ( try { processImage(ctx, img); } catch (e) { - if (!(e instanceof Error)) throw e; + if (!isError(e)) throw e; throw new ImageError(e, cmd); } } @@ -660,7 +661,7 @@ const processCmd: CommandProcessor = async ( } else throw new CommandSyntaxError(cmd); return; } catch (err) { - if (!(err instanceof Error)) throw err; + if (!isError(err)) throw err; if (ctx.options.errorHandler != null) { return ctx.options.errorHandler(err); }