1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 | "use strict";
|
45 |
|
46 | function SymbolDef(scope, orig, init) {
|
47 | this.name = orig.name;
|
48 | this.orig = [ orig ];
|
49 | this.init = init;
|
50 | this.eliminated = 0;
|
51 | this.scope = scope;
|
52 | this.references = [];
|
53 | this.replaced = 0;
|
54 | this.global = false;
|
55 | this.mangled_name = null;
|
56 | this.undeclared = false;
|
57 | this.id = SymbolDef.next_id++;
|
58 | this.lambda = orig instanceof AST_SymbolLambda;
|
59 | }
|
60 |
|
61 | SymbolDef.next_id = 1;
|
62 |
|
63 | SymbolDef.prototype = {
|
64 | unmangleable: function(options) {
|
65 | return this.global && !options.toplevel
|
66 | || this.undeclared
|
67 | || !options.eval && this.scope.pinned()
|
68 | || options.keep_fnames
|
69 | && (this.orig[0] instanceof AST_SymbolLambda
|
70 | || this.orig[0] instanceof AST_SymbolDefun);
|
71 | },
|
72 | mangle: function(options) {
|
73 | var cache = options.cache && options.cache.props;
|
74 | if (this.global && cache && cache.has(this.name)) {
|
75 | this.mangled_name = cache.get(this.name);
|
76 | } else if (!this.mangled_name && !this.unmangleable(options)) {
|
77 | var def;
|
78 | if (def = this.redefined()) {
|
79 | this.mangled_name = def.mangled_name || def.name;
|
80 | } else {
|
81 | this.mangled_name = next_mangled_name(this.scope, options, this);
|
82 | }
|
83 | if (this.global && cache) {
|
84 | cache.set(this.name, this.mangled_name);
|
85 | }
|
86 | }
|
87 | },
|
88 | redefined: function() {
|
89 | return this.defun && this.defun.variables.get(this.name);
|
90 | }
|
91 | };
|
92 |
|
93 | AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
|
94 | options = defaults(options, {
|
95 | cache: null,
|
96 | ie8: false,
|
97 | });
|
98 |
|
99 |
|
100 | var self = this;
|
101 | var scope = self.parent_scope = null;
|
102 | var defun = null;
|
103 | var tw = new TreeWalker(function(node, descend) {
|
104 | if (node instanceof AST_Catch) {
|
105 | var save_scope = scope;
|
106 | scope = new AST_Scope(node);
|
107 | scope.init_scope_vars(save_scope);
|
108 | descend();
|
109 | scope = save_scope;
|
110 | return true;
|
111 | }
|
112 | if (node instanceof AST_Scope) {
|
113 | node.init_scope_vars(scope);
|
114 | var save_scope = scope;
|
115 | var save_defun = defun;
|
116 | defun = scope = node;
|
117 | descend();
|
118 | scope = save_scope;
|
119 | defun = save_defun;
|
120 | return true;
|
121 | }
|
122 | if (node instanceof AST_With) {
|
123 | for (var s = scope; s; s = s.parent_scope) s.uses_with = true;
|
124 | return;
|
125 | }
|
126 | if (node instanceof AST_Symbol) {
|
127 | node.scope = scope;
|
128 | }
|
129 | if (node instanceof AST_Label) {
|
130 | node.thedef = node;
|
131 | node.references = [];
|
132 | }
|
133 | if (node instanceof AST_SymbolDefun) {
|
134 |
|
135 |
|
136 | (node.scope = defun.parent_scope.resolve()).def_function(node, defun);
|
137 | } else if (node instanceof AST_SymbolLambda) {
|
138 | var def = defun.def_function(node, node.name == "arguments" ? undefined : defun);
|
139 | if (options.ie8) def.defun = defun.parent_scope.resolve();
|
140 | } else if (node instanceof AST_SymbolVar) {
|
141 | defun.def_variable(node, node.TYPE == "SymbolVar" ? null : undefined);
|
142 | if (defun !== scope) {
|
143 | node.mark_enclosed(options);
|
144 | var def = scope.find_variable(node);
|
145 | if (node.thedef !== def) {
|
146 | node.thedef = def;
|
147 | }
|
148 | node.reference(options);
|
149 | }
|
150 | } else if (node instanceof AST_SymbolCatch) {
|
151 | scope.def_variable(node).defun = defun;
|
152 | }
|
153 | });
|
154 | self.walk(tw);
|
155 |
|
156 |
|
157 | self.globals = new Dictionary();
|
158 | var tw = new TreeWalker(function(node) {
|
159 | if (node instanceof AST_LoopControl) {
|
160 | if (node.label) node.label.thedef.references.push(node);
|
161 | return true;
|
162 | }
|
163 | if (node instanceof AST_SymbolRef) {
|
164 | var name = node.name;
|
165 | if (name == "eval" && tw.parent() instanceof AST_Call) {
|
166 | for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) {
|
167 | s.uses_eval = true;
|
168 | }
|
169 | }
|
170 | var sym = node.scope.find_variable(name);
|
171 | if (!sym) {
|
172 | sym = self.def_global(node);
|
173 | } else if (sym.scope instanceof AST_Lambda && name == "arguments") {
|
174 | sym.scope.uses_arguments = true;
|
175 | }
|
176 | node.thedef = sym;
|
177 | node.reference(options);
|
178 | return true;
|
179 | }
|
180 |
|
181 | if (node instanceof AST_SymbolCatch) {
|
182 | var def = node.definition().redefined();
|
183 | if (def) for (var s = node.scope; s; s = s.parent_scope) {
|
184 | push_uniq(s.enclosed, def);
|
185 | if (s === def.scope) break;
|
186 | }
|
187 | return true;
|
188 | }
|
189 | });
|
190 | self.walk(tw);
|
191 |
|
192 |
|
193 | if (options.ie8) self.walk(new TreeWalker(function(node) {
|
194 | if (node instanceof AST_SymbolCatch) {
|
195 | var scope = node.thedef.defun;
|
196 | if (scope.name instanceof AST_SymbolLambda && scope.name.name == node.name) {
|
197 | scope = scope.parent_scope.resolve();
|
198 | }
|
199 | redefine(node, scope);
|
200 | return true;
|
201 | }
|
202 | if (node instanceof AST_SymbolLambda) {
|
203 | var def = node.thedef;
|
204 | redefine(node, node.scope.parent_scope.resolve());
|
205 | if (typeof node.thedef.init !== "undefined") {
|
206 | node.thedef.init = false;
|
207 | } else if (def.init) {
|
208 | node.thedef.init = def.init;
|
209 | }
|
210 | return true;
|
211 | }
|
212 | }));
|
213 |
|
214 | function redefine(node, scope) {
|
215 | var name = node.name;
|
216 | var old_def = node.thedef;
|
217 | var new_def = scope.find_variable(name);
|
218 | if (new_def) {
|
219 | var redef;
|
220 | while (redef = new_def.redefined()) new_def = redef;
|
221 | } else {
|
222 | new_def = self.globals.get(name) || scope.def_variable(node);
|
223 | }
|
224 | old_def.orig.concat(old_def.references).forEach(function(node) {
|
225 | node.thedef = new_def;
|
226 | node.reference(options);
|
227 | });
|
228 | if (old_def.lambda) new_def.lambda = true;
|
229 | if (new_def.undeclared) self.variables.set(name, new_def);
|
230 | }
|
231 | });
|
232 |
|
233 | AST_Toplevel.DEFMETHOD("def_global", function(node) {
|
234 | var globals = this.globals, name = node.name;
|
235 | if (globals.has(name)) {
|
236 | return globals.get(name);
|
237 | } else {
|
238 | var g = new SymbolDef(this, node);
|
239 | g.undeclared = true;
|
240 | g.global = true;
|
241 | globals.set(name, g);
|
242 | return g;
|
243 | }
|
244 | });
|
245 |
|
246 | AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope) {
|
247 | this.variables = new Dictionary();
|
248 | this.functions = new Dictionary();
|
249 | this.uses_with = false;
|
250 | this.uses_eval = false;
|
251 | this.parent_scope = parent_scope;
|
252 | this.enclosed = [];
|
253 | this.cname = -1;
|
254 | });
|
255 |
|
256 | AST_Lambda.DEFMETHOD("init_scope_vars", function() {
|
257 | AST_Scope.prototype.init_scope_vars.apply(this, arguments);
|
258 | this.uses_arguments = false;
|
259 | this.def_variable(new AST_SymbolFunarg({
|
260 | name: "arguments",
|
261 | start: this.start,
|
262 | end: this.end
|
263 | }));
|
264 | });
|
265 |
|
266 | AST_Symbol.DEFMETHOD("mark_enclosed", function(options) {
|
267 | var def = this.definition();
|
268 | for (var s = this.scope; s; s = s.parent_scope) {
|
269 | push_uniq(s.enclosed, def);
|
270 | if (options.keep_fnames) {
|
271 | s.functions.each(function(d) {
|
272 | push_uniq(def.scope.enclosed, d);
|
273 | });
|
274 | }
|
275 | if (s === def.scope) break;
|
276 | }
|
277 | });
|
278 |
|
279 | AST_Symbol.DEFMETHOD("reference", function(options) {
|
280 | this.definition().references.push(this);
|
281 | this.mark_enclosed(options);
|
282 | });
|
283 |
|
284 | AST_Scope.DEFMETHOD("find_variable", function(name) {
|
285 | if (name instanceof AST_Symbol) name = name.name;
|
286 | return this.variables.get(name)
|
287 | || (this.parent_scope && this.parent_scope.find_variable(name));
|
288 | });
|
289 |
|
290 | AST_Scope.DEFMETHOD("def_function", function(symbol, init) {
|
291 | var def = this.def_variable(symbol, init);
|
292 | if (!def.init || def.init instanceof AST_Defun) def.init = init;
|
293 | this.functions.set(symbol.name, def);
|
294 | return def;
|
295 | });
|
296 |
|
297 | AST_Scope.DEFMETHOD("def_variable", function(symbol, init) {
|
298 | var def = this.variables.get(symbol.name);
|
299 | if (def) {
|
300 | def.orig.push(symbol);
|
301 | if (def.init instanceof AST_Function) def.init = init;
|
302 | } else {
|
303 | def = new SymbolDef(this, symbol, init);
|
304 | this.variables.set(symbol.name, def);
|
305 | def.global = !this.parent_scope;
|
306 | }
|
307 | return symbol.thedef = def;
|
308 | });
|
309 |
|
310 | AST_Lambda.DEFMETHOD("resolve", return_this);
|
311 | AST_Scope.DEFMETHOD("resolve", function() {
|
312 | return this.parent_scope.resolve();
|
313 | });
|
314 | AST_Toplevel.DEFMETHOD("resolve", return_this);
|
315 |
|
316 | function names_in_use(scope, options) {
|
317 | var names = scope.names_in_use;
|
318 | if (!names) {
|
319 | scope.names_in_use = names = Object.create(scope.mangled_names || null);
|
320 | scope.cname_holes = [];
|
321 | var cache = options.cache && options.cache.props;
|
322 | scope.enclosed.forEach(function(def) {
|
323 | if (def.unmangleable(options)) names[def.name] = true;
|
324 | if (def.global && cache && cache.has(def.name)) {
|
325 | names[cache.get(def.name)] = true;
|
326 | }
|
327 | });
|
328 | }
|
329 | return names;
|
330 | }
|
331 |
|
332 | function next_mangled_name(scope, options, def) {
|
333 | var in_use = names_in_use(scope, options);
|
334 | var holes = scope.cname_holes;
|
335 | var names = Object.create(null);
|
336 | var scopes = [ scope ];
|
337 | def.references.forEach(function(sym) {
|
338 | var scope = sym.scope;
|
339 | do {
|
340 | if (scopes.indexOf(scope) < 0) {
|
341 | for (var name in names_in_use(scope, options)) {
|
342 | names[name] = true;
|
343 | }
|
344 | scopes.push(scope);
|
345 | } else break;
|
346 | } while (scope = scope.parent_scope);
|
347 | });
|
348 | var name;
|
349 | for (var i = 0; i < holes.length; i++) {
|
350 | name = base54(holes[i]);
|
351 | if (names[name]) continue;
|
352 | holes.splice(i, 1);
|
353 | scope.names_in_use[name] = true;
|
354 | return name;
|
355 | }
|
356 | while (true) {
|
357 | name = base54(++scope.cname);
|
358 | if (in_use[name] || RESERVED_WORDS[name] || options.reserved.has[name]) continue;
|
359 | if (!names[name]) break;
|
360 | holes.push(scope.cname);
|
361 | }
|
362 | scope.names_in_use[name] = true;
|
363 | return name;
|
364 | }
|
365 |
|
366 | AST_Symbol.DEFMETHOD("unmangleable", function(options) {
|
367 | var def = this.definition();
|
368 | return !def || def.unmangleable(options);
|
369 | });
|
370 |
|
371 |
|
372 | AST_Label.DEFMETHOD("unmangleable", return_false);
|
373 |
|
374 | AST_Symbol.DEFMETHOD("unreferenced", function() {
|
375 | return !this.definition().references.length && !this.scope.pinned();
|
376 | });
|
377 |
|
378 | AST_Symbol.DEFMETHOD("definition", function() {
|
379 | return this.thedef;
|
380 | });
|
381 |
|
382 | AST_Symbol.DEFMETHOD("global", function() {
|
383 | return this.definition().global;
|
384 | });
|
385 |
|
386 | function _default_mangler_options(options) {
|
387 | options = defaults(options, {
|
388 | eval : false,
|
389 | ie8 : false,
|
390 | keep_fnames : false,
|
391 | reserved : [],
|
392 | toplevel : false,
|
393 | });
|
394 | if (!Array.isArray(options.reserved)) options.reserved = [];
|
395 |
|
396 | push_uniq(options.reserved, "arguments");
|
397 | options.reserved.has = makePredicate(options.reserved);
|
398 | return options;
|
399 | }
|
400 |
|
401 | AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
|
402 | options = _default_mangler_options(options);
|
403 |
|
404 |
|
405 |
|
406 |
|
407 |
|
408 | var lname = -1;
|
409 |
|
410 | if (options.cache && options.cache.props) {
|
411 | var mangled_names = this.mangled_names = Object.create(null);
|
412 | options.cache.props.each(function(mangled_name) {
|
413 | mangled_names[mangled_name] = true;
|
414 | });
|
415 | }
|
416 |
|
417 | var redefined = [];
|
418 | var tw = new TreeWalker(function(node, descend) {
|
419 | if (node instanceof AST_LabeledStatement) {
|
420 |
|
421 | var save_nesting = lname;
|
422 | descend();
|
423 | lname = save_nesting;
|
424 | return true;
|
425 | }
|
426 | if (node instanceof AST_Scope) {
|
427 | descend();
|
428 | if (options.cache && node instanceof AST_Toplevel) {
|
429 | node.globals.each(mangle);
|
430 | }
|
431 | if (node instanceof AST_Defun && tw.has_directive("use asm")) {
|
432 | var sym = new AST_SymbolRef(node.name);
|
433 | sym.scope = node;
|
434 | sym.reference(options);
|
435 | }
|
436 | node.variables.each(function(def) {
|
437 | if (!defer_redef(def)) mangle(def);
|
438 | });
|
439 | return true;
|
440 | }
|
441 | if (node instanceof AST_Label) {
|
442 | var name;
|
443 | do {
|
444 | name = base54(++lname);
|
445 | } while (RESERVED_WORDS[name]);
|
446 | node.mangled_name = name;
|
447 | return true;
|
448 | }
|
449 | if (!options.ie8 && node instanceof AST_Catch) {
|
450 | var def = node.argname.definition();
|
451 | var redef = defer_redef(def, node.argname);
|
452 | descend();
|
453 | if (!redef) mangle(def);
|
454 | return true;
|
455 | }
|
456 | });
|
457 | this.walk(tw);
|
458 | redefined.forEach(mangle);
|
459 |
|
460 | function mangle(def) {
|
461 | if (options.reserved.has[def.name]) return;
|
462 | def.mangle(options);
|
463 | }
|
464 |
|
465 | function defer_redef(def, node) {
|
466 | var redef = def.redefined();
|
467 | if (!redef) return false;
|
468 | redefined.push(def);
|
469 | def.references.forEach(reference);
|
470 | if (node) reference(node);
|
471 | return true;
|
472 |
|
473 | function reference(sym) {
|
474 | sym.thedef = redef;
|
475 | sym.reference(options);
|
476 | sym.thedef = def;
|
477 | }
|
478 | }
|
479 | });
|
480 |
|
481 | AST_Toplevel.DEFMETHOD("find_colliding_names", function(options) {
|
482 | var cache = options.cache && options.cache.props;
|
483 | var avoid = Object.create(null);
|
484 | options.reserved.forEach(to_avoid);
|
485 | this.globals.each(add_def);
|
486 | this.walk(new TreeWalker(function(node) {
|
487 | if (node instanceof AST_Scope) node.variables.each(add_def);
|
488 | if (node instanceof AST_SymbolCatch) add_def(node.definition());
|
489 | }));
|
490 | return avoid;
|
491 |
|
492 | function to_avoid(name) {
|
493 | avoid[name] = true;
|
494 | }
|
495 |
|
496 | function add_def(def) {
|
497 | var name = def.name;
|
498 | if (def.global && cache && cache.has(name)) name = cache.get(name);
|
499 | else if (!def.unmangleable(options)) return;
|
500 | to_avoid(name);
|
501 | }
|
502 | });
|
503 |
|
504 | AST_Toplevel.DEFMETHOD("expand_names", function(options) {
|
505 | base54.reset();
|
506 | base54.sort();
|
507 | options = _default_mangler_options(options);
|
508 | var avoid = this.find_colliding_names(options);
|
509 | var cname = 0;
|
510 | this.globals.each(rename);
|
511 | this.walk(new TreeWalker(function(node) {
|
512 | if (node instanceof AST_Scope) node.variables.each(rename);
|
513 | if (node instanceof AST_SymbolCatch) rename(node.definition());
|
514 | }));
|
515 |
|
516 | function next_name() {
|
517 | var name;
|
518 | do {
|
519 | name = base54(cname++);
|
520 | } while (avoid[name] || RESERVED_WORDS[name]);
|
521 | return name;
|
522 | }
|
523 |
|
524 | function rename(def) {
|
525 | if (def.global && options.cache) return;
|
526 | if (def.unmangleable(options)) return;
|
527 | if (options.reserved.has[def.name]) return;
|
528 | var redef = def.redefined();
|
529 | var name = redef ? redef.rename || redef.name : next_name();
|
530 | def.rename = name;
|
531 | def.orig.forEach(function(sym) {
|
532 | sym.name = name;
|
533 | });
|
534 | def.references.forEach(function(sym) {
|
535 | sym.name = name;
|
536 | });
|
537 | }
|
538 | });
|
539 |
|
540 | AST_Node.DEFMETHOD("tail_node", return_this);
|
541 | AST_Sequence.DEFMETHOD("tail_node", function() {
|
542 | return this.expressions[this.expressions.length - 1];
|
543 | });
|
544 |
|
545 | AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options) {
|
546 | options = _default_mangler_options(options);
|
547 | base54.reset();
|
548 | try {
|
549 | AST_Node.prototype.print = function(stream, force_parens) {
|
550 | this._print(stream, force_parens);
|
551 | if (this instanceof AST_Symbol && !this.unmangleable(options)) {
|
552 | base54.consider(this.name, -1);
|
553 | } else if (options.properties) {
|
554 | if (this instanceof AST_Dot) {
|
555 | base54.consider(this.property, -1);
|
556 | } else if (this instanceof AST_Sub) {
|
557 | skip_string(this.property);
|
558 | }
|
559 | }
|
560 | };
|
561 | base54.consider(this.print_to_string(), 1);
|
562 | } finally {
|
563 | AST_Node.prototype.print = AST_Node.prototype._print;
|
564 | }
|
565 | base54.sort();
|
566 |
|
567 | function skip_string(node) {
|
568 | if (node instanceof AST_String) {
|
569 | base54.consider(node.value, -1);
|
570 | } else if (node instanceof AST_Conditional) {
|
571 | skip_string(node.consequent);
|
572 | skip_string(node.alternative);
|
573 | } else if (node instanceof AST_Sequence) {
|
574 | skip_string(node.tail_node());
|
575 | }
|
576 | }
|
577 | });
|
578 |
|
579 | var base54 = (function() {
|
580 | var freq = Object.create(null);
|
581 | function init(chars) {
|
582 | var array = [];
|
583 | for (var i = 0; i < chars.length; i++) {
|
584 | var ch = chars[i];
|
585 | array.push(ch);
|
586 | freq[ch] = -1e-2 * i;
|
587 | }
|
588 | return array;
|
589 | }
|
590 | var digits = init("0123456789");
|
591 | var leading = init("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_");
|
592 | var chars, frequency;
|
593 | function reset() {
|
594 | frequency = Object.create(freq);
|
595 | }
|
596 | base54.consider = function(str, delta) {
|
597 | for (var i = str.length; --i >= 0;) {
|
598 | frequency[str[i]] += delta;
|
599 | }
|
600 | };
|
601 | function compare(a, b) {
|
602 | return frequency[b] - frequency[a];
|
603 | }
|
604 | base54.sort = function() {
|
605 | chars = leading.sort(compare).concat(digits.sort(compare));
|
606 | };
|
607 | base54.reset = reset;
|
608 | reset();
|
609 | function base54(num) {
|
610 | var ret = "", base = 54;
|
611 | num++;
|
612 | do {
|
613 | num--;
|
614 | ret += chars[num % base];
|
615 | num = Math.floor(num / base);
|
616 | base = 64;
|
617 | } while (num > 0);
|
618 | return ret;
|
619 | }
|
620 | return base54;
|
621 | })();
|