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

javascript-obfuscator / javascript-obfuscator / 21414128016

27 Jan 2026 09:04PM UTC coverage: 96.658% (-0.006%) from 96.664%
21414128016

push

github

web-flow
Fix identifiers generation (#1378)

1895 of 2050 branches covered (92.44%)

Branch coverage included in aggregate %.

35 of 36 new or added lines in 3 files covered. (97.22%)

2 existing lines in 1 file now uncovered.

5885 of 5999 relevant lines covered (98.1%)

31120336.44 hits per line

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

97.08
/src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts
1
import { inject, injectable } from 'inversify';
6✔
2
import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
6✔
3

4
import * as estraverse from '@javascript-obfuscator/estraverse';
6✔
5
import * as ESTree from 'estree';
6

7
import { TDeadNodeInjectionCustomNodeFactory } from '../../types/container/custom-nodes/TDeadNodeInjectionCustomNodeFactory';
8
import { TInitialData } from '../../types/TInitialData';
9
import { TNodeWithStatements } from '../../types/node/TNodeWithStatements';
10

11
import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
12
import { IOptions } from '../../interfaces/options/IOptions';
13
import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
14
import { INodeTransformersRunner } from '../../interfaces/node-transformers/INodeTransformersRunner';
15
import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
16

17
import { DeadCodeInjectionCustomNode } from '../../enums/custom-nodes/DeadCodeInjectionCustomNode';
6✔
18
import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
6✔
19
import { NodeType } from '../../enums/node/NodeType';
6✔
20
import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
6✔
21

22
import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
6✔
23
import { BlockStatementDeadCodeInjectionNode } from '../../custom-nodes/dead-code-injection-nodes/BlockStatementDeadCodeInjectionNode';
24
import { NodeFactory } from '../../node/NodeFactory';
6✔
25
import { NodeGuards } from '../../node/NodeGuards';
6✔
26
import { NodeMetadata } from '../../node/NodeMetadata';
6✔
27
import { NodeStatementUtils } from '../../node/NodeStatementUtils';
6✔
28
import { NodeUtils } from '../../node/NodeUtils';
6✔
29

30
@injectable()
31
export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
6✔
32
    /**
33
     * @type {string}
34
     */
35
    private static readonly deadCodeInjectionRootAstHostNodeName: string = 'deadCodeInjectionRootAstHostNode';
6✔
36

37
    /**
38
     * @type {number}
39
     */
40
    private static readonly maxNestedBlockStatementsCount: number = 4;
6✔
41

42
    /**
43
     * @type {number}
44
     */
45
    private static readonly minCollectedBlockStatementsCount: number = 5;
6✔
46

47
    /**
48
     * @type {NodeTransformer[]}
49
     */
50
    private static readonly transformersToRenameBlockScopeIdentifiers: NodeTransformer[] = [
6✔
51
        NodeTransformer.DeadCodeInjectionIdentifiersTransformer,
52
        NodeTransformer.LabeledStatementTransformer,
53
        NodeTransformer.ScopeIdentifiersTransformer
54
    ];
55

56
    /**
57
     * @type {WeakSet <BlockStatement>}
58
     */
59
    private readonly deadCodeInjectionRootAstHostNodeSet: WeakSet<ESTree.BlockStatement> = new WeakSet();
198,278✔
60

61
    /**
62
     * @type {ESTree.BlockStatement[]}
63
     */
64
    private readonly collectedBlockStatements: ESTree.BlockStatement[] = [];
198,278✔
65

66
    /**
67
     * @type {number}
68
     */
69
    private collectedBlockStatementsTotalLength: number = 0;
198,278✔
70

71
    /**
72
     * @type {TDeadNodeInjectionCustomNodeFactory}
73
     */
74
    private readonly deadCodeInjectionCustomNodeFactory: TDeadNodeInjectionCustomNodeFactory;
75

76
    /**
77
     * @type {INodeTransformersRunner}
78
     */
79
    private readonly transformersRunner: INodeTransformersRunner;
80

81
    /**
82
     * @param {TDeadNodeInjectionCustomNodeFactory} deadCodeInjectionCustomNodeFactory
83
     * @param {INodeTransformersRunner} transformersRunner
84
     * @param {IRandomGenerator} randomGenerator
85
     * @param {IOptions} options
86
     */
87
    public constructor(
88
        @inject(ServiceIdentifiers.Factory__IDeadCodeInjectionCustomNode)
89
        deadCodeInjectionCustomNodeFactory: TDeadNodeInjectionCustomNodeFactory,
90
        @inject(ServiceIdentifiers.INodeTransformersRunner) transformersRunner: INodeTransformersRunner,
91
        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
92
        @inject(ServiceIdentifiers.IOptions) options: IOptions
93
    ) {
94
        super(randomGenerator, options);
198,278✔
95

96
        this.deadCodeInjectionCustomNodeFactory = deadCodeInjectionCustomNodeFactory;
198,278✔
97
        this.transformersRunner = transformersRunner;
198,278✔
98
    }
99

100
    /**
101
     * @param {Node} targetNode
102
     * @returns {boolean}
103
     */
104
    // eslint-disable-next-line complexity
105
    private static isProhibitedNodeInsideCollectedBlockStatement(targetNode: ESTree.Node): boolean {
106
        return (
4,682,795✔
107
            NodeGuards.isFunctionDeclarationNode(targetNode) || // can break code on strict mode
43,820,968✔
108
            NodeGuards.isBreakStatementNode(targetNode) ||
109
            NodeGuards.isContinueStatementNode(targetNode) ||
110
            NodeGuards.isAwaitExpressionNode(targetNode) ||
111
            NodeGuards.isYieldExpressionNode(targetNode) ||
112
            NodeGuards.isSuperNode(targetNode) ||
113
            (NodeGuards.isForOfStatementNode(targetNode) && targetNode.await) ||
114
            NodeGuards.isPrivateIdentifierNode(targetNode) ||
115
            // `arguments` is not allowed in class field initializers or static initialization blocks
116
            (NodeGuards.isIdentifierNode(targetNode) && targetNode.name === 'arguments')
117
        );
118
    }
119

120
    /**
121
     * @param {Node} targetNode
122
     * @returns {boolean}
123
     */
124
    private static isScopeHoistingFunctionDeclaration(targetNode: ESTree.Node): boolean {
125
        if (!NodeGuards.isFunctionDeclarationNode(targetNode)) {
15,786,997✔
126
            return false;
15,675,871✔
127
        }
128

129
        const scopeNode: TNodeWithStatements = NodeStatementUtils.getScopeOfNode(targetNode);
111,126✔
130
        const scopeBody: ESTree.Statement[] = !NodeGuards.isSwitchCaseNode(scopeNode)
111,126✔
131
            ? <ESTree.Statement[]>scopeNode.body
111,126!
132
            : scopeNode.consequent;
133
        const indexInScope: number = scopeBody.indexOf(targetNode);
111,126✔
134

135
        if (indexInScope === 0) {
111,126✔
136
            return false;
111,084✔
137
        }
138

139
        const slicedBody: ESTree.Statement[] = scopeBody.slice(0, indexInScope);
42✔
140
        const hostBlockStatementNode: ESTree.BlockStatement = NodeFactory.blockStatementNode(slicedBody);
42✔
141
        const functionDeclarationName: string = targetNode.id.name;
42✔
142

143
        let isScopeHoistedFunctionDeclaration: boolean = false;
42✔
144

145
        estraverse.traverse(hostBlockStatementNode, {
42✔
146
            enter: (node: ESTree.Node): estraverse.VisitorOption | void => {
147
                if (NodeGuards.isIdentifierNode(node) && node.name === functionDeclarationName) {
3,914✔
148
                    isScopeHoistedFunctionDeclaration = true;
42✔
149

150
                    return estraverse.VisitorOption.Break;
42✔
151
                }
152
            }
153
        });
154

155
        return isScopeHoistedFunctionDeclaration;
42✔
156
    }
157

158
    /**
159
     * @param {BlockStatement} blockStatementNode
160
     * @returns {boolean}
161
     */
162
    private static isValidCollectedBlockStatementNode(blockStatementNode: ESTree.BlockStatement): boolean {
163
        if (!blockStatementNode.body.length) {
196,987✔
164
            return false;
3,942✔
165
        }
166

167
        let nestedBlockStatementsCount: number = 0;
193,045✔
168
        let isValidBlockStatementNode: boolean = true;
193,045✔
169

170
        estraverse.traverse(blockStatementNode, {
193,045✔
171
            enter: (node: ESTree.Node): estraverse.VisitorOption | void => {
172
                if (NodeGuards.isBlockStatementNode(node)) {
4,701,297✔
173
                    nestedBlockStatementsCount++;
327,870✔
174
                }
175

176
                if (
4,701,297✔
177
                    nestedBlockStatementsCount > DeadCodeInjectionTransformer.maxNestedBlockStatementsCount ||
14,034,840✔
178
                    DeadCodeInjectionTransformer.isProhibitedNodeInsideCollectedBlockStatement(node) ||
179
                    DeadCodeInjectionTransformer.isScopeHoistingFunctionDeclaration(node)
180
                ) {
181
                    isValidBlockStatementNode = false;
50,549✔
182

183
                    return estraverse.VisitorOption.Break;
50,549✔
184
                }
185
            }
186
        });
187

188
        return isValidBlockStatementNode;
193,045✔
189
    }
190

191
    /**
192
     * @param {BlockStatement} blockStatementNode
193
     * @param {Node} parentNode
194
     * @returns {boolean}
195
     */
196
    private static isValidWrappedBlockStatementNode(
197
        blockStatementNode: ESTree.BlockStatement,
198
        parentNode: ESTree.Node
199
    ): boolean {
200
        /**
201
         * Special case for ignoring all EvalHost nodes that are added by EvalCallExpressionTransformer
202
         * So, all content of eval expressions should not be affected by dead code injection
203
         */
204
        if (NodeMetadata.isEvalHostNode(parentNode)) {
145,452✔
205
            return false;
42✔
206
        }
207

208
        if (!blockStatementNode.body.length) {
145,410✔
209
            return false;
2,184✔
210
        }
211

212
        let isValidBlockStatementNode: boolean = true;
143,226✔
213

214
        estraverse.traverse(blockStatementNode, {
143,226✔
215
            enter: (node: ESTree.Node): estraverse.VisitorOption | void => {
216
                if (DeadCodeInjectionTransformer.isScopeHoistingFunctionDeclaration(node)) {
11,136,249✔
217
                    isValidBlockStatementNode = false;
42✔
218

219
                    return estraverse.VisitorOption.Break;
42✔
220
                }
221
            }
222
        });
223

224
        if (!isValidBlockStatementNode) {
143,226✔
225
            return false;
42✔
226
        }
227

228
        const parentNodeWithStatements: TNodeWithStatements =
229
            NodeStatementUtils.getParentNodeWithStatements(blockStatementNode);
143,184✔
230

231
        return parentNodeWithStatements.type !== NodeType.Program;
143,184✔
232
    }
233

234
    /**
235
     * @param {NodeTransformationStage} nodeTransformationStage
236
     * @returns {IVisitor | null}
237
     */
238
    public getVisitor(nodeTransformationStage: NodeTransformationStage): IVisitor | null {
239
        switch (nodeTransformationStage) {
1,627,302✔
240
            case NodeTransformationStage.DeadCodeInjection:
1,627,302✔
241
                return {
20,594✔
242
                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
243
                        if (parentNode && NodeGuards.isProgramNode(node)) {
4,665,894✔
244
                            this.prepareNode(node, parentNode);
10,297✔
245

246
                            return node;
10,297✔
247
                        }
248
                    },
249
                    leave: (
250
                        node: ESTree.Node,
251
                        parentNode: ESTree.Node | null
252
                    ): ESTree.Node | estraverse.VisitorOption | undefined => {
253
                        if (parentNode && NodeGuards.isBlockStatementNode(node)) {
4,601,161✔
254
                            return this.transformNode(node, parentNode);
155,671✔
255
                        }
256
                    }
257
                };
258

259
            case NodeTransformationStage.StringArray:
260
                return {
396,292✔
261
                    enter: (
262
                        node: ESTree.Node,
263
                        parentNode: ESTree.Node | null
264
                    ): ESTree.Node | estraverse.VisitorOption | undefined => {
265
                        if (parentNode && this.isDeadCodeInjectionRootAstHostNode(node)) {
53,375,735✔
266
                            return this.restoreNode(node, parentNode);
139,786✔
267
                        }
268
                    }
269
                };
270

271
            default:
272
                return null;
1,210,416✔
273
        }
274
    }
