@dacely/toildefender 0.1.5 → 0.1.7

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.
@@ -3,8 +3,222 @@
3
3
  var assert = require("assert");
4
4
 
5
5
  var esshorten = require("esshorten");
6
+ var escope = require("escope");
6
7
 
7
8
  var estest = require("../estest");
9
+ var traverser = require("../traverser");
10
+
11
+ var RESERVED_WORDS = new Set([
12
+ "await",
13
+ "break",
14
+ "case",
15
+ "catch",
16
+ "class",
17
+ "const",
18
+ "continue",
19
+ "debugger",
20
+ "default",
21
+ "delete",
22
+ "do",
23
+ "else",
24
+ "enum",
25
+ "export",
26
+ "extends",
27
+ "false",
28
+ "finally",
29
+ "for",
30
+ "function",
31
+ "if",
32
+ "import",
33
+ "in",
34
+ "instanceof",
35
+ "new",
36
+ "null",
37
+ "return",
38
+ "super",
39
+ "switch",
40
+ "this",
41
+ "throw",
42
+ "true",
43
+ "try",
44
+ "typeof",
45
+ "var",
46
+ "void",
47
+ "while",
48
+ "with",
49
+ "yield",
50
+ "arguments",
51
+ "undefined"
52
+ ]);
53
+
54
+ var FIRST_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$";
55
+ var REST_CHARS = FIRST_CHARS + "0123456789";
56
+
57
+ function containsModernBindings(ast) {
58
+ var found = false;
59
+ traverser.traverseEx(ast, [], function (node) {
60
+ if (
61
+ (node.type == "VariableDeclaration" && node.kind != "var")
62
+ || node.type == "ClassDeclaration"
63
+ || node.type == "ClassExpression"
64
+ ) {
65
+ found = true;
66
+ this.abort();
67
+ }
68
+ return node;
69
+ });
70
+ return found;
71
+ }
72
+
73
+ function shortName(index) {
74
+ var name = FIRST_CHARS[index % FIRST_CHARS.length];
75
+ index = Math.floor(index / FIRST_CHARS.length);
76
+ while (index > 0) {
77
+ index -= 1;
78
+ name += REST_CHARS[index % REST_CHARS.length];
79
+ index = Math.floor(index / REST_CHARS.length);
80
+ }
81
+ return name;
82
+ }
83
+
84
+ function collectUnresolvedNames(scopeManager) {
85
+ var names = new Set();
86
+ scopeManager.scopes.forEach(scope => {
87
+ scope.through.forEach(reference => {
88
+ if (!reference.resolved) {
89
+ names.add(reference.identifier.name);
90
+ }
91
+ });
92
+ });
93
+ return names;
94
+ }
95
+
96
+ function isRenamableVariable(scope, variable, unresolvedNames) {
97
+ if (scope.type == "global") {
98
+ return false;
99
+ }
100
+ if (
101
+ typeof variable.name == "string"
102
+ && variable.name.indexOf("toildefender$anon$") === 0
103
+ && unresolvedNames.has(variable.name)
104
+ ) {
105
+ return false;
106
+ }
107
+ if (variable.name == "arguments" || variable.name == "undefined") {
108
+ return false;
109
+ }
110
+ if (variable.tainted) {
111
+ return false;
112
+ }
113
+ if (!variable.identifiers || variable.identifiers.length == 0) {
114
+ return false;
115
+ }
116
+ if (variable.defs && variable.defs.some(def => def.type == "ClassName")) {
117
+ return false;
118
+ }
119
+ return true;
120
+ }
121
+
122
+ function reserveUnrenamedNames(scopeManager, renamable) {
123
+ var reserved = new Set(RESERVED_WORDS);
124
+ scopeManager.scopes.forEach(scope => {
125
+ scope.variables.forEach(variable => {
126
+ if (!renamable.has(variable)) {
127
+ reserved.add(variable.name);
128
+ }
129
+ });
130
+ scope.through.forEach(reference => {
131
+ if (!reference.resolved) {
132
+ reserved.add(reference.identifier.name);
133
+ }
134
+ });
135
+ });
136
+ return reserved;
137
+ }
138
+
139
+ function buildParentMap(ast) {
140
+ var parents = new WeakMap();
141
+ traverser.traverse(ast, [], function (node, stack) {
142
+ var parentFrame = stack[1];
143
+ if (parentFrame) {
144
+ parents.set(node, parentFrame.node);
145
+ }
146
+ return node;
147
+ });
148
+ return parents;
149
+ }
150
+
151
+ function renameIdentifier(identifier, name, parents) {
152
+ var parent = parents.get(identifier);
153
+ if (
154
+ parent
155
+ && parent.type == "Property"
156
+ && parent.shorthand === true
157
+ && (parent.key === identifier || parent.value === identifier)
158
+ ) {
159
+ parent.shorthand = false;
160
+ parent.key = {
161
+ type: "Identifier",
162
+ name: identifier.name
163
+ };
164
+ parent.value = {
165
+ type: "Identifier",
166
+ name: name
167
+ };
168
+ parents.set(parent.key, parent);
169
+ parents.set(parent.value, parent);
170
+ return;
171
+ }
172
+
173
+ identifier.name = name;
174
+ }
175
+
176
+ function modernMangle(ast) {
177
+ var scopeManager = escope.analyze(ast, {
178
+ ecmaVersion: 6,
179
+ optimistic: true,
180
+ sourceType: "script"
181
+ });
182
+
183
+ var unresolvedNames = collectUnresolvedNames(scopeManager);
184
+ var variables = [];
185
+ scopeManager.scopes.forEach(scope => {
186
+ scope.variables.forEach(variable => {
187
+ if (isRenamableVariable(scope, variable, unresolvedNames)) {
188
+ variables.push({ scope: scope, variable: variable });
189
+ }
190
+ });
191
+ });
192
+
193
+ variables.sort((left, right) => {
194
+ var leftWeight = left.variable.references.length + left.variable.identifiers.length;
195
+ var rightWeight = right.variable.references.length + right.variable.identifiers.length;
196
+ return rightWeight - leftWeight;
197
+ });
198
+
199
+ var renamable = new Set(variables.map(entry => entry.variable));
200
+ var used = reserveUnrenamedNames(scopeManager, renamable);
201
+ var parents = buildParentMap(ast);
202
+ var next = 0;
203
+
204
+ variables.forEach(entry => {
205
+ var name;
206
+ do {
207
+ name = shortName(next);
208
+ next += 1;
209
+ } while (used.has(name) || RESERVED_WORDS.has(name));
210
+ used.add(name);
211
+
212
+ entry.variable.identifiers.forEach(identifier => {
213
+ renameIdentifier(identifier, name, parents);
214
+ });
215
+ entry.variable.references.forEach(reference => {
216
+ renameIdentifier(reference.identifier, name, parents);
217
+ });
218
+ });
219
+
220
+ return ast;
221
+ }
8
222
 
