fa900734e2
Currently all programs generated by Dart fuzz have NNBD errors. Change-Id: I6f55447ad4c2986ae4ffceff8c88ba263d40e280 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/193081 Reviewed-by: Ben Konyi <bkonyi@google.com>
2463 lines
77 KiB
Dart
2463 lines
77 KiB
Dart
// 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<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');
|
|
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<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;
|
|
}
|
|
}
|