275

276
    /**
277
     * @param {NodeGuards} programNode
278
     * @param {NodeGuards} parentNode
279
     */
280
    public prepareNode(programNode: ESTree.Node, parentNode: ESTree.Node): void {
281
        estraverse.traverse(programNode, {
10,297✔
282
            enter: (node: ESTree.Node): void => {
283
                if (!NodeGuards.isBlockStatementNode(node)) {
5,717,993✔
284
                    return;
5,521,006✔
285
                }
286

287
                const clonedBlockStatementNode: ESTree.BlockStatement = NodeUtils.clone(node);
196,987✔
288

289
                if (!DeadCodeInjectionTransformer.isValidCollectedBlockStatementNode(clonedBlockStatementNode)) {
196,987✔
290
                    return;
54,491✔
291
                }
292

293
                /**
294
                 * We should transform identifiers in the dead code block statement to avoid conflicts with original code
295
                 */
296
                const transformedBlockStatementNode: ESTree.BlockStatement =
297
                    this.makeClonedBlockStatementNodeUnique(clonedBlockStatementNode);
142,496✔
298

299
                this.collectedBlockStatements.push(transformedBlockStatementNode);
142,496✔
300
            }
301
        });
302

303
        this.collectedBlockStatementsTotalLength = this.collectedBlockStatements.length;
10,297✔
304
    }
