@dacely/toildefender 0.1.6 → 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.
@@ -10,9 +10,9 @@ var utils = require("../utils");
10
10
 
11
11
  /**
12
12
  * Merges nested bind calls like
13
- * veilmark$bind(veilmark$bind(main, 1234), 5678)
13
+ * toildefender$bind(toildefender$bind(main, 1234), 5678)
14
14
  * to
15
- * veilmark$bind(main, 1234, 5678)
15
+ * toildefender$bind(main, 1234, 5678)
16
16
  * @param {Node} node
17
17
  * @returns {Node}
18
18
  */
@@ -27,7 +27,7 @@ function mergeNestedBinds(node) {
27
27
  }
28
28
 
29
29
  /**
30
- * Checks whether node is a call to veilmark$bind.
30
+ * Checks whether node is a call to toildefender$bind.
31
31
  * @param {Node} node
32
32
  * @returns {boolean}
33
33
  */
@@ -36,7 +36,7 @@ function isBindCall(node) {
36
36
 
37
37
  return node.type == "CallExpression"
38
38
  && node.callee.type == "Identifier"
39
- && node.callee.name == "veilmark$bind";
39
+ && node.callee.name == "toildefender$bind";
40
40
  }
41
41
 
42
42
  module.exports = class Postprocessing {
@@ -19,11 +19,137 @@ function isClassMethodScope(scope) {
19
19
  if (node.type == "MethodDefinition" || node.type == "ClassBody") {
20
20
  return true;
21
21
  }
22
- node = node.veilmark$parent;
22
+ node = node.toildefender$parent;
23
23
  }
24
24
  return false;
25
25
  }
26
26
 
