blob: 60a57654ee23612bde3c183157d62ee4bec04918 [file] [log] [blame]
// Copyright (c) 2015, 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.
// Random-walk fuzzer for the Dart VM.
//
// Start with all the classes and libraries and various interesting values.
// Repeatedly choose one as a receiver, construct a message it is likely to
// understand, send the message, and add the result.
//
// Intentionally not run on the build bots because through dart:io it could
// trash their setups.
library fuzzer;
import 'dart:io';
import 'dart:math';
import 'dart:mirrors';
import 'dart:typed_data';
var excludeList = [
'dart.io.exit',
'dart.io.exitCode',
'dart.io.sleep',
'dart.io.Process.killPid',
];
final bool trace = false;
void main(List<String> args) {
int seed;
if (args.length == 1) {
seed = int.parse(args[0]);
} else {
// Dart's built-in random number generator doesn't provide access to the
// seed when it is chosen by the implementation. We need to be able to
// report this seed to make runs of the fuzzer reproducible, so we create
// the seed ourselves.
// When running on many machines in parallel, the current time alone
// is a poor choice of seed.
seed = 0;
try {
var f = new File("/dev/urandom").openSync();
seed = (seed << 8) | f.readByteSync();
seed = (seed << 8) | f.readByteSync();
seed = (seed << 8) | f.readByteSync();
seed = (seed << 8) | f.readByteSync();
f.close();
} catch (e) {
print("Failed to read from /dev/urandom: $e");
}
seed ^= new DateTime.now().millisecondsSinceEpoch;
seed &= 0xFFFFFFFF;
}
random = new Random(seed);
// Information needed to reproduce this run.
print("Dart VM fuzzer");
print("Executable: ${Platform.executable}");
print("Arguments: ${Platform.executableArguments}");
print("Version: ${Platform.version}");
print("Seed: ${seed}");
print("------------------------------------------");
setupInterestingValues();
setupClasses();
// Bound the number of steps in our random walk so that if any issue is found
// it can be reproduced without having to wait too long.
for (int i = 0; i < 100000; i++) {
fuzz(randomElementOf(candidateReceivers));
if (maybe(0.01)) garbageCollect();
}
}
Random random;
bool maybe(probability) => random.nextDouble() < probability;
randomElementOf(list) {
return list.length == 0 ? null : list[random.nextInt(list.length)];
}
class Candidate<T> {
Candidate origin;
String message;
T mirror;
trace() {
if (origin == null) {
print("");
} else {
origin.trace();
print(" $message");
}
print(mirror);
}
}
List<Candidate<ObjectMirror>> candidateReceivers =
new List<Candidate<ObjectMirror>>();
List<Candidate<InstanceMirror>> candidateArguments =
new List<Candidate<InstanceMirror>>();
void addInstance(var instance) {
addInstanceMirror(reflect(instance));
}
void addInstanceMirror(InstanceMirror mirror,
[Candidate origin, String message]) {
var c = new Candidate<InstanceMirror>();
c.mirror = mirror;
c.origin = origin;
c.message = message;
candidateReceivers.add(c);
candidateArguments.add(c);
}
void addObjectMirror(ObjectMirror mirror) {
var c = new Candidate<ObjectMirror>();
c.mirror = mirror;
c.origin = null;
c.message = null;
candidateReceivers.add(c);
}
void setupInterestingValues() {
addInstance(null);
addInstance(true);
addInstance(false);
addInstance([]);
addInstance(const []);
addInstance({});
addInstance(const {});
addInstance(() => null);
addInstance(-1);
addInstance(0);
addInstance(1);
addInstance(2);
addInstance(1 << 31);
addInstance(1 << 31 + 1);
addInstance(1 << 31 - 1);
addInstance(1 << 32);
addInstance(1 << 32 + 1);
addInstance(1 << 32 - 1);
addInstance(1 << 63);
addInstance(1 << 63 + 1);
addInstance(1 << 63 - 1);
addInstance(1 << 64);
addInstance(1 << 64 + 1);
addInstance(1 << 64 - 1);
addInstance(-1.0);
addInstance(0.0);
addInstance(1.0);
addInstance(2.0);
addInstance(double.nan);
addInstance(double.infinity);
addInstance(double.negativeInfinity);
addInstance(double.minPositive);
addInstance(double.maxFinite);
addInstance("foo"); // ASCII string
addInstance("blåbærgrød"); // Latin1 string
addInstance("Îñţérñåţîöñåļîžåţîờñ"); // Unicode string
addInstance("𝄞"); // Surrogate pairs
addInstance("𝄞"[0]); // Surrogate pairs
addInstance("𝄞"[1]); // Surrogate pairs
addInstance("\u{0}"); // Non-printing character
addInstance("\u{1}"); // Non-printing character
addInstance("f\u{0}oo"); // Internal NUL
addInstance("blåbæ\u{0}rgrød"); // Internal NUL
addInstance("Îñţérñåţîö\u{0}ñåļîžåţîờñ"); // Internal NUL
addInstance("\u{0}𝄞"); // Internal NUL
for (int len = 0; len < 8; len++) {
addInstance(fillInt(new Int8List(len)));
addInstance(fillInt(new Int16List(len)));
addInstance(fillInt(new Int32List(len)));
addInstance(fillInt(new Int64List(len)));
addInstance(fillInt(new Uint8List(len)));
addInstance(fillInt(new Uint16List(len)));
addInstance(fillInt(new Uint32List(len)));
addInstance(fillInt(new Uint64List(len)));
addInstance(fillFloat(new Float32List(len)));
addInstance(fillFloat(new Float64List(len)));
}
randomInstance(ignore) {
return randomElementOf(candidateArguments).mirror.reflectee;
}
for (int len = 0; len < 8; len++) {
addInstance(new List.generate(len, randomInstance));
}
}
void fillInt(TypedData d) {
for (var i = 0; i < d.length; i++) {
d[i] = random.nextInt(0xFFFFFFFF);
}
}
void fillFloat(TypedData d) {
for (var i = 0; i < d.length; i++) {
d[i] = random.nextDouble();
}
}
void setupClasses() {
currentMirrorSystem().libraries.values.forEach((lib) {
if (lib.simpleName == #fuzzer) return; // Don't recurse.
addObjectMirror(lib);
lib.declarations.values.forEach((decl) {
if (decl is ClassMirror) {
addObjectMirror(decl);
}
});
});
}
MethodMirror randomMethodOf(receiver) {
if (receiver is ClassMirror) {
return randomElementOf(receiver.declarations.values
.where((d) => d is MethodMirror && d.isStatic)
.toList());
} else if (receiver is LibraryMirror) {
return randomElementOf(
receiver.declarations.values.where((d) => d is MethodMirror).toList());
} else if (receiver is InstanceMirror) {
var methods = [];
var cls = receiver.type;
while (cls != reflectClass(Object)) {
cls.declarations.values.forEach((d) {
if (d is MethodMirror && !d.isStatic) methods.add(d);
});
cls = cls.superclass;
}
return randomElementOf(methods);
}
throw new Error("UNREACHABLE");
}
String prettyMessageName(receiver, method) {
var r = "?", m = "?";
if (receiver is InstanceMirror) {
r = MirrorSystem.getName(receiver.type.simpleName);
} else if (receiver is ClassMirror) {
r = MirrorSystem.getName(receiver.simpleName);
r = "$r class";
} else if (receiver is LibraryMirror) {
r = MirrorSystem.getName(receiver.simpleName);
r = "$r lib";
}
m = MirrorSystem.getName(method.simpleName);
return "$r>>#$m";
}
void fuzz(Candidate c) {
ObjectMirror receiver = c.mirror;
MethodMirror method = randomMethodOf(receiver);
if (method == null) return;
if (excludeList.contains(MirrorSystem.getName(method.qualifiedName))) return;
List positional = randomPositionalArgumentsFor(method);
Map named = randomNamedArgumentsFor(method);
InstanceMirror result;
String message = prettyMessageName(receiver, method);
if (trace) {
c.trace();
print(message);
}
if (method.isConstructor) {
try {
result = receiver.newInstance(method.simpleName, positional, named);
} catch (e) {}
} else if (method.isRegularMethod) {
try {
result = receiver.invoke(method.simpleName, positional, named);
} catch (e) {}
} else if (method.isGetter) {
try {
result = receiver.getField(method.simpleName);
} catch (e) {}
} else if (method.isSetter) {
try {
result = receiver.setField(method.simpleName, positional[0]);
} catch (e) {}
}
if (result != null) {
addInstanceMirror(result, c, message);
}
}
InstanceMirror randomArgumentWithBias(TypeMirror bias) {
if (maybe(0.75)) {
for (var candidate in candidateArguments) {
if (candidate.mirror.type.isAssignableTo(bias)) {
return candidate.mirror;
}
}
}
return randomElementOf(candidateArguments).mirror;
}
List randomPositionalArgumentsFor(MethodMirror method) {
var result = [];
for (int i = 0; i < method.parameters.length; i++) {
ParameterMirror p = method.parameters[i];
if (!p.isNamed && (!p.isOptional || maybe(0.5))) {
result.add(randomArgumentWithBias(p.type));
}
}
return result;
}
Map randomNamedArgumentsFor(MethodMirror method) {
var result = {};
for (int i = 0; i < method.parameters.length; i++) {
ParameterMirror p = method.parameters[i];
if (p.isNamed && maybe(0.5)) {
result[p.simpleName] = randomArgumentWithBias(p.type);
}
}
return result;
}
void garbageCollect() {
// Chain a bunch of moderately sized arrays, then let go of them. Using a
// moderate size avoids our allocations going directly to a large object
// page in old space.
var n;
for (int i = 0; i < 2048; i++) {
var m = new List(512);
m[0] = n;
n = m;
}
}