text: // 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 reduce_test = require("../reduce");
  
  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 " /[abc4]/g.exec(((" + 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, sandbox.has_toplevel(m));
                  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(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, sandbox.has_toplevel(m));
          if (sandbox.same_stdout(original_result, r)) {
              errorln("Suspicious options:");
              errorln("  rename");
              errorln();
          }
      }
  }
  
  function log(options) {
      var toplevel = sandbox.has_toplevel(JSON.parse(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, toplevel, original_result, errorln);
      errorln();
      errorln();
      errorln("//-------------------------------------------------------------");
      if (typeof uglify_code == "string") {
          errorln("// uglified code");
          try_beautify(uglify_code, toplevel, uglify_result, errorln);
          errorln();
          errorln();
          errorln("original result:");
          errorln(original_result);
          errorln("uglified result:");
          errorln(uglify_result);
          errorln("//-------------------------------------------------------------");
          var reduced = reduce_test(original_code, JSON.parse(options), {
              verbose: false,
          }).code;
          if (reduced) {
              errorln();
              errorln("// reduced test case (output will differ)");
              errorln();
              errorln(reduced);
              errorln();
              errorln("//-------------------------------------------------------------");
          }
      } else {
          errorln("// !!! uglify failed !!!");
          errorln(uglify_code);
          if (errored) {
              errorln();
              errorln();
              errorln("original stacktrace:");
              errorln(original_result);
          }
      }
      errorln("minify(options):");
      errorln(JSON.stringify(JSON.parse(options), null, 2));
      errorln();
      if (!ok && typeof uglify_code == "string") {
          Object.keys(default_options).forEach(log_suspects.bind(null, JSON.parse(options)));
          log_rename(options);
          errorln("!!!!!! Failed... round " + round);
      }
  }
  
  function fuzzy_match(original, uglified) {
      uglified = uglified.split(" ");
      var i = uglified.length;
      original = original.split(" ", i);
      while (--i >= 0) {
          if (original[i] === uglified[i]) continue;
          var a = +original[i];
          var b = +uglified[i];
          if (Math.abs((b - a) / a) < 1e-10) continue;
          return false;
      }
      return true;
  }
  
  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);
          var toplevel = sandbox.has_toplevel(o);
          uglify_code = UglifyJS.minify(original_code, o);
          original_result = orig_result[toplevel ? 1 : 0];
          if (!uglify_code.error) {
              uglify_code = uglify_code.code;
              uglify_result = sandbox.run_code(uglify_code, toplevel);
              ok = sandbox.same_stdout(original_result, uglify_result);
              if (!ok && typeof uglify_result == "string" && o.compress.unsafe_math) {
                  ok = fuzzy_match(original_result, uglify_result);
                  if (!ok) {
                      var fuzzy_result = sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3"), toplevel);
                      ok = sandbox.same_stdout(fuzzy_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, toplevel, original_result, println);
              println();
              println();
              println("original result:");
              println(original_result);
              println();
          }
          if (!ok && isFinite(num_iterations)) {
              println();
              process.exit(1);
          }
      });
  }
  println();
  