Home | History | Annotate | Download | only in findit
      1 # Copyright (c) 2014 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import chromium_deps
      6 from common import utils
      7 import crash_utils
      8 import findit_for_crash as findit
      9 import stacktrace
     10 
     11 
     12 def SplitStacktrace(stacktrace_string):
     13   """Preprocesses stacktrace string into two parts, release and debug.
     14 
     15   Args:
     16     stacktrace_string: A string representation of stacktrace,
     17                        in clusterfuzz format.
     18 
     19   Returns:
     20     A tuple of list of strings, release build stacktrace and
     21     debug build stacktrace.
     22   """
     23   # Make sure we only parse release/debug build stacktrace, and ignore
     24   # unsymbolised stacktrace.
     25   in_release_or_debug_stacktrace = False
     26   release_build_stacktrace_lines = None
     27   debug_build_stacktrace_lines = None
     28   current_stacktrace_lines = []
     29 
     30   # Iterate through all lines in stacktrace.
     31   for line in stacktrace_string.splitlines():
     32     line = line.strip()
     33 
     34     # If the line starts with +, it signifies the start of new stacktrace.
     35     if line.startswith('+-') and line.endswith('-+'):
     36       if 'Release Build Stacktrace' in line:
     37         in_release_or_debug_stacktrace = True
     38         current_stacktrace_lines = []
     39         release_build_stacktrace_lines = current_stacktrace_lines
     40 
     41       elif 'Debug Build Stacktrace' in line:
     42         in_release_or_debug_stacktrace = True
     43         current_stacktrace_lines = []
     44         debug_build_stacktrace_lines = current_stacktrace_lines
     45 
     46       # If the stacktrace is neither release/debug build stacktrace, ignore
     47       # all lines after it until we encounter release/debug build stacktrace.
     48       else:
     49         in_release_or_debug_stacktrace = False
     50 
     51     # This case, it must be that the line is an actual stack frame, so add to
     52     # the current stacktrace.
     53     elif in_release_or_debug_stacktrace:
     54       current_stacktrace_lines.append(line)
     55 
     56   return (release_build_stacktrace_lines, debug_build_stacktrace_lines)
     57 
     58 
     59 def FindCulpritCLs(stacktrace_string,
     60                    build_type,
     61                    chrome_regression=None,
     62                    component_regression=None,
     63                    chrome_crash_revision=None,
     64                    component_crash_revision=None,
     65                    crashing_component_path=None,
     66                    crashing_component_name=None,
     67                    crashing_component_repo_url=None):
     68   """Returns the result, a list of result.Result objects and message.
     69 
     70   If either or both of component_regression and component_crash_revision is not
     71   None, is is assumed that crashing_component_path and
     72   crashing_component_repo_url are not None.
     73 
     74   Args:
     75     stacktrace_string: A string representing stacktrace.
     76     build_type: The type of the job.
     77     chrome_regression: A string, chrome regression from clusterfuzz, in format
     78                        '123456:123457'
     79     component_regression: A string, component regression in the same format.
     80     chrome_crash_revision: A crash revision of chrome, in string.
     81     component_crash_revision: A crash revision of the component,
     82                               if component build.
     83     crashing_component_path: A relative path of the crashing component, as in
     84                              DEPS file. For example, it would be 'src/v8' for
     85                              v8 and 'src/third_party/WebKit' for blink.
     86     crashing_component_name: A name of the crashing component, such as v8.
     87     crashing_component_repo_url: The URL of the crashing component's repo, as
     88                                  shown in DEPS file. For example,
     89                                  'https://chromium.googlesource.com/skia.git'
     90                                  for skia.
     91 
     92   Returns:
     93     A list of result objects, along with the short description on where the
     94     result is from.
     95   """
     96   build_type = build_type.lower()
     97   component_to_crash_revision_dict = {}
     98   component_to_regression_dict = {}
     99 
    100   # If chrome regression is available, parse DEPS file.
    101   chrome_regression = crash_utils.SplitRange(chrome_regression)
    102   if chrome_regression:
    103     chrome_regression_start = chrome_regression[0]
    104     chrome_regression_end = chrome_regression[1]
    105 
    106     # Do not parse regression information for crashes introduced before the
    107     # first archived build.
    108     if chrome_regression_start != '0':
    109       component_to_regression_dict = chromium_deps.GetChromiumComponentRange(
    110           chrome_regression_start, chrome_regression_end)
    111       if not component_to_regression_dict:
    112         return (('Failed to get component regression ranges for chromium '
    113                  'regression range %s:%s'
    114                  % (chrome_regression_start, chrome_regression_end)), [])
    115 
    116   # Parse crash revision.
    117   if chrome_crash_revision:
    118     component_to_crash_revision_dict = chromium_deps.GetChromiumComponents(
    119         chrome_crash_revision)
    120     if not component_to_crash_revision_dict:
    121       return (('Failed to get component dependencies for chromium revision "%s"'
    122                 % chrome_crash_revision), [])
    123 
    124   # Check if component regression information is available.
    125   component_regression = crash_utils.SplitRange(component_regression)
    126   if component_regression:
    127     component_regression_start = component_regression[0]
    128     component_regression_end = component_regression[1]
    129 
    130     # If this component already has an entry in parsed DEPS file, overwrite
    131     # regression range and url.
    132     if crashing_component_path in component_to_regression_dict:
    133       component_regression_info = \
    134           component_to_regression_dict[crashing_component_path]
    135       component_regression_info['old_revision'] = component_regression_start
    136       component_regression_info['new_revision'] = component_regression_end
    137       component_regression_info['repository'] = crashing_component_repo_url
    138 
    139     # if this component does not have an entry, add the entry to the parsed
    140     # DEPS file.
    141     else:
    142       repository_type = crash_utils.GetRepositoryType(
    143           component_regression_start)
    144       component_regression_info = {
    145           'path': crashing_component_path,
    146           'rolled': True,
    147           'name': crashing_component_name,
    148           'old_revision': component_regression_start,
    149           'new_revision': component_regression_end,
    150           'repository': crashing_component_repo_url,
    151           'repository_type': repository_type
    152       }
    153       component_to_regression_dict[crashing_component_path] = \
    154           component_regression_info
    155 
    156   # If component crash revision is available, add it to the parsed crash
    157   # revisions.
    158   if component_crash_revision:
    159 
    160     # If this component has already a crash revision info, overwrite it.
    161     if crashing_component_path in component_to_crash_revision_dict:
    162       component_crash_revision_info = \
    163           component_to_crash_revision_dict[crashing_component_path]
    164       component_crash_revision_info['revision'] = component_crash_revision
    165       component_crash_revision_info['repository'] = crashing_component_repo_url
    166 
    167     # If not, add it to the parsed DEPS.
    168     else:
    169       if utils.IsGitHash(component_crash_revision):
    170         repository_type = 'git'
    171       else:
    172         repository_type = 'svn'
    173       component_crash_revision_info = {
    174           'path': crashing_component_path,
    175           'name': crashing_component_name,
    176           'repository': crashing_component_repo_url,
    177           'repository_type': repository_type,
    178           'revision': component_crash_revision
    179       }
    180       component_to_crash_revision_dict[crashing_component_path] = \
    181           component_crash_revision_info
    182 
    183   # Parsed DEPS is used to normalize the stacktrace. Since parsed regression
    184   # and parsed crash state essentially contain same information, use either.
    185   if component_to_regression_dict:
    186     parsed_deps = component_to_regression_dict
    187   elif component_to_crash_revision_dict:
    188     parsed_deps = component_to_crash_revision_dict
    189   else:
    190     return (('Identifying culprit CL requires at lease one of regression '
    191              'information or crash revision'), [])
    192 
    193   # Split stacktrace into release build/debug build and parse them.
    194   (release_build_stacktrace, debug_build_stacktrace) = SplitStacktrace(
    195       stacktrace_string)
    196   if not (release_build_stacktrace or debug_build_stacktrace):
    197     parsed_release_build_stacktrace = stacktrace.Stacktrace(
    198         stacktrace_string.splitlines(), build_type, parsed_deps)
    199   else:
    200     parsed_release_build_stacktrace = stacktrace.Stacktrace(
    201         release_build_stacktrace, build_type, parsed_deps)
    202 
    203   parsed_debug_build_stacktrace = stacktrace.Stacktrace(
    204       debug_build_stacktrace, build_type, parsed_deps)
    205 
    206   # Get a highest priority callstack (main_stack) from stacktrace, with release
    207   # build stacktrace in higher priority than debug build stacktace. This stack
    208   # is the callstack to find blame information for.
    209   if parsed_release_build_stacktrace.stack_list:
    210     main_stack = parsed_release_build_stacktrace.GetCrashStack()
    211   elif parsed_debug_build_stacktrace.stack_list:
    212     main_stack = parsed_debug_build_stacktrace.GetCrashStack()
    213   else:
    214     if 'mac_' in build_type:
    215       return ('No line information available in stacktrace.', [])
    216 
    217     return ('Findit failed to find any stack trace. Is it in a new format?', [])
    218 
    219   # Run the algorithm on the parsed stacktrace, and return the result.
    220   stacktrace_list = [parsed_release_build_stacktrace,
    221                      parsed_debug_build_stacktrace]
    222   return findit.FindItForCrash(
    223       stacktrace_list, main_stack, component_to_regression_dict,
    224       component_to_crash_revision_dict)
    225