• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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