improve `Dictionary` performance (#5202)
authorAlex Lam S.L <alexlamsl@gmail.com>
Sun, 5 Dec 2021 06:58:52 +0000 (06:58 +0000)
committerGitHub <noreply@github.com>
Sun, 5 Dec 2021 06:58:52 +0000 (14:58 +0800)
- workaround `__proto__` quirks on v8

bin/uglifyjs
lib/compress.js
lib/propmangle.js
lib/scope.js
lib/sourcemap.js
lib/utils.js
test/compress/evaluate.js
test/compress/objects.js

index 08e3d49..6ee968f 100755 (executable)
@@ -519,7 +519,7 @@ function read_file(path, default_value) {
 }
 
 function parse_js(value, options, flag) {
-    if (!options || typeof options != "object") options = {};
+    if (!options || typeof options != "object") options = Object.create(null);
     if (typeof value == "string") try {
         UglifyJS.parse(value, {
             expression: true
index f7232e9..1a1d969 100644 (file)
@@ -185,90 +185,89 @@ function Compressor(options, false_by_default) {
     };
 }
 
-Compressor.prototype = new TreeTransformer;
-merge(Compressor.prototype, {
-    option: function(key) { return this.options[key] },
-    exposed: function(def) {
-        if (def.exported) return true;
-        if (def.undeclared) return true;
-        if (!(def.global || def.scope.resolve() instanceof AST_Toplevel)) return false;
-        var toplevel = this.toplevel;
-        return !all(def.orig, function(sym) {
-            return toplevel[sym instanceof AST_SymbolDefun ? "funcs" : "vars"];
-        });
-    },
-    compress: function(node) {
-        node = node.resolve_defines(this);
-        node.hoist_exports(this);
-        if (this.option("expression")) {
-            node.process_expression(true);
-        }
-        var merge_vars = this.options.merge_vars;
-        var passes = +this.options.passes || 1;
-        var min_count = 1 / 0;
-        var stopping = false;
-        var mangle = { ie: this.option("ie") };
-        for (var pass = 0; pass < passes; pass++) {
-            node.figure_out_scope(mangle);
-            if (pass > 0 || this.option("reduce_vars"))
-                node.reset_opt_flags(this);
-            this.options.merge_vars = merge_vars && (stopping || pass == passes - 1);
-            node = node.transform(this);
-            if (passes > 1) {
-                var count = 0;
-                node.walk(new TreeWalker(function() {
-                    count++;
-                }));
-                AST_Node.info("pass {pass}: last_count: {min_count}, count: {count}", {
-                    pass: pass,
-                    min_count: min_count,
-                    count: count,
-                });
-                if (count < min_count) {
-                    min_count = count;
-                    stopping = false;
-                } else if (stopping) {
-                    break;
-                } else {
-                    stopping = true;
-                }
+Compressor.prototype = new TreeTransformer(function(node, descend, in_list) {
+    if (node._squeezed) return node;
+    var is_scope = node instanceof AST_Scope;
+    if (is_scope) {
+        node.hoist_properties(this);
+        node.hoist_declarations(this);
+        node.process_boolean_returns(this);
+    }
+    // Before https://github.com/mishoo/UglifyJS/pull/1602 AST_Node.optimize()
+    // would call AST_Node.transform() if a different instance of AST_Node is
+    // produced after OPT().
+    // This corrupts TreeWalker.stack, which cause AST look-ups to malfunction.
+    // Migrate and defer all children's AST_Node.transform() to below, which
+    // will now happen after this parent AST_Node has been properly substituted
+    // thus gives a consistent AST snapshot.
+    descend(node, this);
+    // Existing code relies on how AST_Node.optimize() worked, and omitting the
+    // following replacement call would result in degraded efficiency of both
+    // output and performance.
+    descend(node, this);
+    var opt = node.optimize(this);
+    if (is_scope && opt === node && !this.has_directive("use asm") && !opt.pinned()) {
+        opt.drop_unused(this);
+        if (opt.merge_variables(this)) opt.drop_unused(this);
+        descend(opt, this);
+    }
+    if (opt === node) opt._squeezed = true;
+    return opt;
+});
+Compressor.prototype.option = function(key) {
+    return this.options[key];
+};
+Compressor.prototype.exposed = function(def) {
+    if (def.exported) return true;
+    if (def.undeclared) return true;
+    if (!(def.global || def.scope.resolve() instanceof AST_Toplevel)) return false;
+    var toplevel = this.toplevel;
+    return !all(def.orig, function(sym) {
+        return toplevel[sym instanceof AST_SymbolDefun ? "funcs" : "vars"];
+    });
+};
+Compressor.prototype.compress = function(node) {
+    node = node.resolve_defines(this);
+    node.hoist_exports(this);
+    if (this.option("expression")) {
+        node.process_expression(true);
+    }
+    var merge_vars = this.options.merge_vars;
+    var passes = +this.options.passes || 1;
+    var min_count = 1 / 0;
+    var stopping = false;
+    var mangle = { ie: this.option("ie") };
+    for (var pass = 0; pass < passes; pass++) {
+        node.figure_out_scope(mangle);
+        if (pass > 0 || this.option("reduce_vars"))
+            node.reset_opt_flags(this);
+        this.options.merge_vars = merge_vars && (stopping || pass == passes - 1);
+        node = node.transform(this);
+        if (passes > 1) {
+            var count = 0;
+            node.walk(new TreeWalker(function() {
+                count++;
+            }));
+            AST_Node.info("pass {pass}: last_count: {min_count}, count: {count}", {
+                pass: pass,
+                min_count: min_count,
+                count: count,
+            });
+            if (count < min_count) {
+                min_count = count;
+                stopping = false;
+            } else if (stopping) {
+                break;
+            } else {
+                stopping = true;
             }
         }
-        if (this.option("expression")) {
-            node.process_expression(false);
-        }
-        return node;
-    },
-    before: function(node, descend, in_list) {
-        if (node._squeezed) return node;
-        var is_scope = node instanceof AST_Scope;
-        if (is_scope) {
-            node.hoist_properties(this);
-            node.hoist_declarations(this);
-            node.process_boolean_returns(this);
-        }
-        // Before https://github.com/mishoo/UglifyJS/pull/1602 AST_Node.optimize()
-        // would call AST_Node.transform() if a different instance of AST_Node is
-        // produced after OPT().
-        // This corrupts TreeWalker.stack, which cause AST look-ups to malfunction.
-        // Migrate and defer all children's AST_Node.transform() to below, which
-        // will now happen after this parent AST_Node has been properly substituted
-        // thus gives a consistent AST snapshot.
-        descend(node, this);
-        // Existing code relies on how AST_Node.optimize() worked, and omitting the
-        // following replacement call would result in degraded efficiency of both
-        // output and performance.
-        descend(node, this);
-        var opt = node.optimize(this);
-        if (is_scope && opt === node && !this.has_directive("use asm") && !opt.pinned()) {
-            opt.drop_unused(this);
-            if (opt.merge_variables(this)) opt.drop_unused(this);
-            descend(opt, this);
-        }
-        if (opt === node) opt._squeezed = true;
-        return opt;
     }
-});
+    if (this.option("expression")) {
+        node.process_expression(false);
+    }
+    return node;
+};
 
 (function(OPT) {
     OPT(AST_Node, function(self, compressor) {
@@ -1872,9 +1871,9 @@ merge(Compressor.prototype, {
         function collapse(statements, compressor) {
             if (scope.pinned()) return statements;
             var args;
-            var assignments = Object.create(null);
+            var assignments = new Dictionary();
             var candidates = [];
-            var declare_only = Object.create(null);
+            var declare_only = new Dictionary();
             var force_single;
             var stat_index = statements.length;
             var scanner = new TreeTransformer(function(node, descend) {
@@ -2403,7 +2402,7 @@ merge(Compressor.prototype, {
                     });
                     args = iife.args.slice();
                     var len = args.length;
-                    var names = Object.create(null);
+                    var names = new Dictionary();
                     for (var i = fn.argnames.length; --i >= 0;) {
                         var sym = fn.argnames[i];
                         var arg = args[i];
@@ -2418,8 +2417,8 @@ merge(Compressor.prototype, {
                             candidates.length = 0;
                             break;
                         }
-                        if (sym.name in names) continue;
-                        names[sym.name] = true;
+                        if (names.has(sym.name)) continue;
+                        names.set(sym.name, true);
                         if (value) arg = !arg || is_undefined(arg) ? value : null;
                         if (!arg && !value) {
                             arg = make_node(AST_Undefined, sym).transform(compressor);
@@ -2452,7 +2451,7 @@ merge(Compressor.prototype, {
                     extract_candidates(lhs);
                     extract_candidates(expr.right);
                     if (lhs instanceof AST_SymbolRef && expr.operator == "=") {
-                        assignments[lhs.name] = (assignments[lhs.name] || 0) + 1;
+                        assignments.set(lhs.name, (assignments.get(lhs.name) || 0) + 1);
                     }
                 } else if (expr instanceof AST_Await) {
                     extract_candidates(expr.expression, unused);
@@ -2544,7 +2543,7 @@ merge(Compressor.prototype, {
                                 candidates.push(hit_stack.slice());
                             }
                         } else {
-                            declare_only[expr.name.name] = (declare_only[expr.name.name] || 0) + 1;
+                            declare_only.set(expr.name.name, (declare_only.get(expr.name.name) || 0) + 1);
                         }
                     }
                     if (expr.value) extract_candidates(expr.value);
@@ -2760,7 +2759,7 @@ merge(Compressor.prototype, {
             }
 
             function remaining_refs(def) {
-                return def.references.length - def.replaced - (assignments[def.name] || 0);
+                return def.references.length - def.replaced - (assignments.get(def.name) || 0);
             }
 
             function get_lhs(expr) {
@@ -2792,7 +2791,7 @@ merge(Compressor.prototype, {
                     if (def.const_redefs) return;
                     if (!member(lhs, def.orig)) return;
                     if (scope.uses_arguments && is_funarg(def)) return;
-                    var declared = def.orig.length - def.eliminated - (declare_only[def.name] || 0);
+                    var declared = def.orig.length - def.eliminated - (declare_only.get(def.name) || 0);
                     remaining = remaining_refs(def);
                     if (def.fixed) remaining = Math.min(remaining, def.references.filter(function(ref) {
                         if (!ref.fixed) return true;
@@ -3001,7 +3000,7 @@ merge(Compressor.prototype, {
                     if (hit_index <= end) return handle_custom_scan_order(node, tt);
                     hit = true;
                     if (node instanceof AST_VarDef) {
-                        declare_only[node.name.name] = (declare_only[node.name.name] || 0) + 1;
+                        declare_only.set(node.name.name, (declare_only.get(node.name.name) || 0) + 1);
                         if (value_def) value_def.replaced++;
                         node = node.clone();
                         node.value = null;
@@ -3626,10 +3625,10 @@ merge(Compressor.prototype, {
         }
 
         function trim_assigns(name, value, exprs) {
-            var names = Object.create(null);
-            names[name.name] = true;
+            var names = new Dictionary();
+            names.set(name.name, true);
             while (value instanceof AST_Assign && value.operator == "=") {
-                if (value.left instanceof AST_SymbolRef) names[value.left.name] = true;
+                if (value.left instanceof AST_SymbolRef) names.set(value.left.name, true);
                 value = value.right;
             }
             if (!(value instanceof AST_Object)) return;
@@ -3647,7 +3646,7 @@ merge(Compressor.prototype, {
                 if (!(node.left instanceof AST_PropAccess)) return;
                 var sym = node.left.expression;
                 if (!(sym instanceof AST_SymbolRef)) return;
-                if (!(sym.name in names)) return;
+                if (!names.has(sym.name)) return;
                 if (!node.right.is_constant_expression(scope)) return;
                 var prop = node.left.property;
                 if (prop instanceof AST_Node) {
@@ -4618,7 +4617,6 @@ merge(Compressor.prototype, {
             }
             return this;
         });
-        var nonsafe_props = makePredicate("__proto__ toString valueOf");
         def(AST_Object, function(compressor, ignore_side_effects, cached, depth) {
             if (compressor.option("unsafe")) {
                 var val = {};
@@ -4630,7 +4628,12 @@ merge(Compressor.prototype, {
                         key = key._eval(compressor, ignore_side_effects, cached, depth);
                         if (key === prop.key) return this;
                     }
-                    if (nonsafe_props[key]) return this;
+                    switch (key) {
+                      case "__proto__":
+                      case "toString":
+                      case "valueOf":
+                        return this;
+                    }
                     val[key] = prop.value._eval(compressor, ignore_side_effects, cached, depth);
                     if (val[key] === prop.value) return this;
                 }
@@ -7268,7 +7271,7 @@ merge(Compressor.prototype, {
                     var prop_keys, prop_map;
                     if (value instanceof AST_Object) {
                         prop_keys = [];
-                        prop_map = Object.create(null);
+                        prop_map = new Dictionary();
                         value.properties.forEach(function(prop, index) {
                             if (prop instanceof AST_Spread) return prop_map = false;
                             var key = prop.key;
@@ -7276,7 +7279,7 @@ merge(Compressor.prototype, {
                             if (key instanceof AST_Node) {
                                 prop_map = false;
                             } else if (prop_map && !(prop instanceof AST_ObjectSetter)) {
-                                prop_map[key] = prop;
+                                prop_map.set(key, prop);
                             }
                             prop_keys[index] = key;
                         });
@@ -7285,8 +7288,8 @@ merge(Compressor.prototype, {
                         value = false;
                         node.rest = node.rest.transform(compressor.option("rests") ? trimmer : tt);
                     }
-                    var can_drop = Object.create(null);
-                    var drop_keys = drop && Object.create(null);
+                    var can_drop = new Dictionary();
+                    var drop_keys = drop && new Dictionary();
                     var properties = [];
                     node.properties.map(function(prop) {
                         var key = prop.key;
@@ -7297,7 +7300,7 @@ merge(Compressor.prototype, {
                         if (key instanceof AST_Node) {
                             drop_keys = false;
                         } else {
-                            can_drop[key] = !(key in can_drop);
+                            can_drop.set(key, !can_drop.has(key));
                         }
                         return key;
                     }).forEach(function(key, index) {
@@ -7307,8 +7310,8 @@ merge(Compressor.prototype, {
                             value = false;
                             trimmed = prop.value.transform(trimmer) || retain_lhs(prop.value);
                         } else {
-                            drop = drop_keys && can_drop[key];
-                            var mapped = prop_map && prop_map[key];
+                            drop = drop_keys && can_drop.get(key);
+                            var mapped = prop_map && prop_map.get(key);
                             if (mapped) {
                                 value = mapped.value;
                                 if (value instanceof AST_Accessor) value = false;
@@ -7318,21 +7321,21 @@ merge(Compressor.prototype, {
                             trimmed = prop.value.transform(trimmer);
                             if (!trimmed) {
                                 if (node.rest || retain_key(prop)) trimmed = retain_lhs(prop.value);
-                                if (drop_keys && !(key in drop_keys)) {
+                                if (drop_keys && !drop_keys.has(key)) {
                                     if (mapped) {
-                                        drop_keys[key] = mapped;
+                                        drop_keys.set(key, mapped);
                                         if (value === null) {
-                                            prop_map[key] = retain_key(mapped) && make_node(AST_ObjectKeyVal, mapped, {
+                                            prop_map.set(key, retain_key(mapped) && make_node(AST_ObjectKeyVal, mapped, {
                                                 key: mapped.key,
                                                 value: make_node(AST_Number, mapped, { value: 0 }),
-                                            });
+                                            }));
                                         }
                                     } else {
-                                        drop_keys[key] = true;
+                                        drop_keys.set(key, true);
                                     }
                                 }
                             } else if (drop_keys) {
-                                drop_keys[key] = false;
+                                drop_keys.set(key, false);
                             }
                             if (value) mapped.value = value;
                         }
@@ -7347,10 +7350,10 @@ merge(Compressor.prototype, {
                         if (prop instanceof AST_Spread) return prop;
                         var key = prop_keys[index];
                         if (key instanceof AST_Node) return prop;
-                        if (key in drop_keys) {
-                            var mapped = drop_keys[key];
+                        if (drop_keys.has(key)) {
+                            var mapped = drop_keys.get(key);
                             if (!mapped) return prop;
-                            if (mapped === prop) return prop_map[key] || List.skip;
+                            if (mapped === prop) return prop_map.get(key) || List.skip;
                         } else if (node.rest) {
                             return prop;
                         }
@@ -7443,7 +7446,7 @@ merge(Compressor.prototype, {
             if (var_decl <= 1) hoist_vars = false;
         }
         if (!hoist_funs && !hoist_vars) return;
-        var consts = Object.create(null);
+        var consts = new Dictionary();
         var dirs = [];
         var hoisted = [];
         var vars = new Dictionary(), vars_found = 0;
@@ -7469,7 +7472,7 @@ merge(Compressor.prototype, {
                 if (!all(node.definitions, function(defn) {
                     var sym = defn.name;
                     return sym instanceof AST_SymbolVar
-                        && !consts[sym.name]
+                        && !consts.has(sym.name)
                         && self.find_variable(sym.name) === sym.definition();
                 })) return node;
                 node.definitions.forEach(function(def) {
@@ -7488,7 +7491,7 @@ merge(Compressor.prototype, {
             }
             if (node instanceof AST_Scope) return node;
             if (node instanceof AST_SymbolConst) {
-                consts[node.name] = true;
+                consts.set(node.name, true);
                 return node;
             }
         });
@@ -7650,12 +7653,12 @@ merge(Compressor.prototype, {
     AST_BlockScope.DEFMETHOD("var_names", function() {
         var var_names = this._var_names;
         if (!var_names) {
-            this._var_names = var_names = Object.create(null);
+            this._var_names = var_names = new Dictionary();
             this.enclosed.forEach(function(def) {
-                var_names[def.name] = true;
+                var_names.set(def.name, true);
             });
             this.variables.each(function(def, name) {
-                var_names[name] = true;
+                var_names.set(name, true);
             });
         }
         return var_names;
@@ -7674,7 +7677,7 @@ merge(Compressor.prototype, {
         prefix = prefix.replace(/(?:^[^a-z_$]|[^a-z0-9_$])/ig, "_");
         var name = prefix;
         for (var i = 0; !all(scopes, function(scope) {
-            return !scope.var_names()[name];
+            return !scope.var_names().has(name);
         }); i++) name = prefix + "$" + i;
         var sym = make_node(type, orig, {
             name: name,
@@ -7683,7 +7686,7 @@ merge(Compressor.prototype, {
         var def = this.def_variable(sym);
         scopes.forEach(function(scope) {
             scope.enclosed.push(def);
-            scope.var_names()[name] = true;
+            scope.var_names().set(name, true);
         });
         return sym;
     });
@@ -9142,7 +9145,7 @@ merge(Compressor.prototype, {
         var scope = def.scope.resolve();
         for (var s = def.scope; s !== scope;) {
             s = s.parent_scope;
-            if (s.var_names()[def.name]) return true;
+            if (s.var_names().has(def.name)) return true;
         }
     }
 
@@ -9158,7 +9161,7 @@ merge(Compressor.prototype, {
                         def.scope = scope;
                         scope.variables.set(def.name, def);
                         scope.enclosed.push(def);
-                        scope.var_names()[def.name] = true;
+                        scope.var_names().set(def.name, true);
                     }),
                     value: defn.value,
                 });
@@ -9959,30 +9962,33 @@ merge(Compressor.prototype, {
         }
 
         function var_exists(defined, name) {
-            return defined[name] || identifier_atom[name] || scope.var_names()[name];
+            return defined.has(name) || identifier_atom[name] || scope.var_names().has(name);
         }
 
-        function can_inject_args(defined, used, safe_to_inject) {
+        function can_inject_args(defined, safe_to_inject) {
             var abort = false;
             fn.each_argname(function(arg) {
                 if (abort) return;
                 if (arg.__unused) return;
                 if (!safe_to_inject || var_exists(defined, arg.name)) return abort = true;
-                used[arg.name] = true;
+                arg_used.set(arg.name, true);
                 if (in_loop) in_loop.push(arg.definition());
             });
             return !abort;
         }
 
-        function can_inject_vars(defined, used, safe_to_inject) {
+        function can_inject_vars(defined, safe_to_inject) {
             for (var i = 0; i < fn.body.length; i++) {
                 var stat = fn.body[i];
                 if (stat instanceof AST_LambdaDefinition) {
-                    if (!safe_to_inject || var_exists(used, stat.name.name)) return false;
+                    var name = stat.name;
+                    if (!safe_to_inject) return false;
+                    if (arg_used.has(name.name)) return false;
+                    if (var_exists(defined, name.name)) return false;
                     if (!all(stat.enclosed, function(def) {
-                        return def.scope === stat || !defined[def.name];
+                        return def.scope === stat || !defined.has(def.name);
                     })) return false;
-                    if (in_loop) in_loop.push(stat.name.definition());
+                    if (in_loop) in_loop.push(name.definition());
                     continue;
                 }
                 if (!(stat instanceof AST_Var)) continue;
@@ -9997,12 +10003,12 @@ merge(Compressor.prototype, {
         }
 
         function can_inject_symbols() {
-            var defined = Object.create(null);
+            var defined = new Dictionary();
             var level = 0, child;
             scope = current;
             do {
                 if (scope.variables) scope.variables.each(function(def) {
-                    defined[def.name] = true;
+                    defined.set(def.name, true);
                 });
                 child = scope;
                 scope = compressor.parent(level++);
@@ -10025,23 +10031,22 @@ merge(Compressor.prototype, {
             var safe_to_inject = exp !== fn || fn.parent_scope.resolve() === scope;
             if (scope instanceof AST_Toplevel) {
                 if (compressor.toplevel.vars) {
-                    defined["arguments"] = true;
+                    defined.set("arguments", true);
                 } else {
                     safe_to_inject = false;
                 }
             }
+            arg_used = new Dictionary();
             var inline = compressor.option("inline");
-            arg_used = Object.create(defined);
-            if (!can_inject_args(defined, arg_used, inline >= 2 && safe_to_inject)) return false;
-            var used = Object.create(arg_used);
-            if (!can_inject_vars(defined, used, inline >= 3 && safe_to_inject)) return false;
+            if (!can_inject_args(defined, inline >= 2 && safe_to_inject)) return false;
+            if (!can_inject_vars(defined, inline >= 3 && safe_to_inject)) return false;
             return !in_loop || in_loop.length == 0 || !is_reachable(fn, in_loop);
         }
 
         function append_var(decls, expressions, name, value) {
             var def = name.definition();
-            if (!scope.var_names()[name.name]) {
-                scope.var_names()[name.name] = true;
+            if (!scope.var_names().has(name.name)) {
+                scope.var_names().set(name.name, true);
                 decls.push(make_node(AST_VarDef, name, {
                     name: name,
                     value: null,
@@ -10075,7 +10080,7 @@ merge(Compressor.prototype, {
                     name = argname;
                 }
                 var value = self.args[i];
-                if (name.__unused || scope.var_names()[name.name]) {
+                if (name.__unused || scope.var_names().has(name.name)) {
                     if (value) expressions.push(value);
                 } else {
                     var symbol = make_node(AST_SymbolVar, name, name);
@@ -10153,7 +10158,7 @@ merge(Compressor.prototype, {
                         scope.functions.set(def.name, def);
                         scope.variables.set(def.name, def);
                         scope.enclosed.push(def);
-                        scope.var_names()[def.name] = true;
+                        scope.var_names().set(def.name, true);
                         args.push(stat);
                     }
                     continue;
@@ -10163,7 +10168,7 @@ merge(Compressor.prototype, {
                     var var_def = stat.definitions[j];
                     var name = flatten_var(var_def.name);
                     append_var(decl_var, expr_var, name, var_def.value);
-                    if (in_loop && !HOP(arg_used, name.name)) {
+                    if (in_loop && !arg_used.has(name.name)) {
                         var def = fn.variables.get(name.name);
                         var sym = make_node(AST_SymbolRef, name, name);
                         def.references.push(sym);
@@ -10196,9 +10201,9 @@ merge(Compressor.prototype, {
             }));
             [].splice.apply(scope.body, args);
             fn.enclosed.forEach(function(def) {
-                if (scope.var_names()[def.name]) return;
+                if (scope.var_names().has(def.name)) return;
                 scope.enclosed.push(def);
-                scope.var_names()[def.name] = true;
+                scope.var_names().set(def.name, true);
             });
             return expressions;
         }
@@ -11320,9 +11325,9 @@ merge(Compressor.prototype, {
                     var scope = self.scope.resolve();
                     fixed.enclosed.forEach(function(def) {
                         if (fixed.variables.has(def.name)) return;
-                        if (scope.var_names()[def.name]) return;
+                        if (scope.var_names().has(def.name)) return;
                         scope.enclosed.push(def);
-                        scope.var_names()[def.name] = true;
+                        scope.var_names().set(def.name, true);
                     });
                 }
                 var value;
index e7dc509..d5e13da 100644 (file)
@@ -44,7 +44,7 @@
 "use strict";
 
 var builtins = function() {
-    var names = [];
+    var names = new Dictionary();
     // NaN will be included due to Number.NaN
     [
         "null",
@@ -72,10 +72,10 @@ var builtins = function() {
             Object.getOwnPropertyNames(ctor.prototype).map(add);
         }
     });
-    return makePredicate(names);
+    return names;
 
     function add(name) {
-        names.push(name);
+        names.set(name, true);
     }
 }();
 
@@ -116,9 +116,9 @@ function mangle_properties(ast, options) {
         reserved: null,
     }, true);
 
-    var reserved = Object.create(options.builtins ? null : builtins);
+    var reserved = options.builtins ? new Dictionary() : builtins.clone();
     if (Array.isArray(options.reserved)) options.reserved.forEach(function(name) {
-        reserved[name] = true;
+        reserved.set(name, true);
     });
 
     var cname = -1;
@@ -126,7 +126,7 @@ function mangle_properties(ast, options) {
     if (options.cache) {
         cache = options.cache.props;
         cache.each(function(name) {
-            reserved[name] = true;
+            reserved.set(name, true);
         });
     } else {
         cache = new Dictionary();
@@ -141,8 +141,8 @@ function mangle_properties(ast, options) {
     var debug_suffix;
     if (debug) debug_suffix = options.debug === true ? "" : options.debug;
 
-    var names_to_mangle = Object.create(null);
-    var unmangleable = Object.create(reserved);
+    var names_to_mangle = new Dictionary();
+    var unmangleable = reserved.clone();
 
     // step 1: find candidates to mangle
     ast.walk(new TreeWalker(function(node) {
@@ -211,20 +211,20 @@ function mangle_properties(ast, options) {
     // only function declarations after this line
 
     function can_mangle(name) {
-        if (unmangleable[name]) return false;
+        if (unmangleable.has(name)) return false;
         if (/^-?[0-9]+(\.[0-9]+)?(e[+-][0-9]+)?$/.test(name)) return false;
         return true;
     }
 
     function should_mangle(name) {
-        if (reserved[name]) return false;
+        if (reserved.has(name)) return false;
         if (regex && !regex.test(name)) return false;
-        return cache.has(name) || names_to_mangle[name];
+        return cache.has(name) || names_to_mangle.has(name);
     }
 
     function add(name) {
-        if (can_mangle(name)) names_to_mangle[name] = true;
-        if (!should_mangle(name)) unmangleable[name] = true;
+        if (can_mangle(name)) names_to_mangle.set(name, true);
+        if (!should_mangle(name)) unmangleable.set(name, true);
     }
 
     function mangle(name) {
index 2219b89..a6026e5 100644 (file)
@@ -510,12 +510,12 @@ function names_in_use(scope, options) {
     if (!names) {
         scope.cname = -1;
         scope.cname_holes = [];
-        scope.names_in_use = names = Object.create(null);
+        scope.names_in_use = names = new Dictionary();
         var cache = options.cache && options.cache.props;
         scope.enclosed.forEach(function(def) {
-            if (def.unmangleable(options)) names[def.name] = true;
+            if (def.unmangleable(options)) names.set(def.name, true);
             if (def.global && cache && cache.has(def.name)) {
-                names[cache.get(def.name)] = true;
+                names.set(cache.get(def.name), true);
             }
         });
     }
@@ -526,34 +526,33 @@ function next_mangled_name(def, options) {
     var scope = def.scope;
     var in_use = names_in_use(scope, options);
     var holes = scope.cname_holes;
-    var names = Object.create(null);
+    var names = new Dictionary();
     var scopes = [ scope ];
     def.forEach(function(sym) {
         var scope = sym.scope;
         do {
-            if (scopes.indexOf(scope) < 0) {
-                for (var name in names_in_use(scope, options)) {
-                    names[name] = true;
-                }
-                scopes.push(scope);
-            } else break;
+            if (member(scope, scopes)) break;
+            names_in_use(scope, options).each(function(marker, name) {
+                names.set(name, marker);
+            });
+            scopes.push(scope);
         } while (scope = scope.parent_scope);
     });
     var name;
     for (var i = 0; i < holes.length; i++) {
         name = base54(holes[i]);
-        if (names[name]) continue;
+        if (names.has(name)) continue;
         holes.splice(i, 1);
-        in_use[name] = true;
+        in_use.set(name, true);
         return name;
     }
     while (true) {
         name = base54(++scope.cname);
-        if (in_use[name] || RESERVED_WORDS[name] || options.reserved.has[name]) continue;
-        if (!names[name]) break;
+        if (in_use.has(name) || RESERVED_WORDS[name] || options.reserved.has[name]) continue;
+        if (!names.has(name)) break;
         holes.push(scope.cname);
     }
-    in_use[name] = true;
+    in_use.set(name, true);
     return name;
 }
 
@@ -598,7 +597,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
     if (options.cache && options.cache.props) {
         var mangled_names = names_in_use(this, options);
         options.cache.props.each(function(mangled_name) {
-            mangled_names[mangled_name] = true;
+            mangled_names.set(mangled_name, true);
         });
     }
 
index 94966a6..a230a44 100644 (file)
@@ -77,21 +77,23 @@ function vlq_encode(num) {
 }
 
 function create_array_map() {
-    var map = Object.create(null);
+    var map = new Dictionary();
     var array = [];
     array.index = function(name) {
-        if (!HOP(map, name)) {
-            map[name] = array.length;
+        var index = map.get(name);
+        if (!(index >= 0)) {
+            index = array.length;
             array.push(name);
+            map.set(name, index);
         }
-        return map[name];
+        return index;
     };
     return array;
 }
 
 function SourceMap(options) {
     var sources = create_array_map();
-    var sources_content = options.includeSources && Object.create(null);
+    var sources_content = options.includeSources && new Dictionary();
     var names = create_array_map();
     var mappings = "";
     if (options.orig) Object.keys(options.orig).forEach(function(name) {
@@ -110,7 +112,7 @@ function SourceMap(options) {
         if (!sources_content || !map.sourcesContent) return;
         for (var i = 0; i < map.sources.length; i++) {
             var content = map.sourcesContent[i];
-            if (content) sources_content[map.sources[i]] = content;
+            if (content) sources_content.set(map.sources[i], content);
         }
     });
     var prev_source;
@@ -144,8 +146,8 @@ function SourceMap(options) {
             add(source, gen_line, gen_col, orig_line, orig_col, name);
         } : add,
         setSourceContent: sources_content ? function(source, content) {
-            if (!(source in sources_content)) {
-                sources_content[source] = content;
+            if (!sources_content.has(source)) {
+                sources_content.set(source, content);
             }
         } : noop,
         toString: function() {
@@ -155,7 +157,7 @@ function SourceMap(options) {
                 sourceRoot: options.root || undefined,
                 sources: sources,
                 sourcesContent: sources_content ? sources.map(function(source) {
-                    return sources_content[source] || null;
+                    return sources_content.get(source) || null;
                 }) : undefined,
                 names: names,
                 mappings: mappings,
index 69c2dcd..4c46584 100644 (file)
@@ -96,15 +96,6 @@ function defaults(args, defs, croak) {
     return defs;
 }
 
-function merge(obj, ext) {
-    var count = 0;
-    for (var i in ext) if (HOP(ext, i)) {
-        obj[i] = ext[i];
-        count++;
-    }
-    return count;
-}
-
 function noop() {}
 function return_false() { return false; }
 function return_true() { return true; }
@@ -171,63 +162,80 @@ function all(array, predicate) {
 }
 
 function Dictionary() {
-    this._values = Object.create(null);
-    this._size = 0;
+    this.values = Object.create(null);
 }
 Dictionary.prototype = {
     set: function(key, val) {
-        if (!this.has(key)) ++this._size;
-        this._values["$" + key] = val;
+        if (key == "__proto__") {
+            this.proto_value = val;
+        } else {
+            this.values[key] = val;
+        }
         return this;
     },
     add: function(key, val) {
-        if (this.has(key)) {
-            this.get(key).push(val);
+        var list = this.get(key);
+        if (list) {
+            list.push(val);
         } else {
             this.set(key, [ val ]);
         }
         return this;
     },
-    get: function(key) { return this._values["$" + key] },
+    get: function(key) {
+        return key == "__proto__" ? this.proto_value : this.values[key];
+    },
     del: function(key) {
-        if (this.has(key)) {
-            --this._size;
-            delete this._values["$" + key];
+        if (key == "__proto__") {
+            delete this.proto_value;
+        } else {
+            delete this.values[key];
         }
         return this;
     },
-    has: function(key) { return ("$" + key) in this._values },
+    has: function(key) {
+        return key == "__proto__" ? "proto_value" in this : key in this.values;
+    },
     all: function(predicate) {
-        for (var i in this._values)
-            if (!predicate(this._values[i], i.substr(1)))
-                return false;
+        for (var i in this.values)
+            if (!predicate(this.values[i], i)) return false;
+        if ("proto_value" in this && !predicate(this.proto_value, "__proto__")) return false;
         return true;
     },
     each: function(f) {
-        for (var i in this._values)
-            f(this._values[i], i.substr(1));
+        for (var i in this.values)
+            f(this.values[i], i);
+        if ("proto_value" in this) f(this.proto_value, "__proto__");
     },
     size: function() {
-        return this._size;
+        return Object.keys(this.values).length + ("proto_value" in this);
     },
     map: function(f) {
         var ret = [];
-        for (var i in this._values)
-            ret.push(f(this._values[i], i.substr(1)));
+        for (var i in this.values)
+            ret.push(f(this.values[i], i));
+        if ("proto_value" in this) ret.push(f(this.proto_value, "__proto__"));
         return ret;
     },
     clone: function() {
         var ret = new Dictionary();
-        for (var i in this._values)
-            ret._values[i] = this._values[i];
-        ret._size = this._size;
+        this.each(function(value, i) {
+            ret.set(i, value);
+        });
         return ret;
     },
-    toObject: function() { return this._values }
+    toObject: function() {
+        var obj = {};
+        this.each(function(value, i) {
+            obj["$" + i] = value;
+        });
+        return obj;
+    },
 };
 Dictionary.fromObject = function(obj) {
     var dict = new Dictionary();
-    dict._size = merge(dict._values, obj);
+    for (var i in obj)
+        if (HOP(obj, i)) dict.set(i.slice(1), obj[i]);
     return dict;
 };
 
index ed58ce6..c479a53 100644 (file)
@@ -3203,7 +3203,7 @@ issue_4552: {
     expect_stdout: "NaN"
 }
 
-issue_4886: {
+issue_4886_1: {
     options = {
         evaluate: true,
         unsafe: true,
@@ -3222,3 +3222,23 @@ issue_4886: {
     }
     expect_stdout: "true"
 }
+
+issue_4886_2: {
+    options = {
+        evaluate: true,
+        unsafe: true,
+    }
+    input: {
+        console.log("foo" in {
+            "foo": null,
+            __proto__: 42,
+        });
+    }
+    expect: {
+        console.log("foo" in {
+            "foo": null,
+            __proto__: 42,
+        });
+    }
+    expect_stdout: "true"
+}
index 3c7f2d1..beec4bf 100644 (file)
@@ -198,9 +198,9 @@ numeric_literal: {
     expect_exact: [
         'var obj = {',
         '    0: 0,',
-        '    "-0": 1,',
-        '    42: 3,',
         '    37: 4,',
+        '    42: 3,',
+        '    "-0": 1,',
         '    o: 5,',
         '    1e42: 8,',
         '    b: 7',