From f82b0fef26782f6a871da036a6bd9a6670444509 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 27 Jul 2022 15:51:00 -0400 Subject: fixed document leak related to capturedVariables in scripts. changed capturedVariables to be a string encoding the captured variables instead of a Doc. --- src/client/util/Scripting.ts | 65 ++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 32 deletions(-) (limited to 'src/client/util/Scripting.ts') diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 3791bec73..ea2bf6551 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -5,12 +5,11 @@ // import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts' // @ts-ignore import * as typescriptlib from '!!raw-loader!./type_decls.d'; -import * as ts from "typescript"; -import { Doc, Field } from "../../fields/Doc"; -import { scriptingGlobals, ScriptingGlobals } from "./ScriptingGlobals"; +import * as ts from 'typescript'; +import { Doc, Field } from '../../fields/Doc'; +import { scriptingGlobals, ScriptingGlobals } from './ScriptingGlobals'; export { ts }; - export interface ScriptSuccess { success: true; result: any; @@ -46,7 +45,6 @@ export function isCompileError(toBeDetermined: CompileResult): toBeDetermined is return false; } - function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult { const errors = diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error); if ((options.typecheck !== false && errors.length) || !script) { @@ -63,7 +61,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an const run = (args: { [name: string]: any } = {}, onError?: (e: any) => void, errorVal?: any): ScriptResult => { const argsArray: any[] = []; for (const name of customParams) { - if (name === "this") { + if (name === 'this') { continue; } if (name in args) { @@ -86,11 +84,10 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an return { success: true, result }; } catch (error) { - if (batch) { batch.end(); } - onError?.(script + " " + error); + onError?.(script + ' ' + error); return { success: false, error, result: errorVal }; } }; @@ -151,16 +148,16 @@ class ScriptingCompilerHost { } export type Traverser = (node: ts.Node, indentation: string) => boolean | void; -export type TraverserParam = Traverser | { onEnter: Traverser, onLeave: Traverser }; +export type TraverserParam = Traverser | { onEnter: Traverser; onLeave: Traverser }; export type Transformer = { - transformer: ts.TransformerFactory, - getVars?: () => { capturedVariables: { [name: string]: Field } } + transformer: ts.TransformerFactory; + getVars?: () => { capturedVariables: { [name: string]: Field } }; }; export interface ScriptOptions { requiredType?: string; // does function required a typed return value - addReturn?: boolean; // does the compiler automatically add a return statement + addReturn?: boolean; // does the compiler automatically add a return statement params?: { [name: string]: string }; // list of function parameters and their types - capturedVariables?: { [name: string]: Field }; // list of captured variables + capturedVariables?: { [name: string]: Doc | number | string | boolean }; // list of captured variables typecheck?: boolean; // should the compiler perform typechecking editable?: boolean; // can the script edit Docs traverser?: TraverserParam; @@ -169,24 +166,28 @@ export interface ScriptOptions { } // function forEachNode(node:ts.Node, fn:(node:any) => void); -function forEachNode(node: ts.Node, onEnter: Traverser, onExit?: Traverser, indentation = "") { - return onEnter(node, indentation) || ts.forEachChild(node, (n: any) => { - forEachNode(n, onEnter, onExit, indentation + " "); - }) || (onExit && onExit(node, indentation)); +function forEachNode(node: ts.Node, onEnter: Traverser, onExit?: Traverser, indentation = '') { + return ( + onEnter(node, indentation) || + ts.forEachChild(node, (n: any) => { + forEachNode(n, onEnter, onExit, indentation + ' '); + }) || + (onExit && onExit(node, indentation)) + ); } export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult { - const { requiredType = "", addReturn = false, params = {}, capturedVariables = {}, typecheck = true } = options; + const { requiredType = '', addReturn = false, params = {}, capturedVariables = {}, typecheck = true } = options; if (options.params && !options.params.this) options.params.this = Doc.name; if (options.params && !options.params.self) options.params.self = Doc.name; if (options.globals) { ScriptingGlobals.setScriptingGlobals(options.globals); } - const host = new ScriptingCompilerHost; + const host = new ScriptingCompilerHost(); if (options.traverser) { const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true); - const onEnter = typeof options.traverser === "object" ? options.traverser.onEnter : options.traverser; - const onLeave = typeof options.traverser === "object" ? options.traverser.onLeave : undefined; + const onEnter = typeof options.traverser === 'object' ? options.traverser.onEnter : options.traverser; + const onLeave = typeof options.traverser === 'object' ? options.traverser.onLeave : undefined; forEachNode(sourceFile, onEnter, onLeave); } if (options.transformer) { @@ -199,17 +200,17 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp } const transformed = result.transformed; const printer = ts.createPrinter({ - newLine: ts.NewLineKind.LineFeed + newLine: ts.NewLineKind.LineFeed, }); script = printer.printFile(transformed[0]); result.dispose(); } const paramNames: string[] = []; - if ("this" in params || "this" in capturedVariables) { - paramNames.push("this"); + if ('this' in params || 'this' in capturedVariables) { + paramNames.push('this'); } for (const key in params) { - if (key === "this") continue; + if (key === 'this') continue; paramNames.push(key); } const paramList = paramNames.map(key => { @@ -217,21 +218,21 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp return `${key}: ${val}`; }); for (const key in capturedVariables) { - if (key === "this") continue; + if (key === 'this') continue; const val = capturedVariables[key]; paramNames.push(key); - paramList.push(`${key}: ${typeof val === "object" ? Object.getPrototypeOf(val).constructor.name : typeof val}`); + paramList.push(`${key}: ${typeof val === 'object' ? Object.getPrototypeOf(val).constructor.name : typeof val}`); } - const paramString = paramList.join(", "); - const body = addReturn && !script.startsWith("{ return") ? `return ${script};` : script; + const paramString = paramList.join(', '); + const body = addReturn && !script.startsWith('{ return') ? `return ${script};` : script; const reqTypes = requiredType ? `: ${requiredType}` : ''; const funcScript = `(function(${paramString})${reqTypes} { ${body} })`; - host.writeFile("file.ts", funcScript); + host.writeFile('file.ts', funcScript); if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib); - const program = ts.createProgram(["file.ts"], {}, host); + const program = ts.createProgram(['file.ts'], {}, host); const testResult = program.emit(); - const outputText = host.readFile("file.js"); + const outputText = host.readFile('file.js'); const diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics); -- cgit v1.2.3-70-g09d2