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