@dacely/toildefender 0.1.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.
@@ -0,0 +1,808 @@
1
+ "use strict";
2
+
3
+ var assert = require("assert");
4
+ var events = require("events");
5
+
6
+ var _ = require("lodash");
7
+
8
+ var estest = require("../estest");
9
+ var traverser = require("../traverser");
10
+ var utils = require("../utils");
11
+
12
+ /**
13
+ * Push a SwitchCase onto an array while removing all identical SwitchCases
14
+ * @param {SwitchCase[]} arr
15
+ * @param {SwitchCase} elem
16
+ */
17
+ function pushUniqSwitchCase(arr, elem) {
18
+ _.remove(arr, x => x.test.value == elem.test.value);
19
+ arr.push(elem);
20
+ }
21
+
22
+ /**
23
+ * Shuffle SwitchCase statements while respecting fall troughs.
24
+ * @param entries {SwitchCase[]} Array of the unshuffled cases
25
+ * @returns {SwitchCase[]} New array of the shuffled cases
26
+ */
27
+ function shuffleSwitchCases(entries) {
28
+ var groups = [], stack = [];
29
+ function clearStack() {
30
+ if (stack.length > 0) {
31
+ groups.push(stack);
32
+ stack = [];
33
+ }
34
+ }
35
+ entries.forEach(entry => {
36
+ var breaks = entry.consequent.some(x => x.type == "BreakStatement");
37
+ if (breaks) {
38
+ clearStack();
39
+ groups.push([ entry ]);
40
+ } else {
41
+ stack.push(entry);
42
+ }
43
+ });
44
+ clearStack();
45
+ return Array.prototype.concat.apply([], _.shuffle(groups));
46
+ }
47
+
48
+ /**
49
+ * Merge nested BlockStatements (BlockStatements containing other BlockStatements)
50
+ * @param {BlockStatement} node Root BlockStatement
51
+ * @returns {BlockStatement} Merged BlockStatement
52
+ */
53
+ function mergeNestedBlocks(node) {
54
+ assert(estest.isNode(node));
55
+
56
+ function getBlockBodys(node) {
57
+ if (node.type == "Program" || node.type == "BlockStatement") {
58
+ var stmts = [];
59
+ node.body.forEach(stmt => utils.push(stmts, getBlockBodys(stmt)));
60
+ return stmts;
61
+ } else {
62
+ return [ node ];
63
+ }
64
+ }
65
+
66
+ return {
67
+ type: node.type,
68
+ body: getBlockBodys(node)
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Split array of statements into array of compound statements and BlockStatements containing an array of non-compound statements
74
+ * @param {Node[]} nodes Array of statements
75
+ * @returns {Statement[]} Array of Statements
76
+ */
77
+ function splitBlocks(nodes) {
78
+ var stack = [], output = [];
79
+ for (var i = 0; i < nodes.length; ++i) {
80
+ if (estest.isCompoundStatement(nodes[i])) {
81
+ if (stack.length > 0) {
82
+ output.push({
83
+ type: "BlockStatement",
84
+ body: stack
85
+ });
86
+ stack = [];
87
+ }
88
+ output.push(nodes[i]);
89
+ } else if (estest.isStatement(nodes[i])) {
90
+ stack.push(nodes[i]);
91
+ } else if (estest.isExpression(nodes[i])) {
92
+ this.logger.warn("Unexpected expression " + nodes[i].type);
93
+ stack.push(nodes[i]);
94
+ } else {
95
+ throw new Error("Illegal statement type " + nodes[i].type);
96
+ }
97
+ }
98
+ if (stack.length > 0) {
99
+ output.push({
100
+ type: "BlockStatement",
101
+ body: stack
102
+ });
103
+ }
104
+ return output;
105
+ }
106
+
107
+ module.exports = class Flattener {
108
+
109
+ constructor (logger, rng) {
110
+ this.logger = logger;
111
+ this.rng = rng;
112
+ this.emitter = new events.EventEmitter();
113
+ this.output = [];
114
+ this.handlers = [];
115
+ this.breaks = [];
116
+ this.continues = [];
117
+ }
118
+
119
+ /**
120
+ * Transform method
121
+ * @param {Statement} input Method body
122
+ * @param {number} entry Entry point
123
+ * @param {number} exit Exit point
124
+ */
125
+ addMethod (input, entry, exit) {
126
+ assert.ok(estest.isStatement(input));
127
+ assert.equal(typeof entry, "number");
128
+ assert.equal(typeof exit, "number");
129
+
130
+ this.transformStatement(input, entry, exit);
131
+ }
132
+
133
+ /**
134
+ * Get output switch construct
135
+ * @param {number} entry Entry point
136
+ * @param {number} exit Exit point
137
+ * @returns {Statement} Switch construct
138
+ */
139
+ getCases (entry, exit) {
140
+ assert.equal(typeof entry, "number");
141
+ assert.equal(typeof exit, "number");
142
+
143
+ return {
144
+ type: "TryStatement",
145
+ block: {
146
+ type: "BlockStatement",
147
+ body: [
148
+ {
149
+ type: "SwitchStatement",
150
+ discriminant: { type: "Identifier", name: "state" },
151
+ cases: shuffleSwitchCases(this.output.concat([
152
+ {
153
+ type: "SwitchCase",
154
+ test: { type: "Literal", value: exit },
155
+ consequent: [
156
+ {
157
+ type: "ReturnStatement"
158
+ }
159
+ ]
160
+ }
161
+ ]))
162
+ }
163
+ ]
164
+ },
165
+ handler: {
166
+ type: "CatchClause",
167
+ param: { type: "Identifier", name: "e" },
168
+ body: {
169
+ type: "BlockStatement",
170
+ body: [
171
+ {
172
+ type: "ExpressionStatement",
173
+ expression: {
174
+ type: "AssignmentExpression",
175
+ operator: "=",
176
+ left: { type: "Identifier", name: "veilmark$tobethrown" },
177
+ right: { type: "Literal", value: null }
178
+ }
179
+ },
180
+ {
181
+ type: "SwitchStatement",
182
+ discriminant: { type: "Identifier", name: "state" },
183
+ cases: this.handlers.concat({
184
+ type: "SwitchCase",
185
+ test: null,
186
+ consequent: [
187
+ {
188
+ type: "ThrowStatement",
189
+ argument: { type: "Identifier", name: "e" }
190
+ }
191
+ ]
192
+ })
193
+ }
194
+ ]
195
+ }
196
+ }
197
+ };
198
+ }
199
+
200
+ /**
201
+ * Get output switch construct program
202
+ * @param {number} entry Entry point
203
+ * @param {number} exit Exit point
204
+ * @returns {Program} Switch construct program
205
+ */
206
+ getProgram (entry, exit) {
207
+ assert.equal(typeof entry, "number");
208
+ assert.equal(typeof exit, "number");
209
+
210
+ return {
211
+ type: "Program",
212
+ body: [
213
+ {
214
+ type: "FunctionDeclaration",
215
+ id: { type: "Identifier", name: "main" },
216
+ params: [
217
+ { type: "Identifier", name: "state" },
218
+ { type: "Identifier", name: "scope" }
219
+ ],
220
+ body: {
221
+ type: "BlockStatement",
222
+ body: [
223
+ {
224
+ type: "WhileStatement",
225
+ test: { type: "Literal", value: true },
226
+ body: this.getCases(entry, exit)
227
+ }
228
+ ]
229
+ }
230
+ },
231
+ {
232
+ type: "ExpressionStatement",
233
+ expression: {
234
+ type: "CallExpression",
235
+ callee: { type: "Identifier", name: "main" },
236
+ arguments: [
237
+ { type: "Literal", value: entry },
238
+ { type: "ObjectExpression", properties: [] }
239
+ ]
240
+ }
241
+ }
242
+ ]
243
+ };
244
+ }
245
+
246
+ /**
247
+ * Import statement into control flow table
248
+ * @param {Statement} node
249
+ * @param {number} entry Entry point
250
+ * @param {number} exit Exit point
251
+ */
252
+ transformStatement (node, entry, exit) {
253
+ assert(estest.isStatement(node));
254
+ assert.equal(typeof entry, "number");
255
+ assert.equal(typeof exit, "number");
256
+
257
+ switch (node.type) {
258
+ case "Program":
259
+ case "BlockStatement": {
260
+ this.transformBlock(node, entry, exit);
261
+ break;
262
+ }
263
+ case "IfStatement": {
264
+ this.transformIf(node, entry, exit);
265
+ break;
266
+ }
267
+ case "WhileStatement": {
268
+ this.transformWhile(node, entry, exit);
269
+ break;
270
+ }
271
+ case "DoWhileStatement": {
272
+ this.transformDoWhile(node, entry, exit);
273
+ break;
274
+ }
275
+ case "SwitchStatement": {
276
+ this.transformSwitch(node, entry, exit);
277
+ break;
278
+ }
279
+ case "TryStatement": {
280
+ if (node.handler && !node.finalizer) {
281
+ this.transformTryCatch(node, entry, exit);
282
+ } else {
283
+ throw new Error("Not normalized");
284
+ }
285
+ break;
286
+ }
287
+ case "EmptyStatement": {
288
+ // Empty
289
+ break;
290
+ }
291
+ default: {
292
+ this.logger.warn("Unsupported type " + node.type);
293
+ // This might be not the most elegant solution (TODO?)
294
+ // This caused an infinite loop when SwitchStatement was not handled separately
295
+ this.transformBlock({ type: "BlockStatement", body: [ node ] }, entry, exit);
296
+ break;
297
+ }
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Import BlockStatement into control flow table
303
+ * @param {BlockStatement} node
304
+ * @param {number} entry Entry point
305
+ * @param {number} exit Exit point
306
+ */
307
+ transformBlock (node, entry, exit) {
308
+ assert.ok(node.type == "Program" || node.type == "BlockStatement");
309
+ assert.equal(typeof entry, "number");
310
+ assert.equal(typeof exit, "number");
311
+
312
+ assert(node.type == "Program" || node.type == "BlockStatement");
313
+
314
+ node = mergeNestedBlocks(node);
315
+ var blocks = splitBlocks(node.body);
316
+
317
+ for (var i = 0; i < blocks.length; ++i) {
318
+ if (blocks[i].type == "LabeledStatement") {
319
+ blocks[i].body.label = blocks.label;
320
+ blocks[i] = blocks[i].body;
321
+ }
322
+
323
+ if (!estest.isStatement(blocks[i])) {
324
+ console.warn(blocks[i].type + " is not a statement");
325
+ }
326
+
327
+ var partExit = i != blocks.length - 1 ? this.rng.get() : exit;
328
+ if (blocks[i].type == "BlockStatement") {
329
+ this.transformSequence(blocks[i], entry, partExit);
330
+ } else {
331
+ this.transformStatement(blocks[i], entry, partExit);
332
+ }
333
+ entry = partExit;
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Import sequence from splitBlocks into control flow table
339
+ * @param {BlockStatement} node
340
+ * @param {number} entry Entry point
341
+ * @param {number} exit Exit point
342
+ */
343
+ transformSequence (node, entry, exit) {
344
+ assert.equal(node.type, "BlockStatement");
345
+ assert.equal(typeof entry, "number");
346
+ assert.equal(typeof exit, "number");
347
+
348
+ var stmts = [];
349
+
350
+ var aborted = !node.body.every(stmt => {
351
+ assert(estest.isStatement(stmt), stmt.type + " is not a statement");
352
+
353
+ switch (stmt.type) {
354
+ case "BreakStatement": {
355
+ var break_;
356
+ if (stmt.label) {
357
+ break_ = _.find(this.breaks, x => x.label.name == stmt.label.name);
358
+ } else {
359
+ break_ = _.last(this.breaks);
360
+ }
361
+ assert(break_ && break_.id, "No break target");
362
+
363
+ stmts.push({
364
+ type: "ExpressionStatement",
365
+ expression: {
366
+ type: "AssignmentExpression",
367
+ operator: "=",
368
+ left: { type: "Identifier", name: "state" },
369
+ right: { type: "Literal", value: break_.id }
370
+ }
371
+ });
372
+ stmts.push({
373
+ type: "BreakStatement"
374
+ });
375
+
376
+ return false;
377
+ }
378
+ case "ContinueStatement": {
379
+ var continue_;
380
+ if (stmt.label) {
381
+ continue_ = _.find(this.continues, x => x.label.name == stmt.label.name);
382
+ } else {
383
+ continue_ = _.last(this.continues);
384
+ }
385
+ assert(continue_ && continue_.id, "No continue target");
386
+
387
+ stmts.push({
388
+ type: "ExpressionStatement",
389
+ expression: {
390
+ type: "AssignmentExpression",
391
+ operator: "=",
392
+ left: { type: "Identifier", name: "state" },
393
+ right: { type: "Literal", value: continue_.id }
394
+ }
395
+ });
396
+ stmts.push({
397
+ type: "BreakStatement"
398
+ });
399
+
400
+ return false;
401
+ }
402
+ case "ReturnStatement": {
403
+ stmts.push(stmt);
404
+
405
+ return false;
406
+ }
407
+ case "EmptyStatement": {
408
+ // Empty
409
+
410
+ return true;
411
+ }
412
+ default: {
413
+ stmts.push(stmt);
414
+
415
+ return true;
416
+ }
417
+ }
418
+ });
419
+
420
+ if (!aborted) {
421
+ stmts.push({
422
+ type: "ExpressionStatement",
423
+ expression: {
424
+ type: "AssignmentExpression",
425
+ operator: "=",
426
+ left: { type: "Identifier", name: "state" },
427
+ right: { type: "Literal", value: exit }
428
+ }
429
+ });
430
+ stmts.push({
431
+ type: "BreakStatement"
432
+ });
433
+ }
434
+
435
+ this.output.push({
436
+ type: "SwitchCase",
437
+ test: { type: "Literal", value: entry },
438
+ consequent: stmts
439
+ });
440
+ this.emitter.emit("branch", entry);
441
+ }
442
+
443
+ /**
444
+ * Import IfStatement into control flow table
445
+ * @param {IfStatement} node
446
+ * @param {number} entry Entry point
447
+ * @param {number} exit Exit point
448
+ */
449
+ transformIf (node, entry, exit) {
450
+ assert.equal(node.type, "IfStatement");
451
+ assert.equal(typeof entry, "number");
452
+ assert.equal(typeof exit, "number");
453
+
454
+ var thenEntry = this.rng.get();
455
+ var elseEntry = node.alternate ? this.rng.get() : exit;
456
+ this.output.push({
457
+ type: "SwitchCase",
458
+ test: { type: "Literal", value: entry },
459
+ consequent: [
460
+ {
461
+ type: "ExpressionStatement",
462
+ expression: {
463
+ type: "AssignmentExpression",
464
+ operator: "=",
465
+ left: { type: "Identifier", name: "state" },
466
+ right: {
467
+ type: "ConditionalExpression",
468
+ test: node.test,
469
+ consequent: { type: "Literal", value: thenEntry },
470
+ alternate: { type: "Literal", value: elseEntry }
471
+ }
472
+ }
473
+ },
474
+ {
475
+ type: "BreakStatement"
476
+ }
477
+ ]
478
+ });
479
+ this.emitter.emit("branch", entry);
480
+ this.transformStatement(node.consequent, thenEntry, exit);
481
+ if (node.alternate) {
482
+ this.transformStatement(node.alternate, elseEntry, exit);
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Import WhileStatement into control flow table
488
+ * @param {WhileStatement} node
489
+ * @param {number} entry Entry point
490
+ * @param {number} exit Exit point
491
+ */
492
+ transformWhile (node, entry, exit) {
493
+ assert.equal(node.type, "WhileStatement");
494
+ assert.equal(typeof entry, "number");
495
+ assert.equal(typeof exit, "number");
496
+
497
+ var bodyEntry = this.rng.get();
498
+ this.output.push({
499
+ type: "SwitchCase",
500
+ test: { type: "Literal", value: entry },
501
+ consequent: [
502
+ {
503
+ type: "ExpressionStatement",
504
+ expression: {
505
+ type: "AssignmentExpression",
506
+ operator: "=",
507
+ left: { type: "Identifier", name: "state" },
508
+ right: {
509
+ type: "ConditionalExpression",
510
+ test: node.test,
511
+ consequent: { type: "Literal", value: bodyEntry },
512
+ alternate: { type: "Literal", value: exit }
513
+ }
514
+ }
515
+ },
516
+ {
517
+ type: "BreakStatement"
518
+ }
519
+ ]
520
+ });
521
+ this.emitter.emit("branch", entry);
522
+
523
+ this.breaks.push({
524
+ label: node.label && node.label.name,
525
+ id: exit
526
+ });
527
+ this.continues.push({
528
+ label: node.label && node.label.name,
529
+ id: entry
530
+ });
531
+ this.transformBlock(node.body, bodyEntry, entry);
532
+ this.breaks.pop();
533
+ this.continues.pop();
534
+ }
535
+
536
+ /**
537
+ * Import DoWhileStatement into control flow table
538
+ * @param {DoWhileStatement} node
539
+ * @param {number} entry Entry point
540
+ * @param {number} exit Exit point
541
+ */
542
+ transformDoWhile (node, entry, exit) {
543
+ assert.equal(node.type, "DoWhileStatement");
544
+ assert.equal(typeof entry, "number");
545
+ assert.equal(typeof exit, "number");
546
+
547
+ var testEntry = this.rng.get();
548
+ this.output.push({
549
+ type: "SwitchCase",
550
+ test: { type: "Literal", value: testEntry },
551
+ consequent: [
552
+ {
553
+ type: "ExpressionStatement",
554
+ expression: {
555
+ type: "AssignmentExpression",
556
+ operator: "=",
557
+ left: { type: "Identifier", name: "state" },
558
+ right: {
559
+ type: "ConditionalExpression",
560
+ test: node.test,
561
+ consequent: { type: "Literal", value: entry },
562
+ alternate: { type: "Literal", value: exit }
563
+ }
564
+ }
565
+ },
566
+ {
567
+ type: "BreakStatement"
568
+ }
569
+ ]
570
+ });
571
+ this.emitter.emit("branch", testEntry);
572
+
573
+ this.breaks.push({
574
+ label: node.label && node.label.name,
575
+ id: exit
576
+ });
577
+ this.continues.push({
578
+ label: node.label && node.label.name,
579
+ id: entry
580
+ });
581
+ this.transformBlock(node.body, entry, testEntry);
582
+ this.breaks.pop();
583
+ this.continues.pop();
584
+ }
585
+
586
+ /**
587
+ * Import SwitchStatement into control flow table
588
+ * @param {SwitchStatement} node
589
+ * @param {number} entry Entry point
590
+ * @param {number} exit Exit point
591
+ */
592
+ transformSwitch (node, entry, exit) {
593
+ assert.equal(node.type, "SwitchStatement");
594
+ assert.equal(typeof entry, "number");
595
+ assert.equal(typeof exit, "number");
596
+
597
+ var comps = [];
598
+
599
+ this.breaks.push({
600
+ label: null,
601
+ id: exit
602
+ });
603
+ var nextCaseEntry = this.rng.get();
604
+ node.cases.forEach(switchCase => {
605
+ var isLast = switchCase == _.last(node.cases);
606
+
607
+ var caseEntry = nextCaseEntry;
608
+ nextCaseEntry = this.rng.get();
609
+
610
+ /**
611
+ * What happens if there are empty BlockStatements elsewhere? Does it hang?
612
+ */
613
+
614
+ if (switchCase.consequent.length > 0) {
615
+ this.transformBlock({
616
+ type: "BlockStatement",
617
+ body: switchCase.consequent
618
+ }, caseEntry, isLast ? exit : nextCaseEntry);
619
+ } else {
620
+ nextCaseEntry = caseEntry;
621
+ }
622
+
623
+ if (switchCase.test) {
624
+ comps.push({
625
+ type: "IfStatement",
626
+ test: {
627
+ type: "BinaryExpression",
628
+ operator: "==",
629
+ left: utils.cloneISwearIKnowWhatImDoing(node.discriminant),
630
+ right: switchCase.test
631
+ },
632
+ consequent: {
633
+ type: "BlockStatement",
634
+ body: [
635
+ {
636
+ type: "ExpressionStatement",
637
+ expression: {
638
+ type: "AssignmentExpression",
639
+ operator: "=",
640
+ left: { type: "Identifier", name: "state" },
641
+ right: { type: "Literal", value: caseEntry }
642
+ }
643
+ },
644
+ {
645
+ type: "BreakStatement"
646
+ }
647
+ ]
648
+ }
649
+ });
650
+ } else {
651
+ comps.push({
652
+ type: "BlockStatement",
653
+ body: [
654
+ {
655
+ type: "ExpressionStatement",
656
+ expression: {
657
+ type: "AssignmentExpression",
658
+ operator: "=",
659
+ left: { type: "Identifier", name: "state" },
660
+ right: { type: "Literal", value: caseEntry }
661
+ }
662
+ },
663
+ {
664
+ type: "BreakStatement"
665
+ }
666
+ ]
667
+ });
668
+ }
669
+ });
670
+ this.breaks.pop();
671
+
672
+ this.output.push({
673
+ type: "SwitchCase",
674
+ test: { type: "Literal", value: entry },
675
+ consequent: comps
676
+ });
677
+ }
678
+
679
+ /**
680
+ * Import TryStatement into control flow table
681
+ * @param {TryStatement} node
682
+ * @param {number} entry Entry point
683
+ * @param {number} exit Exit point
684
+ */
685
+ transformTryCatch (node, entry, exit) {
686
+ assert.equal(node.type, "TryStatement");
687
+ assert.equal(typeof entry, "number");
688
+ assert.equal(typeof exit, "number");
689
+ assert.ok(node.handler);
690
+ assert.ok(!node.finalizer);
691
+
692
+ var catchEntry = this.rng.get();
693
+
694
+ if (node.handler) {
695
+ var scopeDef = node.handler.body.body.splice(0, 2);
696
+ assert(
697
+ scopeDef[0].type == "VariableDeclaration" &&
698
+ scopeDef[0].declarations.length == 1 &&
699
+ scopeDef[0].declarations[0].id.name.indexOf("$$scope") == 0,
700
+ "First element of node.handler.body isn't a VariableDeclaration of a scope object");
701
+ assert(
702
+ scopeDef[1].type == "ExpressionStatement" &&
703
+ scopeDef[1].expression.type == "AssignmentExpression" &&
704
+ scopeDef[1].expression.left.type == "MemberExpression" &&
705
+ scopeDef[1].expression.left.object.name.indexOf("$$scope") == 0 &&
706
+ scopeDef[1].expression.right.name.indexOf("$$var") == 0,
707
+ "Second element of node.handler.body is not a e assignment");
708
+ }
709
+ function createHandler(entry) {
710
+ if (node.handler) {
711
+ pushUniqSwitchCase(this.handlers, {
712
+ type: "SwitchCase",
713
+ test: { type: "Literal", value: entry },
714
+ consequent: [
715
+ scopeDef[0],
716
+ {
717
+ type: "ExpressionStatement",
718
+ expression: {
719
+ type: "AssignmentExpression",
720
+ operator: "=",
721
+ left: node.handler.veilmark$exception,
722
+ right: { type: "Identifier", name: "e" }
723
+ }
724
+ },
725
+ {
726
+ type: "ExpressionStatement",
727
+ expression: {
728
+ type: "AssignmentExpression",
729
+ operator: "=",
730
+ left: { type: "Identifier", name: "state" },
731
+ right: { type: "Literal", value: catchEntry }
732
+ }
733
+ },
734
+ {
735
+ type: "BreakStatement"
736
+ }
737
+ ]
738
+ });
739
+ }
740
+ }
741
+ this.emitter.on("branch", createHandler);
742
+ this.transformBlock(node.block, entry, exit);
743
+ this.emitter.removeListener("branch", createHandler);
744
+
745
+ if (node.handler) {
746
+ this.transformBlock(node.handler.body, catchEntry, exit);
747
+ }
748
+ }
749
+
750
+ /**
751
+ * Transform duplicate scope and arguments into single unified declarations
752
+ * @params {Node} ast Root node
753
+ * @returns {Node}
754
+ */
755
+ unifyPrefixStatements (ast) {
756
+ var maximumScopeIndex = 0;
757
+
758
+ ast = traverser.traverse(ast, [], (node, stack) => {
759
+ if (node.veilmark$reassigningArguments && !node.veilmark$followsSlicingArguments) {
760
+ node = { type: "EmptyStatement" };
761
+ } else if (node.veilmark$scopeObject) {
762
+ node = { type: "EmptyStatement" };
763
+ } else if (node.veilmark$scopeObjectReference) {
764
+ maximumScopeIndex = Math.max(maximumScopeIndex, node.property.value);
765
+ } else if (node.type == "Identifier" && _.startsWith(node.name, "$$scope")) {
766
+ node.name = "$$unifiedScope";
767
+ }
768
+ return node;
769
+ });
770
+
771
+ ast.body[0].body.body.splice(0, 0,
772
+ {
773
+ type: "ExpressionStatement",
774
+ expression: {
775
+ type: "VariableDeclaration",
776
+ kind: "var",
777
+ declarations: [
778
+ {
779
+ type: "VariableDeclarator",
780
+ id: { type: "Identifier", name: "$$unifiedScope" },
781
+ init: {
782
+ type: "NewExpression",
783
+ callee: { type: "Identifier", name: "Array" },
784
+ arguments: [
785
+ { type: "Literal", value: maximumScopeIndex }
786
+ ]
787
+ }
788
+ }
789
+ ]
790
+ }
791
+ },
792
+ {
793
+ type: "VariableDeclaration",
794
+ kind: "var",
795
+ declarations: [
796
+ {
797
+ type: "VariableDeclarator",
798
+ id: { type: "Identifier", name: "veilmark$arguments" },
799
+ init: { type: "Identifier", name: "arguments" }
800
+ }
801
+ ]
802
+ }
803
+ );
804
+
805
+ return ast;
806
+ }
807
+
808
+ };