/*
 * Copyright 2016, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <random>

#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <sys/time.h>

namespace {

/*
 * Operators.
 */

#define EMIT(x) fputs((x)[random0(sizeof(x)/sizeof(const char*))], out_);

static constexpr const char* kIncDecOps[]   = { "++", "--" };
static constexpr const char* kIntUnaryOps[] = { "+", "-", "~" };
static constexpr const char* kFpUnaryOps[]  = { "+", "-" };

static constexpr const char* kBoolBinOps[] = { "&&", "||", "&", "|", "^" };  // few less common
static constexpr const char* kIntBinOps[]  = { "+", "-", "*", "/", "%",
                                               ">>", ">>>", "<<", "&", "|", "^" };
static constexpr const char* kFpBinOps[]   = { "+", "-", "*", "/" };

static constexpr const char* kBoolAssignOps[] = { "=", "&=" , "|=", "^=" };  // few less common
static constexpr const char* kIntAssignOps[]  = { "=", "+=", "-=", "*=", "/=", "%=",
                                                  ">>=", ">>>=", "<<=", "&=", "|=", "^=" };
static constexpr const char* kFpAssignOps[]   = { "=", "+=", "-=", "*=", "/=" };

static constexpr const char* kBoolRelOps[] = { "==", "!=" };
static constexpr const char* kRelOps[]     = { "==", "!=", ">", ">=", "<", "<=" };

/*
 * Version of JFuzz. Increase this each time changes are made to the program
 * to preserve the property that a given version of JFuzz yields the same
 * fuzzed program for a deterministic random seed.
 */
const char* VERSION = "1.2";

/*
 * Maximum number of array dimensions, together with corresponding maximum size
 * within each dimension (to keep memory/runtime requirements roughly the same).
 */
static const uint32_t kMaxDim = 10;
static const uint32_t kMaxDimSize[kMaxDim + 1] = { 0, 1000, 32, 10, 6, 4, 3, 3, 2, 2, 2 };

/**
 * A class that generates a random program that compiles correctly. The program
 * is generated using rules that generate various programming constructs. Each rule
 * has a fixed probability to "fire". Running a generated program yields deterministic
 * output, making it suited to test various modes of execution (e.g an interpreter vs.
 * an compiler or two different run times) for divergences.
 */
class JFuzz {
 public:
  JFuzz(FILE* out,
        uint32_t seed,
        uint32_t expr_depth,
        uint32_t stmt_length,
        uint32_t if_nest,
        uint32_t loop_nest)
      : out_(out),
        fuzz_random_engine_(seed),
        fuzz_seed_(seed),
        fuzz_expr_depth_(expr_depth),
        fuzz_stmt_length_(stmt_length),
        fuzz_if_nest_(if_nest),
        fuzz_loop_nest_(loop_nest),
        return_type_(randomType()),
        array_type_(randomType()),
        array_dim_(random1(kMaxDim)),
        array_size_(random1(kMaxDimSize[array_dim_])),
        indentation_(0),
        expr_depth_(0),
        stmt_length_(0),
        if_nest_(0),
        loop_nest_(0),
        switch_nest_(0),
        do_nest_(0),
        boolean_local_(0),
        int_local_(0),
        long_local_(0),
        float_local_(0),
        double_local_(0),
        in_inner_(false) { }

  ~JFuzz() { }

  void emitProgram() {
    emitHeader();
    emitTestClassWithMain();
  }

 private:
  //
  // Types.
  //

  // Current type of each expression during generation.
  enum Type {
    kBoolean,
    kInt,
    kLong,
    kFloat,
    kDouble
  };

  // Test for an integral type.
  static bool isInteger(Type tp) {
    return tp == kInt || tp == kLong;
  }

  // Test for a floating-point type.
  static bool isFP(Type tp) {
    return tp == kFloat || tp == kDouble;
  }

  // Emit type.
  void emitType(Type tp) const {
    switch (tp) {
      case kBoolean: fputs("boolean", out_); break;
      case kInt:     fputs("int",     out_); break;
      case kLong:    fputs("long",    out_); break;
      case kFloat:   fputs("float",   out_); break;
      case kDouble:  fputs("double",  out_); break;
    }
  }

  // Emit type class.
  void emitTypeClass(Type tp) const {
    switch (tp) {
      case kBoolean: fputs("Boolean", out_); break;
      case kInt:     fputs("Integer", out_); break;
      case kLong:    fputs("Long",    out_); break;
      case kFloat:   fputs("Float",   out_); break;
      case kDouble:  fputs("Double",  out_); break;
    }
  }

  // Return a random type.
  Type randomType() {
    switch (random1(5)) {
      case 1:  return kBoolean;
      case 2:  return kInt;
      case 3:  return kLong;
      case 4:  return kFloat;
      default: return kDouble;
    }
  }

  //
  // Expressions.
  //

