• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

overlookmotel / livepack / 6937739311

21 Nov 2023 12:48AM UTC coverage: 90.481% (+0.01%) from 90.467%
6937739311

push

github

overlookmotel
Dev: Update dev dependencies

4700 of 5049 branches covered (0.0%)

Branch coverage included in aggregate %.

12609 of 14081 relevant lines covered (89.55%)

12859.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.71
/lib/init/eval.js
1
/* --------------------
62✔
2
 * livepack module
62✔
3
 * Replacements for `eval` which instrument code before executing.
62✔
4
 * ------------------*/
62✔
5

62✔
6
'use strict';
62✔
7

62✔
8
// Modules
62✔
9
const generate = require('@babel/generator').default,
62✔
10
        {isString} = require('is-it-type'),
62✔
11
        t = require('@babel/types');
62✔
12

62✔
13
// Imports
62✔
14
const createTracker = require('./tracker.js'),
62✔
15
        getScopeId = require('./getScopeId.js'),
62✔
16
        {parseImpl} = require('../instrument/instrument.js'),
62✔
17
        modifyAst = require('../instrument/modify.js'),
62✔
18
        {
62✔
19
                createBlockWithId, createThisBinding, createNewTargetBinding, createBindingWithoutNameCheck
62✔
20
        } = require('../instrument/blocks.js'),
62✔
21
        {
62✔
22
                INTERNAL_VAR_NAMES_PREFIX, TRACKER_VAR_NAME_BODY, GET_SCOPE_ID_VAR_NAME_BODY
62✔
23
        } = require('../shared/constants.js'),
62✔
24
        specialFunctions = require('../shared/internal.js').functions,
62✔
25
        assertBug = require('../shared/assertBug.js');
62✔
26

62✔
27
// Constants
62✔
28
const DEBUG = !!process.env.LIVEPACK_DEBUG_INSTRUMENT;
62✔
29

62✔
30
// Exports
62✔
31

62✔
32
/**
62✔
33
 * Add eval methods to tracker.
62✔
34
 * @param {Function} tracker - Tracker function for file
62✔
35
 * @param {string} filename - File path
62✔
36
 * @param {Object} blockIdCounter - Block ID counter for file
62✔
37
 * @param {number} prefixNum - Internal vars prefix num
62✔
38
 * @returns {undefined}
62✔
39
 */
62✔
40
module.exports = function addEvalFunctionsToTracker(tracker, filename, blockIdCounter, prefixNum) {
62✔
41
        const evalIndirectLocal = {
2,666✔
42
                eval(code) {
2,666✔
43
                        return evalIndirect(code, tracker, filename, blockIdCounter, prefixNum, evalIndirectLocal);
1,938✔
44
                }
1,938✔
45
        }.eval;
2,666✔
46
        const evalDirectLocal = (...args) => evalDirect(
2,666✔
47
                args, filename, blockIdCounter, prefixNum, evalDirectLocal
2,570✔
48
        );
2,666✔
49
        tracker.evalIndirect = evalIndirectLocal;
2,666✔
50
        tracker.evalDirect = evalDirectLocal;
2,666✔
51

2,666✔
52
        // Record eval shim so it can be switched back to `eval` if it's serialized
2,666✔
53
        specialFunctions.set(evalIndirectLocal, {type: 'eval', parent: null, key: filename});
2,666✔
54
};
62✔
55

62✔
56
/**
62✔
57
 * Shimmed version of `eval` exposed as `livepack_tracker.evalIndirect`.
62✔
58
 * Instrumentation replaces an uses of `eval` which are not `eval()` calls with this.
62✔
59
 * Instruments code before executing it.
62✔
60
 * @param {*} code - Argument to `eval`
62✔
61
 * @param {Function} tracker - Tracker function for file
62✔
62
 * @param {string} filename - File path
62✔
63
 * @param {Object} blockIdCounter - Block ID counter for file
62✔
64
 * @param {number} externalPrefixNum - Internal vars prefix num outside `eval`
62✔
65
 * @param {Function} evalIndirectLocal - Function which was called (used for stack traces if error)
62✔
66
 * @returns {*} - Result of `eval()` call
62✔
67
 */
