blob: 0201b4b9e52bc4a0c3a5304d26ba171ac5e9e5f3 [file]
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
from collections import namedtuple
from analysis import detect_regression_range
from analysis.clusterfuzz_parser import ClusterfuzzParser
from analysis.crash_data import CrashData
from analysis.stacktrace import Stacktrace
from analysis.type_enums import SanitizerType
from decorators import cached_property
from libs.deps.dependency import Dependency
from libs.deps.dependency import DependencyRoll
_SANITIZER_SHORT_NAME_TO_SANITIZER_TYPE = {
'SYZYASAN': SanitizerType.SYZYASAN,
'TSAN': SanitizerType.THREAD_SANITIZER,
'UBSAN': SanitizerType.UBSAN,
'MSAN': SanitizerType.MEMORY_SANITIZER,
'ASAN': SanitizerType.ADDRESS_SANITIZER
}
def GetCommitCountInRegressionRange(get_repository, repo_url,
old_revision, new_revision):
"""Gets the number of commits in regression range."""
repository = get_repository(repo_url)
return len(repository.GetCommitsBetweenRevisions(old_revision, new_revision))
class ClusterfuzzData(CrashData):
"""Chrome crash report from Clusterfuzz.
Properties:
...
"""
def __init__(self, crash_data, get_repository, top_n_frames=None):
"""
Args:
crash_data (dict): Data sent from clusterfuzz, example:
{
'stack_trace': '==Error==: AddressSanitizer X', # Sample crash stack.
# The chrome version that produced the stack trace above.
'chrome_version': 'a6537f98f55d016e1d37dd60679253eb6e6ff8e8',
# Whether to redo the analysis of this crash.
'redo': True,
# Client can provide customized data.
'customized_data': {
# The regression range (right now for most crashes, it is chromium
# regression ranges).
'regression_range': {'dep_path': 'src/',
'repo_url': 'https://chromium.googlesource.com/cr/src/',
'old_revision': 'a6537f9...'
'new_revision': 'b63c7f9...'},
# All related crashed dependencies.
'dependencies': [
# dependency information - [dep_path, repo_url, revision]
{'dep_path': 'src/',
'repo_url': 'https://chromium.googlesource.com/cr/src/',
'revision': 'a6537f9...'},
...,
{'dep_path': 'src/v8',
'repo_url': 'https://chromium.googlesource.com/v8/v8/',
'revision': 'eqe12fe...'}
],
# The regression ranges for each dependency.
'dependency_rolls': [
# dependency roll information - [dep_path,
# repo_url, old_revision, new_revision]
{'dep_path': 'src/',
'repo_url': 'https://chromium.googlesource.com/cr/src/',
'old_revision': 'a6537f9...'
'new_revision': 'b63c7f9...'},
...,
{'dep_path': 'src/v8',
'repo_url': 'https://chromium.googlesource.com/v8/v8/',
'old_revision': 'eqe12fe...',
'new_revision': 'bere3f9...'}
],
'crash_type': 'CHECK failure',
'crash_address': '0x000000',
'sanitizer': 'ASAN',
'job_type': 'android_asan_win'
'testcase_id': 230193501234
},
'platform': 'linux', # On which platform the crash occurs.
# Identify which client this request is for.
'client_id': 'clusterfuzz',
# In clusterfuzz, the signature is the crash_state.
'signature': ('!data_->transaction in rankings.cc\n'
'anonymous namespace)::Transaction::Transaction\n'
'disk_cache::Rankings::Insert'),
}
get_repository(Factory of GitRepository): A factory func of GitRepository,
given a repo_url, it can create a real git repository.
top_n_frames (int): number of the frames in stacktrace we should parse.
"""
super(ClusterfuzzData, self).__init__(crash_data)
self._get_repository = get_repository
self._crashed_version = crash_data['crash_revision']
customized_data = crash_data['customized_data']
self._regression_repository = customized_data['regression_range']
self._raw_dependencies = customized_data['dependencies']
self._raw_dependency_rolls = customized_data['dependency_rolls']
self._top_n_frames = top_n_frames
self._crash_type = customized_data['crash_type']
self._crash_address = customized_data['crash_address']
self._sanitizer = _SANITIZER_SHORT_NAME_TO_SANITIZER_TYPE.get(
customized_data['sanitizer'])
self._job_type = customized_data['job_type']
self._testcase_id = str(customized_data['testcase_id'])
self._security_flag = customized_data['security_flag']
@property
def crash_type(self):
return self._crash_type
@property
def crash_address(self):
return self._crash_address
@property
def sanitizer(self):
return self._sanitizer
@property
def security_flag(self):
return self._security_flag
@property
def job_type(self):
return self._job_type
@property
def testcase_id(self):
return self._testcase_id
@property
def regression_repository(self):
return self._regression_repository
@cached_property
def stacktrace(self):
"""Parses stacktrace and returns parsed ``Stacktrace`` object."""
stacktrace = ClusterfuzzParser().Parse(
self._raw_stacktrace, self.dependencies, self.job_type,
self.crash_type, signature=self.signature,
top_n_frames=self._top_n_frames, crash_address=self.crash_address,
root_path=self.regression_repository['dep_path'],
root_repo_url=self.regression_repository['repo_url'])
if not stacktrace:
logging.warning('Failed to parse the stacktrace %s',
self._raw_stacktrace)
return stacktrace
@cached_property
def regression_range(self):
if not self._regression_repository or not (
self._regression_repository.get('old_revision') or
self._regression_repository.get('new_revision')):
return None
return (self._regression_repository.get('old_revision'),
self._regression_repository.get('new_revision'))
@cached_property
def dependencies(self):
return {
dep['dep_path']:
Dependency(dep['dep_path'], dep['repo_url'], dep['revision'])
for dep in self._raw_dependencies
}
@cached_property
def dependency_rolls(self):
return {
roll['dep_path']:
DependencyRoll(roll['dep_path'], roll['repo_url'],
roll.get('old_revision'), roll.get('new_revision'))
for roll in self._raw_dependency_rolls
if roll.get('old_revision') or roll.get('new_revision')
}
@property
def redo(self):
# Always redo analysis for clusterfuzz.
return True
@property
def identifiers(self):
return self.testcase_id
@cached_property
def commit_count_in_regression_range(self):
# 0 commits if the regression range is empty.
if (not self.regression_repository or
(not self.regression_repository.get('old_revision') and
not self.regression_repository.get('new_revision'))):
return 0
return GetCommitCountInRegressionRange(
self._get_repository,
self.regression_repository['repo_url'],
self.regression_repository.get('old_revision'),
self.regression_repository.get('new_revision'))