Upstream version 8.37.186.0
[platform/framework/web/crosswalk.git] / src / v8 / tools / generate-runtime-tests.py
1 #!/usr/bin/env python
2 # Copyright 2014 the V8 project authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 import itertools
7 import js2c
8 import multiprocessing
9 import optparse
10 import os
11 import random
12 import re
13 import shutil
14 import signal
15 import string
16 import subprocess
17 import sys
18 import time
19
20 FILENAME = "src/runtime.cc"
21 HEADERFILENAME = "src/runtime.h"
22 FUNCTION = re.compile("^RUNTIME_FUNCTION\(Runtime_(\w+)")
23 ARGSLENGTH = re.compile(".*ASSERT\(.*args\.length\(\) == (\d+)\);")
24 FUNCTIONEND = "}\n"
25 MACRO = re.compile(r"^#define ([^ ]+)\(([^)]*)\) *([^\\]*)\\?\n$")
26 FIRST_WORD = re.compile("^\s*(.*?)[\s({\[]")
27
28 WORKSPACE = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), ".."))
29 BASEPATH = os.path.join(WORKSPACE, "test", "mjsunit", "runtime-gen")
30 THIS_SCRIPT = os.path.relpath(sys.argv[0])
31
32 # Expand these macros, they define further runtime functions.
33 EXPAND_MACROS = [
34   "BUFFER_VIEW_GETTER",
35   "DATA_VIEW_GETTER",
36   "DATA_VIEW_SETTER",
37   "RUNTIME_UNARY_MATH",
38 ]
39 # TODO(jkummerow): We could also whitelist the following macros, but the
40 # functions they define are so trivial that it's unclear how much benefit
41 # that would provide:
42 # ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION
43 # FIXED_TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION
44 # TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION
45
46 # Counts of functions in each detection state. These are used to assert
47 # that the parser doesn't bit-rot. Change the values as needed when you add,
48 # remove or change runtime functions, but make sure we don't lose our ability
49 # to parse them!
50 EXPECTED_FUNCTION_COUNT = 373
51 EXPECTED_FUZZABLE_COUNT = 335
52 EXPECTED_CCTEST_COUNT = 6
53 EXPECTED_UNKNOWN_COUNT = 10
54 EXPECTED_BUILTINS_COUNT = 918
55
56
57 # Don't call these at all.
58 BLACKLISTED = [
59   "Abort",  # Kills the process.
60   "AbortJS",  # Kills the process.
61   "CompileForOnStackReplacement",  # Riddled with ASSERTs.
62   "IS_VAR",  # Not implemented in the runtime.
63   "ListNatives",  # Not available in Release mode.
64   "SetAllocationTimeout",  # Too slow for fuzzing.
65   "SystemBreak",  # Kills (int3) the process.
66
67   # These are weird. They violate some invariants when called after
68   # bootstrapping.
69   "DisableAccessChecks",
70   "EnableAccessChecks",
71
72   # The current LiveEdit implementation relies on and messes with internals
73   # in ways that makes it fundamentally unfuzzable :-(
74   "DebugGetLoadedScripts",
75   "DebugSetScriptSource",
76   "LiveEditFindSharedFunctionInfosForScript",
77   "LiveEditFunctionSourceUpdated",
78   "LiveEditGatherCompileInfo",
79   "LiveEditPatchFunctionPositions",
80   "LiveEditReplaceFunctionCode",
81   "LiveEditReplaceRefToNestedFunction",
82   "LiveEditReplaceScript",
83   "LiveEditRestartFrame",
84   "SetScriptBreakPoint",
85
86   # TODO(jkummerow): Fix these and un-blacklist them!
87   "CreateDateTimeFormat",
88   "CreateNumberFormat",
89 ]
90
91
92 # These will always throw.
93 THROWS = [
94   "CheckExecutionState",  # Needs to hit a break point.
95   "CheckIsBootstrapping",  # Needs to be bootstrapping.
96   "DebugEvaluate",  # Needs to hit a break point.
97   "DebugEvaluateGlobal",  # Needs to hit a break point.
98   "DebugIndexedInterceptorElementValue",  # Needs an indexed interceptor.
99   "DebugNamedInterceptorPropertyValue",  # Needs a named interceptor.
100   "DebugSetScriptSource",  # Checks compilation state of script.
101   "GetAllScopesDetails",  # Needs to hit a break point.
102   "GetFrameCount",  # Needs to hit a break point.
103   "GetFrameDetails",  # Needs to hit a break point.
104   "GetRootNaN",  # Needs to be bootstrapping.
105   "GetScopeCount",  # Needs to hit a break point.
106   "GetScopeDetails",  # Needs to hit a break point.
107   "GetStepInPositions",  # Needs to hit a break point.
108   "GetTemplateField",  # Needs a {Function,Object}TemplateInfo.
109   "GetThreadCount",  # Needs to hit a break point.
110   "GetThreadDetails",  # Needs to hit a break point.
111   "IsAccessAllowedForObserver",  # Needs access-check-required object.
112   "UnblockConcurrentRecompilation"  # Needs --block-concurrent-recompilation.
113 ]
114
115
116 # Definitions used in CUSTOM_KNOWN_GOOD_INPUT below.
117 _BREAK_ITERATOR = (
118     "%GetImplFromInitializedIntlObject(new Intl.v8BreakIterator())")
119 _COLLATOR = "%GetImplFromInitializedIntlObject(new Intl.Collator('en-US'))"
120 _DATETIME_FORMAT = (
121     "%GetImplFromInitializedIntlObject(new Intl.DateTimeFormat('en-US'))")
122 _NUMBER_FORMAT = (
123     "%GetImplFromInitializedIntlObject(new Intl.NumberFormat('en-US'))")
124
125
126 # Custom definitions for function input that does not throw.
127 # Format: "FunctionName": ["arg0", "arg1", ..., argslength].
128 # None means "fall back to autodetected value".
129 CUSTOM_KNOWN_GOOD_INPUT = {
130   "Apply": ["function() {}", None, None, None, None, None],
131   "ArrayBufferSliceImpl": [None, None, 0, None],
132   "ArrayConcat": ["[1, 'a']", None],
133   "BreakIteratorAdoptText": [_BREAK_ITERATOR, None, None],
134   "BreakIteratorBreakType": [_BREAK_ITERATOR, None],
135   "BreakIteratorCurrent": [_BREAK_ITERATOR, None],
136   "BreakIteratorFirst": [_BREAK_ITERATOR, None],
137   "BreakIteratorNext": [_BREAK_ITERATOR, None],
138   "CompileString": [None, "false", None],
139   "CreateBreakIterator": ["'en-US'", "{type: 'string'}", None, None],
140   "CreateJSFunctionProxy": [None, "function() {}", None, None, None],
141   "CreatePrivateSymbol": ["\"foo\"", None],
142   "CreateSymbol": ["\"foo\"", None],
143   "DateParseString": [None, "new Array(8)", None],
144   "DefineOrRedefineAccessorProperty": [None, None, "function() {}",
145                                        "function() {}", 2, None],
146   "FunctionBindArguments": [None, None, "undefined", None, None],
147   "GetBreakLocations": [None, 0, None],
148   "GetDefaultReceiver": ["function() {}", None],
149   "GetImplFromInitializedIntlObject": ["new Intl.NumberFormat('en-US')", None],
150   "InternalCompare": [_COLLATOR, None, None, None],
151   "InternalDateFormat": [_DATETIME_FORMAT, None, None],
152   "InternalDateParse": [_DATETIME_FORMAT, None, None],
153   "InternalNumberFormat": [_NUMBER_FORMAT, None, None],
154   "InternalNumberParse": [_NUMBER_FORMAT, None, None],
155   "IsSloppyModeFunction": ["function() {}", None],
156   "LoadMutableDouble": ["{foo: 1.2}", None, None],
157   "NewObjectFromBound": ["(function() {}).bind({})", None],
158   "NumberToRadixString": [None, "2", None],
159   "ParseJson": ["\"{}\"", 1],
160   "RegExpExecMultiple": [None, None, "['a']", "['a']", None],
161   "SetAccessorProperty": [None, None, "undefined", "undefined", None, None,
162                           None],
163   "SetIteratorInitialize": [None, None, "2", None],
164   "SetDebugEventListener": ["undefined", None, None],
165   "SetFunctionBreakPoint": [None, 200, None, None],
166   "StringBuilderConcat": ["[1, 2, 3]", 3, None, None],
167   "StringBuilderJoin": ["['a', 'b']", 4, None, None],
168   "StringMatch": [None, None, "['a', 'b']", None],
169   "StringNormalize": [None, 2, None],
170   "StringReplaceGlobalRegExpWithString": [None, None, None, "['a']", None],
171   "TypedArrayInitialize": [None, 6, "new ArrayBuffer(8)", None, 4, None],
172   "TypedArrayInitializeFromArrayLike": [None, 6, None, None, None],
173   "TypedArraySetFastCases": [None, None, "0", None],
174 }
175
176
177 # Types of arguments that cannot be generated in a JavaScript testcase.
178 NON_JS_TYPES = [
179   "Code", "Context", "FixedArray", "FunctionTemplateInfo",
180   "JSFunctionResultCache", "JSMessageObject", "Map", "ScopeInfo",
181   "SharedFunctionInfo"]
182
183
184 class Generator(object):
185
186   def RandomVariable(self, varname, vartype, simple):
187     if simple:
188       return self._Variable(varname, self.GENERATORS[vartype][0])
189     return self.GENERATORS[vartype][1](self, varname,
190                                        self.DEFAULT_RECURSION_BUDGET)
191
192   @staticmethod
193   def IsTypeSupported(typename):
194     return typename in Generator.GENERATORS
195
196   USUAL_SUSPECT_PROPERTIES = ["size", "length", "byteLength", "__proto__",
197                               "prototype", "0", "1", "-1"]
198   DEFAULT_RECURSION_BUDGET = 2
199   PROXY_TRAPS = """{
200       getOwnPropertyDescriptor: function(name) {
201         return {value: function() {}, configurable: true, writable: true,
202                 enumerable: true};
203       },
204       getPropertyDescriptor: function(name) {
205         return {value: function() {}, configurable: true, writable: true,
206                 enumerable: true};
207       },
208       getOwnPropertyNames: function() { return []; },
209       getPropertyNames: function() { return []; },
210       defineProperty: function(name, descriptor) {},
211       delete: function(name) { return true; },
212       fix: function() {}
213     }"""
214
215   def _Variable(self, name, value, fallback=None):
216     args = { "name": name, "value": value, "fallback": fallback }
217     if fallback:
218       wrapper = "try { %%s } catch(e) { var %(name)s = %(fallback)s; }" % args
219     else:
220       wrapper = "%s"
221     return [wrapper % ("var %(name)s = %(value)s;" % args)]
222
223   def _Boolean(self, name, recursion_budget):
224     return self._Variable(name, random.choice(["true", "false"]))
225
226   def _Oddball(self, name, recursion_budget):
227     return self._Variable(name,
228                           random.choice(["true", "false", "undefined", "null"]))
229
230   def _StrictMode(self, name, recursion_budget):
231     return self._Variable(name, random.choice([0, 1]))
232
233   def _Int32(self, name, recursion_budget=0):
234     die = random.random()
235     if die < 0.5:
236       value = random.choice([-3, -1, 0, 1, 2, 10, 515, 0x3fffffff, 0x7fffffff,
237                              0x40000000, -0x40000000, -0x80000000])
238     elif die < 0.75:
239       value = random.randint(-1000, 1000)
240     else:
241       value = random.randint(-0x80000000, 0x7fffffff)
242     return self._Variable(name, value)
243
244   def _Uint32(self, name, recursion_budget=0):
245     die = random.random()
246     if die < 0.5:
247       value = random.choice([0, 1, 2, 3, 4, 8, 0x3fffffff, 0x40000000,
248                              0x7fffffff, 0xffffffff])
249     elif die < 0.75:
250       value = random.randint(0, 1000)
251     else:
252       value = random.randint(0, 0xffffffff)
253     return self._Variable(name, value)
254
255   def _Smi(self, name, recursion_budget):
256     die = random.random()
257     if die < 0.5:
258       value = random.choice([-5, -1, 0, 1, 2, 3, 0x3fffffff, -0x40000000])
259     elif die < 0.75:
260       value = random.randint(-1000, 1000)
261     else:
262       value = random.randint(-0x40000000, 0x3fffffff)
263     return self._Variable(name, value)
264
265   def _Number(self, name, recursion_budget):
266     die = random.random()
267     if die < 0.5:
268       return self._Smi(name, recursion_budget)
269     elif die < 0.6:
270       value = random.choice(["Infinity", "-Infinity", "NaN", "-0",
271                              "1.7976931348623157e+308",  # Max value.
272                              "2.2250738585072014e-308",  # Min value.
273                              "4.9406564584124654e-324"])  # Min subnormal.
274     else:
275       value = random.lognormvariate(0, 15)
276     return self._Variable(name, value)
277
278   def _RawRandomString(self, minlength=0, maxlength=100,
279                        alphabet=string.ascii_letters):
280     length = random.randint(minlength, maxlength)
281     result = ""
282     for i in xrange(length):
283       result += random.choice(alphabet)
284     return result
285
286   def _SeqString(self, name, recursion_budget):
287     s1 = self._RawRandomString(1, 5)
288     s2 = self._RawRandomString(1, 5)
289     # 'foo' + 'bar'
290     return self._Variable(name, "\"%s\" + \"%s\"" % (s1, s2))
291
292   def _SeqTwoByteString(self, name):
293     s1 = self._RawRandomString(1, 5)
294     s2 = self._RawRandomString(1, 5)
295     # 'foo' + unicode + 'bar'
296     return self._Variable(name, "\"%s\" + \"\\2082\" + \"%s\"" % (s1, s2))
297
298   def _SlicedString(self, name):
299     s = self._RawRandomString(20, 30)
300     # 'ffoo12345678901234567890'.substr(1)
301     return self._Variable(name, "\"%s\".substr(1)" % s)
302
303   def _ConsString(self, name):
304     s1 = self._RawRandomString(8, 15)
305     s2 = self._RawRandomString(8, 15)
306     # 'foo12345' + (function() { return 'bar12345';})()
307     return self._Variable(name,
308         "\"%s\" + (function() { return \"%s\";})()" % (s1, s2))
309
310   def _InternalizedString(self, name):
311     return self._Variable(name, "\"%s\"" % self._RawRandomString(0, 20))
312
313   def _String(self, name, recursion_budget):
314     die = random.random()
315     if die < 0.5:
316       string = random.choice(self.USUAL_SUSPECT_PROPERTIES)
317       return self._Variable(name, "\"%s\"" % string)
318     elif die < 0.6:
319       number_name = name + "_number"
320       result = self._Number(number_name, recursion_budget)
321       return result + self._Variable(name, "\"\" + %s" % number_name)
322     elif die < 0.7:
323       return self._SeqString(name, recursion_budget)
324     elif die < 0.8:
325       return self._ConsString(name)
326     elif die < 0.9:
327       return self._InternalizedString(name)
328     else:
329       return self._SlicedString(name)
330
331   def _Symbol(self, name, recursion_budget):
332     raw_string_name = name + "_1"
333     result = self._String(raw_string_name, recursion_budget)
334     return result + self._Variable(name, "Symbol(%s)" % raw_string_name)
335
336   def _Name(self, name, recursion_budget):
337     if random.random() < 0.2:
338       return self._Symbol(name, recursion_budget)
339     return self._String(name, recursion_budget)
340
341   def _JSValue(self, name, recursion_budget):
342     die = random.random()
343     raw_name = name + "_1"
344     if die < 0.33:
345       result = self._String(raw_name, recursion_budget)
346       return result + self._Variable(name, "new String(%s)" % raw_name)
347     elif die < 0.66:
348       result = self._Boolean(raw_name, recursion_budget)
349       return result + self._Variable(name, "new Boolean(%s)" % raw_name)
350     else:
351       result = self._Number(raw_name, recursion_budget)
352       return result + self._Variable(name, "new Number(%s)" % raw_name)
353
354   def _RawRandomPropertyName(self):
355     if random.random() < 0.5:
356       return random.choice(self.USUAL_SUSPECT_PROPERTIES)
357     return self._RawRandomString(0, 10)
358
359   def _AddProperties(self, name, result, recursion_budget):
360     propcount = random.randint(0, 3)
361     propname = None
362     for i in range(propcount):
363       die = random.random()
364       if die < 0.5:
365         propname = "%s_prop%d" % (name, i)
366         result += self._Name(propname, recursion_budget - 1)
367       else:
368         propname = "\"%s\"" % self._RawRandomPropertyName()
369       propvalue_name = "%s_val%d" % (name, i)
370       result += self._Object(propvalue_name, recursion_budget - 1)
371       result.append("try { %s[%s] = %s; } catch (e) {}" %
372                     (name, propname, propvalue_name))
373     if random.random() < 0.2 and propname:
374       # Force the object to slow mode.
375       result.append("delete %s[%s];" % (name, propname))
376
377   def _RandomElementIndex(self, element_name, result):
378     if random.random() < 0.5:
379       return random.randint(-1000, 1000)
380     result += self._Smi(element_name, 0)
381     return element_name
382
383   def _AddElements(self, name, result, recursion_budget):
384     elementcount = random.randint(0, 3)
385     for i in range(elementcount):
386       element_name = "%s_idx%d" % (name, i)
387       index = self._RandomElementIndex(element_name, result)
388       value_name = "%s_elt%d" % (name, i)
389       result += self._Object(value_name, recursion_budget - 1)
390       result.append("try { %s[%s] = %s; } catch(e) {}" %
391                     (name, index, value_name))
392
393   def _AddAccessors(self, name, result, recursion_budget):
394     accessorcount = random.randint(0, 3)
395     for i in range(accessorcount):
396       propname = self._RawRandomPropertyName()
397       what = random.choice(["get", "set"])
398       function_name = "%s_access%d" % (name, i)
399       result += self._PlainFunction(function_name, recursion_budget - 1)
400       result.append("try { Object.defineProperty(%s, \"%s\", {%s: %s}); } "
401                     "catch (e) {}" % (name, propname, what, function_name))
402
403   def _PlainArray(self, name, recursion_budget):
404     die = random.random()
405     if die < 0.5:
406       literal = random.choice(["[]", "[1, 2]", "[1.5, 2.5]",
407                                "['a', 'b', 1, true]"])
408       return self._Variable(name, literal)
409     else:
410       new = random.choice(["", "new "])
411       length = random.randint(0, 101000)
412       return self._Variable(name, "%sArray(%d)" % (new, length))
413
414   def _PlainObject(self, name, recursion_budget):
415     die = random.random()
416     if die < 0.67:
417       literal_propcount = random.randint(0, 3)
418       properties = []
419       result = []
420       for i in range(literal_propcount):
421         propname = self._RawRandomPropertyName()
422         propvalue_name = "%s_lit%d" % (name, i)
423         result += self._Object(propvalue_name, recursion_budget - 1)
424         properties.append("\"%s\": %s" % (propname, propvalue_name))
425       return result + self._Variable(name, "{%s}" % ", ".join(properties))
426     else:
427       return self._Variable(name, "new Object()")
428
429   def _JSArray(self, name, recursion_budget):
430     result = self._PlainArray(name, recursion_budget)
431     self._AddAccessors(name, result, recursion_budget)
432     self._AddProperties(name, result, recursion_budget)
433     self._AddElements(name, result, recursion_budget)
434     return result
435
436   def _RawRandomBufferLength(self):
437     if random.random() < 0.2:
438       return random.choice([0, 1, 8, 0x40000000, 0x80000000])
439     return random.randint(0, 1000)
440
441   def _JSArrayBuffer(self, name, recursion_budget):
442     length = self._RawRandomBufferLength()
443     return self._Variable(name, "new ArrayBuffer(%d)" % length)
444
445   def _JSDataView(self, name, recursion_budget):
446     buffer_name = name + "_buffer"
447     result = self._JSArrayBuffer(buffer_name, recursion_budget)
448     args = [buffer_name]
449     die = random.random()
450     if die < 0.67:
451       offset = self._RawRandomBufferLength()
452       args.append("%d" % offset)
453       if die < 0.33:
454         length = self._RawRandomBufferLength()
455         args.append("%d" % length)
456     result += self._Variable(name, "new DataView(%s)" % ", ".join(args),
457                              fallback="new DataView(new ArrayBuffer(8))")
458     return result
459
460   def _Float32x4(self, name, recursion_budget):
461     x = random.random()
462     y = random.random()
463     z = random.random()
464     w = random.random()
465     return self._Variable(name, "SIMD.float32x4(%s, %s, %s, %s)" %(x, y, z, w))
466
467   def _Float64x2(self, name, recursion_budget):
468     x = random.random()
469     y = random.random()
470     return self._Variable(name, "SIMD.float64x2(%s, %s)" %(x, y))
471
472   def _Int32x4(self, name, recursion_budget):
473     x = random.randint(-0x40000000, 0x3fffffff)
474     y = random.randint(-0x40000000, 0x3fffffff)
475     z = random.randint(-0x40000000, 0x3fffffff)
476     w = random.randint(-0x40000000, 0x3fffffff)
477     return self._Variable(name, "SIMD.int32x4(%s, %s, %s, %s)" %(x, y, z, w))
478
479   def _JSDate(self, name, recursion_budget):
480     die = random.random()
481     if die < 0.25:
482       return self._Variable(name, "new Date()")
483     elif die < 0.5:
484       ms_name = name + "_ms"
485       result = self._Number(ms_name, recursion_budget)
486       return result + self._Variable(name, "new Date(%s)" % ms_name)
487     elif die < 0.75:
488       str_name = name + "_str"
489       month = random.choice(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
490                              "Aug", "Sep", "Oct", "Nov", "Dec"])
491       day = random.randint(1, 28)
492       year = random.randint(1900, 2100)
493       hour = random.randint(0, 23)
494       minute = random.randint(0, 59)
495       second = random.randint(0, 59)
496       str_value = ("\"%s %s, %s %s:%s:%s\"" %
497                    (month, day, year, hour, minute, second))
498       result = self._Variable(str_name, str_value)
499       return result + self._Variable(name, "new Date(%s)" % str_name)
500     else:
501       components = tuple(map(lambda x: "%s_%s" % (name, x),
502                              ["y", "m", "d", "h", "min", "s", "ms"]))
503       return ([j for i in map(self._Int32, components) for j in i] +
504               self._Variable(name, "new Date(%s)" % ", ".join(components)))
505
506   def _PlainFunction(self, name, recursion_budget):
507     result_name = "result"
508     body = ["function() {"]
509     body += self._Object(result_name, recursion_budget - 1)
510     body.append("return result;\n}")
511     return self._Variable(name, "%s" % "\n".join(body))
512
513   def _JSFunction(self, name, recursion_budget):
514     result = self._PlainFunction(name, recursion_budget)
515     self._AddAccessors(name, result, recursion_budget)
516     self._AddProperties(name, result, recursion_budget)
517     self._AddElements(name, result, recursion_budget)
518     return result
519
520   def _JSFunctionProxy(self, name, recursion_budget):
521     # TODO(jkummerow): Revisit this as the Proxy implementation evolves.
522     return self._Variable(name, "Proxy.createFunction(%s, function() {})" %
523                                 self.PROXY_TRAPS)
524
525   def _JSGeneratorObject(self, name, recursion_budget):
526     # TODO(jkummerow): Be more creative here?
527     return self._Variable(name, "(function*() { yield 1; })()")
528
529   def _JSMap(self, name, recursion_budget, weak=""):
530     result = self._Variable(name, "new %sMap()" % weak)
531     num_entries = random.randint(0, 3)
532     for i in range(num_entries):
533       key_name = "%s_k%d" % (name, i)
534       value_name = "%s_v%d" % (name, i)
535       if weak:
536         result += self._JSObject(key_name, recursion_budget - 1)
537       else:
538         result += self._Object(key_name, recursion_budget - 1)
539       result += self._Object(value_name, recursion_budget - 1)
540       result.append("%s.set(%s, %s)" % (name, key_name, value_name))
541     return result
542
543   def _JSMapIterator(self, name, recursion_budget):
544     map_name = name + "_map"
545     result = self._JSMap(map_name, recursion_budget)
546     iterator_type = random.choice(['keys', 'values', 'entries'])
547     return (result + self._Variable(name, "%s.%s()" %
548                                           (map_name, iterator_type)))
549
550   def _JSProxy(self, name, recursion_budget):
551     # TODO(jkummerow): Revisit this as the Proxy implementation evolves.
552     return self._Variable(name, "Proxy.create(%s)" % self.PROXY_TRAPS)
553
554   def _JSRegExp(self, name, recursion_budget):
555     flags = random.choice(["", "g", "i", "m", "gi"])
556     string = "a(b|c)*a"  # TODO(jkummerow): Be more creative here?
557     ctor = random.choice(["/%s/%s", "new RegExp(\"%s\", \"%s\")"])
558     return self._Variable(name, ctor % (string, flags))
559
560   def _JSSet(self, name, recursion_budget, weak=""):
561     result = self._Variable(name, "new %sSet()" % weak)
562     num_entries = random.randint(0, 3)
563     for i in range(num_entries):
564       element_name = "%s_e%d" % (name, i)
565       if weak:
566         result += self._JSObject(element_name, recursion_budget - 1)
567       else:
568         result += self._Object(element_name, recursion_budget - 1)
569       result.append("%s.add(%s)" % (name, element_name))
570     return result
571
572   def _JSSetIterator(self, name, recursion_budget):
573     set_name = name + "_set"
574     result = self._JSSet(set_name, recursion_budget)
575     iterator_type = random.choice(['values', 'entries'])
576     return (result + self._Variable(name, "%s.%s()" %
577                                           (set_name, iterator_type)))
578
579   def _JSTypedArray(self, name, recursion_budget):
580     arraytype = random.choice(["Int8", "Int16", "Int32", "Uint8", "Uint16",
581                                "Uint32", "Float32", "Float64", "Uint8Clamped"])
582     ctor_type = random.randint(0, 3)
583     if ctor_type == 0:
584       length = random.randint(0, 1000)
585       return self._Variable(name, "new %sArray(%d)" % (arraytype, length),
586                             fallback="new %sArray(8)" % arraytype)
587     elif ctor_type == 1:
588       input_name = name + "_typedarray"
589       result = self._JSTypedArray(input_name, recursion_budget - 1)
590       return (result +
591               self._Variable(name, "new %sArray(%s)" % (arraytype, input_name),
592                              fallback="new %sArray(8)" % arraytype))
593     elif ctor_type == 2:
594       arraylike_name = name + "_arraylike"
595       result = self._JSObject(arraylike_name, recursion_budget - 1)
596       length = random.randint(0, 1000)
597       result.append("try { %s.length = %d; } catch(e) {}" %
598                     (arraylike_name, length))
599       return (result +
600               self._Variable(name,
601                              "new %sArray(%s)" % (arraytype, arraylike_name),
602                              fallback="new %sArray(8)" % arraytype))
603     else:
604       die = random.random()
605       buffer_name = name + "_buffer"
606       args = [buffer_name]
607       result = self._JSArrayBuffer(buffer_name, recursion_budget)
608       if die < 0.67:
609         offset_name = name + "_offset"
610         args.append(offset_name)
611         result += self._Int32(offset_name)
612       if die < 0.33:
613         length_name = name + "_length"
614         args.append(length_name)
615         result += self._Int32(length_name)
616       return (result +
617               self._Variable(name,
618                              "new %sArray(%s)" % (arraytype, ", ".join(args)),
619                              fallback="new %sArray(8)" % arraytype))
620
621   def _JSArrayBufferView(self, name, recursion_budget):
622     if random.random() < 0.4:
623       return self._JSDataView(name, recursion_budget)
624     else:
625       return self._JSTypedArray(name, recursion_budget)
626
627   def _JSWeakCollection(self, name, recursion_budget):
628     ctor = random.choice([self._JSMap, self._JSSet])
629     return ctor(name, recursion_budget, weak="Weak")
630
631   def _PropertyDetails(self, name, recursion_budget):
632     # TODO(jkummerow): Be more clever here?
633     return self._Int32(name)
634
635   def _JSObject(self, name, recursion_budget):
636     die = random.random()
637     if die < 0.4:
638       function = random.choice([self._PlainObject, self._PlainArray,
639                                 self._PlainFunction])
640     elif die < 0.5:
641       return self._Variable(name, "this")  # Global object.
642     else:
643       function = random.choice([self._JSArrayBuffer, self._JSDataView,
644                                 self._JSDate, self._JSFunctionProxy,
645                                 self._JSGeneratorObject, self._JSMap,
646                                 self._JSMapIterator, self._JSRegExp,
647                                 self._JSSet, self._JSSetIterator,
648                                 self._JSTypedArray, self._JSValue,
649                                 self._JSWeakCollection])
650     result = function(name, recursion_budget)
651     self._AddAccessors(name, result, recursion_budget)
652     self._AddProperties(name, result, recursion_budget)
653     self._AddElements(name, result, recursion_budget)
654     return result
655
656   def _JSReceiver(self, name, recursion_budget):
657     if random.random() < 0.9: return self._JSObject(name, recursion_budget)
658     return self._JSProxy(name, recursion_budget)
659
660   def _HeapObject(self, name, recursion_budget):
661     die = random.random()
662     if die < 0.9: return self._JSReceiver(name, recursion_budget)
663     elif die < 0.95: return  self._Oddball(name, recursion_budget)
664     else: return self._Name(name, recursion_budget)
665
666   def _Object(self, name, recursion_budget):
667     if recursion_budget <= 0:
668       function = random.choice([self._Oddball, self._Number, self._Name,
669                                 self._JSValue, self._JSRegExp])
670       return function(name, recursion_budget)
671     if random.random() < 0.2:
672       return self._Smi(name, recursion_budget)
673     return self._HeapObject(name, recursion_budget)
674
675   GENERATORS = {
676     "Boolean": ["true", _Boolean],
677     "HeapObject": ["new Object()", _HeapObject],
678     "Int32": ["32", _Int32],
679     "JSArray": ["new Array()", _JSArray],
680     "JSArrayBuffer": ["new ArrayBuffer(8)", _JSArrayBuffer],
681     "JSArrayBufferView": ["new Int32Array(2)", _JSArrayBufferView],
682     "JSDataView": ["new DataView(new ArrayBuffer(24))", _JSDataView],
683     "JSDate": ["new Date()", _JSDate],
684     "JSFunction": ["function() {}", _JSFunction],
685     "JSFunctionProxy": ["Proxy.createFunction({}, function() {})",
686                         _JSFunctionProxy],
687     "JSGeneratorObject": ["(function*(){ yield 1; })()", _JSGeneratorObject],
688     "JSMap": ["new Map()", _JSMap],
689     "JSMapIterator": ["new Map().entries()", _JSMapIterator],
690     "JSObject": ["new Object()", _JSObject],
691     "JSProxy": ["Proxy.create({})", _JSProxy],
692     "JSReceiver": ["new Object()", _JSReceiver],
693     "JSRegExp": ["/ab/g", _JSRegExp],
694     "JSSet": ["new Set()", _JSSet],
695     "JSSetIterator": ["new Set().values()", _JSSetIterator],
696     "JSTypedArray": ["new Int32Array(2)", _JSTypedArray],
697     "JSValue": ["new String('foo')", _JSValue],
698     "JSWeakCollection": ["new WeakMap()", _JSWeakCollection],
699     "Name": ["\"name\"", _Name],
700     "Number": ["1.5", _Number],
701     "Object": ["new Object()", _Object],
702     "PropertyDetails": ["513", _PropertyDetails],
703     "SeqOneByteString": ["\"seq 1-byte\"", _SeqString],
704     "SeqString": ["\"seqstring\"", _SeqString],
705     "SeqTwoByteString": ["\"seq \\u2082-byte\"", _SeqTwoByteString],
706     "Smi": ["1", _Smi],
707     "StrictMode": ["1", _StrictMode],
708     "String": ["\"foo\"", _String],
709     "Symbol": ["Symbol(\"symbol\")", _Symbol],
710     "Uint32": ["32", _Uint32],
711     "Float32x4": ["SIMD.float32x4(0.0, 0.0, 0.0, 0.0)", _Float32x4],
712     "Float64x2": ["SIMD.float64x2(0.0, 0.0)", _Float64x2],
713     "Int32x4": ["SIMD.int32x4(0, 0, 0, 0)", _Int32x4],
714   }
715
716
717 class ArgParser(object):
718   def __init__(self, regex, ctor):
719     self.regex = regex
720     self.ArgCtor = ctor
721
722
723 class Arg(object):
724   def __init__(self, typename, varname, index):
725     self.type = typename
726     self.name = "_%s" % varname
727     self.index = index
728
729
730 class Function(object):
731   def __init__(self, match):
732     self.name = match.group(1)
733     self.argslength = -1
734     self.args = {}
735     self.inline = ""
736
737   handle_arg_parser = ArgParser(
738       re.compile("^\s*CONVERT_ARG_HANDLE_CHECKED\((\w+), (\w+), (\d+)\)"),
739       lambda match: Arg(match.group(1), match.group(2), int(match.group(3))))
740
741   plain_arg_parser = ArgParser(
742       re.compile("^\s*CONVERT_ARG_CHECKED\((\w+), (\w+), (\d+)\)"),
743       lambda match: Arg(match.group(1), match.group(2), int(match.group(3))))
744
745   number_handle_arg_parser = ArgParser(
746       re.compile("^\s*CONVERT_NUMBER_ARG_HANDLE_CHECKED\((\w+), (\d+)\)"),
747       lambda match: Arg("Number", match.group(1), int(match.group(2))))
748
749   smi_arg_parser = ArgParser(
750       re.compile("^\s*CONVERT_SMI_ARG_CHECKED\((\w+), (\d+)\)"),
751       lambda match: Arg("Smi", match.group(1), int(match.group(2))))
752
753   double_arg_parser = ArgParser(
754       re.compile("^\s*CONVERT_DOUBLE_ARG_CHECKED\((\w+), (\d+)\)"),
755       lambda match: Arg("Number", match.group(1), int(match.group(2))))
756
757   number_arg_parser = ArgParser(
758       re.compile(
759           "^\s*CONVERT_NUMBER_CHECKED\(\w+, (\w+), (\w+), args\[(\d+)\]\)"),
760       lambda match: Arg(match.group(2), match.group(1), int(match.group(3))))
761
762   strict_mode_arg_parser = ArgParser(
763       re.compile("^\s*CONVERT_STRICT_MODE_ARG_CHECKED\((\w+), (\d+)\)"),
764       lambda match: Arg("StrictMode", match.group(1), int(match.group(2))))
765
766   boolean_arg_parser = ArgParser(
767       re.compile("^\s*CONVERT_BOOLEAN_ARG_CHECKED\((\w+), (\d+)\)"),
768       lambda match: Arg("Boolean", match.group(1), int(match.group(2))))
769
770   property_details_parser = ArgParser(
771       re.compile("^\s*CONVERT_PROPERTY_DETAILS_CHECKED\((\w+), (\d+)\)"),
772       lambda match: Arg("PropertyDetails", match.group(1), int(match.group(2))))
773
774   arg_parsers = [handle_arg_parser, plain_arg_parser, number_handle_arg_parser,
775                  smi_arg_parser,
776                  double_arg_parser, number_arg_parser, strict_mode_arg_parser,
777                  boolean_arg_parser, property_details_parser]
778
779   def SetArgsLength(self, match):
780     self.argslength = int(match.group(1))
781
782   def TryParseArg(self, line):
783     for parser in Function.arg_parsers:
784       match = parser.regex.match(line)
785       if match:
786         arg = parser.ArgCtor(match)
787         self.args[arg.index] = arg
788         return True
789     return False
790
791   def Filename(self):
792     return "%s.js" % self.name.lower()
793
794   def __str__(self):
795     s = [self.name, "("]
796     argcount = self.argslength
797     if argcount < 0:
798       print("WARNING: unknown argslength for function %s" % self.name)
799       if self.args:
800         argcount = max([self.args[i].index + 1 for i in self.args])
801       else:
802         argcount = 0
803     for i in range(argcount):
804       if i > 0: s.append(", ")
805       s.append(self.args[i].type if i in self.args else "<unknown>")
806     s.append(")")
807     return "".join(s)
808
809
810 class Macro(object):
811   def __init__(self, match):
812     self.name = match.group(1)
813     self.args = [s.strip() for s in match.group(2).split(",")]
814     self.lines = []
815     self.indentation = 0
816     self.AddLine(match.group(3))
817
818   def AddLine(self, line):
819     if not line: return
820     if not self.lines:
821       # This is the first line, detect indentation.
822       self.indentation = len(line) - len(line.lstrip())
823     line = line.rstrip("\\\n ")
824     if not line: return
825     assert len(line[:self.indentation].strip()) == 0, \
826         ("expected whitespace: '%s', full line: '%s'" %
827          (line[:self.indentation], line))
828     line = line[self.indentation:]
829     if not line: return
830     self.lines.append(line + "\n")
831
832   def Finalize(self):
833     for arg in self.args:
834       pattern = re.compile(r"(##|\b)%s(##|\b)" % arg)
835       for i in range(len(self.lines)):
836         self.lines[i] = re.sub(pattern, "%%(%s)s" % arg, self.lines[i])
837
838   def FillIn(self, arg_values):
839     filler = {}
840     assert len(arg_values) == len(self.args)
841     for i in range(len(self.args)):
842       filler[self.args[i]] = arg_values[i]
843     result = []
844     for line in self.lines:
845       result.append(line % filler)
846     return result
847
848
849 # Parses HEADERFILENAME to find out which runtime functions are "inline".
850 def FindInlineRuntimeFunctions():
851   inline_functions = []
852   with open(HEADERFILENAME, "r") as f:
853     inline_list = "#define INLINE_FUNCTION_LIST(F) \\\n"
854     inline_function = re.compile(r"^\s*F\((\w+), \d+, \d+\)\s*\\?")
855     mode = "SEARCHING"
856     for line in f:
857       if mode == "ACTIVE":
858         match = inline_function.match(line)
859         if match:
860           inline_functions.append(match.group(1))
861         if not line.endswith("\\\n"):
862           mode = "SEARCHING"
863       elif mode == "SEARCHING":
864         if line == inline_list:
865           mode = "ACTIVE"
866   return inline_functions
867
868
869 def ReadFileAndExpandMacros(filename):
870   found_macros = {}
871   expanded_lines = []
872   with open(filename, "r") as f:
873     found_macro = None
874     for line in f:
875       if found_macro is not None:
876         found_macro.AddLine(line)
877         if not line.endswith("\\\n"):
878           found_macro.Finalize()
879           found_macro = None
880         continue
881
882       match = MACRO.match(line)
883       if match:
884         found_macro = Macro(match)
885         if found_macro.name in EXPAND_MACROS:
886           found_macros[found_macro.name] = found_macro
887         else:
888           found_macro = None
889         continue
890
891       match = FIRST_WORD.match(line)
892       if match:
893         first_word = match.group(1)
894         if first_word in found_macros:
895           MACRO_CALL = re.compile("%s\(([^)]*)\)" % first_word)
896           match = MACRO_CALL.match(line)
897           assert match
898           args = [s.strip() for s in match.group(1).split(",")]
899           expanded_lines += found_macros[first_word].FillIn(args)
900           continue
901
902       expanded_lines.append(line)
903   return expanded_lines
904
905
906 # Detects runtime functions by parsing FILENAME.
907 def FindRuntimeFunctions():
908   inline_functions = FindInlineRuntimeFunctions()
909   functions = []
910   expanded_lines = ReadFileAndExpandMacros(FILENAME)
911   function = None
912   partial_line = ""
913   for line in expanded_lines:
914     # Multi-line definition support, ignoring macros.
915     if line.startswith("RUNTIME_FUNCTION") and not line.endswith("{\n"):
916       if line.endswith("\\\n"): continue
917       partial_line = line.rstrip()
918       continue
919     if partial_line:
920       partial_line += " " + line.strip()
921       if partial_line.endswith("{"):
922         line = partial_line
923         partial_line = ""
924       else:
925         continue
926
927     match = FUNCTION.match(line)
928     if match:
929       function = Function(match)
930       if function.name in inline_functions:
931         function.inline = "_"
932       continue
933     if function is None: continue
934
935     match = ARGSLENGTH.match(line)
936     if match:
937       function.SetArgsLength(match)
938       continue
939
940     if function.TryParseArg(line):
941       continue
942
943     if line == FUNCTIONEND:
944       if function is not None:
945         functions.append(function)
946         function = None
947   return functions
948
949
950 # Hack: This must have the same fields as class Function above, because the
951 # two are used polymorphically in RunFuzzer(). We could use inheritance...
952 class Builtin(object):
953   def __init__(self, match):
954     self.name = match.group(1)
955     args = match.group(2)
956     self.argslength = 0 if args == "" else args.count(",") + 1
957     self.inline = ""
958     self.args = {}
959     if self.argslength > 0:
960       args = args.split(",")
961       for i in range(len(args)):
962         # a = args[i].strip()  # TODO: filter out /* comments */ first.
963         a = ""
964         self.args[i] = Arg("Object", a, i)
965
966   def __str__(self):
967     return "%s(%d)" % (self.name, self.argslength)
968
969
970 def FindJSBuiltins():
971   PATH = "src"
972   fileslist = []
973   for (root, dirs, files) in os.walk(PATH):
974     for f in files:
975       if f.endswith(".js"):
976         fileslist.append(os.path.join(root, f))
977   builtins = []
978   regexp = re.compile("^function (\w+)\s*\((.*?)\) {")
979   matches = 0
980   for filename in fileslist:
981     with open(filename, "r") as f:
982       file_contents = f.read()
983     file_contents = js2c.ExpandInlineMacros(file_contents)
984     lines = file_contents.split("\n")
985     partial_line = ""
986     for line in lines:
987       if line.startswith("function") and not '{' in line:
988         partial_line += line.rstrip()
989         continue
990       if partial_line:
991         partial_line += " " + line.strip()
992         if '{' in line:
993           line = partial_line
994           partial_line = ""
995         else:
996           continue
997       match = regexp.match(line)
998       if match:
999         builtins.append(Builtin(match))
1000   return builtins
1001
1002
1003 # Classifies runtime functions.
1004 def ClassifyFunctions(functions):
1005   # Can be fuzzed with a JavaScript testcase.
1006   js_fuzzable_functions = []
1007   # We have enough information to fuzz these, but they need inputs that
1008   # cannot be created or passed around in JavaScript.
1009   cctest_fuzzable_functions = []
1010   # This script does not have enough information about these.
1011   unknown_functions = []
1012
1013   types = {}
1014   for f in functions:
1015     if f.name in BLACKLISTED:
1016       continue
1017     decision = js_fuzzable_functions
1018     custom = CUSTOM_KNOWN_GOOD_INPUT.get(f.name, None)
1019     if f.argslength < 0:
1020       # Unknown length -> give up unless there's a custom definition.
1021       if custom and custom[-1] is not None:
1022         f.argslength = custom[-1]
1023         assert len(custom) == f.argslength + 1, \
1024             ("%s: last custom definition must be argslength" % f.name)
1025       else:
1026         decision = unknown_functions
1027     else:
1028       if custom:
1029         # Any custom definitions must match the known argslength.
1030         assert len(custom) == f.argslength + 1, \
1031             ("%s should have %d custom definitions but has %d" %
1032             (f.name, f.argslength + 1, len(custom)))
1033       for i in range(f.argslength):
1034         if custom and custom[i] is not None:
1035           # All good, there's a custom definition.
1036           pass
1037         elif not i in f.args:
1038           # No custom definition and no parse result -> give up.
1039           decision = unknown_functions
1040         else:
1041           t = f.args[i].type
1042           if t in NON_JS_TYPES:
1043             decision = cctest_fuzzable_functions
1044           else:
1045             assert Generator.IsTypeSupported(t), \
1046                 ("type generator not found for %s, function: %s" % (t, f))
1047     decision.append(f)
1048   return (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions)
1049
1050
1051 def _GetKnownGoodArgs(function, generator):
1052   custom_input = CUSTOM_KNOWN_GOOD_INPUT.get(function.name, None)
1053   definitions = []
1054   argslist = []
1055   for i in range(function.argslength):
1056     if custom_input and custom_input[i] is not None:
1057       name = "arg%d" % i
1058       definitions.append("var %s = %s;" % (name, custom_input[i]))
1059     else:
1060       arg = function.args[i]
1061       name = arg.name
1062       definitions += generator.RandomVariable(name, arg.type, simple=True)
1063     argslist.append(name)
1064   return (definitions, argslist)
1065
1066
1067 def _GenerateTestcase(function, definitions, argslist, throws):
1068   s = ["// Copyright 2014 the V8 project authors. All rights reserved.",
1069        "// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY",
1070        "// Flags: --allow-natives-syntax --harmony"] + definitions
1071   if function.name.find("Float32x4") != -1 or function.name.find("Float64x2") != -1 or function.name.find("Int32x4") != -1:
1072     s = ["// Copyright 2014 the V8 project authors. All rights reserved.",
1073         "// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY",
1074         "// Flags: --allow-natives-syntax --harmony --simd-object"] + definitions
1075   call = "%%%s%s(%s);" % (function.inline, function.name, ", ".join(argslist))
1076   if throws:
1077     s.append("try {")
1078     s.append(call);
1079     s.append("} catch(e) {}")
1080   else:
1081     s.append(call)
1082   testcase = "\n".join(s)
1083   return testcase
1084
1085
1086 def GenerateJSTestcaseForFunction(function):
1087   gen = Generator()
1088   (definitions, argslist) = _GetKnownGoodArgs(function, gen)
1089   testcase = _GenerateTestcase(function, definitions, argslist,
1090                                function.name in THROWS)
1091   path = os.path.join(BASEPATH, function.Filename())
1092   with open(path, "w") as f:
1093     f.write("%s\n" % testcase)
1094
1095
1096 def GenerateTestcases(functions):
1097   shutil.rmtree(BASEPATH)  # Re-generate everything.
1098   os.makedirs(BASEPATH)
1099   for f in functions:
1100     GenerateJSTestcaseForFunction(f)
1101
1102
1103 def _SaveFileName(save_path, process_id, save_file_index):
1104   return "%s/fuzz_%d_%d.js" % (save_path, process_id, save_file_index)
1105
1106
1107 def _GetFuzzableRuntimeFunctions():
1108   functions = FindRuntimeFunctions()
1109   (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \
1110       ClassifyFunctions(functions)
1111   return js_fuzzable_functions
1112
1113
1114 FUZZ_TARGET_LISTS = {
1115   "runtime": _GetFuzzableRuntimeFunctions,
1116   "builtins": FindJSBuiltins,
1117 }
1118
1119
1120 def RunFuzzer(process_id, options, stop_running):
1121   MAX_SLEEP_TIME = 0.1
1122   INITIAL_SLEEP_TIME = 0.001
1123   SLEEP_TIME_FACTOR = 1.25
1124   base_file_name = "/dev/shm/runtime_fuzz_%d" % process_id
1125   test_file_name = "%s.js" % base_file_name
1126   stderr_file_name = "%s.out" % base_file_name
1127   save_file_index = 0
1128   while os.path.exists(_SaveFileName(options.save_path, process_id,
1129                                      save_file_index)):
1130     save_file_index += 1
1131
1132   targets = FUZZ_TARGET_LISTS[options.fuzz_target]()
1133   try:
1134     for i in range(options.num_tests):
1135       if stop_running.is_set(): break
1136       function = None
1137       while function is None or function.argslength == 0:
1138         function = random.choice(targets)
1139       args = []
1140       definitions = []
1141       gen = Generator()
1142       for i in range(function.argslength):
1143         arg = function.args[i]
1144         argname = "arg%d%s" % (i, arg.name)
1145         args.append(argname)
1146         definitions += gen.RandomVariable(argname, arg.type, simple=False)
1147       testcase = _GenerateTestcase(function, definitions, args, True)
1148       with open(test_file_name, "w") as f:
1149         f.write("%s\n" % testcase)
1150       with open("/dev/null", "w") as devnull:
1151         with open(stderr_file_name, "w") as stderr:
1152           process = subprocess.Popen(
1153               [options.binary, "--allow-natives-syntax", "--harmony",
1154                "--enable-slow-asserts", test_file_name],
1155               stdout=devnull, stderr=stderr)
1156           end_time = time.time() + options.timeout
1157           timed_out = False
1158           exit_code = None
1159           sleep_time = INITIAL_SLEEP_TIME
1160           while exit_code is None:
1161             if time.time() >= end_time:
1162               # Kill the process and wait for it to exit.
1163               os.kill(process.pid, signal.SIGTERM)
1164               exit_code = process.wait()
1165               timed_out = True
1166             else:
1167               exit_code = process.poll()
1168               time.sleep(sleep_time)
1169               sleep_time = sleep_time * SLEEP_TIME_FACTOR
1170               if sleep_time > MAX_SLEEP_TIME:
1171                 sleep_time = MAX_SLEEP_TIME
1172       if exit_code != 0 and not timed_out:
1173         oom = False
1174         with open(stderr_file_name, "r") as stderr:
1175           for line in stderr:
1176             if line.strip() == "# Allocation failed - process out of memory":
1177               oom = True
1178               break
1179         if oom: continue
1180         save_name = _SaveFileName(options.save_path, process_id,
1181                                   save_file_index)
1182         shutil.copyfile(test_file_name, save_name)
1183         save_file_index += 1
1184   except KeyboardInterrupt:
1185     stop_running.set()
1186   finally:
1187     if os.path.exists(test_file_name):
1188       os.remove(test_file_name)
1189     if os.path.exists(stderr_file_name):
1190       os.remove(stderr_file_name)
1191
1192
1193 def BuildOptionParser():
1194   usage = """Usage: %%prog [options] ACTION
1195
1196 where ACTION can be:
1197
1198 info      Print diagnostic info.
1199 check     Check that runtime functions can be parsed as expected, and that
1200           test cases exist.
1201 generate  Parse source code for runtime functions, and auto-generate
1202           test cases for them. Warning: this will nuke and re-create
1203           %(path)s.
1204 fuzz      Generate fuzz tests, run them, save those that crashed (see options).
1205 """ % {"path": os.path.relpath(BASEPATH)}
1206
1207   o = optparse.OptionParser(usage=usage)
1208   o.add_option("--binary", default="out/x64.debug/d8",
1209                help="d8 binary used for running fuzz tests (default: %default)")
1210   o.add_option("--fuzz-target", default="runtime",
1211                help="Set of functions targeted by fuzzing. Allowed values: "
1212                     "%s (default: %%default)" % ", ".join(FUZZ_TARGET_LISTS))
1213   o.add_option("-n", "--num-tests", default=1000, type="int",
1214                help="Number of fuzz tests to generate per worker process"
1215                     " (default: %default)")
1216   o.add_option("--save-path", default="~/runtime_fuzz_output",
1217                help="Path to directory where failing tests will be stored"
1218                     " (default: %default)")
1219   o.add_option("--timeout", default=20, type="int",
1220                help="Timeout for each fuzz test (in seconds, default:"
1221                     "%default)")
1222   return o
1223
1224
1225 def ProcessOptions(options, args):
1226   options.save_path = os.path.expanduser(options.save_path)
1227   if options.fuzz_target not in FUZZ_TARGET_LISTS:
1228     print("Invalid fuzz target: %s" % options.fuzz_target)
1229     return False
1230   if len(args) != 1 or args[0] == "help":
1231     return False
1232   return True
1233
1234
1235 def Main():
1236   parser = BuildOptionParser()
1237   (options, args) = parser.parse_args()
1238
1239   if not ProcessOptions(options, args):
1240     parser.print_help()
1241     return 1
1242   action = args[0]
1243
1244   functions = FindRuntimeFunctions()
1245   (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \
1246       ClassifyFunctions(functions)
1247   builtins = FindJSBuiltins()
1248
1249   if action == "test":
1250     print("put your temporary debugging code here")
1251     return 0
1252
1253   if action == "info":
1254     print("%d functions total; js_fuzzable_functions: %d, "
1255           "cctest_fuzzable_functions: %d, unknown_functions: %d"
1256           % (len(functions), len(js_fuzzable_functions),
1257              len(cctest_fuzzable_functions), len(unknown_functions)))
1258     print("%d JavaScript builtins" % len(builtins))
1259     print("unknown functions:")
1260     for f in unknown_functions:
1261       print(f)
1262     return 0
1263
1264   if action == "check":
1265     errors = 0
1266
1267     def CheckCount(actual, expected, description):
1268       if len(actual) != expected:
1269         print("Expected to detect %d %s, but found %d." % (
1270               expected, description, len(actual)))
1271         print("If this change is intentional, please update the expectations"
1272               " at the top of %s." % THIS_SCRIPT)
1273         return 1
1274       return 0
1275
1276     errors += CheckCount(functions, EXPECTED_FUNCTION_COUNT,
1277                          "functions in total")
1278     errors += CheckCount(js_fuzzable_functions, EXPECTED_FUZZABLE_COUNT,
1279                          "JavaScript-fuzzable functions")
1280     errors += CheckCount(cctest_fuzzable_functions, EXPECTED_CCTEST_COUNT,
1281                          "cctest-fuzzable functions")
1282     errors += CheckCount(unknown_functions, EXPECTED_UNKNOWN_COUNT,
1283                          "functions with incomplete type information")
1284     errors += CheckCount(builtins, EXPECTED_BUILTINS_COUNT,
1285                          "JavaScript builtins")
1286
1287     def CheckTestcasesExisting(functions):
1288       errors = 0
1289       for f in functions:
1290         if not os.path.isfile(os.path.join(BASEPATH, f.Filename())):
1291           print("Missing testcase for %s, please run '%s generate'" %
1292                 (f.name, THIS_SCRIPT))
1293           errors += 1
1294       files = filter(lambda filename: not filename.startswith("."),
1295                      os.listdir(BASEPATH))
1296       if (len(files) != len(functions)):
1297         unexpected_files = set(files) - set([f.Filename() for f in functions])
1298         for f in unexpected_files:
1299           print("Unexpected testcase: %s" % os.path.join(BASEPATH, f))
1300           errors += 1
1301         print("Run '%s generate' to automatically clean these up."
1302               % THIS_SCRIPT)
1303       return errors
1304
1305     errors += CheckTestcasesExisting(js_fuzzable_functions)
1306
1307     def CheckNameClashes(runtime_functions, builtins):
1308       errors = 0
1309       runtime_map = {}
1310       for f in runtime_functions:
1311         runtime_map[f.name] = 1
1312       for b in builtins:
1313         if b.name in runtime_map:
1314           print("Builtin/Runtime_Function name clash: %s" % b.name)
1315           errors += 1
1316       return errors
1317
1318     errors += CheckNameClashes(functions, builtins)
1319
1320     if errors > 0:
1321       return 1
1322     print("Generated runtime tests: all good.")
1323     return 0
1324
1325   if action == "generate":
1326     GenerateTestcases(js_fuzzable_functions)
1327     return 0
1328
1329   if action == "fuzz":
1330     processes = []
1331     if not os.path.isdir(options.save_path):
1332       os.makedirs(options.save_path)
1333     stop_running = multiprocessing.Event()
1334     for i in range(multiprocessing.cpu_count()):
1335       args = (i, options, stop_running)
1336       p = multiprocessing.Process(target=RunFuzzer, args=args)
1337       p.start()
1338       processes.append(p)
1339     try:
1340       for i in range(len(processes)):
1341         processes[i].join()
1342     except KeyboardInterrupt:
1343       stop_running.set()
1344       for i in range(len(processes)):
1345         processes[i].join()
1346     return 0
1347
1348 if __name__ == "__main__":
1349   sys.exit(Main())