// 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. // @dart = 2.9 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.89'; // 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.name; 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}) { String 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 parameters; final DartFuzz fuzzer; bool recursionAllowed; } /// Class for global methods generated by DartFuzz. class GlobalMethod extends Method { GlobalMethod(String name, List parameters, DartFuzz fuzzer) : super(name, parameters, fuzzer); } /// Class for ffi methods generated by DartFuzz. class FfiMethod extends Method { FfiMethod( String namePrefix, int index, List 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 parameters, DartFuzz fuzzer, this.className) : super(name, parameters, fuzzer) {} @override String emitCall(int depth, {RhsFilter rhsFilter, bool includeSemicolon = false, bool isRecursiveCall = false}) { String 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 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 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, }) { String 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) { String invokingObject = type != null ? fuzzer.pickScalarVar(type) : "${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); 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) : typeToLibraryMethods = DartLib.typeToLibraryMethods { if (ffi) { typeToLibraryMethods[DartType.INT] = [ ...[ DartLib('intComputation', [DartType.VOID, ...List.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.filled(4, DartType.INT)], true), DartLib('sumSmallNumbers', [DartType.VOID, ...List.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.filled(11, DartType.INT)], true, restrictions: [ Restriction.none, ...List.filled(11, Restriction.small) ]), DartLib('sumManyInts', [DartType.VOID, ...List.filled(10, DartType.INT)], true, restrictions: [ Restriction.none, ...List.filled(10, Restriction.small) ]), DartLib('regress37069', [DartType.VOID, ...List.filled(11, DartType.INT)], true, restrictions: [ Restriction.none, ...List.filled(11, Restriction.small) ]), ], ...DartLib.intLibs, ]; typeToLibraryMethods[DartType.DOUBLE] = [ if (ffi) ...[ DartLib('times1_337Float', [DartType.VOID, DartType.DOUBLE], true), DartLib( 'sumManyDoubles', [DartType.VOID, ...List.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); indent = 0; nest = 0; 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 = []; iterVars = []; globalVars = fillTypes1(limit: numGlobalVars); globalVars.addAll(dartType.allTypes); globalMethods = getMethods( numGlobalMethods, numMethodParams, MethodType.globalMethod, namePrefix: methodName); classFields = fillTypes2(limit2: numClasses, limit1: numLocalVars); final int numClassMethods = 1 + numClasses - classFields.length; classMethods = getClassMethods(classFields.length, numClassMethods, numMethodParams, fillTypes1(limit: numMethodParams)); virtualClassMethods = >>[]; classParents = []; // 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 (int i = 0; i < numDartTypeExtensions; ++i) { DartType 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 getMethods(int maxMethods, int maxParams, MethodType methodType, {String className, String extensionName, String namePrefix, DartType type}) { final List list = []; for (int i = 0, n = chooseOneUpTo(maxMethods); i < n; i++) { final List 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> getClassMethods(int numClasses, int maxMethods, int maxParams, List callMethodParams) { final methodsForAllClasses = >[]; for (int 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(List choices) => choices[choose(choices.length)]; T oneOfSet(Set 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(' catch (e, st) '); emitBraceWrapped(catchBody); if (finallyBody != null) { emit(' finally '); emitBraceWrapped(finallyBody); } } dynamic emitWrapped(List 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 (int 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 b1 = emitBraceWrapped(ifBodyEmitter); bool b2 = false; if (elseBodyEmitter != null) { emit(' else '); b2 = emitBraceWrapped(elseBodyEmitter); } return b1 || b2; } 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; 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}'; } } // 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('// @dart=2.7'); 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 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>().asFunction();'); emitNewline(); } void emitMethods(List methods) { for (int 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; int parentClass = classParents[currentClassIndex]; final vcm = >{}; // Chase randomly up in class hierarchy. while (parentClass >= 0) { vcm[parentClass] = []; for (int 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 (int i = 0; i < classFields.length; i++) { if (i == 0) { classParents.add(-1); emit('class X0 '); } else { final int 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 methodList, className, extensionName, namePrefix, {DartType type}) { int 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 (int 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 (int i = 0; i < globalMethods.length; i++) { String outputName; emitTryCatchFinally( () => outputName = globalMethods[i].emitCall(1, includeSemicolon: true), () => emitPrint('$outputName() throws')); emitNewline(); } // Call each class method once. for (int i = 0; i < classMethods.length; i++) { for (int j = 0; j < classMethods[i].length; j++) { String outputName; emitNewline(); emitTryCatchFinally( () => outputName = classMethods[i][j].emitCall(1, includeSemicolon: true), () => emitPrint('${outputName} 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++) { 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(() { String body = ''; for (int i = 0; i < globalVars.length; i++) { body += '\$$varName$i\\n'; } emitPrint('$body'); }, () => emitPrint('print() throws')); }); // // Declarations. // void emitVariableDeclarations(String name, List vars) { for (int i = 0; i < vars.length; i++) { DartType 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.name; if (indent) { emitIndentation(); } emit('$typeName $name', newline: false); if (initializerEmitter != null) { emit(' = '); initializerEmitter(); } if (includeSemicolon) { emit(';', newline: newline); } } void emitParDecls(List pars) => emitCommaSeparated((int i) { DartType tp = pars[i]; emit('${tp.name} $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. assignOp = oneOfSet(dartType.assignOps(tp)); if (assignOp == null) { throw 'No assign operation for ${tp.name}'; } } emitIndentation(); // Emit a variable of the lhs type. final emittedVar = emitVar(0, tp, isLhs: true); RhsFilter rhsFilter = RhsFilter.fromDartType(tp, emittedVar); 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'; } 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 { DartType tp = oneOfSet(dartType.allTypes); emitExpr(0, tp); } }, includeSemicolon: true); return true; } // Emit a return statement. bool emitReturn() { List proto = getCurrentProto(); if (proto == null) { emitLn('return;'); } else { emitLn('return ', newline: false); emitExpr(0, proto[0], includeSemicolon: true); } return false; } // Emit a throw statement. bool emitThrow() { DartType tp = oneOfSet(dartType.allTypes); 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 int 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 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 ', newline: false); emitParenWrapped(() { emit('${elementType.name} $localName$i in '); localVars.add(null); // declared, but don't use emitExpr(0, iterType); 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, isLhs: false); iterVars.add(emittedVar); emit('.forEach'); final i = localVars.length; final j = i + 1; emitParenWrapped(() { emitParenWrapped(() => emit('$localName$i, $localName$j')); emitBraceWrapped(() { 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; }); }, 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 int 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 int 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) { 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(() { int start = choose(1 << 32); int step = chooseOneUpTo(10); final maxCases = 3; for (int 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(() { DartType tp = oneOfSet(dartType.allTypes); final int 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(' 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(); bool ret = emitSingleStatement(depth); processStmtClose(resetSkipStmt); return ret; } // Emit statements. Returns true if code may fall-through. bool emitStatements(int depth) { int s = chooseOneUpTo(numStatements); for (int 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 (int 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; } else if (tp == DartType.DOUBLE) { // Dart does not like floating-point elements. A key would, for // example, yield "The key does not have a primitive operator '=='". emit('null'); 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 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}) { int 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 int 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}) => emitWrapped(DartType.isListType(tp) ? const ['[', ']'] : const ['{', '}'], () { // Collection length decreases as depth increases. int 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}) => emitWrapped( DartType.isListType(tp) ? const ['const [', ']'] : const ['const {', '}'], () => emitElement(depth, tp, rhsFilter: rhsFilter, isConst: true), shouldIndent: DartType.isMapType(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.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 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, 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)) { 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.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. List 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. Set choices = {}; 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 fields = getCurrentFields(); if (fields != null) { for (int i = 0; i < fields.length; i++) { if (tp == fields[i]) choices.add('$fieldName${currentClassIndex}_$i'); } } List 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 int endIndex = currentMethod.recursionAllowed ? proto.length - 1 : proto.length; for (int 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)) { Set 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 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, 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); 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)); } 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 (choose(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 (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))); emitParenWrapped(() => emitExpr(depth + 1, tp, 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); } String binop = oneOfSet(dartType.binOps(tp)); List 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(() { bool 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 proto) { for (int 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}) { DartLib 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(() { List 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 methods, int m, {RhsFilter rhsFilter}) { for (int i = m - 1; i >= 0; i--) { if (tp == methods[i].returnType) { bool 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 { int classIndex = currentClassIndex; // Chase randomly up in class hierarchy. while (classParents[classIndex] > 0) { if (coinFlip()) { break; } classIndex = classParents[classIndex]; } int 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 int 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 ['++', '--'])); } // Emit one type in, boolean out operator. void emitRelOp(DartType tp) { if (tp == DartType.INT || tp == DartType.DOUBLE) { emit(oneOf(const [' > ', ' >= ', ' < ', ' <= ', ' != ', ' == '])); } else { emit(oneOf(const [' != ', ' == '])); } } // // 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 fillTypes1({int limit = 4, bool isFfi = false}) { final list = []; for (int 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> fillTypes2( {bool isFfi = false, int limit2 = 4, int limit1 = 4}) { final list = >[]; for (int i = 0, n = chooseOneUpTo(limit2); i < n; i++) { list.add(fillTypes1(limit: limit1, isFfi: isFfi)); } return list; } List getCurrentProto() { if (currentClassIndex != null) { if (currentMethodIndex != null) { return classMethods[currentClassIndex][currentMethodIndex].parameters; } } else if (currentMethodIndex != null) { return globalMethods[currentMethodIndex].parameters; } return null; } List getCurrentFields() { if (currentClassIndex != null) { return classFields[currentClassIndex]; } return null; } void emitFfiType(DartType tp) { if (tp == DartType.INT) { emit(oneOf( const ['ffi.Int8', 'ffi.Int16', 'ffi.Int32', 'ffi.Int64'])); } else if (tp == DartType.DOUBLE) { emit(oneOf(const ['ffi.Float', 'ffi.Double'])); } else { throw 'Invalid FFI type ${tp.name}'; } } void emitFfiTypedef(Method method) { List 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. DartApi api; // Program variables. Random rand; int indent; int nest; int currentClassIndex; Method currentMethod; int currentMethodIndex; // DartType instance. DartType dartType; // Types of local variables currently in scope. List localVars; // Types of global variables. List globalVars; // Names of currently active iterator variables. // These are tracked to avoid modifications within the loop body, // which can lead to infinite loops. List iterVars; // List of all global methods. List globalMethods; // Types of fields over all classes. List> classFields; // List of methods over all classes. List> classMethods; // List of virtual functions per class. Map is from parent class index to List // of overloaded functions from that parent. List>> virtualClassMethods; // Parent class indices for all classes. List 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 interestingChars = [ '\\u2665', '\\u{1f600}', // rune ]; // Regular characters. static const regularChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#&()+- '; // Interesting integer values. static const List 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) { final rand = Random(); while (seed == 0) { seed = rand.nextInt(1 << 32); } } return seed; } /// Main driver when dartfuzz.dart is run stand-alone. main(List 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'); try { final results = parser.parse(arguments); 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); } } catch (e) { print('Usage: dart dartfuzz.dart [OPTIONS] FILENAME\n${parser.usage}\n$e'); exitCode = 255; } }