• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
6import itertools
7import js2c
8import multiprocessing
9import optparse
10import os
11import random
12import re
13import shutil
14import signal
15import string
16import subprocess
17import sys
18import time
19
20FILENAME = "src/runtime.cc"
21HEADERFILENAME = "src/runtime.h"
22FUNCTION = re.compile("^RUNTIME_FUNCTION\(Runtime_(\w+)")
23ARGSLENGTH = re.compile(".*ASSERT\(.*args\.length\(\) == (\d+)\);")
24FUNCTIONEND = "}\n"
25MACRO = re.compile(r"^#define ([^ ]+)\(([^)]*)\) *([^\\]*)\\?\n$")
26FIRST_WORD = re.compile("^\s*(.*?)[\s({\[]")
27
28WORKSPACE = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), ".."))
29BASEPATH = os.path.join(WORKSPACE, "test", "mjsunit", "runtime-gen")
30THIS_SCRIPT = os.path.relpath(sys.argv[0])
31
32# Expand these macros, they define further runtime functions.
33EXPAND_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!
50EXPECTED_FUNCTION_COUNT = 358
51EXPECTED_FUZZABLE_COUNT = 326
52EXPECTED_CCTEST_COUNT = 6
53EXPECTED_UNKNOWN_COUNT = 4
54EXPECTED_BUILTINS_COUNT = 798
55
56
57# Don't call these at all.
58BLACKLISTED = [
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.
93THROWS = [
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".
129CUSTOM_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.
178NON_JS_TYPES = [
179  "Code", "Context", "FixedArray", "FunctionTemplateInfo",
180  "JSFunctionResultCache", "JSMessageObject", "Map", "ScopeInfo",
181  "SharedFunctionInfo"]
182
183
184class 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 _JSDate(self, name, recursion_budget):
461    die = random.random()
462    if die < 0.25:
463      return self._Variable(name, "new Date()")
464    elif die < 0.5:
465      ms_name = name + "_ms"
466      result = self._Number(ms_name, recursion_budget)
467      return result + self._Variable(name, "new Date(%s)" % ms_name)
468    elif die < 0.75:
469      str_name = name + "_str"
470      month = random.choice(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
471                             "Aug", "Sep", "Oct", "Nov", "Dec"])
472      day = random.randint(1, 28)
473      year = random.randint(1900, 2100)
474      hour = random.randint(0, 23)
475      minute = random.randint(0, 59)
476      second = random.randint(0, 59)
477      str_value = ("\"%s %s, %s %s:%s:%s\"" %
478                   (month, day, year, hour, minute, second))
479      result = self._Variable(str_name, str_value)
480      return result + self._Variable(name, "new Date(%s)" % str_name)
481    else:
482      components = tuple(map(lambda x: "%s_%s" % (name, x),
483                             ["y", "m", "d", "h", "min", "s", "ms"]))
484      return ([j for i in map(self._Int32, components) for j in i] +
485              self._Variable(name, "new Date(%s)" % ", ".join(components)))
486
487  def _PlainFunction(self, name, recursion_budget):
488    result_name = "result"
489    body = ["function() {"]
490    body += self._Object(result_name, recursion_budget - 1)
491    body.append("return result;\n}")
492    return self._Variable(name, "%s" % "\n".join(body))
493
494  def _JSFunction(self, name, recursion_budget):
495    result = self._PlainFunction(name, recursion_budget)
496    self._AddAccessors(name, result, recursion_budget)
497    self._AddProperties(name, result, recursion_budget)
498    self._AddElements(name, result, recursion_budget)
499    return result
500
501  def _JSFunctionProxy(self, name, recursion_budget):
502    # TODO(jkummerow): Revisit this as the Proxy implementation evolves.
503    return self._Variable(name, "Proxy.createFunction(%s, function() {})" %
504                                self.PROXY_TRAPS)
505
506  def _JSGeneratorObject(self, name, recursion_budget):
507    # TODO(jkummerow): Be more creative here?
508    return self._Variable(name, "(function*() { yield 1; })()")
509
510  def _JSMap(self, name, recursion_budget, weak=""):
511    result = self._Variable(name, "new %sMap()" % weak)
512    num_entries = random.randint(0, 3)
513    for i in range(num_entries):
514      key_name = "%s_k%d" % (name, i)
515      value_name = "%s_v%d" % (name, i)
516      if weak:
517        result += self._JSObject(key_name, recursion_budget - 1)
518      else:
519        result += self._Object(key_name, recursion_budget - 1)
520      result += self._Object(value_name, recursion_budget - 1)
521      result.append("%s.set(%s, %s)" % (name, key_name, value_name))
522    return result
523
524  def _JSMapIterator(self, name, recursion_budget):
525    map_name = name + "_map"
526    result = self._JSMap(map_name, recursion_budget)
527    iterator_type = random.choice(['keys', 'values', 'entries'])
528    return (result + self._Variable(name, "%s.%s()" %
529                                          (map_name, iterator_type)))
530
531  def _JSProxy(self, name, recursion_budget):
532    # TODO(jkummerow): Revisit this as the Proxy implementation evolves.
533    return self._Variable(name, "Proxy.create(%s)" % self.PROXY_TRAPS)
534
535  def _JSRegExp(self, name, recursion_budget):
536    flags = random.choice(["", "g", "i", "m", "gi"])
537    string = "a(b|c)*a"  # TODO(jkummerow): Be more creative here?
538    ctor = random.choice(["/%s/%s", "new RegExp(\"%s\", \"%s\")"])
539    return self._Variable(name, ctor % (string, flags))
540
541  def _JSSet(self, name, recursion_budget, weak=""):
542    result = self._Variable(name, "new %sSet()" % weak)
543    num_entries = random.randint(0, 3)
544    for i in range(num_entries):
545      element_name = "%s_e%d" % (name, i)
546      if weak:
547        result += self._JSObject(element_name, recursion_budget - 1)
548      else:
549        result += self._Object(element_name, recursion_budget - 1)
550      result.append("%s.add(%s)" % (name, element_name))
551    return result
552
553  def _JSSetIterator(self, name, recursion_budget):
554    set_name = name + "_set"
555    result = self._JSSet(set_name, recursion_budget)
556    iterator_type = random.choice(['values', 'entries'])
557    return (result + self._Variable(name, "%s.%s()" %
558                                          (set_name, iterator_type)))
559
560  def _JSTypedArray(self, name, recursion_budget):
561    arraytype = random.choice(["Int8", "Int16", "Int32", "Uint8", "Uint16",
562                               "Uint32", "Float32", "Float64", "Uint8Clamped"])
563    ctor_type = random.randint(0, 3)
564    if ctor_type == 0:
565      length = random.randint(0, 1000)
566      return self._Variable(name, "new %sArray(%d)" % (arraytype, length),
567                            fallback="new %sArray(8)" % arraytype)
568    elif ctor_type == 1:
569      input_name = name + "_typedarray"
570      result = self._JSTypedArray(input_name, recursion_budget - 1)
571      return (result +
572              self._Variable(name, "new %sArray(%s)" % (arraytype, input_name),
573                             fallback="new %sArray(8)" % arraytype))
574    elif ctor_type == 2:
575      arraylike_name = name + "_arraylike"
576      result = self._JSObject(arraylike_name, recursion_budget - 1)
577      length = random.randint(0, 1000)
578      result.append("try { %s.length = %d; } catch(e) {}" %
579                    (arraylike_name, length))
580      return (result +
581              self._Variable(name,
582                             "new %sArray(%s)" % (arraytype, arraylike_name),
583                             fallback="new %sArray(8)" % arraytype))
584    else:
585      die = random.random()
586      buffer_name = name + "_buffer"
587      args = [buffer_name]
588      result = self._JSArrayBuffer(buffer_name, recursion_budget)
589      if die < 0.67:
590        offset_name = name + "_offset"
591        args.append(offset_name)
592        result += self._Int32(offset_name)
593      if die < 0.33:
594        length_name = name + "_length"
595        args.append(length_name)
596        result += self._Int32(length_name)
597      return (result +
598              self._Variable(name,
599                             "new %sArray(%s)" % (arraytype, ", ".join(args)),
600                             fallback="new %sArray(8)" % arraytype))
601
602  def _JSArrayBufferView(self, name, recursion_budget):
603    if random.random() < 0.4:
604      return self._JSDataView(name, recursion_budget)
605    else:
606      return self._JSTypedArray(name, recursion_budget)
607
608  def _JSWeakCollection(self, name, recursion_budget):
609    ctor = random.choice([self._JSMap, self._JSSet])
610    return ctor(name, recursion_budget, weak="Weak")
611
612  def _PropertyDetails(self, name, recursion_budget):
613    # TODO(jkummerow): Be more clever here?
614    return self._Int32(name)
615
616  def _JSObject(self, name, recursion_budget):
617    die = random.random()
618    if die < 0.4:
619      function = random.choice([self._PlainObject, self._PlainArray,
620                                self._PlainFunction])
621    elif die < 0.5:
622      return self._Variable(name, "this")  # Global object.
623    else:
624      function = random.choice([self._JSArrayBuffer, self._JSDataView,
625                                self._JSDate, self._JSFunctionProxy,
626                                self._JSGeneratorObject, self._JSMap,
627                                self._JSMapIterator, self._JSRegExp,
628                                self._JSSet, self._JSSetIterator,
629                                self._JSTypedArray, self._JSValue,
630                                self._JSWeakCollection])
631    result = function(name, recursion_budget)
632    self._AddAccessors(name, result, recursion_budget)
633    self._AddProperties(name, result, recursion_budget)
634    self._AddElements(name, result, recursion_budget)
635    return result
636
637  def _JSReceiver(self, name, recursion_budget):
638    if random.random() < 0.9: return self._JSObject(name, recursion_budget)
639    return self._JSProxy(name, recursion_budget)
640
641  def _HeapObject(self, name, recursion_budget):
642    die = random.random()
643    if die < 0.9: return self._JSReceiver(name, recursion_budget)
644    elif die < 0.95: return  self._Oddball(name, recursion_budget)
645    else: return self._Name(name, recursion_budget)
646
647  def _Object(self, name, recursion_budget):
648    if recursion_budget <= 0:
649      function = random.choice([self._Oddball, self._Number, self._Name,
650                                self._JSValue, self._JSRegExp])
651      return function(name, recursion_budget)
652    if random.random() < 0.2:
653      return self._Smi(name, recursion_budget)
654    return self._HeapObject(name, recursion_budget)
655
656  GENERATORS = {
657    "Boolean": ["true", _Boolean],
658    "HeapObject": ["new Object()", _HeapObject],
659    "Int32": ["32", _Int32],
660    "JSArray": ["new Array()", _JSArray],
661    "JSArrayBuffer": ["new ArrayBuffer(8)", _JSArrayBuffer],
662    "JSArrayBufferView": ["new Int32Array(2)", _JSArrayBufferView],
663    "JSDataView": ["new DataView(new ArrayBuffer(24))", _JSDataView],
664    "JSDate": ["new Date()", _JSDate],
665    "JSFunction": ["function() {}", _JSFunction],
666    "JSFunctionProxy": ["Proxy.createFunction({}, function() {})",
667                        _JSFunctionProxy],
668    "JSGeneratorObject": ["(function*(){ yield 1; })()", _JSGeneratorObject],
669    "JSMap": ["new Map()", _JSMap],
670    "JSMapIterator": ["new Map().entries()", _JSMapIterator],
671    "JSObject": ["new Object()", _JSObject],
672    "JSProxy": ["Proxy.create({})", _JSProxy],
673    "JSReceiver": ["new Object()", _JSReceiver],
674    "JSRegExp": ["/ab/g", _JSRegExp],
675    "JSSet": ["new Set()", _JSSet],
676    "JSSetIterator": ["new Set().values()", _JSSetIterator],
677    "JSTypedArray": ["new Int32Array(2)", _JSTypedArray],
678    "JSValue": ["new String('foo')", _JSValue],
679    "JSWeakCollection": ["new WeakMap()", _JSWeakCollection],
680    "Name": ["\"name\"", _Name],
681    "Number": ["1.5", _Number],
682    "Object": ["new Object()", _Object],
683    "PropertyDetails": ["513", _PropertyDetails],
684    "SeqOneByteString": ["\"seq 1-byte\"", _SeqString],
685    "SeqString": ["\"seqstring\"", _SeqString],
686    "SeqTwoByteString": ["\"seq \\u2082-byte\"", _SeqTwoByteString],
687    "Smi": ["1", _Smi],
688    "StrictMode": ["1", _StrictMode],
689    "String": ["\"foo\"", _String],
690    "Symbol": ["Symbol(\"symbol\")", _Symbol],
691    "Uint32": ["32", _Uint32],
692  }
693
694
695class ArgParser(object):
696  def __init__(self, regex, ctor):
697    self.regex = regex
698    self.ArgCtor = ctor
699
700
701class Arg(object):
702  def __init__(self, typename, varname, index):
703    self.type = typename
704    self.name = "_%s" % varname
705    self.index = index
706
707
708class Function(object):
709  def __init__(self, match):
710    self.name = match.group(1)
711    self.argslength = -1
712    self.args = {}
713    self.inline = ""
714
715  handle_arg_parser = ArgParser(
716      re.compile("^\s*CONVERT_ARG_HANDLE_CHECKED\((\w+), (\w+), (\d+)\)"),
717      lambda match: Arg(match.group(1), match.group(2), int(match.group(3))))
718
719  plain_arg_parser = ArgParser(
720      re.compile("^\s*CONVERT_ARG_CHECKED\((\w+), (\w+), (\d+)\)"),
721      lambda match: Arg(match.group(1), match.group(2), int(match.group(3))))
722
723  number_handle_arg_parser = ArgParser(
724      re.compile("^\s*CONVERT_NUMBER_ARG_HANDLE_CHECKED\((\w+), (\d+)\)"),
725      lambda match: Arg("Number", match.group(1), int(match.group(2))))
726
727  smi_arg_parser = ArgParser(
728      re.compile("^\s*CONVERT_SMI_ARG_CHECKED\((\w+), (\d+)\)"),
729      lambda match: Arg("Smi", match.group(1), int(match.group(2))))
730
731  double_arg_parser = ArgParser(
732      re.compile("^\s*CONVERT_DOUBLE_ARG_CHECKED\((\w+), (\d+)\)"),
733      lambda match: Arg("Number", match.group(1), int(match.group(2))))
734
735  number_arg_parser = ArgParser(
736      re.compile(
737          "^\s*CONVERT_NUMBER_CHECKED\(\w+, (\w+), (\w+), args\[(\d+)\]\)"),
738      lambda match: Arg(match.group(2), match.group(1), int(match.group(3))))
739
740  strict_mode_arg_parser = ArgParser(
741      re.compile("^\s*CONVERT_STRICT_MODE_ARG_CHECKED\((\w+), (\d+)\)"),
742      lambda match: Arg("StrictMode", match.group(1), int(match.group(2))))
743
744  boolean_arg_parser = ArgParser(
745      re.compile("^\s*CONVERT_BOOLEAN_ARG_CHECKED\((\w+), (\d+)\)"),
746      lambda match: Arg("Boolean", match.group(1), int(match.group(2))))
747
748  property_details_parser = ArgParser(
749      re.compile("^\s*CONVERT_PROPERTY_DETAILS_CHECKED\((\w+), (\d+)\)"),
750      lambda match: Arg("PropertyDetails", match.group(1), int(match.group(2))))
751
752  arg_parsers = [handle_arg_parser, plain_arg_parser, number_handle_arg_parser,
753                 smi_arg_parser,
754                 double_arg_parser, number_arg_parser, strict_mode_arg_parser,
755                 boolean_arg_parser, property_details_parser]
756
757  def SetArgsLength(self, match):
758    self.argslength = int(match.group(1))
759
760  def TryParseArg(self, line):
761    for parser in Function.arg_parsers:
762      match = parser.regex.match(line)
763      if match:
764        arg = parser.ArgCtor(match)
765        self.args[arg.index] = arg
766        return True
767    return False
768
769  def Filename(self):
770    return "%s.js" % self.name.lower()
771
772  def __str__(self):
773    s = [self.name, "("]
774    argcount = self.argslength
775    if argcount < 0:
776      print("WARNING: unknown argslength for function %s" % self.name)
777      if self.args:
778        argcount = max([self.args[i].index + 1 for i in self.args])
779      else:
780        argcount = 0
781    for i in range(argcount):
782      if i > 0: s.append(", ")
783      s.append(self.args[i].type if i in self.args else "<unknown>")
784    s.append(")")
785    return "".join(s)
786
787
788class Macro(object):
789  def __init__(self, match):
790    self.name = match.group(1)
791    self.args = [s.strip() for s in match.group(2).split(",")]
792    self.lines = []
793    self.indentation = 0
794    self.AddLine(match.group(3))
795
796  def AddLine(self, line):
797    if not line: return
798    if not self.lines:
799      # This is the first line, detect indentation.
800      self.indentation = len(line) - len(line.lstrip())
801    line = line.rstrip("\\\n ")
802    if not line: return
803    assert len(line[:self.indentation].strip()) == 0, \
804        ("expected whitespace: '%s', full line: '%s'" %
805         (line[:self.indentation], line))
806    line = line[self.indentation:]
807    if not line: return
808    self.lines.append(line + "\n")
809
810  def Finalize(self):
811    for arg in self.args:
812      pattern = re.compile(r"(##|\b)%s(##|\b)" % arg)
813      for i in range(len(self.lines)):
814        self.lines[i] = re.sub(pattern, "%%(%s)s" % arg, self.lines[i])
815
816  def FillIn(self, arg_values):
817    filler = {}
818    assert len(arg_values) == len(self.args)
819    for i in range(len(self.args)):
820      filler[self.args[i]] = arg_values[i]
821    result = []
822    for line in self.lines:
823      result.append(line % filler)
824    return result
825
826
827# Parses HEADERFILENAME to find out which runtime functions are "inline".
828def FindInlineRuntimeFunctions():
829  inline_functions = []
830  with open(HEADERFILENAME, "r") as f:
831    inline_list = "#define INLINE_FUNCTION_LIST(F) \\\n"
832    inline_function = re.compile(r"^\s*F\((\w+), \d+, \d+\)\s*\\?")
833    mode = "SEARCHING"
834    for line in f:
835      if mode == "ACTIVE":
836        match = inline_function.match(line)
837        if match:
838          inline_functions.append(match.group(1))
839        if not line.endswith("\\\n"):
840          mode = "SEARCHING"
841      elif mode == "SEARCHING":
842        if line == inline_list:
843          mode = "ACTIVE"
844  return inline_functions
845
846
847def ReadFileAndExpandMacros(filename):
848  found_macros = {}
849  expanded_lines = []
850  with open(filename, "r") as f:
851    found_macro = None
852    for line in f:
853      if found_macro is not None:
854        found_macro.AddLine(line)
855        if not line.endswith("\\\n"):
856          found_macro.Finalize()
857          found_macro = None
858        continue
859
860      match = MACRO.match(line)
861      if match:
862        found_macro = Macro(match)
863        if found_macro.name in EXPAND_MACROS:
864          found_macros[found_macro.name] = found_macro
865        else:
866          found_macro = None
867        continue
868
869      match = FIRST_WORD.match(line)
870      if match:
871        first_word = match.group(1)
872        if first_word in found_macros:
873          MACRO_CALL = re.compile("%s\(([^)]*)\)" % first_word)
874          match = MACRO_CALL.match(line)
875          assert match
876          args = [s.strip() for s in match.group(1).split(",")]
877          expanded_lines += found_macros[first_word].FillIn(args)
878          continue
879
880      expanded_lines.append(line)
881  return expanded_lines
882
883
884# Detects runtime functions by parsing FILENAME.
885def FindRuntimeFunctions():
886  inline_functions = FindInlineRuntimeFunctions()
887  functions = []
888  expanded_lines = ReadFileAndExpandMacros(FILENAME)
889  function = None
890  partial_line = ""
891  for line in expanded_lines:
892    # Multi-line definition support, ignoring macros.
893    if line.startswith("RUNTIME_FUNCTION") and not line.endswith("{\n"):
894      if line.endswith("\\\n"): continue
895      partial_line = line.rstrip()
896      continue
897    if partial_line:
898      partial_line += " " + line.strip()
899      if partial_line.endswith("{"):
900        line = partial_line
901        partial_line = ""
902      else:
903        continue
904
905    match = FUNCTION.match(line)
906    if match:
907      function = Function(match)
908      if function.name in inline_functions:
909        function.inline = "_"
910      continue
911    if function is None: continue
912
913    match = ARGSLENGTH.match(line)
914    if match:
915      function.SetArgsLength(match)
916      continue
917
918    if function.TryParseArg(line):
919      continue
920
921    if line == FUNCTIONEND:
922      if function is not None:
923        functions.append(function)
924        function = None
925  return functions
926
927
928# Hack: This must have the same fields as class Function above, because the
929# two are used polymorphically in RunFuzzer(). We could use inheritance...
930class Builtin(object):
931  def __init__(self, match):
932    self.name = match.group(1)
933    args = match.group(2)
934    self.argslength = 0 if args == "" else args.count(",") + 1
935    self.inline = ""
936    self.args = {}
937    if self.argslength > 0:
938      args = args.split(",")
939      for i in range(len(args)):
940        # a = args[i].strip()  # TODO: filter out /* comments */ first.
941        a = ""
942        self.args[i] = Arg("Object", a, i)
943
944  def __str__(self):
945    return "%s(%d)" % (self.name, self.argslength)
946
947
948def FindJSBuiltins():
949  PATH = "src"
950  fileslist = []
951  for (root, dirs, files) in os.walk(PATH):
952    for f in files:
953      if f.endswith(".js"):
954        fileslist.append(os.path.join(root, f))
955  builtins = []
956  regexp = re.compile("^function (\w+)\s*\((.*?)\) {")
957  matches = 0
958  for filename in fileslist:
959    with open(filename, "r") as f:
960      file_contents = f.read()
961    file_contents = js2c.ExpandInlineMacros(file_contents)
962    lines = file_contents.split("\n")
963    partial_line = ""
964    for line in lines:
965      if line.startswith("function") and not '{' in line:
966        partial_line += line.rstrip()
967        continue
968      if partial_line:
969        partial_line += " " + line.strip()
970        if '{' in line:
971          line = partial_line
972          partial_line = ""
973        else:
974          continue
975      match = regexp.match(line)
976      if match:
977        builtins.append(Builtin(match))
978  return builtins
979
980
981# Classifies runtime functions.
982def ClassifyFunctions(functions):
983  # Can be fuzzed with a JavaScript testcase.
984  js_fuzzable_functions = []
985  # We have enough information to fuzz these, but they need inputs that
986  # cannot be created or passed around in JavaScript.
987  cctest_fuzzable_functions = []
988  # This script does not have enough information about these.
989  unknown_functions = []
990
991  types = {}
992  for f in functions:
993    if f.name in BLACKLISTED:
994      continue
995    decision = js_fuzzable_functions
996    custom = CUSTOM_KNOWN_GOOD_INPUT.get(f.name, None)
997    if f.argslength < 0:
998      # Unknown length -> give up unless there's a custom definition.
999      if custom and custom[-1] is not None:
1000        f.argslength = custom[-1]
1001        assert len(custom) == f.argslength + 1, \
1002            ("%s: last custom definition must be argslength" % f.name)
1003      else:
1004        decision = unknown_functions
1005    else:
1006      if custom:
1007        # Any custom definitions must match the known argslength.
1008        assert len(custom) == f.argslength + 1, \
1009            ("%s should have %d custom definitions but has %d" %
1010            (f.name, f.argslength + 1, len(custom)))
1011      for i in range(f.argslength):
1012        if custom and custom[i] is not None:
1013          # All good, there's a custom definition.
1014          pass
1015        elif not i in f.args:
1016          # No custom definition and no parse result -> give up.
1017          decision = unknown_functions
1018        else:
1019          t = f.args[i].type
1020          if t in NON_JS_TYPES:
1021            decision = cctest_fuzzable_functions
1022          else:
1023            assert Generator.IsTypeSupported(t), \
1024                ("type generator not found for %s, function: %s" % (t, f))
1025    decision.append(f)
1026  return (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions)
1027
1028
1029def _GetKnownGoodArgs(function, generator):
1030  custom_input = CUSTOM_KNOWN_GOOD_INPUT.get(function.name, None)
1031  definitions = []
1032  argslist = []
1033  for i in range(function.argslength):
1034    if custom_input and custom_input[i] is not None:
1035      name = "arg%d" % i
1036      definitions.append("var %s = %s;" % (name, custom_input[i]))
1037    else:
1038      arg = function.args[i]
1039      name = arg.name
1040      definitions += generator.RandomVariable(name, arg.type, simple=True)
1041    argslist.append(name)
1042  return (definitions, argslist)
1043
1044
1045def _GenerateTestcase(function, definitions, argslist, throws):
1046  s = ["// Copyright 2014 the V8 project authors. All rights reserved.",
1047       "// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY",
1048       "// Flags: --allow-natives-syntax --harmony"] + definitions
1049  call = "%%%s%s(%s);" % (function.inline, function.name, ", ".join(argslist))
1050  if throws:
1051    s.append("try {")
1052    s.append(call);
1053    s.append("} catch(e) {}")
1054  else:
1055    s.append(call)
1056  testcase = "\n".join(s)
1057  return testcase
1058
1059
1060def GenerateJSTestcaseForFunction(function):
1061  gen = Generator()
1062  (definitions, argslist) = _GetKnownGoodArgs(function, gen)
1063  testcase = _GenerateTestcase(function, definitions, argslist,
1064                               function.name in THROWS)
1065  path = os.path.join(BASEPATH, function.Filename())
1066  with open(path, "w") as f:
1067    f.write("%s\n" % testcase)
1068
1069
1070def GenerateTestcases(functions):
1071  shutil.rmtree(BASEPATH)  # Re-generate everything.
1072  os.makedirs(BASEPATH)
1073  for f in functions:
1074    GenerateJSTestcaseForFunction(f)
1075
1076
1077def _SaveFileName(save_path, process_id, save_file_index):
1078  return "%s/fuzz_%d_%d.js" % (save_path, process_id, save_file_index)
1079
1080
1081def _GetFuzzableRuntimeFunctions():
1082  functions = FindRuntimeFunctions()
1083  (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \
1084      ClassifyFunctions(functions)
1085  return js_fuzzable_functions
1086
1087
1088FUZZ_TARGET_LISTS = {
1089  "runtime": _GetFuzzableRuntimeFunctions,
1090  "builtins": FindJSBuiltins,
1091}
1092
1093
1094def RunFuzzer(process_id, options, stop_running):
1095  MAX_SLEEP_TIME = 0.1
1096  INITIAL_SLEEP_TIME = 0.001
1097  SLEEP_TIME_FACTOR = 1.25
1098  base_file_name = "/dev/shm/runtime_fuzz_%d" % process_id
1099  test_file_name = "%s.js" % base_file_name
1100  stderr_file_name = "%s.out" % base_file_name
1101  save_file_index = 0
1102  while os.path.exists(_SaveFileName(options.save_path, process_id,
1103                                     save_file_index)):
1104    save_file_index += 1
1105
1106  targets = FUZZ_TARGET_LISTS[options.fuzz_target]()
1107  try:
1108    for i in range(options.num_tests):
1109      if stop_running.is_set(): break
1110      function = None
1111      while function is None or function.argslength == 0:
1112        function = random.choice(targets)
1113      args = []
1114      definitions = []
1115      gen = Generator()
1116      for i in range(function.argslength):
1117        arg = function.args[i]
1118        argname = "arg%d%s" % (i, arg.name)
1119        args.append(argname)
1120        definitions += gen.RandomVariable(argname, arg.type, simple=False)
1121      testcase = _GenerateTestcase(function, definitions, args, True)
1122      with open(test_file_name, "w") as f:
1123        f.write("%s\n" % testcase)
1124      with open("/dev/null", "w") as devnull:
1125        with open(stderr_file_name, "w") as stderr:
1126          process = subprocess.Popen(
1127              [options.binary, "--allow-natives-syntax", "--harmony",
1128               "--enable-slow-asserts", test_file_name],
1129              stdout=devnull, stderr=stderr)
1130          end_time = time.time() + options.timeout
1131          timed_out = False
1132          exit_code = None
1133          sleep_time = INITIAL_SLEEP_TIME
1134          while exit_code is None:
1135            if time.time() >= end_time:
1136              # Kill the process and wait for it to exit.
1137              os.kill(process.pid, signal.SIGTERM)
1138              exit_code = process.wait()
1139              timed_out = True
1140            else:
1141              exit_code = process.poll()
1142              time.sleep(sleep_time)
1143              sleep_time = sleep_time * SLEEP_TIME_FACTOR
1144              if sleep_time > MAX_SLEEP_TIME:
1145                sleep_time = MAX_SLEEP_TIME
1146      if exit_code != 0 and not timed_out:
1147        oom = False
1148        with open(stderr_file_name, "r") as stderr:
1149          for line in stderr:
1150            if line.strip() == "# Allocation failed - process out of memory":
1151              oom = True
1152              break
1153        if oom: continue
1154        save_name = _SaveFileName(options.save_path, process_id,
1155                                  save_file_index)
1156        shutil.copyfile(test_file_name, save_name)
1157        save_file_index += 1
1158  except KeyboardInterrupt:
1159    stop_running.set()
1160  finally:
1161    if os.path.exists(test_file_name):
1162      os.remove(test_file_name)
1163    if os.path.exists(stderr_file_name):
1164      os.remove(stderr_file_name)
1165
1166
1167def BuildOptionParser():
1168  usage = """Usage: %%prog [options] ACTION
1169
1170where ACTION can be:
1171
1172info      Print diagnostic info.
1173check     Check that runtime functions can be parsed as expected, and that
1174          test cases exist.
1175generate  Parse source code for runtime functions, and auto-generate
1176          test cases for them. Warning: this will nuke and re-create
1177          %(path)s.
1178fuzz      Generate fuzz tests, run them, save those that crashed (see options).
1179""" % {"path": os.path.relpath(BASEPATH)}
1180
1181  o = optparse.OptionParser(usage=usage)
1182  o.add_option("--binary", default="out/x64.debug/d8",
1183               help="d8 binary used for running fuzz tests (default: %default)")
1184  o.add_option("--fuzz-target", default="runtime",
1185               help="Set of functions targeted by fuzzing. Allowed values: "
1186                    "%s (default: %%default)" % ", ".join(FUZZ_TARGET_LISTS))
1187  o.add_option("-n", "--num-tests", default=1000, type="int",
1188               help="Number of fuzz tests to generate per worker process"
1189                    " (default: %default)")
1190  o.add_option("--save-path", default="~/runtime_fuzz_output",
1191               help="Path to directory where failing tests will be stored"
1192                    " (default: %default)")
1193  o.add_option("--timeout", default=20, type="int",
1194               help="Timeout for each fuzz test (in seconds, default:"
1195                    "%default)")
1196  return o
1197
1198
1199def ProcessOptions(options, args):
1200  options.save_path = os.path.expanduser(options.save_path)
1201  if options.fuzz_target not in FUZZ_TARGET_LISTS:
1202    print("Invalid fuzz target: %s" % options.fuzz_target)
1203    return False
1204  if len(args) != 1 or args[0] == "help":
1205    return False
1206  return True
1207
1208
1209def Main():
1210  parser = BuildOptionParser()
1211  (options, args) = parser.parse_args()
1212
1213  if not ProcessOptions(options, args):
1214    parser.print_help()
1215    return 1
1216  action = args[0]
1217
1218  functions = FindRuntimeFunctions()
1219  (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \
1220      ClassifyFunctions(functions)
1221  builtins = FindJSBuiltins()
1222
1223  if action == "test":
1224    print("put your temporary debugging code here")
1225    return 0
1226
1227  if action == "info":
1228    print("%d functions total; js_fuzzable_functions: %d, "
1229          "cctest_fuzzable_functions: %d, unknown_functions: %d"
1230          % (len(functions), len(js_fuzzable_functions),
1231             len(cctest_fuzzable_functions), len(unknown_functions)))
1232    print("%d JavaScript builtins" % len(builtins))
1233    print("unknown functions:")
1234    for f in unknown_functions:
1235      print(f)
1236    return 0
1237
1238  if action == "check":
1239    errors = 0
1240
1241    def CheckCount(actual, expected, description):
1242      if len(actual) != expected:
1243        print("Expected to detect %d %s, but found %d." % (
1244              expected, description, len(actual)))
1245        print("If this change is intentional, please update the expectations"
1246              " at the top of %s." % THIS_SCRIPT)
1247        return 1
1248      return 0
1249
1250    errors += CheckCount(functions, EXPECTED_FUNCTION_COUNT,
1251                         "functions in total")
1252    errors += CheckCount(js_fuzzable_functions, EXPECTED_FUZZABLE_COUNT,
1253                         "JavaScript-fuzzable functions")
1254    errors += CheckCount(cctest_fuzzable_functions, EXPECTED_CCTEST_COUNT,
1255                         "cctest-fuzzable functions")
1256    errors += CheckCount(unknown_functions, EXPECTED_UNKNOWN_COUNT,
1257                         "functions with incomplete type information")
1258    errors += CheckCount(builtins, EXPECTED_BUILTINS_COUNT,
1259                         "JavaScript builtins")
1260
1261    def CheckTestcasesExisting(functions):
1262      errors = 0
1263      for f in functions:
1264        if not os.path.isfile(os.path.join(BASEPATH, f.Filename())):
1265          print("Missing testcase for %s, please run '%s generate'" %
1266                (f.name, THIS_SCRIPT))
1267          errors += 1
1268      files = filter(lambda filename: not filename.startswith("."),
1269                     os.listdir(BASEPATH))
1270      if (len(files) != len(functions)):
1271        unexpected_files = set(files) - set([f.Filename() for f in functions])
1272        for f in unexpected_files:
1273          print("Unexpected testcase: %s" % os.path.join(BASEPATH, f))
1274          errors += 1
1275        print("Run '%s generate' to automatically clean these up."
1276              % THIS_SCRIPT)
1277      return errors
1278
1279    errors += CheckTestcasesExisting(js_fuzzable_functions)
1280
1281    def CheckNameClashes(runtime_functions, builtins):
1282      errors = 0
1283      runtime_map = {}
1284      for f in runtime_functions:
1285        runtime_map[f.name] = 1
1286      for b in builtins:
1287        if b.name in runtime_map:
1288          print("Builtin/Runtime_Function name clash: %s" % b.name)
1289          errors += 1
1290      return errors
1291
1292    errors += CheckNameClashes(functions, builtins)
1293
1294    if errors > 0:
1295      return 1
1296    print("Generated runtime tests: all good.")
1297    return 0
1298
1299  if action == "generate":
1300    GenerateTestcases(js_fuzzable_functions)
1301    return 0
1302
1303  if action == "fuzz":
1304    processes = []
1305    if not os.path.isdir(options.save_path):
1306      os.makedirs(options.save_path)
1307    stop_running = multiprocessing.Event()
1308    for i in range(multiprocessing.cpu_count()):
1309      args = (i, options, stop_running)
1310      p = multiprocessing.Process(target=RunFuzzer, args=args)
1311      p.start()
1312      processes.append(p)
1313    try:
1314      for i in range(len(processes)):
1315        processes[i].join()
1316    except KeyboardInterrupt:
1317      stop_running.set()
1318      for i in range(len(processes)):
1319        processes[i].join()
1320    return 0
1321
1322if __name__ == "__main__":
1323  sys.exit(Main())
1324