  // Emit an unary operator (same type in-out).
  void emitUnaryOp(Type tp) {
    if (tp == kBoolean) {
      fputc('!', out_);
    } else if (isInteger(tp)) {
      EMIT(kIntUnaryOps);
    } else {  // isFP(tp)
      EMIT(kFpUnaryOps);
    }
  }

  // Emit a pre/post-increment/decrement operator (same type in-out).
  void emitIncDecOp(Type tp) {
    if (tp == kBoolean) {
      // Not applicable, just leave "as is".
    } else {  // isInteger(tp) || isFP(tp)
      EMIT(kIncDecOps);
    }
  }

  // Emit a binary operator (same type in-out).
  void emitBinaryOp(Type tp) {
    if (tp == kBoolean) {
      EMIT(kBoolBinOps);
    } else if (isInteger(tp)) {
      EMIT(kIntBinOps);
    } else {  // isFP(tp)
      EMIT(kFpBinOps);
    }
  }

  // Emit an assignment operator (same type in-out).
  void emitAssignmentOp(Type tp) {
    if (tp == kBoolean) {
      EMIT(kBoolAssignOps);
    } else if (isInteger(tp)) {
      EMIT(kIntAssignOps);
    } else {  // isFP(tp)
      EMIT(kFpAssignOps);
    }
  }

  // Emit a relational operator (one type in, boolean out).
  void emitRelationalOp(Type tp) {
    if (tp == kBoolean) {
      EMIT(kBoolRelOps);
    } else {  // isInteger(tp) || isFP(tp)
      EMIT(kRelOps);
    }
  }

  // Emit a type conversion operator sequence (out type given, new suitable in type picked).
  Type emitTypeConversionOp(Type tp) {
    if (tp == kInt) {
      switch (random1(5)) {
        case 1: fputs("(int)", out_); return kLong;
        case 2: fputs("(int)", out_); return kFloat;
        case 3: fputs("(int)", out_); return kDouble;
        // Narrowing-widening.
        case 4: fputs("(int)(byte)(int)",  out_); return kInt;
        case 5: fputs("(int)(short)(int)", out_); return kInt;
      }
    } else if (tp == kLong) {
      switch (random1(6)) {
        case 1: /* implicit */         return kInt;
        case 2: fputs("(long)", out_); return kFloat;
        case 3: fputs("(long)", out_); return kDouble;
        // Narrowing-widening.
        case 4: fputs("(long)(byte)(long)",  out_); return kLong;
        case 5: fputs("(long)(short)(long)", out_); return kLong;
        case 6: fputs("(long)(int)(long)",   out_); return kLong;
      }
    } else if (tp == kFloat) {
      switch (random1(4)) {
        case 1: fputs("(float)", out_); return kInt;
        case 2: fputs("(float)", out_); return kLong;
        case 3: fputs("(float)", out_); return kDouble;
        // Narrowing-widening.
        case 4: fputs("(float)(int)(float)", out_); return kFloat;
      }
    } else if (tp == kDouble) {
      switch (random1(5)) {
        case 1: fputs("(double)", out_); return kInt;
        case 2: fputs("(double)", out_); return kLong;
        case 3: fputs("(double)", out_); return kFloat;
        // Narrowing-widening.
        case 4: fputs("(double)(int)(double)",   out_); return kDouble;
        case 5: fputs("(double)(float)(double)", out_); return kDouble;
      }
    }
    return tp;  // nothing suitable, just keep type
  }

  // Emit a type conversion (out type given, new suitable in type picked).
  void emitTypeConversion(Type tp) {
    if (tp == kBoolean) {
      Type tp = randomType();
      emitExpression(tp);
      fputc(' ', out_);
      emitRelationalOp(tp);
      fputc(' ', out_);
      emitExpression(tp);
    } else {
      tp = emitTypeConversionOp(tp);
      fputc(' ', out_);
      emitExpression(tp);
    }
  }

