Skip to content

Commit

Permalink
improve sandbox fidelity (#3415)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexlamsl committed May 15, 2019
1 parent 1f0def1 commit a21c348
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 84 deletions.
13 changes: 9 additions & 4 deletions lib/compress.js
Expand Up @@ -451,6 +451,7 @@ merge(Compressor.prototype, {
if (tw.safe_ids[def.id]) {
if (def.fixed == null) {
if (is_arguments(def)) return false;
if (def.global && def.name == "arguments") return false;
def.fixed = make_node(AST_Undefined, def.orig);
}
return true;
Expand Down Expand Up @@ -894,9 +895,13 @@ merge(Compressor.prototype, {
return orig.length == 1 && orig[0] instanceof AST_SymbolLambda;
});

function is_lhs_read_only(lhs) {
function is_lhs_read_only(lhs, compressor) {
if (lhs instanceof AST_This) return true;
if (lhs instanceof AST_SymbolRef) return lhs.definition().orig[0] instanceof AST_SymbolLambda;
if (lhs instanceof AST_SymbolRef) {
var def = lhs.definition();
return def.orig[0] instanceof AST_SymbolLambda
|| compressor.exposed(def) && identifier_atom[def.name];
}
if (lhs instanceof AST_PropAccess) {
lhs = lhs.expression;
if (lhs instanceof AST_SymbolRef) {
Expand All @@ -905,7 +910,7 @@ merge(Compressor.prototype, {
}
if (!lhs) return true;
if (lhs.is_constant()) return true;
return is_lhs_read_only(lhs);
return is_lhs_read_only(lhs, compressor);
}
return false;
}
Expand Down Expand Up @@ -1220,7 +1225,7 @@ merge(Compressor.prototype, {
var stop_if_hit = null;
var lhs = get_lhs(candidate);
var side_effects = lhs && lhs.has_side_effects(compressor);
var scan_lhs = lhs && !side_effects && !is_lhs_read_only(lhs);
var scan_lhs = lhs && !side_effects && !is_lhs_read_only(lhs, compressor);
var scan_rhs = foldable(get_rhs(candidate));
if (!scan_lhs && !scan_rhs) continue;
// Locate symbols which may execute code outside of scanning range
Expand Down
100 changes: 57 additions & 43 deletions test/compress.js
@@ -1,35 +1,47 @@
#! /usr/bin/env node

var assert = require("assert");
var child_process = require("child_process");
var fs = require("fs");
var path = require("path");
var sandbox = require("./sandbox");
var semver = require("semver");
var U = require("./node");

var failures = 0;
var failed_files = Object.create(null);
var minify_options = require("./ufuzz.json").map(JSON.stringify);
var file = process.argv[2];
var dir = path.resolve(path.dirname(module.filename), "compress");
fs.readdirSync(dir).filter(function(name) {
return /\.js$/i.test(name);
}).forEach(function(file) {
if (file) {
var minify_options = require("./ufuzz.json").map(JSON.stringify);
log("--- {file}", { file: file });
var tests = parse_test(path.resolve(dir, file));
for (var i in tests) if (!test_case(tests[i])) {
failures++;
failed_files[file] = 1;
}
});
if (failures) {
console.error();
console.error("!!! Failed " + failures + " test case(s).");
console.error("!!! " + Object.keys(failed_files).join(", "));
process.exit(1);
process.exit(Object.keys(tests).filter(function(name) {
return !test_case(tests[name]);
}).length);
} else {
var files = fs.readdirSync(dir).filter(function(name) {
return /\.js$/i.test(name);
});
var failures = 0;
var failed_files = Object.create(null);
(function next() {
var file = files.shift();
if (file) {
child_process.spawn(process.argv[0], [ process.argv[1], file ], {
stdio: [ "ignore", 1, 2 ]
}).on("exit", function(code) {
if (code) {
failures += code;
failed_files[file] = code;
}
next();
});
} else if (failures) {
console.error();
console.error("!!! Failed " + failures + " test case(s).");
console.error("!!! " + Object.keys(failed_files).join(", "));
process.exit(1);
}
})();
}

/* -----[ utils ]----- */

function evaluate(code) {
if (code instanceof U.AST_Node) code = make_code(code, { beautify: true });
return new Function("return(" + code + ")")();
Expand Down Expand Up @@ -160,7 +172,7 @@ function parse_test(file) {

// Try to reminify original input with standard options
// to see if it matches expect_stdout.
function reminify(orig_options, input_code, input_formatted, expect_stdout) {
function reminify(orig_options, input_code, input_formatted, stdout) {
for (var i = 0; i < minify_options.length; i++) {
var options = JSON.parse(minify_options[i]);
if (options.compress) [
Expand Down Expand Up @@ -191,11 +203,12 @@ function reminify(orig_options, input_code, input_formatted, expect_stdout) {
});
return false;
} else {
var stdout = run_code(result.code);
if (typeof expect_stdout != "string" && typeof stdout != "string" && expect_stdout.name == stdout.name) {
stdout = expect_stdout;
var expected = stdout[options.toplevel ? 1 : 0];
var actual = run_code(result.code, options.toplevel);
if (typeof expected != "string" && typeof actual != "string" && expected.name == actual.name) {
actual = expected;
}
if (!sandbox.same_stdout(expect_stdout, stdout)) {
if (!sandbox.same_stdout(expected, actual)) {
log([
"!!! failed running reminified input",
"---INPUT---",
Expand All @@ -214,10 +227,10 @@ function reminify(orig_options, input_code, input_formatted, expect_stdout) {
input: input_formatted,
options: options_formatted,
output: result.code,
expected_type: typeof expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: expect_stdout,
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR",
actual: stdout,
expected_type: typeof expected == "string" ? "STDOUT" : "ERROR",
expected: expected,
actual_type: typeof actual == "string" ? "STDOUT" : "ERROR",
actual: actual,
});
return false;
}
Expand All @@ -226,8 +239,8 @@ function reminify(orig_options, input_code, input_formatted, expect_stdout) {
return true;
}

function run_code(code) {
var result = sandbox.run_code(code, true);
function run_code(code, toplevel) {
var result = sandbox.run_code(code, toplevel);
return typeof result == "string" ? result.replace(/\u001b\[\d+m/g, "") : result;
}

Expand Down Expand Up @@ -359,13 +372,14 @@ function test_case(test) {
return false;
}
}
if (test.expect_stdout
&& (!test.node_version || semver.satisfies(process.version, test.node_version))) {
var stdout = run_code(input_code);
if (test.expect_stdout && (!test.node_version || semver.satisfies(process.version, test.node_version))) {
var stdout = [ run_code(input_code), run_code(input_code, true) ];
var toplevel = test.options.toplevel;
var actual = stdout[toplevel ? 1 : 0];
if (test.expect_stdout === true) {
test.expect_stdout = stdout;
test.expect_stdout = actual;
}
if (!sandbox.same_stdout(test.expect_stdout, stdout)) {
if (!sandbox.same_stdout(test.expect_stdout, actual)) {
log([
"!!! Invalid input or expected stdout",
"---INPUT---",
Expand All @@ -380,13 +394,13 @@ function test_case(test) {
input: input_formatted,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: test.expect_stdout,
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR",
actual: stdout,
actual_type: typeof actual == "string" ? "STDOUT" : "ERROR",
actual: actual,
});
return false;
}
stdout = run_code(output);
if (!sandbox.same_stdout(test.expect_stdout, stdout)) {
actual = run_code(output, toplevel);
if (!sandbox.same_stdout(test.expect_stdout, actual)) {
log([
"!!! failed",
"---INPUT---",
Expand All @@ -401,12 +415,12 @@ function test_case(test) {
input: input_formatted,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: test.expect_stdout,
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR",
actual: stdout,
actual_type: typeof actual == "string" ? "STDOUT" : "ERROR",
actual: actual,
});
return false;
}
if (!reminify(test.options, input_code, input_formatted, test.expect_stdout)) {
if (!reminify(test.options, input_code, input_formatted, stdout)) {
return false;
}
}
Expand Down
19 changes: 19 additions & 0 deletions test/compress/collapse_vars.js
Expand Up @@ -6178,3 +6178,22 @@ assign_undeclared: {
"object",
]
}

Infinity_assignment: {
options = {
collapse_vars: true,
pure_getters: "strict",
unsafe: true,
}
input: {
var Infinity;
Infinity = 42;
console.log(Infinity);
}
expect: {
var Infinity;
Infinity = 42;
console.log(Infinity);
}
expect_stdout: true
}
10 changes: 5 additions & 5 deletions test/compress/issue-1704.js
Expand Up @@ -366,7 +366,7 @@ mangle_catch_redef_3: {
console.log(o);
}
expect_exact: 'var o="PASS";try{throw 0}catch(o){(function(){function c(){o="FAIL"}c(),c()})()}console.log(o);'
expect_stdout: "PASS"
expect_stdout: true
}

mangle_catch_redef_3_toplevel: {
Expand All @@ -389,10 +389,10 @@ mangle_catch_redef_3_toplevel: {
console.log(o);
}
expect_exact: 'var c="PASS";try{throw 0}catch(c){(function(){function o(){c="FAIL"}o(),o()})()}console.log(c);'
expect_stdout: "PASS"
expect_stdout: true
}

mangle_catch_redef_ie8_3: {
mangle_catch_redef_3_ie8: {
mangle = {
ie8: true,
toplevel: false,
Expand All @@ -412,7 +412,7 @@ mangle_catch_redef_ie8_3: {
console.log(o);
}
expect_exact: 'var o="PASS";try{throw 0}catch(o){(function(){function c(){o="FAIL"}c(),c()})()}console.log(o);'
expect_stdout: "PASS"
expect_stdout: true
}

mangle_catch_redef_3_ie8_toplevel: {
Expand All @@ -435,5 +435,5 @@ mangle_catch_redef_3_ie8_toplevel: {
console.log(o);
}
expect_exact: 'var c="PASS";try{throw 0}catch(c){(function(){function o(){c="FAIL"}o(),o()})()}console.log(c);'
expect_stdout: "PASS"
expect_stdout: true
}
54 changes: 54 additions & 0 deletions test/compress/sandbox.js
Expand Up @@ -12,3 +12,57 @@ console_log: {
"% %s",
]
}

typeof_arguments: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var arguments;
console.log((typeof arguments).length);
}
expect: {
var arguments;
console.log((typeof arguments).length);
}
expect_stdout: "6"
}

typeof_arguments_assigned: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var arguments = void 0;
console.log((typeof arguments).length);
}
expect: {
console.log("undefined".length);
}
expect_stdout: "9"
}

toplevel_Infinity_NaN_undefined: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var Infinity = "foo";
var NaN = 42;
var undefined = null;
console.log(Infinity, NaN, undefined);
}
expect: {
console.log("foo", 42, null);
}
expect_stdout: "foo 42 null"
}
17 changes: 2 additions & 15 deletions test/sandbox.js
Expand Up @@ -52,32 +52,19 @@ function createContext() {
return ctx;
}

var context;
exports.run_code = function(code, reuse) {
exports.run_code = function(code, toplevel) {
var stdout = "";
var original_write = process.stdout.write;
process.stdout.write = function(chunk) {
stdout += chunk;
};
try {
if (!reuse || !context) context = createContext();
vm.runInContext([
"!function() {",
code,
"}();",
].join("\n"), context, { timeout: 5000 });
vm.runInContext(toplevel ? "(function(){" + code + "})()" : code, createContext(), { timeout: 5000 });
return stdout;
} catch (ex) {
return ex;
} finally {
process.stdout.write = original_write;
if (!reuse || code.indexOf(".prototype") >= 0) {
context = null;
} else {
vm.runInContext(Object.keys(context).map(function(name) {
return "delete " + name;
}).join("\n"), context);
}
}
};

Expand Down

0 comments on commit a21c348

Please sign in to comment.