9
223
  module.exports = class Uglifier {
10
224
 
@@ -19,8 +233,11 @@ module.exports = class Uglifier {
19
233
  */
20
234
  uglify (ast) {
21
235
  assert.ok(estest.isNode(ast));
22
-
236
+
237
+ if (containsModernBindings(ast)) {
238
+ return modernMangle(ast);
239
+ }
23
240
  return esshorten.mangle(ast);
24
241
  }
25
242
 
26
- };
243
+ };
@@ -72,11 +72,29 @@ function isClassMethodScope(scope) {
72
72
  if (node.type == "MethodDefinition" || node.type == "ClassBody") {
73
73
  return true;
74
74
  }
75
- node = node.veilmark$parent;
75
+ node = node.toildefender$parent;
76
76
  }
77
77
  return false;
78
78
  }
79
79
 
80
+ function isNumericVmInternalNode(node) {
81
+ while (node) {
82
+ if (node.toildefender$numericVmInternal === true) {
83
+ return true;
84
+ }
85
+ node = node.toildefender$parent;
86
+ }
87
+ return false;
88
+ }
89
+
90
+ function isNumericVmInternalScope(scope) {
91
+ return isNumericVmInternalNode(scope && scope.block);
92
+ }
93
+
94
+ function isNumericVmInternalVariable(variable) {
95
+ return variable.defs.some(def => isNumericVmInternalNode(def.node));
96
+ }
97
+
80
98
  module.exports = class Variables {
81
99
 
82
100
  constructor (logger) {
@@ -93,6 +111,9 @@ module.exports = class Variables {
93
111
  */
94
112
  removeFunctionExpressionIds (ast) {
95
113
  return traverser.traverse(ast, [], (node, stack) => {
114
+ if (isNumericVmInternalNode(node)) {
115
+ return node;
116
+ }
96
117
  if (node.type == "FunctionExpression" && node.id && !functionExpressionUsesOwnName(node)) {
97
118
  node.id = null;
98
119
  }
@@ -114,7 +135,7 @@ module.exports = class Variables {
114
135
  this.esutils.setParentsRecursive(ast);
115
136
 
116
137
  scopeManager.scopes.forEach(scope => {
117
- if (!this.esutils.canInsertIntoScope(scope) || isClassMethodScope(scope)) {
138
+ if (!this.esutils.canInsertIntoScope(scope) || isClassMethodScope(scope) || isNumericVmInternalScope(scope)) {
118
139
  return;
119
140
  }
120
141
  scope.variables.forEach(variable => {
@@ -127,7 +148,7 @@ module.exports = class Variables {
127
148
  * as a FunctionExpression with an id, which are then
128
149
  * mistakingly replaced with EmptyStatements.
129
150
  */
130
- if (estest.isStatement(def.node)) {
151
+ if (estest.isStatement(def.node) && !isNumericVmInternalNode(def.node)) {
131
152
  this.esutils.replaceNode(ast, def.node, { type: "EmptyStatement" });
132
153
  this.esutils.insertIntoScope(scope, {
133
154
  type: "VariableDeclaration",
@@ -142,7 +163,8 @@ module.exports = class Variables {
142
163
  body: def.node.body,
143
164
  generator: def.node.generator === true,
144
165
  expression: def.node.expression === true,
145
- async: def.node.async === true
166
+ async: def.node.async === true,
167
+ toildefender$numericVmInternal: def.node.toildefender$numericVmInternal === true
146
168
  }
147
169
  }
148
170
  ]
@@ -163,8 +185,22 @@ module.exports = class Variables {
163
185
  * @param {ScopeManager} scopeManager Scope manager
164
186
  */
165
187
  obfuscateIdentifiers (ast, scopeManager) {
188
+ var usedNames = new Set();
189
+
190
+ function uniqueName(variable) {
191
+ var base = "$$var$" + utils.hash(variable);
192
+ var name = base + "$" + variable.name;
193
+ var counter = 0;
194
+ while (usedNames.has(name)) {
195
+ counter += 1;
196
+ name = base + counter.toString(36) + "$" + variable.name;
197
+ }
198
+ usedNames.add(name);
199
+ return name;
200
+ }
201
+
166
202
  scopeManager.scopes.forEach(scope => {
167
- if (isClassMethodScope(scope)) {
203
+ if (isClassMethodScope(scope) || isNumericVmInternalScope(scope)) {
168
204
  return;
169
205
  }
170
206
  if (scope.isStatic()) {
@@ -179,7 +215,11 @@ module.exports = class Variables {
179
215
  });
180
216
 
181
217
  for (let variable of scope.variables) {
182
- var name = "$$var$" + utils.hash(variable) + "$" + variable.name;
218
+ if (isNumericVmInternalVariable(variable)) {
219
+ continue;
220
+ }
221
+
222
+ var name = uniqueName(variable);
183
223
 
184
224
  if (variable.defs.some(def => def.type == "ClassName")) {
185
225
  continue;
@@ -199,7 +239,7 @@ module.exports = class Variables {
199
239
  def.name = name;
200
240
  }
201
241
 
202
- for (let ref of variable.references) {
242
+ for (let ref of variable.references.filter(ref => ref.resolved === variable)) {
203
243
  // change reference's name
204
244
  ref.identifier.name = name;
205
245
  }
@@ -236,10 +276,13 @@ module.exports = class Variables {
236
276
  var rng = new utils.UniqueRandomAlpha(3);
237
277
 
238
278
  scopeManager.scopes.forEach(scope => {
239
- if (!this.esutils.canInsertIntoScope(scope) || isClassMethodScope(scope)) {
279
+ if (!this.esutils.canInsertIntoScope(scope) || isClassMethodScope(scope) || isNumericVmInternalScope(scope)) {
240
280
  return;
241
281
  }
242
282
  scope.variables.forEach(variable => {
283
+ if (isNumericVmInternalVariable(variable)) {
284
+ return;
285
+ }
243
286
  variable.defs.forEach(def => {
244
287
  if (def.type == "Parameter") {
245
288
  assert(def.name.type == "Identifier");