62✔
68
function evalIndirect(code, tracker, filename, blockIdCounter, externalPrefixNum, evalIndirectLocal) {
1,938✔
69
        // If `code` arg is not a string, eval it unchanged - it won't be evaluated as code
1,938✔
70
        // eslint-disable-next-line no-eval
1,938✔
71
        if (!isString(code)) return execEvalCode(eval, code, false, code, evalIndirectLocal);
1,938!
72

1,938✔
73
        // Compile code with no external scope.
1,938✔
74
        // Code returned is for a function which takes arguments `(livepack_tracker, livepack_getScopeId)`.
1,938✔
75
        const {code: fnCode, shouldThrow, internalPrefixNum} = compile(
1,938✔
76
                code, filename, blockIdCounter, externalPrefixNum, true, false, undefined, [], false
1,938✔
77
        );
1,938✔
78

1,938✔
79
        // If prefix num inside `eval` is different from outside, create new tracker
1,938✔
80
        if (internalPrefixNum !== externalPrefixNum) {
1,938!
81
                tracker = createTracker(filename, blockIdCounter, internalPrefixNum);
×
82
        }
×
83

1,938✔
84
        // `eval()` code without external scope and inject in Livepack's vars
1,938✔
85
        // eslint-disable-next-line no-eval
1,938✔
86
        const fn = execEvalCode(eval, fnCode, shouldThrow, code, evalIndirectLocal);
1,938✔
87
        return fn(tracker, getScopeId);
1,938✔
88
}
1,938✔
89

62✔
90
/**
62✔
91
 * Shimmed version of `eval` exposed as `livepack_tracker.evalDirect`.
62✔
92
 * Instrumentation replaces any uses of `eval` which are direct `eval()` calls with this.
62✔
93
 * Instrumentation also passes details of all vars accessible from outside `eval()`.
62✔
94
 * Reconstruct blocks, then instrument code before executing it.
62✔
95
 * @param {Array<*>} args - Arguments `eval()` called with
62✔
96
 * @param {string} filename - File path
62✔
97
 * @param {Object} blockIdCounter - Block ID counter for file
62✔
98
 * @param {number} externalPrefixNum - Internal vars prefix num outside `eval`
62✔
99
 * @param {Function} evalDirectLocal - Function which was called (used for stack traces if error)
62✔
100
 * @returns {*} - Result of `eval()` call
62✔
101
 */