  // Emit an unary intrinsic (out type given, new suitable in type picked).
  Type emitIntrinsic1(Type tp) {
    if (tp == kBoolean) {
      switch (random1(6)) {
        case 1: fputs("Float.isNaN",       out_); return kFloat;
        case 2: fputs("Float.isFinite",    out_); return kFloat;
        case 3: fputs("Float.isInfinite",  out_); return kFloat;
        case 4: fputs("Double.isNaN",      out_); return kDouble;
        case 5: fputs("Double.isFinite",   out_); return kDouble;
        case 6: fputs("Double.isInfinite", out_); return kDouble;
      }
    } else if (isInteger(tp)) {
      const char* prefix = tp == kLong ? "Long" : "Integer";
      switch (random1(13)) {
        case 1: fprintf(out_, "%s.highestOneBit",         prefix); break;
        case 2: fprintf(out_, "%s.lowestOneBit",          prefix); break;
        case 3: fprintf(out_, "%s.numberOfLeadingZeros",  prefix); break;
        case 4: fprintf(out_, "%s.numberOfTrailingZeros", prefix); break;
        case 5: fprintf(out_, "%s.bitCount",              prefix); break;
        case 6: fprintf(out_, "%s.signum",                prefix); break;
        case 7: fprintf(out_, "%s.reverse",               prefix); break;
        case 8: fprintf(out_, "%s.reverseBytes",          prefix); break;
        case 9:  fputs("Math.incrementExact", out_); break;
        case 10: fputs("Math.decrementExact", out_); break;
        case 11: fputs("Math.negateExact",    out_); break;
        case 12: fputs("Math.abs",            out_); break;
        case 13: fputs("Math.round", out_);
                 return tp == kLong ? kDouble : kFloat;
      }
    } else {  // isFP(tp)
      switch (random1(6)) {
        case 1: fputs("Math.abs",      out_); break;
        case 2: fputs("Math.ulp",      out_); break;
        case 3: fputs("Math.signum",   out_); break;
        case 4: fputs("Math.nextUp",   out_); break;
        case 5: fputs("Math.nextDown", out_); break;
        case 6: if (tp == kDouble) {
                  fputs("Double.longBitsToDouble", out_);
                  return kLong;
                } else {
                  fputs("Float.intBitsToFloat", out_);
                  return kInt;
                }
      }
    }
    return tp;  // same type in-out
  }

  // Emit a binary intrinsic (out type given, new suitable in type picked).
  Type emitIntrinsic2(Type tp) {
    if (tp == kBoolean) {
      switch (random1(3)) {
        case 1: fputs("Boolean.logicalAnd", out_); break;
        case 2: fputs("Boolean.logicalOr",  out_); break;
        case 3: fputs("Boolean.logicalXor", out_); break;
      }
    } else if (isInteger(tp)) {
      const char* prefix = tp == kLong ? "Long" : "Integer";
      switch (random1(11)) {
        case 1: fprintf(out_, "%s.compare", prefix); break;
        case 2: fprintf(out_, "%s.sum",     prefix); break;
        case 3: fprintf(out_, "%s.min",     prefix); break;
        case 4: fprintf(out_, "%s.max",     prefix); break;
        case 5:  fputs("Math.min",           out_); break;
        case 6:  fputs("Math.max",           out_); break;
        case 7:  fputs("Math.floorDiv",      out_); break;
        case 8:  fputs("Math.floorMod",      out_); break;
        case 9:  fputs("Math.addExact",      out_); break;
        case 10: fputs("Math.subtractExact", out_); break;
        case 11: fputs("Math.multiplyExact", out_); break;
      }
    } else {  // isFP(tp)
      const char* prefix = tp == kDouble ? "Double" : "Float";
      switch (random1(5)) {
        case 1: fprintf(out_, "%s.sum", prefix); break;
        case 2: fprintf(out_, "%s.min", prefix); break;
        case 3: fprintf(out_, "%s.max", prefix); break;
        case 4: fputs("Math.min", out_); break;
        case 5: fputs("Math.max", out_); break;
      }
    }
    return tp;  // same type in-out
  }

  // Emit an intrinsic (out type given, new suitable in type picked).
  void emitIntrinsic(Type tp) {
    if (random1(2) == 1) {
      tp = emitIntrinsic1(tp);
      fputc('(', out_);
      emitExpression(tp);
      fputc(')', out_);
    } else {
      tp = emitIntrinsic2(tp);
      fputc('(', out_);
      emitExpression(tp);
      fputs(", ", out_);
      emitExpression(tp);
      fputc(')', out_);
    }
  }

  // Emit a method call (out type given).
  void emitMethodCall(Type tp) {
    if (tp != kBoolean && !in_inner_) {
      // Accept all numerical types (implicit conversion) and when not
      // declaring inner classes (to avoid infinite recursion).
      switch (random1(8)) {
        case 1: fputs("mA.a()",  out_); break;
        case 2: fputs("mB.a()",  out_); break;
        case 3: fputs("mB.x()",  out_); break;
        case 4: fputs("mBX.x()", out_); break;
        case 5: fputs("mC.s()",  out_); break;
        case 6: fputs("mC.c()",  out_); break;
        case 7: fputs("mC.x()",  out_); break;
        case 8: fputs("mCX.x()", out_); break;
      }
    } else {
      // Fall back to intrinsic.
      emitIntrinsic(tp);
    }
  }

  // Emit unboxing boxed object.
  void emitUnbox(Type tp) {
    fputc('(', out_);
    emitType(tp);
    fputs(") new ", out_);
    emitTypeClass(tp);
    fputc('(', out_);
    emitExpression(tp);
    fputc(')', out_);
  }

