blob: af137ed69861321f516c93c4d559c765693ccaf5 [file] [log] [blame]
// 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.88';
// 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<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}) {
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<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,
}) {
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<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);
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 = <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 int 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 (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<Method> getMethods(int maxMethods, int maxParams, MethodType methodType,
{String className,
String extensionName,
String namePrefix,
DartType type}) {
final List<Method> list = <Method>[];
for (int i = 0, n = chooseOneUpTo(maxMethods); i < n; i++) {
final List<DartType> 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 (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<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(' 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 (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');
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 (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 = <int, List<int>>{};
// Chase randomly up in class hierarchy.
while (parentClass >= 0) {
vcm[parentClass] = <int>[];
for (int j = 0, n = classMethods[parentClass].length; j < n; j++) {
// 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<Method> 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<DartType> 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<DartType> 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<DartType> 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<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}) {
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<DartType> 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<String> choices = <String>{};
for (int i = 0; i < globalVars.length; i++) {
if (tp == globalVars[i]) choices.add('$varName$i');
}
for (int i = 0; i < localVars.length; i++) {
if (tp == localVars[i]) choices.add('$localName$i');
}
List<DartType> fields = getCurrentFields();
if (fields != null) {
for (int i = 0; i < fields.length; i++) {
if (tp == fields[i]) choices.add('$fieldName${currentClassIndex}_$i');
}
}
List<DartType> 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<String> cleanChoices = choices.difference(Set.from(iterVars));
if (cleanChoices.isNotEmpty) {
choices = cleanChoices;
}
}
}
// Filter out the current lhs of the expression to avoid recursive
// assignments of the form x = x * x.
if (rhsFilter != null && rhsFilter.shouldFilter) {
Set<String> cleanChoices = choices.difference({rhsFilter.lhsVar});
// If we have other choices of variables, use those.
if (cleanChoices.isNotEmpty) {
choices = cleanChoices;
} else if (!isLhs) {
// If we are emitting an rhs variable, we can emit a terminal.
// note that if the variable type is a collection, this might
// still result in a recursion.
emitLiteral(0, tp);
return null;
}
// Otherwise we have to risk creating a recursion.
}
// Then pick one.
if (choices.isEmpty) {
throw 'No variable to emit for type ${tp.name}';
}
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<DartType> binOpParams = oneOfSet(dartType.binOpParameters(tp, binop));
// Avoid recursive assignments of the form a = a * 100.
if (binop == "*") {
rhsFilter?.consume();
}
// Reduce the number of operations of type growable * large value
// as these might lead to timeouts and/or oom errors.
if (binop == "*" &&
DartType.isGrowableType(binOpParams[0]) &&
dartType.isInterfaceOfType(binOpParams[1], DartType.NUM)) {
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<DartType> 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<DartType> 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 (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 <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 (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<List<DartType>> fillTypes2(
{bool isFfi = false, int limit2 = 4, int limit1 = 4}) {
final list = <List<DartType>>[];
for (int 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) {
List<DartType> 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<DartType> localVars;
// Types of global variables.
List<DartType> globalVars;
// Names of currently active iterator variables.
// These are tracked to avoid modifications within the loop body,
// which can lead to infinite loops.
List<String> iterVars;
// List of all global methods.
List<Method> globalMethods;
// Types of fields over all classes.
List<List<DartType>> classFields;
// List of methods over all classes.
List<List<Method>> classMethods;
// List of virtual functions per class. Map is from parent class index to List
// of overloaded functions from that parent.
List<Map<int, List<int>>> virtualClassMethods;
// Parent class indices for all classes.
List<int> classParents;
// Minimization mode extensions.
final bool minimize;
BigInt smask;
BigInt emask;
bool skipStmt;
bool skipExpr;
bool skipExprCntr;
int stmtCntr;
int exprCntr;
// Interesting characters.
static const List<String> interestingChars = [
'\\u2665',
'\\u{1f600}', // rune
];
// Regular characters.
static const regularChars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#&()+- ';
// Interesting integer values.
static const List<int> interestingIntegers = [
0x0000000000000000,
0x0000000000000001,
0x000000007fffffff,
0x0000000080000000,
0x0000000080000001,
0x00000000ffffffff,
0x0000000100000000,
0x0000000100000001,
0x000000017fffffff,
0x0000000180000000,
0x0000000180000001,
0x00000001ffffffff,
0x7fffffff00000000,
0x7fffffff00000001,
0x7fffffff7fffffff,
0x7fffffff80000000,
0x7fffffff80000001,
0x7fffffffffffffff,
0x8000000000000000,
0x8000000000000001,
0x800000007fffffff,
0x8000000080000000,
0x8000000080000001,
0x80000000ffffffff,
0x8000000100000000,
0x8000000100000001,
0x800000017fffffff,
0x8000000180000000,
0x8000000180000001,
0x80000001ffffffff,
0xffffffff00000000,
0xffffffff00000001,
0xffffffff7fffffff,
0xffffffff80000000,
0xffffffff80000001,
0xffffffffffffffff
];
}
// Generate seed. By default (no user-defined nonzero seed given),
// pick the system's best way of seeding randomness and then pick
// a user-visible nonzero seed.
int getSeed(String userSeed) {
int seed = int.parse(userSeed);
if (seed == 0) {
final rand = Random();
while (seed == 0) {
seed = rand.nextInt(1 << 32);
}
}
return seed;
}
/// Main driver when dartfuzz.dart is run stand-alone.
main(List<String> arguments) {
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;
}
}