| // 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; |
| } |
| } |