  // Emit miscellaneous constructs.
  void emitMisc(Type tp) {
    if (tp == kBoolean) {
      fprintf(out_, "this instanceof %s", in_inner_ ? "X" : "Test");
    } else if (isInteger(tp)) {
      const char* prefix = tp == kLong ? "Long" : "Integer";
      switch (random1(2)) {
        case 1: fprintf(out_, "%s.MIN_VALUE", prefix); break;
        case 2: fprintf(out_, "%s.MAX_VALUE", prefix); break;
      }
    } else {  // isFP(tp)
      const char* prefix = tp == kDouble ? "Double" : "Float";
      switch (random1(6)) {
        case 1: fprintf(out_, "%s.MIN_NORMAL", prefix);        break;
        case 2: fprintf(out_, "%s.MIN_VALUE", prefix);         break;
        case 3: fprintf(out_, "%s.MAX_VALUE", prefix);         break;
        case 4: fprintf(out_, "%s.POSITIVE_INFINITY", prefix); break;
        case 5: fprintf(out_, "%s.NEGATIVE_INFINITY", prefix); break;
        case 6: fprintf(out_, "%s.NaN", prefix);               break;
      }
    }
  }

  // Adjust local of given type and return adjusted value.
  uint32_t adjustLocal(Type tp, int32_t a) {
    switch (tp) {
      case kBoolean: boolean_local_ += a; return boolean_local_;
      case kInt:     int_local_     += a; return int_local_;
      case kLong:    long_local_    += a; return long_local_;
      case kFloat:   float_local_   += a; return float_local_;
      default:       double_local_  += a; return double_local_;
    }
  }

  // Emit an expression that is a strict upper bound for an array index.
  void emitUpperBound() {
    if (random1(8) == 1) {
      fputs("mArray.length", out_);
    } else if (random1(8) == 1) {
      fprintf(out_, "%u", random1(array_size_));  // random in range
    } else {
      fprintf(out_, "%u", array_size_);
    }
  }

  // Emit an array index, usually within proper range.
  void emitArrayIndex() {
    if (loop_nest_ > 0 && random1(2) == 1) {
      fprintf(out_, "i%u", random0(loop_nest_));
    } else if (random1(8) == 1) {
      fputs("mArray.length - 1", out_);
    } else {
      fprintf(out_, "%u", random0(array_size_));  // random in range
    }
    // Introduce potential off by one errors with low probability.
    if (random1(100) == 1) {
      if (random1(2) == 1) {
        fputs(" - 1", out_);
      } else {
        fputs(" + 1", out_);
      }
    }
  }

  // Emit a literal.
  void emitLiteral(Type tp) {
    switch (tp) {
      case kBoolean: fputs(random1(2) == 1 ? "true" : "false", out_); break;
      case kInt:     fprintf(out_, "%d",    random()); break;
      case kLong:    fprintf(out_, "%dL",   random()); break;
      case kFloat:   fprintf(out_, "%d.0f", random()); break;
      case kDouble:  fprintf(out_, "%d.0",  random()); break;
    }
  }

  // Emit array variable, if available.
  bool emitArrayVariable(Type tp) {
    if (tp == array_type_) {
      fputs("mArray", out_);
      for (uint32_t i = 0; i < array_dim_; i++) {
        fputc('[', out_);
        emitArrayIndex();
        fputc(']', out_);
      }
      return true;
    }
    return false;
  }

  // Emit a local variable, if available.
  bool emitLocalVariable(Type tp) {
    uint32_t locals = adjustLocal(tp, 0);
    if (locals > 0) {
      uint32_t local = random0(locals);
      switch (tp) {
        case kBoolean: fprintf(out_, "lZ%u", local); break;
        case kInt:     fprintf(out_, "lI%u", local); break;
        case kLong:    fprintf(out_, "lJ%u", local); break;
        case kFloat:   fprintf(out_, "lF%u", local); break;
        case kDouble:  fprintf(out_, "lD%u", local); break;
      }
      return true;
    }
    return false;
  }

  // Emit a field variable.
  void emitFieldVariable(Type tp) {
    switch (tp) {
      case kBoolean:fputs("mZ", out_); break;
      case kInt:    fputs("mI", out_); break;
      case kLong:   fputs("mJ", out_); break;
      case kFloat:  fputs("mF", out_); break;
      case kDouble: fputs("mD", out_); break;
    }
  }

  // Emit a variable.
  void emitVariable(Type tp) {
    switch (random1(4)) {
      case 1:
        if (emitArrayVariable(tp))
          return;
        // FALL-THROUGH
      case 2:
        if (emitLocalVariable(tp))
          return;
        // FALL-THROUGH
      default:
        emitFieldVariable(tp);
        break;
    }
  }

