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