62✔
102
function evalDirect(args, filename, blockIdCounter, externalPrefixNum, evalDirectLocal) {
2,570✔
103
        const callArgs = args.slice(0, -6),
2,570✔
104
                code = callArgs[0],
2,570✔
105
                [
2,570✔
106
                        possibleEval, execEvalSingleArg, execEvalSpread, scopeDefs, isStrict, superIsProto
2,570✔
107
                ] = args.slice(-6);
2,570✔
108

2,570✔
109
        // If var `eval` where `eval()` is called is not global `eval`,
2,570✔
110
        // call `eval()` with original args unaltered
2,570✔
111
        // eslint-disable-next-line no-eval
2,570✔
112
        if (possibleEval !== eval) return execEvalCode(execEvalSpread, callArgs, false, code, evalDirectLocal);
2,570!
113

2,570✔
114
        // If `code` arg is not a string, eval it unchanged - it won't be evaluated as code
2,570✔
115
        if (!isString(code)) return execEvalCode(execEvalSingleArg, code, false, code, evalDirectLocal);
2,570!
116

2,570✔
117
        // Create blocks
2,570✔
118
        const state = {
2,570✔
119
                currentBlock: undefined,
2,570✔
120
                currentThisBlock: undefined,
2,570✔
121
                currentSuperBlock: undefined,
2,570✔
122
                currentSuperIsProto: false
2,570✔
123
        };
2,570✔
124
        const tempVars = [];
2,570✔
125
        let allowNewTarget = false;
2,570✔
126
        for (const [blockId, blockName, scopeId, ...varDefs] of scopeDefs) {
2,570✔
127
                const block = createBlockWithId(blockId, blockName, true, state);
9,506✔
128
                block.scopeIdVarNode = t.numericLiteral(scopeId);
9,506✔
129
                state.currentBlock = block;
9,506✔
130

9,506✔
131
                for (const [varName, isConst, isSilentConst, argNames, tempVarValue] of varDefs) {
9,506✔
132
                        if (varName === 'this') {
23,254✔
133
                                createThisBinding(block);
2,346✔
134
                                state.currentThisBlock = block;
2,346✔
135
                        } else if (varName === 'new.target') {
23,254✔
136
                                createNewTargetBinding(block);
2,346✔
137
                                allowNewTarget = true;
2,346✔
138
                        } else if (varName === 'super') {
20,908✔
139
                                // Don't create binding - `super` binding is created lazily
704✔
140
                                state.currentSuperBlock = block;
704✔
141
                                state.currentSuperIsProto = superIsProto;
704✔
142
                                tempVars.push({value: tempVarValue, block, varName});
704✔
143
                        } else {
18,562✔
144
                                // Whether var is function is not relevant because it will always be in external scope
17,858✔
145
                                // of the functions it's being recorded on, and value of `isFunction` only has any effect
17,858✔
146
                                // for internal vars
17,858✔
147
                                createBindingWithoutNameCheck(
17,858✔
148
                                        block, varName, {isConst: !!isConst, isSilentConst: !!isSilentConst, argNames}
17,858✔
149
                                );
17,858✔
150
                        }
17,858✔
151
                }
23,254✔
152
        }
9,506✔
153

2,570✔
154
        // Compile to executable code with tracking code inserted.
2,570✔
155
        // If var names prefix inside code has to be different from outside,
2,570✔
156
        // code is wrapped in a function which injects tracker and getScopeId functions, and temp vars:
2,570✔
157
        // `() => foo` -> `(livepack1_tracker, livepack1_getScopeId) => () => foo`
2,570✔
158
        const {code: codeInstrumented, shouldThrow, internalPrefixNum, tempVars: tempVarsUsed} = compile(
2,570✔
159
                code, filename, blockIdCounter, externalPrefixNum, false, allowNewTarget, state, tempVars, isStrict
2,570✔
160
        );
2,570✔
161

2,570✔
162
        // Call `eval()` with amended code
2,570✔
163
        let res = execEvalCode(execEvalSingleArg, codeInstrumented, shouldThrow, code, evalDirectLocal);
2,570✔
164

2,570✔
165
        // If was wrapped in a function, create new tracker and inject tracker and `getScopeId`
2,570✔
166
        // and/or any temp vars into function
2,570✔
167
        const params = internalPrefixNum !== externalPrefixNum
2,570✔
168
                ? [createTracker(filename, blockIdCounter, internalPrefixNum), getScopeId]
2,570✔
169
                : [];
2,570✔
170
        if (tempVarsUsed.length > 0) params.push(...tempVarsUsed.map(tempVar => tempVar.value));
2,570✔
171
        if (params.length > 0) res = res(...params);
2,564✔
172

2,560✔
173
        return res;
2,560✔
174
}
2,570✔
175

62✔
176
/**
62✔
177
 * Execute eval call.
62✔
178
 * If `shouldThrowSyntaxError` is set and `eval()` doesn't throw, throw an error.
62✔
179
 * This is to check that if parsing the code threw an error, it is indeed a genuine syntax error.
62✔
180
 * @param {Function} exec - Function to call
62✔
181
 * @param {*} arg - Argument to call `exec` with
62✔
182
 * @param {boolean} shouldThrowSyntaxError - `true` if `exec(arg)` should throw
62✔
183
 * @param {string} code - Code being executed (for error message)
62✔
184
 * @param {Function} fn - Wrapper function
62✔
185
 * @returns {*} - Return value of `exec(arg)`
62✔
186
 * @throws {*} - Error thrown by `exec(arg)` or internal error if should have thrown but didn't
62✔
187
 */