  // Emit an expression.
  void emitExpression(Type tp) {
    // Continuing expression becomes less likely as the depth grows.
    if (random1(expr_depth_ + 1) > fuzz_expr_depth_) {
      if (random1(2) == 1) {
        emitLiteral(tp);
      } else {
        emitVariable(tp);
      }
      return;
    }

    expr_depth_++;

    fputc('(', out_);
    switch (random1(12)) {  // favor binary operations
      case 1:
        // Unary operator: ~ x
        emitUnaryOp(tp);
        fputc(' ', out_);
        emitExpression(tp);
        break;
      case 2:
        // Pre-increment: ++x
        emitIncDecOp(tp);
        emitVariable(tp);
        break;
      case 3:
        // Post-increment: x++
        emitVariable(tp);
        emitIncDecOp(tp);
        break;
      case 4:
        // Ternary operator: b ? x : y
        emitExpression(kBoolean);
        fputs(" ? ", out_);
        emitExpression(tp);
        fputs(" : ", out_);
        emitExpression(tp);
        break;
      case 5:
        // Type conversion: (float) x
        emitTypeConversion(tp);
        break;
      case 6:
        // Intrinsic: foo(x)
        emitIntrinsic(tp);
        break;
      case 7:
        // Method call: mA.a()
        emitMethodCall(tp);
        break;
      case 8:
        // Emit unboxing boxed value: (int) Integer(x)
        emitUnbox(tp);
        break;
      case 9:
        // Miscellaneous constructs: a.length
        emitMisc(tp);
        break;
      default:
        // Binary operator: x + y
        emitExpression(tp);
        fputc(' ', out_);
        emitBinaryOp(tp);
        fputc(' ', out_);
        emitExpression(tp);
        break;
    }
    fputc(')', out_);

    --expr_depth_;
  }

  //
  // Statements.
  //

  // Emit current indentation.
  void emitIndentation() const {
    for (uint32_t i = 0; i < indentation_; i++) {
      fputc(' ', out_);
    }
  }

  // Emit a return statement.
  bool emitReturn(bool mustEmit) {
    // Only emit when we must, or with low probability inside ifs/loops,
    // but outside do-while to avoid confusing the may follow status.
    if (mustEmit || ((if_nest_ + loop_nest_) > 0 && do_nest_ == 0 && random1(10) == 1)) {
      fputs("return ", out_);
      emitExpression(return_type_);
      fputs(";\n", out_);
      return false;
    }
    // Fall back to assignment.
    return emitAssignment();
  }

  // Emit a continue statement.
  bool emitContinue() {
    // Only emit with low probability inside loops.
    if (loop_nest_ > 0 && random1(10) == 1) {
      fputs("continue;\n", out_);
      return false;
    }
    // Fall back to assignment.
    return emitAssignment();
  }

  // Emit a break statement.
  bool emitBreak() {
    // Only emit with low probability inside loops, but outside switches
    // to avoid confusing the may follow status.
    if (loop_nest_ > 0 && switch_nest_ == 0 && random1(10) == 1) {
      fputs("break;\n", out_);
      return false;
    }
    // Fall back to assignment.
    return emitAssignment();
  }

  // Emit a new scope with a local variable declaration statement.
  bool emitScope() {
    Type tp = randomType();
    fputs("{\n", out_);
    indentation_ += 2;
    emitIndentation();
    emitType(tp);
    switch (tp) {
      case kBoolean: fprintf(out_, " lZ%u = ", boolean_local_); break;
      case kInt:     fprintf(out_, " lI%u = ", int_local_);     break;
      case kLong:    fprintf(out_, " lJ%u = ", long_local_);    break;
      case kFloat:   fprintf(out_, " lF%u = ", float_local_);   break;
      case kDouble:  fprintf(out_, " lD%u = ", double_local_);  break;
    }
    emitExpression(tp);
    fputs(";\n", out_);

    adjustLocal(tp, 1);  // local now visible

    bool mayFollow = emitStatementList();

    adjustLocal(tp, -1);  // local no longer visible

    indentation_ -= 2;
    emitIndentation();
    fputs("}\n", out_);
    return mayFollow;
  }

  // Emit a for loop.
  bool emitForLoop() {
    // Continuing loop nest becomes less likely as the depth grows.
    if (random1(loop_nest_ + 1) > fuzz_loop_nest_) {
      return emitAssignment();  // fall back
    }

    bool goesUp = random1(2) == 1;
    fprintf(out_, "for (int i%u = ", loop_nest_);
    if (goesUp) {
      fprintf(out_, "0; i%u < ", loop_nest_);
      emitUpperBound();
      fprintf(out_, "; i%u++) {\n", loop_nest_);
    } else {
      emitUpperBound();
      fprintf(out_, " - 1; i%d >= 0", loop_nest_);
      fprintf(out_, "; i%d--) {\n", loop_nest_);
    }

    ++loop_nest_;  // now in loop

    indentation_ += 2;
    emitStatementList();

    --loop_nest_;  // no longer in loop

    indentation_ -= 2;
    emitIndentation();
    fprintf(out_, "}\n");
    return true;  // loop-body does not block flow
  }

