[dart/fuzzer] Further development DartFuzz

Rationale:
Adds a few more constructs, avoid situations that go out-of-memory
too easily, prepares library method format for automatic generation
of library tables.

https://github.com/dart-lang/sdk/issues/35406

Change-Id: I24b4656ea073fbe41175aa08952588e28d4363a1
Reviewed-on: https://dart-review.googlesource.com/c/90320
Reviewed-by: Alexander Thomas <athom@google.com>
Commit-Queue: Aart Bik <ajcbik@google.com>
diff --git a/runtime/tools/dartfuzz/dartfuzz.dart b/runtime/tools/dartfuzz/dartfuzz.dart
index 0ad5858..9843383 100644
--- a/runtime/tools/dartfuzz/dartfuzz.dart
+++ b/runtime/tools/dartfuzz/dartfuzz.dart
@@ -12,7 +12,7 @@
 // Version of DartFuzz. Increase this each time changes are made
 // to preserve the property that a given version of DartFuzz yields
 // the same fuzzed program for a deterministic random seed.
-const String version = '1.2';
+const String version = '1.3';
 
 // Restriction on statement and expression depths.
 const int stmtDepth = 5;
@@ -160,6 +160,33 @@
   }
 
   //
+  // Comments (for FE and analysis tools).
+  //
+
+  void emitComment() {
+    switch (rand.nextInt(4)) {
+      case 0:
+        emitLn('// Single-line comment.');
+        break;
+      case 1:
+        emitLn('/// Single-line documentation comment.');
+        break;
+      case 2:
+        emitLn('/*');
+        emitLn(' * Multi-line');
+        emitLn(' * comment.');
+        emitLn(' */');
+        break;
+      default:
+        emitLn('/**');
+        emitLn(' ** Multi-line');
+        emitLn(' ** documentation comment.');
+        emitLn(' */');
+        break;
+    }
+  }
+
+  //
   // Statements.
   //
 
@@ -167,7 +194,7 @@
   bool emitAssign() {
     DartType tp = getType();
     emitLn('', newline: false);
-    emitVar(tp);
+    emitVar(0, tp);
     emitAssignOp(tp);
     emitExpr(0, tp);
     emit(';', newline: true);
@@ -202,10 +229,10 @@
     emitExpr(0, DartType.BOOL);
     emit(') {', newline: true);
     indent += 2;
-    bool b = emitStatements(depth + 1);
+    emitStatements(depth + 1);
     indent -= 2;
     emitLn('}');
-    return b;
+    return true;
   }
 
   // Emit a two-way if statement.