27
+ function isNumericVmInternalNode(node) {
28
+ while (node) {
29
+ if (node.toildefender$numericVmInternal === true) {
30
+ return true;
31
+ }
32
+ node = node.toildefender$parent;
33
+ }
34
+ return false;
35
+ }
36
+
37
+ function isNumericVmInternalFunction(stack) {
38
+ return stack.some(frame => frame.node && isNumericVmInternalNode(frame.node));
39
+ }
40
+
41
+ function isNumericVmInternalScope(scope) {
42
+ return isNumericVmInternalNode(scope && scope.block);
43
+ }
44
+
45
+ function scopeReference(scopeVarName, index) {
46
+ return {
47
+ type: "MemberExpression",
48
+ object: { type: "Identifier", name: scopeVarName },
49
+ property: { type: "Literal", value: index },
50
+ computed: true,
51
+ toildefender$scopeObjectReference: true
52
+ };
53
+ }
54
+
55
+ function isReferenceIdentifier(node, stack) {
56
+ var parentFrame = stack[1];
57
+ if (!parentFrame) {
58
+ return true;
59
+ }
60
+
61
+ var parent = parentFrame.node;
62
+ var key = parentFrame.key;
63
+
64
+ if ((parent.type == "FunctionDeclaration" || parent.type == "FunctionExpression") && (key == "id" || key == "params")) {
65
+ return false;
66
+ }
67
+ if ((parent.type == "ClassDeclaration" || parent.type == "ClassExpression") && key == "id") {
68
+ return false;
69
+ }
70
+ if (parent.type == "VariableDeclarator" && key == "id") {
71
+ return false;
72
+ }
73
+ if (parent.type == "CatchClause" && key == "param") {
74
+ return false;
75
+ }
76
+ if ((parent.type == "MemberExpression" || parent.type == "Property") && key == "property" && parent.computed === false) {
77
+ return false;
78
+ }
79
+ if (parent.type == "Property" && key == "key" && parent.computed === false) {
80
+ return false;
81
+ }
82
+ if ((parent.type == "MethodDefinition" || parent.type == "PropertyDefinition" || parent.type == "FieldDefinition") && key == "key" && parent.computed === false) {
83
+ return false;
84
+ }
85
+ if ((parent.type == "LabeledStatement" || parent.type == "BreakStatement" || parent.type == "ContinueStatement") && key == "label") {
86
+ return false;
87
+ }
88
+
89
+ return true;
90
+ }
91
+
92
+ function isInsideNestedScope(stack, root, scopeBlocks) {
93
+ return stack.some(frame => frame.node != root && scopeBlocks.has(frame.node));
94
+ }
95
+
96
+ function isMovableVariable(variable) {
97
+ return variable.defs.some(def => {
98
+ if (isNumericVmInternalNode(def.node)) {
99
+ return false;
100
+ }
101
+ if (def.type == "Variable" || def.type == "CatchClause") {
102
+ return true;
103
+ }
104
+ return def.type == "FunctionName" && def.node.type != "FunctionExpression";
105
+ });
106
+ }
107
+
108
+ function markPropertyValueReplacement(stack) {
109
+ var parentFrame = stack[1];
110
+ if (!parentFrame) {
111
+ return;
112
+ }
113
+ var parent = parentFrame.node;
114
+ if (parent.type == "Property" && parent.shorthand === true && parentFrame.key == "value") {
115
+ parent.shorthand = false;
116
+ }
117
+ }
118
+
119
+ function isReferenceInsideNestedFunction(scopeBlock, identifier) {
120
+ var current = identifier && identifier.toildefender$parent;
121
+ while (current && current != scopeBlock) {
122
+ if (estest.isFunction(current)) {
123
+ return true;
124
+ }
125
+ current = current.toildefender$parent;
126
+ }
127
+ return false;
128
+ }
129
+
130
+ function normalizeRatio(value) {
131
+ var ratio = Number(value);
132
+ if (!Number.isFinite(ratio)) {
133
+ return 1;
134
+ }
135
+ if (ratio < 0) {
136
+ return 0;
137
+ }
138
+ if (ratio > 1) {
139
+ return 1;
140
+ }
141
+ return ratio;
142
+ }
143
+
144
+ function hashString32(value) {
145
+ var h = 0x811c9dc5;
146
+ for (var i = 0; i < value.length; i += 1) {
147
+ h ^= value.charCodeAt(i);
148
+ h = Math.imul(h, 0x01000193) >>> 0;
149
+ }
150
+ return h >>> 0;
151
+ }
152
+
27
153
  module.exports = class Scopes {
28
154
 
29
155
  constructor (logger) {
@@ -41,19 +167,216 @@ module.exports = class Scopes {
41
167
  * @param {Node} ast Root node
42
168
  * @param {ScopeManager} scopeManager Scope manager
43
169
  */
44
- createScopeObjects (ast, scopeManager) {
170
+ createScopeObjects (ast, scopeManager, options) {
45
171
  assert.ok(estest.isNode(ast));
46
172
 
47
173
  this.esutils.setParentsRecursive(ast);
174
+ options = options || {};
175
+ var ratio = normalizeRatio(options.ratio);
176
+ var seed = options.seed || "toildefender-scope";
177
+ var forceProgram = options.forceProgram === true;
48
178
  var scopes = scopeManager.acquireAll(ast);
49
179
  var rngAlpha = new utils.UniqueRandomAlpha(3);
180
+ var replacements = new WeakMap();
181
+ var referencesByVariable = new Map();
182
+ var fallbackReplacementsByName = new Map();
183
+ var scopeBlocks = new WeakSet();
184
+ scopes.forEach(scope => {
185
+ if (scope && scope.block) {
186
+ scopeBlocks.add(scope.block);
187
+ }
188
+ });
189
+
190
+ function cloneReplacement(node) {
191
+ return utils.cloneISwearIKnowWhatImDoing(node);
192
+ }
193
+
194
+ function addFallbackReplacement(name, replacement, block, scopeDecl) {
195
+ var entries = fallbackReplacementsByName.get(name);
196
+ if (!entries) {
197
+ entries = [];
198
+ fallbackReplacementsByName.set(name, entries);
199
+ }
200
+ entries.push({
201
+ block: block,
202
+ scopeDecl: scopeDecl,
203
+ replacement: replacement
204
+ });
205
+ }
206
+
207
+ function ancestorDistance(ancestor, node) {
208
+ var distance = 0;
209
+ var current = node;
210
+ while (current) {
211
+ if (current == ancestor) {
212
+ return distance;
213
+ }
214
+ current = current.toildefender$parent;
215
+ distance += 1;
216
+ }
217
+ return -1;
218
+ }
219
+
220
+ function fallbackReplacementForName(name, node) {
221
+ var entries = fallbackReplacementsByName.get(name);
222
+ if (!entries) {
223
+ return null;
224
+ }
225
+
226
+ var best = null;
227
+ var bestDistance = Infinity;
228
+ entries.forEach(entry => {
229
+ var liveBlock = entry.scopeDecl && entry.scopeDecl.toildefender$parent;
230
+ var distance = liveBlock ? ancestorDistance(liveBlock, node) : -1;
231
+ if (distance < 0) {
232
+ distance = ancestorDistance(entry.block, node);
233
+ }
234
+ if (distance >= 0 && distance < bestDistance) {
235
+ best = entry.replacement;
236
+ bestDistance = distance;
237
+ }
238
+ });
239
+ if (best) {
240
+ return best;
241
+ }
242
+ return null;
243
+ }
244
+
245
+ function addResolvedReference(variable, reference) {
246
+ if (!variable || !reference || !reference.identifier) {
247
+ return;
248
+ }
249
+ var references = referencesByVariable.get(variable);
250
+ if (!references) {
251
+ references = new Set();
252
+ referencesByVariable.set(variable, references);
253
+ }
254
+ references.add(reference);
255
+ }
256
+
50
257
  scopeManager.scopes.forEach(scope => {
51
- if (!this.esutils.canInsertIntoScope(scope) || isClassMethodScope(scope)) {
258
+ scope.variables.forEach(variable => {
259
+ variable.references.forEach(reference => addResolvedReference(variable, reference));
260
+ });
261
+
262
+ scope.references.forEach(reference => {
263
+ addResolvedReference(reference.resolved, reference);
264
+ });
265
+
266
+ scope.through.forEach(reference => {
267
+ addResolvedReference(reference.resolved, reference);
268
+ });
269
+ });
270
+
271
+ function referencesFor(variable) {
272
+ var references = referencesByVariable.get(variable);
273
+ references = references ? Array.from(references) : variable.references;
274
+ return references.filter(reference => reference.resolved === variable);
275
+ }
276
+
277
+ function shouldFlattenScope(scope, movableVariables, index) {
278
+ if (forceProgram && scope && scope.block && scope.block.type == "Program") {
279
+ return true;
280
+ }
281
+ if (movableVariables.some(variable => referencesFor(variable).some(reference => isReferenceInsideNestedFunction(scope.block, reference.identifier)))) {
282
+ return true;
283
+ }
284
+ if (ratio >= 1) {
285
+ return true;
286
+ }
287
+ if (ratio <= 0) {
288
+ return false;
289
+ }
290
+ var blockType = scope && scope.block && scope.block.type || "";
291
+ var variableNames = movableVariables.map(variable => variable.name).sort().join(",");
292
+ var score = hashString32(`${seed}:${index}:${scope.type}:${blockType}:${variableNames}`) / 0x100000000;
293
+ return score < ratio;
294
+ }
295
+
296
+ function rewriteKnownReferences(node) {
297
+ if (!node) {
298
+ return node;
299
+ }
300
+ return traverser.traverse(node, [], (child, stack) => {
301
+ if (isNumericVmInternalFunction(stack)) {
302
+ return child;
303
+ }
304
+ if (child.type == "Identifier" && replacements.has(child)) {
305
+ markPropertyValueReplacement(stack);
306
+ return cloneReplacement(replacements.get(child));
307
+ }
308
+ return child;
309
+ });
310
+ }
311
+
312
+ scopeManager.scopes.forEach((scope, scopeIndex) => {
313
+ if (!this.esutils.canInsertIntoScope(scope) || isClassMethodScope(scope) || isNumericVmInternalScope(scope)) {
314
+ return;
315
+ }
316
+ var movableVariables = scope.variables.filter(isMovableVariable);
317
+ if (movableVariables.length == 0) {
318
+ return;
319
+ }
320
+ if (!shouldFlattenScope(scope, movableVariables, scopeIndex)) {
52
321
  return;
53
322
  }
54
323
  var scopeVarName = `$$scope$${rngAlpha.get()}`;
55
324
 
56
325
  var counter = 0;
326
+ var indexes = new Map();
327
+ var localReplacementsByName = new Map();
328
+ movableVariables.forEach(variable => {
329
+ indexes.set(variable, counter++);
330
+ });
331
+
332
+ movableVariables.forEach(variable => {
333
+ var index = indexes.get(variable);
334
+ variable.defs.forEach(def => {
335
+ if (def.type == "Variable") {
336
+ var replacement = scopeReference(scopeVarName, index);
337
+ localReplacementsByName.set(variable.name, replacement);
338
+ referencesFor(variable).forEach(reference => {
339
+ replacements.set(reference.identifier, scopeReference(scopeVarName, index));
340
+ });
341
+ } else if (def.type == "CatchClause") {
342
+ referencesFor(variable).forEach(reference => {
343
+ replacements.set(reference.identifier, scopeReference(scopeVarName, index));
344
+ });
345
+ } else if (def.type == "FunctionName" && def.node.type != "FunctionExpression") {
346
+ referencesFor(variable).forEach(reference => {
347
+ replacements.set(reference.identifier, {
348
+ type: "CallExpression",
349
+ callee: { type: "Identifier", name: "toildefender$bind" },
350
+ arguments: [
351
+ { type: "Identifier", name: reference.identifier.name },
352
+ { type: "Identifier", name: scopeVarName }
353
+ ]
354
+ });
355
+ });
356
+ }
357
+ });
358
+ });
359
+
360
+ var rewriteLocalReferencesByName = () => {
361
+ this.esutils.setParentsRecursive(scope.block);
362
+ traverser.traverse(scope.block, [], (node, stack) => {
363
+ if (isNumericVmInternalFunction(stack)) {
364
+ return node;
365
+ }
366
+ if (isInsideNestedScope(stack, scope.block, scopeBlocks)) {
367
+ return node;
368
+ }
369
+ if (node.type == "Identifier" && isReferenceIdentifier(node, stack)) {
370
+ var replacement = localReplacementsByName.get(node.name);
371
+ if (replacement) {
372
+ markPropertyValueReplacement(stack);
373
+ return cloneReplacement(replacement);
374
+ }
375
+ }
376
+ return node;
377
+ });
378
+ };
379
+
57
380
  var scopeDecl = {
58
381
  type: "VariableDeclaration",
59
382
  kind: "var",
@@ -64,13 +387,16 @@ module.exports = class Scopes {
64
387
  init: { type: "ArrayExpression", elements: [] }
65
388
  }
66
389
  ],
67
- veilmark$scopeObject: true
390
+ toildefender$scopeObject: true
68
391
  };
69
392
 
70
393
  this.esutils.insertIntoScope(scope, scopeDecl);
394
+ localReplacementsByName.forEach((replacement, name) => {
395
+ addFallbackReplacement(name, replacement, scope.block, scopeDecl);
396
+ });
71
397
 
72
- scope.variables.forEach(variable => {
73
- var index = counter++;
398
+ movableVariables.forEach(variable => {
399
+ var index = indexes.get(variable);
74
400
 
75
401
  variable.defs.forEach(def => {
76
402
  if (def.type == "Variable") {
@@ -88,9 +414,9 @@ module.exports = class Scopes {
88
414
  object: { type: "Identifier", name: scopeVarName },
89
415
  property: { type: "Literal", value: index },
90
416
  computed: true,
91
- veilmark$scopeObjectReference: true
417
+ toildefender$scopeObjectReference: true
92
418
  },
93
- right: def.node.init
419
+ right: rewriteKnownReferences(def.node.init)
94
420
  }
95
421
  });
96
422
  }
@@ -104,25 +430,13 @@ module.exports = class Scopes {
104
430
  } else {
105
431
  this.esutils.replaceNode(scope.block, def.parent, { type: "BlockStatement", body: replacement });
106
432
  }
107
- variable.references.forEach(reference => {
433
+ referencesFor(variable).forEach(reference => {
108
434
  // References can not be replaced via replaceNodeEx for whatever reason
109
- this.esutils.replaceNode(scope.block, reference.identifier, {
110
- type: "MemberExpression",
111
- object: { type: "Identifier", name: scopeVarName },
112
- property: { type: "Literal", value: index },
113
- computed: true,
114
- veilmark$scopeObjectReference: true
115
- });
435
+ this.esutils.replaceNode(scope.block, reference.identifier, cloneReplacement(replacements.get(reference.identifier) || scopeReference(scopeVarName, index)));
116
436
  });
117
437
  } else if (def.type == "CatchClause") {
118
- Object.defineProperty(scope.block, "veilmark$exception", {
119
- value: {
120
- type: "MemberExpression",
121
- object: { type: "Identifier", name: scopeVarName },
122
- property: { type: "Literal", value: index },
123
- computed: true,
124
- veilmark$scopeObjectReference: true
125
- },
438
+ Object.defineProperty(scope.block, "toildefender$exception", {
439
+ value: scopeReference(scopeVarName, index),
126
440
  configurable: true
127
441
  });
128
442
  this.esutils.insertIntoScope(scope, {
@@ -130,49 +444,32 @@ module.exports = class Scopes {
130
444
  expression: {
131
445
  type: "AssignmentExpression",
132
446
  operator: "=",
133
- left: {
134
- type: "MemberExpression",
135
- object: { type: "Identifier", name: scopeVarName },
136
- property: { type: "Literal", value: index },
137
- computed: true,
138
- veilmark$scopeObjectReference: true
139
- },
447
+ left: scopeReference(scopeVarName, index),
140
448
  right: def.name
141
449
  }
142
450
  }, 1);
143
- variable.references.forEach(reference => {
144
- this.esutils.replaceNode(scope.block, reference.identifier, {
145
- type: "MemberExpression",
146
- object: { type: "Identifier", name: scopeVarName },
147
- property: { type: "Literal", value: index },
148
- computed: true,
149
- veilmark$scopeObjectReference: true
150
- });
451
+ referencesFor(variable).forEach(reference => {
452
+ this.esutils.replaceNode(scope.block, reference.identifier, cloneReplacement(replacements.get(reference.identifier) || scopeReference(scopeVarName, index)));
151
453
  });
152
454
  } else if (def.type == "FunctionName") {
153
455
  if (def.node.type == "FunctionExpression") {
154
456
  return;
155
457
  }
156
- variable.references.forEach(reference => {
157
- this.esutils.replaceNode(scope.block, reference.identifier, {
158
- type: "CallExpression",
159
- callee: { type: "Identifier", name: "veilmark$bind" },
160
- arguments: [
161
- reference.identifier,
162
- { type: "Identifier", name: scopeVarName }
163
- ]
164
- });
458
+ referencesFor(variable).forEach(reference => {
459
+ this.esutils.replaceNode(scope.block, reference.identifier, cloneReplacement(replacements.get(reference.identifier)));
165
460
  });
166
461
  }
167
462
  });
168
463
  });
464
+
465
+ rewriteLocalReferencesByName();
169
466
 
170
467
  traverser.traverse(scope.block, [], (node, stack) => {
171
468
  if (scope.block == node) {
172
469
  return node;
173
470
  }
174
471
 
175
- if (isClassMethodFunction(stack)) {
472
+ if (isClassMethodFunction(stack) || isNumericVmInternalFunction(stack)) {
176
473
  return node;
177
474
  }
178
475
 
@@ -186,7 +483,7 @@ module.exports = class Scopes {
186
483
  if (node.type == "FunctionExpression") {
187
484
  return {
188
485
  type: "CallExpression",
189
- callee: { type: "Identifier", name: "veilmark$bind" },
486
+ callee: { type: "Identifier", name: "toildefender$bind" },
190
487
  arguments: [
191
488
  node,
192
489
  { type: "Identifier", name: scopeVarName }
@@ -197,6 +494,25 @@ module.exports = class Scopes {
197
494
  return node;
198
495
  });
199
496
  });
497
+
498
+ this.esutils.setParentsRecursive(ast);
499
+ traverser.traverse(ast, [], (node, stack) => {
500
+ var parentFrame = stack[1];
501
+ if (
502
+ node.type == "Identifier" &&
503
+ node.name.indexOf("$$var$") == 0 &&
504
+ parentFrame &&
505
+ parentFrame.node.type == "CallExpression" &&
506
+ parentFrame.key == "callee" &&
507
+ isReferenceIdentifier(node, stack)
508
+ ) {
509
+ var replacement = fallbackReplacementForName(node.name, node);
510
+ if (replacement) {
511
+ return cloneReplacement(replacement);
512
+ }
513
+ }
514
+ return node;
515
+ });
200
516
  }
201
517
 
202
518
  };
@@ -81,10 +81,29 @@ function shortName(index) {
81
81
  return name;
82
82
  }
83
83
 
84
- function isRenamableVariable(scope, variable) {
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) {
85
97
  if (scope.type == "global") {
86
98
  return false;
87
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
+ }
88
107
  if (variable.name == "arguments" || variable.name == "undefined") {
89
108
  return false;
90
109
  }
@@ -161,10 +180,11 @@ function modernMangle(ast) {
161
180
  sourceType: "script"
162
181
  });
163
182
 
183
+ var unresolvedNames = collectUnresolvedNames(scopeManager);
164
184
  var variables = [];
165
185
  scopeManager.scopes.forEach(scope => {
166
186
  scope.variables.forEach(variable => {
167
- if (isRenamableVariable(scope, variable)) {
187
+ if (isRenamableVariable(scope, variable, unresolvedNames)) {
168
188
  variables.push({ scope: scope, variable: variable });
169
189
  }
170
190
  });