aboutsummaryrefslogtreecommitdiff
path: root/tools/foozzie/v8_suppressions.py
blob: 23d137ae4ef20091cc3a9653518544a85ca6379c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# Copyright 2016 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""
Suppressions for V8 correctness fuzzer failures.

We support three types of suppressions:
1. Ignore test case by pattern.
Map a regular expression to a bug entry. A new failure will be reported
when the pattern matches a JS test case.
Subsequent matches will be recoreded under the first failure.

2. Ignore test run by output pattern:
Map a regular expression to a bug entry. A new failure will be reported
when the pattern matches the output of a particular run.
Subsequent matches will be recoreded under the first failure.

3. Relax line-to-line comparisons with expressions of lines to ignore and
lines to be normalized (i.e. ignore only portions of lines).
These are not tied to bugs, be careful to not silently switch off this tool!

Alternatively, think about adding a behavior change to v8_suppressions.js
to silence a particular class of problems.
"""

import itertools
import re

# Max line length for regular experessions checking for lines to ignore.
MAX_LINE_LENGTH = 512

# For ignoring lines before carets and to ignore caret positions.
CARET_RE = re.compile(r'^\s*\^\s*$')

# Ignore by original source files. Map from bug->list of relative file paths in
# V8, e.g. '/v8/test/mjsunit/d8-performance-now.js' including /v8/. A test will
# be suppressed if one of the files below was used to mutate the test.
IGNORE_SOURCES = {
  # This contains a usage of f.arguments that often fires.
  'crbug.com/662424': [
    '/v8/test/mjsunit/bugs/bug-222.js',
    '/v8/test/mjsunit/bugs/bug-941049.js',
    '/v8/test/mjsunit/regress/regress-crbug-668795.js',
    '/v8/test/mjsunit/regress/regress-2989.js',
  ],

  'crbug.com/681088': [
    '/v8/test/mjsunit/asm/asm-validation.js',
    '/v8/test/mjsunit/asm/b5528-comma.js',
    '/v8/test/mjsunit/asm/pointer-masking.js',
    '/v8/test/mjsunit/compiler/regress-443744.js',
    '/v8/test/mjsunit/regress/regress-599719.js',
    '/v8/test/mjsunit/regress/wasm/regression-647649.js',
    '/v8/test/mjsunit/wasm/asm-wasm.js',
    '/v8/test/mjsunit/wasm/asm-wasm-deopt.js',
    '/v8/test/mjsunit/wasm/asm-wasm-heap.js',
    '/v8/test/mjsunit/wasm/asm-wasm-literals.js',
    '/v8/test/mjsunit/wasm/asm-wasm-stack.js',
  ],

  'crbug.com/681241': [
    '/v8/test/mjsunit/regress/regress-617526.js',
    '/v8/test/mjsunit/regress/wasm/regression-02862.js',
  ],

  'crbug.com/688159': [
    '/v8/test/mjsunit/es7/exponentiation-operator.js',
  ],
}

# Ignore by test case pattern. Map from bug->regexp.
# Regular expressions are assumed to be compiled. We use regexp.match.
# Make sure the code doesn't match in the preamble portion of the test case
# (i.e. in the modified inlined mjsunit.js). You can reference the comment
# between the two parts like so:
#  'crbug.com/666308':
#      re.compile(r'.*End stripped down and modified version.*'
#                 r'\.prototype.*instanceof.*.*', re.S)
# TODO(machenbach): Insert a JS sentinel between the two parts, because
# comments are stripped during minimization.
IGNORE_TEST_CASES = {
}

# Ignore by output pattern. Map from config->bug->regexp. Config '' is used
# to match all configurations. Otherwise use either a compiler configuration,
# e.g. fullcode or validate_asm or an architecture, e.g. x64 or ia32 or a
# comma-separated combination, e.g. x64,fullcode, for more specific
# suppressions.
# Bug is preferred to be a crbug.com/XYZ, but can be any short distinguishable
# label.
# Regular expressions are assumed to be compiled. We use regexp.search.
IGNORE_OUTPUT = {
  '': {
    'crbug.com/664068':
        re.compile(r'RangeError(?!: byte length)', re.S),
    'crbug.com/667678':
        re.compile(r'\[native code\]', re.S),
    'crbug.com/681806':
        re.compile(r'WebAssembly\.Instance', re.S),
    'crbug.com/681088':
        re.compile(r'TypeError: Cannot read property \w+ of undefined', re.S),
  },
  'validate_asm': {
    'validate_asm':
        re.compile(r'TypeError'),
  },
}

# Lines matching any of the following regular expressions will be ignored
# if appearing on both sides. The capturing groups need to match exactly.
# Use uncompiled regular expressions - they'll be compiled later.
ALLOWED_LINE_DIFFS = [
  # Ignore caret position in stack traces.
  r'^\s*\^\s*$',

  # Ignore some stack trace headers as messages might not match.
  r'^(.*)TypeError: .* is not a function$',
  r'^(.*)TypeError: .* is not a constructor$',
  r'^(.*)TypeError: (.*) is not .*$',
  r'^(.*)ReferenceError: .* is not defined$',
  r'^(.*):\d+: ReferenceError: .* is not defined$',

  # These are rarely needed. It includes some cases above.
  r'^\w*Error: .* is not .*$',
  r'^(.*) \w*Error: .* is not .*$',
  r'^(.*):\d+: \w*Error: .* is not .*$',

  # Some test cases just print the message.
  r'^.* is not a function(.*)$',
  r'^(.*) is not a .*$',

  # Ignore lines of stack traces as character positions might not match.
  r'^    at (?:new )?([^:]*):\d+:\d+(.*)$',
  r'^(.*):\d+:(.*)$',

  # crbug.com/662840
  r"^.*(?:Trying to access ')?(\w*)(?:(?:' through proxy)|"
  r"(?: is not defined))$",

  # crbug.com/680064. This subsumes one of the above expressions.
  r'^(.*)TypeError: .* function$',

  # crbug.com/681326
  r'^(.*<anonymous>):\d+:\d+(.*)$',
]

# Lines matching any of the following regular expressions will be ignored.
# Use uncompiled regular expressions - they'll be compiled later.
IGNORE_LINES = [
  r'^Validation of asm\.js module failed: .+$',
  r'^.*:\d+: Invalid asm.js: .*$',
  r'^Warning: unknown flag .*$',
  r'^Warning: .+ is deprecated.*$',
  r'^Try --help for options$',

  # crbug.com/677032
  r'^.*:\d+:.*asm\.js.*: success$',

  # crbug.com/680064
  r'^\s*at .* \(<anonymous>\)$',

  # crbug.com/689877
  r'^.*SyntaxError: .*Stack overflow$',
]


###############################################################################
# Implementation - you should not need to change anything below this point.

# Compile regular expressions.
ALLOWED_LINE_DIFFS = [re.compile(exp) for exp in ALLOWED_LINE_DIFFS]
IGNORE_LINES = [re.compile(exp) for exp in IGNORE_LINES]

ORIGINAL_SOURCE_PREFIX = 'v8-foozzie source: '

def line_pairs(lines):
  return itertools.izip_longest(
      lines, itertools.islice(lines, 1, None), fillvalue=None)


def caret_match(line1, line2):
  if (not line1 or
      not line2 or
      len(line1) > MAX_LINE_LENGTH or
      len(line2) > MAX_LINE_LENGTH):
    return False
  return bool(CARET_RE.match(line1) and CARET_RE.match(line2))


def short_line_output(line):
  if len(line) <= MAX_LINE_LENGTH:
    # Avoid copying.
    return line
  return line[0:MAX_LINE_LENGTH] + '...'


def ignore_by_regexp(line1, line2, allowed):
  if len(line1) > MAX_LINE_LENGTH or len(line2) > MAX_LINE_LENGTH:
    return False
  for exp in allowed:
    match1 = exp.match(line1)
    match2 = exp.match(line2)
    if match1 and match2:
      # If there are groups in the regexp, ensure the groups matched the same
      # things.
      if match1.groups() == match2.groups():  # tuple comparison
        return True
  return False


def diff_output(output1, output2, allowed, ignore1, ignore2):
  """Returns a tuple (difference, source).

  The difference is None if there's no difference, otherwise a string
  with a readable diff.

  The source is the last source output within the test case, or None if no
  such output existed.
  """
  def useful_line(ignore):
    def fun(line):
      return all(not e.match(line) for e in ignore)
    return fun

  lines1 = filter(useful_line(ignore1), output1)
  lines2 = filter(useful_line(ignore2), output2)

  # This keeps track where we are in the original source file of the fuzz
  # test case.
  source = None

  for ((line1, lookahead1), (line2, lookahead2)) in itertools.izip_longest(
      line_pairs(lines1), line_pairs(lines2), fillvalue=(None, None)):

    # Only one of the two iterators should run out.
    assert not (line1 is None and line2 is None)

    # One iterator ends earlier.
    if line1 is None:
      return '+ %s' % short_line_output(line2), source
    if line2 is None:
      return '- %s' % short_line_output(line1), source

    # If lines are equal, no further checks are necessary.
    if line1 == line2:
      # Instrumented original-source-file output must be equal in both
      # versions. It only makes sense to update it here when both lines
      # are equal.
      if line1.startswith(ORIGINAL_SOURCE_PREFIX):
        source = line1[len(ORIGINAL_SOURCE_PREFIX):]
      continue

    # Look ahead. If next line is a caret, ignore this line.
    if caret_match(lookahead1, lookahead2):
      continue

    # Check if a regexp allows these lines to be different.
    if ignore_by_regexp(line1, line2, allowed):
      continue

    # Lines are different.
    return (
        '- %s\n+ %s' % (short_line_output(line1), short_line_output(line2)),
        source,
    )

  # No difference found.
  return None, source


def get_suppression(arch1, config1, arch2, config2):
  return V8Suppression(arch1, config1, arch2, config2)


class Suppression(object):
  def diff(self, output1, output2):
    return None

  def ignore_by_metadata(self, metadata):
    return False

  def ignore_by_content(self, testcase):
    return False

  def ignore_by_output1(self, output):
    return False

  def ignore_by_output2(self, output):
    return False


class V8Suppression(Suppression):
  def __init__(self, arch1, config1, arch2, config2):
    self.arch1 = arch1
    self.config1 = config1
    self.arch2 = arch2
    self.config2 = config2

  def diff(self, output1, output2):
    return diff_output(
        output1.splitlines(),
        output2.splitlines(),
        ALLOWED_LINE_DIFFS,
        IGNORE_LINES,
        IGNORE_LINES,
    )

  def ignore_by_content(self, testcase):
    for bug, exp in IGNORE_TEST_CASES.iteritems():
      if exp.match(testcase):
        return bug
    return False

  def ignore_by_metadata(self, metadata):
    for bug, sources in IGNORE_SOURCES.iteritems():
      for source in sources:
        if source in metadata['sources']:
          return bug
    return False

  def ignore_by_output1(self, output):
    return self.ignore_by_output(output, self.arch1, self.config1)

  def ignore_by_output2(self, output):
    return self.ignore_by_output(output, self.arch2, self.config2)

  def ignore_by_output(self, output, arch, config):
    def check(mapping):
      for bug, exp in mapping.iteritems():
        if exp.search(output):
          return bug
      return None
    bug = check(IGNORE_OUTPUT.get('', {}))
    if bug:
      return bug
    bug = check(IGNORE_OUTPUT.get(arch, {}))
    if bug:
      return bug
    bug = check(IGNORE_OUTPUT.get(config, {}))
    if bug:
      return bug
    bug = check(IGNORE_OUTPUT.get('%s,%s' % (arch, config), {}))
    if bug:
      return bug
    return None