@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.
package/README.md CHANGED
@@ -158,7 +158,7 @@ function licenseGate(input) {
158
158
  : { ok: false, total: total - 5 };
159
159
  }
160
160
 
161
- globalThis.__result = licenseGate("Veilmark");
161
+ globalThis.__result = licenseGate("ToilDefender");
162
162
  ```
163
163
 
164
164
  The demo artifact is generated with every major protection enabled and
package/esutils.js CHANGED
@@ -11,7 +11,7 @@ module.exports = function (logger) {
11
11
  assert.ok(estest.isNode(node));
12
12
 
13
13
  traverser.visitChildrenEx(node, (child, key) => {
14
- Object.defineProperty(child, "veilmark$parent", {
14
+ Object.defineProperty(child, "toildefender$parent", {
15
15
  value: node,
16
16
  configurable: true
17
17
  });
@@ -23,7 +23,7 @@ module.exports = function (logger) {
23
23
  assert.ok(estest.isNode(node));
24
24
 
25
25
  traverser.visitChildrenEx(node, (child, key) => {
26
- Object.defineProperty(child, "veilmark$parent", {
26
+ Object.defineProperty(child, "toildefender$parent", {
27
27
  value: node,
28
28
  configurable: true
29
29
  });
@@ -50,14 +50,14 @@ module.exports = function (logger) {
50
50
  if (scope.block.body && (scope.block.body.type == "Program" || scope.block.body.type == "BlockStatement")) {
51
51
  scope.block.body.body.splice(idx, 0, node);
52
52
 
53
- Object.defineProperty(node, "veilmark$parent", {
53
+ Object.defineProperty(node, "toildefender$parent", {
54
54
  value: scope.block.body,
55
55
  configurable: true
56
56
  });
57
57
  } else if (scope.block.type == "Program" || scope.block.type == "BlockStatement") {
58
58
  scope.block.body.splice(idx, 0, node);
59
59
 
60
- Object.defineProperty(node, "veilmark$parent", {
60
+ Object.defineProperty(node, "toildefender$parent", {
61
61
  value: scope.block,
62
62
  configurable: true
63
63
  });
@@ -74,12 +74,16 @@ module.exports = function (logger) {
74
74
  assert.equal(estest.isExpression(child), estest.isExpression(replacement), `Replacee ${child.type} is not of the same type as replacement ${replacement.type}`);
75
75
 
76
76
  var _this = this;
77
- root = this.getParent(child) || root;
77
+ var parent = this.getParent(child);
78
+ if (parent && parent.type == "Property" && parent.shorthand === true && parent.value == child) {
79
+ parent.shorthand = false;
80
+ }
81
+ root = parent || root;
78
82
  traverser.traverseEx(root, [], function (node, stack) {
79
83
  if (node == child) {
80
84
  this.abort();
81
- Object.defineProperty(replacement, "veilmark$parent", {
82
- value: child.veilmark$parent,
85
+ Object.defineProperty(replacement, "toildefender$parent", {
86
+ value: child.toildefender$parent,
83
87
  configurable: true
84
88
  });
85
89
  _this.setParents(replacement);
@@ -93,7 +97,7 @@ module.exports = function (logger) {
93
97
  this.getParent = function (node) {
94
98
  assert.ok(estest.isNode(node));
95
99
 
96
- var parent = node.veilmark$parent;
100
+ var parent = node.toildefender$parent;
97
101
  var legit = false;
98
102
  if (parent) {
99
103
  traverser.visitChildren(parent, child => {
package/obfuscator.js CHANGED
@@ -76,6 +76,14 @@ var defaultOptions = {
76
76
  },
77
77
  virtualize: "marked"
78
78
  },
79
+ controlFlow: {
80
+ ratio: 1,
81
+ seed: "toildefender-control-flow"
82
+ },
83
+ scope: {
84
+ ratio: 1,
85
+ seed: "toildefender-scope"
86
+ },
79
87
  protections: {
80
88
  virtualMachine: {
81
89
  bigintBytecode: true,
@@ -111,6 +119,25 @@ var featureDeps = {
111
119
  compress: [ "mangle" ]
112
120
  };
113
121
 
122
+ function isNumericVmInternalNode(node) {
123
+ return node && node.toildefender$numericVmInternal === true;
124
+ }
125
+
126
+ function takeNumericVmInternalStatements(ast) {
127
+ if (!ast || ast.type != "Program") {
128
+ return [];
129
+ }
130
+ var retained = [];
131
+ ast.body = ast.body.filter(statement => {
132
+ if (isNumericVmInternalNode(statement)) {
133
+ retained.push(statement);
134
+ return false;
135
+ }
136
+ return true;
137
+ });
138
+ return retained;
139
+ }
140
+
114
141
  var featureDescs = {
115
142
  dead_code: {
116
143
  en: "Insert dead code"
@@ -365,7 +392,7 @@ exports.do = function (options) {
365
392
  }
366
393
 
367
394
  function hasMangleUnsupportedSyntax(root) {
368
- return containsNodeType(root, [ "Super" ]);
395
+ return false;
369
396
  }
370
397
 
371
398
  function dispatcherForMethod(method) {
@@ -380,6 +407,35 @@ exports.do = function (options) {
380
407
  }
381
408
  return "main";
382
409
  }
410
+
411
+ function normalizeRatio(value) {
412
+ var ratio = Number(value);
413
+ if (!Number.isFinite(ratio)) {
414
+ return 1;
415
+ }
416
+ if (ratio < 0) {
417
+ return 0;
418
+ }
419
+ if (ratio > 1) {
420
+ return 1;
421
+ }
422
+ return ratio;
423
+ }
424
+
425
+ function hashString32(value) {
426
+ var h = 0x811c9dc5;
427
+ for (var i = 0; i < value.length; i += 1) {
428
+ h ^= value.charCodeAt(i);
429
+ h = Math.imul(h, 0x01000193) >>> 0;
430
+ }
431
+ return h >>> 0;
432
+ }
433
+
434
+ function methodControlFlowScore(method, index) {
435
+ var name = method && method.id && method.id.name || "";
436
+ var seed = options.controlFlow && options.controlFlow.seed || "";
437
+ return hashString32(`${seed}:${index}:${name}`) / 0x100000000;
438
+ }
383
439
 
384
440
  options = _.merge({}, defaultOptions, options); // first argument gets mutated
385
441
  if (options.protections.virtualMachine.enabled) {
@@ -407,11 +463,19 @@ exports.do = function (options) {
407
463
  } else {
408
464
  options.features = options.forceFeatures;
409
465
  }
466
+ var controlFlowRatio = normalizeRatio(options.controlFlow && options.controlFlow.ratio);
467
+ var controlFlowActive = options.features.control_flow && controlFlowRatio > 0;
468
+ var scopeRatio = normalizeRatio(options.scope && options.scope.ratio);
410
469
 
411
470
  var parseOptions = {};
412
471
  var scopeOptions = {
413
472
  optimistic: true // required or things in the global scope just get lost
414
473
  };
474
+ var lexicalScopeOptions = {
475
+ ecmaVersion: 6,
476
+ optimistic: true,
477
+ sourceType: "script"
478
+ };
415
479
 
416
480
  var logger = new Logger(options.logAdapter);
417
481
  var customBindAdded = false;
@@ -504,13 +568,17 @@ exports.do = function (options) {
504
568
  var variables = new prVariables(logger);
505
569
  variables.removeFunctionExpressionIds(ast);
506
570
  variables.functionDeclarationToExpression(ast, escope.analyze(ast, scopeOptions));
507
- variables.obfuscateIdentifiers(ast, escope.analyze(ast, scopeOptions));
571
+ variables.obfuscateIdentifiers(ast, escope.analyze(ast, lexicalScopeOptions));
508
572
  variables.redefineParameters(ast, escope.analyze(ast, scopeOptions));
509
573
  });
510
574
 
511
575
  // Move identifiers into scope objects
512
576
  doTask("create_scope_objects", true, () => {
513
- scopes.createScopeObjects(ast, escope.analyze(ast, scopeOptions));
577
+ scopes.createScopeObjects(ast, escope.analyze(ast, lexicalScopeOptions), {
578
+ ratio: scopeRatio,
579
+ seed: options.scope && options.scope.seed || "toildefender-scope",
580
+ forceProgram: controlFlowActive
581
+ });
514
582
  });
515
583
 
516
584
  // Calculate entry points for all methods
@@ -526,7 +594,7 @@ exports.do = function (options) {
526
594
  // Extract function declarations and expressions
527
595
  var fns;
528
596
  doTask("extract_methods", true, () => {
529
- var scopeManager = escope.analyze(ast, scopeOptions);
597
+ var scopeManager = escope.analyze(ast, lexicalScopeOptions);
530
598
  fns = methods.extractMethods(ast);
531
599
  fns = fns.map(method => {
532
600
  var refers = methods.methodRefersToArguments(method, scopeManager);
@@ -538,15 +606,25 @@ exports.do = function (options) {
538
606
  methodEntryPoints[method.id.name].dispatcher = dispatcherForMethod(method);
539
607
  }
540
608
  });
541
- if (options.features.control_flow) {
542
- methods.replaceFunctionCalls(ast, methodEntryPoints);
609
+ var selectedMethodEntryPoints = {};
610
+ fns.forEach((method, index) => {
611
+ if (!method || !method.id || !methodEntryPoints[method.id.name]) {
612
+ return;
613
+ }
614
+ if (controlFlowRatio >= 1 || methodControlFlowScore(method, index) < controlFlowRatio) {
615
+ selectedMethodEntryPoints[method.id.name] = methodEntryPoints[method.id.name];
616
+ }
617
+ });
618
+
619
+ if (controlFlowActive) {
620
+ methods.replaceFunctionCalls(ast, selectedMethodEntryPoints);
543
621
  fns.forEach(method => {
544
- methods.replaceFunctionCalls(method.body, methodEntryPoints);
622
+ methods.replaceFunctionCalls(method.body, selectedMethodEntryPoints);
545
623
  });
546
624
  }
547
625
  });
548
626
 
549
- doTask("control_flow", options.features.control_flow, () => {
627
+ doTask("control_flow", controlFlowActive, () => {
550
628
  // Apply control flow flattening and merge methods
551
629
  var flattener = new prFlattener(logger, rng);
552
630
  var entry = rng.get(), exit = rng.get();
@@ -566,8 +644,15 @@ exports.do = function (options) {
566
644
  }
567
645
  };
568
646
  var syncFns = [];
647
+ var retainedFns = [];
648
+ var retainedInternalFns = takeNumericVmInternalStatements(ast);
569
649
 
570
- fns.forEach(method => {
650
+ fns.forEach((method, index) => {
651
+ var selected = controlFlowRatio >= 1 || methodControlFlowScore(method, index) < controlFlowRatio;
652
+ if (!selected) {
653
+ retainedFns.push(method);
654
+ return;
655
+ }
571
656
  var dispatcher = dispatcherForMethod(method);
572
657
  if (dispatcher == "main") {
573
658
  syncFns.push(method);
@@ -623,19 +708,23 @@ exports.do = function (options) {
623
708
  if (asyncPrograms.length > 0) {
624
709
  ast = {
625
710
  type: "Program",
626
- body: Array.prototype.concat.apply([], asyncPrograms.map(program => program.body)).concat(syncAst.body)
711
+ body: retainedInternalFns.concat(retainedFns).concat(Array.prototype.concat.apply([], asyncPrograms.map(program => program.body)).concat(syncAst.body))
627
712
  };
628
713
  } else {
629
- ast = syncAst;
714
+ ast = {
715
+ type: "Program",
716
+ body: retainedInternalFns.concat(retainedFns).concat(syncAst.body)
717
+ };
630
718
  }
631
719
  })
632
720
  .otherwise(() => {
721
+ var retainedInternalFns = takeNumericVmInternalStatements(ast);
633
722
  if (ast.type == "Program") {
634
723
  ast.type = "BlockStatement";
635
724
  }
636
725
  ast = {
637
726
  type: "Program",
638
- body: fns.concat([ ast ])
727
+ body: retainedInternalFns.concat(fns).concat([ ast ])
639
728
  };
640
729
  });
641
730
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dacely/toildefender",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Modern JavaScript code protection, bytecode virtualization, and obfuscation for the Toil tech stack.",
5
5
  "author": "Dacely",
6
6
  "contributors": [
@@ -13,6 +13,33 @@ function isClassMethodBody(stack) {
13
13
  return stack.some(frame => frame.node.type == "MethodDefinition" || frame.node.type == "ClassBody");
14
14
  }
15
15
 
16
+ function containsLexicalDeclaration(node) {
17
+ if (
18
+ node.type == "ClassDeclaration" ||
19
+ node.type == "FunctionDeclaration" ||
20
+ (node.type == "VariableDeclaration" && node.kind != "var")
21
+ ) {
22
+ return true;
23
+ }
24
+
25
+ var found = false;
26
+ traverser.traverseEx(node, [], function (child) {
27
+ if (child != node && estest.isFunction(child)) {
28
+ return child;
29
+ }
30
+ if (
31
+ child.type == "ClassDeclaration" ||
32
+ child.type == "FunctionDeclaration" ||
33
+ (child.type == "VariableDeclaration" && child.kind != "var")
34
+ ) {
35
+ found = true;
36
+ this.abort();
37
+ }
38
+ return child;
39
+ });
40
+ return found;
41
+ }
42
+
16
43
  module.exports = class DeadCode {
17
44
 
18
45
  constructor (logger) {
@@ -41,6 +68,11 @@ module.exports = class DeadCode {
41
68
 
42
69
  var varValue = _.sample(KEYWORDS);
43
70
 
71
+ var selected = node.body.slice(pos, pos + len);
72
+ if (selected.some(containsLexicalDeclaration)) {
73
+ continue;
74
+ }
75
+
44
76
  var spliced = node.body.splice(pos, len);
45
77
  node.body.splice(pos, 0,
46
78
  {
@@ -173,7 +173,7 @@ module.exports = class Flattener {
173
173
  expression: {
174
174
  type: "AssignmentExpression",
175
175
  operator: "=",
176
- left: { type: "Identifier", name: "veilmark$tobethrown" },
176
+ left: { type: "Identifier", name: "toildefender$tobethrown" },
177
177
  right: { type: "Literal", value: null }
178
178
  }
179
179
  },
@@ -229,7 +229,7 @@ module.exports = class Flattener {
229
229
  declarations: [
230
230
  {
231
231
  type: "VariableDeclarator",
232
- id: { type: "Identifier", name: "veilmark$tobethrown" },
232
+ id: { type: "Identifier", name: "toildefender$tobethrown" },
233
233
  init: null
234
234
  }
235
235
  ]
@@ -742,7 +742,7 @@ module.exports = class Flattener {
742
742
  expression: {
743
743
  type: "AssignmentExpression",
744
744
  operator: "=",
745
- left: node.handler.veilmark$exception,
745
+ left: node.handler.toildefender$exception,
746
746
  right: { type: "Identifier", name: "e" }
747
747
  }
748
748
  },
@@ -777,16 +777,81 @@ module.exports = class Flattener {
777
777
  * @returns {Node}
778
778
  */
779
779
  unifyPrefixStatements (ast) {
780
+ var scopeObjects = new Map();
780
781
  var maximumScopeIndex = 0;
782
+
783
+ function scopeNameFromReference(node) {
784
+ if (
785
+ node &&
786
+ node.type == "MemberExpression" &&
787
+ node.object &&
788
+ node.object.type == "Identifier" &&
789
+ _.startsWith(node.object.name, "$$scope") &&
790
+ node.property &&
791
+ node.property.type == "Literal" &&
792
+ typeof node.property.value == "number"
793
+ ) {
794
+ return node.object.name;
795
+ }
796
+ return null;
797
+ }
798
+
799
+ function ensureScopeObject(name) {
800
+ if (!scopeObjects.has(name)) {
801
+ scopeObjects.set(name, {
802
+ max: -1,
803
+ offset: 0
804
+ });
805
+ }
806
+ return scopeObjects.get(name);
807
+ }
808
+
809
+ traverser.traverse(ast, [], (node, stack) => {
810
+ if (node.toildefender$scopeObject) {
811
+ var declaration = node.declarations && node.declarations[0];
812
+ if (declaration && declaration.id && declaration.id.type == "Identifier") {
813
+ ensureScopeObject(declaration.id.name);
814
+ }
815
+ } else if (node.toildefender$scopeObjectReference) {
816
+ var name = scopeNameFromReference(node);
817
+ if (name) {
818
+ var info = ensureScopeObject(name);
819
+ info.max = Math.max(info.max, node.property.value);
820
+ }
821
+ }
822
+ return node;
823
+ });
824
+
825
+ if (scopeObjects.size > 1) {
826
+ return ast;
827
+ }
828
+
829
+ var nextScopeOffset = 0;
830
+ scopeObjects.forEach(info => {
831
+ info.offset = nextScopeOffset;
832
+ nextScopeOffset += info.max + 1;
833
+ });
781
834
 
782
835
  ast = traverser.traverse(ast, [], (node, stack) => {
783
- if (node.veilmark$reassigningArguments && !node.veilmark$followsSlicingArguments) {
836
+ if (node.toildefender$reassigningArguments && !node.toildefender$followsSlicingArguments) {
784
837
  node = { type: "EmptyStatement" };
785
- } else if (node.veilmark$scopeObject) {
838
+ } else if (node.toildefender$scopeObject) {
786
839
  node = { type: "EmptyStatement" };
787
- } else if (node.veilmark$scopeObjectReference) {
788
- maximumScopeIndex = Math.max(maximumScopeIndex, node.property.value);
840
+ } else if (node.toildefender$scopeObjectReference) {
841
+ var name = scopeNameFromReference(node);
842
+ var info = name ? scopeObjects.get(name) : null;
843
+ if (info) {
844
+ node.property.value += info.offset;
845
+ maximumScopeIndex = Math.max(maximumScopeIndex, node.property.value);
846
+ }
847
+ if (node.object && node.object.type == "Identifier") {
848
+ node.object.name = "$$unifiedScope";
849
+ }
789
850
  } else if (node.type == "Identifier" && _.startsWith(node.name, "$$scope")) {
851
+ var parent = stack[1] && stack[1].node;
852
+ if (parent && parent.toildefender$scopeObjectReference) {
853
+ return node;
854
+ }
790
855
  node.name = "$$unifiedScope";
791
856
  }
792
857
  return node;
@@ -819,7 +884,7 @@ module.exports = class Flattener {
819
884
  declarations: [
820
885
  {
821
886
  type: "VariableDeclarator",
822
- id: { type: "Identifier", name: "veilmark$arguments" },
887
+ id: { type: "Identifier", name: "toildefender$arguments" },
823
888
  init: { type: "Identifier", name: "arguments" }
824
889
  }
825
890
  ]
@@ -33,6 +33,17 @@ function isBigIntLiteral(node) {
33
33
  return node.type == "Literal" && typeof node.value == "bigint";
34
34
  }
35
35
 
36
+ function canMoveLiteral(node) {
37
+ if (node.type != "Literal" || isBigIntLiteral(node) || node.regex) {
38
+ return false;
39
+ }
40
+ return typeof node.value == "string";
41
+ }
42
+
43
+ function isNumericVmInternalFunction(stack) {
44
+ return stack.some(frame => frame.node && frame.node.toildefender$numericVmInternal === true);
45
+ }
46
+
36
47
  module.exports = class Identifiers {
37
48
 
38
49
  constructor (logger) {
@@ -65,6 +76,9 @@ module.exports = class Identifiers {
65
76
  assert.ok(estest.isNode(ast));
66
77
 
67
78
  ast = traverser.traverse(ast, [], (node, stack) => {
79
+ if (isNumericVmInternalFunction(stack)) {
80
+ return node;
81
+ }
68
82
  if (node.type == "MemberExpression"
69
83
  && !node.computed) {
70
84
  assert(node.property.type == "Identifier");
@@ -79,7 +93,7 @@ module.exports = class Identifiers {
79
93
  }
80
94
 
81
95
  /**
82
- * Replace objects with an array via veilmark$toObject.
96
+ * Replace objects with an array via toildefender$toObject.
83
97
  * @param {Node} ast Root node
84
98
  * @returns {Node} Root node
85
99
  */
@@ -88,6 +102,9 @@ module.exports = class Identifiers {
88
102
  options = options || {};
89
103
 
90
104
  ast = traverser.traverse(ast, [], (node, stack) => {
105
+ if (isNumericVmInternalFunction(stack)) {
106
+ return node;
107
+ }
91
108
  if (node.type == "ObjectExpression") {
92
109
  if (options.objectPacking === false) {
93
110
  return node;
@@ -108,7 +125,7 @@ module.exports = class Identifiers {
108
125
 
109
126
  return {
110
127
  type: "CallExpression",
111
- callee: { type: "Identifier", name: "veilmark$toObject" },
128
+ callee: { type: "Identifier", name: "toildefender$toObject" },
112
129
  arguments: [
113
130
  literal(String(utils.hash(schema.join(",")))),
114
131
  {
@@ -198,7 +215,7 @@ module.exports = class Identifiers {
198
215
  }
199
216
 
200
217
  /**
201
- * Move all literals into the veilmark$literals array.
218
+ * Move all literals into the toildefender$literals array.
202
219
  * @param {Node} ast Root node
203
220
  * @param {ScopeManager} scopeManager Scope manager
204
221
  * @returns {Node} Root node
@@ -211,7 +228,10 @@ module.exports = class Identifiers {
211
228
  var vars = [];
212
229
 
213
230
  ast = traverser.traverse(ast, [], (node, stack) => {
214
- if (node.type == "Literal" && !isBigIntLiteral(node) && stack.length > 0 && stack[1].node.type != "Property") {
231
+ if (isNumericVmInternalFunction(stack)) {
232
+ return node;
233
+ }
234
+ if (canMoveLiteral(node) && stack.length > 0 && stack[1].node.type != "Property") {
215
235
  var idx = vars.indexOf(node.value);
216
236
  if (idx == -1) {
217
237
  idx = vars.length;
@@ -220,7 +240,7 @@ module.exports = class Identifiers {
220
240
 
221
241
  return {
222
242
  type: "MemberExpression",
223
- object: { type: "Identifier", name: "veilmark$literals" },
243
+ object: { type: "Identifier", name: "toildefender$literals" },
224
244
  property: { type: "Literal", value: idx },
225
245
  computed: true
226
246
  };
@@ -235,7 +255,7 @@ module.exports = class Identifiers {
235
255
  declarations: [
236
256
  {
237
257
  type: "VariableDeclarator",
238
- id: { type: "Identifier", name: "veilmark$literals" },
258
+ id: { type: "Identifier", name: "toildefender$literals" },
239
259
  init: {
240
260
  type: "ArrayExpression",
241
261
  elements: vars.map(x => ({ type: "Literal", value: x }))
@@ -147,11 +147,79 @@ function makeStringByteArrayCall(str) {
147
147
 
148
148
  return {
149
149
  type: "CallExpression",
150
- callee: { type: "Identifier", name: "veilmark$fromCharCodes" },
150
+ callee: { type: "Identifier", name: "toildefender$fromCharCodes" },
151
151
  arguments: str.split("").map(x => ({ type: "Literal", value: x.charCodeAt() }))
152
152
  };
153
153
  }
154
154
 
155
+ function isUnencodedPropertyKey(stack) {
156
+ var parentFrame = stack[1];
157
+ if (!parentFrame || parentFrame.node.type != "Property") {
158
+ return false;
159
+ }
160
+ return parentFrame.key == "key" && parentFrame.node.computed !== true;
161
+ }
162
+
163
+ function isNumericVmInternalFunction(stack) {
164
+ return stack.some(frame => frame.node && frame.node.toildefender$numericVmInternal === true);
165
+ }
166
+
167
+ function makeStringExpression(str) {
168
+ if (str.length == 0) {
169
+ return { type: "Literal", value: "" };
170
+ }
171
+ return makeStringGenerator(str);
172
+ }
173
+
174
+ function makeStringCallExpression(expr) {
175
+ return {
176
+ type: "CallExpression",
177
+ callee: { type: "Identifier", name: "String" },
178
+ arguments: [expr]
179
+ };
180
+ }
181
+
182
+ function concatExpressions(left, right) {
183
+ return {
184
+ type: "BinaryExpression",
185
+ operator: "+",
186
+ left: left,
187
+ right: right
188
+ };
189
+ }
190
+
191
+ function makeTemplateExpression(node) {
192
+ assert.equal(node.type, "TemplateLiteral");
193
+
194
+ var expression;
195
+ for (var i = 0; i < node.quasis.length; i += 1) {
196
+ var quasi = node.quasis[i];
197
+ var cooked = quasi.value && typeof quasi.value.cooked == "string" ? quasi.value.cooked : "";
198
+ var quasiExpression = makeStringExpression(cooked);
199
+ expression = expression ? concatExpressions(expression, quasiExpression) : quasiExpression;
200
+
201
+ if (i < node.expressions.length) {
202
+ expression = concatExpressions(expression, makeStringCallExpression(node.expressions[i]));
203
+ }
204
+ }
205
+
206
+ return expression || { type: "Literal", value: "" };
207
+ }
208
+
209
+ function makeRegexExpression(node) {
210
+ assert.equal(node.type, "Literal");
211
+ assert.ok(node.regex);
212
+
213
+ return {
214
+ type: "NewExpression",
215
+ callee: { type: "Identifier", name: "RegExp" },
216
+ arguments: [
217
+ makeStringExpression(node.regex.pattern || ""),
218
+ makeStringExpression(node.regex.flags || "")
219
+ ]
220
+ };
221
+ }
222
+
155
223
  module.exports = class Literals {
156
224
 
157
225
  constructor (logger) {
@@ -172,6 +240,9 @@ module.exports = class Literals {
172
240
  var stringMap = {};
173
241
 
174
242
  ast = traverser.traverse(ast, [], (node, stack) => {
243
+ if (isNumericVmInternalFunction(stack)) {
244
+ return node;
245
+ }
175
246
  if (node.type == "Literal" && typeof node.value == "string") {
176
247
  var idx = stringMap["_" + node.value];
177
248
  if (!idx) {
@@ -217,10 +288,19 @@ module.exports = class Literals {
217
288
  assert.ok(estest.isNode(ast));
218
289
 
219
290
  ast = traverser.traverse(ast, [], (node, stack) => {
291
+ if (isNumericVmInternalFunction(stack)) {
292
+ return node;
293
+ }
294
+ if (node.type == "TemplateLiteral") {
295
+ return makeTemplateExpression(node);
296
+ }
297
+ if (node.type == "Literal" && node.regex) {
298
+ return makeRegexExpression(node);
299
+ }
220
300
  if (node.type == "Literal"
221
301
  && typeof node.value == "string"
222
302
  && stack.length > 1
223
- && stack[1].node.type != "Property") {
303
+ && !isUnencodedPropertyKey(stack)) {
224
304
  return makeStringGenerator(node.value);
225
305
  }
226
306