  // Emit while or do-while loop.
  bool emitDoLoop() {
    // Continuing loop nest becomes less likely as the depth grows.
    if (random1(loop_nest_ + 1) > fuzz_loop_nest_) {
      return emitAssignment();  // fall back
    }

    // TODO: remove this
    // The jack bug b/28862040 prevents generating while/do-while loops because otherwise
    // we get dozens of reports on the same issue per nightly/ run.
    if (true) {
      return emitAssignment();
    }

    bool isWhile = random1(2) == 1;
    fputs("{\n", out_);
    indentation_ += 2;
    emitIndentation();
    fprintf(out_, "int i%u = %d;", loop_nest_, isWhile ? -1 : 0);
    emitIndentation();
    if (isWhile) {
      fprintf(out_, "while (++i%u < ", loop_nest_);
      emitUpperBound();
      fputs(") {\n", out_);
    } else {
      fputs("do {\n", out_);
      do_nest_++;
    }

    ++loop_nest_;  // now in loop

    indentation_ += 2;
    emitStatementList();

    --loop_nest_;  // no longer in loop

    indentation_ -= 2;
    emitIndentation();
    if (isWhile) {
      fputs("}\n", out_);
    } else {
      fprintf(out_, "} while (++i%u < ", loop_nest_);
      emitUpperBound();
      fputs(");\n", out_);
      --do_nest_;
    }
    indentation_ -= 2;
    emitIndentation();
    fputs("}\n", out_);
    return true;  // loop-body does not block flow
  }

  // Emit an if statement.
  bool emitIfStmt() {
    // Continuing if nest becomes less likely as the depth grows.
    if (random1(if_nest_ + 1) > fuzz_if_nest_) {
      return emitAssignment();  // fall back
    }

    fputs("if (", out_);
    emitExpression(kBoolean);
    fputs(") {\n", out_);

    ++if_nest_;  // now in if

    indentation_ += 2;
    bool mayFollowTrue = emitStatementList();
    indentation_ -= 2;
    emitIndentation();
    fprintf(out_, "} else {\n");
    indentation_ += 2;
    bool mayFollowFalse = emitStatementList();

    --if_nest_;  // no longer in if

    indentation_ -= 2;
    emitIndentation();
    fprintf(out_, "}\n");
    return mayFollowTrue || mayFollowFalse;
  }

  // Emit a switch statement.
  bool emitSwitch() {
    // Continuing if nest becomes less likely as the depth grows.
    if (random1(if_nest_ + 1) > fuzz_if_nest_) {
      return emitAssignment();  // fall back
    }

    bool mayFollow = false;
    fputs("switch (", out_);
    emitArrayIndex();  // restrict its range
    fputs(") {\n", out_);

    ++if_nest_;
    ++switch_nest_;  // now in switch

    indentation_ += 2;
    for (uint32_t i = 0; i < 2; i++) {
      emitIndentation();
      if (i == 0) {
        fprintf(out_, "case %u: {\n", random0(array_size_));
      } else {
        fprintf(out_, "default: {\n");
      }
      indentation_ += 2;
      if (emitStatementList()) {
        // Must end with break.
        emitIndentation();
        fputs("break;\n", out_);
        mayFollow = true;
      }
      indentation_ -= 2;
      emitIndentation();
      fputs("}\n", out_);
    }

    --if_nest_;
    --switch_nest_;  // no longer in switch

    indentation_ -= 2;
    emitIndentation();
    fprintf(out_, "}\n");
    return mayFollow;
  }

  // Emit an assignment statement.
  bool emitAssignment() {
    Type tp = randomType();
    emitVariable(tp);
    fputc(' ', out_);
    emitAssignmentOp(tp);
    fputc(' ', out_);
    emitExpression(tp);
    fputs(";\n", out_);
    return true;
  }

  // Emit a single statement. Returns true if statements may follow.
  bool emitStatement() {
    switch (random1(16)) {  // favor assignments
      case 1:  return emitReturn(false); break;
      case 2:  return emitContinue();    break;
      case 3:  return emitBreak();       break;
      case 4:  return emitScope();       break;
      case 5:  return emitForLoop();     break;
      case 6:  return emitDoLoop();      break;
      case 7:  return emitIfStmt();      break;
      case 8:  return emitSwitch();      break;
      default: return emitAssignment();  break;
    }
  }

  // Emit a statement list. Returns true if statements may follow.
  bool emitStatementList() {
    while (stmt_length_ < 1000) {  // avoid run-away
      stmt_length_++;
      emitIndentation();
      if (!emitStatement()) {
        return false;  // rest would be dead code
      }
      // Continuing this list becomes less likely as the total statement list grows.
      if (random1(stmt_length_) > fuzz_stmt_length_) {
        break;
      }
    }
    return true;
  }

