Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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
Type::kYUV Type::kRGBA() int(0.7 *637)
void print(void *str)
Definition bridge.cpp:126
__init__(self, nbits, mask)
Definition minimize.py:40
update_mask(self, new_mask)
Definition minimize.py:76
update_tested(self, new_mask)
Definition minimize.py:67
Definition main.py:1
generate_dart(dartfuzz_cmd, dart_test, smask, mask, do_expr)
Definition minimize.py:129
run_dart_mp(dart_cmd, dart_test, error_match_p, tries, nthreads, timeout)
Definition minimize.py:166
run_dart(dart_cmd, dart_test, error_match_p, timeout)
Definition minimize.py:152