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