305

306
    /**
307
     * @param {BlockStatement} blockStatementNode
308
     * @param {NodeGuards} parentNode
309
     * @returns {NodeGuards | VisitorOption}
310
     */
311
    public transformNode(
312
        blockStatementNode: ESTree.BlockStatement,
313
        parentNode: ESTree.Node
314
    ): ESTree.Node | estraverse.VisitorOption {
315
        const canBreakTraverse: boolean =
316
            !this.collectedBlockStatements.length ||
155,671✔
317
            this.collectedBlockStatementsTotalLength < DeadCodeInjectionTransformer.minCollectedBlockStatementsCount;
318

319
        if (canBreakTraverse) {
155,671✔
320
            return estraverse.VisitorOption.Break;
10,219✔
321
        }
322

323
        if (
145,452✔
324
            this.randomGenerator.getMathRandom() > this.options.deadCodeInjectionThreshold ||
290,904✔
325
            !DeadCodeInjectionTransformer.isValidWrappedBlockStatementNode(blockStatementNode, parentNode)
326
        ) {
327
            return blockStatementNode;
5,666✔
328
        }
329

330
        const minInteger: number = 0;
139,786✔
331
        const maxInteger: number = this.collectedBlockStatements.length - 1;
139,786✔
332
        const randomIndex: number = this.randomGenerator.getRandomInteger(minInteger, maxInteger);
139,786✔
333
        const randomBlockStatementNode: ESTree.BlockStatement = this.collectedBlockStatements.splice(randomIndex, 1)[0];
139,786✔
334
        const isDuplicateBlockStatementNodes: boolean = randomBlockStatementNode === blockStatementNode;
139,786✔
335

336
        if (isDuplicateBlockStatementNodes) {
139,786!
UNCOV
337
            return blockStatementNode;
×
338
        }
339

340
        return this.replaceBlockStatementNode(blockStatementNode, randomBlockStatementNode, parentNode);
139,786✔
341
    }
