普通文本  |  225行  |  9.31 KB

# Copyright (c) 2014 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 chromium_deps
from common import utils
import crash_utils
import findit_for_crash as findit
import stacktrace


def SplitStacktrace(stacktrace_string):
  """Preprocesses stacktrace string into two parts, release and debug.

  Args:
    stacktrace_string: A string representation of stacktrace,
                       in clusterfuzz format.

  Returns:
    A tuple of list of strings, release build stacktrace and
    debug build stacktrace.
  """
  # Make sure we only parse release/debug build stacktrace, and ignore
  # unsymbolised stacktrace.
  in_release_or_debug_stacktrace = False
  release_build_stacktrace_lines = None
  debug_build_stacktrace_lines = None
  current_stacktrace_lines = []

  # Iterate through all lines in stacktrace.
  for line in stacktrace_string.splitlines():
    line = line.strip()

    # If the line starts with +, it signifies the start of new stacktrace.
    if line.startswith('+-') and line.endswith('-+'):
      if 'Release Build Stacktrace' in line:
        in_release_or_debug_stacktrace = True
        current_stacktrace_lines = []
        release_build_stacktrace_lines = current_stacktrace_lines

      elif 'Debug Build Stacktrace' in line:
        in_release_or_debug_stacktrace = True
        current_stacktrace_lines = []
        debug_build_stacktrace_lines = current_stacktrace_lines

      # If the stacktrace is neither release/debug build stacktrace, ignore
      # all lines after it until we encounter release/debug build stacktrace.
      else:
        in_release_or_debug_stacktrace = False

    # This case, it must be that the line is an actual stack frame, so add to
    # the current stacktrace.
    elif in_release_or_debug_stacktrace:
      current_stacktrace_lines.append(line)

  return (release_build_stacktrace_lines, debug_build_stacktrace_lines)


