summaryrefslogtreecommitdiff
path: root/chromium/v8/tools/clusterfuzz
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/v8/tools/clusterfuzz')
-rw-r--r--chromium/v8/tools/clusterfuzz/v8_commands.py7
-rwxr-xr-xchromium/v8/tools/clusterfuzz/v8_foozzie.py80
-rwxr-xr-xchromium/v8/tools/clusterfuzz/v8_foozzie_test.py21
-rw-r--r--chromium/v8/tools/clusterfuzz/v8_mock.js14
4 files changed, 104 insertions, 18 deletions
diff --git a/chromium/v8/tools/clusterfuzz/v8_commands.py b/chromium/v8/tools/clusterfuzz/v8_commands.py
index 1956ef2802b..58a757e7a79 100644
--- a/chromium/v8/tools/clusterfuzz/v8_commands.py
+++ b/chromium/v8/tools/clusterfuzz/v8_commands.py
@@ -40,9 +40,6 @@ JS_SUPPRESSIONS = os.path.join(BASE_PATH, 'v8_suppressions.js')
ARCH_MOCKS = os.path.join(BASE_PATH, 'v8_mock_archs.js')
WEBASSEMBLY_MOCKS = os.path.join(BASE_PATH, 'v8_mock_webassembly.js')
-# Timeout in seconds for one d8 run.
-TIMEOUT = 3
-
def _startup_files(options):
"""Default files and optional config-specific mock files."""
@@ -70,7 +67,7 @@ class Command(object):
self.files = _startup_files(options)
- def run(self, testcase, verbose=False):
+ def run(self, testcase, timeout, verbose=False):
"""Run the executable with a specific testcase."""
args = [self.executable] + self.flags + self.files + [testcase]
if verbose:
@@ -82,7 +79,7 @@ class Command(object):
return Execute(
args,
cwd=os.path.dirname(os.path.abspath(testcase)),
- timeout=TIMEOUT,
+ timeout=timeout,
)
@property
diff --git a/chromium/v8/tools/clusterfuzz/v8_foozzie.py b/chromium/v8/tools/clusterfuzz/v8_foozzie.py
index b6638cc3774..b3cb8cf94fa 100755
--- a/chromium/v8/tools/clusterfuzz/v8_foozzie.py
+++ b/chromium/v8/tools/clusterfuzz/v8_foozzie.py
@@ -94,6 +94,10 @@ RETURN_FAIL = 2
BASE_PATH = os.path.dirname(os.path.abspath(__file__))
SANITY_CHECKS = os.path.join(BASE_PATH, 'v8_sanity_checks.js')
+# Timeout for one d8 run.
+SANITY_CHECK_TIMEOUT_SEC = 1
+TEST_TIMEOUT_SEC = 3
+
SUPPORTED_ARCHS = ['ia32', 'x64', 'arm', 'arm64']
# Output for suppressed failure case.
@@ -143,6 +147,25 @@ ORIGINAL_SOURCE_HASH_LENGTH = 3
# Placeholder string if no original source file could be determined.
ORIGINAL_SOURCE_DEFAULT = 'none'
+# Placeholder string for failures from crash tests. If a failure is found with
+# this signature, the matching sources should be moved to the mapping below.
+ORIGINAL_SOURCE_CRASHTESTS = 'placeholder for CrashTests'
+
+# Mapping from relative original source path (e.g. CrashTests/path/to/file.js)
+# to a string key. Map to the same key for duplicate issues. The key should
+# have more than 3 characters to not collide with other existing hashes.
+# If a symptom from a particular original source file is known to map to a
+# known failure, it can be added to this mapping. This should be done for all
+# failures from CrashTests, as those by default map to the placeholder above.
+KNOWN_FAILURES = {
+ # Foo.caller with asm.js: https://crbug.com/1042556
+ 'CrashTests/5712410200899584/04483.js': '.caller',
+ 'CrashTests/5703451898085376/02176.js': '.caller',
+ 'CrashTests/4846282433495040/04342.js': '.caller',
+ # Flaky issue that almost never repros.
+ 'CrashTests/5694376231632896/1033966.js': 'flaky',
+}
+
def infer_arch(d8):
"""Infer the V8 architecture from the build configuration next to the
@@ -229,7 +252,7 @@ def parse_args():
options.first = first_config_arguments.make_options(options)
options.second = second_config_arguments.make_options(options)
- # Ensure we make a sane comparison.
+ # Ensure we make a valid comparison.
if (options.first.d8 == options.second.d8 and
options.first.config == options.second.config):
parser.error('Need either executable or config difference.')
@@ -292,6 +315,7 @@ def print_difference(
else:
first_stdout = first_config_output.stdout.decode('utf-8', 'replace')
second_stdout = second_config_output.stdout.decode('utf-8', 'replace')
+ difference = difference.decode('utf-8', 'replace')
text = (FAILURE_TEMPLATE % dict(
configs='%s:%s' % (first_config_label, second_config_label),
@@ -313,6 +337,31 @@ def print_difference(
print(text.encode('utf-8', 'replace'))
+def cluster_failures(source, known_failures=None):
+ """Returns a string key for clustering duplicate failures.
+
+ Args:
+ source: The original source path where the failure happened.
+ known_failures: Mapping from original source path to failure key.
+ """
+ known_failures = known_failures or KNOWN_FAILURES
+ # No source known. Typical for manually uploaded issues. This
+ # requires also manual issue creation.
+ if not source:
+ return ORIGINAL_SOURCE_DEFAULT
+ # Source is known to produce a particular failure.
+ if source in known_failures:
+ return known_failures[source]
+ # Subsume all other sources from CrashTests under one key. Otherwise
+ # failures lead to new crash tests which in turn lead to new failures.
+ if source.startswith('CrashTests'):
+ return ORIGINAL_SOURCE_CRASHTESTS
+
+ # We map all remaining failures to a short hash of the original source.
+ long_key = hashlib.sha1(source.encode('utf-8')).hexdigest()
+ return long_key[:ORIGINAL_SOURCE_HASH_LENGTH]
+
+
def main():
options = parse_args()
@@ -342,8 +391,20 @@ def main():
# Sanity checks. Run both configurations with the sanity-checks file only and
# bail out early if different.
if not options.skip_sanity_checks:
- first_config_output = first_cmd.run(SANITY_CHECKS)
- second_config_output = second_cmd.run(SANITY_CHECKS)
+ first_config_output = first_cmd.run(
+ SANITY_CHECKS, timeout=SANITY_CHECK_TIMEOUT_SEC)
+
+ # Early bailout if first run was a timeout.
+ if timeout_bailout(first_config_output, 1):
+ return RETURN_PASS
+
+ second_config_output = second_cmd.run(
+ SANITY_CHECKS, timeout=SANITY_CHECK_TIMEOUT_SEC)
+
+ # Bailout if second run was a timeout.
+ if timeout_bailout(second_config_output, 2):
+ return RETURN_PASS
+
difference, _ = suppress.diff(first_config_output, second_config_output)
if difference:
# Special source key for sanity checks so that clusterfuzz dedupes all
@@ -354,13 +415,15 @@ def main():
first_config_output, second_config_output, difference)
return RETURN_FAIL
- first_config_output = first_cmd.run(options.testcase, verbose=True)
+ first_config_output = first_cmd.run(
+ options.testcase, timeout=TEST_TIMEOUT_SEC, verbose=True)
# Early bailout if first run was a timeout.
if timeout_bailout(first_config_output, 1):
return RETURN_PASS
- second_config_output = second_cmd.run(options.testcase, verbose=True)
+ second_config_output = second_cmd.run(
+ options.testcase, timeout=TEST_TIMEOUT_SEC, verbose=True)
# Bailout if second run was a timeout.
if timeout_bailout(second_config_output, 2):
@@ -368,12 +431,6 @@ def main():
difference, source = suppress.diff(first_config_output, second_config_output)
- if source:
- long_key = hashlib.sha1(source.encode('utf-8')).hexdigest()
- source_key = long_key[:ORIGINAL_SOURCE_HASH_LENGTH]
- else:
- source_key = ORIGINAL_SOURCE_DEFAULT
-
if difference:
# Only bail out due to suppressed output if there was a difference. If a
# suppression doesn't show up anymore in the statistics, we might want to
@@ -383,6 +440,7 @@ def main():
if fail_bailout(second_config_output, suppress.ignore_by_output2):
return RETURN_FAIL
+ source_key = cluster_failures(source)
print_difference(
options, source_key, first_cmd, second_cmd,
first_config_output, second_config_output, difference, source)
diff --git a/chromium/v8/tools/clusterfuzz/v8_foozzie_test.py b/chromium/v8/tools/clusterfuzz/v8_foozzie_test.py
index f82afc9e205..4a5affd76ea 100755
--- a/chromium/v8/tools/clusterfuzz/v8_foozzie_test.py
+++ b/chromium/v8/tools/clusterfuzz/v8_foozzie_test.py
@@ -51,8 +51,8 @@ class ConfigTest(unittest.TestCase):
assert all(map(lambda x: x[3] in KNOWN_BUILDS, EXPERIMENTS))
# Ensure we compare different configs and same d8, or same config
# to different d8.
- is_sane_comparison = lambda x: (x[1] == x[2]) == ('d8' != x[3])
- assert all(map(is_sane_comparison, EXPERIMENTS))
+ is_valid_comparison = lambda x: (x[1] == x[2]) == ('d8' != x[3])
+ assert all(map(is_valid_comparison, EXPERIMENTS))
# All flags have a probability.
first_is_float = lambda x: type(x[0]) == float
assert all(map(first_is_float, FLAGS))
@@ -101,6 +101,23 @@ class ConfigTest(unittest.TestCase):
class UnitTest(unittest.TestCase):
+ def testCluster(self):
+ crash_test_example_path = 'CrashTests/path/to/file.js'
+ self.assertEqual(
+ v8_foozzie.ORIGINAL_SOURCE_DEFAULT,
+ v8_foozzie.cluster_failures(''))
+ self.assertEqual(
+ v8_foozzie.ORIGINAL_SOURCE_CRASHTESTS,
+ v8_foozzie.cluster_failures(crash_test_example_path))
+ self.assertEqual(
+ '_o_O_',
+ v8_foozzie.cluster_failures(
+ crash_test_example_path,
+ known_failures={crash_test_example_path: '_o_O_'}))
+ self.assertEqual(
+ '980',
+ v8_foozzie.cluster_failures('v8/test/mjsunit/apply.js'))
+
def testDiff(self):
def diff_fun(one, two, skip=False):
suppress = v8_suppressions.get_suppression(
diff --git a/chromium/v8/tools/clusterfuzz/v8_mock.js b/chromium/v8/tools/clusterfuzz/v8_mock.js
index 6372a7afe2b..ca1336a0df6 100644
--- a/chromium/v8/tools/clusterfuzz/v8_mock.js
+++ b/chromium/v8/tools/clusterfuzz/v8_mock.js
@@ -149,6 +149,20 @@ Object.defineProperty(
Float64Array = mock(Float64Array);
})();
+// Mock buffer access via DataViews because of varying NaN patterns.
+(function() {
+ const origIsNaN = isNaN;
+ const deNaNify = function(value) { return origIsNaN(value) ? 1 : value; };
+ const origSetFloat32 = DataView.prototype.setFloat32;
+ DataView.prototype.setFloat32 = function(offset, value, ...rest) {
+ origSetFloat32.call(this, offset, deNaNify(value), ...rest);
+ };
+ const origSetFloat64 = DataView.prototype.setFloat64;
+ DataView.prototype.setFloat64 = function(offset, value, ...rest) {
+ origSetFloat64.call(this, offset, deNaNify(value), ...rest);
+ };
+})();
+
// Mock Worker.
(function() {
let index = 0;