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

overlookmotel / livepack / 6840917458

12 Nov 2023 01:08PM UTC coverage: 90.45% (+0.001%) from 90.449%
6840917458

push

github

overlookmotel
Conform var to boolean [refactor]

4643 of 4990 branches covered (0.0%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

12585 of 14057 relevant lines covered (89.53%)

12993.58 hits per line

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

93.85
/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,314✔
42
                eval(code) {
2,314✔
43
                        return evalIndirect(code, tracker, filename, blockIdCounter, prefixNum, evalIndirectLocal);
1,906✔
44
                }
1,906✔
45
        }.eval;
2,314✔
46
        const evalDirectLocal = (...args) => evalDirect(
2,314✔
47
                args, filename, blockIdCounter, prefixNum, evalDirectLocal
2,090✔
48
        );
2,314✔
49
        tracker.evalIndirect = evalIndirectLocal;
2,314✔
50
        tracker.evalDirect = evalDirectLocal;
2,314✔
51

2,314✔
52
        // Record eval shim so it can be switched back to `eval` if it's serialized
2,314✔
53
        specialFunctions.set(evalIndirectLocal, {type: 'eval', parent: null, key: filename});
2,314✔
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,906✔
69
        // If `code` arg is not a string, eval it unchanged - it won't be evaluated as code
1,906✔
70
        // eslint-disable-next-line no-eval
1,906✔
71
        if (!isString(code)) return execEvalCode(eval, code, false, code, evalIndirectLocal);
1,906!
72

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

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

1,906✔
84
        // `eval()` code without external scope and inject in Livepack's vars
1,906✔
85
        // eslint-disable-next-line no-eval
1,906✔
86
        const fn = execEvalCode(eval, fnCode, shouldThrow, code, evalIndirectLocal);
1,906✔
87
        return fn(tracker, getScopeId);
1,906✔
88
}
1,906✔
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,090✔
103
        const callArgs = args.slice(0, -5),
2,090✔
104
                code = callArgs[0],
2,090✔
105
                [possibleEval, execEvalSingleArg, execEvalSpread, scopeDefs, isStrict] = args.slice(-5);
2,090✔
106

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

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

2,090✔
115
        // Create blocks
2,090✔
116
        const state = {
2,090✔
117
                currentBlock: undefined,
2,090✔
118
                currentThisBlock: undefined,
2,090✔
119
                currentSuperBlock: undefined
2,090✔
120
        };
2,090✔
121
        for (const [blockId, blockName, scopeId, ...varDefs] of scopeDefs) {
2,090✔
122
                const block = createBlockWithId(blockId, blockName, true, state);
6,706✔
123
                block.scopeIdVarNode = t.numericLiteral(scopeId);
6,706✔
124
                state.currentBlock = block;
6,706✔
125

6,706✔
126
                for (const [varName, isConst, isSilentConst, argNames] of varDefs) {
6,706✔
127
                        if (varName === 'this') {
17,158✔
128
                                createThisBinding(block);
1,866✔
129
                                state.currentThisBlock = block;
1,866✔
130
                        } else if (varName === 'new.target') {
17,158✔
131
                                createNewTargetBinding(block);
1,866✔
132
                        } else {
15,292✔
133
                                // TODO: Also need to set `superIsProto` and create a new temp var for `super` target
13,426✔
134
                                // (the external var could be shadowed inside `eval()` if prefix num is changing)
13,426✔
135
                                if (varName === 'super') state.currentSuperBlock = block;
13,426✔
136

13,426✔
137
                                // Whether var is function is not relevant because it will always be in external scope
13,426✔
138
                                // of the functions it's being recorded on, and value of `isFunction` only has any effect
13,426✔
139
                                // for internal vars
13,426✔
140
                                createBindingWithoutNameCheck(
13,426✔
141
                                        block, varName, {isConst: !!isConst, isSilentConst: !!isSilentConst, argNames}
13,426✔
142
                                );
13,426✔
143
                        }
13,426✔
144
                }
17,158✔
145
        }
6,706✔
146

2,090✔
147
        // Compile to executable code with tracking code inserted.
2,090✔
148
        // If var names prefix inside code has to be different from outside,
2,090✔
149
        // code is wrapped in a function which injects tracker and getScopeId functions:
2,090✔
150
        // `() => foo` -> `(livepack1_tracker, livepack1_getScopeId) => () => foo`
2,090✔
151
        const {
2,090✔
152
                code: codeInstrumented, shouldThrow, internalPrefixNum
2,090✔
153
        } = compile(code, filename, blockIdCounter, externalPrefixNum, false, state, isStrict);
2,090✔
154

2,090✔
155
        // Call `eval()` with amended code
2,090✔
156
        let res = execEvalCode(execEvalSingleArg, codeInstrumented, shouldThrow, code, evalDirectLocal);
2,090✔
157

2,090✔
158
        // If was wrapped in a function, create new tracker and inject tracker and `getScopeId` into function
2,090✔
159
        if (internalPrefixNum !== externalPrefixNum) {
2,090✔
160
                const tracker = createTracker(filename, blockIdCounter, internalPrefixNum);
128✔
161
                res = res(tracker, getScopeId);
128✔
162
        }
128✔
163

2,080✔
164
        return res;
2,080✔
165
}
2,090✔
166

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