def FindCulpritCLs(stacktrace_string,
                   build_type,
                   chrome_regression=None,
                   component_regression=None,
                   chrome_crash_revision=None,
                   component_crash_revision=None,
                   crashing_component_path=None,
                   crashing_component_name=None,
                   crashing_component_repo_url=None):
  """Returns the result, a list of result.Result objects and message.

  If either or both of component_regression and component_crash_revision is not
  None, is is assumed that crashing_component_path and
  crashing_component_repo_url are not None.

  Args:
    stacktrace_string: A string representing stacktrace.
    build_type: The type of the job.
    chrome_regression: A string, chrome regression from clusterfuzz, in format
                       '123456:123457'
    component_regression: A string, component regression in the same format.
    chrome_crash_revision: A crash revision of chrome, in string.
    component_crash_revision: A crash revision of the component,
                              if component build.
    crashing_component_path: A relative path of the crashing component, as in
                             DEPS file. For example, it would be 'src/v8' for
                             v8 and 'src/third_party/WebKit' for blink.
    crashing_component_name: A name of the crashing component, such as v8.
    crashing_component_repo_url: The URL of the crashing component's repo, as
                                 shown in DEPS file. For example,
                                 'https://chromium.googlesource.com/skia.git'
                                 for skia.

  Returns:
    A list of result objects, along with the short description on where the
    result is from.
  """
  build_type = build_type.lower()
  component_to_crash_revision_dict = {}
  component_to_regression_dict = {}

  # If chrome regression is available, parse DEPS file.
  chrome_regression = crash_utils.SplitRange(chrome_regression)
  if chrome_regression:
    chrome_regression_start = chrome_regression[0]
    chrome_regression_end = chrome_regression[1]

    # Do not parse regression information for crashes introduced before the
    # first archived build.
    if chrome_regression_start != '0':
      component_to_regression_dict = chromium_deps.GetChromiumComponentRange(
          chrome_regression_start, chrome_regression_end)
      if not component_to_regression_dict:
        return (('Failed to get component regression ranges for chromium '
                 'regression range %s:%s'
                 % (chrome_regression_start, chrome_regression_end)), [])

  # Parse crash revision.
  if chrome_crash_revision:
    component_to_crash_revision_dict = chromium_deps.GetChromiumComponents(
        chrome_crash_revision)
    if not component_to_crash_revision_dict:
      return (('Failed to get component dependencies for chromium revision "%s"'
                % chrome_crash_revision), [])

  # Check if component regression information is available.
  component_regression = crash_utils.SplitRange(component_regression)
  if component_regression:
    component_regression_start = component_regression[0]
    component_regression_end = component_regression[1]

    # If this component already has an entry in parsed DEPS file, overwrite
    # regression range and url.
    if crashing_component_path in component_to_regression_dict:
      component_regression_info = \
          component_to_regression_dict[crashing_component_path]
      component_regression_info['old_revision'] = component_regression_start
      component_regression_info['new_revision'] = component_regression_end
      component_regression_info['repository'] = crashing_component_repo_url

    # if this component does not have an entry, add the entry to the parsed
    # DEPS file.
    else:
      repository_type = crash_utils.GetRepositoryType(
          component_regression_start)
      component_regression_info = {
          'path': crashing_component_path,
          'rolled': True,
          'name': crashing_component_name,
          'old_revision': component_regression_start,
          'new_revision': component_regression_end,
          'repository': crashing_component_repo_url,
          'repository_type': repository_type
      }
      component_to_regression_dict[crashing_component_path] = \
          component_regression_info

  # If component crash revision is available, add it to the parsed crash
  # revisions.
  if component_crash_revision:

    # If this component has already a crash revision info, overwrite it.
    if crashing_component_path in component_to_crash_revision_dict:
      component_crash_revision_info = \
          component_to_crash_revision_dict[crashing_component_path]
      component_crash_revision_info['revision'] = component_crash_revision
      component_crash_revision_info['repository'] = crashing_component_repo_url

    # If not, add it to the parsed DEPS.
    else:
      if utils.IsGitHash(component_crash_revision):
        repository_type = 'git'
      else:
        repository_type = 'svn'
      component_crash_revision_info = {
          'path': crashing_component_path,
          'name': crashing_component_name,
          'repository': crashing_component_repo_url,
          'repository_type': repository_type,
          'revision': component_crash_revision
      }
      component_to_crash_revision_dict[crashing_component_path] = \
          component_crash_revision_info

  # Parsed DEPS is used to normalize the stacktrace. Since parsed regression
  # and parsed crash state essentially contain same information, use either.
  if component_to_regression_dict:
    parsed_deps = component_to_regression_dict
  elif component_to_crash_revision_dict:
    parsed_deps = component_to_crash_revision_dict
  else:
    return (('Identifying culprit CL requires at lease one of regression '
             'information or crash revision'), [])

  # Split stacktrace into release build/debug build and parse them.
  (release_build_stacktrace, debug_build_stacktrace) = SplitStacktrace(
      stacktrace_string)
  if not (release_build_stacktrace or debug_build_stacktrace):
    parsed_release_build_stacktrace = stacktrace.Stacktrace(
        stacktrace_string.splitlines(), build_type, parsed_deps)
  else:
    parsed_release_build_stacktrace = stacktrace.Stacktrace(
        release_build_stacktrace, build_type, parsed_deps)

  parsed_debug_build_stacktrace = stacktrace.Stacktrace(
      debug_build_stacktrace, build_type, parsed_deps)

  # Get a highest priority callstack (main_stack) from stacktrace, with release
  # build stacktrace in higher priority than debug build stacktace. This stack
  # is the callstack to find blame information for.
  if parsed_release_build_stacktrace.stack_list:
    main_stack = parsed_release_build_stacktrace.GetCrashStack()
  elif parsed_debug_build_stacktrace.stack_list:
    main_stack = parsed_debug_build_stacktrace.GetCrashStack()
  else:
    if 'mac_' in build_type:
      return ('No line information available in stacktrace.', [])

    return ('Findit failed to find any stack trace. Is it in a new format?', [])

  # Run the algorithm on the parsed stacktrace, and return the result.
  stacktrace_list = [parsed_release_build_stacktrace,
                     parsed_debug_build_stacktrace]
  return findit.FindItForCrash(
      stacktrace_list, main_stack, component_to_regression_dict,
      component_to_crash_revision_dict)