Skip to content

Commit 75916de

Browse files
Better error case handling, auto snapshotting, minor cleanup
1 parent 685c446 commit 75916de

File tree

5 files changed

+66
-47
lines changed

5 files changed

+66
-47
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
- Simplifier improvements: bugfixes, faster fixed-point search.
55
- Fixed issue treating failures as executions (printing . & double counting)
66
- New simplifier constraint "contains output"
7+
- Better error when port is already taken and observatory fails to start
8+
- Auto-snapshot scripts for users
9+
- Better error when script is erroneous (though erroneous snapshots may not give
10+
a good error message)
11+
- switch from `vm_service_lib` to `vm_service`
712

813
* 1.0.0-beta.8
914

README.md

+10-5
Original file line numberDiff line numberDiff line change
@@ -188,18 +188,23 @@ The fuzzer works like so:
188188
- [ ] improve error handling for cases where the dart VM crashes etc
189189
- [ ] auto-snapshot scripts for users
190190
- [ ] improve coverage %
191+
- [ ] provide coverage report in standardized format
191192
- [ ] some renames in the code: Library/Corpus
192193
- [ ] targeted scoring for paths through certain files/packages/etc
193-
- [ ] better (different?) simplification algorithm(s?)
194+
- [ ] entropy based simplifier algorithm
194195
- [ ] automatic detection of simpler seeds during fuzzing?
195196
- [ ] store fuzzing options in script file (such as custom mutators, timeouts)
196197
- [ ] use locality sensitive hashing to dedupe failures with different messages
197198
(or in the case of timeouts, the same messages) by jaccard index of their
198199
code coverage sets. Perhaps from: https://arxiv.org/pdf/1811.04633
199200
- [ ] customizable limits & timeouts for simplifier?
200-
- [ ] can we benefit from kernel transformers? For instance, break apart string
201-
checks?
202-
- [ ] service extensions?
203-
- [ ] special value recording? (perhaps via service extensions)?
201+
- [ ] special value recording via service extensions + kernel transformer?
202+
- [ ] break apart string comparisons with kernel transformer?
203+
- [ ] other service extensions?
204+
- [ ] other kernel transformer?
205+
- [ ] semantically-valid-dart transformer
206+
- [ ] generations of seeds?
207+
- [ ] way to exclude or score seeds when found?
208+
- [ ] change scoring of failed seeds?
204209

205210
etc.

lib/src/cli.dart