342

343
    /**
344
     * @param {FunctionExpression} deadCodeInjectionRootAstHostNode
345
     * @param {Node} parentNode
346
     * @returns {Node}
347
     */
348
    public restoreNode(deadCodeInjectionRootAstHostNode: ESTree.BlockStatement, parentNode: ESTree.Node): ESTree.Node {
349
        const hostNodeFirstStatement: ESTree.Statement = deadCodeInjectionRootAstHostNode.body[0];
139,786✔
350

351
        if (!NodeGuards.isFunctionDeclarationNode(hostNodeFirstStatement)) {
139,786!
UNCOV
352
            throw new Error(
×
353
                'Wrong dead code injection root AST host node. Host node should contain `FunctionDeclaration` node'
354
            );
355
        }
356

357
        return hostNodeFirstStatement.body;
139,786✔
358
    }
359

360
    /**
361
     * @param {Node} node
362
     * @returns {boolean}
363
     */
364
    private isDeadCodeInjectionRootAstHostNode(node: ESTree.Node): node is ESTree.BlockStatement {
365
        const isDeadCodeInjectionRootAstHostNode =
366
            NodeGuards.isBlockStatementNode(node) && this.deadCodeInjectionRootAstHostNodeSet.has(node);
53,375,735✔
367

368
        if (isDeadCodeInjectionRootAstHostNode) {
53,375,735✔
369
            this.deadCodeInjectionRootAstHostNodeSet.delete(node);
139,786✔
370
        }
371

372
        return isDeadCodeInjectionRootAstHostNode;
53,375,735✔
373
    }
