improve Dictionary performance (#5202)
- workaround `__proto__` quirks on v8
This commit is contained in:
@@ -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
|
||||
|
||||
297
lib/compress.js
297
lib/compress.js
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
31
lib/scope.js
31
lib/scope.js
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
74
lib/utils.js
74
lib/utils.js
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user