| // 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.93'; |
| |
| // Restriction on statements and expressions. |
| const int stmtDepth = 1; |
| const int exprDepth = 2; |
| const int nestDepth = 1; |
| const int numStatements = 2; |
| const int numGlobalVars = 4; |
| const int numLocalVars = 4; |
| const int numGlobalMethods = 4; |
| const int numMethodParams = 4; |
| const int numClasses = 4; |
| const int numExtensionMethodsPerClass = 3; |
| const int numDartTypeExtensions = 5; |
| |
| // Naming conventions. |
| const varName = 'var'; |
| const paramName = 'par'; |
| const localName = 'loc'; |
| const fieldName = 'fld'; |
| const methodName = 'foo'; |
| |
| /// Enum for the different types of methods generated by the fuzzer. |
| enum MethodType { |
| globalMethod, |
| ffiMethod, |
| instanceMethod, |
| callMethod, |
| extensionMethod |
| } |
| |
| /// Base class for all methods in the program generated by DartFuzz. |
| abstract class Method { |
| Method(this.name, this.parameters, this.fuzzer) |
| : recursionAllowed = fuzzer.coinFlip() { |
| if (recursionAllowed) { |
| // Add the recursion depth parameter. |
| parameters.add(DartType.INT); |
| } |
| } |
| |
| String get recursionDepthParamName => '$paramName${parameters.length - 1}'; |
| DartType get returnType => parameters[0]; |
| |
| void emitCallParameters(int depth, bool isRecursiveCall, |
| {RhsFilter? rhsFilter}) { |
| if (isRecursiveCall && !recursionAllowed) { |
| throw StateError('Did not expect a recursive call in $name'); |
| } |
| fuzzer.emitParenWrapped(() => fuzzer.emitCommaSeparated((int i) { |
| // If this function can be called recursively, the last parameter is the |
| // recursion depth parameter. |
| if (i == parameters.length - 1 && recursionAllowed) { |
| if (isRecursiveCall) { |
| // This is a recursive call, so we increase the recursion depth |
| // parameter. |
| fuzzer.emit('$recursionDepthParamName + 1'); |
| } else { |
| // This isn't a recursive call. Our recursion depth is 0. |
| fuzzer.emit('0'); |
| } |
| } else { |
| fuzzer.emitExpr(depth, parameters[i], rhsFilter: rhsFilter); |
| } |
| }, parameters.length, start: 1)); |
| } |
| |
| void disableRecursionScope(Function callback) { |
| final originalState = recursionAllowed; |
| recursionAllowed = false; |
| callback(); |
| recursionAllowed = originalState; |
| } |
| |
| void emitRecursionBaseCase() { |
| fuzzer.emitIfStatement(() { |
| fuzzer.emit('$recursionDepthParamName >= '); |
| fuzzer.emitSmallPositiveInt(); |
| }, () { |
| // Temporarily set recursionAllowed to false so that we don't have a |
| // recursive call in the return statement of the base case. |
| disableRecursionScope(fuzzer.emitReturn); |
| return false; |
| }); |
| } |
| |
| void emitFunctionBody() { |
| if (!recursionAllowed && fuzzer.rollDice(10)) { |
| // Emit a method using "=>" syntax. |
| fuzzer.emit(' => '); |
| fuzzer.emitExpr(0, returnType, includeSemicolon: true); |
| } else { |
| fuzzer.emitBraceWrapped(() { |
| assert(fuzzer.localVars.isEmpty); |
| if (recursionAllowed) { |
| emitRecursionBaseCase(); |
| } |
| if (fuzzer.emitStatements(0)) { |
| fuzzer.emitReturn(); |
| } |
| assert(fuzzer.localVars.isEmpty); |
| }); |
| } |
| } |
| |
| void emitFunctionDefinition() { |
| final type = returnType.dartName; |
| fuzzer.emitLn('$type $name', newline: false); |
| fuzzer.emitParenWrapped(() => fuzzer.emitParDecls(parameters)); |
| emitFunctionBody(); |
| fuzzer.emitNewline(); |
| fuzzer.emitNewline(); |
| } |
| |
| String emitCall(int depth, |
| {RhsFilter? rhsFilter, |
| bool includeSemicolon = false, |
| bool isRecursiveCall = false}) { |
| var outputName = name; |
| fuzzer.emitLn(outputName, newline: false); |
| emitCallParameters(depth + 1, isRecursiveCall, rhsFilter: rhsFilter); |
| if (includeSemicolon) { |
| fuzzer.emit(';'); |
| } |
| return outputName; |
| } |
| |
| // Main emitter for a method. |
| void emit() { |
| emitFunctionDefinition(); |
| } |
| |
| final String name; |
| final List<DartType> parameters; |
| final DartFuzz fuzzer; |
| bool recursionAllowed; |
| } |
| |
| /// Class for global methods generated by DartFuzz. |
| class GlobalMethod extends Method { |
| GlobalMethod(String name, List<DartType> parameters, DartFuzz fuzzer) |
| : super(name, parameters, fuzzer); |
| } |
| |
| /// Class for ffi methods generated by DartFuzz. |
| class FfiMethod extends Method { |
| FfiMethod( |
| String namePrefix, int index, List<DartType> parameters, DartFuzz fuzzer) |
| : ffiCastName = '${namePrefix}FfiCast$index', |
| super('${namePrefix}Ffi$index', parameters, fuzzer); |
| |
| @override |
| void emitFunctionBody() { |
| fuzzer.emitBraceWrapped(() { |
| assert(fuzzer.localVars.isEmpty); |
| if (recursionAllowed) { |
| emitRecursionBaseCase(); |
| } |
| if (fuzzer.emitStatements(0)) { |
| fuzzer.emitReturn(); |
| } |
| assert(fuzzer.localVars.isEmpty); |
| }); |
| } |
| |
| @override |
| void emit() { |
| fuzzer.emitFfiTypedef(this); |
| emitFunctionDefinition(); |
| fuzzer.emitFfiCast('$ffiCastName', '$name', '${name}Type', parameters); |
| fuzzer.emitNewline(); |
| fuzzer.emitNewline(); |
| } |
| |
| final String ffiCastName; |
| } |
| |
| /// Class for instance methods generated by DartFuzz. |
| class InstanceMethod extends Method { |
| InstanceMethod( |
| String name, List<DartType> parameters, DartFuzz fuzzer, this.className) |
| : super(name, parameters, fuzzer); |
| |
| @override |
| String emitCall(int depth, |
| {RhsFilter? rhsFilter, |
| bool includeSemicolon = false, |
| bool isRecursiveCall = false}) { |
| var outputName = name; |
| if (fuzzer.currentClassIndex == null) { |
| // If we're calling an instance method from outside the class, then we |
| // should construct an object of that class first. |
| outputName = '$className().$name'; |
| } |
| fuzzer.emitLn(outputName, newline: false); |
| emitCallParameters(depth + 1, isRecursiveCall, rhsFilter: rhsFilter); |
| if (includeSemicolon) { |
| fuzzer.emit(';'); |
| } |
| return outputName; |
| } |
| |
| final String className; |
| } |
| |
| class CallMethod extends Method { |
| CallMethod(List<DartType> parameters, DartFuzz fuzzer, this.className) |
| : super('call', parameters, fuzzer); |
| |
| @override |
| String emitCall(int depth, |
| {RhsFilter? rhsFilter, |
| bool includeSemicolon = false, |
| bool isRecursiveCall = false}) { |
| String outputName; |
| outputName = '$className()'; |
| fuzzer.emitLn(outputName, newline: false); |
| emitCallParameters(depth + 1, isRecursiveCall, rhsFilter: rhsFilter); |
| if (includeSemicolon) { |
| fuzzer.emit(';'); |
| } |
| return outputName; |
| } |
| |
| @override |
| void emit() { |
| // Override the parent class' call method. |
| fuzzer.emit('@override'); |
| fuzzer.emitNewline(); |
| emitFunctionDefinition(); |
| } |
| |
| final String className; |
| } |
| |
| // Class for extension methods generated by DartFuzz. |
| class ExtensionMethod extends Method { |
| ExtensionMethod(String name, List<DartType> parameters, DartFuzz fuzzer, |
| this.className, this.extensionName, this.type) |
| : super(name, parameters, fuzzer); |
| @override |
| String emitCall( |
| int depth, { |
| RhsFilter? rhsFilter, |
| bool includeSemicolon = false, |
| bool isRecursiveCall = false, |
| }) { |
| var outputName = name; |
| |
| // If we're calling an extension method on a class from outside the class, |
| // we must call it using an object of the class or using explicit extension |
| // application. Extension methods on Dart types are presently always called |
| // using a variable of the Dart type or using explicit extension |
| // application. |
| if (fuzzer.currentClassIndex == null || type != null) { |
| var invokingObject = type != null |
| ? fuzzer.pickScalarVar(type!.toNonNullable()) |
| : '$className()'; |
| |
| if (extensionName != null && fuzzer.coinFlip()) { |
| outputName = '$extensionName($invokingObject).$name'; |
| } else { |
| outputName = '$invokingObject.$name'; |
| } |
| } |
| fuzzer.emitLn(outputName, newline: false); |
| emitCallParameters(depth + 1, isRecursiveCall, rhsFilter: rhsFilter); |
| if (includeSemicolon) { |
| fuzzer.emit(';'); |
| } |
| return outputName; |
| } |
| |
| final String className; |
| final String extensionName; |
| final DartType? type; |
| } |
| |
| // 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); |
| static 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. |
| static 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) : typeToLibraryMethods = DartLib.typeToLibraryMethods { |
| if (ffi) { |
| typeToLibraryMethods[DartType.INT] = [ |
| ...[ |
| DartLib('intComputation', |
| [DartType.VOID, ...List<DartType>.filled(4, DartType.INT)], true), |
| DartLib('takeMaxUint16', [DartType.VOID, DartType.INT], true), |
| DartLib( |
| 'sumPlus42', [DartType.VOID, DartType.INT, DartType.INT], true), |
| DartLib('returnMaxUint8', [DartType.VOID, DartType.VOID], true), |
| DartLib('returnMaxUint16', [DartType.VOID, DartType.VOID], true), |
| DartLib('returnMaxUint32', [DartType.VOID, DartType.VOID], true), |
| DartLib('returnMinInt8', [DartType.VOID, DartType.VOID], true), |
| DartLib('returnMinInt16', [DartType.VOID, DartType.VOID], true), |
| DartLib('returnMinInt32', [DartType.VOID, DartType.VOID], true), |
| DartLib('takeMinInt16', [DartType.VOID, DartType.INT], true), |
| DartLib('takeMinInt32', [DartType.VOID, DartType.INT], true), |
| DartLib('uintComputation', |
| [DartType.VOID, ...List<DartType>.filled(4, DartType.INT)], true), |
| DartLib('sumSmallNumbers', |
| [DartType.VOID, ...List<DartType>.filled(6, DartType.INT)], true), |
| DartLib('takeMinInt8', [DartType.VOID, DartType.INT], true), |
| DartLib('takeMaxUint32', [DartType.VOID, DartType.INT], true), |
| DartLib('takeMaxUint8', [DartType.VOID, DartType.INT], true), |
| DartLib('minInt64', [DartType.VOID, DartType.VOID], true), |
| DartLib('minInt32', [DartType.VOID, DartType.VOID], true), |
| // Use small int to avoid overflow divergences due to size |
| // differences in intptr_t on 32-bit and 64-bit platforms. |
| DartLib('sumManyIntsOdd', |
| [DartType.VOID, ...List<DartType>.filled(11, DartType.INT)], true, |
| restrictions: [ |
| Restriction.none, |
| ...List<Restriction>.filled(11, Restriction.small) |
| ]), |
| DartLib('sumManyInts', |
| [DartType.VOID, ...List<DartType>.filled(10, DartType.INT)], true, |
| restrictions: [ |
| Restriction.none, |
| ...List<Restriction>.filled(10, Restriction.small) |
| ]), |
| DartLib('regress37069', |
| [DartType.VOID, ...List<DartType>.filled(11, DartType.INT)], true, |
| restrictions: [ |
| Restriction.none, |
| ...List<Restriction>.filled(11, Restriction.small) |
| ]), |
| ], |
| ...DartLib.intLibs, |
| ]; |
| |
| typeToLibraryMethods[DartType.DOUBLE] = [ |
| if (ffi) ...[ |
| DartLib('times1_337Float', [DartType.VOID, DartType.DOUBLE], true), |
| DartLib( |
| 'sumManyDoubles', |
| [DartType.VOID, ...List<DartType>.filled(10, DartType.DOUBLE)], |
| true), |
| DartLib('times1_337Double', [DartType.VOID, DartType.DOUBLE], true), |
| DartLib( |
| 'sumManyNumbers', |
| [ |
| DartType.VOID, |
| DartType.INT, |
| DartType.DOUBLE, |
| DartType.INT, |
| DartType.DOUBLE, |
| DartType.INT, |
| DartType.DOUBLE, |
| DartType.INT, |
| DartType.DOUBLE, |
| DartType.INT, |
| DartType.DOUBLE, |
| DartType.INT, |
| DartType.DOUBLE, |
| DartType.INT, |
| DartType.DOUBLE, |
| DartType.INT, |
| DartType.DOUBLE, |
| DartType.INT, |
| DartType.DOUBLE, |
| DartType.INT, |
| DartType.DOUBLE |
| ], |
| true), |
| DartLib('inventFloatValue', [DartType.VOID, DartType.VOID], true), |
| DartLib('smallDouble', [DartType.VOID, DartType.VOID], true), |
| ], |
| ...DartLib.doubleLibs, |
| ]; |
| } |
| } |
| |
| final typeToLibraryMethods; |
| } |
| |
| /// 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); |
| currentClassIndex = null; |
| currentMethod = null; |
| currentMethodIndex = 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 = getMethods( |
| numGlobalMethods, numMethodParams, MethodType.globalMethod, |
| namePrefix: methodName); |
| classFields = fillTypes2(limit2: numClasses, limit1: numLocalVars); |
| final numClassMethods = 1 + numClasses - classFields.length; |
| classMethods = getClassMethods(classFields.length, numClassMethods, |
| numMethodParams, fillTypes1(limit: numMethodParams)); |
| virtualClassMethods = <Map<int, List<int>>>[]; |
| classParents = <int>[]; |
| // Setup optional ffi methods and types. |
| if (ffi) { |
| globalMethods.addAll(getMethods( |
| numGlobalMethods, numMethodParams, MethodType.ffiMethod, |
| namePrefix: methodName)); |
| } |
| // Generate. |
| emitHeader(); |
| emitVariableDeclarations(varName, globalVars); |
| emitMethods(globalMethods); |
| for (var i = 0; i < numDartTypeExtensions; ++i) { |
| var type = oneOfSet(dartType.allTypes); |
| emitAndAddExtensionMethods(globalMethods, type.name, '${methodName}E$i', |
| '$methodName${i}_Extension', |
| type: type); |
| } |
| emitClasses(); |
| emitMain(); |
| // Sanity. |
| assert(currentClassIndex == null); |
| assert(currentMethod == null); |
| assert(currentMethodIndex == null); |
| assert(indent == 0); |
| assert(nest == 0); |
| assert(localVars.isEmpty); |
| } |
| |
| List<Method> getMethods(int maxMethods, int maxParams, MethodType methodType, |
| {String? className, |
| String? extensionName, |
| String? namePrefix, |
| DartType? type}) { |
| final list = <Method>[]; |
| for (var i = 0, n = chooseOneUpTo(maxMethods); i < n; i++) { |
| final params = fillTypes1( |
| limit: maxParams, isFfi: methodType == MethodType.ffiMethod); |
| switch (methodType) { |
| case MethodType.globalMethod: |
| list.add(GlobalMethod('$namePrefix$i', params, this)); |
| break; |
| case MethodType.ffiMethod: |
| list.add(FfiMethod(namePrefix!, i, params, this)); |
| break; |
| case MethodType.instanceMethod: |
| list.add(InstanceMethod('$namePrefix$i', params, this, className!)); |
| break; |
| case MethodType.extensionMethod: |
| list.add(ExtensionMethod( |
| '$namePrefix$i', params, this, className!, extensionName!, type)); |
| break; |
| default: |
| break; |
| } |
| } |
| return list; |
| } |
| |
| List<List<Method>> getClassMethods(int numClasses, int maxMethods, |
| int maxParams, List<DartType> callMethodParams) { |
| final methodsForAllClasses = <List<Method>>[]; |
| for (var i = 0; i < numClasses; i++) { |
| final methodsForCurrentClass = getMethods( |
| maxMethods, maxParams, MethodType.instanceMethod, |
| className: 'X$i', namePrefix: '$methodName${i}_'); |
| // Add the call method for the current class. The prototype for the call |
| // method is pre-decided. This is because we are possibly overriding the |
| // parent class' call method, in which case the prototype should be the |
| // same. To simplify the dartfuzz logic, all call methods have the same |
| // prototype. |
| methodsForCurrentClass.add(CallMethod(callMethodParams, this, 'X$i')); |
| methodsForAllClasses.add(methodsForCurrentClass); |
| } |
| return methodsForAllClasses; |
| } |
| |
| // |
| // General Helpers. |
| // |
| |
| BigInt genMask(int m) => BigInt.from(1) << m; |
| int choose(int c) => rand.nextInt(c); |
| int chooseOneUpTo(int c) => choose(c) + 1; |
| bool coinFlip() => rand.nextBool(); |
| bool rollDice(int c) => (choose(c) == 0); |
| double uniform() => rand.nextDouble(); |
| |
| // Picks one of the given choices. |
| T oneOf<T>(List<T> choices) => choices[choose(choices.length)]; |
| T oneOfSet<T>(Set<T> choices) => choices.elementAt(choose(choices.length)); |
| |
| // |
| // Code Structure Helpers. |
| // |
| |
| void incIndent() => indent += 2; |
| void decIndent() => indent -= 2; |
| |
| void emitZero() => emit('0'); |
| |
| void emitTryCatchFinally(Function tryBody, Function catchBody, |
| {Function? finallyBody}) { |
| emitLn('try ', newline: false); |
| emitBraceWrapped(() => tryBody()); |
| emit(' on OutOfMemoryError '); |
| emitBraceWrapped(() => emitLn('exit($oomExitCode);', newline: false)); |
| emit(' on StackOverflowError '); |
| emitBraceWrapped(() => emitLn('exit($oomExitCode);', newline: false)); |
| emit(' catch (e, st) '); |
| emitBraceWrapped(catchBody); |
| if (finallyBody != null) { |
| emit(' finally '); |
| emitBraceWrapped(finallyBody); |
| } |
| } |
| |
| dynamic emitWrapped(List<String> pair, Function exprEmitter, |
| {bool shouldIndent = true}) { |
| assert(pair.length == 2); |
| emit(pair[0]); |
| if (shouldIndent) { |
| incIndent(); |
| emitNewline(); |
| } |
| final result = exprEmitter(); |
| if (shouldIndent) { |
| decIndent(); |
| emitNewline(); |
| emitLn(pair[1], newline: false); |
| } else { |
| emit(pair[1]); |
| } |
| return result; |
| } |
| |
| dynamic emitParenWrapped(Function exprEmitter, |
| {bool includeSemicolon = false}) { |
| final result = |
| emitWrapped(const ['(', ')'], exprEmitter, shouldIndent: false); |
| if (includeSemicolon) { |
| emit(';'); |
| } |
| return result; |
| } |
| |
| dynamic emitBraceWrapped(Function exprEmitter, {bool shouldIndent = true}) => |
| emitWrapped(const ['{', '}'], exprEmitter, shouldIndent: shouldIndent); |
| |
| dynamic emitSquareBraceWrapped(Function exprEmitter) => |
| emitWrapped(const ['[', ']'], exprEmitter, shouldIndent: false); |
| |
| dynamic emitCommaSeparated(Function(int) elementBuilder, int length, |
| {int start = 0, bool newline = false}) { |
| for (var i = start; i < length; ++i) { |
| elementBuilder(i); |
| if (i + 1 != length) { |
| emit(',', newline: newline); |
| if (!newline) { |
| emit(' '); |
| } |
| } |
| } |
| } |
| |
| void emitFunctionDefinition(String name, Function body, {String? retType}) { |
| emitIndentation(); |
| if (retType != null) { |
| emit(retType + ' '); |
| } |
| emit(name); |
| emit('() '); // TODO(bkonyi): handle params |
| |
| emitBraceWrapped(body); |
| } |
| |
| bool emitIfStatement( |
| Function ifConditionEmitter, bool Function() ifBodyEmitter, |
| {bool Function()? elseBodyEmitter}) { |
| emitLn('if ', newline: false); |
| emitParenWrapped(ifConditionEmitter); |
| emit(' '); |
| final bool ifMayFallThrough = emitBraceWrapped(ifBodyEmitter); |
| var elseMayFallThrough = true; |
| if (elseBodyEmitter != null) { |
| emit(' else '); |
| elseMayFallThrough = emitBraceWrapped(elseBodyEmitter); |
| } |
| return ifMayFallThrough || elseMayFallThrough; |
| } |
| |
| void emitImport(String library, {String? asName}) => (asName == null) |
| ? emitLn("import '$library';") |
| : emitLn("import '$library' as $asName;"); |
| |
| void emitBinaryComparison(Function e1, String op, Function e2, |
| {bool includeSemicolon = false}) { |
| e1(); |
| emit(' $op '); |
| e2(); |
| if (includeSemicolon) { |
| emit(';'); |
| } |
| } |
| |
| // Randomly specialize interface if possible. E.g. num to int. |
| DartType maybeSpecializeInterface(DartType tp) { |
| if (!dartType.isSpecializable(tp)) return tp; |
| var 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'; |
| } |
| } |
| |
| // 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 = 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 = 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('// @dart=2.14'); |
| emitNewline(); |
| emitImport('dart:async'); |
| emitImport('dart:cli'); |
| emitImport('dart:collection'); |
| emitImport('dart:convert'); |
| emitImport('dart:core'); |
| emitImport('dart:io'); |
| emitImport('dart:isolate'); |
| emitImport('dart:math'); |
| emitImport('dart:typed_data'); |
| if (ffi) { |
| emitImport('dart:ffi', asName: 'ffi'); |
| emitLn(DartFuzzFfiApi.ffiapi); |
| } |
| emitNewline(); |
| } |
| |
| void emitFfiCast(String dartFuncName, String ffiFuncName, String typeName, |
| List<DartType> pars) { |
| emit('${pars[0].name} Function'); |
| emitParenWrapped(() => emitCommaSeparated( |
| (int i) => emit('${pars[i].name}'), pars.length, |
| start: 1)); |
| emit(' $dartFuncName = ffi.Pointer.fromFunction<$typeName>'); |
| emitParenWrapped(() { |
| emit('$ffiFuncName, '); |
| emitLiteral(0, pars[0], smallPositiveValue: true); |
| }); |
| emit('.cast<ffi.NativeFunction<$typeName>>().asFunction();'); |
| emitNewline(); |
| } |
| |
| void emitMethods(List<Method> methods) { |
| for (var i = 0; i < methods.length; i++) { |
| currentMethod = methods[i]; |
| currentMethodIndex = i; |
| currentMethod!.emit(); |
| currentMethod = null; |
| currentMethodIndex = null; |
| } |
| } |
| |
| // Randomly overwrite some methods from the parent classes. |
| void emitVirtualMethods() { |
| final currentClassTmp = currentClassIndex; |
| var parentClass = classParents[currentClassIndex!]; |
| final vcm = <int, List<int>>{}; |
| // Chase randomly up in class hierarchy. |
| while (parentClass >= 0) { |
| vcm[parentClass] = <int>[]; |
| for (var j = 0, n = classMethods[parentClass].length; j < n; j++) { |
| // Call methods are presently always already overriden, so we |
| // shouldn't override them here. |
| if (rollDice(8) && !(classMethods[parentClass][j] is CallMethod)) { |
| currentClassIndex = parentClass; |
| currentMethod = classMethods[parentClass][j]; |
| currentMethodIndex = j; |
| classMethods[parentClass][j].emit(); |
| vcm[parentClass]!.add(currentMethodIndex!); |
| currentClassIndex = null; |
| currentMethod = null; |
| currentMethodIndex = null; |
| } |
| } |
| if (coinFlip() || classParents.length > parentClass) { |
| break; |
| } else { |
| parentClass = classParents[parentClass]; |
| } |
| } |
| currentClassIndex = currentClassTmp; |
| virtualClassMethods.add(vcm); |
| } |
| |
| void emitClasses() { |
| assert(classFields.length == classMethods.length); |
| for (var i = 0; i < classFields.length; i++) { |
| if (i == 0) { |
| classParents.add(-1); |
| emit('class X0 '); |
| } else { |
| final parentClass = choose(i); |
| classParents.add(parentClass); |
| if (coinFlip()) { |
| // Inheritance |
| emit('class X$i extends X$parentClass '); |
| } else { |
| // Mixin |
| if (classParents[parentClass] >= 0) { |
| emit( |
| 'class X$i extends X${classParents[parentClass]} with X$parentClass '); |
| } else { |
| emit('class X$i with X$parentClass '); |
| } |
| } |
| } |
| emitBraceWrapped(() { |
| emitVariableDeclarations('$fieldName${i}_', classFields[i]); |
| currentClassIndex = i; |
| emitVirtualMethods(); |
| emitMethods(classMethods[currentClassIndex!]); |
| emitFunctionDefinition('run', () { |
| if (i > 0) { |
| // FIXME(bkonyi): fix potential issue where we try to apply a class |
| // as a mixin when it calls super. |
| emitLn('super.run();'); |
| } |
| assert(localVars.isEmpty); |
| emitStatements(0); |
| assert(localVars.isEmpty); |
| }, retType: 'void'); |
| }); |
| emitNewline(); |
| emitNewline(); |
| emitAndAddExtensionMethods( |
| classMethods[currentClassIndex!], |
| 'X$currentClassIndex', |
| 'XE$currentClassIndex', |
| '$methodName${currentClassIndex}_Extension'); |
| currentClassIndex = null; |
| } |
| } |
| |
| void emitAndAddExtensionMethods( |
| List<Method> methodList, className, extensionName, namePrefix, |
| {DartType? type}) { |
| var endIndex = methodList.length; |
| methodList.addAll(getMethods(numExtensionMethodsPerClass, numMethodParams, |
| MethodType.extensionMethod, |
| className: className, |
| // Randomly select between named and anonymous extensions. |
| extensionName: coinFlip() ? extensionName : '', |
| namePrefix: namePrefix, |
| type: type)); |
| emit('extension $extensionName on $className '); |
| emitBraceWrapped(() { |
| // Emit the newly added methods. |
| for (var i = endIndex; i < methodList.length; i++) { |
| currentMethod = methodList[i]; |
| currentMethodIndex = i; |
| methodList[i].emit(); |
| currentMethod = null; |
| currentMethodIndex = null; |
| } |
| }); |
| emitNewline(); |
| emitNewline(); |
| } |
| |
| 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'); |
| emitIfStatement(() => emit('ffiTestFunctions == null'), |
| () => emitPrint('Did not load ffi test functions')); |
| emitNewline(); |
| } |
| } |
| |
| void emitMain() => emitFunctionDefinition('main', () { |
| emitLoadFfiLib(); |
| |
| // Call each global method once. |
| for (var i = 0; i < globalMethods.length; i++) { |
| late String outputName; |
| emitTryCatchFinally( |
| () => outputName = |
| globalMethods[i].emitCall(1, includeSemicolon: true), |
| () => emitPrint('${outputName}() throws')); |
| emitNewline(); |
| } |
| |
| // Call each class method once. |
| for (var i = 0; i < classMethods.length; i++) { |
| for (var j = 0; j < classMethods[i].length; j++) { |
| late String outputName; |
| emitNewline(); |
| emitTryCatchFinally( |
| () => outputName = |
| classMethods[i][j].emitCall(1, includeSemicolon: true), |
| () => emitPrint('$outputName throws')); |
| } |
| // Call each virtual class method once. |
| var parentClass = classParents[i]; |
| while (parentClass >= 0) { |
| if (virtualClassMethods[i].containsKey(parentClass)) { |
| for (var j = 0; |
| j < virtualClassMethods[i][parentClass]!.length; |
| j++) { |
| late String outputName; |
| emitNewline(); |
| emitTryCatchFinally( |
| () => outputName = classMethods[parentClass][j] |
| .emitCall(1, includeSemicolon: true), |
| () => emitPrint('$outputName throws')); |
| } |
| } |
| parentClass = classParents[parentClass]; |
| } |
| } |
| |
| emitNewline(); |
| emitTryCatchFinally(() { |
| emitLn('X${classFields.length - 1}().run();', newline: false); |
| }, () { |
| emitPrint('X${classFields.length - 1}().run() throws'); |
| }); |
| |
| emitNewline(); |
| emitTryCatchFinally(() { |
| for (var i = 0; i < globalVars.length; i++) { |
| emitPrint('$varName$i: \$$varName$i'); |
| emitNewline(); |
| } |
| }, () => emitPrint('print() throws')); |
| }); |
| |
| // |
| // Declarations. |
| // |
| |
| void emitVariableDeclarations(String name, List<DartType> vars) { |
| for (var i = 0; i < vars.length; i++) { |
| var tp = vars[i]; |
| final varName = '$name$i'; |
| emitVariableDeclaration(varName, tp, |
| initializerEmitter: () => emitConstructorOrLiteral(0, tp)); |
| } |
| emitNewline(); |
| } |
| |
| void emitVariableDeclaration(String name, DartType tp, |
| {Function? initializerEmitter, |
| bool indent = true, |
| bool newline = true, |
| bool includeSemicolon = true}) { |
| final typeName = tp.dartName; |
| if (indent) { |
| emitIndentation(); |
| } |
| emit('$typeName $name', newline: false); |
| if (initializerEmitter != null) { |
| emit(' = '); |
| initializerEmitter(); |
| } |
| if (includeSemicolon) { |
| emit(';', newline: newline); |
| } |
| } |
| |
| void emitParDecls(List<DartType> pars) => emitCommaSeparated((int i) { |
| var tp = pars[i]; |
| emit('${tp.dartName} $paramName$i'); |
| }, pars.length, start: 1); |
| |
| // |
| // Comments (for FE and analysis tools). |
| // |
| |
| void emitComment() { |
| switch (choose(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() { |
| final tp = oneOfSet(dartType.allTypes); |
| String assignOp; |
| if (DartType.isGrowableType(tp)) { |
| // Assignments like *= and += on growable types (String, List, ...) |
| // may lead to OOM, especially within loops. |
| // TODO: Implement a more specific heuristic that selectively allows |
| // modifying assignment operators (like += *=) for growable types. |
| assignOp = '='; |
| } else { |
| // Select one of the assign operations for the given type. |
| var assignOps = dartType.assignOps(tp); |
| if (assignOps.isEmpty) { |
| throw 'No assign operation for $tp'; |
| } |
| assignOp = oneOfSet(assignOps); |
| } |
| emitIndentation(); |
| // Emit a variable of the lhs type. |
| final emittedVar = emitVar(0, tp, isLhs: true, assignOp: assignOp)!; |
| var rhsFilter = RhsFilter.fromDartType(tp, emittedVar); |
| emit(' $assignOp '); |
| // Select one of the possible rhs types for the given lhs type and assign |
| // operation. |
| var rhsTypes = dartType.assignOpRhs(tp, assignOp); |
| if (rhsTypes.isEmpty) { |
| throw 'No rhs type for assign $tp $assignOp'; |
| } |
| var rhsType = oneOfSet(rhsTypes); |
| emitExpr(0, rhsType, rhsFilter: rhsFilter, includeSemicolon: true); |
| return true; |
| } |
| |
| // Emit a print statement. |
| bool emitPrint([String? body]) { |
| emitLn('print', newline: false); |
| emitParenWrapped(() { |
| if (body != null) { |
| emit("'$body'"); |
| } else { |
| var tp = oneOfSet(dartType.allTypes); |
| emitExpr(0, tp); |
| } |
| }, includeSemicolon: true); |
| return true; |
| } |
| |
| // Emit a return statement. |
| bool emitReturn() { |
| var proto = getCurrentProto(); |
| if (proto == null || proto[0] == DartType.VOID) { |
| emitLn('return;'); |
| } else { |
| emitLn('return ', newline: false); |
| emitExpr(0, proto[0], includeSemicolon: true); |
| } |
| return false; |
| } |
| |
| // Emit a throw statement. |
| bool emitThrow() { |
| var tp; |
| do { |
| tp = oneOfSet(dartType.allTypes).toNonNullable(); |
| } while (tp == DartType.NULL); |
| emitLn('throw ', newline: false); |
| emitExpr(0, tp, includeSemicolon: true); |
| return false; |
| } |
| |
| // Emit a one-way if statement. |
| bool emitIf1(int depth) => emitIfStatement( |
| () => emitExpr(0, DartType.BOOL), () => emitStatements(depth + 1)); |
| |
| // Emit a two-way if statement. |
| bool emitIf2(int depth) => emitIfStatement( |
| () => emitExpr(0, DartType.BOOL), () => emitStatements(depth + 1), |
| elseBodyEmitter: () => emitStatements(depth + 1)); |
| |
| // Emit a simple increasing for-loop. |
| bool emitFor(int depth) { |
| // Make deep nesting of loops increasingly unlikely. |
| if (choose(nest + 1) > nestDepth) { |
| return emitAssign(); |
| } |
| final i = localVars.length; |
| emitLn('for ', newline: false); |
| emitParenWrapped(() { |
| final name = '$localName$i'; |
| emitVariableDeclaration(name, DartType.INT, |
| initializerEmitter: emitZero, newline: false, indent: false); |
| emitBinaryComparison(() => emit(name), '<', emitSmallPositiveInt, |
| includeSemicolon: true); |
| emit('$name++'); |
| }); |
| emitBraceWrapped(() { |
| nest++; |
| iterVars.add('$localName$i'); |
| localVars.add(DartType.INT); |
| emitStatements(depth + 1); |
| localVars.removeLast(); |
| iterVars.removeLast(); |
| nest--; |
| }); |
| return true; |
| } |
| |
| // Emit a simple membership for-in-loop. |
| bool emitForIn(int depth) { |
| // Make deep nesting of loops increasingly unlikely. |
| if (choose(nest + 1) > nestDepth) { |
| return emitAssign(); |
| } |
| final 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 ', newline: false); |
| emitParenWrapped(() { |
| emit('${elementType.dartName} $localName$i in '); |
| localVars.add(null); // declared, but don't use |
| emitExpr(0, iterType.toNonNullable()); |
| localVars.removeLast(); // will get type |
| }); |
| emitBraceWrapped(() { |
| nest++; |
| localVars.add(elementType); |
| emitStatements(depth + 1); |
| localVars.removeLast(); |
| nest--; |
| }); |
| return true; |
| } |
| |
| // Emit a simple membership forEach loop. |
| bool emitForEach(int depth) { |
| // Make deep nesting of loops increasingly unlikely. |
| if (choose(nest + 1) > nestDepth) { |
| return emitAssign(); |
| } |
| emitIndentation(); |
| |
| // Select one map type to be used in forEach loop. |
| final mapType = oneOfSet(dartType.mapTypes); |
| final emittedVar = emitScalarVar(mapType.toNonNullable(), isLhs: false); |
| iterVars.add(emittedVar); |
| emit('.forEach'); |
| final i = localVars.length; |
| final j = i + 1; |
| emitParenWrapped(() { |
| emitParenWrapped(() => emit('$localName$i, $localName$j')); |
| emitBraceWrapped(() { |
| // Reset, since forEach cannot break out of own or enclosing context. |
| final nestTmp = nest; |
| nest = 0; |
| // Reset, since return type of forEach is void. |
| final currentMethodIndexTemp = currentMethodIndex; |
| currentMethodIndex = null; |
| |
| // 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(); |
| |
| currentMethodIndex = currentMethodIndexTemp; |
| nest = nestTmp; |
| }); |
| }, includeSemicolon: true); |
| return true; |
| } |
| |
| // Emit a while-loop. |
| bool emitWhile(int depth) { |
| // Make deep nesting of loops increasingly unlikely. |
| if (choose(nest + 1) > nestDepth) { |
| return emitAssign(); |
| } |
| final i = localVars.length; |
| emitIndentation(); |
| emitBraceWrapped(() { |
| final name = '$localName$i'; |
| emitVariableDeclaration(name, DartType.INT, |
| initializerEmitter: () => emitSmallPositiveInt()); |
| emitLn('while ', newline: false); |
| emitParenWrapped( |
| () => emitBinaryComparison(() => emit('--$name'), '>', emitZero)); |
| emitBraceWrapped(() { |
| nest++; |
| iterVars.add(name); |
| localVars.add(DartType.INT); |
| emitStatements(depth + 1); |
| localVars.removeLast(); |
| iterVars.removeLast(); |
| nest--; |
| }); |
| }); |
| return true; |
| } |
| |
| // Emit a do-while-loop. |
| bool emitDoWhile(int depth) { |
| // Make deep nesting of loops increasingly unlikely. |
| if (choose(nest + 1) > nestDepth) { |
| return emitAssign(); |
| } |
| final i = localVars.length; |
| emitIndentation(); |
| emitBraceWrapped(() { |
| final name = '$localName$i'; |
| emitVariableDeclaration(name, DartType.INT, initializerEmitter: emitZero); |
| emitLn('do ', newline: false); |
| emitBraceWrapped(() { |
| nest++; |
| iterVars.add(name); |
| localVars.add(DartType.INT); |
| emitStatements(depth + 1); |
| localVars.removeLast(); |
| iterVars.removeLast(); |
| nest--; |
| }); |
| emit(' while '); |
| emitParenWrapped( |
| () => emitBinaryComparison( |
| () => emit('++$name'), '<', emitSmallPositiveInt), |
| includeSemicolon: true); |
| }); |
| emitNewline(); |
| return true; |
| } |
| |
| // Emit a break/continue when inside iteration. |
| bool emitBreakOrContinue(int depth) { |
| if (nest > 0) { |
| switch (choose(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) { |
| void emitCase(Function bodyEmitter, {int? kase}) { |
| if (kase == null) { |
| emitLn('default: ', newline: false); |
| } else { |
| emitLn('case $kase: ', newline: false); |
| } |
| emitBraceWrapped(() { |
| bodyEmitter(); |
| emitNewline(); |
| emitLn('break;', |
| newline: false); // always generate, avoid FE complaints |
| }); |
| } |
| |
| emitLn('switch ', newline: false); |
| emitParenWrapped(() => emitExpr(0, DartType.INT)); |
| emitBraceWrapped(() { |
| var start = choose(1 << 32); |
| var step = chooseOneUpTo(10); |
| final maxCases = 3; |
| for (var i = 0; i < maxCases; i++, start += step) { |
| emitCase(() => emitStatement(depth + 1), |
| kase: (i == 2 && coinFlip()) ? null : start); |
| if (i + 1 != maxCases) { |
| emitNewline(); |
| } |
| } |
| }); |
| return true; |
| } |
| |
| // Emit a new program scope that introduces a new local variable. |
| bool emitScope(int depth) { |
| emitIndentation(); |
| emitBraceWrapped(() { |
| var tp = oneOfSet(dartType.allTypes); |
| final i = localVars.length; |
| final name = '$localName$i'; |
| emitVariableDeclaration(name, tp, initializerEmitter: () { |
| localVars.add(null); // declared, but don't use |
| emitExpr(0, tp); |
| localVars.removeLast(); // will get type |
| }); |
| localVars.add(tp); |
| emitStatements(depth + 1); |
| localVars.removeLast(); |
| }); |
| return true; |
| } |
| |
| // Emit try/catch/finally. |
| bool emitTryCatch(int depth) { |
| final emitStatementsClosure = () => emitStatements(depth + 1); |
| emitLn('try ', newline: false); |
| emitBraceWrapped(emitStatementsClosure); |
| emit(' on OutOfMemoryError '); |
| emitBraceWrapped(() => emitLn('exit($oomExitCode);', newline: false)); |
| emit(' on StackOverflowError '); |
| emitBraceWrapped(() => emitLn('exit($oomExitCode);', newline: false)); |
| emit(' catch (exception, stackTrace) ', newline: false); |
| emitBraceWrapped(emitStatementsClosure); |
| if (coinFlip()) { |
| emit(' finally ', newline: false); |
| emitBraceWrapped(emitStatementsClosure); |
| } |
| return true; |
| } |
| |
| // Emit a single statement. |
| bool emitSingleStatement(int depth) { |
| // Throw in a comment every once in a while. |
| if (rollDice(10)) { |
| emitComment(); |
| } |
| // Continuing nested statements becomes less likely as the depth grows. |
| if (choose(depth + 1) > stmtDepth) { |
| return emitAssign(); |
| } |
| // Possibly nested statement. |
| switch (choose(17)) { |
| // 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); |
| case 14: |
| emitLibraryCall(depth, DartType.VOID, includeSemicolon: true); |
| return true; |
| 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(); |
| var ret = emitSingleStatement(depth); |
| processStmtClose(resetSkipStmt); |
| return ret; |
| } |
| |
| // Emit statements. Returns true if code may fall-through. |
| bool emitStatements(int depth) { |
| var s = chooseOneUpTo(numStatements); |
| for (var i = 0; i < s; i++) { |
| if (!emitStatement(depth)) { |
| return false; // rest would be dead code |
| } |
| if (i + 1 != s) { |
| emitNewline(); |
| } |
| } |
| return true; |
| } |
| |
| // |
| // Expressions. |
| // |
| |
| void emitBool() => emit(coinFlip() ? 'true' : 'false'); |
| |
| void emitNull() => emit('null'); |
| |
| void emitSmallPositiveInt({int limit = 50, bool includeSemicolon = false}) { |
| emit('${choose(limit)}'); |
| if (includeSemicolon) { |
| emit(';'); |
| } |
| } |
| |
| void emitSmallNegativeInt() => emit('-${choose(100)}'); |
| |
| void emitInt() { |
| switch (choose(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('${uniform()}'); |
| |
| void emitNum({bool smallPositiveValue = false}) { |
| if (!fp || coinFlip()) { |
| if (smallPositiveValue) { |
| emitSmallPositiveInt(); |
| } else { |
| emitInt(); |
| } |
| } else { |
| emitDouble(); |
| } |
| } |
| |
| void emitChar() { |
| switch (choose(10)) { |
| // Favors regular char. |
| case 0: |
| emit(oneOf(interestingChars)); |
| break; |
| default: |
| emit(regularChars[choose(regularChars.length)]); |
| break; |
| } |
| } |
| |
| void emitString({int length = 8}) { |
| final n = choose(length); |
| emit("'"); |
| for (var i = 0; i < n; i++) { |
| emitChar(); |
| } |
| emit("'"); |
| } |
| |
| void emitElementExpr(int depth, DartType tp, |
| {RhsFilter? rhsFilter, bool isConst = false}) { |
| // Inside a constant collection, keep collection elements constants too. |
| if (isConst) { |
| if (DartType.isCollectionType(tp)) { |
| emitConstCollection(depth + 1, tp, rhsFilter: rhsFilter); |
| return; |
| } |
| } |
| // Use literals outside a class or inside a constant collection. |
| if (currentMethodIndex == null || isConst) { |
| emitLiteral(depth, tp, rhsFilter: rhsFilter); |
| } else { |
| emitExpr(depth, tp, rhsFilter: rhsFilter); |
| } |
| } |
| |
| void emitElement(int depth, DartType tp, |
| {RhsFilter? rhsFilter, bool isConst = false}) { |
| // 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); |
| emitIndentation(); |
| emitElementExpr(depth, indexType, rhsFilter: rhsFilter, isConst: isConst); |
| emit(' : '); |
| // Emit construct for the map value type. |
| emitElementExpr(depth, elementType, |
| rhsFilter: rhsFilter, isConst: isConst); |
| } else { |
| // List and Set types. |
| emitElementExpr(depth, elementType, |
| rhsFilter: rhsFilter, isConst: isConst); |
| } |
| } |
| |
| void emitCollectionElement(int depth, DartType tp, {RhsFilter? rhsFilter}) { |
| var r = (depth <= exprDepth) ? choose(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: |
| emitLn('...', newline: false); // spread |
| emitCollection(depth + 1, tp, rhsFilter: rhsFilter); |
| break; |
| case 1: |
| emitLn('if ', newline: false); |
| emitParenWrapped(() => |
| emitElementExpr(depth + 1, DartType.BOOL, rhsFilter: rhsFilter)); |
| emitCollectionElement(depth + 1, tp, rhsFilter: rhsFilter); |
| if (coinFlip()) { |
| emitNewline(); |
| emitLn('else ', newline: false); |
| emitCollectionElement(depth + 1, tp, rhsFilter: rhsFilter); |
| } |
| break; |
| case 2: |
| { |
| final i = localVars.length; |
| // TODO (felih): update to use new type system. Add types like |
| // LIST_STRING etc. |
| emitLn('for ', newline: false); |
| emitParenWrapped(() { |
| final local = '$localName$i'; |
| iterVars.add(local); |
| // For-loop (induction, list, set). |
| localVars.add(null); // declared, but don't use |
| switch (choose(3)) { |
| case 0: |
| emitVariableDeclaration(local, DartType.INT, |
| initializerEmitter: emitZero, indent: false); |
| emitBinaryComparison(() => emit(local), '<', |
| () => emitSmallPositiveInt(limit: 16), |
| includeSemicolon: true); |
| emit('$local++'); |
| break; |
| case 1: |
| emitVariableDeclaration(local, DartType.INT, |
| includeSemicolon: false, indent: false); |
| emit(' in '); |
| emitCollection(depth + 1, DartType.LIST_INT, |
| rhsFilter: rhsFilter); |
| break; |
| default: |
| emitVariableDeclaration(local, DartType.INT, |
| includeSemicolon: false, indent: false); |
| emit(' in '); |
| emitCollection(depth + 1, DartType.SET_INT, |
| rhsFilter: rhsFilter); |
| break; |
| } |
| localVars.removeLast(); // will get type |
| }); |
| nest++; |
| localVars.add(DartType.INT); |
| emitNewline(); |
| 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}) { |
| var l, r; |
| if (DartType.isListType(tp)) { |
| var elementType = dartType.elementType(tp); |
| l = '<${elementType.dartName}>['; |
| r = ']'; |
| } else if (DartType.isSetType(tp)) { |
| var elementType = dartType.elementType(tp); |
| l = '<${elementType.dartName}>{'; |
| r = '}'; |
| } else if (DartType.isMapType(tp)) { |
| var indexType = dartType.indexType(tp); |
| var elementType = dartType.elementType(tp); |
| l = '<${indexType.dartName},${elementType.dartName}>{'; |
| r = '}'; |
| } else { |
| throw "Unrecognized collection type $tp"; |
| } |
| emitWrapped([l, r], () { |
| // Collection length decreases as depth increases. |
| var collectionLength = max(1, 8 - depth); |
| emitCommaSeparated( |
| (int _) => emitCollectionElement(depth, tp, rhsFilter: rhsFilter), |
| chooseOneUpTo(collectionLength), |
| newline: DartType.isMapType(tp)); |
| }, shouldIndent: DartType.isMapType(tp)); |
| } |
| |
| void emitConstCollection(int depth, DartType tp, {RhsFilter? rhsFilter}) { |
| var l, r, canHaveElements; |
| if (DartType.isListType(tp)) { |
| var elementType = dartType.elementType(tp); |
| l = 'const <${elementType.dartName}>['; |
| r = ']'; |
| canHaveElements = true; |
| } else if (DartType.isSetType(tp)) { |
| var elementType = dartType.elementType(tp); |
| l = 'const <${elementType.dartName}>{'; |
| r = '}'; |
| canHaveElements = (elementType != DartType.DOUBLE) && |
| (elementType != DartType.DOUBLE_NULLABLE); |
| } else if (DartType.isMapType(tp)) { |
| var indexType = dartType.indexType(tp); |
| var elementType = dartType.elementType(tp); |
| l = 'const <${indexType.dartName},${elementType.dartName}>{'; |
| r = '}'; |
| canHaveElements = (indexType != DartType.DOUBLE) && |
| (indexType != DartType.DOUBLE_NULLABLE); |
| } else { |
| throw "Unrecognized collection type $tp"; |
| } |
| if (!canHaveElements) { |
| // Dart does not like floating-point elements. A key would, for |
| // example, yield "The key does not have a primitive operator '=='". |
| // So emit an empty collection. |
| emit(l); |
| emit(r); |
| return; |
| } |
| emitWrapped([l, r], |
| () => emitElement(depth, tp, rhsFilter: rhsFilter, isConst: true), |
| shouldIndent: DartType.isMapType(tp)); |
| } |
| |
| void emitLiteral(int depth, DartType tp, |
| {bool smallPositiveValue = false, RhsFilter? rhsFilter}) { |
| if (tp.isNullable) { |
| if (choose(20) == 0) { |
| emitNull(); |
| return; |
| } |
| tp = tp.toNonNullable(); |
| } |
| // Randomly specialize interface if possible. E.g. num to int. |
| tp = maybeSpecializeInterface(tp); |
| if (tp == DartType.NULL) { |
| emitNull(); |
| } else 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 (tp == DartType.ENDIAN) { |
| // The Endian type doesn't have a public constructor, so we emit a |
| // library call instead. |
| emitLibraryCall(depth, tp); |
| } 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 $tp'; |
| } |
| } |
| |
| // 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, bool includeSemicolon = false}) { |
| // If there is at least one non trivial constructor for the type tp |
| // select one of these constructors. |
| if (dartType.hasConstructor(tp)) { |
| var 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.isNotEmpty) { |
| emit('${tp.name}.$constructor'); |
| } else { |
| // New is no longer necessary as of Dart 2, but is still supported. |
| // Emit a `new` once in a while to ensure it's covered. |
| emit('${rollDice(10) ? "new " : ""}${tp.name}'); |
| } |
| emitParenWrapped(() { |
| // Iterate over constructor parameters. |
| var constructorParameters = |
| dartType.constructorParameters(tp, constructor); |
| if (constructorParameters == null) { |
| throw 'No constructor parameters for ${tp.name}.$constructor'; |
| } |
| emitCommaSeparated((int 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); |
| }, constructorParameters.length); |
| }); |
| } else { |
| // Otherwise type can be constructed from a literal. |
| emitLiteral(depth + 1, tp, rhsFilter: rhsFilter); |
| } |
| if (includeSemicolon) { |
| emit(';'); |
| } |
| } |
| |
| // Pick an existing variable of the given type. |
| String? pickScalarVar(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. |
| var choices = <String>{}; |
| for (var i = 0; i < globalVars.length; i++) { |
| if (tp == globalVars[i]) choices.add('$varName$i'); |
| } |
| for (var i = 0; i < localVars.length; i++) { |
| if (tp == localVars[i]) choices.add('$localName$i'); |
| } |
| var fields = getCurrentFields(); |
| if (fields != null) { |
| for (var i = 0; i < fields.length; i++) { |
| if (tp == fields[i]) choices.add('$fieldName${currentClassIndex}_$i'); |
| } |
| } |
| var proto = getCurrentProto(); |
| if (proto != null) { |
| // If recursion is allowed on the current method, the last parameter is |
| // the recursion depth parameter. We should avoid reassigning the |
| // recursion depth parameter to prevent infinite recursion. |
| final endIndex = |
| currentMethod!.recursionAllowed ? proto.length - 1 : proto.length; |
| for (var i = 1; i < endIndex; i++) { |
| if (tp == proto[i]) choices.add('$paramName$i'); |
| } |
| } |
| // Make modification of the iteration variable from the loop |
| // body less likely. |
| if (isLhs) { |
| if (!rollDice(100)) { |
| var 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) { |
| var 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}'; |
| } |
| |
| return '${choices.elementAt(choose(choices.length))}'; |
| } |
| |
| String? emitScalarVar(DartType tp, |
| {bool isLhs = false, RhsFilter? rhsFilter}) { |
| final emittedVar = pickScalarVar(tp, isLhs: isLhs, rhsFilter: rhsFilter); |
| if (emittedVar != null) { |
| if (rhsFilter != null && (emittedVar == rhsFilter.lhsVar)) { |
| rhsFilter.consume(); |
| } |
| emit(emittedVar); |
| } |
| return emittedVar; |
| } |
| |
| String? emitSubscriptedVar(int depth, DartType tp, |
| {bool isLhs = false, String? assignOp, 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. |
| var iterType = oneOfSet(dartType.indexableElementTypes(tp)); |
| // For `collection[key] <op>= value` to work, collection must not be a |
| // Map or Expando because their subscript operators return a nullable |
| // type. |
| if (assignOp != null && assignOp != "=") { |
| while (!DartType.isListType(iterType)) { |
| 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); |
| emitSquareBraceWrapped(() => |
| // Emit an expression resolving into the index type. For |
| // collection type, only constant collections are used |
| // to avoid rehashing the same value into many entries. |
| DartType.isCollectionType(indexType) |
| ? emitConstCollection(depth + 1, indexType) |
| : emitExpr(depth + 1, indexType)); |
| // Map.[] and Expando.[] have a nullable result, but we should not write |
| // map[key]! = value. |
| if (!tp.isNullable && !isLhs) { |
| emit('!'); |
| } |
| } else { |
| ret = emitScalarVar(tp, |
| isLhs: isLhs, rhsFilter: rhsFilter); // resort to scalar |
| } |
| return ret; |
| } |
| |
| String? emitVar(int depth, DartType tp, |
| {bool isLhs = false, String? assignOp, RhsFilter? rhsFilter}) { |
| switch (choose(2)) { |
| case 0: |
| return emitScalarVar(tp, isLhs: isLhs, rhsFilter: rhsFilter); |
| break; |
| default: |
| return emitSubscriptedVar(depth, tp, |
| isLhs: isLhs, assignOp: assignOp, rhsFilter: rhsFilter); |
| break; |
| } |
| } |
| |
| void emitTerminal(int depth, DartType tp, {RhsFilter? rhsFilter}) { |
| switch (choose(2)) { |
| case 0: |
| emitLiteral(depth, tp, rhsFilter: rhsFilter); |
| break; |
| default: |
| emitVar(depth, tp, rhsFilter: rhsFilter); |
| break; |
| } |
| } |
| |
| // 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); |
| } |
| emitParenWrapped(() { |
| emit(oneOfSet(dartType.uniOps(tp))); |
| var uniOpParam = tp.toNonNullable(); |
| emitParenWrapped( |
| () => emitExpr(depth + 1, uniOpParam, rhsFilter: rhsFilter)); |
| }); |
| } |
| |
| // Emit expression with binary operator: (x + y) |
| void emitBinaryExpr(int depth, DartType tp, {RhsFilter? rhsFilter}) { |
| if (dartType.binOps(tp).isEmpty) { |
| return emitLiteral(depth, tp); |
| } |
| |
| var binop = oneOfSet(dartType.binOps(tp)); |
| var 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)) { |
| emitParenWrapped(() { |
| emitExpr(depth + 1, binOpParams[0], rhsFilter: rhsFilter); |
| emit(' $binop '); |
| emitLiteral(depth + 1, binOpParams[1], smallPositiveValue: true); |
| }); |
| } else if (binop == '*' && |
| DartType.isGrowableType(binOpParams[1]) && |
| dartType.isInterfaceOfType(binOpParams[0], DartType.NUM)) { |
| emitParenWrapped(() { |
| emitLiteral(depth + 1, binOpParams[0], smallPositiveValue: true); |
| emit(' $binop '); |
| emitExpr(depth + 1, binOpParams[1], rhsFilter: rhsFilter); |
| }); |
| } else { |
| emitParenWrapped(() { |
| emitExpr(depth + 1, binOpParams[0], rhsFilter: rhsFilter); |
| emit(' $binop '); |
| emitExpr(depth + 1, binOpParams[1], rhsFilter: rhsFilter); |
| }); |
| } |
| } |
| |
| // Emit expression with ternary operator: (b ? x : y) |
| void emitTernaryExpr(int depth, DartType tp, {RhsFilter? rhsFilter}) => |
| emitParenWrapped(() { |
| emitExpr(depth + 1, DartType.BOOL, rhsFilter: rhsFilter); |
| emit(' ? '); |
| emitExpr(depth + 1, tp, rhsFilter: rhsFilter); |
| emit(' : '); |
| emitExpr(depth + 1, tp, rhsFilter: rhsFilter); |
| }); |
| |
| // Emit expression with pre/post-increment/decrement operator: (x++) |
| void emitPreOrPostExpr(int depth, DartType tp, {RhsFilter? rhsFilter}) { |
| if (tp == DartType.INT) { |
| emitParenWrapped(() { |
| var pre = coinFlip(); |
| if (pre) { |
| emitPreOrPostOp(tp); |
| } |
| emitScalarVar(tp, isLhs: true); |
| if (!pre) { |
| emitPreOrPostOp(tp); |
| } |
| }); |
| } else { |
| emitTerminal(depth, tp, rhsFilter: rhsFilter); // resort to terminal |
| } |
| } |
| |
| bool isTypedDataFloatType(List<DartType> proto) { |
| for (var i = 0; i < proto.length; ++i) { |
| if (DartLib.typedDataFloatTypes.contains(proto[i])) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Emit library call. |
| void emitLibraryCall(int depth, DartType tp, |
| {RhsFilter? rhsFilter, bool includeSemicolon = false}) { |
| var lib = getLibraryMethod(tp); |
| if (lib == null || (!fp && isTypedDataFloatType(lib.proto))) { |
| // We cannot find a library method, or we found a library method but its |
| // prototype has a floating point type, and fp is disabled. This can only |
| // happen for non-void types. In those cases, we resort to a literal. |
| assert(tp != DartType.VOID); |
| emitLiteral(depth + 1, tp, rhsFilter: rhsFilter); |
| return; |
| } |
| emitParenWrapped(() { |
| var prototype = lib.proto; |
| // Receiver. |
| if (prototype[0] != DartType.VOID) { |
| emitParenWrapped(() => emitArg( |
| depth + 1, prototype[0], lib.getRestriction(0), |
| rhsFilter: rhsFilter)); |
| emit('.'); |
| } |
| // Call. |
| emit('${lib.name}'); |
| // Parameters. |
| if (lib.isMethod) { |
| emitParenWrapped(() { |
| if (prototype[1] != DartType.VOID) { |
| emitCommaSeparated( |
| (int i) => emitArg( |
| depth + 1, prototype[i], lib.getRestriction(i), |
| rhsFilter: rhsFilter), |
| prototype.length, |
| start: 1); |
| } |
| }); |
| } |
| // Add cast to avoid error of double or int being interpreted as num. |
| if (dartType.isInterfaceOfType(tp, DartType.NUM)) { |
| emit(' as ${tp.name}'); |
| } |
| }); |
| if (includeSemicolon) { |
| emit(';'); |
| } |
| } |
| |
| // Helper for a method call. |
| bool pickedCall(int depth, DartType tp, List<Method> methods, int m, |
| {RhsFilter? rhsFilter}) { |
| for (var i = m - 1; i >= 0; i--) { |
| if (tp == methods[i].returnType) { |
| var isRecursiveCall = (currentMethod == methods[i]); |
| if (isRecursiveCall && !methods[i].recursionAllowed) { |
| continue; |
| } |
| methods[i].emitCall(depth + 1, |
| rhsFilter: rhsFilter, isRecursiveCall: isRecursiveCall); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Emit method call within the program. |
| void emitMethodCall(int depth, DartType tp, {RhsFilter? rhsFilter}) { |
| // Only call an already emitted method or the current method to avoid mutual recursion. |
| if (currentClassIndex == null) { |
| // Outside a class but inside a method: call backward in global methods. |
| if (currentMethodIndex != null && |
| pickedCall(depth, tp, globalMethods, currentMethodIndex! + 1, |
| rhsFilter: rhsFilter)) { |
| return; |
| } |
| } else { |
| var classIndex = currentClassIndex; |
| // Chase randomly up in class hierarchy. |
| while (classParents[classIndex!] > 0) { |
| if (coinFlip()) { |
| break; |
| } |
| classIndex = classParents[classIndex]; |
| } |
| var m1 = 0; |
| // Inside a class: try to call backwards into current or parent class |
| // methods first. |
| if (currentMethodIndex == null || classIndex != currentClassIndex) { |
| // 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 or the current method. |
| // We allow simple recursion but avoid mutual recursion. |
| // (to avoid infinite recursions). |
| m1 = currentMethodIndex! + 1; |
| } |
| final m2 = globalMethods.length; |
| if (pickedCall(depth, tp, classMethods[classIndex], m1, |
| rhsFilter: rhsFilter) || |
| pickedCall(depth, tp, globalMethods, m2, rhsFilter: rhsFilter)) { |
| return; |
| } |
| } |
| emitTerminal(depth, tp, rhsFilter: rhsFilter); // resort to terminal. |
| } |
| |
| // Emit expression. |
| void emitExpr(int depth, DartType tp, |
| {RhsFilter? rhsFilter, bool includeSemicolon = false}) { |
| final resetExprStmt = processExprOpen(tp); |
| // Continuing nested expressions becomes less likely as the depth grows. |
| if (choose(depth + 1) > exprDepth) { |
| emitTerminal(depth, tp, rhsFilter: rhsFilter); |
| } else { |
| // Possibly nested expression. |
| switch (choose(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); |
| if (includeSemicolon) { |
| emit(';'); |
| } |
| } |
| |
| // |
| // Operators. |
| // |
| |
| // Emit same type in-out increment operator. |
| void emitPreOrPostOp(DartType tp) { |
| assert(tp == DartType.INT); |
| emit(oneOf(const <String>['++', '--'])); |
| } |
| |
| // 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) => |
| api.typeToLibraryMethods.containsKey(tp) |
| ? oneOf(api.typeToLibraryMethods[tp]!) |
| : null; |
| |
| // Emit a library argument, possibly subject to restrictions. |
| void emitArg(int depth, DartType type, Restriction restriction, |
| {RhsFilter? rhsFilter}) { |
| if ((type == DartType.INT) && (restriction == Restriction.small)) { |
| emitSmallPositiveInt(); |
| } else if ((type == DartType.DOUBLE) && !fp) { |
| // resort to INT if floating point is disabled. |
| emitExpr(depth, DartType.INT, rhsFilter: rhsFilter); |
| } else if ((type == DartType.STRING) && |
| (restriction == Restriction.small)) { |
| emitString(length: 2); |
| } else { |
| emitExpr(depth, type, rhsFilter: rhsFilter); |
| } |
| } |
| |
| // |
| // Types. |
| // |
| |
| List<DartType> fillTypes1({int limit = 4, bool isFfi = false}) { |
| final list = <DartType>[]; |
| for (var i = 0, n = chooseOneUpTo(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 (var i = 0, n = chooseOneUpTo(limit2); i < n; i++) { |
| list.add(fillTypes1(limit: limit1, isFfi: isFfi)); |
| } |
| return list; |
| } |
| |
| List<DartType>? getCurrentProto() { |
| if (currentClassIndex != null) { |
| if (currentMethodIndex != null) { |
| return classMethods[currentClassIndex!][currentMethodIndex!].parameters; |
| } |
| } else if (currentMethodIndex != null) { |
| return globalMethods[currentMethodIndex!].parameters; |
| } |
| return null; |
| } |
| |
| List<DartType>? getCurrentFields() { |
| if (currentClassIndex != null) { |
| return classFields[currentClassIndex!]; |
| } |
| return null; |
| } |
| |
| void emitFfiType(DartType tp) { |
| if (tp == DartType.INT) { |
| emit(oneOf( |
| const <String>['ffi.Int8', 'ffi.Int16', 'ffi.Int32', 'ffi.Int64'])); |
| } else if (tp == DartType.DOUBLE) { |
| emit(oneOf(const <String>['ffi.Float', 'ffi.Double'])); |
| } else { |
| throw 'Invalid FFI type ${tp.name}'; |
| } |
| } |
| |
| void emitFfiTypedef(Method method) { |
| var pars = method.parameters; |
| emit('typedef ${method.name}Type = '); |
| emitFfiType(pars[0]); |
| emit(' Function'); |
| emitParenWrapped( |
| () => emitCommaSeparated((int i) => emitFfiType(pars[i]), pars.length, |
| start: 1), |
| includeSemicolon: true); |
| emitNewline(); |
| emitNewline(); |
| } |
| |
| // |
| // Output. |
| // |
| |
| // Emits a newline to the program. |
| void emitNewline() => emit('', newline: true); |
| |
| // Emits indentation based on the current indentation count. |
| void emitIndentation() => file.writeStringSync(' ' * indent); |
| |
| // Emits indented line to append to program. |
| void emitLn(String line, {bool newline = true}) { |
| emitIndentation(); |
| 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'); |
| } |
| } |
| |
| // 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. |
| late DartApi api; |
| |
| // Program variables. |
| late Random rand; |
| int indent = 0; |
| int nest = 0; |
| int? currentClassIndex; |
| Method? currentMethod; |
| int? currentMethodIndex; |
| |
| // DartType instance. |
| late DartType dartType; |
| |
| // Types of local variables currently in scope. |
| late List<DartType?> localVars; |
| |
| // Types of global variables. |
| late 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. |
| late List<String?> iterVars; |
| |
| // List of all global methods. |
| late List<Method> globalMethods; |
| |
| // Types of fields over all classes. |
| late List<List<DartType>> classFields; |
| |
| // List of methods over all classes. |
| late List<List<Method>> classMethods; |
| |
| // List of virtual functions per class. Map is from parent class index to List |
| // of overloaded functions from that parent. |
| late List<Map<int, List<int>>> virtualClassMethods; |
| |
| // Parent class indices for all classes. |
| late List<int> classParents; |
| |
| // Minimization mode extensions. |
| final bool minimize; |
| BigInt? smask; |
| BigInt? emask; |
| bool skipStmt = false; |
| bool skipExpr = false; |
| bool skipExprCntr = false; |
| int stmtCntr = 0; |
| int exprCntr = 0; |
| |
| // 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) { |
| var seed = int.parse(userSeed); |
| if (seed == 0) { |
| final rand = Random(); |
| while (seed == 0) { |
| seed = rand.nextInt(1 << 32); |
| } |
| } |
| return seed; |
| } |
| |
| /// Main driver when dartfuzz.dart is run stand-alone. |
| void main(List<String> arguments) { |
| const kSeed = 'seed'; |
| const kFp = 'fp'; |
| const kFfi = 'ffi'; |
| const kFlat = 'flat'; |
| const kMini = 'mini'; |
| const kSMask = 'smask'; |
| const kEMask = 'emask'; |
| final parser = ArgParser() |
| ..addOption(kSeed, |
| help: 'random seed (0 forces time-based seed)', defaultsTo: '0') |
| ..addFlag(kFp, help: 'enables floating-point operations', defaultsTo: true) |
| ..addFlag(kFfi, |
| help: 'enables FFI method calls (default: off)', defaultsTo: false) |
| ..addFlag(kFlat, |
| help: 'enables flat types (default: off)', defaultsTo: false) |
| // Minimization mode extensions. |
| ..addFlag(kMini, |
| help: 'enables minimization mode (default: off)', defaultsTo: false) |
| ..addOption(kSMask, |
| help: 'Bitmask indicating which statements to omit' |
| '(Bit=1 omits)', |
| defaultsTo: '0') |
| ..addOption(kEMask, |
| help: 'Bitmask indicating which expressions to omit' |
| '(Bit=1 omits)', |
| defaultsTo: '0'); |
| var results; |
| try { |
| results = parser.parse(arguments); |
| } catch (e) { |
| print('Usage: dart dartfuzz.dart [OPTIONS] FILENAME\n${parser.usage}\n$e'); |
| exitCode = 255; |
| } |
| final seed = getSeed(results[kSeed]); |
| final fp = results[kFp]; |
| final ffi = results[kFfi]; |
| final flatTp = results[kFlat]; |
| final file = File(results.rest.single).openSync(mode: FileMode.write); |
| final minimize = results[kMini]; |
| final smask = BigInt.parse(results[kSMask]); |
| final emask = BigInt.parse(results[kEMask]); |
| 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); |
| } |
| } |