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 5from threading import Lock 6 7from common import utils 8import crash_utils 9 10 11class Blame(object): 12 """Represents a blame object. 13 14 The object contains blame information for one line of stack, and this 15 information is shown when there are no CLs that change the crashing files. 16 Attributes: 17 line_content: The content of the line to find the blame for. 18 component_name: The name of the component for this line. 19 stack_frame_index: The stack frame index of this file. 20 file_name: The name of the file. 21 line_number: The line that caused a crash. 22 author: The author of this line on the latest revision. 23 crash_revision: The revision that caused the crash. 24 revision: The latest revision of this line before the crash revision. 25 url: The url of the change for the revision. 26 range_start: The starting range of the regression for this component. 27 range_end: The ending range of the regression. 28 29 """ 30 31 def __init__(self, line_content, component_name, stack_frame_index, 32 file_name, line_number, author, revision, message, 33 url, range_start, range_end): 34 # Set all the variables from the arguments. 35 self.line_content = line_content 36 self.component_name = component_name 37 self.stack_frame_index = stack_frame_index 38 self.file = file_name 39 self.line_number = line_number 40 self.author = author 41 self.revision = revision 42 self.message = message 43 self.url = url 44 self.range_start = range_start 45 self.range_end = range_end 46 47 48class BlameList(object): 49 """Represents a list of blame objects. 50 51 Thread-safe. 52 """ 53 54 def __init__(self): 55 self.blame_list = [] 56 self.blame_list_lock = Lock() 57 58 def __getitem__(self, index): 59 return self.blame_list[index] 60 61 def FindBlame(self, callstack, component_to_crash_revision_dict, 62 component_to_regression_dict, parsers, 63 top_n_frames=10): 64 """Given a stack within a stacktrace, retrieves blame information. 65 66 Only either first 'top_n_frames' or the length of stack, whichever is 67 shorter, results are returned. The default value of 'top_n_frames' is 10. 68 69 Args: 70 callstack: The list of stack frames. 71 component_to_crash_revision_dict: A dictionary that maps component to its 72 crash revision. 73 component_to_regression_dict: A dictionary that maps component to its 74 revision range. 75 parsers: A list of two parsers, svn_parser and git_parser 76 top_n_frames: A number of stack frames to show the blame result for. 77 """ 78 # Only return blame information for first 'top_n_frames' frames. 79 stack_frames = callstack.GetTopNFrames(top_n_frames) 80 tasks = [] 81 # Iterate through frames in stack. 82 for stack_frame in stack_frames: 83 # If the component this line is from does not have a crash revision, 84 # it is not possible to get blame information, so ignore this line. 85 component_path = stack_frame.component_path 86 if component_path not in component_to_crash_revision_dict: 87 continue 88 89 crash_revision = component_to_crash_revision_dict[ 90 component_path]['revision'] 91 range_start = None 92 range_end = None 93 repository_type = crash_utils.GetRepositoryType(crash_revision) 94 repository_parser = parsers[repository_type] 95 96 # If the revision is in SVN, and if regression information is available, 97 # get it. For Git, we cannot know the ordering between hash numbers. 98 if repository_type == 'svn': 99 if component_to_regression_dict and \ 100 component_path in component_to_regression_dict: 101 component_object = component_to_regression_dict[component_path] 102 range_start = int(component_object['old_revision']) 103 range_end = int(component_object['new_revision']) 104 105 # Create a task to generate blame entry. 106 tasks.append({ 107 'function': self.__GenerateBlameEntry, 108 'args': [repository_parser, stack_frame, crash_revision, 109 range_start, range_end]}) 110 111 # Run all the tasks. 112 crash_utils.RunTasks(tasks) 113 114 def __GenerateBlameEntry(self, repository_parser, stack_frame, 115 crash_revision, range_start, range_end): 116 """Generates blame list from the arguments.""" 117 stack_frame_index = stack_frame.index 118 component_path = stack_frame.component_path 119 component_name = stack_frame.component_name 120 file_name = stack_frame.file_name 121 file_path = stack_frame.file_path 122 crashed_line_number = stack_frame.crashed_line_range[0] 123 124 if file_path.startswith(component_path): 125 file_path = file_path[len(component_path):] 126 127 # Parse blame information. 128 parsed_blame_info = repository_parser.ParseBlameInfo( 129 component_path, file_path, crashed_line_number, crash_revision) 130 131 # If it fails to retrieve information, do not do anything. 132 if not parsed_blame_info: 133 return 134 135 # Create blame object from the parsed info and add it to the list. 136 (line_content, revision, author, url, message) = parsed_blame_info 137 blame = Blame(line_content, component_name, stack_frame_index, file_name, 138 crashed_line_number, author, revision, message, url, 139 range_start, range_end) 140 141 with self.blame_list_lock: 142 self.blame_list.append(blame) 143 144 def FilterAndSortBlameList(self): 145 """Filters and sorts the blame list.""" 146 # Sort the blame list by its position in stack. 147 self.blame_list.sort(key=lambda blame: blame.stack_frame_index) 148 149 filtered_blame_list = [] 150 151 for blame in self.blame_list: 152 # If regression information is available, check if it needs to be 153 # filtered. 154 if blame.range_start and blame.range_end: 155 156 # Discards results that are after the end of regression. 157 if not utils.IsGitHash(blame.revision) and ( 158 int(blame.range_end) <= int(blame.revision)): 159 continue 160 161 filtered_blame_list.append(blame) 162 163 self.blame_list = filtered_blame_list 164