62✔
188
function execEvalCode(exec, arg, shouldThrowSyntaxError, code, fn) {
4,508✔
189
        let hasThrown = false;
4,508✔
190
        try {
4,508✔
191
                return exec(arg);
4,508✔
192
        } catch (err) {
4,508✔
193
                hasThrown = true;
10✔
194
                Error.captureStackTrace(err, fn);
10✔
195
                throw err;
10✔
196
        } finally {
4,508!
197
                assertBug(
4,508✔
198
                        !shouldThrowSyntaxError || hasThrown,
4,508✔
199
                        'Failed to parse `eval` expression',
4,508✔
200
                        () => `Eval expression: ${JSON.stringify(code)}`
4,508✔
201
                );
4,508✔
202
        }
4,508✔
203
}
4,508✔
204

62✔
205
/**
62✔
206
 * Instrument code.
62✔
207
 * If is indirect `eval`, wraps the code in a function which takes `livepack_tracker`
62✔
208
 * and `livepack_getScopeId` arguments. This is to inject Livepack's internal functions into scope.
62✔
209
 * If is direct `eval()` and internal vars prefix number outside `eval()` and inside `eval()` differ,
62✔
210
 * wrap in an IIFE to inject the internal vars with new names:
62✔
211
 * `((livepack20_tracker, livepack20_getScopeId) => { ... })(livepack_tracker, livepack_getScopeId)`
62✔
212
 *
62✔
213
 * @param {string} code - Code string passed to `eval()`
62✔
214
 * @param {string} filename - File path
62✔
215
 * @param {Object} blockIdCounter - Block ID counter for file
62✔
216
 * @param {number} externalPrefixNum - Internal vars prefix num outside `eval()`
62✔
217
 * @param {boolean} isIndirectEval - `true` if is indirect eval
62✔
218
 * @param {boolean} allowNewTarget - `true` if `new.target` is legal at top level
62✔
219
 * @param {Object} [state] - State to initialize instrumentation state (only if direct `eval()` call)
62✔
220
 * @param {Array<Object>} tempVars - Array of temp vars to be injected
62✔
221
 * @param {boolean} isStrict - `true` if environment outside `eval()` is strict mode
62✔
222
 * @returns {Object} - Object with properties:
62✔
223
 *   {string} .code - Instrumented code (or input code if parsing failed)
62✔
224
 *   {boolean} .shouldThrow - `true` if could not parse code, so calling `eval()` with this code
62✔
225
 *     should throw syntax error
62✔
226
 *   {number} .internalPrefixNum - Internal vars prefix num inside `eval()`
62✔
227
 *   {Array<Object>} .tempVars - Array of temp vars to be injected
62✔
228
 */