@@ -257,13 +284,17 @@
   // Emit a statement. Returns true if code may fall-through.
   // TODO: add many more constructs
   bool emitStatement(int depth) {
+    // Throw in a comment every once in a while.
+    if (rand.nextInt(10) == 0) {
+      emitComment();
+    }
     // Continuing nested statements becomes less likely as the depth grows.
     if (rand.nextInt(depth + 1) > stmtDepth) {
       return emitAssign();
     }
     // Possibly nested statement.
     switch (rand.nextInt(8)) {
-      // favors assignment
+      // Favors assignment.
       case 0:
         return emitIf1(depth);
       case 1:
@@ -310,10 +341,9 @@
 
   void emitInt() {
     switch (rand.nextInt(4)) {
-      // favors small positive int
+      // Favors small positive int.
       case 0:
-        emit(
-            '${DartFuzzValues.interestingIntegers[rand.nextInt(DartFuzzValues.interestingIntegers.length)]}');
+        emit('${oneOf(DartFuzzValues.interestingIntegers)}');
         break;
       case 1:
         emitSmallNegativeInt();
@@ -325,22 +355,10 @@
   }
 
   void emitDouble() {
-    switch (rand.nextInt(7)) {
-      // favors small double
+    switch (rand.nextInt(10)) {
+      // Favors regular double.
       case 0:
-        emit('double.infinity');
-        break;
-      case 1:
-        emit('double.maxFinite');
-        break;
-      case 2:
-        emit('double.minPositive');
-        break;
-      case 3:
-        emit('double.nan');
-        break;
-      case 4:
-        emit('double.negativeInfinity');
+        emit(oneOf(DartFuzzValues.interestingDoubles));
         break;
       default:
         emit('${rand.nextDouble()}');
@@ -350,15 +368,12 @@
 
   void emitChar() {
     switch (rand.nextInt(10)) {
-      // favors regular char
+      // Favors regular char.
       case 0:
-        emit('\\u2665');
-        break;
-      case 1:
-        emit('\\u{1f600}'); // rune
+        emit(oneOf(DartFuzzValues.interestingChars));
         break;
       default:
-        emit(DartFuzzValues.interestingChars[
+        emit(DartFuzzValues.regularChars[
             rand.nextInt(DartFuzzValues.interestingChars.length)]);
         break;
     }
@@ -442,18 +457,40 @@
     emit('${choices[rand.nextInt(choices.length)]}');
   }
 
-  void emitVar(DartType tp) {
-    // TODO: add subscripted var
-    emitScalarVar(tp);
+  void emitSubscriptedVar(int depth, DartType tp) {
+    if (tp == DartType.INT) {
+      emitScalarVar(DartType.INT_LIST);
+      emit('[');
+      emitExpr(depth + 1, DartType.INT);
+      emit(']');
+    } else if (tp == DartType.STRING) {
+      emitScalarVar(DartType.INT_STRING_MAP);
+      emit('[');
+      emitExpr(depth + 1, DartType.INT);
+      emit(']');
+    } else {
+      emitScalarVar(tp); // resort to scalar
+    }
   }
 
-  void emitTerminal(DartType tp) {
+  void emitVar(int depth, DartType tp) {
+    switch (rand.nextInt(2)) {
+      case 0:
+        emitScalarVar(tp);
+        break;
+      default:
+        emitSubscriptedVar(depth, tp);
+        break;
+    }
+  }
+
+  void emitTerminal(int depth, DartType tp) {
     switch (rand.nextInt(2)) {
       case 0:
         emitLiteral(tp);
         break;
       default:
-        emitVar(tp);
+        emitVar(depth, tp);
         break;
     }
   }
@@ -478,7 +515,7 @@
       emitExpr(depth + 1, tp);
       emit('))');
     } else {
-      emitTerminal(tp); // resort to terminal
+      emitTerminal(depth, tp); // resort to terminal
     }
   }
 
@@ -495,6 +532,16 @@
         emit(')');
         return;
       }
+    } else if (tp == DartType.STRING || tp == DartType.INT_LIST) {
+      // For strings and lists, a construct like x = x + x; inside a loop
+      // yields an exponentially growing data structure. We avoid this
+      // situation by forcing a literal on the rhs of each +.
+      emit('(');
+      emitExpr(depth + 1, tp);
+      emitBinaryOp(tp);
+      emitLiteral(tp);
+      emit(')');
+      return;
     }
     emit('(');
     emitExpr(depth + 1, tp);
@@ -524,32 +571,34 @@
       if (r == 1) emitPreOrPostOp(tp);
       emit(')');
     } else {
-      emitTerminal(tp); // resort to terminal
+      emitTerminal(depth, tp); // resort to terminal
     }
   }
 
   // Emit library call.
   void emitLibraryCall(int depth, DartType tp) {
-    if (tp == DartType.INT_STRING_MAP) {
-      emitTerminal(tp); // resort to terminal
-      return;
-    }
     DartLib lib = getLibraryMethod(tp);
-    List<DartType> proto = lib.proto;
+    String proto = lib.proto;
     // Receiver.
-    if (proto[0] != null) {
-      DartType deeper_tp = proto[0];
+    if (proto[0] != 'V') {
       emit('(');
-      emitExpr(depth + 1, deeper_tp);
+      emitArg(depth + 1, proto[0]);
       emit(').');
     }
     // Call.
     emit('${lib.name}');
     // Parameters.
-    if (proto.length == 1) {
-      emit('()');
-    } else if (proto[1] != null) {
-      emitExprList(depth + 1, proto);
+    if (proto[1] != 'v') {
+      emit('(');
+      if (proto[1] != 'V') {
+        for (int i = 1; i < proto.length; i++) {
+          emitArg(depth + 1, proto[i]);
+          if (i != (proto.length - 1)) {
+            emit(', ');
+          }
+        }
+      }
+      emit(')');
     }
   }
 
@@ -586,14 +635,14 @@
         return;
       }
     }
-    emitTerminal(tp); // resort to terminal.
+    emitTerminal(depth, tp); // resort to terminal.
   }
 
   // Emit expression.
   void emitExpr(int depth, DartType tp) {
     // Continuing nested expressions becomes less likely as the depth grows.
     if (rand.nextInt(depth + 1) > exprDepth) {
-      emitTerminal(tp);
+      emitTerminal(depth, tp);
       return;
     }
     // Possibly nested expression.
@@ -617,7 +666,7 @@
         emitMethodCall(depth, tp);
         break;
       default:
-        emitTerminal(tp);
+        emitTerminal(depth, tp);
         break;
     }
   }
@@ -725,11 +774,42 @@
       return oneOf(DartLib.stringLibs);
     } else if (tp == DartType.INT_LIST) {
       return oneOf(DartLib.intListLibs);
+    } else if (tp == DartType.INT_STRING_MAP) {
+      return oneOf(DartLib.intStringMapLibs);
     } else {
       assert(false);
     }
   }
 
