Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1357)

Side by Side Diff: tools/findit/stacktrace.py

Issue 430943003: [Findit] Plain objects to represent and parse stack trace. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: addressed codereview. Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« tools/findit/crash_utils.py ('K') | « tools/findit/crash_utils.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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 os
6 import re
7
8 import crash_utils
9
10
11 class Stacktrace(object):
stgao 2014/08/07 21:03:37 As StackFrame is used by CallStack and CallStack i
12 """Represents Stacktrace object.
13
14 Contains a list of callstacks, because one stacktrace might have more than
15 one callstacks.
16 """
17
18 def __init__(self, stacktrace, build_type):
19 self.stack_list = []
20 self.ParseStacktrace(stacktrace, build_type)
21
22 def ParseStacktrace(self, stacktrace, build_type):
23 """Parses stacktrace and normalizes it.
24
25 If there are multiple callstacks within the stacktrace,
26 it will parse each of them separately, and store them in the stack_list
27 variable.
28
29 Args:
30 stacktrace: A string containing stacktrace.
31 build_type: A string containing the build type of the crash.
32 """
33 # If the passed in string is empty, the object does not represent anything.
34 if not stacktrace:
35 self.stack_list = None
36 return
37
38 # Reset the stack list.
39 self.stack_list = []
40 reached_new_callstack = False
41 # Note that we do not need exact stack frame index, we only need relative
42 # position of a frame within a callstack. The reason for not extracting
43 # index from a line is that some stack frames do not have index.
44 stack_frame_index = 0
45 current_stack = None
46
47 for line in stacktrace:
48 (is_new_callstack, stack_priority) = self.__IsStartOfNewCallStack(
49 line, build_type)
50
51 if is_new_callstack:
52
53 # If this callstack is crash stack, update the boolean.
54 if not reached_new_callstack:
55 reached_new_callstack = True
56 current_stack = CallStack(stack_priority)
57
58 # If this is from freed or allocation, add the callstack we have
59 # to the list of callstacks, and increment the stack priority.
60 else:
61 stack_frame_index = 0
62 if current_stack and current_stack.frame_list:
63 self.stack_list.append(current_stack)
64 current_stack = CallStack(stack_priority)
65
66 # Generate stack frame object from the line.
67 parsed_stack_frame = self.__GenerateStackFrame(
68 stack_frame_index, line, build_type)
69
70 # If the line does not represent the stack frame, ignore this line.
71 if not parsed_stack_frame:
72 continue
73
74 # Add the parsed stack frame object to the current stack.
75 current_stack.Add(parsed_stack_frame)
76 stack_frame_index += 1
77
78 # Add the current callstack only if there are frames in it.
79 if current_stack and current_stack.frame_list:
80 self.stack_list.append(current_stack)
81
82 def __IsStartOfNewCallStack(self, line, build_type):
83 """Check if this line is the start of the new callstack.
84
85 Since each builds have different format of stacktrace, the logic for
86 checking the line for all builds is handled in here.
87
88 Args:
89 line: Line to check for.
90 build_type: The name of the build.
91
92 Returns:
93 True if the line is the start of new callstack, False otherwise. If True,
94 it also returns the priority of the line.
95 """
96 # Currently not supported.
97 if 'android' in build_type:
98 pass
99
100 elif 'syzyasan' in build_type:
101 # In syzyasan build, new stack starts with 'crash stack:',
102 # 'freed stack:', etc.
103 callstack_start_pattern = re.compile(r'^(.*) stack:$')
104 match = callstack_start_pattern.match(line)
105
106 # If the line matches the callstack start pattern.
107 if match:
108 # Check the type of the new match.
109 stack_type = match.group(1)
110
111 # Crash stack gets priority 0.
112 if stack_type == 'Crash':
113 return (True, 0)
114
115 # Other callstacks all get priority 1.
116 else:
117 return (True, 1)
118
119 elif 'tsan' in build_type:
120 # Create patterns for each callstack type.
121 crash_callstack_start_pattern = re.compile(
122 r'^(Read|Write) of size \d+')
123
124 allocation_callstack_start_pattern = re.compile(
125 r'^Previous (write|read) of size \d+')
126
127 location_callstack_start_pattern = re.compile(
128 r'^Location is heap block of size \d+')
129
130 # Crash stack gets priority 0.
131 if crash_callstack_start_pattern.match(line):
132 return (True, 0)
133
134 # All other stacks get priority 1.
135 if allocation_callstack_start_pattern.match(line):
136 return (True, 1)
137
138 if location_callstack_start_pattern.match(line):
139 return (True, 1)
140
141 else:
142 # In asan and other build types, crash stack can start
143 # in two different ways.
144 crash_callstack_start_pattern1 = re.compile(r'^==\d+== ?ERROR:')
145 crash_callstack_start_pattern2 = re.compile(
146 r'^(READ|WRITE) of size \d+ at')
147
148 freed_callstack_start_pattern = re.compile(
149 r'^freed by thread T\d+ (.* )?here:')
150
151 allocation_callstack_start_pattern = re.compile(
152 r'^previously allocated by thread T\d+ (.* )?here:')
153
154 other_callstack_start_pattern = re.compile(
155 r'^Thread T\d+ (.* )?created by')
156
157 # Crash stack gets priority 0.
158 if (crash_callstack_start_pattern1.match(line) or
159 crash_callstack_start_pattern2.match(line)):
160 return (True, 0)
161
162 # All other callstack gets priority 1.
163 if freed_callstack_start_pattern.match(line):
164 return (True, 1)
165
166 if allocation_callstack_start_pattern.match(line):
167 return (True, 1)
168
169 if other_callstack_start_pattern.match(line):
170 return (True, 1)
171
172 # If the line does not match any pattern, return false and a dummy for
173 # stack priority.
174 return (False, -1)
175
176 def __GenerateStackFrame(self, stack_frame_index, line, build_type):
177 """Extracts information from a line in stacktrace.
178
179 Args:
180 stack_frame_index: A stack frame index of this line.
181 line: A stacktrace string to extract data from.
182 build_type: A string containing the build type
183 of this crash (e.g. linux_asan_chrome_mp).
184
185 Returns:
186 A triple containing the name of the function, the path of the file and
187 the crashed line number.
188 """
189 line_parts = line.split()
190
191 try:
192 # Filter out lines that are not stack frame.
193 stack_frame_index_pattern = re.compile(r'#(\d+)')
194 if not stack_frame_index_pattern.match(line_parts[0]):
195 return None
196
197 # Tsan has different stack frame style from other builds.
198 if build_type.startswith('linux_tsan'):
199 file_path_and_line = line_parts[-2]
200 function = ' '.join(line_parts[1:-2])
201
202 else:
203 file_path_and_line = line_parts[-1]
204 function = ' '.join(line_parts[3:-1])
205
206 # Get file path and line info from the line.
207 file_path_and_line = file_path_and_line.split(':')
208 file_path = file_path_and_line[0]
209 crashed_line_number = int(file_path_and_line[1])
210
211 # Return None if the line is malformed.
212 except IndexError:
213 return None
214 except ValueError:
215 return None
216
217 # Normalize the file path so that it can be compared to repository path.
218 file_name = os.path.basename(file_path)
219 (component, file_path) = crash_utils.NormalizePathLinux(file_path)
220
221 # FIXME(jeun): Add other components.
222 if not (component == 'blink' or component == 'chromium'):
223 return None
224
225 # Return a new stack frame object with the parsed information.
226 return StackFrame(stack_frame_index, component, file_name, function,
227 file_path, crashed_line_number)
228
229 def __getitem__(self, index):
230 return self.stack_list[index]
231
232 def GetCrashStack(self):
233 for callstack in self.stack_list:
234 # Only the crash stack has the priority 0.
235 if callstack.priority == 0:
236 return callstack
237
238
239 class CallStack(object):
240 """Represents a call stack within a stacktrace.
241
242 It is a list of StackFrame object, and the object keeps track of whether
243 the stack is crash stack, freed or previously-allocated.
244 """
245
246 def __init__(self, stack_priority):
247 self.frame_list = []
248 self.priority = stack_priority
249
250 def Add(self, stacktrace_line):
251 self.frame_list.append(stacktrace_line)
252
253 def GetTopNFrames(self, n):
254 return self.frame_list[:n]
255
256
257 class StackFrame(object):
258 """Represents a frame in stacktrace.
259
260 Attributes:
261 index: An index of the stack frame.
262 component: A component this line represents, such as blink, chrome, etc.
263 file_name: The name of the file that crashed.
264 function: The function that caused the crash.
265 file_path: The path of the crashed file.
266 crashed_line_number: The line of the file that caused the crash.
267 """
268
269 def __init__(self, stack_frame_index, component, file_name,
270 function, file_path, crashed_line_number):
271 self.index = stack_frame_index
272 self.component = component
273 self.file_name = file_name
274 self.function = function
275 self.file_path = file_path
276 self.crashed_line_number = crashed_line_number
OLDNEW
« tools/findit/crash_utils.py ('K') | « tools/findit/crash_utils.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698