62✔
229
function compile(
4,508✔
230
        code, filename, blockIdCounter, externalPrefixNum,
4,508✔
231
        isIndirectEval, allowNewTarget, state, tempVars, isStrict
4,508✔
232
) {
4,508✔
233
        // Parse code.
4,508✔
234
        // If parsing fails, swallow error. Expression will be passed to `eval()`
4,508✔
235
        // which should throw - this maintains native errors.
4,508✔
236
        const allowSuper = !!state?.currentSuperBlock;
4,508✔
237
        let ast;
4,508✔
238
        try {
4,508✔
239
                ast = parseImpl(
4,508✔
240
                        code, filename, false, false, allowNewTarget, allowSuper, false, isStrict, false, undefined
4,508✔
241
                ).ast;
4,508✔
242
        } catch (err) {
4,508✔
243
                return {code, shouldThrow: true, internalPrefixNum: externalPrefixNum, tempVars};
10✔
244
        }
10✔
245

4,498✔
246
        // Instrument code.
4,498✔
247
        // Filename in eval code will be same as host file.
4,498✔
248
        // Block IDs in created code will be higher than block IDs used in this file (to avoid clashes).
4,498✔
249
        // Var name prefix will be kept same as in host file if possible,
4,498✔
250
        // to avoid wrapping in a function unless impossible to avoid.
4,498✔
251
        // Details of vars which can be obtained from external scopes is passed in.
4,498✔
252
        state = {
4,498✔
253
                nextBlockId: blockIdCounter.nextBlockId,
4,498✔
254
                isStrict,
4,498✔
255
                internalVarsPrefixNum: externalPrefixNum,
4,498✔
256
                ...state
4,498✔
257
        };
4,498✔
258
        modifyAst(ast, filename, false, isStrict, undefined, state);
4,498✔
259

4,498✔
260
        // Update next block ID for file
4,498✔
261
        blockIdCounter.nextBlockId = state.nextBlockId;
4,498✔
262

4,498✔
263
        // If indirect `eval`, or direct `eval()` with different prefix nums inside and outside `eval()`,
4,498✔
264
        // wrap in function to inject Livepack's internal vars.
4,498✔
265
        // `123` => `(livepack_tracker, livepack_getScopeId) => eval('123')`.
4,498✔
266
        // Wrapping in a 2nd `eval()` is required to ensure it returns its value.
4,498✔
267
        const internalPrefixNum = state.internalVarsPrefixNum;
4,498✔
268
        const params = (isIndirectEval || internalPrefixNum !== externalPrefixNum)
4,508✔
269
                ? [
4,508✔
270
                        internalVarNode(TRACKER_VAR_NAME_BODY, internalPrefixNum),
2,290✔
271
                        internalVarNode(GET_SCOPE_ID_VAR_NAME_BODY, internalPrefixNum)
2,290✔
272
                ]
2,290✔
273
                : [];
4,508✔
274

4,508✔
275
        // Filter out any temp vars which aren't used in `eval`-ed code
4,508✔
276
        if (tempVars.length > 0) {
4,508✔
277
                tempVars = tempVars.filter((tempVar) => {
704✔
278
                        const varNode = tempVar.block.bindings[tempVar.varName]?.varNode;
704✔
279
                        if (!varNode) return false;
704✔
280
                        params.push(varNode);
304✔
281
                        return true;
304✔
282
                });
704✔
283
        }
704✔
284

4,498✔
285
        if (params.length > 0) ast = wrapInFunction(ast, params);
4,502✔
286

4,498✔
287
        // Return instrumented code
4,498✔
288
        code = generate(ast, {retainLines: true, compact: true}).code;
4,498✔
289

4,498✔
290
        if (DEBUG) {
4,502!
291
                /* eslint-disable no-console */
×
292
                console.log('----------------------------------------');
×
293
                console.log('TRANSFORMED: eval code in', filename);
×
294
                console.log('----------------------------------------');
×
295
                console.log(code);
×
296
                console.log('');
×
297
                /* eslint-enable no-console */
×
298
        }
×
299

4,498✔
300
        return {code, shouldThrow: false, internalPrefixNum, tempVars};
4,498✔
301
}
4,508✔
302

62✔
303
function wrapInFunction(ast, params) {
2,450✔
304
        return t.program([
2,450✔
305
                t.expressionStatement(
2,450✔
306
                        t.arrowFunctionExpression(
2,450✔
307
                                params,
2,450✔
308
                                t.callExpression(
2,450✔
309
                                        t.identifier('eval'), [
2,450✔
310
                                                t.stringLiteral(generate(ast, {retainLines: true, compact: true}).code)
2,450✔
311
                                        ]
2,450✔
312
                                )
2,450✔
313
                        )
2,450✔
314
                )
2,450✔
315
        ]);
2,450✔
316
}
2,450✔
317

62✔
318
function internalVarNode(name, prefixNum) {
4,580✔
319
        return t.identifier(`${INTERNAL_VAR_NAMES_PREFIX}${prefixNum || ''}_${name}`);
4,580✔
320
}
4,580✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc