• 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

98.35
/src/node-transformers/control-flow-transformers/FunctionControlFlowTransformer.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 { TControlFlowCustomNodeFactory } from '../../types/container/custom-nodes/TControlFlowCustomNodeFactory';
8
import { TControlFlowReplacerFactory } from '../../types/container/node-transformers/TControlFlowReplacerFactory';
9
import { TControlFlowStorageFactory } from '../../types/container/node-transformers/TControlFlowStorageFactory';
10
import { TControlFlowStorageFactoryCreator } from '../../types/container/node-transformers/TControlFlowStorageFactoryCreator';
11
import { TInitialData } from '../../types/TInitialData';
12
import { TNodeWithStatements } from '../../types/node/TNodeWithStatements';
13

14
import { IControlFlowStorage } from '../../interfaces/storages/control-flow-transformers/IControlFlowStorage';
15
import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
16
import { IOptions } from '../../interfaces/options/IOptions';
17
import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
18
import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
19

20
import { ControlFlowCustomNode } from '../../enums/custom-nodes/ControlFlowCustomNode';
6✔
21
import { ControlFlowReplacer } from '../../enums/node-transformers/control-flow-transformers/control-flow-replacers/ControlFlowReplacer';
6✔
22
import { ControlFlowStorage } from '../../enums/storages/ControlFlowStorage';
6✔
23
import { NodeType } from '../../enums/node/NodeType';
6✔
24
import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
6✔
25

26
import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
6✔
27
import { ControlFlowStorageNode } from '../../custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/ControlFlowStorageNode';
28
import { NodeAppender } from '../../node/NodeAppender';
6✔
29
import { NodeGuards } from '../../node/NodeGuards';
6✔
30
import { NodeMetadata } from '../../node/NodeMetadata';
6✔
31
import { NodeStatementUtils } from '../../node/NodeStatementUtils';
6✔
32
import { NodeUtils } from '../../node/NodeUtils';
6✔
33

34
@injectable()
35
export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
6✔
36
    /**
37
     * @type {number}
38
     */
39
    private static readonly hostNodeSearchMinDepth: number = 0;
6✔
40

41
    /**
42
     * @type {number}
43
     */
44
    private static readonly hostNodeSearchMaxDepth: number = 2;
6✔
45

46
    /**
47
     * @type {Map <string, ControlFlowReplacer>}
48
     */
49
    protected readonly controlFlowReplacersMap: Map<string, ControlFlowReplacer> = new Map([
395,616✔
50
        [NodeType.BinaryExpression, ControlFlowReplacer.BinaryExpressionControlFlowReplacer],
51
        [NodeType.CallExpression, ControlFlowReplacer.CallExpressionControlFlowReplacer],
52
        [NodeType.LogicalExpression, ControlFlowReplacer.LogicalExpressionControlFlowReplacer],
53
        [NodeType.Literal, ControlFlowReplacer.StringLiteralControlFlowReplacer]
54
    ]);
55

56
    /**
57
     * @type {WeakMap<TNodeWithStatements, IControlFlowStorage>}
58
     */
59
    protected readonly controlFlowData: WeakMap<TNodeWithStatements, IControlFlowStorage> = new WeakMap();
395,616✔
60

61
    /**
62
     * @type {WeakMap<TNodeWithStatements, VariableDeclaration>}
63
     */
64
    protected readonly hostNodesWithControlFlowNode: WeakMap<TNodeWithStatements, ESTree.VariableDeclaration> =
395,616✔
65
        new WeakMap();
66

67
    /**
68
     * @type {TControlFlowReplacerFactory}
69
     */
70
    protected readonly controlFlowReplacerFactory: TControlFlowReplacerFactory;
71

72
    /**
73
     * @type {TControlFlowStorageFactory}
74
     */
75
    protected controlFlowStorageFactory: TControlFlowStorageFactory;
76

77
    /**
78
     * @type {TControlFlowCustomNodeFactory}
79
     */
80
    protected readonly controlFlowCustomNodeFactory: TControlFlowCustomNodeFactory;
81

82
    /**
83
     * @type {WeakSet<ESTree.Function>}
84
     */
85
    protected readonly visitedFunctionNodes: WeakSet<ESTree.Function> = new WeakSet();
395,616✔
86

87
    /**
88
     * @param {TControlFlowStorageFactoryCreator} controlFlowStorageFactoryCreator
89
     * @param {TControlFlowReplacerFactory} controlFlowReplacerFactory
90
     * @param {TControlFlowCustomNodeFactory} controlFlowCustomNodeFactory
91
     * @param {IRandomGenerator} randomGenerator
92
     * @param {IOptions} options
93
     */
