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

javascript-obfuscator / javascript-obfuscator / 23398179525

22 Mar 2026 07:22AM UTC coverage: 96.329% (-0.03%) from 96.358%
23398179525

Pull #1390

github

web-flow
Merge 4c1463469 into bac513e73
Pull Request #1390: Fixed infinite loop / stack overflow when `reservedNames` patterns match all generated identifier names

1933 of 2104 branches covered (91.87%)

Branch coverage included in aggregate %.

19 of 19 new or added lines in 3 files covered. (100.0%)

2 existing lines in 2 files now uncovered.

5991 of 6122 relevant lines covered (97.86%)

5060090.66 hits per line

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

96.15
/src/generators/identifier-names-generators/AbstractIdentifierNamesGenerator.ts
1
import { inject, injectable } from 'inversify';
1✔
2
import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
1✔
3

4
import { TNodeWithLexicalScope } from '../../types/node/TNodeWithLexicalScope';
5

6
import { IIdentifierNamesGenerator } from '../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator';
7
import { IOptions } from '../../interfaces/options/IOptions';
8
import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
9

10
import { NodeGuards } from '../../node/NodeGuards';
1✔
11

12
@injectable()
13
export abstract class AbstractIdentifierNamesGenerator implements IIdentifierNamesGenerator {
1✔
14
    /**
15
     * @type {number}
16
     */
17
    private static readonly maxGenerationAttempts: number = 10000;
1✔
18

19
    /**
20
     * @type {IOptions}
21
     */
22
    protected readonly options: IOptions;
23

24
    /**
25
     * @type {IRandomGenerator}
26
     */
27
    protected readonly randomGenerator: IRandomGenerator;
28

29
    /**
30
     * @type {Set<string>}
31
     */
32
    protected readonly preservedNamesSet: Set<string> = new Set();
43,276✔
33

34
    /**
35
     * @type {WeakMap<TNodeWithLexicalScope, Set<string>>}
36
     */
37
    protected readonly lexicalScopesPreservedNamesMap: WeakMap<TNodeWithLexicalScope, Set<string>> = new WeakMap();
43,276✔
38

39
    /**
40
     * @type {Set<string>}
41
     */
42
    protected readonly allLexicalScopePreservedNames: Set<string> = new Set();
43,276✔
43

44
    /**
45
     * @param {IRandomGenerator} randomGenerator
46
     * @param {IOptions} options
47
     */
48
    public constructor(
49
        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
50
        @inject(ServiceIdentifiers.IOptions) options: IOptions
51
    ) {
52
        this.randomGenerator = randomGenerator;
43,276✔
53
        this.options = options;
43,276✔
54
    }
55

56
    /**
57
     * @param {TNodeWithLexicalScope} lexicalScopeNode
58
     * @param {number} nameLength
59
     * @returns {string}
60
     */
61
    public generate(lexicalScopeNode: TNodeWithLexicalScope, nameLength?: number): string {
62
        return NodeGuards.isProgramNode(lexicalScopeNode)
2,922✔
63
            ? this.generateForGlobalScope()
2,922✔
64
            : this.generateForLexicalScope(lexicalScopeNode);
65
    }
66

67
    /**
68
     * @param {string} name
69
     */
70
    public preserveName(name: string): void {
71
        this.preservedNamesSet.add(name);
5,801,000✔
72
    }
73

74
    /**
75
     * @param {string} name
76
     * @param {TNodeWithLexicalScope} lexicalScopeNode
77
     */
78
    public preserveNameForLexicalScope(name: string, lexicalScopeNode: TNodeWithLexicalScope): void {
79
        const preservedNamesForLexicalScopeSet: Set<string> =
80
            this.lexicalScopesPreservedNamesMap.get(lexicalScopeNode) ?? new Set();
2,432,370✔
81

82
        preservedNamesForLexicalScopeSet.add(name);
2,432,370✔
83

84
        this.lexicalScopesPreservedNamesMap.set(lexicalScopeNode, preservedNamesForLexicalScopeSet);
2,432,370✔
85

86
        this.allLexicalScopePreservedNames.add(name);
2,432,370✔
87
    }
88

89
    /**
90
     * @param {string} name
91
     * @returns {boolean}
92
     */
93
    public isValidIdentifierName(name: string): boolean {
94
        return !this.isReservedName(name) && !this.preservedNamesSet.has(name);
11,803,071✔
95
    }
96

97
    /**
98
     * @param {string} name
99
     * @param {TNodeWithLexicalScope[]} lexicalScopeNodes
100
     * @returns {boolean}
101
     */
102
    public isValidIdentifierNameInLexicalScopes(name: string, lexicalScopeNodes: TNodeWithLexicalScope[]): boolean {
103
        if (!this.isValidIdentifierName(name)) {
1,353,594✔
104
            return false;
14,377✔
105
        }
106

107
        for (const lexicalScope of lexicalScopeNodes) {
1,339,217✔
108
            const preservedNamesForLexicalScopeSet: Set<string> | null =
109
                this.lexicalScopesPreservedNamesMap.get(lexicalScope) ?? null;
3,571,007✔
110

111
            if (!preservedNamesForLexicalScopeSet) {
3,571,007✔
112
                continue;
663,662✔
113
            }
114

115
            if (preservedNamesForLexicalScopeSet.has(name)) {
2,907,345✔
116
                return false;
139,482✔
117
            }
118
        }
119

120
        return true;
1,199,735✔
121
    }
122

123
    /**
124
     * Checks if the name is valid and not preserved in any scope (global or lexical).
125
     * This is used for global scope name generation to avoid conflicts with
126
     * variables in any lexical scope that might shadow the global variable.
127
     *
128
     * @param {string} name
129
     * @returns {boolean}
130
     */
131
    public isValidIdentifierNameInAllScopes(name: string): boolean {
132
        if (!this.isValidIdentifierName(name)) {
93!
UNCOV
133
            return false;
×
134
        }
135

136
        // Check if the name is preserved in any lexical scope
137
        return !this.allLexicalScopePreservedNames.has(name);
93✔
138
    }
139

140
    /**
141
     * @param {number} attempts
142
     */
143
    protected checkGenerationAttempts(attempts: number): void {
144
        if (attempts > AbstractIdentifierNamesGenerator.maxGenerationAttempts) {
3,749,322✔
145
            throw new Error(
4✔
146
                'Unable to generate a valid identifier name. ' +
147
                    'This is likely caused by `reservedNames` patterns that match all generated names. ' +
148
                    'Please check your `reservedNames` option.'
149
            );
150
        }
151
    }
152

153
    /**
154
     * @param {string} name
155
     * @returns {boolean}
156
     */
157
    private isReservedName(name: string): boolean {
158
        return this.options.reservedNames.length
11,803,071✔
159
            ? this.options.reservedNames.some(
11,803,071✔
160
                  (reservedName: string) => new RegExp(reservedName, 'g').exec(name) !== null
765,192✔
161
              )
162
            : false;
163
    }
164

165
    /**
166
     * @param {number} nameLength
167
     * @returns {string}
168
     */
169
    public abstract generateForGlobalScope(nameLength?: number): string;
170

171
    /**
172
     * @param {number} nameLength
173
     * @returns {string}
174
     */
175
    public abstract generateForGlobalScopeWithAllScopesValidation(nameLength?: number): string;
176

177
    /**
178
     * @param {TNodeWithLexicalScope} lexicalScopeNode
179
     * @param {number} nameLength
180
     * @returns {string}
181
     */
182
    public abstract generateForLexicalScope(lexicalScopeNode: TNodeWithLexicalScope, nameLength?: number): string;
183

184
    /**
185
     * @param {string} label
186
     * @param {number} nameLength
187
     * @returns {string}
188
     */
189
    public abstract generateForLabel(label: string, nameLength?: number): string;
190

191
    /**
192
     * @param {number} nameLength
193
     * @returns {string}
194
     */
195
    public abstract generateNext(nameLength?: number): string;
196
}
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