Flutter Engine
The Flutter Engine
minimize.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
3# for details. All rights reserved. Use of this source code is governed by a
4# BSD-style license that can be found in the LICENSE file.
5#
6"""
7This script minimizes a program generated by dartfuzz.dart.
8"""
9
10import argparse
11import multiprocessing as mp
12import re
13import shlex
14import subprocess
15
16STATEMENT_MASK_LINE = 0
17STATEMENT_NUMBER_LINE = 1
18EXPRESSION_MASK_LINE = 2
19EXPRESSION_NUMBER_LINE = 3
20
21
22class MaskGen(object):
23 """
24 Generates bitpatterns of <nbits> bits which indicate which
25 statements or expressions should be masked when passed as the
26 --smask or --emask parameter to dartfuzz.dart.
27 The patterns are generated in the following manner:
28 11111111111111111111111111111111
29 11111111111111110000000000000000
30 00000000000000001111111111111111
31 11111111111111111111111100000000
32 11111111111111110000000011111111
33 11111111000000001111111111111111
34 00000000111111111111111111111111
35 ...
36 If the error persists with a given a pattern it is stored in the
37 parameter <mask>.
38 """
39
40 def __init__(self, nbits, mask):
41 # Mask bitlength.
42 self.nbits = nbits
43 # Mask determining which statements or expressions to skip.
44 self.mask = mask
45 # Current mask modification start index.
47 # Max mask with all bits set to 1.
48 self.max = (1 << (nbits + 1)) - 1
49 # Current number of bits set in the mask modification mask.
50 self.nbitsVar = int(nbits)
51 # Current mask modification mask.
52 # This will be folded over the current value of self.mask
53 # starting from 0.
54 self.filter_mask = (1 << self.nbitsVar) - 1
55 # All the masks already tested.
56 self.tested = set()
57 self.gen_new_mask = False
58
59 def __iter__(self):
60 return self
61
62 def __next__(self):
63 return self.next()
64
65 # Update the set of tested masks.
66 # This is used to keep track of tested masks that did not produce a crash.
67 def update_tested(self, new_mask):
68 self.tested.add(new_mask)
69
70 # Update the current mask with the new mask that produced a crash.
71 # Note: We assume that if mask
72 # 11110000 produces a crash and
73 # 00000011 produces a crash that
74 # 11110011 will also produce a crash.
75 # This might not be true for some cases.
76 def update_mask(self, new_mask):
77 new_mask = (self.mask | new_mask) & self.max
78 self.mask = new_mask
79
80 # Count the bits of the current mask.
81 # More bits mean more statements omitted, so this is used
82 # to determine the quality of the current mask.
83 def count_bits(self):
84 return bin(self.mask).count('1')
85
86 def inc_var(self):
87 self.convolution_idx = 0
88 self.nbitsVar = self.nbitsVar >> 1
89 self.filter_mask = (1 << self.nbitsVar) - 1
90 return self.filter_mask == 0
91
92 def stop(self):
93 if self.convolution_idx >= self.nbits and self.inc_var() > 0:
94 # Check if the last run generated a new mask.
95 if self.gen_new_mask:
96 # Restart mask generation from beginning.
97 self.nbitsVar = self.nbits >> 1
98 self.inc_var()
99 self.gen_new_mask = False
100 return False
101 print("STOP")
102 return True
103 return False
104
105 def next(self):
106 while not self.stop():
107 # Generate a new mask according to the mask generation algorithm.
108 new_mask = (self.mask |
109 (self.filter_mask << self.convolution_idx)) & self.max
110 # Update the counter variable for the mask generation algorithm.
111 self.convolution_idx += self.nbitsVar
112 # If the new_mask has new bits and has not been tested yet.
113 # We need to check both because only masks that produce an
114 # error are merged into self.mask.
115 if new_mask != self.mask and \
116 new_mask not in self.tested:
117 # Add new mask to the set of tested masks.
118 self.tested.add(new_mask)
119 # Mark that we generated a new mask on this run.
120 self.gen_new_mask = True
121 # Return the new mask to be tested.
122 return new_mask
123 raise StopIteration()
124
125
126# Generate a Dart program for the given statement (smask) and
127# expression (emask) masks. Dartfuzz parameters for seed, ffi and
128# fp are static and therefore contained in the dartfuzz_cmd parameter.
129def generate_dart(dartfuzz_cmd, dart_test, smask, mask, do_expr):
130 if do_expr:
131 # Minimizing expressions.
132 cmds = shlex.split(dartfuzz_cmd) + [dart_test] + \
133 ['--mini', '--smask', '%d' % smask, '--emask', '%d' % mask]
134 p = subprocess.Popen(cmds, stdout=subprocess.PIPE)
135 else:
136 # Minimizing statements.
137 cmds = shlex.split(dartfuzz_cmd) + [dart_test] + \
138 ['--mini', '--smask', '%d' % mask]
139 p = subprocess.Popen(cmds, stdout=subprocess.PIPE)
140 p_stdout, p_stderr = p.communicate()
141 if p.returncode != 0:
142 raise 'Invalid return code on generate %d' % p.returncode
143 mask_new = 0
144 if do_expr:
145 mask_new = int(p_stdout.decode().splitlines()[EXPRESSION_MASK_LINE])
146 else:
147 mask_new = int(p_stdout.decode().splitlines()[STATEMENT_MASK_LINE])
148 return mask_new
149
150
151# Run the Dart program and check for error (by matching error_match_p).
152def run_dart(dart_cmd, dart_test, error_match_p, timeout):
153 cmd = ['timeout', str(timeout)] + shlex.split(dart_cmd) + [dart_test]
154 p = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
155 p_stdout, p_stderr = p.communicate()
156 if error_match_p.search(p_stdout.decode()) or \
157 error_match_p.search(p_stderr.decode()):
158 return True
159 if p.returncode != 0:
160 print("Warning: error return code %d" % p.returncode)
161 return False
162
163
164# Run multiple tries of the given dart_cmd and check for errors by matching
165# against error_match_p.
166def run_dart_mp(dart_cmd, dart_test, error_match_p, tries, nthreads, timeout):
167 if tries == 1:
168 return run_dart(dart_cmd, dart_test, error_match_p, timeout)
169 pool = mp.Pool(nthreads)
170 results = [
171 pool.apply_async(run_dart,
172 (dart_cmd, dart_test, error_match_p, timeout))
173 for i in range(tries)
174 ]
175 worker_done = [False for i in range(tries)]
176 while True:
177 all_done = True
178 for i, result in enumerate(results):
179 if worker_done[i]:
180 continue
181 try:
182 r = result.get(timeout=0.5)
183 worker_done[i] = True
184 if r:
185 pool.close()
186 pool.terminate()
187 pool.join()
188 return True
189 except mp.TimeoutError:
190 all_done = False
191 if all_done:
192 break
193 pool.close()
194 pool.join()
195 return False
196
197
198def minimize(dartfuzz_cmd,
199 dart_cmd,
200 dart_cmd_ref,
201 dart_test,
202 error_match1,
203 error_match2,
204 smask,
205 emask,
206 do_expr,
207 verbose,
208 tries,
209 tries_ref,
210 threads,
211 timeout,
212 print_every=32):
213 # Run dartfuzz command once to get the total number of statements
214 # and expressions in the generated program.
215 # Output will look like this:
216 # <Current Statement Mask>
217 # <Number of Statements>
218 # <Current Expression Mask>
219 # <Number of Expressions>
220 p = subprocess.Popen(
221 dartfuzz_cmd + " --mini " + dart_test,
222 shell=True,
223 stdout=subprocess.PIPE)
224 p_stdout, p_stderr = p.communicate()
225 if do_expr:
226 # Minimizing expressions.
227 nstmts = int(p_stdout.decode().splitlines()[EXPRESSION_NUMBER_LINE])
228 mask_gen = MaskGen(nstmts, emask)
229 else:
230 # Minimizing statements.
231 nstmts = int(p_stdout.decode().splitlines()[STATEMENT_NUMBER_LINE])
232 mask_gen = MaskGen(nstmts, smask)
233 # Best mask found so far.
234 min_mask = 0
235 # Maximum number of statements or expressions masked.
236 max_bits = 0
237 # Count number of iterations.
238 cntr = 0
239 # Result of the last run.
240 last_err = True
241 # Compile pattern to match standard output and error for a string
242 # identifying the crash.
243 error_match_p1 = re.compile(error_match1)
244 error_match_p2 = None
245 if error_match2 is not None:
246 error_match_p2 = re.compile(error_match2)
247 for mask in mask_gen:
248 if (verbose):
249 print("Mask: %x" % mask)
250 cntr += 1
251 if cntr % print_every == 0:
252 cntr = 0
253 print("Best I could do so far is mask %d/%d" \
254 % (max_bits, nstmts))
255 if do_expr:
256 print(dartfuzz_cmd + " " + dart_test +
257 " --mini --smask 0x%x --emask 0x%x" % (smask, min_mask))
258 else:
259 print(dartfuzz_cmd + " " + dart_test +
260 " --mini --smask 0x%x --emask 0" % (min_mask))
261 # The return value mask_new contains the actual mask applied by
262 # dartfuzz. Due to nesting, masking one statement might lead to other
263 # statements being masked as well; mask_new will contain this updated
264 # mask.
265 mask_new = generate_dart(dartfuzz_cmd, dart_test, smask, mask, do_expr)
266 # Run generated Dart program and check for error.
267 err = run_dart_mp(dart_cmd, dart_test, error_match_p1, tries, threads,
268 timeout)
269 if err and verbose:
270 print("Matched error 1 " + error_match1)
271 err_ref = True
272 if (dart_cmd_ref is not None) and (error_match_p2 is not None):
273 err_ref = run_dart_mp(dart_cmd_ref, dart_test, error_match_p2,
274 tries_ref, threads, timeout)
275 if err_ref and verbose:
276 print("Matched error 2 " + error_match2)
277 if err and err_ref:
278 # In case the mask used for generating the Dart program lead to an
279 # error, update the mask generator accordingly.
280 mask_gen.update_mask(mask)
281 # TODO (felih): this line should be enough but there seems to be a
282 # bug in dartfuzz.dart calculating mask_new.
283 mask_gen.update_mask(mask_new)
284 max_bits = mask_gen.count_bits()
285 min_mask = mask_gen.mask
286 elif last_err:
287 # If the last run removed the error try the inverse.
288 invMaskNew = mask_gen.mask | (mask_gen.max & ~mask_new)
289 if invMaskNew != mask_gen.mask and \
290 invMaskNew not in mask_gen.tested:
291 if (verbose):
292 print("Mask: %x (i)" % invMaskNew)
293 mask_new = generate_dart(dartfuzz_cmd, dart_test, smask,
294 invMaskNew, do_expr)
295 err = run_dart_mp(dart_cmd, dart_test, error_match_p1, tries,
296 threads, timeout)
297 if err and verbose:
298 print("Matched error 1 " + error_match1)
299 err_ref = True
300 if (dart_cmd_ref is not None) and (error_match_p2 is not None):
301 err_ref = run_dart_mp(dart_cmd_ref, dart_test,
302 error_match_p2, tries_ref, threads,
303 timeout)
304 if err_ref and verbose:
305 print("Matched error 2 " + error_match2)
306 if err and err_ref:
307 mask_gen.update_mask(invMaskNew)
308 mask_gen.update_mask(mask_new)
309 max_bits = mask_gen.count_bits()
310 min_mask = mask_gen.mask
311 last_err = err and err_ref
312 # Update the set of tested masks with the one we just tested.
313 mask_gen.update_tested(mask_new)
314
315 print("Best I could do is %d/%d" \
316 % (max_bits,nstmts))
317
318 if do_expr:
319 print(dartfuzz_cmd + " " + dart_test +
320 " --mini --smask 0x%x --emask 0x%x" % (smask, min_mask))
321 else:
322 print(dartfuzz_cmd + " " + dart_test +
323 " --mini --smask 0x%x --emask 0" % (min_mask))
324
325 return min_mask
326
327
328def main():
329 parser = argparse.ArgumentParser(
330 description='Minimize a generated Dart program ' +
331 ' while maintaining an identified crash or divergence. ' +
332 'A second Dart program can be specified for divergence testing.')
333 parser.add_argument(
334 '--dartfuzz',
335 required=True,
336 help="dartfuzz command string "
337 "e.g. dart dartfuzz.dart --no-ffi --no-fp --seed 243123600")
338 parser.add_argument(
339 '--dart',
340 help="Dart command string "
341 "e.g. ./sdk/out/ReleaseX64/dart",
342 required=True)
343 parser.add_argument(
344 '--dart-ref',
345 dest="dart_ref",
346 help="Dart command string for reference build " +
347 "(in case of divergence testing) " + "e.g. ./sdk/out/ReleaseX64/dart",
348 default=None)
349 parser.add_argument(
350 '--testfile',
351 required=True,
352 help="output filename for program generated by "
353 "dartfuzz command and passed to Dart command "
354 "e.g fuzz.dart")
355 parser.add_argument(
356 '--err',
357 help="string indicating an error for Dart cmd (no multiline matches)",
358 required=True)
359 parser.add_argument(
360 '--err-ref',
361 dest="err_ref",
362 help="string matching the diverging output for the Dart " +
363 "reference command (no multiline matches)",
364 default=None)
365 parser.add_argument(
366 '--smask', help="hexadecimal statements mask", default="0")
367 parser.add_argument(
368 '--emask', help="hexadecimal expressions mask", default="0")
369 parser.add_argument(
370 '--typ',
371 choices=["s", "e", "se"],
372 required=True,
373 help="minimize statements (s) or expressions (e) or both (se)")
374 parser.add_argument(
375 '--tries',
376 type=int,
377 default=1,
378 help='number of retries per run for Dart cmd')
379 parser.add_argument(
380 '--tries-ref',
381 type=int,
382 dest='tries_ref',
383 default=1,
384 help='number of retries per run for Dart reference cmd')
385 parser.add_argument(
386 '--threads',
387 type=int,
388 default=4,
389 help='number of threads to use for retries')
390 parser.add_argument(
391 '--timeout', type=int, default=60, help='timeout for Dart command')
392 parser.add_argument(
393 '--verbose', help="print intermediate results", action="store_true")
394 args = parser.parse_args()
395 timeout = args.timeout
396 do_stmt = "s" in args.typ
397 do_expr = "e" in args.typ
398 smask = int(args.smask, 16)
399 if do_stmt:
400 print("Minimizing Statements")
401 smask = minimize(args.dartfuzz, args.dart, args.dart_ref,
402 args.testfile, args.err, args.err_ref, smask,
403 int(args.emask, 16), False, args.verbose, args.tries,
404 args.tries_ref, args.threads, timeout)
405 if do_expr:
406 print("Minimizing Expressions")
407 minimize(args.dartfuzz, args.dart, args.dart_ref,
408 args.testfile, args.err, args.err_ref, smask,
409 int(args.emask, 16), True, args.verbose, args.tries,
410 args.tries_ref, args.threads, timeout)
411
412
413if __name__ == "__main__":
414 main()
int count
Definition: FontMgrTest.cpp:50
def __init__(self, nbits, mask)
Definition: minimize.py:40
def update_mask(self, new_mask)
Definition: minimize.py:76
def stop(self)
Definition: minimize.py:92
def __iter__(self)
Definition: minimize.py:59
def update_tested(self, new_mask)
Definition: minimize.py:67
def inc_var(self)
Definition: minimize.py:86
def __next__(self)
Definition: minimize.py:62
def next(self)
Definition: minimize.py:105
def count_bits(self)
Definition: minimize.py:83
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not set
Definition: switches.h:76
Definition: main.py:1
def run_dart(dart_cmd, dart_test, error_match_p, timeout)
Definition: minimize.py:152
def run_dart_mp(dart_cmd, dart_test, error_match_p, tries, nthreads, timeout)
Definition: minimize.py:166
def generate_dart(dartfuzz_cmd, dart_test, smask, mask, do_expr)
Definition: minimize.py:129
def main()
Definition: minimize.py:328
def minimize(dartfuzz_cmd, dart_cmd, dart_cmd_ref, dart_test, error_match1, error_match2, smask, emask, do_expr, verbose, tries, tries_ref, threads, timeout, print_every=32)
Definition: minimize.py:212
def print(*args, **kwargs)
Definition: run_tests.py:49