62✔
196
/**
62✔
197
 * Instrument code.
62✔
198
 * If is indirect `eval`, wraps the code in a function which takes `livepack_tracker`
62✔
199
 * and `livepack_getScopeId` arguments. This is to inject Livepack's internal functions into scope.
62✔
200
 * If is direct `eval()` and internal vars prefix number outside `eval()` and inside `eval()` differ,
62✔
201
 * wrap in an IIFE to inject the internal vars with new names:
62✔
202
 * `((livepack20_tracker, livepack20_getScopeId) => { ... })(livepack_tracker, livepack_getScopeId)`
62✔
203
 *
62✔
204
 * @param {string} code - Code string passed to `eval()`
62✔
205
 * @param {string} filename - File path
62✔
206
 * @param {Object} blockIdCounter - Block ID counter for file
62✔
207
 * @param {number} externalPrefixNum - Internal vars prefix num outside `eval()`
62✔
208
 * @param {boolean} isIndirectEval - `true` if is indirect eval
62✔
209
 * @param {Object} [state] - State to initialize instrumentation state (only if direct `eval()` call)
62✔
210
 * @param {boolean} isStrict - `true` if environment outside `eval()` is strict mode
62✔
211
 * @returns {Object} - Object with properties:
62✔
212
 *   {string} .code - Instrumented code (or input code if parsing failed)
62✔
213
 *   {boolean} .shouldThrow - `true` if could not parse code, so calling `eval()` with this code
62✔
214
 *     should throw syntax error
62✔
215
 *   {number} .internalPrefixNum - Internal vars prefix num inside `eval()`
62✔
216
 */
62✔
217
function compile(code, filename, blockIdCounter, externalPrefixNum, isIndirectEval, state, isStrict) {
3,996✔
218
        // Parse code.
3,996✔
219
        // If parsing fails, swallow error. Expression will be passed to `eval()`
3,996✔
220
        // which should throw - this maintains native errors.
3,996✔
221
        let ast;
3,996✔
222
        try {
3,996✔
223
                ast = parseImpl(
3,996✔
224
                        code, filename, false, false, !isIndirectEval, false, isStrict, false, undefined
3,996✔
225
                ).ast;
3,996✔
226
        } catch (err) {
3,996✔
227
                return {code, shouldThrow: true, internalPrefixNum: externalPrefixNum};
10✔
228
        }
10✔
229

3,986✔
230
        // Instrument code.
3,986✔
231
        // Filename in eval code will be same as host file.
3,986✔
232
        // Block IDs in created code will be higher than block IDs used in this file (to avoid clashes).
3,986✔
233
        // Var name prefix will be kept same as in host file if possible,
3,986✔
234
        // to avoid wrapping in a function unless impossible to avoid.
3,986✔
235
        // Details of vars which can be obtained from external scopes is passed in.
3,986✔
236
        state = {
3,986✔
237
                nextBlockId: blockIdCounter.nextBlockId,
3,986✔
238
                isStrict,
3,986✔
239
                internalVarsPrefixNum: externalPrefixNum,
3,986✔
240
                ...state
3,986✔
241
        };
3,986✔
242
        modifyAst(ast, filename, false, isStrict, undefined, state);
3,986✔
243

3,986✔
244
        // Update next block ID for file
3,986✔
245
        blockIdCounter.nextBlockId = state.nextBlockId;
3,986✔
246

3,986✔
247
        // If indirect `eval`, or direct `eval()` with different prefix nums inside and outside `eval()`,
3,986✔
248
        // wrap in function to inject Livepack's internal vars.
3,986✔
249
        // `123` => `(livepack_tracker, livepack_getScopeId) => eval('123')`.
3,986✔
250
        // Wrapping in a 2nd `eval()` is required to ensure it returns its value.
3,986✔
251
        const internalPrefixNum = state.internalVarsPrefixNum;
3,986✔
252
        if (isIndirectEval || internalPrefixNum !== externalPrefixNum) {
3,996✔
253
                ast = wrapInFunction(ast, internalPrefixNum);
2,034✔
254
        }
2,034✔
255

3,986✔
256
        // Return instrumented code
3,986✔
257
        code = generate(ast, {retainLines: true, compact: true}).code;
3,986✔
258

3,986✔
259
        if (DEBUG) {
3,990!
UNCOV
260
                /* eslint-disable no-console */
×
261
                console.log('----------------------------------------');
×
262
                console.log('TRANSFORMED: eval code in', filename);
×
263
                console.log('----------------------------------------');
×
264
                console.log(code);
×
265
                console.log('');
×
266
                /* eslint-enable no-console */
×
267
        }
×
268

3,986✔
269
        return {code, shouldThrow: false, internalPrefixNum};
3,986✔
270
}
3,996✔
271

62✔
272
function wrapInFunction(ast, internalPrefixNum) {
2,034✔
273
        return t.program([
2,034✔
274
                t.expressionStatement(
2,034✔
275
                        t.arrowFunctionExpression(
2,034✔
276
                                [
2,034✔
277
                                        internalVarNode(TRACKER_VAR_NAME_BODY, internalPrefixNum),
2,034✔
278
                                        internalVarNode(GET_SCOPE_ID_VAR_NAME_BODY, internalPrefixNum)
2,034✔
279
                                ],
2,034✔
280
                                t.callExpression(
2,034✔
281
                                        t.identifier('eval'), [
2,034✔
282
                                                t.stringLiteral(generate(ast, {retainLines: true, compact: true}).code)
2,034✔
283
                                        ]
2,034✔
284
                                )
2,034✔
285
                        )
2,034✔
286
                )
2,034✔
287
        ]);
2,034✔
288
}
2,034✔
289

62✔
290
function internalVarNode(name, prefixNum) {
4,068✔
291
        return t.identifier(`${INTERNAL_VAR_NAMES_PREFIX}${prefixNum || ''}_${name}`);
4,068✔
292
}
4,068✔
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