94
    public constructor(
95
        @inject(ServiceIdentifiers.Factory__TControlFlowStorage)
96
        controlFlowStorageFactoryCreator: TControlFlowStorageFactoryCreator,
97
        @inject(ServiceIdentifiers.Factory__IControlFlowReplacer)
98
        controlFlowReplacerFactory: TControlFlowReplacerFactory,
99
        @inject(ServiceIdentifiers.Factory__IControlFlowCustomNode)
100
        controlFlowCustomNodeFactory: TControlFlowCustomNodeFactory,
101
        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
102
        @inject(ServiceIdentifiers.IOptions) options: IOptions
103
    ) {
104
        super(randomGenerator, options);
395,616✔
105

106
        this.controlFlowStorageFactory = controlFlowStorageFactoryCreator(
395,616✔
107
            ControlFlowStorage.FunctionControlFlowStorage
108
        );
109
        this.controlFlowReplacerFactory = controlFlowReplacerFactory;
395,616✔
110
        this.controlFlowCustomNodeFactory = controlFlowCustomNodeFactory;
395,616✔
111
    }
112

113
    /**
114
     * @param {NodeTransformationStage} nodeTransformationStage
115
     * @returns {IVisitor | null}
116
     */
117
    public getVisitor(nodeTransformationStage: NodeTransformationStage): IVisitor | null {
118
        if (!this.options.controlFlowFlattening) {
1,446,684✔
119
            return null;
1,184,460✔
120
        }
121

122
        switch (nodeTransformationStage) {
262,224✔
123
            case NodeTransformationStage.ControlFlowFlattening:
262,224✔
124
                return {
63,300✔
125
                    leave: (
126
                        node: ESTree.Node,
127
                        parentNode: ESTree.Node | null
128
                    ): ESTree.Node | estraverse.VisitorOption | void => {
129
                        if (parentNode && NodeGuards.isFunctionNode(node)) {
11,017,594✔
130
                            return this.transformNode(node, parentNode);
323,663✔
131
                        }
132
                    }
133
                };
134

135
            default:
136
                return null;
198,924✔
137
        }
138
    }
139

140
    /**
141
     * @param {Function} functionNode
142
     * @param {Node} parentNode
143
     * @returns {Function}
144
     */
145
    public transformNode(functionNode: ESTree.Function, parentNode: ESTree.Node): ESTree.Function {
146
        this.visitedFunctionNodes.add(functionNode);
2,496,774✔
147

148
        if (!NodeGuards.isBlockStatementNode(functionNode.body)) {
2,496,774✔
149
            return functionNode;
12,390✔
150
        }
151

152
        const hostNode: TNodeWithStatements = this.getHostNode(functionNode.body);
2,484,384✔
153
        const controlFlowStorage: IControlFlowStorage = this.getControlFlowStorage(hostNode);
2,484,384✔
154

155
        this.transformFunctionBody(functionNode, controlFlowStorage);
2,484,384✔
156

157
        if (!controlFlowStorage.getLength()) {
2,484,384✔
158
            return functionNode;
332,231✔
159
        }
160

161
        const controlFlowStorageNode: ESTree.VariableDeclaration = this.getControlFlowStorageNode(controlFlowStorage);
2,152,153✔
162

163
        this.appendControlFlowStorageNode(hostNode, controlFlowStorageNode);
2,152,153✔
164

165
        return functionNode;
2,152,153✔
166
    }
167

168
    /**
169
     * @param {BlockStatement} functionNode
170
     * @param {IControlFlowStorage} controlFlowStorage
171
     */
172
    protected transformFunctionBody(functionNode: ESTree.Function, controlFlowStorage: IControlFlowStorage): void {
173
        estraverse.replace(functionNode.body, {
2,484,384✔
174
            enter: (node: ESTree.Node, parentNode: ESTree.Node | null): estraverse.VisitorOption | ESTree.Node =>
175
                this.transformFunctionBodyNode(node, parentNode, functionNode, controlFlowStorage)
223,363,541✔
176
        });
177
    }
178

179
    /**
180
     * @param {Node} node
181
     * @param {Node | null} parentNode
182
     * @param {Function} functionNode
183
     * @param {IControlFlowStorage} controlFlowStorage
184
     * @returns {ESTraverse.VisitorOption | Node}
185
     */
186
    protected transformFunctionBodyNode(
187
        node: ESTree.Node,
188
        parentNode: ESTree.Node | null,
189
        functionNode: ESTree.Function,
190
        controlFlowStorage: IControlFlowStorage
191
    ): estraverse.VisitorOption | ESTree.Node {
192
        const shouldSkipTraverse = !parentNode || NodeMetadata.isIgnoredNode(node) || this.isVisitedFunctionNode(node);
221,463,433✔
193

194
        if (shouldSkipTraverse) {
221,463,433✔
195
            return estraverse.VisitorOption.Skip;
2,442,476✔
196
        }
197

198
        const controlFlowReplacerName: ControlFlowReplacer | null = this.controlFlowReplacersMap.get(node.type) ?? null;
219,020,957✔
199

200
        if (!controlFlowReplacerName) {
219,020,957✔
201
            return node;
166,917,145✔
202
        }
203

204
        if (!this.isAllowedTransformationByThreshold()) {
52,103,812✔
205
            return node;
121,020✔
206
        }
207

208
        const replacedNode: ESTree.Node = this.controlFlowReplacerFactory(controlFlowReplacerName).replace(
51,982,792✔
209
            node,
210
            parentNode,
211
            controlFlowStorage
212
        );
213

214
        NodeUtils.parentizeNode(replacedNode, parentNode);
51,982,792✔
215

216
        return replacedNode;
51,982,792✔
217
    }
