| // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:io'; |
| import 'dart:math'; |
| |
| import 'package:args/args.dart'; |
| |
| import 'dartfuzz_api_table.dart'; |
| import 'dartfuzz_ffi_api.dart'; |
| import 'dartfuzz_type_table.dart'; |
| |
| // 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.57'; |
| |
| // Restriction on statements and expressions. |
| const int stmtDepth = 1; |
| const int exprDepth = 2; |
| const int numStatements = 2; |
| const int numGlobalVars = 4; |
| const int numLocalVars = 4; |
| const int numGlobalMethods = 4; |
| const int numMethodParams = 4; |
| const int numClasses = 4; |
| |
| // Naming conventions. |
| const varName = 'var'; |
| const paramName = 'par'; |
| const localName = 'loc'; |
| const fieldName = 'fld'; |
| const methodName = 'foo'; |
| |
| // Class that tracks the state of the filter applied to the |
| // right-hand-side of an assignment in order to avoid generating |
| // left-hand-side variables. |
| class RhsFilter { |
| RhsFilter(this._remaining, this.lhsVar); |
| factory RhsFilter.fromDartType(DartType tp, String lhsVar) { |
| if (DartType.isGrowableType(tp)) { |
| return RhsFilter(1, lhsVar); |
| } |
| return null; |
| } |
| // Clone the current RhsFilter instance and set remaining to 0. |
| // This is used for parameter expressions. |
| factory RhsFilter.cloneEmpty(RhsFilter rhsFilter) => |
| rhsFilter == null ? null : RhsFilter(0, rhsFilter.lhsVar); |
| void consume() => _remaining--; |
| bool get shouldFilter => _remaining <= 0; |
| // Number of times the lhs variable can still be used on the rhs. |
| int _remaining; |
| // The name of the lhs variable to be filtered from the rhs. |
| final String lhsVar; |
| } |
| |
| /// Class that specifies the api for calling library and ffi functions (if |
| /// enabled). |
| class DartApi { |
| DartApi(bool ffi) |
| : intLibs = [ |
| if (ffi) ...[ |
| DartLib('intComputation', 'VIIII'), |
| DartLib('takeMaxUint16', 'VI'), |
| DartLib('sumPlus42', 'VII'), |
| DartLib('returnMaxUint8', 'VV'), |
| DartLib('returnMaxUint16', 'VV'), |
| DartLib('returnMaxUint32', 'VV'), |
| DartLib('returnMinInt8', 'VV'), |
| DartLib('returnMinInt16', 'VV'), |
| DartLib('returnMinInt32', 'VV'), |
| DartLib('takeMinInt16', 'VI'), |
| DartLib('takeMinInt32', 'VI'), |
| DartLib('uintComputation', 'VIIII'), |
| DartLib('sumSmallNumbers', 'VIIIIII'), |
| DartLib('takeMinInt8', 'VI'), |
| DartLib('takeMaxUint32', 'VI'), |
| DartLib('takeMaxUint8', 'VI'), |
| DartLib('minInt64', 'VV'), |
| DartLib('minInt32', 'VV'), |
| // Use small int to avoid overflow divergences due to size |
| // differences in intptr_t on 32-bit and 64-bit platforms. |
| DartLib('sumManyIntsOdd', 'Viiiiiiiiiii'), |
| DartLib('sumManyInts', 'Viiiiiiiiii'), |
| DartLib('regress37069', 'Viiiiiiiiiii'), |
| ], |
| ...DartLib.intLibs, |
| ], |
| doubleLibs = [ |
| if (ffi) ...[ |
| DartLib('times1_337Float', 'VD'), |
| DartLib('sumManyDoubles', 'VDDDDDDDDDD'), |
| DartLib('times1_337Double', 'VD'), |
| DartLib('sumManyNumbers', 'VIDIDIDIDIDIDIDIDIDID'), |
| DartLib('inventFloatValue', 'VV'), |
| DartLib('smallDouble', 'VV'), |
| ], |
| ...DartLib.doubleLibs, |
| ]; |
| |
| final boolLibs = DartLib.boolLibs; |
| final stringLibs = DartLib.stringLibs; |
| final listLibs = DartLib.listLibs; |
| final setLibs = DartLib.setLibs; |
| final mapLibs = DartLib.mapLibs; |
| final List<DartLib> intLibs; |
| final List<DartLib> doubleLibs; |
| } |
| |
| /// Class that generates a random, but runnable Dart program for fuzz testing. |
| class DartFuzz { |
| DartFuzz(this.seed, this.fp, this.ffi, this.flatTp, this.file, |
| {this.minimize = false, this.smask, this.emask}); |
| |
| void run() { |
| // Initialize program variables. |
| rand = Random(seed); |
| indent = 0; |
| nest = 0; |
| currentClass = null; |
| currentMethod = null; |
| // Setup Dart types. |
| dartType = DartType.fromDartConfig(enableFp: fp, disableNesting: flatTp); |
| // Setup minimization parameters. |
| initMinimization(); |
| // Setup the library and ffi api. |
| api = DartApi(ffi); |
| // Setup the types. |
| localVars = <DartType>[]; |
| iterVars = <String>[]; |
| globalVars = fillTypes1(limit: numGlobalVars); |
| globalVars.addAll(dartType.allTypes); |
| globalMethods = |
| fillTypes2(limit2: numGlobalMethods, limit1: numMethodParams); |
| classFields = fillTypes2(limit2: numClasses, limit1: numLocalVars); |
| final int numClassMethods = 1 + numClasses - classFields.length; |
| classMethods = fillTypes3(classFields.length, |
| limit2: numClassMethods, limit1: numMethodParams); |
| virtualClassMethods = <Map<int, List<int>>>[]; |
| classParents = <int>[]; |
| // Setup optional ffi methods and types. |
| final ffiStatus = <bool>[for (final _ in globalMethods) false]; |
| if (ffi) { |
| List<List<DartType>> globalMethodsFfi = fillTypes2( |
| limit2: numGlobalMethods, limit1: numMethodParams, isFfi: true); |
| for (var m in globalMethodsFfi) { |
| globalMethods.add(m); |
| ffiStatus.add(true); |
| } |
| } |
| // Generate. |
| emitHeader(); |
| emitVarDecls(varName, globalVars); |
| emitMethods(methodName, globalMethods, ffiStatus); |
| emitClasses(); |
| emitMain(); |
| // Sanity. |
| assert(currentClass == null); |
| assert(currentMethod == null); |
| assert(indent == 0); |
| assert(nest == 0); |
| assert(localVars.isEmpty); |
| } |
| |
| // Randomly specialize interface if possible. E.g. num to int. |
| DartType maybeSpecializeInterface(DartType tp) { |
| if (!dartType.isSpecializable(tp)) return tp; |
| DartType resolvedTp = oneOfSet(dartType.interfaces(tp)); |
| return resolvedTp; |
| } |
| |
| // |
| // Minimization components. |
| // |
| |
| void initMinimization() { |
| stmtCntr = 0; |
| exprCntr = 0; |
| skipStmt = false; |
| skipExpr = false; |
| } |
| |
| void emitMinimizedLiteral(DartType tp) { |
| switch (tp) { |
| case DartType.BOOL: |
| emit('true'); |
| break; |
| case DartType.INT: |
| emit('1'); |
| break; |
| case DartType.DOUBLE: |
| emit('1.0'); |
| break; |
| case DartType.STRING: |
| emit('"a"'); |
| break; |
| case DartType.LIST_INT: |
| emit('[1]'); |
| break; |
| case DartType.SET_INT: |
| emit('{1}'); |
| break; |
| case DartType.MAP_INT_STRING: |
| emit('{1: "a"}'); |
| break; |
| default: |
| throw 'Unknown DartType ${tp}'; |
| } |
| } |
| |
| BigInt genMask(int m) => BigInt.from(1) << m; |
| |
| // Process the opening of a statement. |
| // Determine whether the statement should be skipped based on the |
| // statement index stored in stmtCntr and the statement mask stored |
| // in smask. |
| // Returns true if the statement should be skipped. |
| bool processStmtOpen() { |
| // Do nothing if we are not in minimization mode. |
| if (!minimize) { |
| return false; |
| } |
| // Check whether the bit for the current statement number is set in the |
| // statement bitmap. If so skip this statement. |
| final newMask = genMask(stmtCntr); |
| final maskBitSet = (smask & newMask) != BigInt.zero; |
| // Statements are nested, therefore masking one statement like e.g. |
| // a for loop leads to other statements being omitted. |
| // Here we update the statement mask to include the additionally |
| // omitted statements. |
| if (skipStmt) { |
| smask |= newMask; |
| } |
| // Increase the statement counter. |
| stmtCntr++; |
| if (!skipStmt && maskBitSet) { |
| skipStmt = true; |
| return true; |
| } |
| return false; |
| } |
| |
| // Process the closing of a statement. |
| // The variable resetSkipStmt indicates whether this |
| // statement closes the sequence of skipped statement. |
| // E.g. the end of a loop where all contained statements |
| // were skipped. |
| void processStmtClose(bool resetSkipStmt) { |
| if (!minimize) { |
| return; |
| } |
| if (resetSkipStmt) { |
| skipStmt = false; |
| } |
| } |
| |
| // Process the opening of an expression. |
| // Determine whether the expression should be skipped based on the |
| // expression index stored in exprCntr and the expression mask stored |
| // in emask. |
| // Returns true is the expression is skipped. |
| bool processExprOpen(DartType tp) { |
| // Do nothing if we are not in minimization mode. |
| if (!minimize) { |
| return false; |
| } |
| // Check whether the bit for the current expression number is set in the |
| // expression bitmap. If so skip this expression. |
| final newMask = genMask(exprCntr); |
| final maskBitSet = (emask & newMask) != BigInt.zero; |
| // Expressions are nested, therefore masking one expression like e.g. |
| // a for loop leads to other expressions being omitted. |
| // Similarly, if the whole statement is skipped, all the expressions |
| // within that statement are implicitly masked. |
| // Here we update the expression mask to include the additionally |
| // omitted expressions. |
| if (skipExpr || skipStmt) { |
| emask |= newMask; |
| } |
| exprCntr++; |
| if (skipStmt) { |
| return false; |
| } |
| if (!skipExpr && maskBitSet) { |
| emitMinimizedLiteral(tp); |
| skipExpr = true; |
| return true; |
| } |
| return false; |
| } |
| |
| void processExprClose(bool resetExprStmt) { |
| if (!minimize || skipStmt) { |
| return; |
| } |
| if (resetExprStmt) { |
| skipExpr = false; |
| } |
| } |
| |
| // |
| // Program components. |
| // |
| |
| void emitHeader() { |
| emitLn('// The Dart Project Fuzz Tester ($version).'); |
| emitLn('// Program generated as:'); |
| emitLn('// dart dartfuzz.dart --seed $seed --${fp ? "" : "no-"}fp ' + |
| '--${ffi ? "" : "no-"}ffi --${flatTp ? "" : "no-"}flat'); |
| emitLn(''); |
| emitLn("import 'dart:async';"); |
| emitLn("import 'dart:cli';"); |
| emitLn("import 'dart:collection';"); |
| emitLn("import 'dart:convert';"); |
| emitLn("import 'dart:core';"); |
| emitLn("import 'dart:io';"); |
| emitLn("import 'dart:isolate';"); |
| emitLn("import 'dart:math';"); |
| emitLn("import 'dart:typed_data';"); |
| if (ffi) { |
| emitLn("import 'dart:ffi' as ffi;"); |
| emitLn(DartFuzzFfiApi.ffiapi); |
| } |
| } |
| |
| void emitFfiCast(String dartFuncName, String ffiFuncName, String typeName, |
| List<DartType> pars) { |
| emit("${pars[0].name} Function("); |
| for (int i = 1; i < pars.length; i++) { |
| DartType tp = pars[i]; |
| emit('${tp.name}'); |
| if (i != (pars.length - 1)) { |
| emit(', '); |
| } |
| } |
| emit(') ${dartFuncName} = ' + |
| 'ffi.Pointer.fromFunction<${typeName}>(${ffiFuncName}, '); |
| emitLiteral(0, pars[0], smallPositiveValue: true); |
| emitLn(').cast<ffi.NativeFunction<${typeName}>>().asFunction();'); |
| } |
| |
| void emitMethod( |
| String name, int index, List<DartType> method, bool isFfiMethod) { |
| if (isFfiMethod) { |
| emitFfiTypedef("${name}Ffi${index}Type", method); |
| emitLn('${method[0].name} ${name}Ffi$index(', newline: false); |
| } else { |
| emitLn('${method[0].name} $name$index(', newline: false); |
| } |
| emitParDecls(method); |
| if (!isFfiMethod && rand.nextInt(10) == 0) { |
| // Emit a method using "=>" syntax. |
| emit(') => '); |
| emitExpr(0, method[0]); |
| emit(';', newline: true); |
| return; |
| } |
| emit(') {', newline: true); |
| indent += 2; |
| assert(localVars.isEmpty); |
| if (emitStatements(0)) { |
| emitReturn(); |
| } |
| assert(localVars.isEmpty); |
| indent -= 2; |
| emitLn('}'); |
| if (isFfiMethod) { |
| emitFfiCast("${name}${index}", "${name}Ffi${index}", |
| "${name}Ffi${index}Type", method); |
| } |
| emit('', newline: true); |
| } |
| |
| void emitMethods(String name, List<List<DartType>> methods, |
| [List<bool> ffiStatus]) { |
| for (int i = 0; i < methods.length; i++) { |
| List<DartType> method = methods[i]; |
| currentMethod = i; |
| final bool isFfiMethod = ffiStatus != null && ffiStatus[i]; |
| emitMethod(name, i, method, isFfiMethod); |
| currentMethod = null; |
| } |
| } |
| |
| // Randomly overwrite some methods from the parent classes. |
| void emitVirtualMethods() { |
| final currentClassTmp = currentClass; |
| int parentClass = classParents[currentClass]; |
| final vcm = <int, List<int>>{}; |
| // Chase randomly up in class hierarchy. |
| while (parentClass >= 0) { |
| vcm[parentClass] = <int>[]; |
| for (int j = 0, n = classMethods[parentClass].length; j < n; j++) { |
| if (rand.nextInt(8) == 0) { |
| currentClass = parentClass; |
| currentMethod = j; |
| emitMethod('$methodName${parentClass}_', j, |
| classMethods[parentClass][j], false); |
| vcm[parentClass].add(currentMethod); |
| currentMethod = null; |
| currentClass = null; |
| } |
| } |
| if (rand.nextInt(2) == 0 || classParents.length > parentClass) { |
| break; |
| } else { |
| parentClass = classParents[parentClass]; |
| } |
| } |
| currentClass = currentClassTmp; |
| virtualClassMethods.add(vcm); |
| } |
| |
| void emitClasses() { |
| assert(classFields.length == classMethods.length); |
| for (int i = 0; i < classFields.length; i++) { |
| if (i == 0) { |
| classParents.add(-1); |
| emitLn('class X0 {'); |
| } else { |
| final int parentClass = rand.nextInt(i); |
| classParents.add(parentClass); |
| if (rand.nextInt(2) != 0) { |
| // Inheritance |
| emitLn('class X$i extends X${parentClass} {'); |
| } else { |
| // Mixin |
| if (classParents[parentClass] >= 0) { |
| emitLn( |
| 'class X$i extends X${classParents[parentClass]} with X${parentClass} {'); |
| } else { |
| emitLn('class X$i with X${parentClass} {'); |
| } |
| } |
| } |
| indent += 2; |
| emitVarDecls('$fieldName${i}_', classFields[i]); |
| currentClass = i; |
| emitVirtualMethods(); |
| emitMethods('$methodName${i}_', classMethods[i]); |
| emitLn('void run() {'); |
| indent += 2; |
| if (i > 0) { |
| emitLn('super.run();'); |
| } |
| assert(localVars.isEmpty); |
| emitStatements(0); |
| assert(localVars.isEmpty); |
| indent -= 2; |
| emitLn('}'); |
| indent -= 2; |
| emitLn('}'); |
| emit('', newline: true); |
| currentClass = null; |
| } |
| } |
| |
| void emitLoadFfiLib() { |
| if (ffi) { |
| emitLn( |
| '// The following throws an uncaught exception if the ffi library ' + |
| 'is not found.'); |
| emitLn( |
| '// By not catching this exception, we terminate the program with ' + |
| 'a full stack trace'); |
| emitLn('// which, in turn, flags the problem prominently'); |
| emitLn('if (ffiTestFunctions == null) {'); |
| indent += 2; |
| emitLn('print(\'Did not load ffi test functions\');'); |
| indent -= 2; |
| emitLn('}'); |
| } |
| } |
| |
| void emitTryCatchFinally(Function tryBody, Function catchBody, |
| {Function finallyBody}) { |
| emitLn('try {'); |
| indent += 2; |
| emitLn("", newline: false); |
| tryBody(); |
| emit(";", newline: true); |
| indent -= 2; |
| emitLn('} on OutOfMemoryError {'); |
| indent += 2; |
| emitLn("print(\'oom\');"); |
| emitLn("exit(${oomExitCode});"); |
| indent -= 2; |
| emitLn('} catch (e, st) {'); |
| indent += 2; |
| catchBody(); |
| indent -= 2; |
| if (finallyBody != null) { |
| emitLn('} finally {'); |
| indent += 2; |
| finallyBody(); |
| indent -= 2; |
| } |
| emitLn('}'); |
| } |
| |
| void emitMain() { |
| emitLn('main() {'); |
| indent += 2; |
| |
| emitLoadFfiLib(); |
| |
| // Call each global method once. |
| for (int i = 0; i < globalMethods.length; i++) { |
| emitTryCatchFinally(() { |
| emitCall(1, "$methodName${i}", globalMethods[i]); |
| }, () { |
| emitLn("print('$methodName$i throws');"); |
| }); |
| } |
| |
| // Call each class method once. |
| for (int i = 0; i < classMethods.length; i++) { |
| for (int j = 0; j < classMethods[i].length; j++) { |
| emitTryCatchFinally(() { |
| emitCall(1, "X${i}().$methodName${i}_${j}", classMethods[i][j]); |
| }, () { |
| emitLn("print('X${i}().$methodName${i}_${j}() throws');"); |
| }); |
| } |
| // Call each virtual class method once. |
| int parentClass = classParents[i]; |
| while (parentClass >= 0) { |
| if (virtualClassMethods[i].containsKey(parentClass)) { |
| for (int j = 0; j < virtualClassMethods[i][parentClass].length; j++) { |
| emitTryCatchFinally(() { |
| emitCall(1, "X${i}().$methodName${parentClass}_${j}", |
| classMethods[parentClass][j]); |
| }, () { |
| emitLn( |
| "print('X${i}().$methodName${parentClass}_${j}() throws');"); |
| }); |
| } |
| } |
| parentClass = classParents[parentClass]; |
| } |
| } |
| |
| emitTryCatchFinally(() { |
| emit('X${classFields.length - 1}().run()'); |
| }, () { |
| emitLn("print('X${classFields.length - 1}().run() throws');"); |
| }); |
| |
| emitTryCatchFinally(() { |
| emit("print('", newline: false); |
| for (int i = 0; i < globalVars.length; i++) { |
| emit('\$$varName$i\\n'); |
| } |
| emit("')"); |
| }, () { |
| emitLn("print('print throws');"); |
| }); |
| |
| indent -= 2; |
| emitLn('}'); |
| } |
| |
| // |
| // Declarations. |
| // |
| |
| void emitVarDecls(String name, List<DartType> vars) { |
| emit('', newline: true); |
| for (int i = 0; i < vars.length; i++) { |
| DartType tp = vars[i]; |
| emitLn('${tp.name} $name$i = ', newline: false); |
| emitConstructorOrLiteral(0, tp); |
| emit(';', newline: true); |
| } |
| emit('', newline: true); |
| } |
| |
| void emitParDecls(List<DartType> pars) { |
| for (int i = 1; i < pars.length; i++) { |
| DartType tp = pars[i]; |
| emit('${tp.name} $paramName$i'); |
| if (i != (pars.length - 1)) { |
| emit(', '); |
| } |
| } |
| } |
| |
| // |
| // 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. |
| // |
| |
| // Emit an assignment statement. |
| bool emitAssign() { |
| // Select a type at random. |
| final tp = oneOfSet(dartType.allTypes); |
| // Select one of the assign operations for the given type. |
| final assignOp = oneOfSet(dartType.assignOps(tp)); |
| if (assignOp == null) { |
| throw 'No assign operation for ${tp.name}'; |
| } |
| emitLn('', newline: false); |
| // Emit a variable of the lhs type. |
| final emittedVar = emitVar(0, tp, isLhs: true); |
| RhsFilter rhsFilter = RhsFilter.fromDartType(tp, emittedVar); |
| if ({'*=', '+='}.contains(assignOp)) { |
| rhsFilter?.consume(); |
| } |
| emit(" $assignOp "); |
| // Select one of the possible rhs types for the given lhs type and assign |
| // operation. |
| DartType rhsType = oneOfSet(dartType.assignOpRhs(tp, assignOp)); |
| if (rhsType == null) { |
| throw 'No rhs type for assign ${tp.name} $assignOp'; |
| } |
| |
| // We need to avoid cases of "abcde" *= large number in loops. |
| if (assignOp == "*=" && tp == DartType.STRING && rhsType == DartType.INT) { |
| emitSmallPositiveInt(); |
| } else { |
| // Emit an expression for the right hand side. |
| emitExpr(0, rhsType, rhsFilter: rhsFilter); |
| } |
| emit(';', newline: true); |
| return true; |
| } |
| |
| // Emit a print statement. |
| bool emitPrint() { |
| DartType tp = oneOfSet(dartType.allTypes); |
| emitLn('print(', newline: false); |
| emitExpr(0, tp); |
| emit(');', newline: true); |
| return true; |
| } |
| |
| // Emit a return statement. |
| bool emitReturn() { |
| List<DartType> proto = getCurrentProto(); |
| if (proto == null) { |
| emitLn('return;'); |
| } else { |
| emitLn('return ', newline: false); |
| emitExpr(0, proto[0]); |
| emit(';', newline: true); |
| } |
| return false; |
| } |
| |
| // Emit a throw statement. |
| bool emitThrow() { |
| DartType tp = oneOfSet(dartType.allTypes); |
| emitLn('throw ', newline: false); |
| emitExpr(0, tp); |
| emit(';', newline: true); |
| return false; |
| } |
| |
| // Emit a one-way if statement. |
| bool emitIf1(int depth) { |
| emitLn('if (', newline: false); |
| emitExpr(0, DartType.BOOL); |
| emit(') {', newline: true); |
| indent += 2; |
| emitStatements(depth + 1); |
| indent -= 2; |
| emitLn('}'); |
| return true; |
| } |
| |
| // Emit a two-way if statement. |
| bool emitIf2(int depth) { |
| emitLn('if (', newline: false); |
| emitExpr(0, DartType.BOOL); |
| emit(') {', newline: true); |
| indent += 2; |
| bool b1 = emitStatements(depth + 1); |
| indent -= 2; |
| emitLn('} else {'); |
| indent += 2; |
| bool b2 = emitStatements(depth + 1); |
| indent -= 2; |
| emitLn('}'); |
| return b1 || b2; |
| } |
| |
| // Emit a simple increasing for-loop. |
| bool emitFor(int depth) { |
| final int i = localVars.length; |
| emitLn('for (int $localName$i = 0; $localName$i < ', newline: false); |
| emitSmallPositiveInt(); |
| emit('; $localName$i++) {', newline: true); |
| indent += 2; |
| nest++; |
| iterVars.add("$localName$i"); |
| localVars.add(DartType.INT); |
| emitStatements(depth + 1); |
| localVars.removeLast(); |
| iterVars.removeLast(); |
| nest--; |
| indent -= 2; |
| emitLn('}'); |
| return true; |
| } |
| |
| // Emit a simple membership for-in-loop. |
| bool emitForIn(int depth) { |
| final int i = localVars.length; |
| // Select one iterable type to be used in 'for in' statement. |
| final iterType = oneOfSet(dartType.iterableTypes1); |
| // Get the element type contained within the iterable type. |
| final elementType = dartType.elementType(iterType); |
| if (elementType == null) { |
| throw 'No element type for iteration type ${iterType.name}'; |
| } |
| emitLn('for (${elementType.name} $localName$i in ', newline: false); |
| localVars.add(null); // declared, but don't use |
| emitExpr(0, iterType); |
| localVars.removeLast(); // will get type |
| emit(') {', newline: true); |
| indent += 2; |
| nest++; |
| localVars.add(elementType); |
| emitStatements(depth + 1); |
| localVars.removeLast(); |
| nest--; |
| indent -= 2; |
| emitLn('}'); |
| return true; |
| } |
| |
| // Emit a simple membership forEach loop. |
| bool emitForEach(int depth) { |
| final int i = localVars.length; |
| final int j = i + 1; |
| emitLn("", newline: false); |
| // Select one map type to be used in forEach loop. |
| final mapType = oneOfSet(dartType.mapTypes); |
| final emittedVar = emitScalarVar(mapType, isLhs: false); |
| iterVars.add(emittedVar); |
| emit('.forEach(($localName$i, $localName$j) {\n'); |
| indent += 2; |
| final int nestTmp = nest; |
| // Reset, since forEach cannot break out of own or enclosing context. |
| nest = 0; |
| // Get the type of the map key and add it to the local variables. |
| localVars.add(dartType.indexType(mapType)); |
| // Get the type of the map values and add it to the local variables. |
| localVars.add(dartType.elementType(mapType)); |
| emitStatements(depth + 1); |
| localVars.removeLast(); |
| localVars.removeLast(); |
| nest = nestTmp; |
| indent -= 2; |
| emitLn('});'); |
| return true; |
| } |
| |
| // Emit a while-loop. |
| bool emitWhile(int depth) { |
| final int i = localVars.length; |
| emitLn('{ int $localName$i = ', newline: false); |
| emitSmallPositiveInt(); |
| emit(';', newline: true); |
| indent += 2; |
| emitLn('while (--$localName$i > 0) {'); |
| indent += 2; |
| nest++; |
| iterVars.add("$localName$i"); |
| localVars.add(DartType.INT); |
| emitStatements(depth + 1); |
| localVars.removeLast(); |
| iterVars.removeLast(); |
| nest--; |
| indent -= 2; |
| emitLn('}'); |
| indent -= 2; |
| emitLn('}'); |
| return true; |
| } |
| |
| // Emit a do-while-loop. |
| bool emitDoWhile(int depth) { |
| final int i = localVars.length; |
| emitLn('{ int $localName$i = 0;'); |
| indent += 2; |
| emitLn('do {'); |
| indent += 2; |
| nest++; |
| iterVars.add("$localName$i"); |
| localVars.add(DartType.INT); |
| emitStatements(depth + 1); |
| localVars.removeLast(); |
| iterVars.removeLast(); |
| nest--; |
| indent -= 2; |
| emitLn('} while (++$localName$i < ', newline: false); |
| emitSmallPositiveInt(); |
| emit(');', newline: true); |
| indent -= 2; |
| emitLn('}'); |
| return true; |
| } |
| |
| // Emit a break/continue when inside iteration. |
| bool emitBreakOrContinue(int depth) { |
| if (nest > 0) { |
| switch (rand.nextInt(2)) { |
| case 0: |
| emitLn('continue;'); |
| return false; |
| default: |
| emitLn('break;'); |
| return false; |
| } |
| } |
| return emitAssign(); // resort to assignment |
| } |
| |
| // Emit a switch statement. |
| bool emitSwitch(int depth) { |
| emitLn('switch (', newline: false); |
| emitExpr(0, DartType.INT); |
| emit(') {', newline: true); |
| int start = rand.nextInt(1 << 32); |
| int step = 1 + rand.nextInt(10); |
| for (int i = 0; i < 2; i++, start += step) { |
| indent += 2; |
| if (i == 2) { |
| emitLn('default: {'); |
| } else { |
| emitLn('case $start: {'); |
| } |
| indent += 2; |
| emitStatements(depth + 1); |
| indent -= 2; |
| emitLn('}'); |
| emitLn('break;'); // always generate, avoid FE complaints |
| indent -= 2; |
| } |
| emitLn('}'); |
| return true; |
| } |
| |
| // Emit a new program scope that introduces a new local variable. |
| bool emitScope(int depth) { |
| DartType tp = oneOfSet(dartType.allTypes); |
| final int i = localVars.length; |
| emitLn('{ ${tp.name} $localName$i = ', newline: false); |
| localVars.add(null); // declared, but don't use |
| emitExpr(0, tp); |
| localVars.removeLast(); // will get type |
| emit(';', newline: true); |
| indent += 2; |
| localVars.add(tp); |
| emitStatements(depth + 1); |
| localVars.removeLast(); |
| indent -= 2; |
| emitLn('}'); |
| return true; |
| } |
| |
| // Emit try/catch/finally. |
| bool emitTryCatch(int depth) { |
| emitLn('try {'); |
| indent += 2; |
| emitStatements(depth + 1); |
| indent -= 2; |
| emitLn('} catch (exception, stackTrace) {'); |
| indent += 2; |
| emitStatements(depth + 1); |
| indent -= 2; |
| if (rand.nextInt(2) == 0) { |
| emitLn('} finally {'); |
| indent += 2; |
| emitStatements(depth + 1); |
| indent -= 2; |
| } |
| emitLn('}'); |
| return true; |
| } |
| |
| // Emit a single statement. |
| bool emitSingleStatement(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(16)) { |
| // Favors assignment. |
| case 0: |
| return emitPrint(); |
| case 1: |
| return emitReturn(); |
| case 2: |
| return emitThrow(); |
| case 3: |
| return emitIf1(depth); |
| case 4: |
| return emitIf2(depth); |
| case 5: |
| return emitFor(depth); |
| case 6: |
| return emitForIn(depth); |
| case 7: |
| return emitWhile(depth); |
| case 8: |
| return emitDoWhile(depth); |
| case 9: |
| return emitBreakOrContinue(depth); |
| case 10: |
| return emitSwitch(depth); |
| case 11: |
| return emitScope(depth); |
| case 12: |
| return emitTryCatch(depth); |
| case 13: |
| return emitForEach(depth); |
| default: |
| return emitAssign(); |
| } |
| } |
| |
| // Emit a statement (main entry). |
| // Returns true if code *may* fall-through |
| // (not made too advanced to avoid FE complaints). |
| bool emitStatement(int depth) { |
| final resetSkipStmt = processStmtOpen(); |
| bool ret = emitSingleStatement(depth); |
| processStmtClose(resetSkipStmt); |
| return ret; |
| } |
| |
| // Emit statements. Returns true if code may fall-through. |
| bool emitStatements(int depth) { |
| int s = 1 + rand.nextInt(numStatements); |
| for (int i = 0; i < s; i++) { |
| if (!emitStatement(depth)) { |
| return false; // rest would be dead code |
| } |
| } |
| return true; |
| } |
| |
| // |
| // Expressions. |
| // |
| |
| void emitBool() { |
| emit(rand.nextInt(2) == 0 ? 'true' : 'false'); |
| } |
| |
| void emitSmallPositiveInt({int limit = 100}) { |
| emit('${rand.nextInt(limit)}'); |
| } |
| |
| void emitSmallNegativeInt() { |
| emit('-${rand.nextInt(100)}'); |
| } |
| |
| void emitInt() { |
| switch (rand.nextInt(7)) { |
| // Favors small ints. |
| case 0: |
| case 1: |
| case 2: |
| emitSmallPositiveInt(); |
| break; |
| case 3: |
| case 4: |
| case 5: |
| emitSmallNegativeInt(); |
| break; |
| default: |
| emit('${oneOf(interestingIntegers)}'); |
| break; |
| } |
| } |
| |
| void emitDouble() { |
| emit('${rand.nextDouble()}'); |
| } |
| |
| void emitNum({bool smallPositiveValue = false}) { |
| if (!fp || rand.nextInt(2) == 0) { |
| if (smallPositiveValue) { |
| emitSmallPositiveInt(); |
| } else { |
| emitInt(); |
| } |
| } else { |
| emitDouble(); |
| } |
| } |
| |
| void emitChar() { |
| switch (rand.nextInt(10)) { |
| // Favors regular char. |
| case 0: |
| emit(oneOf(interestingChars)); |
| break; |
| default: |
| emit(regularChars[rand.nextInt(regularChars.length)]); |
| break; |
| } |
| } |
| |
| void emitString({int length = 8}) { |
| emit("'"); |
| for (int i = 0, n = rand.nextInt(length); i < n; i++) { |
| emitChar(); |
| } |
| emit("'"); |
| } |
| |
| void emitElementExpr(int depth, DartType tp, {RhsFilter rhsFilter}) { |
| // This check determines whether we are emitting a global variable. |
| // I.e. whenever we are not currently emitting part of a class. |
| if (currentMethod != null) { |
| emitExpr(depth, tp, rhsFilter: rhsFilter); |
| } else { |
| emitLiteral(depth, tp, rhsFilter: rhsFilter); |
| } |
| } |
| |
| void emitElement(int depth, DartType tp, {RhsFilter rhsFilter}) { |
| // Get the element type contained in type tp. |
| // E.g. element type of List<String> is String. |
| final elementType = dartType.elementType(tp); |
| // Decide whether we need to generate Map or List/Set type elements. |
| if (DartType.isMapType(tp)) { |
| // Emit construct for the map key type. |
| final indexType = dartType.indexType(tp); |
| // This check determines whether we are emitting a global variable. |
| // I.e. whenever we are not currently emitting part of a class. |
| if (currentMethod != null) { |
| emitExpr(depth, indexType, rhsFilter: rhsFilter); |
| } else { |
| emitLiteral(depth, indexType, rhsFilter: rhsFilter); |
| } |
| emit(' : '); |
| // Emit construct for the map value type. |
| emitElementExpr(depth, elementType, rhsFilter: rhsFilter); |
| } else { |
| // List and Set types. |
| emitElementExpr(depth, elementType, rhsFilter: rhsFilter); |
| } |
| } |
| |
| void emitCollectionElement(int depth, DartType tp, {RhsFilter rhsFilter}) { |
| int r = depth <= exprDepth ? rand.nextInt(10) : 10; |
| // TODO (felih): disable complex collection constructs for new types for |
| // now. |
| if (!{DartType.MAP_INT_STRING, DartType.LIST_INT, DartType.SET_INT} |
| .contains(tp)) { |
| emitElement(depth, tp, rhsFilter: rhsFilter); |
| return; |
| } |
| switch (r) { |
| // Favors elements over control-flow collections. |
| case 0: |
| // TODO (ajcbik): Remove restriction once compiler is fixed. |
| if (depth < 2) { |
| emit('...'); // spread |
| emitCollection(depth + 1, tp, rhsFilter: rhsFilter); |
| } else { |
| emitElement(depth, tp, rhsFilter: rhsFilter); |
| } |
| break; |
| case 1: |
| emit('if ('); |
| emitElementExpr(depth + 1, DartType.BOOL, rhsFilter: rhsFilter); |
| emit(') '); |
| emitCollectionElement(depth + 1, tp, rhsFilter: rhsFilter); |
| if (rand.nextBool()) { |
| emit(' else '); |
| emitCollectionElement(depth + 1, tp, rhsFilter: rhsFilter); |
| } |
| break; |
| case 2: |
| { |
| final int i = localVars.length; |
| // TODO (felih): update to use new type system. Add types like |
| // LIST_STRING etc. |
| emit('for (int $localName$i '); |
| // For-loop (induction, list, set). |
| localVars.add(null); // declared, but don't use |
| switch (rand.nextInt(3)) { |
| case 0: |
| emit('= 0; $localName$i < '); |
| emitSmallPositiveInt(limit: 16); |
| emit('; $localName$i++) '); |
| break; |
| case 1: |
| emit('in '); |
| emitCollection(depth + 1, DartType.LIST_INT, |
| rhsFilter: rhsFilter); |
| emit(') '); |
| break; |
| default: |
| emit('in '); |
| emitCollection(depth + 1, DartType.SET_INT, rhsFilter: rhsFilter); |
| emit(') '); |
| break; |
| } |
| localVars.removeLast(); // will get type |
| nest++; |
| iterVars.add("$localName$i"); |
| localVars.add(DartType.INT); |
| emitCollectionElement(depth + 1, tp, rhsFilter: rhsFilter); |
| localVars.removeLast(); |
| iterVars.removeLast(); |
| nest--; |
| break; |
| } |
| default: |
| emitElement(depth, tp, rhsFilter: rhsFilter); |
| break; |
| } |
| } |
| |
| void emitCollection(int depth, DartType tp, {RhsFilter rhsFilter}) { |
| // Collection length decreases as depth increases. |
| int collectionLength = max(1, 8 - depth); |
| emit(DartType.isListType(tp) ? '[ ' : '{ '); |
| for (int i = 0, n = 1 + rand.nextInt(collectionLength); i < n; i++) { |
| emitCollectionElement(depth, tp, rhsFilter: rhsFilter); |
| if (i != (n - 1)) { |
| emit(', '); |
| } |
| } |
| emit(DartType.isListType(tp) ? ' ]' : ' }'); |
| } |
| |
| void emitLiteral(int depth, DartType tp, |
| {bool smallPositiveValue = false, RhsFilter rhsFilter}) { |
| // Randomly specialize interface if possible. E.g. num to int. |
| tp = maybeSpecializeInterface(tp); |
| if (tp == DartType.BOOL) { |
| emitBool(); |
| } else if (tp == DartType.INT) { |
| if (smallPositiveValue) { |
| emitSmallPositiveInt(); |
| } else { |
| emitInt(); |
| } |
| } else if (tp == DartType.DOUBLE) { |
| emitDouble(); |
| } else if (tp == DartType.NUM) { |
| emitNum(smallPositiveValue: smallPositiveValue); |
| } else if (tp == DartType.STRING) { |
| emitString(); |
| } else if (dartType.constructors(tp).isNotEmpty) { |
| // Constructors serve as literals for non trivially constructable types. |
| // Important note: We have to test for existence of a non trivial |
| // constructor before testing for list type assiciation, since some types |
| // like ListInt32 are of list type but can not be constructed |
| // from a literal. |
| emitConstructorOrLiteral(depth + 1, tp, rhsFilter: rhsFilter); |
| } else if (DartType.isCollectionType(tp)) { |
| final resetExprStmt = processExprOpen(tp); |
| emitCollection(depth + 1, tp, rhsFilter: rhsFilter); |
| processExprClose(resetExprStmt); |
| } else { |
| throw 'Can not emit literal for type ${tp.name}'; |
| } |
| } |
| |
| // Emit a constructor for a type, this can either be a trivial constructor |
| // (i.e. parsed from a literal) or an actual function invocation. |
| void emitConstructorOrLiteral(int depth, DartType tp, {RhsFilter rhsFilter}) { |
| // If there is at least one non trivial constructor for the type tp |
| // select one of these constructors. |
| if (dartType.hasConstructor(tp)) { |
| String constructor = oneOfSet(dartType.constructors(tp)); |
| // There are two types of constructors, named constructors and 'empty' |
| // constructors (e.g. X.fromList(...) and new X(...) respectively). |
| // Empty constructors are invoked with new + type name, non-empty |
| // constructors are static functions of the type. |
| if (!constructor.isEmpty) { |
| emit('${tp.name}'); |
| emit('.${constructor}'); |
| } else { |
| emit('new ${tp.name}'); |
| } |
| emit('('); |
| // Iterate over constructor parameters. |
| List<DartType> constructorParameters = |
| dartType.constructorParameters(tp, constructor); |
| if (constructorParameters == null) { |
| throw 'No constructor parameters for ${tp.name}.$constructor'; |
| } |
| for (int i = 0, n = constructorParameters.length; i < n; ++i) { |
| // If we are emitting a constructor parameter, we want to use small |
| // values to avoid programs that run out of memory. |
| // TODO (felih): maybe allow occasionally? |
| emitLiteral(depth + 1, constructorParameters[i], |
| smallPositiveValue: true, rhsFilter: rhsFilter); |
| if (i != n - 1) { |
| emit(", "); |
| } |
| } |
| emit(')'); |
| } else { |
| // Otherwise type can be constructed from a literal. |
| emitLiteral(depth + 1, tp, rhsFilter: rhsFilter); |
| } |
| } |
| |
| String emitScalarVar(DartType tp, {bool isLhs = false, RhsFilter rhsFilter}) { |
| // Randomly specialize interface type, unless emitting left hand side. |
| if (!isLhs) tp = maybeSpecializeInterface(tp); |
| // Collect all choices from globals, fields, locals, and parameters. |
| Set<String> choices = <String>{}; |
| for (int i = 0; i < globalVars.length; i++) { |
| if (tp == globalVars[i]) choices.add('$varName$i'); |
| } |
| for (int i = 0; i < localVars.length; i++) { |
| if (tp == localVars[i]) choices.add('$localName$i'); |
| } |
| List<DartType> fields = getCurrentFields(); |
| if (fields != null) { |
| for (int i = 0; i < fields.length; i++) { |
| if (tp == fields[i]) choices.add('$fieldName${currentClass}_$i'); |
| } |
| } |
| List<DartType> proto = getCurrentProto(); |
| if (proto != null) { |
| for (int i = 1; i < proto.length; i++) { |
| if (tp == proto[i]) choices.add('$paramName$i'); |
| } |
| } |
| // Make modification of the iteration variable from the loop |
| // body less likely. |
| if (isLhs) { |
| if (rand.nextInt(100) != 0) { |
| Set<String> cleanChoices = choices.difference(Set.from(iterVars)); |
| if (cleanChoices.isNotEmpty) { |
| choices = cleanChoices; |
| } |
| } |
| } |
| // Filter out the current lhs of the expression to avoid recursive |
| // assignments of the form x = x * x. |
| if (rhsFilter != null && rhsFilter.shouldFilter) { |
| Set<String> cleanChoices = choices.difference({rhsFilter.lhsVar}); |
| // If we have other choices of variables, use those. |
| if (cleanChoices.isNotEmpty) { |
| choices = cleanChoices; |
| } else if (!isLhs) { |
| // If we are emitting an rhs variable, we can emit a terminal. |
| // note that if the variable type is a collection, this might |
| // still result in a recursion. |
| emitLiteral(0, tp); |
| return null; |
| } |
| // Otherwise we have to risk creating a recursion. |
| } |
| // Then pick one. |
| if (choices.isEmpty) { |
| throw 'No variable to emit for type ${tp.name}'; |
| } |
| final emittedVar = '${choices.elementAt(rand.nextInt(choices.length))}'; |
| if (rhsFilter != null && (emittedVar == rhsFilter.lhsVar)) { |
| rhsFilter.consume(); |
| } |
| emit(emittedVar); |
| return emittedVar; |
| } |
| |
| String emitSubscriptedVar(int depth, DartType tp, |
| {bool isLhs = false, RhsFilter rhsFilter}) { |
| String ret; |
| // Check if type tp is an indexable element of some other type. |
| if (dartType.isIndexableElementType(tp)) { |
| // Select a list or map type that contains elements of type tp. |
| final iterType = oneOfSet(dartType.indexableElementTypes(tp)); |
| // Get the index type for the respective list or map type. |
| final indexType = dartType.indexType(iterType); |
| // Emit a variable of the selected list or map type. |
| ret = emitScalarVar(iterType, isLhs: isLhs, rhsFilter: rhsFilter); |
| emit('['); |
| // Emit an expression resolving into the index type. |
| emitExpr(depth + 1, indexType); |
| emit(']'); |
| } else { |
| ret = emitScalarVar(tp, |
| isLhs: isLhs, rhsFilter: rhsFilter); // resort to scalar |
| } |
| return ret; |
| } |
| |
| String emitVar(int depth, DartType tp, |
| {bool isLhs = false, RhsFilter rhsFilter}) { |
| switch (rand.nextInt(2)) { |
| case 0: |
| return emitScalarVar(tp, isLhs: isLhs, rhsFilter: rhsFilter); |
| break; |
| default: |
| return emitSubscriptedVar(depth, tp, |
| isLhs: isLhs, rhsFilter: rhsFilter); |
| break; |
| } |
| } |
| |
| void emitTerminal(int depth, DartType tp, {RhsFilter rhsFilter}) { |
| switch (rand.nextInt(2)) { |
| case 0: |
| emitLiteral(depth, tp, rhsFilter: rhsFilter); |
| break; |
| default: |
| emitVar(depth, tp, rhsFilter: rhsFilter); |
| break; |
| } |
| } |
| |
| void emitExprList(int depth, List<DartType> proto, {RhsFilter rhsFilter}) { |
| emit('('); |
| for (int i = 1; i < proto.length; i++) { |
| emitExpr(depth, proto[i], rhsFilter: rhsFilter); |
| if (i != (proto.length - 1)) { |
| emit(', '); |
| } |
| } |
| emit(')'); |
| } |
| |
| // Emit expression with unary operator: (~(x)) |
| void emitUnaryExpr(int depth, DartType tp, {RhsFilter rhsFilter}) { |
| if (dartType.uniOps(tp).isEmpty) { |
| return emitTerminal(depth, tp, rhsFilter: rhsFilter); |
| } |
| emit('('); |
| emit(oneOfSet(dartType.uniOps(tp))); |
| emit('('); |
| emitExpr(depth + 1, tp, rhsFilter: rhsFilter); |
| emit('))'); |
| } |
| |
| // Emit expression with binary operator: (x + y) |
| void emitBinaryExpr(int depth, DartType tp, {RhsFilter rhsFilter}) { |
| if (dartType.binOps(tp).isEmpty) { |
| return emitLiteral(depth, tp); |
| } |
| |
| String binop = oneOfSet(dartType.binOps(tp)); |
| List<DartType> binOpParams = oneOfSet(dartType.binOpParameters(tp, binop)); |
| |
| // Avoid recursive assignments of the form a = a * 100. |
| if (binop == "*") { |
| rhsFilter?.consume(); |
| } |
| |
| // Reduce the number of operations of type growable * large value |
| // as these might lead to timeouts and/or oom errors. |
| if (binop == "*" && |
| DartType.isGrowableType(binOpParams[0]) && |
| dartType.isInterfaceOfType(binOpParams[1], DartType.NUM)) { |
| emit('('); |
| emitExpr(depth + 1, binOpParams[0], rhsFilter: rhsFilter); |
| emit(' $binop '); |
| emitLiteral(depth + 1, binOpParams[1], smallPositiveValue: true); |
| emit(')'); |
| } else if (binop == "*" && |
| DartType.isGrowableType(binOpParams[1]) && |
| dartType.isInterfaceOfType(binOpParams[0], DartType.NUM)) { |
| emit('('); |
| emitLiteral(depth + 1, binOpParams[0], smallPositiveValue: true); |
| emit(' $binop '); |
| emitExpr(depth + 1, binOpParams[1], rhsFilter: rhsFilter); |
| emit(')'); |
| } else { |
| emit('('); |
| emitExpr(depth + 1, binOpParams[0], rhsFilter: rhsFilter); |
| emit(' $binop '); |
| emitExpr(depth + 1, binOpParams[1], rhsFilter: rhsFilter); |
| emit(')'); |
| } |
| } |
| |
| // Emit expression with ternary operator: (b ? x : y) |
| void emitTernaryExpr(int depth, DartType tp, {RhsFilter rhsFilter}) { |
| emit('('); |
| emitExpr(depth + 1, DartType.BOOL, rhsFilter: rhsFilter); |
| emit(' ? '); |
| emitExpr(depth + 1, tp, rhsFilter: rhsFilter); |
| emit(' : '); |
| emitExpr(depth + 1, tp, rhsFilter: rhsFilter); |
| emit(')'); |
| } |
| |
| // Emit expression with pre/post-increment/decrement operator: (x++) |
| void emitPreOrPostExpr(int depth, DartType tp, {RhsFilter rhsFilter}) { |
| if (tp == DartType.INT) { |
| int r = rand.nextInt(2); |
| emit('('); |
| if (r == 0) emitPreOrPostOp(tp); |
| emitScalarVar(tp, isLhs: true); |
| if (r == 1) emitPreOrPostOp(tp); |
| emit(')'); |
| } else { |
| emitTerminal(depth, tp, rhsFilter: rhsFilter); // resort to terminal |
| } |
| } |
| |
| // Emit library call. |
| void emitLibraryCall(int depth, DartType tp, {RhsFilter rhsFilter}) { |
| DartLib lib = getLibraryMethod(tp); |
| if (lib == null) { |
| // no matching lib: resort to literal. |
| emitLiteral(depth + 1, tp, rhsFilter: rhsFilter); |
| return; |
| } |
| emit('('); |
| String proto = lib.proto; |
| // Receiver. |
| if (proto[0] != 'V') { |
| emit('('); |
| emitArg(depth + 1, proto[0], rhsFilter: rhsFilter); |
| emit(').'); |
| } |
| // Call. |
| emit('${lib.name}'); |
| // Parameters. |
| if (proto[1] != 'v') { |
| emit('('); |
| if (proto[1] != 'V') { |
| for (int i = 1; i < proto.length; i++) { |
| emitArg(depth + 1, proto[i], rhsFilter: rhsFilter); |
| if (i != (proto.length - 1)) { |
| emit(', '); |
| } |
| } |
| } |
| emit(')'); |
| } |
| // Add cast to avoid error of double or int being interpreted as num. |
| if (dartType.isInterfaceOfType(tp, DartType.NUM)) { |
| emit(' as ${tp.name}'); |
| } |
| emit(')'); |
| } |
| |
| // Emit call to a specific method. |
| void emitCall(int depth, String name, List<DartType> proto, |
| {RhsFilter rhsFilter}) { |
| emit(name); |
| emitExprList(depth + 1, proto, rhsFilter: rhsFilter); |
| } |
| |
| // Helper for a method call. |
| bool pickedCall( |
| int depth, DartType tp, String name, List<List<DartType>> protos, int m, |
| {RhsFilter rhsFilter}) { |
| for (int i = m - 1; i >= 0; i--) { |
| if (tp == protos[i][0]) { |
| emitCall(depth + 1, "$name$i", protos[i], rhsFilter: rhsFilter); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Emit method call within the program. |
| void emitMethodCall(int depth, DartType tp, {RhsFilter rhsFilter}) { |
| // Only call backward to avoid infinite recursion. |
| if (currentClass == null) { |
| // Outside a class but inside a method: call backward in global methods. |
| if (currentMethod != null && |
| pickedCall(depth, tp, methodName, globalMethods, currentMethod, |
| rhsFilter: rhsFilter)) { |
| return; |
| } |
| } else { |
| int classIndex = currentClass; |
| // Chase randomly up in class hierarchy. |
| while (classParents[classIndex] > 0) { |
| if (rand.nextInt(2) == 0) { |
| break; |
| } |
| classIndex = classParents[classIndex]; |
| } |
| int m1 = 0; |
| // Inside a class: try to call backwards into current or parent class |
| // methods first. |
| if (currentMethod == null || classIndex != currentClass) { |
| // If currently emitting the 'run' method or calling into a parent class |
| // pick any of the current or parent class methods respectively. |
| m1 = classMethods[classIndex].length; |
| } else { |
| // If calling into the current class from any method other than 'run' |
| // pick one of the already emitted methods |
| // (to avoid infinite recursions). |
| m1 = currentMethod; |
| } |
| final int m2 = globalMethods.length; |
| if (pickedCall(depth, tp, '$methodName${classIndex}_', |
| classMethods[classIndex], m1, rhsFilter: rhsFilter) || |
| pickedCall(depth, tp, methodName, globalMethods, m2, |
| rhsFilter: rhsFilter)) { |
| return; |
| } |
| } |
| emitTerminal(depth, tp, rhsFilter: rhsFilter); // resort to terminal. |
| } |
| |
| // Emit expression. |
| void emitExpr(int depth, DartType tp, {RhsFilter rhsFilter}) { |
| final resetExprStmt = processExprOpen(tp); |
| // Continuing nested expressions becomes less likely as the depth grows. |
| if (rand.nextInt(depth + 1) > exprDepth) { |
| emitTerminal(depth, tp, rhsFilter: rhsFilter); |
| } else { |
| // Possibly nested expression. |
| switch (rand.nextInt(7)) { |
| case 0: |
| emitUnaryExpr(depth, tp, rhsFilter: rhsFilter); |
| break; |
| case 1: |
| emitBinaryExpr(depth, tp, rhsFilter: rhsFilter); |
| break; |
| case 2: |
| emitTernaryExpr(depth, tp, rhsFilter: rhsFilter); |
| break; |
| case 3: |
| emitPreOrPostExpr(depth, tp, rhsFilter: rhsFilter); |
| break; |
| case 4: |
| emitLibraryCall(depth, tp, |
| rhsFilter: RhsFilter.cloneEmpty(rhsFilter)); |
| break; |
| case 5: |
| emitMethodCall(depth, tp, rhsFilter: RhsFilter.cloneEmpty(rhsFilter)); |
| break; |
| default: |
| emitTerminal(depth, tp, rhsFilter: rhsFilter); |
| break; |
| } |
| } |
| processExprClose(resetExprStmt); |
| } |
| |
| // |
| // Operators. |
| // |
| |
| // Emit same type in-out increment operator. |
| void emitPreOrPostOp(DartType tp) { |
| if (tp == DartType.INT) { |
| emit(oneOf(const <String>['++', '--'])); |
| } else { |
| assert(false); |
| } |
| } |
| |
| // Emit one type in, boolean out operator. |
| void emitRelOp(DartType tp) { |
| if (tp == DartType.INT || tp == DartType.DOUBLE) { |
| emit(oneOf(const <String>[' > ', ' >= ', ' < ', ' <= ', ' != ', ' == '])); |
| } else { |
| emit(oneOf(const <String>[' != ', ' == '])); |
| } |
| } |
| |
| // |
| // Library methods. |
| // |
| |
| // Get a library method that returns given type. |
| DartLib getLibraryMethod(DartType tp) { |
| if (tp == DartType.BOOL) { |
| return oneOf(api.boolLibs); |
| } else if (tp == DartType.INT) { |
| return oneOf(api.intLibs); |
| } else if (tp == DartType.DOUBLE) { |
| return oneOf(api.doubleLibs); |
| } else if (tp == DartType.STRING) { |
| return oneOf(DartLib.stringLibs); |
| } else if (tp == DartType.LIST_INT) { |
| return oneOf(DartLib.listLibs); |
| } else if (tp == DartType.SET_INT) { |
| return oneOf(DartLib.setLibs); |
| } else if (tp == DartType.MAP_INT_STRING) { |
| return oneOf(DartLib.mapLibs); |
| } |
| // No library method available that returns this type. |
| return null; |
| } |
| |
| // Emit a library argument, possibly subject to restrictions. |
| void emitArg(int depth, String p, {RhsFilter rhsFilter}) { |
| switch (p) { |
| case 'B': |
| emitExpr(depth, DartType.BOOL); |
| break; |
| case 'i': // emit small int |
| emitSmallPositiveInt(); |
| break; |
| case 'I': |
| emitExpr(depth, DartType.INT); |
| break; |
| case 'D': |
| emitExpr(depth, fp ? DartType.DOUBLE : DartType.INT); |
| break; |
| case 'S': |
| emitExpr(depth, DartType.STRING, rhsFilter: rhsFilter); |
| break; |
| case 's': // emit small string |
| emitString(length: 2); |
| break; |
| case 'L': |
| emitExpr(depth, DartType.LIST_INT, rhsFilter: rhsFilter); |
| break; |
| case 'X': |
| emitExpr(depth, DartType.SET_INT, rhsFilter: rhsFilter); |
| break; |
| case 'M': |
| emitExpr(depth, DartType.MAP_INT_STRING, rhsFilter: rhsFilter); |
| break; |
| default: |
| throw ArgumentError('Invalid p value: $p'); |
| } |
| } |
| |
| // |
| // Types. |
| // |
| List<DartType> fillTypes1({int limit = 4, bool isFfi = false}) { |
| final list = <DartType>[]; |
| for (int i = 0, n = 1 + rand.nextInt(limit); i < n; i++) { |
| if (isFfi) { |
| list.add(fp ? oneOf([DartType.INT, DartType.DOUBLE]) : DartType.INT); |
| } else { |
| list.add(oneOfSet(dartType.allTypes)); |
| } |
| } |
| return list; |
| } |
| |
| List<List<DartType>> fillTypes2( |
| {bool isFfi = false, int limit2 = 4, int limit1 = 4}) { |
| final list = <List<DartType>>[]; |
| for (int i = 0, n = 1 + rand.nextInt(limit2); i < n; i++) { |
| list.add(fillTypes1(limit: limit1, isFfi: isFfi)); |
| } |
| return list; |
| } |
| |
| List<List<List<DartType>>> fillTypes3(int n, |
| {int limit2 = 4, int limit1 = 4}) { |
| final list = <List<List<DartType>>>[]; |
| for (int i = 0; i < n; i++) { |
| list.add(fillTypes2(limit2: limit2, limit1: limit1)); |
| } |
| return list; |
| } |
| |
| List<DartType> getCurrentProto() { |
| if (currentClass != null) { |
| if (currentMethod != null) { |
| return classMethods[currentClass][currentMethod]; |
| } |
| } else if (currentMethod != null) { |
| return globalMethods[currentMethod]; |
| } |
| return null; |
| } |
| |
| List<DartType> getCurrentFields() { |
| if (currentClass != null) { |
| return classFields[currentClass]; |
| } |
| return null; |
| } |
| |
| void emitFfiType(DartType tp) { |
| if (tp == DartType.INT) { |
| emit(oneOf(['ffi.Int8', 'ffi.Int16', 'ffi.Int32', 'ffi.Int64'])); |
| } else if (tp == DartType.DOUBLE) { |
| emit(oneOf(['ffi.Float', 'ffi.Double'])); |
| } else { |
| throw 'Invalid FFI type ${tp.name}'; |
| } |
| } |
| |
| void emitFfiTypedef(String typeName, List<DartType> pars) { |
| emit("typedef ${typeName} = "); |
| emitFfiType(pars[0]); |
| emit(" Function("); |
| for (int i = 1; i < pars.length; i++) { |
| DartType tp = pars[i]; |
| emitFfiType(tp); |
| if (i != (pars.length - 1)) { |
| emit(', '); |
| } |
| } |
| emitLn(');'); |
| } |
| |
| // |
| // Output. |
| // |
| |
| // Emits indented line to append to program. |
| void emitLn(String line, {bool newline = true}) { |
| file.writeStringSync(' ' * indent); |
| emit(line, newline: newline); |
| } |
| |
| // Emits text to append to program. |
| void emit(String txt, {bool newline = false}) { |
| if (skipStmt || skipExpr) { |
| return; |
| } |
| file.writeStringSync(txt); |
| if (newline) { |
| file.writeStringSync('\n'); |
| } |
| } |
| |
| // Emits one of the given choices. |
| T oneOf<T>(List<T> choices) { |
| return choices[rand.nextInt(choices.length)]; |
| } |
| |
| T oneOfSet<T>(Set<T> choices) { |
| return choices.elementAt(rand.nextInt(choices.length)); |
| } |
| |
| // Special return code to handle oom errors. |
| static const oomExitCode = 254; |
| |
| // Random seed used to generate program. |
| final int seed; |
| |
| // Enables floating-point operations. |
| final bool fp; |
| |
| // Enables FFI method calls. |
| final bool ffi; |
| |
| // Disables nested types. |
| final bool flatTp; |
| |
| // File used for output. |
| final RandomAccessFile file; |
| |
| // Library and ffi api. |
| DartApi api; |
| |
| // Program variables. |
| Random rand; |
| int indent; |
| int nest; |
| int currentClass; |
| int currentMethod; |
| |
| // DartType instance. |
| DartType dartType; |
| |
| // Types of local variables currently in scope. |
| List<DartType> localVars; |
| |
| // Types of global variables. |
| List<DartType> globalVars; |
| |
| // Names of currently active iterator variables. |
| // These are tracked to avoid modifications within the loop body, |
| // which can lead to infinite loops. |
| List<String> iterVars; |
| |
| // Prototypes of all global methods (first element is return type). |
| List<List<DartType>> globalMethods; |
| |
| // Types of fields over all classes. |
| List<List<DartType>> classFields; |
| |
| // Prototypes of all methods over all classes (first element is return type). |
| List<List<List<DartType>>> classMethods; |
| |
| // List of virtual functions per class. Map is from parent class index to List |
| // of overloaded functions from that parent. |
| List<Map<int, List<int>>> virtualClassMethods; |
| |
| // Parent class indices for all classes. |
| List<int> classParents; |
| |
| // Minimization mode extensions. |
| final bool minimize; |
| BigInt smask; |
| BigInt emask; |
| bool skipStmt; |
| bool skipExpr; |
| bool skipExprCntr; |
| int stmtCntr; |
| int exprCntr; |
| |
| // Interesting characters. |
| static const List<String> interestingChars = [ |
| '\\u2665', |
| '\\u{1f600}', // rune |
| ]; |
| |
| // Regular characters. |
| static const regularChars = |
| 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#&()+- '; |
| |
| // Interesting integer values. |
| static const List<int> interestingIntegers = [ |
| 0x0000000000000000, |
| 0x0000000000000001, |
| 0x000000007fffffff, |
| 0x0000000080000000, |
| 0x0000000080000001, |
| 0x00000000ffffffff, |
| 0x0000000100000000, |
| 0x0000000100000001, |
| 0x000000017fffffff, |
| 0x0000000180000000, |
| 0x0000000180000001, |
| 0x00000001ffffffff, |
| 0x7fffffff00000000, |
| 0x7fffffff00000001, |
| 0x7fffffff7fffffff, |
| 0x7fffffff80000000, |
| 0x7fffffff80000001, |
| 0x7fffffffffffffff, |
| 0x8000000000000000, |
| 0x8000000000000001, |
| 0x800000007fffffff, |
| 0x8000000080000000, |
| 0x8000000080000001, |
| 0x80000000ffffffff, |
| 0x8000000100000000, |
| 0x8000000100000001, |
| 0x800000017fffffff, |
| 0x8000000180000000, |
| 0x8000000180000001, |
| 0x80000001ffffffff, |
| 0xffffffff00000000, |
| 0xffffffff00000001, |
| 0xffffffff7fffffff, |
| 0xffffffff80000000, |
| 0xffffffff80000001, |
| 0xffffffffffffffff |
| ]; |
| } |
| |
| // Generate seed. By default (no user-defined nonzero seed given), |
| // pick the system's best way of seeding randomness and then pick |
| // a user-visible nonzero seed. |
| int getSeed(String userSeed) { |
| int seed = int.parse(userSeed); |
| if (seed == 0) { |
| Random rand = Random(); |
| while (seed == 0) { |
| seed = rand.nextInt(1 << 32); |
| } |
| } |
| return seed; |
| } |
| |
| /// Main driver when dartfuzz.dart is run stand-alone. |
| main(List<String> arguments) { |
| final parser = ArgParser() |
| ..addOption('seed', |
| help: 'random seed (0 forces time-based seed)', defaultsTo: '0') |
| ..addFlag('fp', help: 'enables floating-point operations', defaultsTo: true) |
| ..addFlag('ffi', |
| help: 'enables FFI method calls (default: off)', defaultsTo: false) |
| ..addFlag('flat', |
| help: 'enables flat types (default: off)', defaultsTo: false) |
| // Minimization mode extensions. |
| ..addFlag('mini', |
| help: 'enables minimization mode (default: off)', defaultsTo: false) |
| ..addOption('smask', |
| help: 'Bitmask indicating which statements to omit' |
| '(Bit=1 omits)', |
| defaultsTo: '0') |
| ..addOption('emask', |
| help: 'Bitmask indicating which expressions to omit' |
| '(Bit=1 omits)', |
| defaultsTo: '0'); |
| try { |
| final results = parser.parse(arguments); |
| final seed = getSeed(results['seed']); |
| final fp = results['fp']; |
| final ffi = results['ffi']; |
| final flatTp = results['flat']; |
| final file = File(results.rest.single).openSync(mode: FileMode.write); |
| final minimize = results['mini']; |
| final smask = BigInt.parse(results['smask']); |
| final emask = BigInt.parse(results['emask']); |
| final dartFuzz = DartFuzz(seed, fp, ffi, flatTp, file, |
| minimize: minimize, smask: smask, emask: emask); |
| dartFuzz.run(); |
| file.closeSync(); |
| // Print information that will be parsed by minimize.py |
| if (minimize) { |
| // Updated statement mask. |
| // This might be different from the input parameter --smask |
| // since masking a statement that contains nested statements leads to |
| // those being masked as well. |
| print(dartFuzz.smask.toRadixString(10)); |
| // Total number of statements in the generated program. |
| print(dartFuzz.stmtCntr); |
| // Updated expression mask. |
| // This might be different from the input parameter --emask |
| // since masking a statement that contains expressions or |
| // an expression that contains nested expressions leads to |
| // those being masked as well. |
| print(dartFuzz.emask.toRadixString(10)); |
| // Total number of expressions in the generated program. |
| print(dartFuzz.exprCntr); |
| } |
| } catch (e) { |
| print('Usage: dart dartfuzz.dart [OPTIONS] FILENAME\n${parser.usage}\n$e'); |
| exitCode = 255; |
| } |
| } |