• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 the V8 project authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5from collections import namedtuple
6import time
7
8from . import base
9
10
11# Extra flags randomly added to all fuzz tests with numfuzz. List of tuples
12# (probability, flag).
13EXTRA_FLAGS = [
14    (0.1, '--always-opt'),
15    (0.1, '--assert-types'),
16    (0.1, '--interrupt-budget-for-feedback-allocation=0'),
17    (0.1, '--cache=code'),
18    (0.25, '--compact-maps'),
19    (0.1, '--force-slow-path'),
20    (0.2, '--future'),
21    (0.1, '--interrupt-budget=100'),
22    (0.1, '--liftoff'),
23    (0.2, '--no-analyze-environment-liveness'),
24    # TODO(machenbach): Enable when it doesn't collide with crashing on missing
25    # simd features.
26    #(0.1, '--no-enable-sse3'),
27    #(0.1, '--no-enable-ssse3'),
28    #(0.1, '--no-enable-sse4_1'),
29    (0.1, '--no-enable-sse4_2'),
30    (0.1, '--no-enable-sahf'),
31    (0.1, '--no-enable-avx'),
32    (0.1, '--no-enable-fma3'),
33    (0.1, '--no-enable-bmi1'),
34    (0.1, '--no-enable-bmi2'),
35    (0.1, '--no-enable-lzcnt'),
36    (0.1, '--no-enable-popcnt'),
37    (0.3, '--no-lazy-feedback-allocation'),
38    (0.1, '--no-liftoff'),
39    (0.1, '--no-opt'),
40    (0.2, '--no-regexp-tier-up'),
41    (0.1, '--no-wasm-tier-up'),
42    (0.1, '--regexp-interpret-all'),
43    (0.1, '--regexp-tier-up-ticks=10'),
44    (0.1, '--regexp-tier-up-ticks=100'),
45    (0.1, '--stress-background-compile'),
46    (0.1, '--stress-concurrent-inlining'),
47    (0.1, '--stress-flush-code'),
48    (0.1, '--stress-lazy-source-positions'),
49    (0.1, '--stress-wasm-code-gc'),
50    (0.1, '--turbo-instruction-scheduling'),
51    (0.1, '--turbo-stress-instruction-scheduling'),
52    (0.1, '--turbo-force-mid-tier-regalloc'),
53]
54
55def random_extra_flags(rng):
56  """Returns a random list of flags chosen from the configurations in
57  EXTRA_FLAGS.
58  """
59  return [flag for prob, flag in EXTRA_FLAGS if rng.random() < prob]
60
61
62class FuzzerConfig(object):
63  def __init__(self, probability, analyzer, fuzzer):
64    """
65    Args:
66      probability: of choosing this fuzzer (0; 10]
67      analyzer: instance of Analyzer class, can be None if no analysis is needed
68      fuzzer: instance of Fuzzer class
69    """
70    assert probability > 0 and probability <= 10
71
72    self.probability = probability
73    self.analyzer = analyzer
74    self.fuzzer = fuzzer
75
76
77class Analyzer(object):
78  def get_analysis_flags(self):
79    raise NotImplementedError()
80
81  def do_analysis(self, result):
82    raise NotImplementedError()
83
84
85class Fuzzer(object):
86  def create_flags_generator(self, rng, test, analysis_value):
87    """
88    Args:
89      rng: random number generator
90      test: test for which to create flags
91      analysis_value: value returned by the analyzer. None if there is no
92        corresponding analyzer to this fuzzer or the analysis phase is disabled
93    """
94    raise NotImplementedError()
95
96
97# TODO(majeski): Allow multiple subtests to run at once.
98class FuzzerProc(base.TestProcProducer):
99  def __init__(self, rng, count, fuzzers, disable_analysis=False):
100    """
101    Args:
102      rng: random number generator used to select flags and values for them
103      count: number of tests to generate based on each base test
104      fuzzers: list of FuzzerConfig instances
105      disable_analysis: disable analysis phase and filtering base on it. When
106        set, processor passes None as analysis result to fuzzers
107    """
108    super(FuzzerProc, self).__init__('Fuzzer')
109
110    self._rng = rng
111    self._count = count
112    self._fuzzer_configs = fuzzers
113    self._disable_analysis = disable_analysis
114    self._gens = {}
115
116  def setup(self, requirement=base.DROP_RESULT):
117    # Fuzzer is optimized to not store the results
118    assert requirement == base.DROP_RESULT
119    super(FuzzerProc, self).setup(requirement)
120
121  def _next_test(self, test):
122    if self.is_stopped:
123      return False
124
125    analysis_subtest = self._create_analysis_subtest(test)
126    if analysis_subtest:
127      return self._send_test(analysis_subtest)
128
129    self._gens[test.procid] = self._create_gen(test)
130    return self._try_send_next_test(test)
131
132  def _create_analysis_subtest(self, test):
133    if self._disable_analysis:
134      return None
135
136    analysis_flags = []
137    for fuzzer_config in self._fuzzer_configs:
138      if fuzzer_config.analyzer:
139        analysis_flags += fuzzer_config.analyzer.get_analysis_flags()
140
141    if analysis_flags:
142      analysis_flags = list(set(analysis_flags))
143      return self._create_subtest(test, 'analysis', flags=analysis_flags,
144                                  keep_output=True)
145
146  def _result_for(self, test, subtest, result):
147    if not self._disable_analysis:
148      if result is not None:
149        # Analysis phase, for fuzzing we drop the result.
150        if result.has_unexpected_output:
151          self._send_result(test, None)
152          return
153
154        self._gens[test.procid] = self._create_gen(test, result)
155
156    self._try_send_next_test(test)
157
158  def _create_gen(self, test, analysis_result=None):
159    # It will be called with analysis_result==None only when there is no
160    # analysis phase at all, so no fuzzer has it's own analyzer.
161    gens = []
162    indexes = []
163    for fuzzer_config in self._fuzzer_configs:
164      analysis_value = None
165      if analysis_result and fuzzer_config.analyzer:
166        analysis_value = fuzzer_config.analyzer.do_analysis(analysis_result)
167        if not analysis_value:
168          # Skip fuzzer for this test since it doesn't have analysis data
169          continue
170      p = fuzzer_config.probability
171      flag_gen = fuzzer_config.fuzzer.create_flags_generator(self._rng, test,
172                                                             analysis_value)
173      indexes += [len(gens)] * p
174      gens.append((p, flag_gen))
175
176    if not gens:
177      # No fuzzers for this test, skip it
178      return
179
180    i = 0
181    while not self._count or i < self._count:
182      main_index = self._rng.choice(indexes)
183      _, main_gen = gens[main_index]
184
185      flags = random_extra_flags(self._rng) + next(main_gen)
186      for index, (p, gen) in enumerate(gens):
187        if index == main_index:
188          continue
189        if self._rng.randint(1, 10) <= p:
190          flags += next(gen)
191
192      flags.append('--fuzzer-random-seed=%s' % self._next_seed())
193      yield self._create_subtest(test, str(i), flags=flags)
194
195      i += 1
196
197  def _try_send_next_test(self, test):
198    if not self.is_stopped:
199      for subtest in self._gens[test.procid]:
200        if self._send_test(subtest):
201          return True
202
203    del self._gens[test.procid]
204    return False
205
206  def _next_seed(self):
207    seed = None
208    while not seed:
209      seed = self._rng.randint(-2147483648, 2147483647)
210    return seed
211
212
213class ScavengeAnalyzer(Analyzer):
214  def get_analysis_flags(self):
215    return ['--fuzzer-gc-analysis']
216
217  def do_analysis(self, result):
218    for line in reversed(result.output.stdout.splitlines()):
219      if line.startswith('### Maximum new space size reached = '):
220        return int(float(line.split()[7]))
221
222
223class ScavengeFuzzer(Fuzzer):
224  def create_flags_generator(self, rng, test, analysis_value):
225    while True:
226      yield ['--stress-scavenge=%d' % (analysis_value or 100)]
227
228
229class MarkingAnalyzer(Analyzer):
230  def get_analysis_flags(self):
231    return ['--fuzzer-gc-analysis']
232
233  def do_analysis(self, result):
234    for line in reversed(result.output.stdout.splitlines()):
235      if line.startswith('### Maximum marking limit reached = '):
236        return int(float(line.split()[6]))
237
238
239class MarkingFuzzer(Fuzzer):
240  def create_flags_generator(self, rng, test, analysis_value):
241    while True:
242      yield ['--stress-marking=%d' % (analysis_value or 100)]
243
244
245class GcIntervalAnalyzer(Analyzer):
246  def get_analysis_flags(self):
247    return ['--fuzzer-gc-analysis']
248
249  def do_analysis(self, result):
250    for line in reversed(result.output.stdout.splitlines()):
251      if line.startswith('### Allocations = '):
252        return int(float(line.split()[3][:-1]))
253
254
255class GcIntervalFuzzer(Fuzzer):
256  def create_flags_generator(self, rng, test, analysis_value):
257    if analysis_value:
258      value = analysis_value // 10
259    else:
260      value = 10000
261    while True:
262      yield ['--random-gc-interval=%d' % value]
263
264
265class CompactionFuzzer(Fuzzer):
266  def create_flags_generator(self, rng, test, analysis_value):
267    while True:
268      yield ['--stress-compaction-random']
269
270
271class InterruptBudgetFuzzer(Fuzzer):
272  def create_flags_generator(self, rng, test, analysis_value):
273    while True:
274      # Half with half without lazy feedback allocation. The first flag
275      # overwrites potential flag negations from the extra flags list.
276      flag1 = rng.choice(
277          '--lazy-feedback-allocation', '--no-lazy-feedback-allocation')
278      # For most code paths, only one of the flags below has a meaning
279      # based on the flag above.
280      flag2 = '--interrupt-budget=%d' % rng.randint(0, 135168)
281      flag3 = '--interrupt-budget-for-feedback-allocation=%d' % rng.randint(
282          0, 940)
283
284      yield [flag1, flag2, flag3]
285
286
287class StackSizeFuzzer(Fuzzer):
288  def create_flags_generator(self, rng, test, analysis_value):
289    while True:
290      yield ['--stack-size=%d' % rng.randint(54, 983)]
291
292
293class TaskDelayFuzzer(Fuzzer):
294  def create_flags_generator(self, rng, test, analysis_value):
295    while True:
296      yield ['--stress-delay-tasks']
297
298
299class ThreadPoolSizeFuzzer(Fuzzer):
300  def create_flags_generator(self, rng, test, analysis_value):
301    while True:
302      yield ['--thread-pool-size=%d' % rng.randint(1, 8)]
303
304
305class DeoptAnalyzer(Analyzer):
306  MAX_DEOPT=1000000000
307
308  def __init__(self, min_interval):
309    super(DeoptAnalyzer, self).__init__()
310    self._min = min_interval
311
312  def get_analysis_flags(self):
313    return ['--deopt-every-n-times=%d' % self.MAX_DEOPT,
314            '--print-deopt-stress']
315
316  def do_analysis(self, result):
317    for line in reversed(result.output.stdout.splitlines()):
318      if line.startswith('=== Stress deopt counter: '):
319        counter = self.MAX_DEOPT - int(line.split(' ')[-1])
320        if counter < self._min:
321          # Skip this test since we won't generate any meaningful interval with
322          # given minimum.
323          return None
324        return counter
325
326
327class DeoptFuzzer(Fuzzer):
328  def __init__(self, min_interval):
329    super(DeoptFuzzer, self).__init__()
330    self._min = min_interval
331
332  def create_flags_generator(self, rng, test, analysis_value):
333    while True:
334      if analysis_value:
335        value = analysis_value // 2
336      else:
337        value = 10000
338      interval = rng.randint(self._min, max(value, self._min))
339      yield ['--deopt-every-n-times=%d' % interval]
340
341
342FUZZERS = {
343  'compaction': (None, CompactionFuzzer),
344  'delay': (None, TaskDelayFuzzer),
345  'deopt': (DeoptAnalyzer, DeoptFuzzer),
346  'gc_interval': (GcIntervalAnalyzer, GcIntervalFuzzer),
347  'interrupt': InterruptBudgetFuzzer,
348  'marking': (MarkingAnalyzer, MarkingFuzzer),
349  'scavenge': (ScavengeAnalyzer, ScavengeFuzzer),
350  'stack': (None, StackSizeFuzzer),
351  'threads': (None, ThreadPoolSizeFuzzer),
352}
353
354
355def create_fuzzer_config(name, probability, *args, **kwargs):
356  analyzer_class, fuzzer_class = FUZZERS[name]
357  return FuzzerConfig(
358      probability,
359      analyzer_class(*args, **kwargs) if analyzer_class else None,
360      fuzzer_class(*args, **kwargs),
361  )
362