webfuscator 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/constant.js +3 -4
- package/dist/analysis/constant.js.map +1 -1
- package/dist/analysis/inlineability.js +1 -1
- package/dist/analysis/inlineability.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/obfuscator.d.ts +2 -1
- package/dist/obfuscator.d.ts.map +1 -1
- package/dist/obfuscator.js +47 -12
- package/dist/obfuscator.js.map +1 -1
- package/dist/options.d.ts +17 -4
- package/dist/options.d.ts.map +1 -1
- package/dist/preparation/expand-destructuring.d.ts +3 -3
- package/dist/preparation/expand-destructuring.js +6 -6
- package/dist/preparation/expand-destructuring.js.map +1 -1
- package/dist/transforms/arrow-to-function.js +3 -5
- package/dist/transforms/arrow-to-function.js.map +1 -1
- package/dist/transforms/conceal-strings.d.ts +57 -0
- package/dist/transforms/conceal-strings.d.ts.map +1 -0
- package/dist/transforms/conceal-strings.js +361 -0
- package/dist/transforms/conceal-strings.js.map +1 -0
- package/dist/transforms/const-let-to-var.d.ts +3 -3
- package/dist/transforms/const-let-to-var.d.ts.map +1 -1
- package/dist/transforms/const-let-to-var.js +36 -9
- package/dist/transforms/const-let-to-var.js.map +1 -1
- package/dist/transforms/const-to-let.d.ts +2 -3
- package/dist/transforms/const-to-let.d.ts.map +1 -1
- package/dist/transforms/const-to-let.js +3 -5
- package/dist/transforms/const-to-let.js.map +1 -1
- package/dist/transforms/fold-builtin-methods.d.ts.map +1 -1
- package/dist/transforms/fold-builtin-methods.js +48 -10
- package/dist/transforms/fold-builtin-methods.js.map +1 -1
- package/dist/transforms/fold-constants.js +3 -0
- package/dist/transforms/fold-constants.js.map +1 -1
- package/dist/transforms/hoist-variables.d.ts +30 -0
- package/dist/transforms/hoist-variables.d.ts.map +1 -0
- package/dist/transforms/hoist-variables.js +224 -0
- package/dist/transforms/hoist-variables.js.map +1 -0
- package/dist/transforms/inline-functions.js +3 -4
- package/dist/transforms/inline-functions.js.map +1 -1
- package/dist/transforms/mangle-properties.js +7 -4
- package/dist/transforms/mangle-properties.js.map +1 -1
- package/dist/transforms/merge-variable-declarations.d.ts +17 -0
- package/dist/transforms/merge-variable-declarations.d.ts.map +1 -0
- package/dist/transforms/merge-variable-declarations.js +68 -0
- package/dist/transforms/merge-variable-declarations.js.map +1 -0
- package/dist/transforms/numbers-to-strings.d.ts.map +1 -1
- package/dist/transforms/numbers-to-strings.js +1 -8
- package/dist/transforms/numbers-to-strings.js.map +1 -1
- package/dist/transforms/pack-declarations-into-parameters.js +1 -5
- package/dist/transforms/pack-declarations-into-parameters.js.map +1 -1
- package/dist/transforms/pack.d.ts +5 -7
- package/dist/transforms/pack.d.ts.map +1 -1
- package/dist/transforms/pack.js +10 -17
- package/dist/transforms/pack.js.map +1 -1
- package/dist/transforms/remove-unused-code.d.ts.map +1 -1
- package/dist/transforms/remove-unused-code.js +7 -39
- package/dist/transforms/remove-unused-code.js.map +1 -1
- package/dist/transforms/rename-identifiers.d.ts.map +1 -1
- package/dist/transforms/rename-identifiers.js +29 -17
- package/dist/transforms/rename-identifiers.js.map +1 -1
- package/dist/transforms/reorder-function-declarations.d.ts +25 -0
- package/dist/transforms/reorder-function-declarations.d.ts.map +1 -0
- package/dist/transforms/reorder-function-declarations.js +64 -0
- package/dist/transforms/reorder-function-declarations.js.map +1 -0
- package/dist/transforms/specials-to-strings.js +4 -3
- package/dist/transforms/specials-to-strings.js.map +1 -1
- package/dist/transforms/update-to-assignment.js +1 -16
- package/dist/transforms/update-to-assignment.js.map +1 -1
- package/dist/utils/ast.d.ts +1 -2
- package/dist/utils/ast.d.ts.map +1 -1
- package/dist/utils/ast.js +12 -42
- package/dist/utils/ast.js.map +1 -1
- package/dist/utils/literal.d.ts.map +1 -1
- package/dist/utils/literal.js +74 -2
- package/dist/utils/literal.js.map +1 -1
- package/dist/utils/statement-lists.d.ts +9 -0
- package/dist/utils/statement-lists.d.ts.map +1 -0
- package/dist/utils/statement-lists.js +27 -0
- package/dist/utils/statement-lists.js.map +1 -0
- package/package.json +10 -12
- package/src/analysis/constant.ts +3 -6
- package/src/analysis/inlineability.ts +1 -1
- package/src/index.ts +1 -0
- package/src/obfuscator.ts +51 -14
- package/src/options.ts +21 -3
- package/src/preparation/expand-destructuring.ts +6 -6
- package/src/transforms/arrow-to-function.ts +3 -5
- package/src/transforms/conceal-strings.ts +530 -0
- package/src/transforms/const-let-to-var.ts +37 -9
- package/src/transforms/const-to-let.ts +3 -5
- package/src/transforms/fold-builtin-methods.ts +67 -12
- package/src/transforms/fold-constants.ts +3 -0
- package/src/transforms/hoist-variables.ts +272 -0
- package/src/transforms/inline-functions.ts +3 -4
- package/src/transforms/mangle-properties.ts +7 -7
- package/src/transforms/merge-variable-declarations.ts +91 -0
- package/src/transforms/numbers-to-strings.ts +2 -12
- package/src/transforms/pack-declarations-into-parameters.ts +1 -5
- package/src/transforms/pack.ts +10 -18
- package/src/transforms/remove-unused-code.ts +9 -64
- package/src/transforms/rename-identifiers.ts +42 -17
- package/src/transforms/reorder-function-declarations.ts +80 -0
- package/src/transforms/specials-to-strings.ts +4 -3
- package/src/transforms/update-to-assignment.ts +1 -17
- package/src/utils/ast.ts +15 -51
- package/src/utils/literal.ts +91 -2
- package/src/utils/statement-lists.ts +45 -0
- package/DISCLOSURE +0 -9
- package/README.md +0 -67
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import traverse from '@babel/traverse'
|
|
2
|
-
import type { NodePath,
|
|
2
|
+
import type { NodePath, Visitor } from '@babel/traverse'
|
|
3
3
|
import * as t from '@babel/types'
|
|
4
4
|
import type { File } from '@babel/types'
|
|
5
5
|
|
|
6
6
|
import { isSideEffectFree } from '../analysis/purity'
|
|
7
|
-
import { isDirectEvalCall } from '../utils/ast'
|
|
8
7
|
import type { ChangeState } from '../utils/change-tracking'
|
|
9
8
|
import { hasAnnexBFunctionAlias } from '../utils/paths'
|
|
10
9
|
|
|
@@ -28,10 +27,7 @@ export function removeUnusedCode(ast: File): boolean {
|
|
|
28
27
|
// then maintain counts during removal.
|
|
29
28
|
crawlProgramScope(ast)
|
|
30
29
|
let anyChanged = false
|
|
31
|
-
const state:
|
|
32
|
-
changed: true,
|
|
33
|
-
evalReachableScopes: collectEvalReachableScopes(ast),
|
|
34
|
-
}
|
|
30
|
+
const state: ChangeState = { changed: true }
|
|
35
31
|
while (state.changed) {
|
|
36
32
|
state.changed = false
|
|
37
33
|
traverse(ast, sweepVisitor, undefined, state)
|
|
@@ -51,49 +47,19 @@ function crawlProgramScope(ast: File): void {
|
|
|
51
47
|
})
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
|
|
55
|
-
// Sweep state keeps this set local to one pass.
|
|
56
|
-
interface SweepState extends ChangeState {
|
|
57
|
-
evalReachableScopes: Set<t.Node>
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function collectEvalReachableScopes(ast: File): Set<t.Node> {
|
|
61
|
-
const reachable = new Set<t.Node>()
|
|
62
|
-
traverse(ast, {
|
|
63
|
-
CallExpression(path) {
|
|
64
|
-
if (!isDirectEvalCall(path.node)) {
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
let scope: Scope | undefined = path.scope
|
|
68
|
-
while (scope) {
|
|
69
|
-
reachable.add(scope.block)
|
|
70
|
-
scope = scope.parent
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
})
|
|
74
|
-
return reachable
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function bindingReachableByEval(
|
|
78
|
-
binding: { scope: Scope },
|
|
79
|
-
evalReachableScopes: Set<t.Node>,
|
|
80
|
-
): boolean {
|
|
81
|
-
return evalReachableScopes.has(binding.scope.block)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const sweepVisitor: Visitor<SweepState> = {
|
|
50
|
+
const sweepVisitor: Visitor<ChangeState> = {
|
|
85
51
|
VariableDeclarator(path, state) {
|
|
86
|
-
if (tryRemoveDeclarator(path
|
|
52
|
+
if (tryRemoveDeclarator(path)) {
|
|
87
53
|
state.changed = true
|
|
88
54
|
}
|
|
89
55
|
},
|
|
90
56
|
FunctionDeclaration(path, state) {
|
|
91
|
-
if (tryRemoveFunctionDecl(path
|
|
57
|
+
if (tryRemoveFunctionDecl(path)) {
|
|
92
58
|
state.changed = true
|
|
93
59
|
}
|
|
94
60
|
},
|
|
95
61
|
ClassDeclaration(path, state) {
|
|
96
|
-
if (tryRemoveClassDecl(path
|
|
62
|
+
if (tryRemoveClassDecl(path)) {
|
|
97
63
|
state.changed = true
|
|
98
64
|
}
|
|
99
65
|
},
|
|
@@ -126,10 +92,7 @@ function dereferenceSubtree(rootPath: NodePath): void {
|
|
|
126
92
|
rootPath.traverse(dereferenceVisitor)
|
|
127
93
|
}
|
|
128
94
|
|
|
129
|
-
function tryRemoveDeclarator(
|
|
130
|
-
path: NodePath<t.VariableDeclarator>,
|
|
131
|
-
evalReachableScopes: Set<t.Node>,
|
|
132
|
-
): boolean {
|
|
95
|
+
function tryRemoveDeclarator(path: NodePath<t.VariableDeclarator>): boolean {
|
|
133
96
|
const declaration = path.parentPath
|
|
134
97
|
if (!declaration || !declaration.isVariableDeclaration()) {
|
|
135
98
|
return false
|
|
@@ -170,10 +133,6 @@ function tryRemoveDeclarator(
|
|
|
170
133
|
if (binding.constantViolations.length > 0) {
|
|
171
134
|
return false
|
|
172
135
|
}
|
|
173
|
-
if (bindingReachableByEval(binding, evalReachableScopes)) {
|
|
174
|
-
return false
|
|
175
|
-
}
|
|
176
|
-
|
|
177
136
|
if (path.node.init) {
|
|
178
137
|
dereferenceSubtree(path.get('init') as NodePath)
|
|
179
138
|
}
|
|
@@ -185,10 +144,7 @@ function tryRemoveDeclarator(
|
|
|
185
144
|
return true
|
|
186
145
|
}
|
|
187
146
|
|
|
188
|
-
function tryRemoveFunctionDecl(
|
|
189
|
-
path: NodePath<t.FunctionDeclaration>,
|
|
190
|
-
evalReachableScopes: Set<t.Node>,
|
|
191
|
-
): boolean {
|
|
147
|
+
function tryRemoveFunctionDecl(path: NodePath<t.FunctionDeclaration>): boolean {
|
|
192
148
|
if (isExported(path)) {
|
|
193
149
|
return false
|
|
194
150
|
}
|
|
@@ -211,20 +167,13 @@ function tryRemoveFunctionDecl(
|
|
|
211
167
|
if (binding.constantViolations.length > 0) {
|
|
212
168
|
return false
|
|
213
169
|
}
|
|
214
|
-
if (bindingReachableByEval(binding, evalReachableScopes)) {
|
|
215
|
-
return false
|
|
216
|
-
}
|
|
217
|
-
|
|
218
170
|
dereferenceSubtree(path.get('body') as NodePath)
|
|
219
171
|
path.scope.removeBinding(id.name)
|
|
220
172
|
path.remove()
|
|
221
173
|
return true
|
|
222
174
|
}
|
|
223
175
|
|
|
224
|
-
function tryRemoveClassDecl(
|
|
225
|
-
path: NodePath<t.ClassDeclaration>,
|
|
226
|
-
evalReachableScopes: Set<t.Node>,
|
|
227
|
-
): boolean {
|
|
176
|
+
function tryRemoveClassDecl(path: NodePath<t.ClassDeclaration>): boolean {
|
|
228
177
|
if (isExported(path)) {
|
|
229
178
|
return false
|
|
230
179
|
}
|
|
@@ -265,10 +214,6 @@ function tryRemoveClassDecl(
|
|
|
265
214
|
if (binding.constantViolations.length > 0) {
|
|
266
215
|
return false
|
|
267
216
|
}
|
|
268
|
-
if (bindingReachableByEval(binding, evalReachableScopes)) {
|
|
269
|
-
return false
|
|
270
|
-
}
|
|
271
|
-
|
|
272
217
|
dereferenceSubtree(path.get('body') as NodePath)
|
|
273
218
|
if (path.node.superClass) {
|
|
274
219
|
dereferenceSubtree(path.get('superClass') as NodePath)
|
|
@@ -4,7 +4,7 @@ import * as t from '@babel/types'
|
|
|
4
4
|
import type { File } from '@babel/types'
|
|
5
5
|
|
|
6
6
|
import type { TransformContext } from '../options'
|
|
7
|
-
import {
|
|
7
|
+
import { isInsideWith } from '../utils/ast'
|
|
8
8
|
import { hasAnnexBFunctionAlias, referencesOrWritesVariable } from '../utils/paths'
|
|
9
9
|
import { mulberry32 } from '../utils/random'
|
|
10
10
|
import { StringGenerator } from '../utils/string-generator'
|
|
@@ -31,6 +31,7 @@ import { StringGenerator } from '../utils/string-generator'
|
|
|
31
31
|
*/
|
|
32
32
|
export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
33
33
|
const generator = new StringGenerator(ctx.stringGeneratorMode, mulberry32(ctx.seed))
|
|
34
|
+
const separatedDirectExports = separateDirectExports(ast.program)
|
|
34
35
|
|
|
35
36
|
let programScope: Scope | undefined
|
|
36
37
|
traverse(ast, {
|
|
@@ -40,14 +41,14 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
40
41
|
},
|
|
41
42
|
})
|
|
42
43
|
if (!programScope) {
|
|
43
|
-
return
|
|
44
|
+
return separatedDirectExports
|
|
44
45
|
}
|
|
45
46
|
programScope.crawl()
|
|
46
47
|
|
|
47
48
|
const liveAncestors = new Map<Scope, Set<Binding>>()
|
|
48
49
|
// Reserve unbound names so generated bindings cannot capture them.
|
|
49
50
|
const freeNames = new Set<string>()
|
|
50
|
-
// `with`
|
|
51
|
+
// A reference inside `with` pins its binding to the source name.
|
|
51
52
|
const unrenamable = new Set<Binding>()
|
|
52
53
|
// Reserve pinned source names globally, including before their binding is seen.
|
|
53
54
|
const pinnedNames = new Set<string>()
|
|
@@ -82,19 +83,6 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
82
83
|
scope = scope.parent
|
|
83
84
|
}
|
|
84
85
|
},
|
|
85
|
-
CallExpression(path) {
|
|
86
|
-
// Direct eval can read every visible binding by its source name.
|
|
87
|
-
if (!isDirectEvalCall(path.node)) {
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
let scope: Scope | undefined = path.scope
|
|
91
|
-
while (scope) {
|
|
92
|
-
for (const name of Object.keys(scope.bindings)) {
|
|
93
|
-
pin(scope.bindings[name]!, name)
|
|
94
|
-
}
|
|
95
|
-
scope = scope.parent
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
86
|
FunctionDeclaration(path) {
|
|
99
87
|
// ECMA-262 B.3.3 gives sloppy block functions an alias Babel does not model.
|
|
100
88
|
if (!hasAnnexBFunctionAlias(path)) {
|
|
@@ -169,7 +157,7 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
169
157
|
})
|
|
170
158
|
|
|
171
159
|
if (bindingToName.size === 0) {
|
|
172
|
-
return renameLabels(ast, generator)
|
|
160
|
+
return renameLabels(ast, generator) || separatedDirectExports
|
|
173
161
|
}
|
|
174
162
|
|
|
175
163
|
traverse(ast, {
|
|
@@ -199,6 +187,43 @@ export function renameIdentifiers(ast: File, ctx: TransformContext): boolean {
|
|
|
199
187
|
return true
|
|
200
188
|
}
|
|
201
189
|
|
|
190
|
+
function separateDirectExports(program: t.Program): boolean {
|
|
191
|
+
const body: t.Program['body'] = []
|
|
192
|
+
let changed = false
|
|
193
|
+
|
|
194
|
+
for (const statement of program.body) {
|
|
195
|
+
const declaration = t.isExportNamedDeclaration(statement) ? statement.declaration : null
|
|
196
|
+
if (
|
|
197
|
+
!t.isVariableDeclaration(declaration) &&
|
|
198
|
+
!t.isFunctionDeclaration(declaration) &&
|
|
199
|
+
!t.isClassDeclaration(declaration)
|
|
200
|
+
) {
|
|
201
|
+
body.push(statement)
|
|
202
|
+
continue
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const names = Object.keys(t.getBindingIdentifiers(declaration))
|
|
206
|
+
if (names.length === 0) {
|
|
207
|
+
body.push(statement)
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
body.push(
|
|
212
|
+
declaration,
|
|
213
|
+
t.exportNamedDeclaration(
|
|
214
|
+
null,
|
|
215
|
+
names.map((name) => t.exportSpecifier(t.identifier(name), t.identifier(name))),
|
|
216
|
+
),
|
|
217
|
+
)
|
|
218
|
+
changed = true
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (changed) {
|
|
222
|
+
program.body = body
|
|
223
|
+
}
|
|
224
|
+
return changed
|
|
225
|
+
}
|
|
226
|
+
|
|
202
227
|
function isNonBindingPosition(path: { node: t.Identifier; parent: t.Node }): boolean {
|
|
203
228
|
const node = path.node
|
|
204
229
|
const parent = path.parent
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { NodePath } from '@babel/traverse'
|
|
2
|
+
import type * as t from '@babel/types'
|
|
3
|
+
import type { File } from '@babel/types'
|
|
4
|
+
|
|
5
|
+
import { hasAnnexBFunctionAlias } from '../utils/paths'
|
|
6
|
+
import { rewriteStatementLists } from '../utils/statement-lists'
|
|
7
|
+
import type { StatementListContainer } from '../utils/statement-lists'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Moves hoisted function declarations to the end of each program, block, switch
|
|
11
|
+
* case, or static block. It keeps declaration order, including duplicate names,
|
|
12
|
+
* and leaves sloppy Annex B block functions in place.
|
|
13
|
+
*
|
|
14
|
+
* If moving every declaration would turn a string expression into a directive,
|
|
15
|
+
* one declaration stays before the string. Direct exports stay in place.
|
|
16
|
+
* Indirect exports move with their local declaration.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // ◀️ before
|
|
20
|
+
* function area(r) {
|
|
21
|
+
* return r * r;
|
|
22
|
+
* }
|
|
23
|
+
* log(area(2));
|
|
24
|
+
*
|
|
25
|
+
* // ▶️ after
|
|
26
|
+
* log(area(2));
|
|
27
|
+
* function area(r) {
|
|
28
|
+
* return r * r;
|
|
29
|
+
* }
|
|
30
|
+
*/
|
|
31
|
+
export function reorderFunctionDeclarations(ast: File): boolean {
|
|
32
|
+
return rewriteStatementLists(ast, (statements, container) =>
|
|
33
|
+
reorderedStatements(statements, containerAllowsDirective(container)),
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Only programs and function bodies recognize directives, so only they need the guard.
|
|
38
|
+
function containerAllowsDirective(container: NodePath<StatementListContainer>): boolean {
|
|
39
|
+
if (container.isProgram()) {
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
return container.isBlockStatement() && container.parentPath.isFunction()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function reorderedStatements(
|
|
46
|
+
children: NodePath<t.Statement>[],
|
|
47
|
+
canCreateDirective: boolean,
|
|
48
|
+
): t.Statement[] | null {
|
|
49
|
+
const rest: t.Statement[] = []
|
|
50
|
+
const moved: t.Statement[] = []
|
|
51
|
+
for (const child of children) {
|
|
52
|
+
if (isMovable(child)) {
|
|
53
|
+
moved.push(child.node)
|
|
54
|
+
} else {
|
|
55
|
+
rest.push(child.node)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (moved.length === 0) {
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
if (canCreateDirective && createsDirective(rest)) {
|
|
63
|
+
rest.unshift(moved.shift()!)
|
|
64
|
+
}
|
|
65
|
+
const reordered = [...rest, ...moved]
|
|
66
|
+
return reordered.every((node, index) => node === children[index]?.node) ? null : reordered
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function createsDirective(statements: readonly t.Statement[]): boolean {
|
|
70
|
+
const first = statements[0]
|
|
71
|
+
return (
|
|
72
|
+
first !== undefined &&
|
|
73
|
+
first.type === 'ExpressionStatement' &&
|
|
74
|
+
first.expression.type === 'StringLiteral'
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isMovable(child: NodePath<t.Statement>): boolean {
|
|
79
|
+
return child.isFunctionDeclaration() && child.node.id !== null && !hasAnnexBFunctionAlias(child)
|
|
80
|
+
}
|
|
@@ -4,7 +4,7 @@ import * as t from '@babel/types'
|
|
|
4
4
|
import type { File } from '@babel/types'
|
|
5
5
|
|
|
6
6
|
import type { TransformContext } from '../options'
|
|
7
|
-
import {
|
|
7
|
+
import { isInsideWith } from '../utils/ast'
|
|
8
8
|
import type { ChangeState } from '../utils/change-tracking'
|
|
9
9
|
import { mulberry32 } from '../utils/random'
|
|
10
10
|
import { StringGenerator } from '../utils/string-generator'
|
|
@@ -69,8 +69,9 @@ const visitor: Visitor<State> = {
|
|
|
69
69
|
if (path.scope.getBinding(name)) {
|
|
70
70
|
return
|
|
71
71
|
}
|
|
72
|
-
//
|
|
73
|
-
|
|
72
|
+
// A replacement cannot occupy a write target. `with` may also shadow `NaN`,
|
|
73
|
+
// `Infinity`, or `undefined`.
|
|
74
|
+
if (isWriteTarget(path) || isInsideWith(path)) {
|
|
74
75
|
return
|
|
75
76
|
}
|
|
76
77
|
const str = t.stringLiteral(state.generator.next())
|
|
@@ -2,7 +2,7 @@ import type { Binding, NodePath, Visitor } from '@babel/traverse'
|
|
|
2
2
|
import * as t from '@babel/types'
|
|
3
3
|
import type { File } from '@babel/types'
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { isInsideWith } from '../utils/ast'
|
|
6
6
|
import { traverseForChanges } from '../utils/change-tracking'
|
|
7
7
|
import type { ChangeState } from '../utils/change-tracking'
|
|
8
8
|
|
|
@@ -52,10 +52,6 @@ const visitor: Visitor<ChangeState> = {
|
|
|
52
52
|
if (!isNumericBinding(binding)) {
|
|
53
53
|
return
|
|
54
54
|
}
|
|
55
|
-
// Direct eval anywhere in the binding's scope can change its type.
|
|
56
|
-
if (bindingScopeReachesDirectEval(binding!)) {
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
55
|
const compound = operator === '++' ? '+=' : '-='
|
|
60
56
|
path.replaceWith(t.assignmentExpression(compound, argument, t.numericLiteral(1)))
|
|
61
57
|
state.changed = true
|
|
@@ -95,18 +91,6 @@ function isNumericBinding(binding: Binding | undefined): boolean {
|
|
|
95
91
|
return binding.constantViolations.every((violation) => keepsNumeric(violation.node))
|
|
96
92
|
}
|
|
97
93
|
|
|
98
|
-
// A sibling or nested direct eval can still close over and reassign the binding.
|
|
99
|
-
function bindingScopeReachesDirectEval(binding: Binding): boolean {
|
|
100
|
-
let found = false
|
|
101
|
-
t.traverseFast(binding.scope.block, (node) => {
|
|
102
|
-
if (isDirectEvalCall(node)) {
|
|
103
|
-
found = true
|
|
104
|
-
}
|
|
105
|
-
return found ? t.traverseFast.stop : undefined
|
|
106
|
-
})
|
|
107
|
-
return found
|
|
108
|
-
}
|
|
109
|
-
|
|
110
94
|
function isForInOfLoopVariable(binding: Binding): boolean {
|
|
111
95
|
const declaration = binding.path.parentPath
|
|
112
96
|
if (!declaration || !declaration.isVariableDeclaration()) {
|
package/src/utils/ast.ts
CHANGED
|
@@ -62,75 +62,39 @@ export function isProtoKey(key: t.Node): boolean {
|
|
|
62
62
|
)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// A non-computed key names a property; it is not a runtime value.
|
|
66
|
+
export function isNonComputedKey(path: NodePath): boolean {
|
|
67
|
+
const parent = path.parent
|
|
68
|
+
return (
|
|
69
|
+
'key' in parent && parent.key === path.node && 'computed' in parent && parent.computed === false
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
65
73
|
// `with` can shadow names outside Babel's scope table.
|
|
66
74
|
export function isInsideWith(path: NodePath): boolean {
|
|
67
|
-
if (!
|
|
75
|
+
if (!programHasWith(path.scope.getProgramParent().block)) {
|
|
68
76
|
return false
|
|
69
77
|
}
|
|
70
78
|
return path.findParent((ancestor) => ancestor.isWithStatement()) !== null
|
|
71
79
|
}
|
|
72
80
|
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
export function enclosingScopeHasDirectEval(path: NodePath): boolean {
|
|
76
|
-
if (!programContains(path.scope.getProgramParent().block, programDirectEval, isDirectEvalCall)) {
|
|
77
|
-
return false
|
|
78
|
-
}
|
|
79
|
-
let current: NodePath | null = path
|
|
80
|
-
while (current) {
|
|
81
|
-
if ((current.isFunction() || current.isProgram()) && ownScopeHasDirectEval(current.node)) {
|
|
82
|
-
return true
|
|
83
|
-
}
|
|
84
|
-
current = current.parentPath
|
|
85
|
-
}
|
|
86
|
-
return false
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Cache program-wide `eval` and `with` checks to avoid a subtree walk per name.
|
|
90
|
-
// No transform creates either construct, so a negative result stays valid.
|
|
91
|
-
const programDirectEval = new WeakMap<t.Node, boolean>()
|
|
81
|
+
// Cache the program-wide scan because `isInsideWith` runs for many identifiers.
|
|
82
|
+
// Transforms never add `with`, so a cached miss remains valid.
|
|
92
83
|
const programWith = new WeakMap<t.Node, boolean>()
|
|
93
84
|
|
|
94
|
-
function
|
|
95
|
-
|
|
96
|
-
cache: WeakMap<t.Node, boolean>,
|
|
97
|
-
matches: (node: t.Node) => boolean,
|
|
98
|
-
): boolean {
|
|
99
|
-
const cached = cache.get(programNode)
|
|
85
|
+
function programHasWith(programNode: t.Node): boolean {
|
|
86
|
+
const cached = programWith.get(programNode)
|
|
100
87
|
if (cached !== undefined) {
|
|
101
88
|
return cached
|
|
102
89
|
}
|
|
103
90
|
let found = false
|
|
104
91
|
t.traverseFast(programNode, (node) => {
|
|
105
|
-
if (!
|
|
92
|
+
if (!t.isWithStatement(node)) {
|
|
106
93
|
return
|
|
107
94
|
}
|
|
108
95
|
found = true
|
|
109
96
|
return t.traverseFast.stop
|
|
110
97
|
})
|
|
111
|
-
|
|
98
|
+
programWith.set(programNode, found)
|
|
112
99
|
return found
|
|
113
100
|
}
|
|
114
|
-
|
|
115
|
-
function ownScopeHasDirectEval(owner: t.Node): boolean {
|
|
116
|
-
let found = false
|
|
117
|
-
// This walk stops at the first hit, unlike `walkOwnFunctionScope`.
|
|
118
|
-
t.traverseFast(owner, (node) => {
|
|
119
|
-
if (isDirectEvalCall(node)) {
|
|
120
|
-
found = true
|
|
121
|
-
return t.traverseFast.stop
|
|
122
|
-
}
|
|
123
|
-
return node !== owner && t.isFunction(node) ? t.traverseFast.skip : undefined
|
|
124
|
-
})
|
|
125
|
-
return found
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function isDirectEvalCall(node: t.Node): boolean {
|
|
129
|
-
// `eval?.(...)` is an OptionalCallExpression yet still a direct eval, so both
|
|
130
|
-
// call node shapes count.
|
|
131
|
-
return (
|
|
132
|
-
(t.isCallExpression(node) || t.isOptionalCallExpression(node)) &&
|
|
133
|
-
t.isIdentifier(node.callee) &&
|
|
134
|
-
node.callee.name === 'eval'
|
|
135
|
-
)
|
|
136
|
-
}
|
package/src/utils/literal.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import generate from '@babel/generator'
|
|
1
2
|
import * as t from '@babel/types'
|
|
2
3
|
|
|
4
|
+
const MAX_GENERATED_LITERAL_LENGTH = 8192
|
|
5
|
+
const MAX_LITERAL_DEPTH = 32
|
|
6
|
+
const MAX_LITERAL_NODES = 512
|
|
3
7
|
const MAX_STRING_LITERAL_LENGTH = 4096
|
|
4
8
|
|
|
5
9
|
// RegExp literals allocate mutable objects and are not primitive values.
|
|
@@ -45,19 +49,56 @@ function literalShapeKind(node: t.Node): 'bigint' | 'other' | null {
|
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
export function valueToLiteral(value: unknown): t.Expression | null {
|
|
52
|
+
const literal = buildValueLiteral(value, new Set(), { nodesRemaining: MAX_LITERAL_NODES }, 0)
|
|
53
|
+
if (!literal || generate(literal).code.length > MAX_GENERATED_LITERAL_LENGTH) {
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
return literal
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface LiteralBudget {
|
|
60
|
+
nodesRemaining: number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function reserveLiteralNodes(budget: LiteralBudget, count: number): boolean {
|
|
64
|
+
if (budget.nodesRemaining < count) {
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
budget.nodesRemaining -= count
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildValueLiteral(
|
|
72
|
+
value: unknown,
|
|
73
|
+
seen: Set<object>,
|
|
74
|
+
budget: LiteralBudget,
|
|
75
|
+
depth: number,
|
|
76
|
+
): t.Expression | null {
|
|
77
|
+
if (depth > MAX_LITERAL_DEPTH) {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
48
80
|
if (typeof value === 'string') {
|
|
49
|
-
if (value.length > MAX_STRING_LITERAL_LENGTH) {
|
|
81
|
+
if (value.length > MAX_STRING_LITERAL_LENGTH || !reserveLiteralNodes(budget, 1)) {
|
|
50
82
|
return null
|
|
51
83
|
}
|
|
52
84
|
return t.stringLiteral(value)
|
|
53
85
|
}
|
|
54
86
|
if (typeof value === 'boolean') {
|
|
87
|
+
if (!reserveLiteralNodes(budget, 1)) {
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
55
90
|
return t.booleanLiteral(value)
|
|
56
91
|
}
|
|
57
92
|
if (value === null) {
|
|
93
|
+
if (!reserveLiteralNodes(budget, 1)) {
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
58
96
|
return t.nullLiteral()
|
|
59
97
|
}
|
|
60
98
|
if (value === undefined) {
|
|
99
|
+
if (!reserveLiteralNodes(budget, 2)) {
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
61
102
|
// `void 0` cannot be shadowed like the identifier `undefined`.
|
|
62
103
|
return t.unaryExpression('void', t.numericLiteral(0))
|
|
63
104
|
}
|
|
@@ -65,8 +106,14 @@ export function valueToLiteral(value: unknown): t.Expression | null {
|
|
|
65
106
|
// A UnaryExpression lets the generator parenthesize negative BigInt where
|
|
66
107
|
// precedence requires it.
|
|
67
108
|
if (value < 0n) {
|
|
109
|
+
if (!reserveLiteralNodes(budget, 2)) {
|
|
110
|
+
return null
|
|
111
|
+
}
|
|
68
112
|
return t.unaryExpression('-', t.bigIntLiteral(-value))
|
|
69
113
|
}
|
|
114
|
+
if (!reserveLiteralNodes(budget, 1)) {
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
70
117
|
return t.bigIntLiteral(value)
|
|
71
118
|
}
|
|
72
119
|
if (typeof value === 'number') {
|
|
@@ -74,17 +121,30 @@ export function valueToLiteral(value: unknown): t.Expression | null {
|
|
|
74
121
|
return null
|
|
75
122
|
}
|
|
76
123
|
if (Object.is(value, -0)) {
|
|
124
|
+
if (!reserveLiteralNodes(budget, 2)) {
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
77
127
|
return t.unaryExpression('-', t.numericLiteral(0))
|
|
78
128
|
}
|
|
79
129
|
if (value < 0) {
|
|
130
|
+
if (!reserveLiteralNodes(budget, 2)) {
|
|
131
|
+
return null
|
|
132
|
+
}
|
|
80
133
|
return t.unaryExpression('-', t.numericLiteral(-value))
|
|
81
134
|
}
|
|
135
|
+
if (!reserveLiteralNodes(budget, 1)) {
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
82
138
|
return t.numericLiteral(value)
|
|
83
139
|
}
|
|
84
140
|
if (Array.isArray(value)) {
|
|
141
|
+
if (seen.has(value) || !arrayHasLiteralShape(value) || !reserveLiteralNodes(budget, 1)) {
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
seen.add(value)
|
|
85
145
|
const elements: t.Expression[] = []
|
|
86
146
|
for (const item of value) {
|
|
87
|
-
const lit =
|
|
147
|
+
const lit = buildValueLiteral(item, seen, budget, depth + 1)
|
|
88
148
|
if (!lit) {
|
|
89
149
|
return null
|
|
90
150
|
}
|
|
@@ -94,3 +154,32 @@ export function valueToLiteral(value: unknown): t.Expression | null {
|
|
|
94
154
|
}
|
|
95
155
|
return null
|
|
96
156
|
}
|
|
157
|
+
|
|
158
|
+
function arrayHasLiteralShape(value: unknown[]): boolean {
|
|
159
|
+
if (Object.getPrototypeOf(value) !== Array.prototype || !Object.isExtensible(value)) {
|
|
160
|
+
return false
|
|
161
|
+
}
|
|
162
|
+
const names = Object.getOwnPropertyNames(value)
|
|
163
|
+
if (names.length !== value.length + 1 || names.at(-1) !== 'length') {
|
|
164
|
+
return false
|
|
165
|
+
}
|
|
166
|
+
for (let index = 0; index < value.length; index++) {
|
|
167
|
+
if (
|
|
168
|
+
names[index] !== String(index) ||
|
|
169
|
+
!isOrdinaryDataProperty(Object.getOwnPropertyDescriptor(value, String(index)))
|
|
170
|
+
) {
|
|
171
|
+
return false
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return Object.getOwnPropertySymbols(value).length === 0
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isOrdinaryDataProperty(descriptor: PropertyDescriptor | undefined): boolean {
|
|
178
|
+
return (
|
|
179
|
+
descriptor !== undefined &&
|
|
180
|
+
'value' in descriptor &&
|
|
181
|
+
descriptor.configurable === true &&
|
|
182
|
+
descriptor.enumerable === true &&
|
|
183
|
+
descriptor.writable === true
|
|
184
|
+
)
|
|
185
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { NodePath } from '@babel/traverse'
|
|
2
|
+
import type * as t from '@babel/types'
|
|
3
|
+
import type { File } from '@babel/types'
|
|
4
|
+
|
|
5
|
+
import { traverseForChanges } from './change-tracking'
|
|
6
|
+
import type { ChangeState } from './change-tracking'
|
|
7
|
+
|
|
8
|
+
export type StatementListContainer = t.Program | t.BlockStatement | t.StaticBlock | t.SwitchCase
|
|
9
|
+
|
|
10
|
+
type Rewrite = (
|
|
11
|
+
statements: NodePath<t.Statement>[],
|
|
12
|
+
container: NodePath<StatementListContainer>,
|
|
13
|
+
) => t.Statement[] | null
|
|
14
|
+
|
|
15
|
+
/** Applies a rewrite to every statement-list container in the AST. */
|
|
16
|
+
export function rewriteStatementLists(ast: File, rewrite: Rewrite): boolean {
|
|
17
|
+
return traverseForChanges(ast, {
|
|
18
|
+
Program(path, state) {
|
|
19
|
+
apply(path, 'body', rewrite, state)
|
|
20
|
+
},
|
|
21
|
+
BlockStatement(path, state) {
|
|
22
|
+
apply(path, 'body', rewrite, state)
|
|
23
|
+
},
|
|
24
|
+
StaticBlock(path, state) {
|
|
25
|
+
apply(path, 'body', rewrite, state)
|
|
26
|
+
},
|
|
27
|
+
SwitchCase(path, state) {
|
|
28
|
+
apply(path, 'consequent', rewrite, state)
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function apply(
|
|
34
|
+
container: NodePath<StatementListContainer>,
|
|
35
|
+
key: 'body' | 'consequent',
|
|
36
|
+
rewrite: Rewrite,
|
|
37
|
+
state: ChangeState,
|
|
38
|
+
): void {
|
|
39
|
+
const statements = container.get(key) as NodePath<t.Statement>[]
|
|
40
|
+
const replacement = rewrite(statements, container)
|
|
41
|
+
if (replacement) {
|
|
42
|
+
container.set(key, replacement)
|
|
43
|
+
state.changed = true
|
|
44
|
+
}
|
|
45
|
+
}
|
package/DISCLOSURE
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
webfuscator transforms and obfuscates JavaScript source code. Its features
|
|
2
|
-
include identifier and property-name mangling, constant folding, dead-code
|
|
3
|
-
removal, and control-flow and literal rewrites.
|
|
4
|
-
|
|
5
|
-
These capabilities can make JavaScript harder to inspect and may have both
|
|
6
|
-
legitimate and harmful uses. The intended use is transforming software that
|
|
7
|
-
the user owns or is authorized to modify. This package is not intended to
|
|
8
|
-
create, conceal, distribute, or operate malware, or to interfere with systems
|
|
9
|
-
without authorization.
|