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) {
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

View File

@@ -185,10 +185,39 @@ function Compressor(options, false_by_default) {
};
}
Compressor.prototype = new TreeTransformer;
merge(Compressor.prototype, {
option: function(key) { return this.options[key] },
exposed: function(def) {
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;
@@ -196,8 +225,8 @@ merge(Compressor.prototype, {
return !all(def.orig, function(sym) {
return toplevel[sym instanceof AST_SymbolDefun ? "funcs" : "vars"];
});
},
compress: function(node) {
};
Compressor.prototype.compress = function(node) {
node = node.resolve_defines(this);
node.hoist_exports(this);
if (this.option("expression")) {
@@ -238,37 +267,7 @@ merge(Compressor.prototype, {
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;
}
});
};
(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;

View 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) {

View 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;
}
if (member(scope, scopes)) break;
names_in_use(scope, options).each(function(marker, name) {
names.set(name, marker);
});
scopes.push(scope);
} else break;
} 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);
});
}

View 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,

View 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;
};

View 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"
}

View 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',