Source code

Revision control

Other Tools

1
# This Source Code Form is subject to the terms of the Mozilla Public
2
# License, v. 2.0. If a copy of the MPL was not distributed with this
3
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5
from __future__ import absolute_import
6
import re
7
8
9
class LSANLeaks(object):
10
11
"""
12
Parses the log when running an LSAN build, looking for interesting stack frames
13
in allocation stacks
14
"""
15
16
def __init__(self, logger, scope=None, allowed=None, maxNumRecordedFrames=None):
17
self.logger = logger
18
self.inReport = False
19
self.fatalError = False
20
self.symbolizerError = False
21
self.foundFrames = set()
22
self.recordMoreFrames = None
23
self.currStack = None
24
self.maxNumRecordedFrames = maxNumRecordedFrames if maxNumRecordedFrames else 4
25
self.summaryData = None
26
self.scope = scope
27
self.allowedMatch = None
28
self.sawError = False
29
30
# Don't various allocation-related stack frames, as they do not help much to
31
# distinguish different leaks.
32
unescapedSkipList = [
33
"malloc", "js_malloc", "malloc_", "__interceptor_malloc", "moz_xmalloc",
34
"calloc", "js_calloc", "calloc_", "__interceptor_calloc", "moz_xcalloc",
35
"realloc", "js_realloc", "realloc_", "__interceptor_realloc", "moz_xrealloc",
36
"new",
37
"js::MallocProvider",
38
]
39
self.skipListRegExp = re.compile(
40
"^" + "|".join([re.escape(f) for f in unescapedSkipList]) + "$")
41
42
self.startRegExp = re.compile(
43
"==\d+==ERROR: LeakSanitizer: detected memory leaks")
44
self.fatalErrorRegExp = re.compile(
45
"==\d+==LeakSanitizer has encountered a fatal error.")
46
self.symbolizerOomRegExp = re.compile(
47
"LLVMSymbolizer: error reading file: Cannot allocate memory")
48
self.stackFrameRegExp = re.compile(" #\d+ 0x[0-9a-f]+ in ([^(</]+)")
49
self.sysLibStackFrameRegExp = re.compile(
50
" #\d+ 0x[0-9a-f]+ \(([^+]+)\+0x[0-9a-f]+\)")
51
self.summaryRegexp = re.compile(
52
"SUMMARY: AddressSanitizer: (\d+) byte\(s\) leaked in (\d+) allocation\(s\).")
53
self.rustRegexp = re.compile("::h[a-f0-9]{16}$")
54
self.setAllowed(allowed)
55
56
def setAllowed(self, allowedLines):
57
if not allowedLines:
58
self.allowedRegexp = None
59
else:
60
self.allowedRegexp = re.compile(
61
"^" + "|".join([re.escape(f) for f in allowedLines]))
62
63
def log(self, line):
64
if re.match(self.startRegExp, line):
65
self.inReport = True
66
# Downgrade this from an ERROR
67
self.sawError = True
68
return "LeakSanitizer: detected memory leaks"
69
70
if re.match(self.fatalErrorRegExp, line):
71
self.fatalError = True
72
return line
73
74
if re.match(self.symbolizerOomRegExp, line):
75
self.symbolizerError = True
76
return line
77
78
if not self.inReport:
79
return line
80
81
if line.startswith("Direct leak") or line.startswith("Indirect leak"):
82
self._finishStack()
83
self.recordMoreFrames = True
84
self.currStack = []
85
return line
86
87
summaryData = self.summaryRegexp.match(line)
88
if summaryData:
89
assert self.summaryData is None
90
self._finishStack()
91
self.inReport = False
92
self.summaryData = (int(item) for item in summaryData.groups())
93
# We don't return the line here because we want to control whether the
94
# leak is seen as an expected failure later
95
return
96
97
if not self.recordMoreFrames:
98
return line
99
100
stackFrame = re.match(self.stackFrameRegExp, line)
101
if stackFrame:
102
# Split the frame to remove any return types.
103
frame = stackFrame.group(1).split()[-1]
104
if not re.match(self.skipListRegExp, frame):
105
self._recordFrame(frame)
106
return line
107
108
sysLibStackFrame = re.match(self.sysLibStackFrameRegExp, line)
109
if sysLibStackFrame:
110
# System library stack frames will never match the skip list,
111
# so don't bother checking if they do.
112
self._recordFrame(sysLibStackFrame.group(1))
113
114
# If we don't match either of these, just ignore the frame.
115
# We'll end up with "unknown stack" if everything is ignored.
116
return line
117
118
def process(self):
119
failures = 0
120
121
if self.summaryData:
122
allowed = all(allowed for _, allowed in self.foundFrames)
123
self.logger.lsan_summary(*self.summaryData, allowed=allowed)
124
self.summaryData = None
125
126
if self.fatalError:
127
self.logger.error("LeakSanitizer | LeakSanitizer has encountered a fatal error.")
128
failures += 1
129
130
if self.symbolizerError:
131
self.logger.error("LeakSanitizer | LLVMSymbolizer was unable to allocate memory.\n"
132
"This will cause leaks that "
133
"should be ignored to instead be reported as an error")
134
failures += 1
135
136
if self.foundFrames:
137
self.logger.info("LeakSanitizer | To show the "
138
"addresses of leaked objects add report_objects=1 to LSAN_OPTIONS\n"
139
"This can be done in testing/mozbase/mozrunner/mozrunner/utils.py")
140
self.logger.info("Allowed depth was %d" % self.maxNumRecordedFrames)
141
142
for frames, allowed in self.foundFrames:
143
self.logger.lsan_leak(frames, scope=self.scope, allowed_match=allowed)
144
if not allowed:
145
failures += 1
146
147
if self.sawError and not (self.summaryData or
148
self.foundFrames or
149
self.fatalError or
150
self.symbolizerError):
151
self.logger.error("LeakSanitizer | Memory leaks detected but no leak report generated")
152
153
self.sawError = False
154
155
return failures
156
157
def _finishStack(self):
158
if self.recordMoreFrames and len(self.currStack) == 0:
159
self.currStack = {"unknown stack"}
160
if self.currStack:
161
self.foundFrames.add((tuple(self.currStack), self.allowedMatch))
162
self.currStack = None
163
self.allowedMatch = None
164
self.recordMoreFrames = False
165
self.numRecordedFrames = 0
166
167
def _recordFrame(self, frame):
168
if self.allowedMatch is None and self.allowedRegexp is not None:
169
self.allowedMatch = frame if self.allowedRegexp.match(frame) else None
170
frame = self._cleanFrame(frame)
171
self.currStack.append(frame)
172
self.numRecordedFrames += 1
173
if self.numRecordedFrames >= self.maxNumRecordedFrames:
174
self.recordMoreFrames = False
175
176
def _cleanFrame(self, frame):
177
# Rust frames aren't properly demangled and in particular can contain
178
# some trailing junk of the form ::h[a-f0-9]{16} that changes with
179
# compiler versions; see bug 1507350.
180
return self.rustRegexp.sub("", frame)