aboutsummaryrefslogtreecommitdiff
path: root/test/ufuzz/index.js
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2019-10-21 04:11:14 +0800
committerGitHub <noreply@github.com>2019-10-21 04:11:14 +0800
commit5bd0cf8633d32a4b71be5654a04e353d3f86324e (patch)
treeaca30200c02bc1525abbdfeeb090fbb909695ac5 /test/ufuzz/index.js
parent9199ab584667357122637df49eb96919b0a04eb8 (diff)
downloadtracifyjs-5bd0cf8633d32a4b71be5654a04e353d3f86324e.tar.gz
tracifyjs-5bd0cf8633d32a4b71be5654a04e353d3f86324e.zip
enable GitHub Actions (#3503)
Diffstat (limited to 'test/ufuzz/index.js')
-rw-r--r--test/ufuzz/index.js1131
1 files changed, 1131 insertions, 0 deletions
diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js
new file mode 100644
index 00000000..8f86e49c
--- /dev/null
+++ b/test/ufuzz/index.js
@@ -0,0 +1,1131 @@
+// derived from https://github.com/qfox/uglyfuzzer by Peter van der Zee
+"use strict";
+
+// check both CLI and file modes of nodejs (!). See #1695 for details. and the various settings of uglify.
+// bin/uglifyjs s.js -c && bin/uglifyjs s.js -c passes=3 && bin/uglifyjs s.js -c passes=3 -m
+// cat s.js | node && node s.js && bin/uglifyjs s.js -c | node && bin/uglifyjs s.js -c passes=3 | node && bin/uglifyjs s.js -c passes=3 -m | node
+
+require("../../tools/exit");
+
+var UglifyJS = require("../..");
+var randomBytes = require("crypto").randomBytes;
+var sandbox = require("../sandbox");
+
+var MAX_GENERATED_TOPLEVELS_PER_RUN = 1;
+var MAX_GENERATION_RECURSION_DEPTH = 12;
+var INTERVAL_COUNT = 100;
+
+var STMT_ARG_TO_ID = Object.create(null);
+var STMTS_TO_USE = [];
+function STMT_(name) {
+ return STMT_ARG_TO_ID[name] = STMTS_TO_USE.push(STMTS_TO_USE.length) - 1;
+}
+
+var STMT_BLOCK = STMT_("block");
+var STMT_IF_ELSE = STMT_("ifelse");
+var STMT_DO_WHILE = STMT_("dowhile");
+var STMT_WHILE = STMT_("while");
+var STMT_FOR_LOOP = STMT_("forloop");
+var STMT_FOR_IN = STMT_("forin");
+var STMT_SEMI = STMT_("semi");
+var STMT_EXPR = STMT_("expr");
+var STMT_SWITCH = STMT_("switch");
+var STMT_VAR = STMT_("var");
+var STMT_RETURN_ETC = STMT_("stop");
+var STMT_FUNC_EXPR = STMT_("funcexpr");
+var STMT_TRY = STMT_("try");
+var STMT_C = STMT_("c");
+
+var STMT_FIRST_LEVEL_OVERRIDE = -1;
+var STMT_SECOND_LEVEL_OVERRIDE = -1;
+var STMT_COUNT_FROM_GLOBAL = true; // count statement depth from nearest function scope or just global scope?
+
+var num_iterations = +process.argv[2] || 1/0;
+var verbose = false; // log every generated test
+var verbose_interval = false; // log every 100 generated tests
+var use_strict = false;
+var catch_redef = require.main === module;
+var generate_directive = require.main === module;
+for (var i = 2; i < process.argv.length; ++i) {
+ switch (process.argv[i]) {
+ case '-v':
+ verbose = true;
+ break;
+ case '-V':
+ verbose_interval = true;
+ break;
+ case '-t':
+ MAX_GENERATED_TOPLEVELS_PER_RUN = +process.argv[++i];
+ if (!MAX_GENERATED_TOPLEVELS_PER_RUN) throw new Error('Must generate at least one toplevel per run');
+ break;
+ case '-r':
+ MAX_GENERATION_RECURSION_DEPTH = +process.argv[++i];
+ if (!MAX_GENERATION_RECURSION_DEPTH) throw new Error('Recursion depth must be at least 1');
+ break;
+ case '-s1':
+ var name = process.argv[++i];
+ STMT_FIRST_LEVEL_OVERRIDE = STMT_ARG_TO_ID[name];
+ if (!(STMT_FIRST_LEVEL_OVERRIDE >= 0)) throw new Error('Unknown statement name; use -? to get a list');
+ break;
+ case '-s2':
+ var name = process.argv[++i];
+ STMT_SECOND_LEVEL_OVERRIDE = STMT_ARG_TO_ID[name];
+ if (!(STMT_SECOND_LEVEL_OVERRIDE >= 0)) throw new Error('Unknown statement name; use -? to get a list');
+ break;
+ case '--no-catch-redef':
+ catch_redef = false;
+ break;
+ case '--no-directive':
+ generate_directive = false;
+ break;
+ case '--use-strict':
+ use_strict = true;
+ break;
+ case '--stmt-depth-from-func':
+ STMT_COUNT_FROM_GLOBAL = false;
+ break;
+ case '--only-stmt':
+ STMTS_TO_USE = process.argv[++i].split(',').map(function(name){ return STMT_ARG_TO_ID[name]; });
+ break;
+ case '--without-stmt':
+ // meh. it runs once it's fine.
+ process.argv[++i].split(',').forEach(function(name){
+ var omit = STMT_ARG_TO_ID[name];
+ STMTS_TO_USE = STMTS_TO_USE.filter(function(id){ return id !== omit; })
+ });
+ break;
+ case '--help':
+ case '-h':
+ case '-?':
+ println('** UglifyJS fuzzer help **');
+ println('Valid options (optional):');
+ println('<number>: generate this many cases (if used must be first arg)');
+ println('-v: print every generated test case');
+ println('-V: print every 100th generated test case');
+ println('-t <int>: generate this many toplevels per run (more take longer)');
+ println('-r <int>: maximum recursion depth for generator (higher takes longer)');
+ println('-s1 <statement name>: force the first level statement to be this one (see list below)');
+ println('-s2 <statement name>: force the second level statement to be this one (see list below)');
+ println('--no-catch-redef: do not redefine catch variables');
+ println('--no-directive: do not generate directives');
+ println('--use-strict: generate "use strict"');
+ println('--stmt-depth-from-func: reset statement depth counter at each function, counts from global otherwise');
+ println('--only-stmt <statement names>: a comma delimited white list of statements that may be generated');
+ println('--without-stmt <statement names>: a comma delimited black list of statements never to generate');
+ println('List of accepted statement names: ' + Object.keys(STMT_ARG_TO_ID));
+ println('** UglifyJS fuzzer exiting **');
+ return 0;
+ default:
+ // first arg may be a number.
+ if (i > 2 || !parseInt(process.argv[i], 10)) throw new Error('Unknown argument[' + process.argv[i] + ']; see -h for help');
+ }
+}
+
+var VALUES = [
+ '"a"',
+ '"b"',
+ '"c"',
+ '""',
+ 'true',
+ 'false',
+ ' /[a2][^e]+$/ ',
+ '(-1)',
+ '(-2)',
+ '(-3)',
+ '(-4)',
+ '(-5)',
+ '0',
+ '1',
+ '2',
+ '3',
+ '4',
+ '5',
+ '22',
+ '-0', // 0/-0 !== 0
+ '23..toString()',
+ '24 .toString()',
+ '25. ',
+ '0x26.toString()',
+ 'NaN',
+ 'undefined',
+ 'Infinity',
+ 'null',
+ '[]',
+ '[,0][1]', // an array with elisions... but this is always false
+ '([,0].length === 2)', // an array with elisions... this is always true
+ '({})', // wrapped the object causes too many syntax errors in statements
+ '"foo"',
+ '"bar"',
+ '"undefined"',
+ '"object"',
+ '"number"',
+ '"function"',
+ 'this',
+];
+
+var BINARY_OPS_NO_COMMA = [
+ ' + ', // spaces needed to disambiguate with ++ cases (could otherwise cause syntax errors)
+ ' - ',
+ '/',
+ '*',
+ '&',
+ '|',
+ '^',
+ '<',
+ '<=',
+ '>',
+ '>=',
+ '==',
+ '===',
+ '!=',
+ '!==',
+ '<<',
+ '>>',
+ '>>>',
+ '%',
+ '&&',
+ '||',
+ '^' ];
+
+var BINARY_OPS = [','].concat(BINARY_OPS_NO_COMMA);
+
+var ASSIGNMENTS = [
+ '=',
+ '=',
+ '=',
+ '=',
+ '=',
+ '=',
+ '=',
+ '=',
+ '=',
+ '=',
+
+ '=',
+ '=',
+ '=',
+ '=',
+ '=',
+ '=',
+ '=',
+ '=',
+ '=',
+ '=',
+
+ '+=',
+ '+=',
+ '+=',
+ '+=',
+ '+=',
+ '+=',
+ '+=',
+ '+=',
+ '+=',
+ '+=',
+
+ '-=',
+ '*=',
+ '/=',
+ '&=',
+ '|=',
+ '^=',
+ '<<=',
+ '>>=',
+ '>>>=',
+ '%=',
+];
+
+var UNARY_SAFE = [
+ '+',
+ '-',
+ '~',
+ '!',
+ 'void ',
+ 'delete ',
+];
+var UNARY_POSTFIX = [
+ '++',
+ '--',
+];
+var UNARY_PREFIX = UNARY_POSTFIX.concat(UNARY_SAFE);
+
+var NO_COMMA = true;
+var COMMA_OK = false;
+var MAYBE = true;
+var MANDATORY = false;
+var CAN_THROW = true;
+var CANNOT_THROW = false;
+var CAN_BREAK = true;
+var CANNOT_BREAK = false;
+var CAN_CONTINUE = true;
+var CANNOT_CONTINUE = false;
+var CAN_RETURN = false;
+var CANNOT_RETURN = true;
+var NO_DEFUN = false;
+var DEFUN_OK = true;
+var DONT_STORE = true;
+
+var VAR_NAMES = [
+ 'a',
+ 'a',
+ 'a',
+ 'a',
+ 'b',
+ 'b',
+ 'b',
+ 'b',
+ 'c', // prevent redeclaring this, avoid assigning to this
+ 'foo',
+ 'foo',
+ 'bar',
+ 'bar',
+ 'undefined',
+ 'NaN',
+ 'Infinity',
+ 'arguments',
+ 'Math',
+ 'parseInt',
+];
+var INITIAL_NAMES_LEN = VAR_NAMES.length;
+
+var TYPEOF_OUTCOMES = [
+ 'function',
+ 'undefined',
+ 'string',
+ 'number',
+ 'object',
+ 'boolean',
+ 'special',
+ 'unknown',
+ 'symbol',
+ 'crap' ];
+
+var unique_vars = [];
+var loops = 0;
+var funcs = 0;
+var called = Object.create(null);
+var labels = 10000;
+
+function rng(max) {
+ var r = randomBytes(2).readUInt16LE(0) / 65536;
+ return Math.floor(max * r);
+}
+
+function strictMode() {
+ return use_strict && rng(4) == 0 ? '"use strict";' : '';
+}
+
+function createTopLevelCode() {
+ VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list
+ unique_vars.length = 0;
+ loops = 0;
+ funcs = 0;
+ called = Object.create(null);
+ return [
+ strictMode(),
+ 'var _calls_ = 10, a = 100, b = 10, c = 0;',
+ rng(2) == 0
+ ? 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');
+}
+
+function createFunctions(n, recurmax, allowDefun, canThrow, stmtDepth) {
+ if (--recurmax < 0) { return ';'; }
+ var s = '';
+ while (n-- > 0) {
+ s += createFunction(recurmax, allowDefun, canThrow, stmtDepth) + '\n';
+ }
+ return s;
+}
+
+function createParams() {
+ var params = [];
+ for (var n = rng(4); --n >= 0;) {
+ params.push(createVarName(MANDATORY));
+ }
+ return params.join(', ');
+}
+
+function createArgs(recurmax, stmtDepth, canThrow) {
+ var args = [];
+ for (var n = rng(4); --n >= 0;) {
+ args.push(rng(2) ? createValue() : createExpression(recurmax - 1, COMMA_OK, stmtDepth, canThrow));
+ }
+ return args.join(', ');
+}
+
+function filterDirective(s) {
+ if (!generate_directive && !s[1] && /\("/.test(s[2])) s[2] = ';' + s[2];
+ return s;
+}
+
+function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
+ if (--recurmax < 0) { return ';'; }
+ if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0;
+ var namesLenBefore = VAR_NAMES.length;
+ var name;
+ if (allowDefun || rng(5) > 0) {
+ name = 'f' + funcs++;
+ } else {
+ unique_vars.push('a', 'b', 'c');
+ name = createVarName(MANDATORY, !allowDefun);
+ unique_vars.length -= 3;
+ }
+ var s = [
+ 'function ' + name + '(' + createParams() + '){',
+ strictMode()
+ ];
+ if (rng(5) === 0) {
+ // functions with functions. lower the recursion to prevent a mess.
+ s.push(createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), DEFUN_OK, canThrow, stmtDepth));
+ } else {
+ // functions with statements
+ s.push(createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth));
+ }
+ s.push('}', '');
+ s = filterDirective(s).join('\n');
+
+ VAR_NAMES.length = namesLenBefore;
+
+ if (!allowDefun) {
+ // avoid "function statements" (decl inside statements)
+ s = 'var ' + createVarName(MANDATORY) + ' = ' + s;
+ s += '(' + createArgs(recurmax, stmtDepth, canThrow) + ')';
+ } else if (!(name in called) || rng(3) > 0) {
+ s += 'var ' + createVarName(MANDATORY) + ' = ' + name;
+ s += '(' + createArgs(recurmax, stmtDepth, canThrow) + ')';
+ }
+
+ return s + ';';
+}
+
+function createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
+ if (--recurmax < 0) { return ';'; }
+ var s = '';
+ while (--n > 0) {
+ s += createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '\n';
+ }
+ return s;
+}
+
+function enableLoopControl(flag, defaultValue) {
+ return Array.isArray(flag) && flag.indexOf("") < 0 ? flag.concat("") : flag || defaultValue;
+}
+
+function createLabel(canBreak, canContinue) {
+ var label;
+ if (rng(10) < 3) {
+ label = ++labels;
+ if (Array.isArray(canBreak)) {
+ canBreak = canBreak.slice();
+ } else {
+ canBreak = canBreak ? [ "" ] : [];
+ }
+ canBreak.push(label);
+ if (Array.isArray(canContinue)) {
+ canContinue = canContinue.slice();
+ } else {
+ canContinue = canContinue ? [ "" ] : [];
+ }
+ canContinue.push(label);
+ }
+ return {
+ break: canBreak,
+ continue: canContinue,
+ target: label ? "L" + label + ": " : ""
+ };
+}
+
+function getLabel(label) {
+ if (!Array.isArray(label)) return "";
+ label = label[rng(label.length)];
+ return label && " L" + label;
+}
+
+function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth, target) {
+ ++stmtDepth;
+ var loop = ++loops;
+ if (--recurmax < 0) {
+ return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ';';
+ }
+
+ // allow to forcefully generate certain structures at first or second recursion level
+ if (target === undefined) {
+ if (stmtDepth === 1 && STMT_FIRST_LEVEL_OVERRIDE >= 0) target = STMT_FIRST_LEVEL_OVERRIDE;
+ else if (stmtDepth === 2 && STMT_SECOND_LEVEL_OVERRIDE >= 0) target = STMT_SECOND_LEVEL_OVERRIDE;
+ else target = STMTS_TO_USE[rng(STMTS_TO_USE.length)];
+ }
+
+ switch (target) {
+ case STMT_BLOCK:
+ var label = createLabel(canBreak);
+ return label.target + '{' + createStatements(rng(5) + 1, recurmax, canThrow, label.break, canContinue, cannotReturn, stmtDepth) + '}';
+ case STMT_IF_ELSE:
+ return 'if (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + (rng(2) === 1 ? ' else ' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) : '');
+ case STMT_DO_WHILE:
+ var label = createLabel(canBreak, canContinue);
+ canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
+ canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
+ return '{var brake' + loop + ' = 5; ' + label.target + 'do {' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '} while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0);}';
+ case STMT_WHILE:
+ var label = createLabel(canBreak, canContinue);
+ canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
+ canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
+ return '{var brake' + loop + ' = 5; ' + label.target + 'while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0)' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '}';
+ case STMT_FOR_LOOP:
+ var label = createLabel(canBreak, canContinue);
+ canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
+ canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
+ return label.target + 'for (var brake' + loop + ' = 5; (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && brake' + loop + ' > 0; --brake' + loop + ')' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth);
+ case STMT_FOR_IN:
+ var label = createLabel(canBreak, canContinue);
+ canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
+ canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
+ var optElementVar = '';
+ if (rng(5) > 1) {
+ optElementVar = 'c = 1 + c; var ' + createVarName(MANDATORY) + ' = expr' + loop + '[key' + loop + ']; ';
+ }
+ return '{var expr' + loop + ' = ' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + '; ' + label.target + ' for (var key' + loop + ' in expr' + loop + ') {' + optElementVar + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '}}';
+ case STMT_SEMI:
+ return use_strict && rng(20) === 0 ? '"use strict";' : ';';
+ case STMT_EXPR:
+ return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ';';
+ case STMT_SWITCH:
+ // note: case args are actual expressions
+ // note: default does not _need_ to be last
+ return 'switch (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') { ' + createSwitchParts(recurmax, 4, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '}';
+ case STMT_VAR:
+ switch (rng(3)) {
+ case 0:
+ unique_vars.push('c');
+ var name = createVarName(MANDATORY);
+ unique_vars.pop();
+ return '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) + ';';
+ 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) + ';';
+ }
+ case STMT_RETURN_ETC:
+ switch (rng(8)) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ if (canBreak && rng(5) === 0) return 'break' + getLabel(canBreak) + ';';
+ if (canContinue && rng(5) === 0) return 'continue' + getLabel(canContinue) + ';';
+ if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
+ if (rng(3) == 0) return '/*3*/return;';
+ return 'return ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
+ case 4:
+ // this is actually more like a parser test, but perhaps it hits some dead code elimination traps
+ // must wrap in curlies to prevent orphaned `else` statement
+ // note: you can't `throw` without an expression so don't put a `throw` option in this case
+ if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
+ return '{ /*2*/ return\n' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
+ default:
+ // must wrap in curlies to prevent orphaned `else` statement
+ if (canThrow && rng(5) === 0) return '{ throw ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
+ if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
+ return '{ return ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
+ }
+ case STMT_FUNC_EXPR:
+ // "In non-strict mode code, functions can only be declared at top level, inside a block, or ..."
+ // (dont both with func decls in `if`; it's only a parser thing because you cant call them without a block)
+ return '{' + createFunction(recurmax, NO_DEFUN, canThrow, stmtDepth) + '}';
+ case STMT_TRY:
+ // catch var could cause some problems
+ // note: the "blocks" are syntactically mandatory for try/catch/finally
+ var n = rng(3); // 0=only catch, 1=only finally, 2=catch+finally
+ var s = 'try {' + createStatement(recurmax, n === 1 ? CANNOT_THROW : CAN_THROW, canBreak, canContinue, cannotReturn, stmtDepth) + ' }';
+ if (n !== 1) {
+ // the catch var should only be accessible in the catch clause...
+ // we have to do go through some trouble here to prevent leaking it
+ var nameLenBefore = VAR_NAMES.length;
+ var catchName = createVarName(MANDATORY);
+ var freshCatchName = VAR_NAMES.length !== nameLenBefore;
+ if (!catch_redef) unique_vars.push(catchName);
+ s += ' catch (' + catchName + ') { ' + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + ' }';
+ // remove catch name
+ if (!catch_redef) unique_vars.pop();
+ if (freshCatchName) VAR_NAMES.splice(nameLenBefore, 1);
+ }
+ if (n !== 0) s += ' finally { ' + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + ' }';
+ return s;
+ case STMT_C:
+ return 'c = c + 1;';
+ default:
+ throw 'no';
+ }
+}
+
+function createSwitchParts(recurmax, n, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
+ var hadDefault = false;
+ var s = [''];
+ canBreak = enableLoopControl(canBreak, CAN_BREAK);
+ while (n-- > 0) {
+ //hadDefault = n > 0; // disables weird `default` clause positioning (use when handling destabilizes)
+ if (hadDefault || rng(5) > 0) {
+ s.push(
+ 'case ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':',
+ createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
+ rng(10) > 0 ? ' break;' : '/* fall-through */',
+ ''
+ );
+ } else {
+ hadDefault = true;
+ s.push(
+ 'default:',
+ createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
+ ''
+ );
+ }
+ }
+ return s.join('\n');
+}
+
+function createExpression(recurmax, noComma, stmtDepth, canThrow) {
+ if (--recurmax < 0) {
+ return '(c = 1 + c, ' + createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')'; // note: should return a simple non-recursing expression value!
+ }
+ // since `a` and `b` are our canaries we want them more frequently than other expressions (1/3rd chance of a canary)
+ switch (rng(6)) {
+ case 0:
+ return '(a++ + (' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + '))';
+ case 1:
+ return '((--b) + (' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + '))';
+ case 2:
+ return '((c = c + 1) + (' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + '))'; // c only gets incremented
+ default:
+ return '(' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + ')';
+ }
+}
+function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
+ var p = 0;
+ switch (rng(_createExpression.N)) {
+ case p++:
+ case p++:
+ return createUnaryPrefix() + (rng(2) === 1 ? 'a' : 'b');
+ case p++:
+ case p++:
+ return (rng(2) === 1 ? 'a' : 'b') + createUnaryPostfix();
+ case p++:
+ case p++:
+ // parens needed because assignments aren't valid unless they're the left-most op(s) in an expression
+ return 'b ' + createAssignment() + ' a';
+ case p++:
+ case p++:
+ return rng(2) + ' === 1 ? a : b';
+ case p++:
+ case p++:
+ return createValue();
+ case p++:
+ case p++:
+ return getVarName();
+ case p++:
+ return getVarName() + createAssignment() + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
+ case p++:
+ return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
+ case p++:
+ return createExpression(recurmax, noComma, stmtDepth, canThrow) + '?' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':' + createExpression(recurmax, noComma, stmtDepth, canThrow);
+ case p++:
+ case p++:
+ var nameLenBefore = VAR_NAMES.length;
+ unique_vars.push('c');
+ var name = createVarName(MAYBE); // note: this name is only accessible from _within_ the function. and immutable at that.
+ unique_vars.pop();
+ var s = [];
+ switch (rng(5)) {
+ case 0:
+ s.push(
+ '(function ' + name + '(){',
+ strictMode(),
+ createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
+ rng(2) == 0 ? '})' : '})()'
+ );
+ break;
+ case 1:
+ s.push(
+ '+function ' + name + '(){',
+ strictMode(),
+ createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
+ '}()'
+ );
+ break;
+ case 2:
+ s.push(
+ '!function ' + name + '(){',
+ strictMode(),
+ createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
+ '}()'
+ );
+ break;
+ case 3:
+ s.push(
+ 'void function ' + name + '(){',
+ strictMode(),
+ createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
+ '}()'
+ );
+ break;
+ default:
+ var instantiate = rng(4) ? 'new ' : '';
+ s.push(
+ instantiate + 'function ' + name + '(){',
+ strictMode()
+ );
+ if (instantiate) for (var i = rng(4); --i >= 0;) {
+ if (rng(2)) s.push('this.' + getDotKey(true) + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ';');
+ else s.push('this[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']' + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ';');
+ }
+ s.push(
+ createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
+ rng(2) == 0 ? '}' : '}()'
+ );
+ break;
+ }
+ VAR_NAMES.length = nameLenBefore;
+ return filterDirective(s).join('\n');
+ case p++:
+ case p++:
+ return createTypeofExpr(recurmax, stmtDepth, canThrow);
+ case p++:
+ case p++:
+ // more like a parser test but perhaps comment nodes mess up the analysis?
+ // note: parens not needed for post-fix (since that's the default when ambiguous)
+ // for prefix ops we need parens to prevent accidental syntax errors.
+ switch (rng(6)) {
+ case 0:
+ return 'a/* ignore */++';
+ case 1:
+ return 'b/* ignore */--';
+ case 2:
+ return '++/* ignore */a';
+ case 3:
+ return '--/* ignore */b';
+ case 4:
+ // only groups that wrap a single variable return a "Reference", so this is still valid.
+ // may just be a parser edge case that is invisible to uglify...
+ return '--(b)';
+ case 5:
+ // classic 0.3-0.1 case; 1-0.1-0.1-0.1 is not 0.7 :)
+ return 'b + 1-0.1-0.1-0.1';
+ default:
+ return '--/* ignore */b';
+ }
+ case p++:
+ case p++:
+ return createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
+ case p++:
+ case p++:
+ return createUnarySafePrefix() + '(' + createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
+ case p++:
+ return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || a || 3).toString() ";
+ case p++:
+ return " /[abc4]/.test(((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || b || 5).toString()) ";
+ case p++:
+ return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) +
+ ") || " + rng(10) + ").toString()[" +
+ createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "] ";
+ case p++:
+ return createArrayLiteral(recurmax, stmtDepth, canThrow);
+ case p++:
+ return createObjectLiteral(recurmax, stmtDepth, canThrow);
+ case p++:
+ return createArrayLiteral(recurmax, stmtDepth, canThrow) + '[' +
+ createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
+ case p++:
+ return createObjectLiteral(recurmax, stmtDepth, canThrow) + '[' +
+ createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
+ case p++:
+ return createArrayLiteral(recurmax, stmtDepth, canThrow) + '.' + getDotKey();
+ case p++:
+ return createObjectLiteral(recurmax, stmtDepth, canThrow) + '.' + getDotKey();
+ case p++:
+ var name = getVarName();
+ return name + ' && ' + name + '[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
+ case p++:
+ var name = getVarName();
+ return name + ' && ' + name + '.' + getDotKey();
+ case p++:
+ case p++:
+ case p++:
+ case p++:
+ var name = rng(3) == 0 ? getVarName() : 'f' + rng(funcs + 2);
+ called[name] = true;
+ return 'typeof ' + name + ' == "function" && --_calls_ >= 0 && ' + name + '(' + createArgs(recurmax, stmtDepth, canThrow) + ')';
+ }
+ _createExpression.N = p;
+ return _createExpression(recurmax, noComma, stmtDepth, canThrow);
+}
+
+function createArrayLiteral(recurmax, stmtDepth, canThrow) {
+ recurmax--;
+ var arr = "[";
+ for (var i = rng(6); --i >= 0;) {
+ // in rare cases produce an array hole element
+ var element = rng(20) ? createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) : "";
+ arr += element + ", ";
+ }
+ return arr + "]";
+}
+
+var SAFE_KEYS = [
+ "length",
+ "foo",
+ "a",
+ "b",
+ "c",
+ "undefined",
+ "null",
+ "NaN",
+ "Infinity",
+ "in",
+ "var",
+];
+var KEYS = [
+ "''",
+ '"\t"',
+ '"-2"',
+ "0",
+ "1.5",
+ "3",
+].concat(SAFE_KEYS);
+
+function getDotKey(assign) {
+ var key;
+ do {
+ key = SAFE_KEYS[rng(SAFE_KEYS.length)];
+ } while (assign && key == "length");
+ return key;
+}
+
+function createAccessor(recurmax, stmtDepth, canThrow) {
+ var namesLenBefore = VAR_NAMES.length;
+ var s;
+ var prop1 = getDotKey();
+ if (rng(2) == 0) {
+ s = [
+ 'get ' + prop1 + '(){',
+ strictMode(),
+ createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
+ createStatement(recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth, STMT_RETURN_ETC),
+ '},'
+ ];
+ } else {
+ var prop2;
+ do {
+ prop2 = getDotKey();
+ } while (prop1 == prop2);
+ s = [
+ 'set ' + prop1 + '(' + createVarName(MANDATORY) + '){',
+ strictMode(),
+ createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
+ 'this.' + prop2 + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ';',
+ '},'
+ ];
+ }
+ VAR_NAMES.length = namesLenBefore;
+ return filterDirective(s).join('\n');
+}
+
+function createObjectLiteral(recurmax, stmtDepth, canThrow) {
+ recurmax--;
+ var obj = ['({'];
+ for (var i = rng(6); --i >= 0;) {
+ if (rng(20) == 0) {
+ obj.push(createAccessor(recurmax, stmtDepth, canThrow));
+ } else {
+ var key = KEYS[rng(KEYS.length)];
+ obj.push(key + ':(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + '),');
+ }
+ }
+ obj.push('})');
+ return obj.join('\n');
+}
+
+function createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
+ recurmax = 3; // note that this generates 2^recurmax expression parts... make sure to cap it
+ return _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
+}
+function _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
+ return '(' + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow)
+ + createBinaryOp(noComma) + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
+}
+function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
+ // intentionally generate more hardcore ops
+ if (--recurmax < 0) return createValue();
+ var assignee, expr;
+ switch (rng(30)) {
+ case 0:
+ return '(c = c + 1, ' + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
+ case 1:
+ return '(' + createUnarySafePrefix() + '(' + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + '))';
+ case 2:
+ assignee = getVarName();
+ return '(' + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
+ case 3:
+ assignee = getVarName();
+ expr = '(' + assignee + '[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow)
+ + ']' + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
+ return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')';
+ case 4:
+ assignee = getVarName();
+ expr = '(' + assignee + '.' + getDotKey(true) + createAssignment()
+ + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
+ return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')';
+ default:
+ return _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
+ }
+}
+
+function createTypeofExpr(recurmax, stmtDepth, canThrow) {
+ switch (rng(8)) {
+ case 0:
+ return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' === "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
+ case 1:
+ return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' !== "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
+ case 2:
+ return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' == "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
+ case 3:
+ return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' != "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
+ case 4:
+ return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ')';
+ default:
+ return '(typeof ' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')';
+ }
+}
+
+function createValue() {
+ return VALUES[rng(VALUES.length)];
+}
+
+function createBinaryOp(noComma) {
+ if (noComma) return BINARY_OPS_NO_COMMA[rng(BINARY_OPS_NO_COMMA.length)];
+ return BINARY_OPS[rng(BINARY_OPS.length)];
+}
+
+function createAssignment() {
+ return ASSIGNMENTS[rng(ASSIGNMENTS.length)];
+}
+
+function createUnarySafePrefix() {
+ return UNARY_SAFE[rng(UNARY_SAFE.length)];
+}
+
+function createUnaryPrefix() {
+ return UNARY_PREFIX[rng(UNARY_PREFIX.length)];
+}
+
+function createUnaryPostfix() {
+ return UNARY_POSTFIX[rng(UNARY_POSTFIX.length)];
+}
+
+function getVarName() {
+ // try to get a generated name reachable from current scope. default to just `a`
+ return VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)] || 'a';
+}
+
+function createVarName(maybe, dontStore) {
+ if (!maybe || rng(2)) {
+ var suffix = rng(3);
+ var name;
+ do {
+ name = VAR_NAMES[rng(VAR_NAMES.length)];
+ if (suffix) name += '_' + suffix;
+ } while (unique_vars.indexOf(name) >= 0);
+ if (suffix && !dontStore) VAR_NAMES.push(name);
+ return name;
+ }
+ return '';
+}
+
+if (require.main !== module) {
+ exports.createTopLevelCode = createTopLevelCode;
+ exports.num_iterations = num_iterations;
+ return;
+}
+
+function writeln(stream, msg) {
+ if (typeof msg != "undefined") {
+ stream.write(typeof msg == "string" ? msg : msg.stack || "" + msg);
+ }
+ stream.write("\n");
+}
+
+function println(msg) {
+ writeln(process.stdout, msg);
+}
+
+function errorln(msg) {
+ writeln(process.stderr, msg);
+}
+
+function try_beautify(code, toplevel, result, printfn) {
+ var beautified = UglifyJS.minify(code, {
+ compress: false,
+ mangle: false,
+ output: {
+ beautify: true,
+ braces: true,
+ },
+ });
+ if (beautified.error) {
+ printfn("// !!! beautify failed !!!");
+ printfn(beautified.error);
+ } else if (sandbox.same_stdout(sandbox.run_code(beautified.code, toplevel), result)) {
+ printfn("// (beautified)");
+ printfn(beautified.code);
+ return;
+ }
+ printfn("//");
+ printfn(code);
+}
+
+var default_options = UglifyJS.default_options();
+
+function log_suspects(minify_options, component) {
+ var options = component in minify_options ? minify_options[component] : true;
+ if (!options) return;
+ if (typeof options != "object") options = {};
+ var defs = default_options[component];
+ var suspects = Object.keys(defs).filter(function(name) {
+ var flip = name == "keep_fargs";
+ if (flip ? name in options : (name in options ? options : defs)[name]) {
+ var m = JSON.parse(JSON.stringify(minify_options));
+ var o = JSON.parse(JSON.stringify(options));
+ o[name] = flip;
+ m[component] = o;
+ var result = UglifyJS.minify(original_code, m);
+ if (result.error) {
+ errorln("Error testing options." + component + "." + name);
+ errorln(result.error);
+ } else {
+ var r = sandbox.run_code(result.code, m.toplevel);
+ return sandbox.same_stdout(original_result, r);
+ }
+ }
+ });
+ if (suspects.length > 0) {
+ errorln("Suspicious " + component + " options:");
+ suspects.forEach(function(name) {
+ errorln(" " + name);
+ });
+ errorln();
+ }
+}
+
+function log_rename(options) {
+ var m = JSON.parse(JSON.stringify(options));
+ m.rename = false;
+ var result = UglifyJS.minify(original_code, m);
+ if (result.error) {
+ errorln("Error testing options.rename");
+ errorln(result.error);
+ } else {
+ var r = sandbox.run_code(result.code, m.toplevel);
+ if (sandbox.same_stdout(original_result, r)) {
+ errorln("Suspicious options:");
+ errorln(" rename");
+ errorln();
+ }
+ }
+}
+
+function log(options) {
+ if (!ok) errorln('\n\n\n\n\n\n!!!!!!!!!!\n\n\n');
+ errorln("//=============================================================");
+ if (!ok) errorln("// !!!!!! Failed... round " + round);
+ errorln("// original code");
+ try_beautify(original_code, false, original_result, errorln);
+ errorln();
+ errorln();
+ errorln("//-------------------------------------------------------------");
+ options = JSON.parse(options);
+ if (typeof uglify_code == "string") {
+ errorln("// uglified code");
+ try_beautify(uglify_code, options.toplevel, uglify_result, errorln);
+ errorln();
+ errorln();
+ errorln("original result:");
+ errorln(original_result);
+ errorln("uglified result:");
+ errorln(uglify_result);
+ } else {
+ errorln("// !!! uglify failed !!!");
+ errorln(uglify_code);
+ if (errored) {
+ errorln();
+ errorln();
+ errorln("original stacktrace:");
+ errorln(original_result);
+ }
+ }
+ errorln("minify(options):");
+ errorln(JSON.stringify(options, null, 2));
+ errorln();
+ if (!ok && typeof uglify_code == "string") {
+ Object.keys(default_options).forEach(log_suspects.bind(null, options));
+ log_rename(options);
+ errorln("!!!!!! Failed... round " + round);
+ }
+}
+
+var fallback_options = [ JSON.stringify({
+ compress: false,
+ mangle: false
+}) ];
+var minify_options = require("./options.json").map(JSON.stringify);
+var original_code, original_result, errored;
+var uglify_code, uglify_result, ok;
+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) ];
+ errored = typeof orig_result[0] != "string";
+ if (!errored) orig_result.push(sandbox.run_code(original_code, true));
+ (errored ? fallback_options : minify_options).forEach(function(options) {
+ var o = JSON.parse(options);
+ uglify_code = UglifyJS.minify(original_code, o);
+ original_result = orig_result[o.toplevel ? 1 : 0];
+ if (!uglify_code.error) {
+ uglify_code = uglify_code.code;
+ uglify_result = sandbox.run_code(uglify_code, o.toplevel);
+ ok = sandbox.same_stdout(original_result, uglify_result);
+ } else {
+ uglify_code = uglify_code.error;
+ if (errored) {
+ ok = uglify_code.name == original_result.name;
+ }
+ }
+ if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options);
+ else if (errored) {
+ println("//=============================================================");
+ println("// original code");
+ try_beautify(original_code, o.toplevel, original_result, println);
+ println();
+ println();
+ println("original result:");
+ println(original_result);
+ println();
+ }
+ if (!ok && isFinite(num_iterations)) {
+ println();
+ process.exit(1);
+ }
+ });
+}
+println();