aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2021-02-26 20:56:34 +0000
committerGitHub <noreply@github.com>2021-02-27 04:56:34 +0800
commitba4a771bbccb0b6026588a56b0c31f5bac466775 (patch)
tree4ff73f772c92a86328ca59d022b0ccec840c3d6e
parentac26993b5a1546e790d93603d0d7a05740566b07 (diff)
downloadtracifyjs-ba4a771bbccb0b6026588a56b0c31f5bac466775.tar.gz
tracifyjs-ba4a771bbccb0b6026588a56b0c31f5bac466775.zip
support limited `ufuzz` testing for `export` (#4693)
fixes #4692
-rw-r--r--lib/ast.js10
-rw-r--r--lib/compress.js12
-rw-r--r--lib/output.js19
-rw-r--r--test/compress/exports.js60
-rw-r--r--test/reduce.js14
-rw-r--r--test/sandbox.js8
-rw-r--r--test/ufuzz/index.js90
7 files changed, 166 insertions, 47 deletions
diff --git a/lib/ast.js b/lib/ast.js
index 3c6672c2..5fe6efc1 100644
--- a/lib/ast.js
+++ b/lib/ast.js
@@ -1176,15 +1176,7 @@ var AST_ExportDefault = DEFNODE("ExportDefault", "body", {
});
},
_validate: function() {
- if (this.body instanceof AST_Class && this.body.name) {
- if (!(this.body instanceof AST_DefClass)) {
- throw new Error("body must be AST_DefClass when named");
- }
- } else if (this.body instanceof AST_Lambda && this.body.name) {
- if (!(this.body instanceof AST_LambdaDefinition)) {
- throw new Error("body must be AST_LambdaDefinition when named");
- }
- } else {
+ if (!(this.body instanceof AST_DefClass || this.body instanceof AST_LambdaDefinition)) {
must_be_expression(this, "body");
}
},
diff --git a/lib/compress.js b/lib/compress.js
index 0e698804..101fdb4e 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -940,6 +940,8 @@ merge(Compressor.prototype, {
});
if (!node.name) return;
var d = node.name.definition();
+ var parent = tw.parent();
+ if (parent instanceof AST_ExportDeclaration || parent instanceof AST_ExportDefault) d.single_use = false;
if (safe_to_assign(tw, d, true)) {
mark(tw, d);
tw.loop_ids[d.id] = tw.in_loop;
@@ -6709,6 +6711,7 @@ merge(Compressor.prototype, {
var var_decl = 0;
self.walk(new TreeWalker(function(node) {
if (var_decl > 1) return true;
+ if (node instanceof AST_ExportDeclaration) return true;
if (node instanceof AST_Scope && node !== self) return true;
if (node instanceof AST_Var) {
var_decl++;
@@ -6728,12 +6731,15 @@ merge(Compressor.prototype, {
dirs.push(node);
return make_node(AST_EmptyStatement, node);
}
- if (hoist_funs && node instanceof AST_Defun
- && (tt.parent() === self || !compressor.has_directive("use strict"))) {
+ if (node instanceof AST_Defun) {
+ if (!hoist_funs) return node;
+ if (tt.parent() !== self && compressor.has_directive("use strict")) return node;
hoisted.push(node);
return make_node(AST_EmptyStatement, node);
}
- if (hoist_vars && node instanceof AST_Var) {
+ if (node instanceof AST_Var) {
+ if (!hoist_vars) return node;
+ if (tt.parent() instanceof AST_ExportDeclaration) return node;
if (!all(node.definitions, function(defn) {
var sym = defn.name;
return sym instanceof AST_SymbolVar
diff --git a/lib/output.js b/lib/output.js
index 49164359..bebcd7fd 100644
--- a/lib/output.js
+++ b/lib/output.js
@@ -664,7 +664,9 @@ function OutputStream(options) {
function needs_parens_function(output) {
if (!output.has_parens() && first_in_statement(output)) return true;
var p = output.parent();
- // export default (function() {})()
+ // export default (function foo() {});
+ if (this.name && p instanceof AST_ExportDefault) return true;
+ // export default (function() {})(foo);
if (p && p.TYPE == "Call" && output.parent(1) instanceof AST_ExportDefault) return true;
if (output.option("webkit") && p instanceof AST_PropAccess && p.expression === this) return true;
if (output.option("wrap_iife") && p instanceof AST_Call && p.expression === this) return true;
@@ -725,6 +727,8 @@ function OutputStream(options) {
// { [(1, 2)]: foo } = bar
// { 1: (2, foo) } = bar
|| p instanceof AST_DestructuredKeyVal
+ // export default (foo, bar)
+ || p instanceof AST_ExportDefault
// for (foo of (bar, baz));
|| p instanceof AST_ForOf
// { [(1, 2)]: 3 }[2] ---> 3
@@ -1032,9 +1036,16 @@ function OutputStream(options) {
output.space();
output.print("default");
output.space();
- this.body.print(output);
- if (this.body instanceof AST_Class) return;
- if (this.body instanceof AST_Lambda && !is_arrow(this.body)) return;
+ var body = this.body;
+ body.print(output);
+ if (body instanceof AST_ClassExpression) {
+ if (!body.name) return;
+ }
+ if (body instanceof AST_DefClass) return;
+ if (body instanceof AST_LambdaDefinition) return;
+ if (body instanceof AST_LambdaExpression) {
+ if (!body.name && !is_arrow(body)) return;
+ }
output.semicolon();
});
DEFPRINT(AST_ExportForeign, function(output) {
diff --git a/test/compress/exports.js b/test/compress/exports.js
index 425b0245..c573223d 100644
--- a/test/compress/exports.js
+++ b/test/compress/exports.js
@@ -56,6 +56,20 @@ defaults_parentheses_2: {
expect_exact: 'export default(async function(){console.log("PASS")})();'
}
+defaults_parentheses_3: {
+ input: {
+ export default (42, "PASS");
+ }
+ expect_exact: 'export default(42,"PASS");'
+}
+
+defaults_parentheses_4: {
+ input: {
+ export default (function f() {});
+ }
+ expect_exact: "export default(function f(){});"
+}
+
foreign: {
input: {
export * from "foo";
@@ -203,6 +217,20 @@ hoist_exports: {
}
}
+hoist_vars: {
+ options = {
+ hoist_vars: true,
+ }
+ input: {
+ var a;
+ export var b = 42;
+ }
+ expect: {
+ var a;
+ export var b = 42;
+ }
+}
+
keep_return_values: {
options = {
booleans: true,
@@ -301,3 +329,35 @@ single_use_default: {
f();
}
}
+
+single_use_class: {
+ options = {
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ export class A {}
+ A.prototype.p = "PASS";
+ }
+ expect: {
+ export class A {}
+ A.prototype.p = "PASS";
+ }
+}
+
+single_use_class_default: {
+ options = {
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ export default class A {}
+ A.prototype.p = "PASS";
+ }
+ expect: {
+ export default class A {}
+ A.prototype.p = "PASS";
+ }
+}
diff --git a/test/reduce.js b/test/reduce.js
index 25dc14ae..dd0cb6f3 100644
--- a/test/reduce.js
+++ b/test/reduce.js
@@ -132,6 +132,11 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
return;
}
if (parent instanceof U.AST_VarDef && parent.name === node) return;
+ // preserve exports
+ if (parent instanceof U.AST_ExportDeclaration) return;
+ if (parent instanceof U.AST_ExportDefault) return;
+ if (parent instanceof U.AST_ExportForeign) return;
+ if (parent instanceof U.AST_ExportReferences) return;
// preserve for (var xxx; ...)
if (parent instanceof U.AST_For && parent.init === node && node instanceof U.AST_Definitions) return node;
// preserve for (xxx in/of ...)
@@ -455,6 +460,13 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
return List.skip;
}
+ // preserve sole definition of an export statement
+ if (node instanceof U.AST_VarDef
+ && parent.definitions.length == 1
+ && tt.parent(1) instanceof U.AST_ExportDeclaration) {
+ return node;
+ }
+
// remove this node unless its the sole element of a (transient) sequence
if (!(parent instanceof U.AST_Sequence) || parent.expressions.length > 1) {
node.start._permute++;
@@ -720,7 +732,7 @@ function run_code(code, toplevel, result_cache, timeout) {
if (!value) {
var start = Date.now();
result_cache[key] = value = {
- result: sandbox.run_code(code, toplevel, timeout),
+ result: sandbox.run_code(sandbox.strip_exports(code), toplevel, timeout),
elapsed: Date.now() - start,
};
}
diff --git a/test/sandbox.js b/test/sandbox.js
index 1aee7753..1585c988 100644
--- a/test/sandbox.js
+++ b/test/sandbox.js
@@ -49,6 +49,14 @@ exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expec
} : function(expected, actual) {
return typeof expected == typeof actual && strip_func_ids(expected) == strip_func_ids(actual);
};
+exports.strip_exports = function(code) {
+ var count = 0;
+ return code.replace(/\bexport(?:\s*\{[^}]*};|\s+default\b(?:\s*(\(|\{|class\s*\{|class\s+(?=extends\b)|(?:async\s+)?function\s*(?:\*\s*)?\())?|\b)/g, function(match, header) {
+ if (!header) return "";
+ if (header.length == 1) return "~" + header;
+ return header.slice(0, -1) + " _" + ++count + header.slice(-1);
+ });
+};
function is_error(result) {
return result && typeof result.name == "string" && typeof result.message == "string";
diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js
index 3d0824b6..e756b747 100644
--- a/test/ufuzz/index.js
+++ b/test/ufuzz/index.js
@@ -363,6 +363,7 @@ var lambda_vars = [];
var unique_vars = [];
var classes = [];
var async = false;
+var export_default = false;
var generator = false;
var loops = 0;
var funcs = 0;
@@ -380,6 +381,17 @@ function strictMode() {
return use_strict && rng(4) == 0 ? '"use strict";' : "";
}
+function appendExport(stmtDepth, allowDefault) {
+ if (stmtDepth == 1 && rng(20) == 0) {
+ if (allowDefault && !export_default && rng(5) == 0) {
+ export_default = true;
+ return "export default ";
+ }
+ return "export ";
+ }
+ return "";
+}
+
function createTopLevelCode() {
VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list
block_vars.length = 0;
@@ -387,21 +399,28 @@ function createTopLevelCode() {
unique_vars.length = 0;
classes.length = 0;
async = false;
+ export_default = false;
generator = false;
loops = 0;
funcs = 0;
clazz = 0;
in_class = 0;
called = Object.create(null);
- return [
+ var s = [
strictMode(),
- "var _calls_ = 10, a = 100, b = 10, c = 0;",
- rng(2)
- ? createStatements(3, MAX_GENERATION_RECURSION_DEPTH, CANNOT_THROW, CANNOT_BREAK, CANNOT_CONTINUE, CANNOT_RETURN, 0)
- : createFunctions(rng(MAX_GENERATED_TOPLEVELS_PER_RUN) + 1, MAX_GENERATION_RECURSION_DEPTH, DEFUN_OK, CANNOT_THROW, 0),
- // preceding `null` makes for a cleaner output (empty string still shows up etc)
- "console.log(null, a, b, c, Infinity, NaN, undefined);"
- ].join("\n");
+ appendExport(1) + "var _calls_ = 10, a = 100, b = 10, c = 0;",
+ ];
+ createBlockVariables(MAX_GENERATION_RECURSION_DEPTH, 0, CANNOT_THROW, function(defns) {
+ s.push(defns());
+ if (rng(2)) {
+ s.push(createStatements(3, MAX_GENERATION_RECURSION_DEPTH, CANNOT_THROW, CANNOT_BREAK, CANNOT_CONTINUE, CANNOT_RETURN, 0));
+ } else {
+ s.push(createFunctions(rng(MAX_GENERATED_TOPLEVELS_PER_RUN) + 1, MAX_GENERATION_RECURSION_DEPTH, DEFUN_OK, CANNOT_THROW, 0));
+ }
+ });
+ // preceding `null` makes for a cleaner output (empty string still shows up etc)
+ s.push("console.log(null, a, b, c, Infinity, NaN, undefined);");
+ return s.join("\n");
}
function createFunctions(n, recurmax, allowDefun, canThrow, stmtDepth) {
@@ -668,6 +687,7 @@ function filterDirective(s) {
}
function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
+ ++stmtDepth;
var block_len = block_vars.length;
var class_len = classes.length;
var nameLenBefore = VAR_NAMES.length;
@@ -691,7 +711,7 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
if (SUPPORT.class) while (rng(100) == 0) {
var name = "C" + clazz++;
classes.push(name);
- s.push(createClassLiteral(recurmax,stmtDepth, canThrow, name));
+ s.push(appendExport(stmtDepth, true) + createClassLiteral(recurmax, stmtDepth, canThrow, name));
}
if (rng(2)) {
s.push(createDefinitions("const", consts), createDefinitions("let", lets));
@@ -707,7 +727,7 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
function createDefinitions(type, names) {
if (!names.length) return "";
- var s = type + " ";
+ var s = appendExport(stmtDepth) + type + " ";
switch (SUPPORT.destructuring ? rng(10) : 2) {
case 0:
while (!rng(10)) names.splice(rng(names.length + 1), 0, "");
@@ -780,6 +800,7 @@ function invokeGenerator(was_generator) {
function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
if (--recurmax < 0) { return ";"; }
if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0;
+ ++stmtDepth;
var s = [];
var name, args;
var nameLenBefore = VAR_NAMES.length;
@@ -831,13 +852,14 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
lambda_vars.length = lambda_len;
VAR_NAMES.length = nameLenBefore;
+ if (allowDefun) s = appendExport(stmtDepth, true) + s;
if (!allowDefun) {
// avoid "function statements" (decl inside statements)
- s = "var " + createVarName(MANDATORY) + " = " + s;
+ s = appendExport(stmtDepth) + "var " + createVarName(MANDATORY) + " = " + s;
s += args || createArgs(recurmax, stmtDepth, canThrow);
s += call_next;
} else if (!(name in called) || args || rng(3)) {
- s += "var " + createVarName(MANDATORY) + " = " + name;
+ s += appendExport(stmtDepth) + "var " + createVarName(MANDATORY) + " = " + name;
s += args || createArgs(recurmax, stmtDepth, canThrow);
s += call_next;
}
@@ -987,6 +1009,10 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
case STMT_SEMI:
return use_strict && rng(20) === 0 ? '"use strict";' : ";";
case STMT_EXPR:
+ if (stmtDepth == 1 && !export_default && rng(20) == 0) {
+ export_default = true;
+ return "export default " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ";";
+ }
return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ";";
case STMT_SWITCH:
// note: case args are actual expressions
@@ -995,7 +1021,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
case STMT_VAR:
if (SUPPORT.destructuring && rng(20) == 0) {
var pairs = createAssignmentPairs(recurmax, stmtDepth, canThrow);
- return "var " + pairs.names.map(function(name, index) {
+ return appendExport(stmtDepth) + "var " + pairs.names.map(function(name, index) {
return index in pairs.values ? name + " = " + pairs.values[index] : name;
}).join(", ") + ";";
} else switch (rng(3)) {
@@ -1003,20 +1029,20 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
unique_vars.push("c");
var name = createVarName(MANDATORY);
unique_vars.pop();
- return "var " + name + ";";
+ return appendExport(stmtDepth) + "var " + name + ";";
case 1:
// initializer can only have one expression
unique_vars.push("c");
var name = createVarName(MANDATORY);
unique_vars.pop();
- return "var " + name + " = " + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ";";
+ return appendExport(stmtDepth) + "var " + name + " = " + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ";";
default:
// initializer can only have one expression
unique_vars.push("c");
var n1 = createVarName(MANDATORY);
var n2 = createVarName(MANDATORY);
unique_vars.pop();
- return "var " + n1 + " = " + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ", " + n2 + " = " + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ";";
+ return appendExport(stmtDepth) + "var " + n1 + " = " + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ", " + n2 + " = " + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ";";
}
case STMT_RETURN_ETC:
switch (rng(8)) {
@@ -1939,6 +1965,10 @@ if (require.main !== module) {
return;
}
+function run_code(code, toplevel) {
+ return sandbox.run_code(sandbox.strip_exports(code), toplevel);
+}
+
function writeln(stream, msg) {
if (typeof msg != "undefined") {
stream.write(typeof msg == "string" ? msg : msg.stack || "" + msg);
@@ -1960,7 +1990,7 @@ function try_beautify(code, toplevel, result, printfn, options) {
printfn("// !!! beautify failed !!!");
printfn(beautified.error);
beautified = null;
- } else if (!sandbox.same_stdout(sandbox.run_code(beautified.code, toplevel), result)) {
+ } else if (!sandbox.same_stdout(run_code(beautified.code, toplevel), result)) {
beautified = null;
} else if (options) {
var uglified = UglifyJS.minify(beautified.code, JSON.parse(options));
@@ -1970,7 +2000,7 @@ function try_beautify(code, toplevel, result, printfn, options) {
actual = uglified.error;
} else {
expected = uglify_result;
- actual = sandbox.run_code(uglified.code, toplevel);
+ actual = run_code(uglified.code, toplevel);
}
if (!sandbox.same_stdout(expected, actual)) {
beautified = null;
@@ -2008,7 +2038,7 @@ function log_suspects(minify_options, component) {
errorln("Error testing options." + component + "." + name);
errorln(result.error);
} else {
- var r = sandbox.run_code(result.code, toplevel);
+ var r = run_code(result.code, toplevel);
return !sandbox.same_stdout(uglify_result, r);
}
}
@@ -2036,7 +2066,7 @@ function log_suspects_global(options, toplevel) {
errorln("Error testing options." + component);
errorln(result.error);
} else {
- var r = sandbox.run_code(result.code, toplevel);
+ var r = run_code(result.code, toplevel);
return !sandbox.same_stdout(uglify_result, r);
}
});
@@ -2108,7 +2138,7 @@ function log(options) {
}
function sort_globals(code) {
- var globals = sandbox.run_code("throw Object.keys(this).sort(" + function(global) {
+ var globals = run_code("throw Object.keys(this).sort(" + function(global) {
return function(m, n) {
return (n == "toString") - (m == "toString")
|| (typeof global[n] == "function") - (typeof global[m] == "function")
@@ -2221,7 +2251,7 @@ function patch_try_catch(orig, toplevel) {
].join("\n");
}
var new_code = code.slice(0, index) + insert + code.slice(index) + tail_throw;
- var result = sandbox.run_code(new_code, toplevel);
+ var result = run_code(new_code, toplevel);
if (!sandbox.is_error(result)) {
if (!stack.filled && match[1]) stack.push({
code: code,
@@ -2292,7 +2322,7 @@ for (var round = 1; round <= num_iterations; round++) {
process.stdout.write(round + " of " + num_iterations + "\r");
original_code = createTopLevelCode();
- var orig_result = [ sandbox.run_code(original_code), sandbox.run_code(original_code, true) ];
+ var orig_result = [ run_code(original_code), run_code(original_code, true) ];
if (orig_result.some(function(result, toplevel) {
if (typeof result == "string") return;
println();
@@ -2317,7 +2347,7 @@ for (var round = 1; round <= num_iterations; round++) {
errored = typeof original_result != "string";
if (!uglify_code.error) {
uglify_code = uglify_code.code;
- uglify_result = sandbox.run_code(uglify_code, toplevel);
+ uglify_result = run_code(uglify_code, toplevel);
ok = sandbox.same_stdout(original_result, uglify_result);
// ignore v8 parser bug
if (!ok && bug_async_arrow_rest(uglify_result)) ok = true;
@@ -2328,13 +2358,13 @@ for (var round = 1; round <= num_iterations; round++) {
ok = true;
} else {
// ignore spurious time-outs
- if (!orig_result[toplevel ? 3 : 2]) orig_result[toplevel ? 3 : 2] = sandbox.run_code(original_code, toplevel, 10000);
+ if (!orig_result[toplevel ? 3 : 2]) orig_result[toplevel ? 3 : 2] = run_code(original_code, toplevel, 10000);
ok = sandbox.same_stdout(orig_result[toplevel ? 3 : 2], uglify_result);
}
}
// ignore declaration order of global variables
if (!ok && !toplevel) {
- ok = sandbox.same_stdout(sandbox.run_code(sort_globals(original_code)), sandbox.run_code(sort_globals(uglify_code)));
+ ok = sandbox.same_stdout(run_code(sort_globals(original_code)), run_code(sort_globals(uglify_code)));
}
// ignore numerical imprecision caused by `unsafe_math`
if (!ok && o.compress && o.compress.unsafe_math && typeof original_result == typeof uglify_result) {
@@ -2344,7 +2374,7 @@ for (var round = 1; round <= num_iterations; round++) {
ok = original_result.name == uglify_result.name && fuzzy_match(original_result.message, uglify_result.message);
}
if (!ok) {
- var fuzzy_result = sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3"), toplevel);
+ var fuzzy_result = run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3"), toplevel);
ok = sandbox.same_stdout(fuzzy_result, uglify_result);
}
}
@@ -2352,8 +2382,8 @@ for (var round = 1; round <= num_iterations; round++) {
if (!ok && errored && uglify_result.name == "ReferenceError" && original_result.name == "ReferenceError") ok = true;
// ignore difference due to implicit strict-mode in `class`
if (!ok && /\bclass\b/.test(original_code)) {
- var original_strict = sandbox.run_code('"use strict";' + original_code, toplevel);
- var uglify_strict = sandbox.run_code('"use strict";' + uglify_code, toplevel);
+ var original_strict = run_code('"use strict";' + original_code, toplevel);
+ var uglify_strict = run_code('"use strict";' + uglify_code, toplevel);
if (typeof original_strict != "string" && /strict/.test(original_strict.message)) {
ok = typeof uglify_strict != "string" && /strict/.test(uglify_strict.message);
} else {
@@ -2385,7 +2415,7 @@ for (var round = 1; round <= num_iterations; round++) {
var orig_skipped = patch_try_catch(original_code, toplevel);
var uglify_skipped = patch_try_catch(uglify_code, toplevel);
if (orig_skipped && uglify_skipped) {
- ok = sandbox.same_stdout(sandbox.run_code(orig_skipped, toplevel), sandbox.run_code(uglify_skipped, toplevel));
+ ok = sandbox.same_stdout(run_code(orig_skipped, toplevel), run_code(uglify_skipped, toplevel));
}
}
} else {