• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2016 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
5"""
6Suppressions for V8 correctness fuzzer failures.
7
8We support three types of suppressions:
91. Ignore test case by pattern.
10Map a regular expression to a bug entry. A new failure will be reported
11when the pattern matches a JS test case.
12Subsequent matches will be recoreded under the first failure.
13
142. Ignore test run by output pattern:
15Map a regular expression to a bug entry. A new failure will be reported
16when the pattern matches the output of a particular run.
17Subsequent matches will be recoreded under the first failure.
18
193. Relax line-to-line comparisons with expressions of lines to ignore and
20lines to be normalized (i.e. ignore only portions of lines).
21These are not tied to bugs, be careful to not silently switch off this tool!
22
23Alternatively, think about adding a behavior change to v8_suppressions.js
24to silence a particular class of problems.
25"""
26
27import itertools
28import re
29
30# Max line length for regular experessions checking for lines to ignore.
31MAX_LINE_LENGTH = 512
32
33# For ignoring lines before carets and to ignore caret positions.
34CARET_RE = re.compile(r'^\s*\^\s*$')
35
36# Ignore by original source files. Map from bug->list of relative file paths in
37# V8, e.g. '/v8/test/mjsunit/d8-performance-now.js' including /v8/. A test will
38# be suppressed if one of the files below was used to mutate the test.
39IGNORE_SOURCES = {
40  # This contains a usage of f.arguments that often fires.
41  'crbug.com/662424': [
42    '/v8/test/mjsunit/bugs/bug-222.js',
43    '/v8/test/mjsunit/bugs/bug-941049.js',
44    '/v8/test/mjsunit/regress/regress-crbug-668795.js',
45    '/v8/test/mjsunit/regress/regress-2989.js',
46  ],
47
48  'crbug.com/681088': [
49    '/v8/test/mjsunit/asm/asm-validation.js',
50    '/v8/test/mjsunit/asm/b5528-comma.js',
51    '/v8/test/mjsunit/asm/pointer-masking.js',
52    '/v8/test/mjsunit/compiler/regress-443744.js',
53    '/v8/test/mjsunit/regress/regress-599719.js',
54    '/v8/test/mjsunit/regress/wasm/regression-647649.js',
55    '/v8/test/mjsunit/wasm/asm-wasm.js',
56    '/v8/test/mjsunit/wasm/asm-wasm-deopt.js',
57    '/v8/test/mjsunit/wasm/asm-wasm-heap.js',
58    '/v8/test/mjsunit/wasm/asm-wasm-literals.js',
59    '/v8/test/mjsunit/wasm/asm-wasm-stack.js',
60  ],
61
62  'crbug.com/681241': [
63    '/v8/test/mjsunit/regress/regress-617526.js',
64    '/v8/test/mjsunit/regress/wasm/regression-02862.js',
65  ],
66
67  'crbug.com/688159': [
68    '/v8/test/mjsunit/es7/exponentiation-operator.js',
69  ],
70}
71
72# Ignore by test case pattern. Map from bug->regexp.
73# Regular expressions are assumed to be compiled. We use regexp.match.
74# Make sure the code doesn't match in the preamble portion of the test case
75# (i.e. in the modified inlined mjsunit.js). You can reference the comment
76# between the two parts like so:
77#  'crbug.com/666308':
78#      re.compile(r'.*End stripped down and modified version.*'
79#                 r'\.prototype.*instanceof.*.*', re.S)
80# TODO(machenbach): Insert a JS sentinel between the two parts, because
81# comments are stripped during minimization.
82IGNORE_TEST_CASES = {
83}
84
85# Ignore by output pattern. Map from config->bug->regexp. Config '' is used
86# to match all configurations. Otherwise use either a compiler configuration,
87# e.g. fullcode or validate_asm or an architecture, e.g. x64 or ia32 or a
88# comma-separated combination, e.g. x64,fullcode, for more specific
89# suppressions.
90# Bug is preferred to be a crbug.com/XYZ, but can be any short distinguishable
91# label.
92# Regular expressions are assumed to be compiled. We use regexp.search.
93IGNORE_OUTPUT = {
94  '': {
95    'crbug.com/664068':
96        re.compile(r'RangeError(?!: byte length)', re.S),
97    'crbug.com/667678':
98        re.compile(r'\[native code\]', re.S),
99    'crbug.com/681806':
100        re.compile(r'WebAssembly\.Instance', re.S),
101    'crbug.com/681088':
102        re.compile(r'TypeError: Cannot read property \w+ of undefined', re.S),
103  },
104  'validate_asm': {
105    'validate_asm':
106        re.compile(r'TypeError'),
107  },
108}
109
110# Lines matching any of the following regular expressions will be ignored
111# if appearing on both sides. The capturing groups need to match exactly.
112# Use uncompiled regular expressions - they'll be compiled later.
113ALLOWED_LINE_DIFFS = [
114  # Ignore caret position in stack traces.
115  r'^\s*\^\s*$',
116
117  # Ignore some stack trace headers as messages might not match.
118  r'^(.*)TypeError: .* is not a function$',
119  r'^(.*)TypeError: .* is not a constructor$',
120  r'^(.*)TypeError: (.*) is not .*$',
121  r'^(.*)ReferenceError: .* is not defined$',
122  r'^(.*):\d+: ReferenceError: .* is not defined$',
123
124  # These are rarely needed. It includes some cases above.
125  r'^\w*Error: .* is not .*$',
126  r'^(.*) \w*Error: .* is not .*$',
127  r'^(.*):\d+: \w*Error: .* is not .*$',
128
129  # Some test cases just print the message.
130  r'^.* is not a function(.*)$',
131  r'^(.*) is not a .*$',
132
133  # Ignore lines of stack traces as character positions might not match.
134  r'^    at (?:new )?([^:]*):\d+:\d+(.*)$',
135  r'^(.*):\d+:(.*)$',
136
137  # crbug.com/662840
138  r"^.*(?:Trying to access ')?(\w*)(?:(?:' through proxy)|"
139  r"(?: is not defined))$",
140
141  # crbug.com/680064. This subsumes one of the above expressions.
142  r'^(.*)TypeError: .* function$',
143
144  # crbug.com/681326
145  r'^(.*<anonymous>):\d+:\d+(.*)$',
146]
147
148# Lines matching any of the following regular expressions will be ignored.
149# Use uncompiled regular expressions - they'll be compiled later.
150IGNORE_LINES = [
151  r'^Validation of asm\.js module failed: .+$',
152  r'^.*:\d+: Invalid asm.js: .*$',
153  r'^Warning: unknown flag .*$',
154  r'^Warning: .+ is deprecated.*$',
155  r'^Try --help for options$',
156
157  # crbug.com/677032
158  r'^.*:\d+:.*asm\.js.*: success$',
159
160  # crbug.com/680064
161  r'^\s*at .* \(<anonymous>\)$',
162
163  # crbug.com/689877
164  r'^.*SyntaxError: .*Stack overflow$',
165]
166
167
168###############################################################################
169# Implementation - you should not need to change anything below this point.
170
171# Compile regular expressions.
172ALLOWED_LINE_DIFFS = [re.compile(exp) for exp in ALLOWED_LINE_DIFFS]
173IGNORE_LINES = [re.compile(exp) for exp in IGNORE_LINES]
174
175ORIGINAL_SOURCE_PREFIX = 'v8-foozzie source: '
176
177def line_pairs(lines):
178  return itertools.izip_longest(
179      lines, itertools.islice(lines, 1, None), fillvalue=None)
180
181
182def caret_match(line1, line2):
183  if (not line1 or
184      not line2 or
185      len(line1) > MAX_LINE_LENGTH or
186      len(line2) > MAX_LINE_LENGTH):
187    return False
188  return bool(CARET_RE.match(line1) and CARET_RE.match(line2))
189
190
191def short_line_output(line):
192  if len(line) <= MAX_LINE_LENGTH:
193    # Avoid copying.
194    return line
195  return line[0:MAX_LINE_LENGTH] + '...'
196
197
198def ignore_by_regexp(line1, line2, allowed):
199  if len(line1) > MAX_LINE_LENGTH or len(line2) > MAX_LINE_LENGTH:
200    return False
201  for exp in allowed:
202    match1 = exp.match(line1)
203    match2 = exp.match(line2)
204    if match1 and match2:
205      # If there are groups in the regexp, ensure the groups matched the same
206      # things.
207      if match1.groups() == match2.groups():  # tuple comparison
208        return True
209  return False
210
211
212def diff_output(output1, output2, allowed, ignore1, ignore2):
213  """Returns a tuple (difference, source).
214
215  The difference is None if there's no difference, otherwise a string
216  with a readable diff.
217
218  The source is the last source output within the test case, or None if no
219  such output existed.
220  """
221  def useful_line(ignore):
222    def fun(line):
223      return all(not e.match(line) for e in ignore)
224    return fun
225
226  lines1 = filter(useful_line(ignore1), output1)
227  lines2 = filter(useful_line(ignore2), output2)
228
229  # This keeps track where we are in the original source file of the fuzz
230  # test case.
231  source = None
232
233  for ((line1, lookahead1), (line2, lookahead2)) in itertools.izip_longest(
234      line_pairs(lines1), line_pairs(lines2), fillvalue=(None, None)):
235
236    # Only one of the two iterators should run out.
237    assert not (line1 is None and line2 is None)
238
239    # One iterator ends earlier.
240    if line1 is None:
241      return '+ %s' % short_line_output(line2), source
242    if line2 is None:
243      return '- %s' % short_line_output(line1), source
244
245    # If lines are equal, no further checks are necessary.
246    if line1 == line2:
247      # Instrumented original-source-file output must be equal in both
248      # versions. It only makes sense to update it here when both lines
249      # are equal.
250      if line1.startswith(ORIGINAL_SOURCE_PREFIX):
251        source = line1[len(ORIGINAL_SOURCE_PREFIX):]
252      continue
253
254    # Look ahead. If next line is a caret, ignore this line.
255    if caret_match(lookahead1, lookahead2):
256      continue
257
258    # Check if a regexp allows these lines to be different.
259    if ignore_by_regexp(line1, line2, allowed):
260      continue
261
262    # Lines are different.
263    return (
264        '- %s\n+ %s' % (short_line_output(line1), short_line_output(line2)),
265        source,
266    )
267
268  # No difference found.
269  return None, source
270
271
272def get_suppression(arch1, config1, arch2, config2):
273  return V8Suppression(arch1, config1, arch2, config2)
274
275
276class Suppression(object):
277  def diff(self, output1, output2):
278    return None
279
280  def ignore_by_metadata(self, metadata):
281    return False
282
283  def ignore_by_content(self, testcase):
284    return False
285
286  def ignore_by_output1(self, output):
287    return False
288
289  def ignore_by_output2(self, output):
290    return False
291
292
293class V8Suppression(Suppression):
294  def __init__(self, arch1, config1, arch2, config2):
295    self.arch1 = arch1
296    self.config1 = config1
297    self.arch2 = arch2
298    self.config2 = config2
299
300  def diff(self, output1, output2):
301    return diff_output(
302        output1.splitlines(),
303        output2.splitlines(),
304        ALLOWED_LINE_DIFFS,
305        IGNORE_LINES,
306        IGNORE_LINES,
307    )
308
309  def ignore_by_content(self, testcase):
310    for bug, exp in IGNORE_TEST_CASES.iteritems():
311      if exp.match(testcase):
312        return bug
313    return False
314
315  def ignore_by_metadata(self, metadata):
316    for bug, sources in IGNORE_SOURCES.iteritems():
317      for source in sources:
318        if source in metadata['sources']:
319          return bug
320    return False
321
322  def ignore_by_output1(self, output):
323    return self.ignore_by_output(output, self.arch1, self.config1)
324
325  def ignore_by_output2(self, output):
326    return self.ignore_by_output(output, self.arch2, self.config2)
327
328  def ignore_by_output(self, output, arch, config):
329    def check(mapping):
330      for bug, exp in mapping.iteritems():
331        if exp.search(output):
332          return bug
333      return None
334    bug = check(IGNORE_OUTPUT.get('', {}))
335    if bug:
336      return bug
337    bug = check(IGNORE_OUTPUT.get(arch, {}))
338    if bug:
339      return bug
340    bug = check(IGNORE_OUTPUT.get(config, {}))
341    if bug:
342      return bug
343    bug = check(IGNORE_OUTPUT.get('%s,%s' % (arch, config), {}))
344    if bug:
345      return bug
346    return None
347