+13-3
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ class Cli {
9292
abbr: 'l',
9393
help: 'Whether to simplify input & discovered seeds.',
9494
defaultsTo: true)
95+
..addFlag('snapshot',
96+
abbr: 'a',
97+
help: 'Whether to generate a snapshot before fuzzing. Defaults to true'
98+
' for scripts that end in .dart',
99+
defaultsTo: null)
95100
..addCommand(
96101
'simplify',
97102
ArgParser()
@@ -132,7 +137,7 @@ class Cli {
132137
_usageAndExit();
133138
}
134139

135-
final script = args.rest[0];
140+
var script = args.rest[0];
136141

137142
int batchSize;
138143
int vms;
@@ -167,6 +172,12 @@ class Cli {
167172
await failurePersistence.load();
168173
}
169174

175+
if (args['snapshot'] ?? script.endsWith('.dart')) {
176+
final snapshot = '$script.snapshot';
177+
await VmController.snapshot(script, snapshot);
178+
script = snapshot;
179+
}
180+
170181
List<VmController> vmControllers;
171182
_CliStats cliStats;
172183
List<IsolateMutator> isolateMutators;
@@ -183,8 +194,7 @@ class Cli {
183194
await Future.wait(
184195
vmControllers.map((vmController) => vmController.prestart()));
185196

186-
final statsCollector =
187-
StatsCollector(ProgramStats(await vmControllers[0].countPaths()));
197+
final statsCollector = StatsCollector(ProgramStats(0));
188198
isolateMutators = await _getIsolateMutators(args);
189199
final mutators = _getMutators(args, isolateMutators, seedLibrary);
190200
final driver = Driver(seedLibrary, failureLibrary, batchSize,

lib/src/vm_controller.dart

+37-38
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@
55
import 'dart:async';
66
import 'dart:convert';
77
import 'dart:io';
8-
import 'dart:math';
98

109
import 'package:dust/src/path.dart';
1110
import 'package:dust/src/path_canonicalizer.dart';
1211
import 'package:dust/src/vm_result.dart';
1312
import 'package:path/path.dart' as path;
1413
import 'package:pedantic/pedantic.dart';
15-
import 'package:vm_service_lib/vm_service_lib.dart';
16-
import 'package:vm_service_lib/vm_service_lib_io.dart';
14+
import 'package:vm_service/vm_service.dart';
15+
import 'package:vm_service/vm_service_io.dart';
1716

1817
/// A class to start a VM, connect to its service, and control its fuzz cases.
1918
///
@@ -47,17 +46,6 @@ class VmController {
4746
/// Check if this VM is connected.
4847
bool get isConnected => _serviceClient != null;
4948

50-
/// Count all paths that could be exercised for the target script.
51-
Future<int> countPaths() async {
52-
final fuzzIsolate = await _preRunCase('');
53-
54-
final pathCount = await _countPath(fuzzIsolate);
55-
56-
await _finalizeOutput(fuzzIsolate);
57-
58-
return pathCount;
59-
}
60-
6149
/// Kill the process & close the service connection.
6250
Future<void> dispose() async {
6351
try {
@@ -85,8 +73,10 @@ class VmController {
8573
final fuzzIsolate = await _preRunCase(input);
8674

8775
final paths = readCoverage ? await _getPath(fuzzIsolate) : null;
76+
await _serviceClient.resume(fuzzIsolate.id);
77+
await _fuzzIsolateDead();
8878

89-
final jsonOut = await _finalizeOutput(fuzzIsolate);
79+
final jsonOut = await _finalizeOutput();
9080

9181
final bool succeeded = jsonOut['success'];
9282
if (succeeded) {
@@ -122,19 +112,6 @@ class VmController {
122112
return _serviceClient;
123113
}
124114

125-
Future<int> _countPath(Isolate isolate) async {
126-
final report = await _serviceClient
127-
.getSourceReport(isolate.id, [SourceReportKind.kCoverage]);
128-
var sum = 0;
129-
for (final range in report.ranges) {
130-
if (range.coverage == null) {
131-
continue;
132-
}
133-
sum += range.coverage.hits.length + range.coverage.misses.length;
134-
}
135-
return sum;
136-
}
137-
138115
Future<T> _exponentialBackoff<T>(
139116
Future<T> Function() action, bool Function(T) accept,
140117
{Duration limit = const Duration(seconds: 5)}) async {
@@ -166,14 +143,9 @@ class VmController {
166143
throw Exception('$limit tries exceeded: $reason');
167144
}
168145

169-
/// Continue [fuzzIsolate] to unblock the main isolate and get json output.
170-
Future<Map<String, dynamic>> _finalizeOutput(Isolate fuzzIsolate) async {
171-
await _serviceClient.resume(fuzzIsolate.id);
172-
await _fuzzIsolateDead();
173-
174-
return _exponentialBackoff(
175-
() async => jsonDecode(_outputBuffer.toString().trim()), (_) => true);
176-
}
146+
/// Read the json output from the main isolate.
147+
Future<Map<String, dynamic>> _finalizeOutput() async => _exponentialBackoff(
148+
() async => jsonDecode(_outputBuffer.toString().trim()), (_) => true);
177149

178150
Future<Isolate> _fuzzIsolateComplete() async {
179151
final isolateRef = await _exponentialBackoff(
@@ -184,7 +156,7 @@ class VmController {
184156

185157
final isolate = await _exponentialBackoff(
186158
() async => _serviceClient.getIsolate(isolateRef.id),
187-
(isolate) => isolate.pauseEvent.kind == 'PauseExit',
159+
(isolate) => isolate == null || isolate.pauseEvent.kind == 'PauseExit',
188160
limit: Duration(seconds: _timeout + 2));
189161

190162
_timeElapsed ??= DateTime.now().difference(_startTime);
@@ -234,7 +206,7 @@ class VmController {
234206
path.join(Platform.environment['HOME'],
235207
'.pub-cache/global_packages/dust/bin/controller.dart.snapshot.dart2'),
236208
_script,
237-
"$_timeout",
209+
'$_timeout',
238210
]);
239211

240212
final vmCompleter = Completer();
@@ -246,12 +218,39 @@ class VmController {
246218
}));
247219
_processExit = vmCompleter.future;
248220

221+
_outputBuffer = StringBuffer();
249222
_process.stdout
250223
.transform(utf8.decoder)
251224
.listen((output) => _outputBuffer?.write(output));
252225

253226
_stdErrBuffer = StringBuffer();
254227
_process.stderr.transform(utf8.decoder).listen(_stdErrBuffer.write);
228+
229+
// Ensure observatory starts up properly.
230+
// TODO: Try a different port on failure.
231+
try {
232+
await _exponentialBackoff(
233+
() async => _outputBuffer.toString(),
234+
(output) =>
235+
output.contains('listening on') && output.contains(':$_port/'));
236+
} catch (_) {
237+
throw 'Observatory did not start on $_port\noutput:\n$_outputBuffer';
238+
}
239+
}
240+
241+
/// Generate a snapshot for the script, for faster fuzzing.
242+
static Future<void> snapshot(String script, String snapshotPath) async {
243+
final sdk = path.dirname(path.dirname(Platform.resolvedExecutable));
244+
245+
final result = await Process.run('$sdk/bin/dart', [
246+
'--snapshot=$snapshotPath',
247+
'--snapshot-kind=kernel',
248+
script,
249+
]);
250+
251+
if (result.exitCode != 0) {
252+
throw '${result.stdout}${result.stderr}';
253+
}
255254
}
256255
}
257256

pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ dependencies:
1111
pedantic: '^1.8.0+1'
1212
crypto: '^2.0.6'
1313
args: '^1.5.2'
14-
vm_service_lib: '^3.21.0'
14+
vm_service: '^1.2.0'
1515
test: '^1.6.5'
1616
quiver: '^2.0.3'
1717
collection: '^1.14.11'

0 commit comments

Comments
 (0)