218

219
    /**
220
     * @param {BlockStatement} functionNodeBody
221
     * @returns {TNodeWithStatements}
222
     */
223
    protected getHostNode(functionNodeBody: ESTree.BlockStatement): TNodeWithStatements {
224
        const blockScopesOfNode: TNodeWithStatements[] =
225
            NodeStatementUtils.getParentNodesWithStatements(functionNodeBody);
2,484,384✔
226

227
        if (blockScopesOfNode.length === 1) {
2,484,384✔
228
            return functionNodeBody;
55,390✔
229
        } else {
230
            blockScopesOfNode.pop();
2,428,994✔
231
        }
232

233
        if (blockScopesOfNode.length > FunctionControlFlowTransformer.hostNodeSearchMinDepth) {
2,428,994✔
234
            blockScopesOfNode.splice(0, FunctionControlFlowTransformer.hostNodeSearchMinDepth);
2,428,994✔
235
        }
236

237
        if (blockScopesOfNode.length > FunctionControlFlowTransformer.hostNodeSearchMaxDepth) {
2,428,994✔
238
            blockScopesOfNode.length = FunctionControlFlowTransformer.hostNodeSearchMaxDepth;
1,482,274✔
239
        }
240

241
        return this.randomGenerator.getRandomGenerator().pickone(blockScopesOfNode);
2,428,994✔
242
    }
243

244
    /**
245
     * @param {TNodeWithStatements} hostNode
246
     * @returns {TControlFlowStorage}
247
     */
248
    protected getControlFlowStorage(hostNode: TNodeWithStatements): IControlFlowStorage {
249
        let controlFlowStorage: IControlFlowStorage;
250

251
        const hostControlFlowStorage: IControlFlowStorage | null = this.controlFlowData.get(hostNode) ?? null;
317,465✔
252

253
        if (!hostControlFlowStorage) {
317,465✔
254
            controlFlowStorage = this.controlFlowStorageFactory();
123,316✔
255
        } else {
256
            const existingControlFlowStorageNode: ESTree.VariableDeclaration | null =
257
                this.hostNodesWithControlFlowNode.get(hostNode) ?? null;
194,149✔
258

259
            if (existingControlFlowStorageNode) {
194,149✔
260
                NodeAppender.remove(hostNode, existingControlFlowStorageNode);
159,901✔
261
            }
262

263
            controlFlowStorage = hostControlFlowStorage;
194,149✔
264
        }
265

266
        this.controlFlowData.set(hostNode, controlFlowStorage);
317,465✔
267

268
        return controlFlowStorage;
317,465✔
269
    }
270

271
    /**
272
     * @param {IControlFlowStorage} controlFlowStorage
273
     * @returns {VariableDeclaration}
274
     */
275
    protected getControlFlowStorageNode(controlFlowStorage: IControlFlowStorage): ESTree.VariableDeclaration {
276
        const controlFlowStorageCustomNode: ICustomNode<TInitialData<ControlFlowStorageNode>> =
277
            this.controlFlowCustomNodeFactory(ControlFlowCustomNode.ControlFlowStorageNode);
2,152,153✔
278

279
        controlFlowStorageCustomNode.initialize(controlFlowStorage);
2,152,153✔
280

281
        const controlFlowStorageNode: ESTree.Node = controlFlowStorageCustomNode.getNode()[0];
2,152,153✔
282

283
        if (!NodeGuards.isVariableDeclarationNode(controlFlowStorageNode)) {
2,152,153!
284
            throw new Error(
×
285
                '`controlFlowStorageNode` should contain `VariableDeclaration` node with control flow storage object'
286
            );
287
        }
288

289
        return controlFlowStorageNode;
2,152,153✔
290
    }
291

292
    /**
293
     * @param {TNodeWithStatements} hostNode
294
     * @param {VariableDeclaration} controlFlowStorageNode
295
     */
296
    protected appendControlFlowStorageNode(
297
        hostNode: TNodeWithStatements,
298
        controlFlowStorageNode: ESTree.VariableDeclaration
299
    ): void {
300
        NodeUtils.parentizeAst(controlFlowStorageNode);
2,152,153✔
301
        NodeAppender.prepend(hostNode, [controlFlowStorageNode]);
2,152,153✔
302

303
        this.hostNodesWithControlFlowNode.set(hostNode, controlFlowStorageNode);
2,152,153✔
304
    }
305

306
    /**
307
     * @param {NodeGuards} node
308
     * @returns {boolean}
309
     */
310
    protected isVisitedFunctionNode(node: ESTree.Node): boolean {
311
        return NodeGuards.isFunctionNode(node) && this.visitedFunctionNodes.has(node);
221,460,973✔
312
    }
313

314
    /**
315
     * @returns {boolean}
316
     */
317
    protected isAllowedTransformationByThreshold(): boolean {
318
        return this.randomGenerator.getMathRandom() <= this.options.controlFlowFlatteningThreshold;
2,884,907✔
319
    }
320
}
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