+  // Emit a library argument, possibly subject to restrictions.
+  void emitArg(int depth, String p) {
+    switch (p) {
+      case 'B':
+        emitExpr(depth, DartType.BOOL);
+        break;
+      case 'i':
+        emitSmallPositiveInt();
+        break;
+      case 'I':
+        emitExpr(depth, DartType.INT);
+        break;
+      case 'D':
+        emitExpr(depth, DartType.DOUBLE);
+        break;
+      case 'S':
+        emitExpr(depth, DartType.STRING);
+        break;
+      case 'L':
+        emitExpr(depth, DartType.INT_LIST);
+        break;
+      case 'M':
+        emitExpr(depth, DartType.INT_STRING_MAP);
+        break;
+      default:
+        assert(false);
+    }
+  }
+
   //
   // Types.
   //
diff --git a/runtime/tools/dartfuzz/dartfuzz_values.dart b/runtime/tools/dartfuzz/dartfuzz_values.dart
index e19e301..c97249d 100644
--- a/runtime/tools/dartfuzz/dartfuzz_values.dart
+++ b/runtime/tools/dartfuzz/dartfuzz_values.dart
@@ -26,9 +26,24 @@
 /// Class with interesting values for fuzzing.
 class DartFuzzValues {
   // Interesting characters.
-  static const interestingChars =
+  static const List<String> interestingChars = [
+    '\\u2665',
+    '\\u{1f600}', // rune
+  ];
+
+  // Regular characters.
+  static const regularChars =
       'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#&()+- ';
 
+  // Interesting doubles.
+  static const interestingDoubles = [
+    'double.infinity',
+    'double.maxFinite',
+    'double.minPositive',
+    'double.nan',
+    'double.negativeInfinity',
+  ];
+
   // Interesting integer values.
   static const List<int> interestingIntegers = [
     0x0000000000000000,
@@ -71,97 +86,111 @@
 }
 
 /// Class that represents Dart library methods.
+//
 /// The invididual lists are organized by return type.
-/// Proto list:
-///   [ receiver-type (null denotes none),
-///     param1 type (null denotes getter),
-///     param2 type,
-///     ...
-///   ]
+/// The proto string has the following format:
+///    +-------> receiver type (V denotes none)
+///    |+------> param1 type  (V denotes none, v denotes getter)
+///    ||+-----> param2 type
+///    |||+----> ....
+///    ||||
+///   "TTTT...."
+/// where:
+///   V void
+///   v void (special)
+///   B bool
+///   I int
+///   i int (small)
+///   D double
+///   S String
+///   L List<int>
+///   M Map<int, String>
 ///
 /// TODO(ajcbik): generate these lists automatically
 ///
 class DartLib {
   final String name;
-  final List<DartType> proto;
+  final String proto;
   const DartLib(this.name, this.proto);
 
   static const boolLibs = [
-    DartLib('isEven', [DartType.INT, null]),
-    DartLib('isOdd', [DartType.INT, null]),
-    DartLib('isEmpty', [DartType.STRING, null]),
-    DartLib('isEmpty', [DartType.INT_STRING_MAP, null]),
-    DartLib('isNotEmpty', [DartType.STRING, null]),
-    DartLib('isNotEmpty', [DartType.INT_STRING_MAP, null]),
-    DartLib('endsWith', [DartType.STRING, DartType.STRING]),
-    DartLib('remove', [DartType.INT_LIST, DartType.INT]),
-    DartLib('containsValue', [DartType.INT_STRING_MAP, DartType.STRING]),
-    DartLib('containsKey', [DartType.INT_STRING_MAP, DartType.INT]),
+    DartLib('isEven', "Iv"),
+    DartLib('isOdd', "Iv"),
+    DartLib('isEmpty', "Sv"),
+    DartLib('isEmpty', "Mv"),
+    DartLib('isNotEmpty', "Sv"),
+    DartLib('isNotEmpty', "Mv"),
+    DartLib('endsWith', "SS"),
+    DartLib('remove', "LI"),
+    DartLib('containsValue', "MS"),
+    DartLib('containsKey', "MI"),
   ];
 
   static const intLibs = [
-    DartLib('bitLength', [DartType.INT, null]),
-    DartLib('sign', [DartType.INT, null]),
-    DartLib('abs', [DartType.INT]),
-    DartLib('round', [DartType.INT]),
-    DartLib('round', [DartType.DOUBLE]),
-    DartLib('floor', [DartType.INT]),
-    DartLib('floor', [DartType.DOUBLE]),
-    DartLib('ceil', [DartType.INT]),
-    DartLib('ceil', [DartType.DOUBLE]),
-    DartLib('truncate', [DartType.INT]),
-    DartLib('truncate', [DartType.DOUBLE]),
-    DartLib('toInt', [DartType.DOUBLE]),
-    DartLib('toUnsigned', [DartType.INT, DartType.INT]),
-    DartLib('toSigned', [DartType.INT, DartType.INT]),
-    DartLib('modInverse', [DartType.INT, DartType.INT]),
-    DartLib('modPow', [DartType.INT, DartType.INT, DartType.INT]),
-    DartLib('length', [DartType.STRING, null]),
-    DartLib('length', [DartType.INT_LIST, null]),
-    DartLib('length', [DartType.INT_STRING_MAP, null]),
-    DartLib('codeUnitAt', [DartType.STRING, DartType.INT]),
-    DartLib('compareTo', [DartType.STRING, DartType.STRING]),
-    DartLib('removeLast', [DartType.INT_LIST]),
-    DartLib('removeAt', [DartType.INT_LIST, DartType.INT]),
-    DartLib('indexOf', [DartType.INT_LIST, DartType.INT]),
-    DartLib('lastIndexOf', [DartType.INT_LIST, DartType.INT]),
+    DartLib('bitLength', "Iv"),
+    DartLib('sign', "Iv"),
+    DartLib('abs', "IV"),
+    DartLib('round', "IV"),
+    DartLib('round', "DV"),
+    DartLib('floor', "IV"),
+    DartLib('floor', "DV"),
+    DartLib('ceil', "IV"),
+    DartLib('ceil', "DV"),
+    DartLib('truncate', "IV"),
+    DartLib('truncate', "DV"),
+    DartLib('toInt', "DV"),
+    DartLib('toUnsigned', "II"),
+    DartLib('toSigned', "II"),
+    DartLib('modInverse', "II"),
+    DartLib('modPow', "III"),
+    DartLib('length', "Sv"),
+    DartLib('length', "Lv"),
+    DartLib('length', "Mv"),
+    DartLib('codeUnitAt', "SI"),
+    DartLib('compareTo', "SS"),
+    DartLib('removeLast', "LV"),
+    DartLib('removeAt', "LI"),
+    DartLib('indexOf', "LI"),
+    DartLib('lastIndexOf', "LI"),
   ];
 
   static const doubleLibs = [
-    DartLib('sign', [DartType.DOUBLE, null]),
-    DartLib('abs', [DartType.DOUBLE]),
-    DartLib('toDouble', [DartType.INT]),
-    DartLib('roundToDouble', [DartType.INT]),
-    DartLib('roundToDouble', [DartType.DOUBLE]),
-    DartLib('floorToDouble', [DartType.INT]),
-    DartLib('floorToDouble', [DartType.DOUBLE]),
-    DartLib('ceilToDouble', [DartType.INT]),
-    DartLib('ceilToDouble', [DartType.DOUBLE]),
-    DartLib('truncateToDouble', [DartType.INT]),
-    DartLib('truncateToDouble', [DartType.DOUBLE]),
-    DartLib('remainder', [DartType.DOUBLE, DartType.DOUBLE]),
+    DartLib('sign', "Dv"),
+    DartLib('abs', "DV"),
+    DartLib('toDouble', "IV"),
+    DartLib('roundToDouble', "IV"),
+    DartLib('roundToDouble', "DV"),
+    DartLib('floorToDouble', "IV"),
+    DartLib('floorToDouble', "DV"),
+    DartLib('ceilToDouble', "IV"),
+    DartLib('ceilToDouble', "DV"),
+    DartLib('truncateToDouble', "IV"),
+    DartLib('truncateToDouble', "DV"),
+    DartLib('remainder', "DD"),
   ];
 
   static const stringLibs = [
-    DartLib('toString', [DartType.BOOL]),
-    DartLib('toString', [DartType.INT]),
-    DartLib('toString', [DartType.DOUBLE]),
-    DartLib('toRadixString', [DartType.INT, DartType.INT]),
-    DartLib('trim', [DartType.STRING]),
-    DartLib('trimLeft', [DartType.STRING]),
-    DartLib('trimRight', [DartType.STRING]),
-    DartLib('toLowerCase', [DartType.STRING]),
-    DartLib('toUpperCase', [DartType.STRING]),
-    DartLib('substring', [DartType.STRING, DartType.INT]),
-    DartLib('replaceRange',
-        [DartType.STRING, DartType.INT, DartType.INT, DartType.STRING]),
-    DartLib('remove', [DartType.INT_STRING_MAP, DartType.INT]),
-    // Avoid (OOM divergences, TODO(ajcbik): restrict parameters)
-    // DartLib('padLeft', [DartType.STRING, DartType.INT]),
-    // DartLib('padRight', [DartType.STRING, DartType.INT]),
+    DartLib('toString', "BV"),
+    DartLib('toString', "IV"),
+    DartLib('toString', "DV"),
+    DartLib('toRadixString', "II"),
+    DartLib('trim', "SV"),
+    DartLib('trimLeft', "SV"),
+    DartLib('trimRight', "SV"),
+    DartLib('toLowerCase', "SV"),
+    DartLib('toUpperCase', "SV"),
+    DartLib('substring', "SI"),
+    DartLib('replaceRange', "SIIS"),
+    DartLib('remove', "MI"),
+    DartLib('padLeft', "Si"), // restrict!
+    DartLib('padRight', "Si"), // restrict!
   ];
 
   static const intListLibs = [
-    DartLib('sublist', [DartType.INT_LIST, DartType.INT])
+    DartLib('sublist', "LI"),
+  ];
+
+  static const intStringMapLibs = [
+    DartLib('Map.from', "VM"),
   ];
 }