1/***********************************************************************
2
3  A JavaScript tokenizer / parser / beautifier / compressor.
4  https://github.com/mishoo/UglifyJS2
5
6  -------------------------------- (C) ---------------------------------
7
8                           Author: Mihai Bazon
9                         <mihai.bazon@gmail.com>
10                       http://mihai.bazon.net/blog
11
12  Distributed under the BSD license:
13
14    Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
15
16    Redistribution and use in source and binary forms, with or without
17    modification, are permitted provided that the following conditions
18    are met:
19
20        * Redistributions of source code must retain the above
21          copyright notice, this list of conditions and the following
22          disclaimer.
23
24        * Redistributions in binary form must reproduce the above
25          copyright notice, this list of conditions and the following
26          disclaimer in the documentation and/or other materials
27          provided with the distribution.
28
29    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
30    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
32    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
33    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
34    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
35    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
36    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
38    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
39    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40    SUCH DAMAGE.
41
42 ***********************************************************************/
43
44"use strict";
45
46function SymbolDef(scope, index, orig) {
47    this.name = orig.name;
48    this.orig = [ orig ];
49    this.scope = scope;
50    this.references = [];
51    this.global = false;
52    this.mangled_name = null;
53    this.undeclared = false;
54    this.index = index;
55    this.id = SymbolDef.next_id++;
56};
57
58SymbolDef.next_id = 1;
59
60SymbolDef.prototype = {
61    unmangleable: function(options) {
62        if (!options) options = {};
63
64        return (this.global && !options.toplevel)
65            || this.undeclared
66            || (!options.eval && (this.scope.uses_eval || this.scope.uses_with))
67            || (options.keep_fnames
68                && (this.orig[0] instanceof AST_SymbolLambda
69                    || this.orig[0] instanceof AST_SymbolDefun));
70    },
71    mangle: function(options) {
72        var cache = options.cache && options.cache.props;
73        if (this.global && cache && cache.has(this.name)) {
74            this.mangled_name = cache.get(this.name);
75        }
76        else if (!this.mangled_name && !this.unmangleable(options)) {
77            var s = this.scope;
78            var sym = this.orig[0];
79            if (!options.screw_ie8 && sym instanceof AST_SymbolLambda)
80                s = s.parent_scope;
81            var def;
82            if (this.defun && (def = this.defun.variables.get(this.name))) {
83                this.mangled_name = def.mangled_name || def.name;
84            } else
85                this.mangled_name = s.next_mangled(options, this);
86            if (this.global && cache) {
87                cache.set(this.name, this.mangled_name);
88            }
89        }
90    }
91};
92
93AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
94    options = defaults(options, {
95        cache: null,
96        screw_ie8: true,
97    });
98
99    // pass 1: setup scope chaining and handle definitions
100    var self = this;
101    var scope = self.parent_scope = null;
102    var labels = new Dictionary();
103    var defun = null;
104    var tw = new TreeWalker(function(node, descend){
105        if (node instanceof AST_Catch) {
106            var save_scope = scope;
107            scope = new AST_Scope(node);
108            scope.init_scope_vars(save_scope);
109            descend();
110            scope = save_scope;
111            return true;
112        }
113        if (node instanceof AST_Scope) {
114            node.init_scope_vars(scope);
115            var save_scope = scope;
116            var save_defun = defun;
117            var save_labels = labels;
118            defun = scope = node;
119            labels = new Dictionary();
120            descend();
121            scope = save_scope;
122            defun = save_defun;
123            labels = save_labels;
124            return true;        // don't descend again in TreeWalker
125        }
126        if (node instanceof AST_LabeledStatement) {
127            var l = node.label;
128            if (labels.has(l.name)) {
129                throw new Error(string_template("Label {name} defined twice", l));
130            }
131            labels.set(l.name, l);
132            descend();
133            labels.del(l.name);
134            return true;        // no descend again
135        }
136        if (node instanceof AST_With) {
137            for (var s = scope; s; s = s.parent_scope)
138                s.uses_with = true;
139            return;
140        }
141        if (node instanceof AST_Symbol) {
142            node.scope = scope;
143        }
144        if (node instanceof AST_Label) {
145            node.thedef = node;
146            node.references = [];
147        }
148        if (node instanceof AST_SymbolLambda) {
149            defun.def_function(node);
150        }
151        else if (node instanceof AST_SymbolDefun) {
152            // Careful here, the scope where this should be defined is
153            // the parent scope.  The reason is that we enter a new
154            // scope when we encounter the AST_Defun node (which is
155            // instanceof AST_Scope) but we get to the symbol a bit
156            // later.
157            (node.scope = defun.parent_scope).def_function(node);
158        }
159        else if (node instanceof AST_SymbolVar
160            || node instanceof AST_SymbolConst) {
161            defun.def_variable(node);
162            if (defun !== scope) {
163                node.mark_enclosed(options);
164                var def = scope.find_variable(node);
165                if (node.thedef !== def) {
166                    node.thedef = def;
167                    node.reference(options);
168                }
169            }
170        }
171        else if (node instanceof AST_SymbolCatch) {
172            scope.def_variable(node).defun = defun;
173        }
174        else if (node instanceof AST_LabelRef) {
175            var sym = labels.get(node.name);
176            if (!sym) throw new Error(string_template("Undefined label {name} [{line},{col}]", {
177                name: node.name,
178                line: node.start.line,
179                col: node.start.col
180            }));
181            node.thedef = sym;
182        }
183    });
184    self.walk(tw);
185
186    // pass 2: find back references and eval
187    var func = null;
188    var globals = self.globals = new Dictionary();
189    var tw = new TreeWalker(function(node, descend){
190        if (node instanceof AST_Lambda) {
191            var prev_func = func;
192            func = node;
193            descend();
194            func = prev_func;
195            return true;
196        }
197        if (node instanceof AST_LoopControl && node.label) {
198            node.label.thedef.references.push(node);
199            return true;
200        }
201        if (node instanceof AST_SymbolRef) {
202            var name = node.name;
203            if (name == "eval" && tw.parent() instanceof AST_Call) {
204                for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) {
205                    s.uses_eval = true;
206                }
207            }
208            var sym = node.scope.find_variable(name);
209            if (node.scope instanceof AST_Lambda && name == "arguments") {
210                node.scope.uses_arguments = true;
211            }
212            if (!sym) {
213                sym = self.def_global(node);
214            }
215            node.thedef = sym;
216            node.reference(options);
217            return true;
218        }
219    });
220    self.walk(tw);
221
222    // pass 3: fix up any scoping issue with IE8
223    if (!options.screw_ie8) {
224        self.walk(new TreeWalker(function(node, descend) {
225            if (node instanceof AST_SymbolCatch) {
226                var name = node.name;
227                var refs = node.thedef.references;
228                var scope = node.thedef.defun;
229                var def = scope.find_variable(name) || self.globals.get(name) || scope.def_variable(node);
230                refs.forEach(function(ref) {
231                    ref.thedef = def;
232                    ref.reference(options);
233                });
234                node.thedef = def;
235                return true;
236            }
237        }));
238    }
239
240    if (options.cache) {
241        this.cname = options.cache.cname;
242    }
243});
244
245AST_Toplevel.DEFMETHOD("def_global", function(node){
246    var globals = this.globals, name = node.name;
247    if (globals.has(name)) {
248        return globals.get(name);
249    } else {
250        var g = new SymbolDef(this, globals.size(), node);
251        g.undeclared = true;
252        g.global = true;
253        globals.set(name, g);
254        return g;
255    }
256});
257
258AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope){
259    this.variables = new Dictionary();  // map name to AST_SymbolVar (variables defined in this scope; includes functions)
260    this.functions = new Dictionary();  // map name to AST_SymbolDefun (functions defined in this scope)
261    this.uses_with = false;             // will be set to true if this or some nested scope uses the `with` statement
262    this.uses_eval = false;             // will be set to true if this or nested scope uses the global `eval`
263    this.parent_scope = parent_scope;   // the parent scope
264    this.enclosed = [];                 // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
265    this.cname = -1;                    // the current index for mangling functions/variables
266});
267
268AST_Lambda.DEFMETHOD("init_scope_vars", function(){
269    AST_Scope.prototype.init_scope_vars.apply(this, arguments);
270    this.uses_arguments = false;
271    this.def_variable(new AST_SymbolVar({
272        name: "arguments",
273        start: this.start,
274        end: this.end
275    }));
276});
277
278AST_Symbol.DEFMETHOD("mark_enclosed", function(options) {
279    var def = this.definition();
280    var s = this.scope;
281    while (s) {
282        push_uniq(s.enclosed, def);
283        if (options.keep_fnames) {
284            s.functions.each(function(d) {
285                push_uniq(def.scope.enclosed, d);
286            });
287        }
288        if (s === def.scope) break;
289        s = s.parent_scope;
290    }
291});
292
293AST_Symbol.DEFMETHOD("reference", function(options) {
294    this.definition().references.push(this);
295    this.mark_enclosed(options);
296});
297
298AST_Scope.DEFMETHOD("find_variable", function(name){
299    if (name instanceof AST_Symbol) name = name.name;
300    return this.variables.get(name)
301        || (this.parent_scope && this.parent_scope.find_variable(name));
302});
303
304AST_Scope.DEFMETHOD("def_function", function(symbol){
305    this.functions.set(symbol.name, this.def_variable(symbol));
306});
307
308AST_Scope.DEFMETHOD("def_variable", function(symbol){
309    var def;
310    if (!this.variables.has(symbol.name)) {
311        def = new SymbolDef(this, this.variables.size(), symbol);
312        this.variables.set(symbol.name, def);
313        def.global = !this.parent_scope;
314    } else {
315        def = this.variables.get(symbol.name);
316        def.orig.push(symbol);
317    }
318    return symbol.thedef = def;
319});
320
321AST_Scope.DEFMETHOD("next_mangled", function(options){
322    var ext = this.enclosed;
323    out: while (true) {
324        var m = base54(++this.cname);
325        if (!is_identifier(m)) continue; // skip over "do"
326
327        // https://github.com/mishoo/UglifyJS2/issues/242 -- do not
328        // shadow a name excepted from mangling.
329        if (options.except.indexOf(m) >= 0) continue;
330
331        // we must ensure that the mangled name does not shadow a name
332        // from some parent scope that is referenced in this or in
333        // inner scopes.
334        for (var i = ext.length; --i >= 0;) {
335            var sym = ext[i];
336            var name = sym.mangled_name || (sym.unmangleable(options) && sym.name);
337            if (m == name) continue out;
338        }
339        return m;
340    }
341});
342
343AST_Function.DEFMETHOD("next_mangled", function(options, def){
344    // #179, #326
345    // in Safari strict mode, something like (function x(x){...}) is a syntax error;
346    // a function expression's argument cannot shadow the function expression's name
347
348    var tricky_def = def.orig[0] instanceof AST_SymbolFunarg && this.name && this.name.definition();
349
350    // the function's mangled_name is null when keep_fnames is true
351    var tricky_name = tricky_def ? tricky_def.mangled_name || tricky_def.name : null;
352
353    while (true) {
354        var name = AST_Lambda.prototype.next_mangled.call(this, options, def);
355        if (!tricky_name || tricky_name != name)
356            return name;
357    }
358});
359
360AST_Symbol.DEFMETHOD("unmangleable", function(options){
361    return this.definition().unmangleable(options);
362});
363
364// labels are always mangleable
365AST_Label.DEFMETHOD("unmangleable", function(){
366    return false;
367});
368
369AST_Symbol.DEFMETHOD("unreferenced", function(){
370    return this.definition().references.length == 0
371        && !(this.scope.uses_eval || this.scope.uses_with);
372});
373
374AST_Symbol.DEFMETHOD("undeclared", function(){
375    return this.definition().undeclared;
376});
377
378AST_LabelRef.DEFMETHOD("undeclared", function(){
379    return false;
380});
381
382AST_Label.DEFMETHOD("undeclared", function(){
383    return false;
384});
385
386AST_Symbol.DEFMETHOD("definition", function(){
387    return this.thedef;
388});
389
390AST_Symbol.DEFMETHOD("global", function(){
391    return this.definition().global;
392});
393
394AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
395    return defaults(options, {
396        eval        : false,
397        except      : [],
398        keep_fnames : false,
399        screw_ie8   : true,
400        sort        : false, // Ignored. Flag retained for backwards compatibility.
401        toplevel    : false,
402    });
403});
404
405AST_Toplevel.DEFMETHOD("mangle_names", function(options){
406    options = this._default_mangler_options(options);
407
408    // Never mangle arguments
409    options.except.push('arguments');
410
411    // We only need to mangle declaration nodes.  Special logic wired
412    // into the code generator will display the mangled name if it's
413    // present (and for AST_SymbolRef-s it'll use the mangled name of
414    // the AST_SymbolDeclaration that it points to).
415    var lname = -1;
416    var to_mangle = [];
417
418    if (options.cache) {
419        this.globals.each(function(symbol){
420            if (options.except.indexOf(symbol.name) < 0) {
421                to_mangle.push(symbol);
422            }
423        });
424    }
425
426    var tw = new TreeWalker(function(node, descend){
427        if (node instanceof AST_LabeledStatement) {
428            // lname is incremented when we get to the AST_Label
429            var save_nesting = lname;
430            descend();
431            lname = save_nesting;
432            return true;        // don't descend again in TreeWalker
433        }
434        if (node instanceof AST_Scope) {
435            var p = tw.parent(), a = [];
436            node.variables.each(function(symbol){
437                if (options.except.indexOf(symbol.name) < 0) {
438                    a.push(symbol);
439                }
440            });
441            to_mangle.push.apply(to_mangle, a);
442            return;
443        }
444        if (node instanceof AST_Label) {
445            var name;
446            do name = base54(++lname); while (!is_identifier(name));
447            node.mangled_name = name;
448            return true;
449        }
450        if (options.screw_ie8 && node instanceof AST_SymbolCatch) {
451            to_mangle.push(node.definition());
452            return;
453        }
454    });
455    this.walk(tw);
456    to_mangle.forEach(function(def){ def.mangle(options) });
457
458    if (options.cache) {
459        options.cache.cname = this.cname;
460    }
461});
462
463AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){
464    options = this._default_mangler_options(options);
465    var tw = new TreeWalker(function(node){
466        if (node instanceof AST_Constant)
467            base54.consider(node.print_to_string());
468        else if (node instanceof AST_Return)
469            base54.consider("return");
470        else if (node instanceof AST_Throw)
471            base54.consider("throw");
472        else if (node instanceof AST_Continue)
473            base54.consider("continue");
474        else if (node instanceof AST_Break)
475            base54.consider("break");
476        else if (node instanceof AST_Debugger)
477            base54.consider("debugger");
478        else if (node instanceof AST_Directive)
479            base54.consider(node.value);
480        else if (node instanceof AST_While)
481            base54.consider("while");
482        else if (node instanceof AST_Do)
483            base54.consider("do while");
484        else if (node instanceof AST_If) {
485            base54.consider("if");
486            if (node.alternative) base54.consider("else");
487        }
488        else if (node instanceof AST_Var)
489            base54.consider("var");
490        else if (node instanceof AST_Const)
491            base54.consider("const");
492        else if (node instanceof AST_Lambda)
493            base54.consider("function");
494        else if (node instanceof AST_For)
495            base54.consider("for");
496        else if (node instanceof AST_ForIn)
497            base54.consider("for in");
498        else if (node instanceof AST_Switch)
499            base54.consider("switch");
500        else if (node instanceof AST_Case)
501            base54.consider("case");
502        else if (node instanceof AST_Default)
503            base54.consider("default");
504        else if (node instanceof AST_With)
505            base54.consider("with");
506        else if (node instanceof AST_ObjectSetter)
507            base54.consider("set" + node.key);
508        else if (node instanceof AST_ObjectGetter)
509            base54.consider("get" + node.key);
510        else if (node instanceof AST_ObjectKeyVal)
511            base54.consider(node.key);
512        else if (node instanceof AST_New)
513            base54.consider("new");
514        else if (node instanceof AST_This)
515            base54.consider("this");
516        else if (node instanceof AST_Try)
517            base54.consider("try");
518        else if (node instanceof AST_Catch)
519            base54.consider("catch");
520        else if (node instanceof AST_Finally)
521            base54.consider("finally");
522        else if (node instanceof AST_Symbol && node.unmangleable(options))
523            base54.consider(node.name);
524        else if (node instanceof AST_Unary || node instanceof AST_Binary)
525            base54.consider(node.operator);
526        else if (node instanceof AST_Dot)
527            base54.consider(node.property);
528    });
529    this.walk(tw);
530    base54.sort();
531});
532
533var base54 = (function() {
534    var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789";
535    var chars, frequency;
536    function reset() {
537        frequency = Object.create(null);
538        chars = string.split("").map(function(ch){ return ch.charCodeAt(0) });
539        chars.forEach(function(ch){ frequency[ch] = 0 });
540    }
541    base54.consider = function(str){
542        for (var i = str.length; --i >= 0;) {
543            var code = str.charCodeAt(i);
544            if (code in frequency) ++frequency[code];
545        }
546    };
547    base54.sort = function() {
548        chars = mergeSort(chars, function(a, b){
549            if (is_digit(a) && !is_digit(b)) return 1;
550            if (is_digit(b) && !is_digit(a)) return -1;
551            return frequency[b] - frequency[a];
552        });
553    };
554    base54.reset = reset;
555    reset();
556    base54.get = function(){ return chars };
557    base54.freq = function(){ return frequency };
558    function base54(num) {
559        var ret = "", base = 54;
560        num++;
561        do {
562            num--;
563            ret += String.fromCharCode(chars[num % base]);
564            num = Math.floor(num / base);
565            base = 64;
566        } while (num > 0);
567        return ret;
568    };
569    return base54;
570})();
571
572AST_Toplevel.DEFMETHOD("scope_warnings", function(options){
573    options = defaults(options, {
574        assign_to_global : true,
575        eval             : true,
576        func_arguments   : true,
577        nested_defuns    : true,
578        undeclared       : false, // this makes a lot of noise
579        unreferenced     : true,
580    });
581    var tw = new TreeWalker(function(node){
582        if (options.undeclared
583            && node instanceof AST_SymbolRef
584            && node.undeclared())
585        {
586            // XXX: this also warns about JS standard names,
587            // i.e. Object, Array, parseInt etc.  Should add a list of
588            // exceptions.
589            AST_Node.warn("Undeclared symbol: {name} [{file}:{line},{col}]", {
590                name: node.name,
591                file: node.start.file,
592                line: node.start.line,
593                col: node.start.col
594            });
595        }
596        if (options.assign_to_global)
597        {
598            var sym = null;
599            if (node instanceof AST_Assign && node.left instanceof AST_SymbolRef)
600                sym = node.left;
601            else if (node instanceof AST_ForIn && node.init instanceof AST_SymbolRef)
602                sym = node.init;
603            if (sym
604                && (sym.undeclared()
605                    || (sym.global() && sym.scope !== sym.definition().scope))) {
606                AST_Node.warn("{msg}: {name} [{file}:{line},{col}]", {
607                    msg: sym.undeclared() ? "Accidental global?" : "Assignment to global",
608                    name: sym.name,
609                    file: sym.start.file,
610                    line: sym.start.line,
611                    col: sym.start.col
612                });
613            }
614        }
615        if (options.eval
616            && node instanceof AST_SymbolRef
617            && node.undeclared()
618            && node.name == "eval") {
619            AST_Node.warn("Eval is used [{file}:{line},{col}]", node.start);
620        }
621        if (options.unreferenced
622            && (node instanceof AST_SymbolDeclaration || node instanceof AST_Label)
623            && !(node instanceof AST_SymbolCatch)
624            && node.unreferenced()) {
625            AST_Node.warn("{type} {name} is declared but not referenced [{file}:{line},{col}]", {
626                type: node instanceof AST_Label ? "Label" : "Symbol",
627                name: node.name,
628                file: node.start.file,
629                line: node.start.line,
630                col: node.start.col
631            });
632        }
633        if (options.func_arguments
634            && node instanceof AST_Lambda
635            && node.uses_arguments) {
636            AST_Node.warn("arguments used in function {name} [{file}:{line},{col}]", {
637                name: node.name ? node.name.name : "anonymous",
638                file: node.start.file,
639                line: node.start.line,
640                col: node.start.col
641            });
642        }
643        if (options.nested_defuns
644            && node instanceof AST_Defun
645            && !(tw.parent() instanceof AST_Scope)) {
646            AST_Node.warn("Function {name} declared in nested statement \"{type}\" [{file}:{line},{col}]", {
647                name: node.name.name,
648                type: tw.parent().TYPE,
649                file: node.start.file,
650                line: node.start.line,
651                col: node.start.col
652            });
653        }
654    });
655    this.walk(tw);
656});
657