2 # This Source Code Form is subject to the terms of the Mozilla Public
3 # License, v. 2.0. If a copy of the MPL was not distributed with this
4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 from __future__
import print_function
7 usage
= """%prog: A test for OOM conditions in the shell.
9 %prog finds segfaults and other errors caused by incorrect handling of
10 allocation during OOM (out-of-memory) conditions.
13 help = """Check for regressions only. This runs a set of files with a known
14 number of OOM errors (specified by REGRESSION_COUNT), and exits with a non-zero
15 result if more or less errors are found. See js/src/Makefile.in for invocation.
27 from optparse
import OptionParser
29 #####################################################################
31 #####################################################################
32 def run(args
, stdin
=None):
33 class ThreadWorker(threading
.Thread
):
34 def __init__(self
, pipe
):
35 super(ThreadWorker
, self
).__init
__()
42 line
= self
.pipe
.readline()
49 args
= shlex
.split(args
)
51 args
= [str(a
) for a
in args
] # convert to strs
53 stdin_pipe
= subprocess
.PIPE
if stdin
else None
54 proc
= subprocess
.Popen(args
, stdin
=stdin_pipe
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
56 proc
.stdin
.write(stdin
)
59 stdout_worker
= ThreadWorker(proc
.stdout
)
60 stderr_worker
= ThreadWorker(proc
.stderr
)
68 except KeyboardInterrupt as e
:
71 stdout
, stderr
= stdout_worker
.all
, stderr_worker
.all
72 result
= (stdout
, stderr
, proc
.returncode
)
76 (out
, err
, exit
) = run('find ../jit-test/tests -name "*.js"')
77 if (err
, exit
) != ("", 0):
78 sys
.exit("Wrong directory, run from an objdir")
83 #####################################################################
85 #####################################################################
86 def in_blacklist(sig
):
87 return sig
in blacklist
89 def add_to_blacklist(sig
):
90 blacklist
[sig
] = blacklist
.get(sig
, 0)
93 # How often is a particular lines important for this.
95 """Keep track of the amount of times individual lines occur, in order to
96 prioritize the errors which occur most frequently."""
98 for string
,count
in blacklist
.items():
99 for line
in string
.split("\n"):
100 counts
[line
] = counts
.get(line
, 0) + count
103 for k
,v
in counts
.items():
104 lines
.append("{0:6}: {1}".format(v
, k
))
108 countlog
= file("../OOM_count_log", "w")
109 countlog
.write("\n".join(lines
))
114 #####################################################################
116 #####################################################################
117 def clean_voutput(err
):
118 # Skip what we can't reproduce
119 err
= re
.sub(r
"^--\d+-- run: /usr/bin/dsymutil \"shell
/js
\"$
", "", err, flags=re.MULTILINE)
120 err = re.sub(r"^
==\d
+==", "", err, flags=re.MULTILINE)
121 err = re.sub(r"^\
*\
*\d
+\
*\
*", "", err, flags=re.MULTILINE)
122 err = re.sub(r"^\s
+by
0x
[0-9A
-Fa
-f
]+: ", "by
: ", err, flags=re.MULTILINE)
123 err = re.sub(r"^\s
+at
0x
[0-9A
-Fa
-f
]+: ", "at
: ", err, flags=re.MULTILINE)
124 err = re.sub(r"(^\s
+Address
0x
)[0-9A
-Fa
-f
]+( is not stack
'd)", r"\1\2", err, flags=re.MULTILINE)
125 err = re.sub(r"(^\s+Invalid write of size )\d+", r"\1x", err, flags=re.MULTILINE)
126 err = re.sub(r"(^\s+Invalid read of size )\d+", r"\1x", err, flags=re.MULTILINE)
127 err = re.sub(r"(^\s+Address 0x)[0-9A-Fa-f]+( is )\d+( bytes inside a block of size )[0-9,]+( free'd
)", r"\
1\
2\
3\
4", err, flags=re.MULTILINE)
129 # Skip the repeating bit due to the segfault
131 for l in err.split('\n'):
132 if l == " Process terminating with default action of signal
11 (SIGSEGV
)":
135 err = '\n'.join(lines)
139 def remove_failed_allocation_backtraces(err):
143 for l in err.split('\n'):
145 # Set start and end conditions for including text
146 if l == " The site of the failed allocation
is:":
148 elif l[:2] not in ['by: ', 'at:']:
155 err = '\n'.join(lines)
160 def clean_output(err):
161 err = re.sub(r"^js\
(\d
+,0x
[0-9a
-f
]+\
) malloc
: \
*\
*\
* error
for object 0x
[0-9a
-f
]+: pointer being freed was
not allocated
\n\
*\
*\
* set a breakppoint
in malloc_error_break to debug
\n$
", "pointer being freed was
not allocated
", err, flags=re.MULTILINE)
166 #####################################################################
168 #####################################################################
170 command_template = 'shell/js' \
172 + ' -e "const platform
=\'darwin
\'; const libdir
=\'../jit
-test
/lib
/\';"' \
173 + ' -f ../jit-test/lib/prolog.js' \
177 # Blacklists are things we don't want to see in our logs again (though we do
178 # want to count them when they happen). Whitelists we do want to see in our
179 # logs again, principally because the information we have isn't enough.
182 add_to_blacklist(r"('', '', 1)") # 1 means OOM if the shell hasn't launched yet.
183 add_to_blacklist(r"('', 'out of memory\n', 1)")
186 whitelist.add(r"('', 'out of memory\n', -11)") # -11 means OOM
187 whitelist.add(r"('', 'out of memory\nout of memory\n', -11)")
191 #####################################################################
193 #####################################################################
196 parser = OptionParser(usage=usage)
197 parser.add_option("-r
", "--regression
", action="store
", metavar="REGRESSION_COUNT
", help=help,
198 type="int", dest="regression
", default=None)
200 (OPTIONS, args) = parser.parse_args()
203 if OPTIONS.regression != None:
204 # TODO: This should be expanded as we get a better hang of the OOM problems.
205 # For now, we'll just check that the number of OOMs in one short file does not
207 files = ["../jit
-test
/tests
/arguments
/args
-createontrace
.js
"]
209 files = get_js_files()
211 # Use a command-line arg to reduce the set of files
213 files = [f for f in files if f.find(args[0]) != -1]
216 if OPTIONS.regression == None:
217 # Don't use a logfile, this is automated for tinderbox.
218 log = file("../OOM_log
", "w
")
224 # Run it once to establish boundaries
225 command = (command_template + ' -O').format(f)
226 out, err, exit = run(command)
227 max = re.match(".*OOM
max count
: (\d
+).*", out, flags=re.DOTALL).groups()[0]
230 # OOMs don't recover well for the first 20 allocations or so.
231 # TODO: revisit this.
232 for i in range(20, max):
234 if OPTIONS.regression == None:
235 print("Testing allocation {0}
/{1}
in {2}
".format(i,max,f))
237 sys.stdout.write('.') # something short for tinderbox, no space or \n
239 command = (command_template + ' -A {0}').format(f, i)
240 out, err, exit = run(command)
242 # Success (5 is SM's exit code for controlled errors)
243 if exit == 5 and err.find("out of memory
") != -1:
249 if OPTIONS.regression != None:
254 #########################################################################
255 # The regression tests ends above. The rest of this is for running the
257 #########################################################################
259 problem = str((out, err, exit))
260 if in_blacklist(problem) and problem not in whitelist:
261 add_to_blacklist(problem)
264 add_to_blacklist(problem)
267 # Get valgrind output for a good stack trace
268 vcommand = "valgrind
--dsymutil
=yes
-q
--log
-file=OOM_valgrind_log_file
" + command
270 vout = file("OOM_valgrind_log_file
").read()
271 vout = clean_voutput(vout)
272 sans_alloc_sites = remove_failed_allocation_backtraces(vout)
274 # Don't print duplicate information
275 if in_blacklist(sans_alloc_sites):
276 add_to_blacklist(sans_alloc_sites)
279 add_to_blacklist(sans_alloc_sites)
283 log.write ("=========================================================================")
285 log.write ("An allocation failure at
\n\tallocation {0}
/{1}
in {2}
\n\t"
286 "causes
problems (detected using bug
624094)"
291 log.write ("Command (from obj directory
, using patch
from bug
624094):\n " + command)
294 log.write ("stdout
, stderr
, exitcode
:\n " + problem)
298 double_free = err.find("pointer being freed was
not allocated
") != -1
299 oom_detected = err.find("out of memory
") != -1
300 multiple_oom_detected = err.find("out of memory
\nout of memory
") != -1
301 segfault_detected = exit == -11
303 log.write ("Diagnosis
: ")
305 if multiple_oom_detected:
306 log.write (" - Multiple OOMs reported
")
308 if segfault_detected:
309 log.write (" - segfault
")
312 log.write (" - No OOM checking
")
315 log.write (" - Double free
")
320 log.write ("Valgrind info
:\n" + vout)
325 if OPTIONS.regression == None:
330 # Do the actual regression check
331 if OPTIONS.regression != None:
332 expected_num_failures = OPTIONS.regression
334 if num_failures != expected_num_failures:
336 print("TEST
-UNEXPECTED
-FAIL |
", end='')
337 if num_failures > expected_num_failures:
338 print("More out
-of
-memory errors were
found ({0}
) than
expected ({1}
). "
339 "This probably means an allocation site has been added without a
"
340 "NULL
-check
. If this
is unavoidable
, you can account
for it by
"
341 "updating Makefile
.in.".format(num_failures, expected_num_failures),
344 print("Congratulations
, you have removed {0} out
-of
-memory
error(s
) "
345 "({1} remain
)! Please account
for it by updating Makefile
.in."
346 .format(expected_num_failures - num_failures, num_failures),
350 print('TEST-PASS | find_OOM_errors | Found the expected number of OOM '
351 'errors ({0})'.format(expected_num_failures))