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

javascript-obfuscator / javascript-obfuscator / 19907815758

03 Dec 2025 08:27PM UTC coverage: 97.319%. Remained the same
19907815758

push

github

sanex3339
Adjust precommit hook

1770 of 1891 branches covered (93.6%)

Branch coverage included in aggregate %.

5671 of 5755 relevant lines covered (98.54%)

34102965.58 hits per line

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

97.04
/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();
197,808✔
60

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

66
    /**
67
     * @type {number}
68
     */
69
    private collectedBlockStatementsTotalLength: number = 0;
197,808✔
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);
197,808✔
95

96
        this.deadCodeInjectionCustomNodeFactory = deadCodeInjectionCustomNodeFactory;
197,808✔
97
        this.transformersRunner = transformersRunner;
197,808✔
98
    }
99

100
    /**
101
     * @param {Node} targetNode
102
     * @returns {boolean}
103
     */
104
    private static isProhibitedNodeInsideCollectedBlockStatement(targetNode: ESTree.Node): boolean {
105
        return (
5,914,913✔
106
            NodeGuards.isFunctionDeclarationNode(targetNode) || // can break code on strict mode
47,265,142✔
107
            NodeGuards.isBreakStatementNode(targetNode) ||
108
            NodeGuards.isContinueStatementNode(targetNode) ||
109
            NodeGuards.isAwaitExpressionNode(targetNode) ||
110
            NodeGuards.isYieldExpressionNode(targetNode) ||
111
            NodeGuards.isSuperNode(targetNode) ||
112
            (NodeGuards.isForOfStatementNode(targetNode) && targetNode.await) ||
113
            NodeGuards.isPrivateIdentifierNode(targetNode)
114
        );
115
    }
116

117
    /**
118
     * @param {Node} targetNode
119
     * @returns {boolean}
120
     */
121
    private static isScopeHoistingFunctionDeclaration(targetNode: ESTree.Node): boolean {
122
        if (!NodeGuards.isFunctionDeclarationNode(targetNode)) {
19,208,864✔
123
            return false;
19,072,233✔
124
        }
125

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

132
        if (indexInScope === 0) {
136,631✔
133
            return false;
136,589✔
134
        }
135

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

140
        let isScopeHoistedFunctionDeclaration: boolean = false;
42✔
141

142
        estraverse.traverse(hostBlockStatementNode, {
42✔
143
            enter: (node: ESTree.Node): estraverse.VisitorOption | void => {
144
                if (NodeGuards.isIdentifierNode(node) && node.name === functionDeclarationName) {
4,059✔
145
                    isScopeHoistedFunctionDeclaration = true;
42✔
146

147
                    return estraverse.VisitorOption.Break;
42✔
148
                }
149
            }
150
        });
151

152
        return isScopeHoistedFunctionDeclaration;
42✔
153
    }
154

155
    /**
156
     * @param {BlockStatement} blockStatementNode
157
     * @returns {boolean}
158
     */
159
    private static isValidCollectedBlockStatementNode(blockStatementNode: ESTree.BlockStatement): boolean {
160
        if (!blockStatementNode.body.length) {
194,953✔
161
            return false;
3,798✔
162
        }
163

164
        let nestedBlockStatementsCount: number = 0;
191,155✔
165
        let isValidBlockStatementNode: boolean = true;
191,155✔
166

167
        estraverse.traverse(blockStatementNode, {
191,155✔
168
            enter: (node: ESTree.Node): estraverse.VisitorOption | void => {
169
                if (NodeGuards.isBlockStatementNode(node)) {
5,940,039✔
170
                    nestedBlockStatementsCount++;
342,098✔
171
                }
172

173
                if (
5,940,039✔
174
                    nestedBlockStatementsCount > DeadCodeInjectionTransformer.maxNestedBlockStatementsCount ||
17,758,681✔
175
                    DeadCodeInjectionTransformer.isProhibitedNodeInsideCollectedBlockStatement(node) ||
176
                    DeadCodeInjectionTransformer.isScopeHoistingFunctionDeclaration(node)
177
                ) {
178
                    isValidBlockStatementNode = false;
36,310✔
179

180
                    return estraverse.VisitorOption.Break;
36,310✔
181
                }
182
            }
183
        });
184

185
        return isValidBlockStatementNode;
191,155✔
186
    }