  // Emit interface and class declarations.
  void emitClassDecls() {
    in_inner_ = true;
    fputs("  private interface X {\n", out_);
    fputs("    int x();\n", out_);
    fputs("  }\n\n", out_);
    fputs("  private class A {\n", out_);
    fputs("    public int a() {\n", out_);
    fputs("      return ", out_);
    emitExpression(kInt);
    fputs(";\n    }\n", out_);
    fputs("  }\n\n", out_);
    fputs("  private class B extends A implements X {\n", out_);
    fputs("    public int a() {\n", out_);
    fputs("      return super.a() + ", out_);
    emitExpression(kInt);
    fputs(";\n    }\n", out_);
    fputs("    public int x() {\n", out_);
    fputs("      return ", out_);
    emitExpression(kInt);
    fputs(";\n    }\n", out_);
    fputs("  }\n\n", out_);
    fputs("  private static class C implements X {\n", out_);
    fputs("    public static int s() {\n", out_);
    fputs("      return ", out_);
    emitLiteral(kInt);
    fputs(";\n    }\n", out_);
    fputs("    public int c() {\n", out_);
    fputs("      return ", out_);
    emitLiteral(kInt);
    fputs(";\n    }\n", out_);
    fputs("    public int x() {\n", out_);
    fputs("      return ", out_);
    emitLiteral(kInt);
    fputs(";\n    }\n", out_);
    fputs("  }\n\n", out_);
    in_inner_ = false;
  }

  // Emit field declarations.
  void emitFieldDecls() {
    fputs("  private A mA  = new B();\n", out_);
    fputs("  private B mB  = new B();\n", out_);
    fputs("  private X mBX = new B();\n", out_);
    fputs("  private C mC  = new C();\n", out_);
    fputs("  private X mCX = new C();\n\n", out_);
    fputs("  private boolean mZ = false;\n", out_);
    fputs("  private int     mI = 0;\n", out_);
    fputs("  private long    mJ = 0;\n", out_);
    fputs("  private float   mF = 0;\n", out_);
    fputs("  private double  mD = 0;\n\n", out_);
  }

  // Emit array declaration.
  void emitArrayDecl() {
    fputs("  private ", out_);
    emitType(array_type_);
    for (uint32_t i = 0; i < array_dim_; i++) {
      fputs("[]", out_);
    }
    fputs(" mArray = new ", out_);
    emitType(array_type_);
    for (uint32_t i = 0; i < array_dim_; i++) {
      fprintf(out_, "[%d]", array_size_);
    }
    fputs(";\n\n", out_);
  }

  // Emit test constructor.
  void emitTestConstructor() {
    fputs("  private Test() {\n", out_);
    indentation_ += 2;
    emitIndentation();
    emitType(array_type_);
    fputs(" a = ", out_);
    emitLiteral(array_type_);
    fputs(";\n", out_);
    for (uint32_t i = 0; i < array_dim_; i++) {
      emitIndentation();
      fprintf(out_, "for (int i%u = 0; i%u < %u; i%u++) {\n", i, i, array_size_, i);
      indentation_ += 2;
    }
    emitIndentation();
    fputs("mArray", out_);
    for (uint32_t i = 0; i < array_dim_; i++) {
      fprintf(out_, "[i%u]", i);
    }
    fputs(" = a;\n", out_);
    emitIndentation();
    if (array_type_ == kBoolean) {
      fputs("a = !a;\n", out_);
    } else {
      fputs("a++;\n", out_);
    }
    for (uint32_t i = 0; i < array_dim_; i++) {
      indentation_ -= 2;
      emitIndentation();
      fputs("}\n", out_);
    }
    indentation_ -= 2;
    fputs("  }\n\n", out_);
  }

  // Emit test method.
  void emitTestMethod() {
    fputs("  private ", out_);
    emitType(return_type_);
    fputs(" testMethod() {\n", out_);
    indentation_ += 2;
    if (emitStatementList()) {
      // Must end with return.
      emitIndentation();
      emitReturn(true);
    }
    indentation_ -= 2;
    fputs("  }\n\n", out_);
  }

  // Emit main method driver.
  void emitMainMethod() {
    fputs("  public static void main(String[] args) {\n", out_);
    indentation_ += 2;
    fputs("    Test t = new Test();\n    ", out_);
    emitType(return_type_);
    fputs(" r = ", out_);
    emitLiteral(return_type_);
    fputs(";\n", out_);
    fputs("    try {\n", out_);
    fputs("      r = t.testMethod();\n", out_);
    fputs("    } catch (Exception e) {\n", out_);
    fputs("      // Arithmetic, null pointer, index out of bounds, etc.\n", out_);
    fputs("      System.out.println(\"An exception was caught.\");\n", out_);
    fputs("    }\n", out_);
    fputs("    System.out.println(\"r  = \" + r);\n",    out_);
    fputs("    System.out.println(\"mZ = \" + t.mZ);\n", out_);
    fputs("    System.out.println(\"mI = \" + t.mI);\n", out_);
    fputs("    System.out.println(\"mJ = \" + t.mJ);\n", out_);
    fputs("    System.out.println(\"mF = \" + t.mF);\n", out_);
    fputs("    System.out.println(\"mD = \" + t.mD);\n", out_);
    fputs("    System.out.println(\"mArray = \" + ", out_);
    if (array_dim_ == 1) {
      fputs("Arrays.toString(t.mArray)", out_);
    } else {
      fputs("Arrays.deepToString(t.mArray)", out_);
    }
    fputs(");\n", out_);
    indentation_ -= 2;
    fputs("  }\n", out_);
  }

