2016-12-19 10:13:48 +00:00
|
|
|
# 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
|
|
|
|
|
2020-03-28 12:45:36 +00:00
|
|
|
try:
|
|
|
|
# Python 3
|
|
|
|
from itertools import zip_longest
|
|
|
|
except ImportError:
|
|
|
|
# Python 2
|
|
|
|
from itertools import izip_longest as zip_longest
|
|
|
|
|
2016-12-19 10:13:48 +00:00
|
|
|
# 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*$')
|
|
|
|
|
2017-01-18 10:49:39 +00:00
|
|
|
# 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
|
2017-01-11 10:49:09 +00:00
|
|
|
# be suppressed if one of the files below was used to mutate the test.
|
|
|
|
IGNORE_SOURCES = {
|
|
|
|
}
|
|
|
|
|
2018-01-11 15:10:47 +00:00
|
|
|
# Ignore by test case pattern. Map from config->bug->regexp. Config '' is used
|
|
|
|
# to match all configurations. Otherwise use either a compiler configuration,
|
|
|
|
# e.g. ignition or validate_asm or an architecture, e.g. x64 or ia32.
|
|
|
|
# Bug is preferred to be a crbug.com/XYZ, but can be any short distinguishable
|
|
|
|
# label.
|
2017-05-15 14:05:51 +00:00
|
|
|
# Regular expressions are assumed to be compiled. We use regexp.search.
|
2016-12-19 10:13:48 +00:00
|
|
|
IGNORE_TEST_CASES = {
|
|
|
|
}
|
|
|
|
|
2018-01-11 15:10:47 +00:00
|
|
|
# Ignore by output pattern. Map from config->bug->regexp. See IGNORE_TEST_CASES
|
|
|
|
# on how to specify config keys.
|
2016-12-19 10:13:48 +00:00
|
|
|
# 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 = {
|
|
|
|
'': {
|
2017-02-24 13:10:15 +00:00
|
|
|
'crbug.com/689877':
|
|
|
|
re.compile(r'^.*SyntaxError: .*Stack overflow$', re.M),
|
2016-12-19 10:13:48 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
# 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 .*$',
|
2019-08-30 14:06:04 +00:00
|
|
|
r'^(.*):\d+: TypeError: Message suppressed for fuzzers.*$',
|
2016-12-19 10:13:48 +00:00
|
|
|
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 .*$',
|
|
|
|
|
2017-01-11 11:32:12 +00:00
|
|
|
# crbug.com/680064. This subsumes one of the above expressions.
|
|
|
|
r'^(.*)TypeError: .* function$',
|
2016-12-19 10:13:48 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
# Lines matching any of the following regular expressions will be ignored.
|
|
|
|
# Use uncompiled regular expressions - they'll be compiled later.
|
|
|
|
IGNORE_LINES = [
|
|
|
|
r'^Warning: unknown flag .*$',
|
|
|
|
r'^Warning: .+ is deprecated.*$',
|
|
|
|
r'^Try --help for options$',
|
2016-12-26 17:14:36 +00:00
|
|
|
|
2017-04-11 11:45:44 +00:00
|
|
|
# crbug.com/705962
|
|
|
|
r'^\s\[0x[0-9a-f]+\]$',
|
2016-12-19 10:13:48 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
###############################################################################
|
|
|
|
# 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]
|
|
|
|
|
2017-01-13 06:31:54 +00:00
|
|
|
ORIGINAL_SOURCE_PREFIX = 'v8-foozzie source: '
|
2016-12-19 10:13:48 +00:00
|
|
|
|
2020-02-03 16:45:57 +00:00
|
|
|
|
2020-02-11 10:23:31 +00:00
|
|
|
def get_output_capped(output1, output2):
|
|
|
|
"""Returns a pair of stdout strings.
|
2020-02-03 16:45:57 +00:00
|
|
|
|
2020-02-11 10:23:31 +00:00
|
|
|
The strings are safely capped if at least one run has crashed.
|
2020-02-03 16:45:57 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
# No length difference or no crash -> no capping.
|
2020-02-11 10:23:31 +00:00
|
|
|
if (len(output1.stdout) == len(output2.stdout) or
|
2020-02-03 16:45:57 +00:00
|
|
|
(not output1.HasCrashed() and not output2.HasCrashed())):
|
2020-02-11 10:23:31 +00:00
|
|
|
return output1.stdout, output2.stdout
|
2020-02-03 16:45:57 +00:00
|
|
|
|
|
|
|
# Both runs have crashed, cap by the shorter output.
|
|
|
|
if output1.HasCrashed() and output2.HasCrashed():
|
2020-02-11 10:23:31 +00:00
|
|
|
cap = min(len(output1.stdout), len(output2.stdout))
|
2020-02-03 16:45:57 +00:00
|
|
|
# Only the first run has crashed, cap by its output length.
|
|
|
|
elif output1.HasCrashed():
|
2020-02-11 10:23:31 +00:00
|
|
|
cap = len(output1.stdout)
|
2020-02-03 16:45:57 +00:00
|
|
|
# Similar if only the second run has crashed.
|
|
|
|
else:
|
2020-02-11 10:23:31 +00:00
|
|
|
cap = len(output2.stdout)
|
2020-02-03 16:45:57 +00:00
|
|
|
|
2020-02-11 10:23:31 +00:00
|
|
|
return output1.stdout[0:cap], output2.stdout[0:cap]
|
2020-02-03 16:45:57 +00:00
|
|
|
|
|
|
|
|
2016-12-19 10:13:48 +00:00
|
|
|
def line_pairs(lines):
|
2020-03-28 12:45:36 +00:00
|
|
|
return zip_longest(
|
2016-12-19 10:13:48 +00:00
|
|
|
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):
|
2017-01-16 19:53:35 +00:00
|
|
|
"""Returns a tuple (difference, source).
|
2017-01-13 06:31:54 +00:00
|
|
|
|
|
|
|
The difference is None if there's no difference, otherwise a string
|
|
|
|
with a readable diff.
|
|
|
|
|
2017-01-16 19:53:35 +00:00
|
|
|
The source is the last source output within the test case, or None if no
|
|
|
|
such output existed.
|
2017-01-13 06:31:54 +00:00
|
|
|
"""
|
2016-12-19 10:13:48 +00:00
|
|
|
def useful_line(ignore):
|
|
|
|
def fun(line):
|
|
|
|
return all(not e.match(line) for e in ignore)
|
|
|
|
return fun
|
|
|
|
|
2020-03-28 12:45:36 +00:00
|
|
|
lines1 = list(filter(useful_line(ignore1), output1))
|
|
|
|
lines2 = list(filter(useful_line(ignore2), output2))
|
2016-12-19 10:13:48 +00:00
|
|
|
|
2017-01-13 06:31:54 +00:00
|
|
|
# This keeps track where we are in the original source file of the fuzz
|
|
|
|
# test case.
|
2017-01-16 19:53:35 +00:00
|
|
|
source = None
|
2017-01-13 06:31:54 +00:00
|
|
|
|
2020-03-28 12:45:36 +00:00
|
|
|
for ((line1, lookahead1), (line2, lookahead2)) in zip_longest(
|
2016-12-19 10:13:48 +00:00
|
|
|
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:
|
2017-01-16 19:53:35 +00:00
|
|
|
return '+ %s' % short_line_output(line2), source
|
2016-12-19 10:13:48 +00:00
|
|
|
if line2 is None:
|
2017-01-16 19:53:35 +00:00
|
|
|
return '- %s' % short_line_output(line1), source
|
2016-12-19 10:13:48 +00:00
|
|
|
|
|
|
|
# If lines are equal, no further checks are necessary.
|
|
|
|
if line1 == line2:
|
2017-01-13 06:31:54 +00:00
|
|
|
# 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):]
|
2016-12-19 10:13:48 +00:00
|
|
|
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.
|
2017-01-13 06:31:54 +00:00
|
|
|
return (
|
|
|
|
'- %s\n+ %s' % (short_line_output(line1), short_line_output(line2)),
|
2017-01-16 13:00:50 +00:00
|
|
|
source,
|
2017-01-13 06:31:54 +00:00
|
|
|
)
|
2016-12-19 10:13:48 +00:00
|
|
|
|
|
|
|
# No difference found.
|
2017-01-16 19:53:35 +00:00
|
|
|
return None, source
|
2016-12-19 10:13:48 +00:00
|
|
|
|
|
|
|
|
2020-02-04 14:23:21 +00:00
|
|
|
def get_suppression(arch1, config1, arch2, config2, skip=False):
|
|
|
|
return V8Suppression(arch1, config1, arch2, config2, skip)
|
2016-12-19 10:13:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Suppression(object):
|
|
|
|
def diff(self, output1, output2):
|
|
|
|
return None
|
|
|
|
|
2017-01-11 10:49:09 +00:00
|
|
|
def ignore_by_metadata(self, metadata):
|
2018-01-11 15:10:47 +00:00
|
|
|
return None
|
2017-01-11 10:49:09 +00:00
|
|
|
|
|
|
|
def ignore_by_content(self, testcase):
|
2018-01-11 15:10:47 +00:00
|
|
|
return None
|
2016-12-19 10:13:48 +00:00
|
|
|
|
|
|
|
def ignore_by_output1(self, output):
|
2018-01-11 15:10:47 +00:00
|
|
|
return None
|
2016-12-19 10:13:48 +00:00
|
|
|
|
|
|
|
def ignore_by_output2(self, output):
|
2018-01-11 15:10:47 +00:00
|
|
|
return None
|
2016-12-19 10:13:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
class V8Suppression(Suppression):
|
2020-02-04 14:23:21 +00:00
|
|
|
def __init__(self, arch1, config1, arch2, config2, skip):
|
2016-12-19 10:13:48 +00:00
|
|
|
self.arch1 = arch1
|
|
|
|
self.config1 = config1
|
|
|
|
self.arch2 = arch2
|
|
|
|
self.config2 = config2
|
2020-02-04 14:23:21 +00:00
|
|
|
if skip:
|
|
|
|
self.allowed_line_diffs = []
|
|
|
|
self.ignore_output = {}
|
|
|
|
self.ignore_sources = {}
|
|
|
|
else:
|
|
|
|
self.allowed_line_diffs = ALLOWED_LINE_DIFFS
|
|
|
|
self.ignore_output = IGNORE_OUTPUT
|
|
|
|
self.ignore_sources = IGNORE_SOURCES
|
2016-12-19 10:13:48 +00:00
|
|
|
|
|
|
|
def diff(self, output1, output2):
|
2020-02-03 16:45:57 +00:00
|
|
|
# Diff capped lines in the presence of crashes.
|
2020-02-11 10:23:31 +00:00
|
|
|
return self.diff_lines(
|
|
|
|
*map(str.splitlines, get_output_capped(output1, output2)))
|
2020-02-03 16:45:57 +00:00
|
|
|
|
|
|
|
def diff_lines(self, output1_lines, output2_lines):
|
2016-12-19 10:13:48 +00:00
|
|
|
return diff_output(
|
2020-02-03 16:45:57 +00:00
|
|
|
output1_lines,
|
|
|
|
output2_lines,
|
2020-02-04 14:23:21 +00:00
|
|
|
self.allowed_line_diffs,
|
2016-12-19 10:13:48 +00:00
|
|
|
IGNORE_LINES,
|
|
|
|
IGNORE_LINES,
|
|
|
|
)
|
|
|
|
|
2017-01-11 10:49:09 +00:00
|
|
|
def ignore_by_content(self, testcase):
|
2017-05-15 14:05:51 +00:00
|
|
|
# Strip off test case preamble.
|
2017-05-16 07:36:58 +00:00
|
|
|
try:
|
|
|
|
lines = testcase.splitlines()
|
2017-12-12 08:52:02 +00:00
|
|
|
lines = lines[lines.index(
|
|
|
|
'print("js-mutation: start generated test case");'):]
|
2017-05-16 07:36:58 +00:00
|
|
|
content = '\n'.join(lines)
|
|
|
|
except ValueError:
|
|
|
|
# Search the whole test case if preamble can't be found. E.g. older
|
|
|
|
# already minimized test cases might have dropped the delimiter line.
|
|
|
|
content = testcase
|
2018-01-11 15:10:47 +00:00
|
|
|
for key in ['', self.arch1, self.arch2, self.config1, self.config2]:
|
2020-03-28 09:36:50 +00:00
|
|
|
for bug, exp in IGNORE_TEST_CASES.get(key, {}).items():
|
2018-01-11 15:10:47 +00:00
|
|
|
if exp.search(content):
|
|
|
|
return bug
|
|
|
|
return None
|
2016-12-19 10:13:48 +00:00
|
|
|
|
2017-01-11 10:49:09 +00:00
|
|
|
def ignore_by_metadata(self, metadata):
|
2020-03-28 09:36:50 +00:00
|
|
|
for bug, sources in self.ignore_sources.items():
|
2017-01-18 10:49:39 +00:00
|
|
|
for source in sources:
|
|
|
|
if source in metadata['sources']:
|
|
|
|
return bug
|
2018-01-11 15:10:47 +00:00
|
|
|
return None
|
2017-01-11 10:49:09 +00:00
|
|
|
|
2016-12-19 10:13:48 +00:00
|
|
|
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):
|
2020-03-28 09:36:50 +00:00
|
|
|
for bug, exp in mapping.items():
|
2016-12-19 10:13:48 +00:00
|
|
|
if exp.search(output):
|
|
|
|
return bug
|
|
|
|
return None
|
2018-01-11 15:10:47 +00:00
|
|
|
for key in ['', arch, config]:
|
2020-02-04 14:23:21 +00:00
|
|
|
bug = check(self.ignore_output.get(key, {}))
|
2018-01-11 15:10:47 +00:00
|
|
|
if bug:
|
|
|
|
return bug
|
2016-12-19 10:13:48 +00:00
|
|
|
return None
|