187

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

205
        if (!blockStatementNode.body.length) {
160,947✔
206
            return false;
3,066✔
207
        }
208

209
        let isValidBlockStatementNode: boolean = true;
157,881✔
210

211
        estraverse.traverse(blockStatementNode, {
157,881✔
212
            enter: (node: ESTree.Node): estraverse.VisitorOption | void => {
213
                if (DeadCodeInjectionTransformer.isScopeHoistingFunctionDeclaration(node)) {
13,305,135✔
214
                    isValidBlockStatementNode = false;
42✔
215

216
                    return estraverse.VisitorOption.Break;
42✔
217
                }
218
            }
219
        });
220

221
        if (!isValidBlockStatementNode) {
157,881✔
222
            return false;
42✔
223
        }
224

225
        const parentNodeWithStatements: TNodeWithStatements =
226
            NodeStatementUtils.getParentNodeWithStatements(blockStatementNode);
157,839✔
227

228
        return parentNodeWithStatements.type !== NodeType.Program;
157,839✔
229
    }
230

231
    /**
232
     * @param {NodeTransformationStage} nodeTransformationStage
233
     * @returns {IVisitor | null}
234
     */
235
    public getVisitor(nodeTransformationStage: NodeTransformationStage): IVisitor | null {
236
        switch (nodeTransformationStage) {
1,623,096✔
237
            case NodeTransformationStage.DeadCodeInjection:
1,623,096✔
238
                return {
20,772✔
239
                    enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
240
                        if (parentNode && NodeGuards.isProgramNode(node)) {
5,084,255✔
241
                            this.prepareNode(node, parentNode);
10,386✔
242

243
                            return node;
10,386✔
244
                        }
245
                    },
246
                    leave: (
247
                        node: ESTree.Node,
248
                        parentNode: ESTree.Node | null
249
                    ): ESTree.Node | estraverse.VisitorOption | undefined => {
250
                        if (parentNode && NodeGuards.isBlockStatementNode(node)) {
5,024,951✔
251
                            return this.transformNode(node, parentNode);
171,261✔
252
                        }
253
                    }
254
                };
255

256
            case NodeTransformationStage.StringArray:
257
                return {
395,352✔
258
                    enter: (
259
                        node: ESTree.Node,
260
                        parentNode: ESTree.Node | null
261
                    ): ESTree.Node | estraverse.VisitorOption | undefined => {
262
                        if (parentNode && this.isDeadCodeInjectionRootAstHostNode(node)) {
56,239,799✔
263
                            return this.restoreNode(node, parentNode);
154,103✔
264
                        }
265
                    }
266
                };
267

268
            default:
269
                return null;
1,206,972✔
270
        }
271
    }
272

273
    /**
274
     * @param {NodeGuards} programNode
275
     * @param {NodeGuards} parentNode
276
     */
277
    public prepareNode(programNode: ESTree.Node, parentNode: ESTree.Node): void {
278
        estraverse.traverse(programNode, {
10,386✔
279
            enter: (node: ESTree.Node): void => {
280
                if (!NodeGuards.isBlockStatementNode(node)) {
5,628,806✔
281
                    return;
5,433,853✔
282
                }
283

284
                const clonedBlockStatementNode: ESTree.BlockStatement = NodeUtils.clone(node);
194,953✔
285

286
                if (!DeadCodeInjectionTransformer.isValidCollectedBlockStatementNode(clonedBlockStatementNode)) {
194,953✔
287
                    return;
40,108✔
288
                }
289

290
                /**
291
                 * We should transform identifiers in the dead code block statement to avoid conflicts with original code
292
                 */
293
                const transformedBlockStatementNode: ESTree.BlockStatement =
294
                    this.makeClonedBlockStatementNodeUnique(clonedBlockStatementNode);
154,845✔
295

296
                this.collectedBlockStatements.push(transformedBlockStatementNode);
154,845✔
297
            }
298
        });
299

300
        this.collectedBlockStatementsTotalLength = this.collectedBlockStatements.length;
10,386✔
301
    }
302

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

316
        if (canBreakTraverse) {
171,261✔
317
            return estraverse.VisitorOption.Break;
10,308✔
318
        }
319

