improve Dictionary performance (#5202)

- workaround `__proto__` quirks on v8
This commit is contained in:
Alex Lam S.L
2021-12-05 06:58:52 +00:00
committed by GitHub
parent 860aa9531b
commit b0799105c2
8 changed files with 255 additions and 221 deletions

View File

@@ -519,7 +519,7 @@ function read_file(path, default_value) {
} }
function parse_js(value, options, flag) { 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 { if (typeof value == "string") try {
UglifyJS.parse(value, { UglifyJS.parse(value, {
expression: true expression: true

View File

@@ -185,90 +185,89 @@ function Compressor(options, false_by_default) {
}; };
} }
Compressor.prototype = new TreeTransformer; Compressor.prototype = new TreeTransformer(function(node, descend, in_list) {
merge(Compressor.prototype, { if (node._squeezed) return node;
option: function(key) { return this.options[key] }, var is_scope = node instanceof AST_Scope;
exposed: function(def) { if (is_scope) {
if (def.exported) return true; node.hoist_properties(this);
if (def.undeclared) return true; node.hoist_declarations(this);
if (!(def.global || def.scope.resolve() instanceof AST_Toplevel)) return false; node.process_boolean_returns(this);
var toplevel = this.toplevel; }
return !all(def.orig, function(sym) { // Before https://github.com/mishoo/UglifyJS/pull/1602 AST_Node.optimize()
return toplevel[sym instanceof AST_SymbolDefun ? "funcs" : "vars"]; // 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.
compress: function(node) { // Migrate and defer all children's AST_Node.transform() to below, which
node = node.resolve_defines(this); // will now happen after this parent AST_Node has been properly substituted
node.hoist_exports(this); // thus gives a consistent AST snapshot.
if (this.option("expression")) { descend(node, this);
node.process_expression(true); // Existing code relies on how AST_Node.optimize() worked, and omitting the
} // following replacement call would result in degraded efficiency of both
var merge_vars = this.options.merge_vars; // output and performance.
var passes = +this.options.passes || 1; descend(node, this);
var min_count = 1 / 0; var opt = node.optimize(this);
var stopping = false; if (is_scope && opt === node && !this.has_directive("use asm") && !opt.pinned()) {
var mangle = { ie: this.option("ie") }; opt.drop_unused(this);
for (var pass = 0; pass < passes; pass++) { if (opt.merge_variables(this)) opt.drop_unused(this);
node.figure_out_scope(mangle); descend(opt, this);
if (pass > 0 || this.option("reduce_vars")) }
node.reset_opt_flags(this); if (opt === node) opt._squeezed = true;
this.options.merge_vars = merge_vars && (stopping || pass == passes - 1); return opt;
node = node.transform(this); });
if (passes > 1) { Compressor.prototype.option = function(key) {
var count = 0; return this.options[key];
node.walk(new TreeWalker(function() { };
count++; Compressor.prototype.exposed = function(def) {
})); if (def.exported) return true;
AST_Node.info("pass {pass}: last_count: {min_count}, count: {count}", { if (def.undeclared) return true;
pass: pass, if (!(def.global || def.scope.resolve() instanceof AST_Toplevel)) return false;
min_count: min_count, var toplevel = this.toplevel;
count: count, return !all(def.orig, function(sym) {
}); return toplevel[sym instanceof AST_SymbolDefun ? "funcs" : "vars"];
if (count < min_count) { });
min_count = count; };
stopping = false; Compressor.prototype.compress = function(node) {
} else if (stopping) { node = node.resolve_defines(this);
break; node.hoist_exports(this);
} else { if (this.option("expression")) {
stopping = true; 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) { (function(OPT) {
OPT(AST_Node, function(self, compressor) { OPT(AST_Node, function(self, compressor) {
@@ -1872,9 +1871,9 @@ merge(Compressor.prototype, {
function collapse(statements, compressor) { function collapse(statements, compressor) {
if (scope.pinned()) return statements; if (scope.pinned()) return statements;
var args; var args;
var assignments = Object.create(null); var assignments = new Dictionary();
var candidates = []; var candidates = [];
var declare_only = Object.create(null); var declare_only = new Dictionary();
var force_single; var force_single;
var stat_index = statements.length; var stat_index = statements.length;
var scanner = new TreeTransformer(function(node, descend) { var scanner = new TreeTransformer(function(node, descend) {
@@ -2403,7 +2402,7 @@ merge(Compressor.prototype, {
}); });
args = iife.args.slice(); args = iife.args.slice();
var len = args.length; var len = args.length;
var names = Object.create(null); var names = new Dictionary();
for (var i = fn.argnames.length; --i >= 0;) { for (var i = fn.argnames.length; --i >= 0;) {
var sym = fn.argnames[i]; var sym = fn.argnames[i];
var arg = args[i]; var arg = args[i];
@@ -2418,8 +2417,8 @@ merge(Compressor.prototype, {
candidates.length = 0; candidates.length = 0;
break; break;
} }
if (sym.name in names) continue; if (names.has(sym.name)) continue;
names[sym.name] = true; names.set(sym.name, true);
if (value) arg = !arg || is_undefined(arg) ? value : null; if (value) arg = !arg || is_undefined(arg) ? value : null;
if (!arg && !value) { if (!arg && !value) {
arg = make_node(AST_Undefined, sym).transform(compressor); arg = make_node(AST_Undefined, sym).transform(compressor);
@@ -2452,7 +2451,7 @@ merge(Compressor.prototype, {
extract_candidates(lhs); extract_candidates(lhs);
extract_candidates(expr.right); extract_candidates(expr.right);
if (lhs instanceof AST_SymbolRef && expr.operator == "=") { 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) { } else if (expr instanceof AST_Await) {
extract_candidates(expr.expression, unused); extract_candidates(expr.expression, unused);
@@ -2544,7 +2543,7 @@ merge(Compressor.prototype, {
candidates.push(hit_stack.slice()); candidates.push(hit_stack.slice());
} }
} else { } 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); if (expr.value) extract_candidates(expr.value);
@@ -2760,7 +2759,7 @@ merge(Compressor.prototype, {
} }
function remaining_refs(def) { 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) { function get_lhs(expr) {
@@ -2792,7 +2791,7 @@ merge(Compressor.prototype, {
if (def.const_redefs) return; if (def.const_redefs) return;
if (!member(lhs, def.orig)) return; if (!member(lhs, def.orig)) return;
if (scope.uses_arguments && is_funarg(def)) 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); remaining = remaining_refs(def);
if (def.fixed) remaining = Math.min(remaining, def.references.filter(function(ref) { if (def.fixed) remaining = Math.min(remaining, def.references.filter(function(ref) {
if (!ref.fixed) return true; if (!ref.fixed) return true;
@@ -3001,7 +3000,7 @@ merge(Compressor.prototype, {
if (hit_index <= end) return handle_custom_scan_order(node, tt); if (hit_index <= end) return handle_custom_scan_order(node, tt);
hit = true; hit = true;
if (node instanceof AST_VarDef) { 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++; if (value_def) value_def.replaced++;
node = node.clone(); node = node.clone();
node.value = null; node.value = null;
@@ -3626,10 +3625,10 @@ merge(Compressor.prototype, {
} }
function trim_assigns(name, value, exprs) { function trim_assigns(name, value, exprs) {
var names = Object.create(null); var names = new Dictionary();
names[name.name] = true; names.set(name.name, true);
while (value instanceof AST_Assign && value.operator == "=") { 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; value = value.right;
} }
if (!(value instanceof AST_Object)) return; if (!(value instanceof AST_Object)) return;
@@ -3647,7 +3646,7 @@ merge(Compressor.prototype, {
if (!(node.left instanceof AST_PropAccess)) return; if (!(node.left instanceof AST_PropAccess)) return;
var sym = node.left.expression; var sym = node.left.expression;
if (!(sym instanceof AST_SymbolRef)) return; 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; if (!node.right.is_constant_expression(scope)) return;
var prop = node.left.property; var prop = node.left.property;
if (prop instanceof AST_Node) { if (prop instanceof AST_Node) {
@@ -4618,7 +4617,6 @@ merge(Compressor.prototype, {
} }
return this; return this;
}); });
var nonsafe_props = makePredicate("__proto__ toString valueOf");
def(AST_Object, function(compressor, ignore_side_effects, cached, depth) { def(AST_Object, function(compressor, ignore_side_effects, cached, depth) {
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
var val = {}; var val = {};
@@ -4630,7 +4628,12 @@ merge(Compressor.prototype, {
key = key._eval(compressor, ignore_side_effects, cached, depth); key = key._eval(compressor, ignore_side_effects, cached, depth);
if (key === prop.key) return this; 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); val[key] = prop.value._eval(compressor, ignore_side_effects, cached, depth);
if (val[key] === prop.value) return this; if (val[key] === prop.value) return this;
} }
@@ -7268,7 +7271,7 @@ merge(Compressor.prototype, {
var prop_keys, prop_map; var prop_keys, prop_map;
if (value instanceof AST_Object) { if (value instanceof AST_Object) {
prop_keys = []; prop_keys = [];
prop_map = Object.create(null); prop_map = new Dictionary();
value.properties.forEach(function(prop, index) { value.properties.forEach(function(prop, index) {
if (prop instanceof AST_Spread) return prop_map = false; if (prop instanceof AST_Spread) return prop_map = false;
var key = prop.key; var key = prop.key;
@@ -7276,7 +7279,7 @@ merge(Compressor.prototype, {
if (key instanceof AST_Node) { if (key instanceof AST_Node) {
prop_map = false; prop_map = false;
} else if (prop_map && !(prop instanceof AST_ObjectSetter)) { } else if (prop_map && !(prop instanceof AST_ObjectSetter)) {
prop_map[key] = prop; prop_map.set(key, prop);
} }
prop_keys[index] = key; prop_keys[index] = key;
}); });
@@ -7285,8 +7288,8 @@ merge(Compressor.prototype, {
value = false; value = false;
node.rest = node.rest.transform(compressor.option("rests") ? trimmer : tt); node.rest = node.rest.transform(compressor.option("rests") ? trimmer : tt);
} }
var can_drop = Object.create(null); var can_drop = new Dictionary();
var drop_keys = drop && Object.create(null); var drop_keys = drop && new Dictionary();
var properties = []; var properties = [];
node.properties.map(function(prop) { node.properties.map(function(prop) {
var key = prop.key; var key = prop.key;
@@ -7297,7 +7300,7 @@ merge(Compressor.prototype, {
if (key instanceof AST_Node) { if (key instanceof AST_Node) {
drop_keys = false; drop_keys = false;
} else { } else {
can_drop[key] = !(key in can_drop); can_drop.set(key, !can_drop.has(key));
} }
return key; return key;
}).forEach(function(key, index) { }).forEach(function(key, index) {
@@ -7307,8 +7310,8 @@ merge(Compressor.prototype, {
value = false; value = false;
trimmed = prop.value.transform(trimmer) || retain_lhs(prop.value); trimmed = prop.value.transform(trimmer) || retain_lhs(prop.value);
} else { } else {
drop = drop_keys && can_drop[key]; drop = drop_keys && can_drop.get(key);
var mapped = prop_map && prop_map[key]; var mapped = prop_map && prop_map.get(key);
if (mapped) { if (mapped) {
value = mapped.value; value = mapped.value;
if (value instanceof AST_Accessor) value = false; if (value instanceof AST_Accessor) value = false;
@@ -7318,21 +7321,21 @@ merge(Compressor.prototype, {
trimmed = prop.value.transform(trimmer); trimmed = prop.value.transform(trimmer);
if (!trimmed) { if (!trimmed) {
if (node.rest || retain_key(prop)) trimmed = retain_lhs(prop.value); 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) { if (mapped) {
drop_keys[key] = mapped; drop_keys.set(key, mapped);
if (value === null) { 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, key: mapped.key,
value: make_node(AST_Number, mapped, { value: 0 }), value: make_node(AST_Number, mapped, { value: 0 }),
}); }));
} }
} else { } else {
drop_keys[key] = true; drop_keys.set(key, true);
} }
} }
} else if (drop_keys) { } else if (drop_keys) {
drop_keys[key] = false; drop_keys.set(key, false);
} }
if (value) mapped.value = value; if (value) mapped.value = value;
} }
@@ -7347,10 +7350,10 @@ merge(Compressor.prototype, {
if (prop instanceof AST_Spread) return prop; if (prop instanceof AST_Spread) return prop;
var key = prop_keys[index]; var key = prop_keys[index];
if (key instanceof AST_Node) return prop; if (key instanceof AST_Node) return prop;
if (key in drop_keys) { if (drop_keys.has(key)) {
var mapped = drop_keys[key]; var mapped = drop_keys.get(key);
if (!mapped) return prop; 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) { } else if (node.rest) {
return prop; return prop;
} }
@@ -7443,7 +7446,7 @@ merge(Compressor.prototype, {
if (var_decl <= 1) hoist_vars = false; if (var_decl <= 1) hoist_vars = false;
} }
if (!hoist_funs && !hoist_vars) return; if (!hoist_funs && !hoist_vars) return;
var consts = Object.create(null); var consts = new Dictionary();
var dirs = []; var dirs = [];
var hoisted = []; var hoisted = [];
var vars = new Dictionary(), vars_found = 0; var vars = new Dictionary(), vars_found = 0;
@@ -7469,7 +7472,7 @@ merge(Compressor.prototype, {
if (!all(node.definitions, function(defn) { if (!all(node.definitions, function(defn) {
var sym = defn.name; var sym = defn.name;
return sym instanceof AST_SymbolVar return sym instanceof AST_SymbolVar
&& !consts[sym.name] && !consts.has(sym.name)
&& self.find_variable(sym.name) === sym.definition(); && self.find_variable(sym.name) === sym.definition();
})) return node; })) return node;
node.definitions.forEach(function(def) { node.definitions.forEach(function(def) {
@@ -7488,7 +7491,7 @@ merge(Compressor.prototype, {
} }
if (node instanceof AST_Scope) return node; if (node instanceof AST_Scope) return node;
if (node instanceof AST_SymbolConst) { if (node instanceof AST_SymbolConst) {
consts[node.name] = true; consts.set(node.name, true);
return node; return node;
} }
}); });
@@ -7650,12 +7653,12 @@ merge(Compressor.prototype, {
AST_BlockScope.DEFMETHOD("var_names", function() { AST_BlockScope.DEFMETHOD("var_names", function() {
var var_names = this._var_names; var var_names = this._var_names;
if (!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) { this.enclosed.forEach(function(def) {
var_names[def.name] = true; var_names.set(def.name, true);
}); });
this.variables.each(function(def, name) { this.variables.each(function(def, name) {
var_names[name] = true; var_names.set(name, true);
}); });
} }
return var_names; return var_names;
@@ -7674,7 +7677,7 @@ merge(Compressor.prototype, {
prefix = prefix.replace(/(?:^[^a-z_$]|[^a-z0-9_$])/ig, "_"); prefix = prefix.replace(/(?:^[^a-z_$]|[^a-z0-9_$])/ig, "_");
var name = prefix; var name = prefix;
for (var i = 0; !all(scopes, function(scope) { for (var i = 0; !all(scopes, function(scope) {
return !scope.var_names()[name]; return !scope.var_names().has(name);
}); i++) name = prefix + "$" + i; }); i++) name = prefix + "$" + i;
var sym = make_node(type, orig, { var sym = make_node(type, orig, {
name: name, name: name,
@@ -7683,7 +7686,7 @@ merge(Compressor.prototype, {
var def = this.def_variable(sym); var def = this.def_variable(sym);
scopes.forEach(function(scope) { scopes.forEach(function(scope) {
scope.enclosed.push(def); scope.enclosed.push(def);
scope.var_names()[name] = true; scope.var_names().set(name, true);
}); });
return sym; return sym;
}); });
@@ -9142,7 +9145,7 @@ merge(Compressor.prototype, {
var scope = def.scope.resolve(); var scope = def.scope.resolve();
for (var s = def.scope; s !== scope;) { for (var s = def.scope; s !== scope;) {
s = s.parent_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; def.scope = scope;
scope.variables.set(def.name, def); scope.variables.set(def.name, def);
scope.enclosed.push(def); scope.enclosed.push(def);
scope.var_names()[def.name] = true; scope.var_names().set(def.name, true);
}), }),
value: defn.value, value: defn.value,
}); });
@@ -9959,30 +9962,33 @@ merge(Compressor.prototype, {
} }
function var_exists(defined, name) { 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; var abort = false;
fn.each_argname(function(arg) { fn.each_argname(function(arg) {
if (abort) return; if (abort) return;
if (arg.__unused) return; if (arg.__unused) return;
if (!safe_to_inject || var_exists(defined, arg.name)) return abort = true; 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()); if (in_loop) in_loop.push(arg.definition());
}); });
return !abort; 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++) { for (var i = 0; i < fn.body.length; i++) {
var stat = fn.body[i]; var stat = fn.body[i];
if (stat instanceof AST_LambdaDefinition) { 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) { if (!all(stat.enclosed, function(def) {
return def.scope === stat || !defined[def.name]; return def.scope === stat || !defined.has(def.name);
})) return false; })) return false;
if (in_loop) in_loop.push(stat.name.definition()); if (in_loop) in_loop.push(name.definition());
continue; continue;
} }
if (!(stat instanceof AST_Var)) continue; if (!(stat instanceof AST_Var)) continue;
@@ -9997,12 +10003,12 @@ merge(Compressor.prototype, {
} }
function can_inject_symbols() { function can_inject_symbols() {
var defined = Object.create(null); var defined = new Dictionary();
var level = 0, child; var level = 0, child;
scope = current; scope = current;
do { do {
if (scope.variables) scope.variables.each(function(def) { if (scope.variables) scope.variables.each(function(def) {
defined[def.name] = true; defined.set(def.name, true);
}); });
child = scope; child = scope;
scope = compressor.parent(level++); scope = compressor.parent(level++);
@@ -10025,23 +10031,22 @@ merge(Compressor.prototype, {
var safe_to_inject = exp !== fn || fn.parent_scope.resolve() === scope; var safe_to_inject = exp !== fn || fn.parent_scope.resolve() === scope;
if (scope instanceof AST_Toplevel) { if (scope instanceof AST_Toplevel) {
if (compressor.toplevel.vars) { if (compressor.toplevel.vars) {
defined["arguments"] = true; defined.set("arguments", true);
} else { } else {
safe_to_inject = false; safe_to_inject = false;
} }
} }
arg_used = new Dictionary();
var inline = compressor.option("inline"); var inline = compressor.option("inline");
arg_used = Object.create(defined); if (!can_inject_args(defined, inline >= 2 && safe_to_inject)) return false;
if (!can_inject_args(defined, arg_used, inline >= 2 && safe_to_inject)) return false; if (!can_inject_vars(defined, inline >= 3 && safe_to_inject)) return false;
var used = Object.create(arg_used);
if (!can_inject_vars(defined, used, inline >= 3 && safe_to_inject)) return false;
return !in_loop || in_loop.length == 0 || !is_reachable(fn, in_loop); return !in_loop || in_loop.length == 0 || !is_reachable(fn, in_loop);
} }
function append_var(decls, expressions, name, value) { function append_var(decls, expressions, name, value) {
var def = name.definition(); var def = name.definition();
if (!scope.var_names()[name.name]) { if (!scope.var_names().has(name.name)) {
scope.var_names()[name.name] = true; scope.var_names().set(name.name, true);
decls.push(make_node(AST_VarDef, name, { decls.push(make_node(AST_VarDef, name, {
name: name, name: name,
value: null, value: null,
@@ -10075,7 +10080,7 @@ merge(Compressor.prototype, {
name = argname; name = argname;
} }
var value = self.args[i]; 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); if (value) expressions.push(value);
} else { } else {
var symbol = make_node(AST_SymbolVar, name, name); var symbol = make_node(AST_SymbolVar, name, name);
@@ -10153,7 +10158,7 @@ merge(Compressor.prototype, {
scope.functions.set(def.name, def); scope.functions.set(def.name, def);
scope.variables.set(def.name, def); scope.variables.set(def.name, def);
scope.enclosed.push(def); scope.enclosed.push(def);
scope.var_names()[def.name] = true; scope.var_names().set(def.name, true);
args.push(stat); args.push(stat);
} }
continue; continue;
@@ -10163,7 +10168,7 @@ merge(Compressor.prototype, {
var var_def = stat.definitions[j]; var var_def = stat.definitions[j];
var name = flatten_var(var_def.name); var name = flatten_var(var_def.name);
append_var(decl_var, expr_var, name, var_def.value); 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 def = fn.variables.get(name.name);
var sym = make_node(AST_SymbolRef, name, name); var sym = make_node(AST_SymbolRef, name, name);
def.references.push(sym); def.references.push(sym);
@@ -10196,9 +10201,9 @@ merge(Compressor.prototype, {
})); }));
[].splice.apply(scope.body, args); [].splice.apply(scope.body, args);
fn.enclosed.forEach(function(def) { 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.enclosed.push(def);
scope.var_names()[def.name] = true; scope.var_names().set(def.name, true);
}); });
return expressions; return expressions;
} }
@@ -11320,9 +11325,9 @@ merge(Compressor.prototype, {
var scope = self.scope.resolve(); var scope = self.scope.resolve();
fixed.enclosed.forEach(function(def) { fixed.enclosed.forEach(function(def) {
if (fixed.variables.has(def.name)) return; 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.enclosed.push(def);
scope.var_names()[def.name] = true; scope.var_names().set(def.name, true);
}); });
} }
var value; var value;

View File

@@ -44,7 +44,7 @@
"use strict"; "use strict";
var builtins = function() { var builtins = function() {
var names = []; var names = new Dictionary();
// NaN will be included due to Number.NaN // NaN will be included due to Number.NaN
[ [
"null", "null",
@@ -72,10 +72,10 @@ var builtins = function() {
Object.getOwnPropertyNames(ctor.prototype).map(add); Object.getOwnPropertyNames(ctor.prototype).map(add);
} }
}); });
return makePredicate(names); return names;
function add(name) { function add(name) {
names.push(name); names.set(name, true);
} }
}(); }();
@@ -116,9 +116,9 @@ function mangle_properties(ast, options) {
reserved: null, reserved: null,
}, true); }, 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) { if (Array.isArray(options.reserved)) options.reserved.forEach(function(name) {
reserved[name] = true; reserved.set(name, true);
}); });
var cname = -1; var cname = -1;
@@ -126,7 +126,7 @@ function mangle_properties(ast, options) {
if (options.cache) { if (options.cache) {
cache = options.cache.props; cache = options.cache.props;
cache.each(function(name) { cache.each(function(name) {
reserved[name] = true; reserved.set(name, true);
}); });
} else { } else {
cache = new Dictionary(); cache = new Dictionary();
@@ -141,8 +141,8 @@ function mangle_properties(ast, options) {
var debug_suffix; var debug_suffix;
if (debug) debug_suffix = options.debug === true ? "" : options.debug; if (debug) debug_suffix = options.debug === true ? "" : options.debug;
var names_to_mangle = Object.create(null); var names_to_mangle = new Dictionary();
var unmangleable = Object.create(reserved); var unmangleable = reserved.clone();
// step 1: find candidates to mangle // step 1: find candidates to mangle
ast.walk(new TreeWalker(function(node) { ast.walk(new TreeWalker(function(node) {
@@ -211,20 +211,20 @@ function mangle_properties(ast, options) {
// only function declarations after this line // only function declarations after this line
function can_mangle(name) { 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; if (/^-?[0-9]+(\.[0-9]+)?(e[+-][0-9]+)?$/.test(name)) return false;
return true; return true;
} }
function should_mangle(name) { function should_mangle(name) {
if (reserved[name]) return false; if (reserved.has(name)) return false;
if (regex && !regex.test(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) { function add(name) {
if (can_mangle(name)) names_to_mangle[name] = true; if (can_mangle(name)) names_to_mangle.set(name, true);
if (!should_mangle(name)) unmangleable[name] = true; if (!should_mangle(name)) unmangleable.set(name, true);
} }
function mangle(name) { function mangle(name) {

View File

@@ -510,12 +510,12 @@ function names_in_use(scope, options) {
if (!names) { if (!names) {
scope.cname = -1; scope.cname = -1;
scope.cname_holes = []; 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; var cache = options.cache && options.cache.props;
scope.enclosed.forEach(function(def) { 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)) { 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 scope = def.scope;
var in_use = names_in_use(scope, options); var in_use = names_in_use(scope, options);
var holes = scope.cname_holes; var holes = scope.cname_holes;
var names = Object.create(null); var names = new Dictionary();
var scopes = [ scope ]; var scopes = [ scope ];
def.forEach(function(sym) { def.forEach(function(sym) {
var scope = sym.scope; var scope = sym.scope;
do { do {
if (scopes.indexOf(scope) < 0) { if (member(scope, scopes)) break;
for (var name in names_in_use(scope, options)) { names_in_use(scope, options).each(function(marker, name) {
names[name] = true; names.set(name, marker);
} });
scopes.push(scope); scopes.push(scope);
} else break;
} while (scope = scope.parent_scope); } while (scope = scope.parent_scope);
}); });
var name; var name;
for (var i = 0; i < holes.length; i++) { for (var i = 0; i < holes.length; i++) {
name = base54(holes[i]); name = base54(holes[i]);
if (names[name]) continue; if (names.has(name)) continue;
holes.splice(i, 1); holes.splice(i, 1);
in_use[name] = true; in_use.set(name, true);
return name; return name;
} }
while (true) { while (true) {
name = base54(++scope.cname); name = base54(++scope.cname);
if (in_use[name] || RESERVED_WORDS[name] || options.reserved.has[name]) continue; if (in_use.has(name) || RESERVED_WORDS[name] || options.reserved.has[name]) continue;
if (!names[name]) break; if (!names.has(name)) break;
holes.push(scope.cname); holes.push(scope.cname);
} }
in_use[name] = true; in_use.set(name, true);
return name; return name;
} }
@@ -598,7 +597,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
if (options.cache && options.cache.props) { if (options.cache && options.cache.props) {
var mangled_names = names_in_use(this, options); var mangled_names = names_in_use(this, options);
options.cache.props.each(function(mangled_name) { options.cache.props.each(function(mangled_name) {
mangled_names[mangled_name] = true; mangled_names.set(mangled_name, true);
}); });
} }

View File

@@ -77,21 +77,23 @@ function vlq_encode(num) {
} }
function create_array_map() { function create_array_map() {
var map = Object.create(null); var map = new Dictionary();
var array = []; var array = [];
array.index = function(name) { array.index = function(name) {
if (!HOP(map, name)) { var index = map.get(name);
map[name] = array.length; if (!(index >= 0)) {
index = array.length;
array.push(name); array.push(name);
map.set(name, index);
} }
return map[name]; return index;
}; };
return array; return array;
} }
function SourceMap(options) { function SourceMap(options) {
var sources = create_array_map(); 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 names = create_array_map();
var mappings = ""; var mappings = "";
if (options.orig) Object.keys(options.orig).forEach(function(name) { if (options.orig) Object.keys(options.orig).forEach(function(name) {
@@ -110,7 +112,7 @@ function SourceMap(options) {
if (!sources_content || !map.sourcesContent) return; if (!sources_content || !map.sourcesContent) return;
for (var i = 0; i < map.sources.length; i++) { for (var i = 0; i < map.sources.length; i++) {
var content = map.sourcesContent[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; var prev_source;
@@ -144,8 +146,8 @@ function SourceMap(options) {
add(source, gen_line, gen_col, orig_line, orig_col, name); add(source, gen_line, gen_col, orig_line, orig_col, name);
} : add, } : add,
setSourceContent: sources_content ? function(source, content) { setSourceContent: sources_content ? function(source, content) {
if (!(source in sources_content)) { if (!sources_content.has(source)) {
sources_content[source] = content; sources_content.set(source, content);
} }
} : noop, } : noop,
toString: function() { toString: function() {
@@ -155,7 +157,7 @@ function SourceMap(options) {
sourceRoot: options.root || undefined, sourceRoot: options.root || undefined,
sources: sources, sources: sources,
sourcesContent: sources_content ? sources.map(function(source) { sourcesContent: sources_content ? sources.map(function(source) {
return sources_content[source] || null; return sources_content.get(source) || null;
}) : undefined, }) : undefined,
names: names, names: names,
mappings: mappings, mappings: mappings,

View File

@@ -96,15 +96,6 @@ function defaults(args, defs, croak) {
return defs; 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 noop() {}
function return_false() { return false; } function return_false() { return false; }
function return_true() { return true; } function return_true() { return true; }
@@ -171,63 +162,80 @@ function all(array, predicate) {
} }
function Dictionary() { function Dictionary() {
this._values = Object.create(null); this.values = Object.create(null);
this._size = 0;
} }
Dictionary.prototype = { Dictionary.prototype = {
set: function(key, val) { set: function(key, val) {
if (!this.has(key)) ++this._size; if (key == "__proto__") {
this._values["$" + key] = val; this.proto_value = val;
} else {
this.values[key] = val;
}
return this; return this;
}, },
add: function(key, val) { add: function(key, val) {
if (this.has(key)) { var list = this.get(key);
this.get(key).push(val); if (list) {
list.push(val);
} else { } else {
this.set(key, [ val ]); this.set(key, [ val ]);
} }
return this; return this;
}, },
get: function(key) { return this._values["$" + key] }, get: function(key) {
return key == "__proto__" ? this.proto_value : this.values[key];
},
del: function(key) { del: function(key) {
if (this.has(key)) { if (key == "__proto__") {
--this._size; delete this.proto_value;
delete this._values["$" + key]; } else {
delete this.values[key];
} }
return this; 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) { all: function(predicate) {
for (var i in this._values) for (var i in this.values)
if (!predicate(this._values[i], i.substr(1))) if (!predicate(this.values[i], i)) return false;
return false; if ("proto_value" in this && !predicate(this.proto_value, "__proto__")) return false;
return true; return true;
}, },
each: function(f) { each: function(f) {
for (var i in this._values) for (var i in this.values)
f(this._values[i], i.substr(1)); f(this.values[i], i);
if ("proto_value" in this) f(this.proto_value, "__proto__");
}, },
size: function() { size: function() {
return this._size; return Object.keys(this.values).length + ("proto_value" in this);
}, },
map: function(f) { map: function(f) {
var ret = []; var ret = [];
for (var i in this._values) for (var i in this.values)
ret.push(f(this._values[i], i.substr(1))); ret.push(f(this.values[i], i));
if ("proto_value" in this) ret.push(f(this.proto_value, "__proto__"));
return ret; return ret;
}, },
clone: function() { clone: function() {
var ret = new Dictionary(); var ret = new Dictionary();
for (var i in this._values) this.each(function(value, i) {
ret._values[i] = this._values[i]; ret.set(i, value);
ret._size = this._size; });
return ret; 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) { Dictionary.fromObject = function(obj) {
var dict = new Dictionary(); 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; return dict;
}; };

View File

@@ -3203,7 +3203,7 @@ issue_4552: {
expect_stdout: "NaN" expect_stdout: "NaN"
} }
issue_4886: { issue_4886_1: {
options = { options = {
evaluate: true, evaluate: true,
unsafe: true, unsafe: true,
@@ -3222,3 +3222,23 @@ issue_4886: {
} }
expect_stdout: "true" 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"
}

View File

@@ -198,9 +198,9 @@ numeric_literal: {
expect_exact: [ expect_exact: [
'var obj = {', 'var obj = {',
' 0: 0,', ' 0: 0,',
' "-0": 1,',
' 42: 3,',
' 37: 4,', ' 37: 4,',
' 42: 3,',
' "-0": 1,',
' o: 5,', ' o: 5,',
' 1e42: 8,', ' 1e42: 8,',
' b: 7', ' b: 7',