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
5import re
6
7from threading import Lock
8
9import crash_utils
10
11
12REVIEW_URL_PATTERN = re.compile(r'Review URL:( *)(.*?)/(\d+)')
13
14
15class Match(object):
16  """Represents a match entry.
17
18  A match is a CL that is suspected to have caused the crash. A match object
19  contains information about files it changes, their authors, etc.
20
21  Attributes:
22    is_revert: True if this CL is reverted by other CL.
23    revert_of: If this CL is a revert of some other CL, a revision number/
24               git hash of that CL.
25    crashed_line_numbers: The list of lines that caused crash for this CL.
26    function_list: The list of functions that caused the crash.
27    min_distance: The minimum distance between the lines that CL changed and
28                  lines that caused the crash.
29    changed_files: The list of files that the CL changed.
30    changed_file_urls: The list of URLs for the file.
31    author: The author of the CL.
32    component_name: The name of the component that this CL belongs to.
33    stack_frame_indices: For files that caused crash, list of where in the
34                         stackframe they occur.
35    priorities: A list of priorities for each of the changed file. A priority
36                is 1 if the file changes a crashed line, and 2 if it changes
37                the file but not the crashed line.
38    reivision_url: The revision URL of the CL.
39    review_url: The codereview URL that reviews this CL.
40    reviewers: The list of people that reviewed this CL.
41    reason: The reason why this CL is suspected.
42  """
43  REVERT_PATTERN = re.compile(r'(revert\w*) r?(\d+)', re.I)
44
45  def __init__(self, revision, component_name):
46    self.is_revert = False
47    self.revert_of = None
48    self.message = None
49    self.crashed_line_numbers = []
50    self.function_list = []
51    self.min_distance = crash_utils.INFINITY
52    self.min_distance_info = None
53    self.changed_files = []
54    self.changed_file_urls = []
55    self.author = revision['author']
56    self.component_name = component_name
57    self.stack_frame_indices = []
58    self.priorities = []
59    self.revision_url = revision['url']
60    self.review_url = ''
61    self.reviewers = []
62    self.reason = None
63
64  def ParseMessage(self, message, codereview_api_url):
65    """Parses the message.
66
67    It checks the message to extract the code review website and list of
68    reviewers, and it also checks if the CL is a revert of another CL.
69
70    Args:
71      message: The message to parse.
72      codereview_api_url: URL to retrieve codereview data from.
73    """
74    self.message = message
75    for line in message.splitlines():
76      line = line.strip()
77      review_url_line_match = REVIEW_URL_PATTERN.match(line)
78
79      # Check if the line has the code review information.
80      if review_url_line_match:
81
82        # Get review number for the code review site from the line.
83        issue_number = review_url_line_match.group(3)
84
85        # Get JSON from the code review site, ignore the line if it fails.
86        url = codereview_api_url % issue_number
87        json_string = crash_utils.GetDataFromURL(url)
88        if not json_string:
89          continue
90
91        # Load the JSON from the string, and get the list of reviewers.
92        code_review = crash_utils.LoadJSON(json_string)
93        if code_review:
94          self.reviewers = code_review['reviewers']
95
96      # Check if this CL is a revert of other CL.
97      if line.lower().startswith('revert'):
98        self.is_revert = True
99
100        # Check if the line says what CL this CL is a revert of.
101        revert = self.REVERT_PATTERN.match(line)
102        if revert:
103          self.revert_of = revert.group(2)
104        return
105
106
107class MatchSet(object):
108  """Represents a set of matches.
109
110  Attributes:
111    matches: A map from CL to a match object.
112    cls_to_ignore: A set of CLs to ignore.
113    matches_lock: A lock guarding matches dictionary.
114  """
115
116  def __init__(self, codereview_api_url):
117    self.codereview_api_url = codereview_api_url
118    self.matches = {}
119    self.cls_to_ignore = set()
120    self.matches_lock = Lock()
121
122  def RemoveRevertedCLs(self):
123    """Removes CLs that are revert."""
124    for cl in self.matches:
125      if cl in self.cls_to_ignore:
126        del self.matches[cl]
127