320
        if (
160,953✔
321
            this.randomGenerator.getMathRandom() > this.options.deadCodeInjectionThreshold ||
321,906✔
322
            !DeadCodeInjectionTransformer.isValidWrappedBlockStatementNode(blockStatementNode, parentNode)
323
        ) {
324
            return blockStatementNode;
6,850✔
325
        }
326

327
        const minInteger: number = 0;
154,103✔
328
        const maxInteger: number = this.collectedBlockStatements.length - 1;
154,103✔
329
        const randomIndex: number = this.randomGenerator.getRandomInteger(minInteger, maxInteger);
154,103✔
330
        const randomBlockStatementNode: ESTree.BlockStatement = this.collectedBlockStatements.splice(randomIndex, 1)[0];
154,103✔
331
        const isDuplicateBlockStatementNodes: boolean = randomBlockStatementNode === blockStatementNode;
154,103✔
332

333
        if (isDuplicateBlockStatementNodes) {
154,103!
334
            return blockStatementNode;
×
335
        }
336

337
        return this.replaceBlockStatementNode(blockStatementNode, randomBlockStatementNode, parentNode);
154,103✔
338
    }
339

340
    /**
341
     * @param {FunctionExpression} deadCodeInjectionRootAstHostNode
342
     * @param {Node} parentNode
343
     * @returns {Node}
344
     */
345
    public restoreNode(deadCodeInjectionRootAstHostNode: ESTree.BlockStatement, parentNode: ESTree.Node): ESTree.Node {
346
        const hostNodeFirstStatement: ESTree.Statement = deadCodeInjectionRootAstHostNode.body[0];
154,103✔
347

348
        if (!NodeGuards.isFunctionDeclarationNode(hostNodeFirstStatement)) {
154,103!
349
            throw new Error(
×
350
                'Wrong dead code injection root AST host node. Host node should contain `FunctionDeclaration` node'
351
            );
352
        }
353

354
        return hostNodeFirstStatement.body;
154,103✔
355
    }
356

357
    /**
358
     * @param {Node} node
359
     * @returns {boolean}
360
     */
361
    private isDeadCodeInjectionRootAstHostNode(node: ESTree.Node): node is ESTree.BlockStatement {
362
        const isDeadCodeInjectionRootAstHostNode =
363
            NodeGuards.isBlockStatementNode(node) && this.deadCodeInjectionRootAstHostNodeSet.has(node);
56,239,799✔
364

365
        if (isDeadCodeInjectionRootAstHostNode) {
56,239,799✔
366
            this.deadCodeInjectionRootAstHostNodeSet.delete(node);
154,103✔
367
        }
368

369
        return isDeadCodeInjectionRootAstHostNode;
56,239,799✔
370
    }
371

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

384
        NodeUtils.parentizeAst(hostNode);
154,845✔
385
        NodeUtils.parentizeNode(hostNode, hostNode);
154,845✔
386

387
        this.transformersRunner.transform(
154,845✔
388
            hostNode,
389
            DeadCodeInjectionTransformer.transformersToRenameBlockScopeIdentifiers,
390
            NodeTransformationStage.RenameIdentifiers
391
        );
392

393
        return clonedBlockStatementNode;
154,845✔
394
    }
395

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

420
        /**
421
         * Should store that host node and then extract random block statement node on the `finalizing` stage
422
         */
423
        this.deadCodeInjectionRootAstHostNodeSet.add(deadCodeInjectionRootAstHostNode);
154,103✔
424

425
        const blockStatementDeadCodeInjectionCustomNode: ICustomNode<
426
            TInitialData<BlockStatementDeadCodeInjectionNode>
427
        > = this.deadCodeInjectionCustomNodeFactory(DeadCodeInjectionCustomNode.BlockStatementDeadCodeInjectionNode);
154,103✔
428

429
        blockStatementDeadCodeInjectionCustomNode.initialize(blockStatementNode, deadCodeInjectionRootAstHostNode);
154,103✔
430

431
        const newBlockStatementNode: ESTree.BlockStatement = <ESTree.BlockStatement>(
432
            blockStatementDeadCodeInjectionCustomNode.getNode()[0]
154,103✔
433
        );
434

435
        NodeUtils.parentizeNode(newBlockStatementNode, parentNode);
154,103✔
436

437
        return newBlockStatementNode;
154,103✔
438
    }
439
}
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