Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python
      2 # Copyright 2014 the V8 project authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 import itertools
      7 import js2c
      8 import multiprocessing
      9 import optparse
     10 import os
     11 import random
     12 import re
     13 import shutil
     14 import signal
     15 import string
     16 import subprocess
     17 import sys
     18 import time
     19 
     20 FILENAME = "src/runtime.cc"
     21 HEADERFILENAME = "src/runtime.h"
     22 FUNCTION = re.compile("^RUNTIME_FUNCTION\(Runtime_(\w+)")
     23 ARGSLENGTH = re.compile(".*ASSERT\(.*args\.length\(\) == (\d+)\);")
     24 FUNCTIONEND = "}\n"
     25 MACRO = re.compile(r"^#define ([^ ]+)\(([^)]*)\) *([^\\]*)\\?\n$")
     26 FIRST_WORD = re.compile("^\s*(.*?)[\s({\[]")
     27 
     28 WORKSPACE = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), ".."))
     29 BASEPATH = os.path.join(WORKSPACE, "test", "mjsunit", "runtime-gen")
     30 THIS_SCRIPT = os.path.relpath(sys.argv[0])
     31 
     32 # Expand these macros, they define further runtime functions.
     33 EXPAND_MACROS = [
     34   "BUFFER_VIEW_GETTER",
     35   "DATA_VIEW_GETTER",
     36   "DATA_VIEW_SETTER",
     37   "RUNTIME_UNARY_MATH",
     38 ]
     39 # TODO(jkummerow): We could also whitelist the following macros, but the
     40 # functions they define are so trivial that it's unclear how much benefit
     41 # that would provide:
     42 # ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION
     43 # FIXED_TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION
     44 # TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION
     45 
     46 # Counts of functions in each detection state. These are used to assert
     47 # that the parser doesn't bit-rot. Change the values as needed when you add,
     48 # remove or change runtime functions, but make sure we don't lose our ability
     49 # to parse them!
     50 EXPECTED_FUNCTION_COUNT = 358
     51 EXPECTED_FUZZABLE_COUNT = 326
     52 EXPECTED_CCTEST_COUNT = 6
     53 EXPECTED_UNKNOWN_COUNT = 4
     54 EXPECTED_BUILTINS_COUNT = 798
     55 
     56 
     57 # Don't call these at all.
     58 BLACKLISTED = [
     59   "Abort",  # Kills the process.
     60   "AbortJS",  # Kills the process.
     61   "CompileForOnStackReplacement",  # Riddled with ASSERTs.
     62   "IS_VAR",  # Not implemented in the runtime.
     63   "ListNatives",  # Not available in Release mode.
     64   "SetAllocationTimeout",  # Too slow for fuzzing.
     65   "SystemBreak",  # Kills (int3) the process.
     66 
     67   # These are weird. They violate some invariants when called after
     68   # bootstrapping.
     69   "DisableAccessChecks",
     70   "EnableAccessChecks",
     71 
     72   # The current LiveEdit implementation relies on and messes with internals
     73   # in ways that makes it fundamentally unfuzzable :-(
     74   "DebugGetLoadedScripts",
     75   "DebugSetScriptSource",
     76   "LiveEditFindSharedFunctionInfosForScript",
     77   "LiveEditFunctionSourceUpdated",
     78   "LiveEditGatherCompileInfo",
     79   "LiveEditPatchFunctionPositions",
     80   "LiveEditReplaceFunctionCode",
     81   "LiveEditReplaceRefToNestedFunction",
     82   "LiveEditReplaceScript",
     83   "LiveEditRestartFrame",
     84   "SetScriptBreakPoint",
     85 
     86   # TODO(jkummerow): Fix these and un-blacklist them!
     87   "CreateDateTimeFormat",
     88   "CreateNumberFormat",
     89 ]
     90 
     91 
     92 # These will always throw.
     93 THROWS = [
     94   "CheckExecutionState",  # Needs to hit a break point.
     95   "CheckIsBootstrapping",  # Needs to be bootstrapping.
     96   "DebugEvaluate",  # Needs to hit a break point.
     97   "DebugEvaluateGlobal",  # Needs to hit a break point.
     98   "DebugIndexedInterceptorElementValue",  # Needs an indexed interceptor.
     99   "DebugNamedInterceptorPropertyValue",  # Needs a named interceptor.
    100   "DebugSetScriptSource",  # Checks compilation state of script.
    101   "GetAllScopesDetails",  # Needs to hit a break point.
    102   "GetFrameCount",  # Needs to hit a break point.
    103   "GetFrameDetails",  # Needs to hit a break point.
    104   "GetRootNaN",  # Needs to be bootstrapping.
    105   "GetScopeCount",  # Needs to hit a break point.
    106   "GetScopeDetails",  # Needs to hit a break point.
    107   "GetStepInPositions",  # Needs to hit a break point.
    108   "GetTemplateField",  # Needs a {Function,Object}TemplateInfo.
    109   "GetThreadCount",  # Needs to hit a break point.
    110   "GetThreadDetails",  # Needs to hit a break point.
    111   "IsAccessAllowedForObserver",  # Needs access-check-required object.
    112   "UnblockConcurrentRecompilation"  # Needs --block-concurrent-recompilation.
    113 ]
    114 
    115 
    116 # Definitions used in CUSTOM_KNOWN_GOOD_INPUT below.
    117 _BREAK_ITERATOR = (
    118     "%GetImplFromInitializedIntlObject(new Intl.v8BreakIterator())")
    119 _COLLATOR = "%GetImplFromInitializedIntlObject(new Intl.Collator('en-US'))"
    120 _DATETIME_FORMAT = (
    121     "%GetImplFromInitializedIntlObject(new Intl.DateTimeFormat('en-US'))")
    122 _NUMBER_FORMAT = (
    123     "%GetImplFromInitializedIntlObject(new Intl.NumberFormat('en-US'))")
    124 
    125 
    126 # Custom definitions for function input that does not throw.
    127 # Format: "FunctionName": ["arg0", "arg1", ..., argslength].
    128 # None means "fall back to autodetected value".
    129 CUSTOM_KNOWN_GOOD_INPUT = {
    130   "Apply": ["function() {}", None, None, None, None, None],
    131   "ArrayBufferSliceImpl": [None, None, 0, None],
    132   "ArrayConcat": ["[1, 'a']", None],
    133   "BreakIteratorAdoptText": [_BREAK_ITERATOR, None, None],
    134   "BreakIteratorBreakType": [_BREAK_ITERATOR, None],
    135   "BreakIteratorCurrent": [_BREAK_ITERATOR, None],
    136   "BreakIteratorFirst": [_BREAK_ITERATOR, None],
    137   "BreakIteratorNext": [_BREAK_ITERATOR, None],
    138   "CompileString": [None, "false", None],
    139   "CreateBreakIterator": ["'en-US'", "{type: 'string'}", None, None],
    140   "CreateJSFunctionProxy": [None, "function() {}", None, None, None],
    141   "CreatePrivateSymbol": ["\"foo\"", None],
    142   "CreateSymbol": ["\"foo\"", None],
    143   "DateParseString": [None, "new Array(8)", None],
    144   "DefineOrRedefineAccessorProperty": [None, None, "function() {}",
    145                                        "function() {}", 2, None],
    146   "FunctionBindArguments": [None, None, "undefined", None, None],
    147   "GetBreakLocations": [None, 0, None],
    148   "GetDefaultReceiver": ["function() {}", None],
    149   "GetImplFromInitializedIntlObject": ["new Intl.NumberFormat('en-US')", None],
    150   "InternalCompare": [_COLLATOR, None, None, None],
    151   "InternalDateFormat": [_DATETIME_FORMAT, None, None],
    152   "InternalDateParse": [_DATETIME_FORMAT, None, None],
    153   "InternalNumberFormat": [_NUMBER_FORMAT, None, None],
    154   "InternalNumberParse": [_NUMBER_FORMAT, None, None],
    155   "IsSloppyModeFunction": ["function() {}", None],
    156   "LoadMutableDouble": ["{foo: 1.2}", None, None],
    157   "NewObjectFromBound": ["(function() {}).bind({})", None],
    158   "NumberToRadixString": [None, "2", None],
    159   "ParseJson": ["\"{}\"", 1],
    160   "RegExpExecMultiple": [None, None, "['a']", "['a']", None],
    161   "SetAccessorProperty": [None, None, "undefined", "undefined", None, None,
    162                           None],
    163   "SetIteratorInitialize": [None, None, "2", None],
    164   "SetDebugEventListener": ["undefined", None, None],
    165   "SetFunctionBreakPoint": [None, 200, None, None],
    166   "StringBuilderConcat": ["[1, 2, 3]", 3, None, None],
    167   "StringBuilderJoin": ["['a', 'b']", 4, None, None],
    168   "StringMatch": [None, None, "['a', 'b']", None],
    169   "StringNormalize": [None, 2, None],
    170   "StringReplaceGlobalRegExpWithString": [None, None, None, "['a']", None],
    171   "TypedArrayInitialize": [None, 6, "new ArrayBuffer(8)", None, 4, None],
    172   "TypedArrayInitializeFromArrayLike": [None, 6, None, None, None],
    173   "TypedArraySetFastCases": [None, None, "0", None],
    174 }
    175 
    176 
    177 # Types of arguments that cannot be generated in a JavaScript testcase.
    178 NON_JS_TYPES = [
    179   "Code", "Context", "FixedArray", "FunctionTemplateInfo",
    180   "JSFunctionResultCache", "JSMessageObject", "Map", "ScopeInfo",
    181   "SharedFunctionInfo"]
    182 
    183 
    184 class Generator(object):
    185 
    186   def RandomVariable(self, varname, vartype, simple):
    187     if simple:
    188       return self._Variable(varname, self.GENERATORS[vartype][0])
    189     return self.GENERATORS[vartype][1](self, varname,
    190                                        self.DEFAULT_RECURSION_BUDGET)
    191 
    192   @staticmethod
    193   def IsTypeSupported(typename):
    194     return typename in Generator.GENERATORS
    195 
    196   USUAL_SUSPECT_PROPERTIES = ["size", "length", "byteLength", "__proto__",
    197                               "prototype", "0", "1", "-1"]
    198   DEFAULT_RECURSION_BUDGET = 2
    199   PROXY_TRAPS = """{
    200       getOwnPropertyDescriptor: function(name) {
    201         return {value: function() {}, configurable: true, writable: true,
    202                 enumerable: true};
    203       },
    204       getPropertyDescriptor: function(name) {
    205         return {value: function() {}, configurable: true, writable: true,
    206                 enumerable: true};
    207       },
    208       getOwnPropertyNames: function() { return []; },
    209       getPropertyNames: function() { return []; },
    210       defineProperty: function(name, descriptor) {},
    211       delete: function(name) { return true; },
    212       fix: function() {}
    213     }"""
    214 
    215   def _Variable(self, name, value, fallback=None):
    216     args = { "name": name, "value": value, "fallback": fallback }
    217     if fallback:
    218       wrapper = "try { %%s } catch(e) { var %(name)s = %(fallback)s; }" % args
    219     else:
    220       wrapper = "%s"
    221     return [wrapper % ("var %(name)s = %(value)s;" % args)]
    222 
    223   def _Boolean(self, name, recursion_budget):
    224     return self._Variable(name, random.choice(["true", "false"]))
    225 
    226   def _Oddball(self, name, recursion_budget):
    227     return self._Variable(name,
    228                           random.choice(["true", "false", "undefined", "null"]))
    229 
    230   def _StrictMode(self, name, recursion_budget):
    231     return self._Variable(name, random.choice([0, 1]))
    232 
    233   def _Int32(self, name, recursion_budget=0):
    234     die = random.random()
    235     if die < 0.5:
    236       value = random.choice([-3, -1, 0, 1, 2, 10, 515, 0x3fffffff, 0x7fffffff,
    237                              0x40000000, -0x40000000, -0x80000000])
    238     elif die < 0.75:
    239       value = random.randint(-1000, 1000)
    240     else:
    241       value = random.randint(-0x80000000, 0x7fffffff)
    242     return self._Variable(name, value)
    243 
    244   def _Uint32(self, name, recursion_budget=0):
    245     die = random.random()
    246     if die < 0.5:
    247       value = random.choice([0, 1, 2, 3, 4, 8, 0x3fffffff, 0x40000000,
    248                              0x7fffffff, 0xffffffff])
    249     elif die < 0.75:
    250       value = random.randint(0, 1000)
    251     else:
    252       value = random.randint(0, 0xffffffff)
    253     return self._Variable(name, value)
    254 
    255   def _Smi(self, name, recursion_budget):
    256     die = random.random()
    257     if die < 0.5:
    258       value = random.choice([-5, -1, 0, 1, 2, 3, 0x3fffffff, -0x40000000])
    259     elif die < 0.75:
    260       value = random.randint(-1000, 1000)
    261     else:
    262       value = random.randint(-0x40000000, 0x3fffffff)
    263     return self._Variable(name, value)
    264 
    265   def _Number(self, name, recursion_budget):
    266     die = random.random()
    267     if die < 0.5:
    268       return self._Smi(name, recursion_budget)
    269     elif die < 0.6:
    270       value = random.choice(["Infinity", "-Infinity", "NaN", "-0",
    271                              "1.7976931348623157e+308",  # Max value.
    272                              "2.2250738585072014e-308",  # Min value.
    273                              "4.9406564584124654e-324"])  # Min subnormal.
    274     else:
    275       value = random.lognormvariate(0, 15)
    276     return self._Variable(name, value)
    277 
    278   def _RawRandomString(self, minlength=0, maxlength=100,
    279                        alphabet=string.ascii_letters):
    280     length = random.randint(minlength, maxlength)
    281     result = ""
    282     for i in xrange(length):
    283       result += random.choice(alphabet)
    284     return result
    285 
    286   def _SeqString(self, name, recursion_budget):
    287     s1 = self._RawRandomString(1, 5)
    288     s2 = self._RawRandomString(1, 5)
    289     # 'foo' + 'bar'
    290     return self._Variable(name, "\"%s\" + \"%s\"" % (s1, s2))
    291 
    292   def _SeqTwoByteString(self, name):
    293     s1 = self._RawRandomString(1, 5)
    294     s2 = self._RawRandomString(1, 5)
    295     # 'foo' + unicode + 'bar'
    296     return self._Variable(name, "\"%s\" + \"\\2082\" + \"%s\"" % (s1, s2))
    297 
    298   def _SlicedString(self, name):
    299     s = self._RawRandomString(20, 30)
    300     # 'ffoo12345678901234567890'.substr(1)
    301     return self._Variable(name, "\"%s\".substr(1)" % s)
    302 
    303   def _ConsString(self, name):
    304     s1 = self._RawRandomString(8, 15)
    305     s2 = self._RawRandomString(8, 15)
    306     # 'foo12345' + (function() { return 'bar12345';})()
    307     return self._Variable(name,
    308         "\"%s\" + (function() { return \"%s\";})()" % (s1, s2))
    309 
    310   def _InternalizedString(self, name):
    311     return self._Variable(name, "\"%s\"" % self._RawRandomString(0, 20))
    312 
    313   def _String(self, name, recursion_budget):
    314     die = random.random()
    315     if die < 0.5:
    316       string = random.choice(self.USUAL_SUSPECT_PROPERTIES)
    317       return self._Variable(name, "\"%s\"" % string)
    318     elif die < 0.6:
    319       number_name = name + "_number"
    320       result = self._Number(number_name, recursion_budget)
    321       return result + self._Variable(name, "\"\" + %s" % number_name)
    322     elif die < 0.7:
    323       return self._SeqString(name, recursion_budget)
    324     elif die < 0.8:
    325       return self._ConsString(name)
    326     elif die < 0.9:
    327       return self._InternalizedString(name)
    328     else:
    329       return self._SlicedString(name)
    330 
    331   def _Symbol(self, name, recursion_budget):
    332     raw_string_name = name + "_1"
    333     result = self._String(raw_string_name, recursion_budget)
    334     return result + self._Variable(name, "Symbol(%s)" % raw_string_name)
    335 
    336   def _Name(self, name, recursion_budget):
    337     if random.random() < 0.2:
    338       return self._Symbol(name, recursion_budget)
    339     return self._String(name, recursion_budget)
    340 
    341   def _JSValue(self, name, recursion_budget):
    342     die = random.random()
    343     raw_name = name + "_1"
    344     if die < 0.33:
    345       result = self._String(raw_name, recursion_budget)
    346       return result + self._Variable(name, "new String(%s)" % raw_name)
    347     elif die < 0.66:
    348       result = self._Boolean(raw_name, recursion_budget)
    349       return result + self._Variable(name, "new Boolean(%s)" % raw_name)
    350     else:
    351       result = self._Number(raw_name, recursion_budget)
    352       return result + self._Variable(name, "new Number(%s)" % raw_name)
    353 
    354   def _RawRandomPropertyName(self):
    355     if random.random() < 0.5:
    356       return random.choice(self.USUAL_SUSPECT_PROPERTIES)
    357     return self._RawRandomString(0, 10)
    358 
    359   def _AddProperties(self, name, result, recursion_budget):
    360     propcount = random.randint(0, 3)
    361     propname = None
    362     for i in range(propcount):
    363       die = random.random()
    364       if die < 0.5:
    365         propname = "%s_prop%d" % (name, i)
    366         result += self._Name(propname, recursion_budget - 1)
    367       else:
    368         propname = "\"%s\"" % self._RawRandomPropertyName()
    369       propvalue_name = "%s_val%d" % (name, i)
    370       result += self._Object(propvalue_name, recursion_budget - 1)
    371       result.append("try { %s[%s] = %s; } catch (e) {}" %
    372                     (name, propname, propvalue_name))
    373     if random.random() < 0.2 and propname:
    374       # Force the object to slow mode.
    375       result.append("delete %s[%s];" % (name, propname))
    376 
    377   def _RandomElementIndex(self, element_name, result):
    378     if random.random() < 0.5:
    379       return random.randint(-1000, 1000)
    380     result += self._Smi(element_name, 0)
    381     return element_name
    382 
    383   def _AddElements(self, name, result, recursion_budget):
    384     elementcount = random.randint(0, 3)
    385     for i in range(elementcount):
    386       element_name = "%s_idx%d" % (name, i)
    387       index = self._RandomElementIndex(element_name, result)
    388       value_name = "%s_elt%d" % (name, i)
    389       result += self._Object(value_name, recursion_budget - 1)
    390       result.append("try { %s[%s] = %s; } catch(e) {}" %
    391                     (name, index, value_name))
    392 
    393   def _AddAccessors(self, name, result, recursion_budget):
    394     accessorcount = random.randint(0, 3)
    395     for i in range(accessorcount):
    396       propname = self._RawRandomPropertyName()
    397       what = random.choice(["get", "set"])
    398       function_name = "%s_access%d" % (name, i)
    399       result += self._PlainFunction(function_name, recursion_budget - 1)
    400       result.append("try { Object.defineProperty(%s, \"%s\", {%s: %s}); } "
    401                     "catch (e) {}" % (name, propname, what, function_name))
    402 
    403   def _PlainArray(self, name, recursion_budget):
    404     die = random.random()
    405     if die < 0.5:
    406       literal = random.choice(["[]", "[1, 2]", "[1.5, 2.5]",
    407                                "['a', 'b', 1, true]"])
    408       return self._Variable(name, literal)
    409     else:
    410       new = random.choice(["", "new "])
    411       length = random.randint(0, 101000)
    412       return self._Variable(name, "%sArray(%d)" % (new, length))
    413 
    414   def _PlainObject(self, name, recursion_budget):
    415     die = random.random()
    416     if die < 0.67:
    417       literal_propcount = random.randint(0, 3)
    418       properties = []
    419       result = []
    420       for i in range(literal_propcount):
    421         propname = self._RawRandomPropertyName()
    422         propvalue_name = "%s_lit%d" % (name, i)
    423         result += self._Object(propvalue_name, recursion_budget - 1)
    424         properties.append("\"%s\": %s" % (propname, propvalue_name))
    425       return result + self._Variable(name, "{%s}" % ", ".join(properties))
    426     else:
    427       return self._Variable(name, "new Object()")
    428 
    429   def _JSArray(self, name, recursion_budget):
    430     result = self._PlainArray(name, recursion_budget)
    431     self._AddAccessors(name, result, recursion_budget)
    432     self._AddProperties(name, result, recursion_budget)
    433     self._AddElements(name, result, recursion_budget)
    434     return result
    435 
    436   def _RawRandomBufferLength(self):
    437     if random.random() < 0.2:
    438       return random.choice([0, 1, 8, 0x40000000, 0x80000000])
    439     return random.randint(0, 1000)
    440 
    441   def _JSArrayBuffer(self, name, recursion_budget):
    442     length = self._RawRandomBufferLength()
    443     return self._Variable(name, "new ArrayBuffer(%d)" % length)
    444 
    445   def _JSDataView(self, name, recursion_budget):
    446     buffer_name = name + "_buffer"
    447     result = self._JSArrayBuffer(buffer_name, recursion_budget)
    448     args = [buffer_name]
    449     die = random.random()
    450     if die < 0.67:
    451       offset = self._RawRandomBufferLength()
    452       args.append("%d" % offset)
    453       if die < 0.33:
    454         length = self._RawRandomBufferLength()
    455         args.append("%d" % length)
    456     result += self._Variable(name, "new DataView(%s)" % ", ".join(args),
    457                              fallback="new DataView(new ArrayBuffer(8))")
    458     return result
    459 
    460   def _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 
    695 class ArgParser(object):
    696   def __init__(self, regex, ctor):
    697     self.regex = regex
    698     self.ArgCtor = ctor
    699 
    700 
    701 class Arg(object):
    702   def __init__(self, typename, varname, index):
    703     self.type = typename
    704     self.name = "_%s" % varname
    705     self.index = index
    706 
    707 
    708 class 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 
    788 class 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".
    828 def 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 
    847 def 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.
    885 def 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...
    930 class 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 
    948 def 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.
    982 def 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 
   1029 def _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 
   1045 def _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 
   1060 def 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 
   1070 def GenerateTestcases(functions):
   1071   shutil.rmtree(BASEPATH)  # Re-generate everything.
   1072   os.makedirs(BASEPATH)
   1073   for f in functions:
   1074     GenerateJSTestcaseForFunction(f)
   1075 
   1076 
   1077 def _SaveFileName(save_path, process_id, save_file_index):
   1078   return "%s/fuzz_%d_%d.js" % (save_path, process_id, save_file_index)
   1079 
   1080 
   1081 def _GetFuzzableRuntimeFunctions():
   1082   functions = FindRuntimeFunctions()
   1083   (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \
   1084       ClassifyFunctions(functions)
   1085   return js_fuzzable_functions
   1086 
   1087 
   1088 FUZZ_TARGET_LISTS = {
   1089   "runtime": _GetFuzzableRuntimeFunctions,
   1090   "builtins": FindJSBuiltins,
   1091 }
   1092 
   1093 
   1094 def 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 
   1167 def BuildOptionParser():
   1168   usage = """Usage: %%prog [options] ACTION
   1169 
   1170 where ACTION can be:
   1171 
   1172 info      Print diagnostic info.
   1173 check     Check that runtime functions can be parsed as expected, and that
   1174           test cases exist.
   1175 generate  Parse source code for runtime functions, and auto-generate
   1176           test cases for them. Warning: this will nuke and re-create
   1177           %(path)s.
   1178 fuzz      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 
   1199 def 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 
   1209 def 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 
   1322 if __name__ == "__main__":
   1323   sys.exit(Main())
   1324