# 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)