  // Emit program header. Emit command line options in the comments.
  void emitHeader() {
    fputs("\n/**\n * AOSP JFuzz Tester.\n", out_);
    fputs(" * Automatically generated program.\n", out_);
    fprintf(out_,
            " * jfuzz -s %u -d %u -l %u -i %u -n %u (version %s)\n */\n\n",
            fuzz_seed_,
            fuzz_expr_depth_,
            fuzz_stmt_length_,
            fuzz_if_nest_,
            fuzz_loop_nest_,
            VERSION);
    fputs("import java.util.Arrays;\n\n", out_);
  }

  // Emit single test class with main driver.
  void emitTestClassWithMain() {
    fputs("public class Test {\n\n", out_);
    indentation_ += 2;
    emitClassDecls();
    emitFieldDecls();
    emitArrayDecl();
    emitTestConstructor();
    emitTestMethod();
    emitMainMethod();
    indentation_ -= 2;
    fputs("}\n\n", out_);
  }

  //
  // Random integers.
  //

  // Return random integer.
  int32_t random() {
    return fuzz_random_engine_();
  }

  // Return random integer in range [0,max).
  uint32_t random0(uint32_t max) {
    std::uniform_int_distribution<uint32_t> gen(0, max - 1);
    return gen(fuzz_random_engine_);
  }

  // Return random integer in range [1,max].
  uint32_t random1(uint32_t max) {
    std::uniform_int_distribution<uint32_t> gen(1, max);
    return gen(fuzz_random_engine_);
  }

  // Fuzzing parameters.
  FILE* out_;
  std::mt19937 fuzz_random_engine_;
  const uint32_t fuzz_seed_;
  const uint32_t fuzz_expr_depth_;
  const uint32_t fuzz_stmt_length_;
  const uint32_t fuzz_if_nest_;
  const uint32_t fuzz_loop_nest_;

  // Return and array setup.
  const Type return_type_;
  const Type array_type_;
  const uint32_t array_dim_;
  const uint32_t array_size_;

  // Current context.
  uint32_t indentation_;
  uint32_t expr_depth_;
  uint32_t stmt_length_;
  uint32_t if_nest_;
  uint32_t loop_nest_;
  uint32_t switch_nest_;
  uint32_t do_nest_;
  uint32_t boolean_local_;
  uint32_t int_local_;
  uint32_t long_local_;
  uint32_t float_local_;
  uint32_t double_local_;
  bool in_inner_;
};

}  // anonymous namespace

int32_t main(int32_t argc, char** argv) {
  // Time-based seed.
  struct timeval tp;
  gettimeofday(&tp, NULL);

  // Defaults.
  uint32_t seed = (tp.tv_sec * 1000000 + tp.tv_usec);
  uint32_t expr_depth = 1;
  uint32_t stmt_length = 8;
  uint32_t if_nest = 2;
  uint32_t loop_nest = 3;

  // Parse options.
  while (1) {
    int32_t option = getopt(argc, argv, "s:d:l:i:n:vh");
    if (option < 0) {
      break;  // done
    }
    switch (option) {
      case 's':
        seed = strtoul(optarg, nullptr, 0);  // deterministic seed
        break;
      case 'd':
        expr_depth = strtoul(optarg, nullptr, 0);
        break;
      case 'l':
        stmt_length = strtoul(optarg, nullptr, 0);
        break;
      case 'i':
        if_nest = strtoul(optarg, nullptr, 0);
        break;
      case 'n':
        loop_nest = strtoul(optarg, nullptr, 0);
        break;
      case 'v':
        fprintf(stderr, "jfuzz version %s\n", VERSION);
        return 0;
      case 'h':
      default:
        fprintf(stderr,
                "usage: %s [-s seed] "
                "[-d expr-depth] [-l stmt-length] "
                "[-i if-nest] [-n loop-nest] [-v] [-h]\n",
                argv[0]);
        return 1;
    }
  }

  // Seed global random generator.
  srand(seed);

  // Generate fuzzed program.
  JFuzz fuzz(stdout, seed, expr_depth, stmt_length, if_nest, loop_nest);
  fuzz.emitProgram();
  return 0;
}