374

375
    /**
376
     * Make all identifiers in cloned block statement unique
377
     *
378
     * @param {BlockStatement} clonedBlockStatementNode
379
     * @returns {BlockStatement}
380
     */
381
    private makeClonedBlockStatementNodeUnique(clonedBlockStatementNode: ESTree.BlockStatement): ESTree.BlockStatement {
382
        // should wrap cloned block statement node into function node for correct scope encapsulation
383
        const hostNode: ESTree.Program = NodeFactory.programNode([
142,496✔
384
            NodeFactory.expressionStatementNode(NodeFactory.functionExpressionNode([], clonedBlockStatementNode))
385
        ]);
386

387
        NodeUtils.parentizeAst(hostNode);
142,496✔
388
        NodeUtils.parentizeNode(hostNode, hostNode);
142,496✔
389

390
        this.transformersRunner.transform(
142,496✔
391
            hostNode,
392
            DeadCodeInjectionTransformer.transformersToRenameBlockScopeIdentifiers,
393
            NodeTransformationStage.RenameIdentifiers
394
        );
395

396
        return clonedBlockStatementNode;
142,496✔
397
    }
398

399
    /**
400
     * @param {BlockStatement} blockStatementNode
401
     * @param {BlockStatement} randomBlockStatementNode
402
     * @param {Node} parentNode
403
     * @returns {BlockStatement}
404
     */
405
    private replaceBlockStatementNode(
406
        blockStatementNode: ESTree.BlockStatement,
407
        randomBlockStatementNode: ESTree.BlockStatement,
408
        parentNode: ESTree.Node
409
    ): ESTree.BlockStatement {
410
        /**
411
         * Should wrap original random block statement node into the parent block statement node (ast root host node)
412
         * with function declaration node. This function declaration node will create block scope for all identifiers
413
         * inside random block statement node and this identifiers won't affect identifiers of the rest AST tree.
414
         */
415
        const deadCodeInjectionRootAstHostNode: ESTree.BlockStatement = NodeFactory.blockStatementNode([
139,786✔
416
            NodeFactory.functionDeclarationNode(
417
                DeadCodeInjectionTransformer.deadCodeInjectionRootAstHostNodeName,
418
                [],
419
                randomBlockStatementNode
420
            )
421
        ]);
422

423
        /**
424
         * Should store that host node and then extract random block statement node on the `finalizing` stage
425
         */
426
        this.deadCodeInjectionRootAstHostNodeSet.add(deadCodeInjectionRootAstHostNode);
139,786✔
427

428
        const blockStatementDeadCodeInjectionCustomNode: ICustomNode<
429
            TInitialData<BlockStatementDeadCodeInjectionNode>
430
        > = this.deadCodeInjectionCustomNodeFactory(DeadCodeInjectionCustomNode.BlockStatementDeadCodeInjectionNode);
139,786✔
431

432
        blockStatementDeadCodeInjectionCustomNode.initialize(blockStatementNode, deadCodeInjectionRootAstHostNode);
139,786✔
433

434
        const newBlockStatementNode: ESTree.BlockStatement = <ESTree.BlockStatement>(
435
            blockStatementDeadCodeInjectionCustomNode.getNode()[0]
139,786✔
436
        );
437

438
        NodeUtils.parentizeNode(newBlockStatementNode, parentNode);
139,786✔
439

440
        return newBlockStatementNode;
139,786✔
441
    }
442
}
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