From 5bd0cf8633d32a4b71be5654a04e353d3f86324e Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 21 Oct 2019 04:11:14 +0800 Subject: enable GitHub Actions (#3503) --- test/ufuzz/index.js | 1131 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1131 insertions(+) create mode 100644 test/ufuzz/index.js (limited to 'test/ufuzz/index.js') 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(': 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 : generate this many toplevels per run (more take longer)'); + println('-r : maximum recursion depth for generator (higher takes longer)'); + println('-s1 : force the first level statement to be this one (see list below)'); + println('-s2 : 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 : a comma delimited white list of statements that may be generated'); + println('--without-stmt : 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(); -- cgit v1.2.3