257adcf0ed
When an iterable object is passed in as the argument to the Map and Set constructor the elements of the iterable object are used to populate the Map and Set. http://people.mozilla.org/~jorendorff/es6-draft.html#sec-map-iterable http://people.mozilla.org/~jorendorff/es6-draft.html#sec-set-iterable BUG=v8:3398 LOG=Y R=rossberg@chromium.org Review URL: https://codereview.chromium.org/345613003 Patch from Erik Arvidsson <arv@chromium.org>. git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@21950 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
1323 lines
48 KiB
Python
Executable File
1323 lines
48 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# Copyright 2014 the V8 project authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import itertools
|
|
import js2c
|
|
import multiprocessing
|
|
import optparse
|
|
import os
|
|
import random
|
|
import re
|
|
import shutil
|
|
import signal
|
|
import string
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
FILENAME = "src/runtime.cc"
|
|
HEADERFILENAME = "src/runtime.h"
|
|
FUNCTION = re.compile("^RUNTIME_FUNCTION\(Runtime_(\w+)")
|
|
ARGSLENGTH = re.compile(".*ASSERT\(.*args\.length\(\) == (\d+)\);")
|
|
FUNCTIONEND = "}\n"
|
|
MACRO = re.compile(r"^#define ([^ ]+)\(([^)]*)\) *([^\\]*)\\?\n$")
|
|
FIRST_WORD = re.compile("^\s*(.*?)[\s({\[]")
|
|
|
|
WORKSPACE = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), ".."))
|
|
BASEPATH = os.path.join(WORKSPACE, "test", "mjsunit", "runtime-gen")
|
|
THIS_SCRIPT = os.path.relpath(sys.argv[0])
|
|
|
|
# Expand these macros, they define further runtime functions.
|
|
EXPAND_MACROS = [
|
|
"BUFFER_VIEW_GETTER",
|
|
"DATA_VIEW_GETTER",
|
|
"DATA_VIEW_SETTER",
|
|
"RUNTIME_UNARY_MATH",
|
|
]
|
|
# TODO(jkummerow): We could also whitelist the following macros, but the
|
|
# functions they define are so trivial that it's unclear how much benefit
|
|
# that would provide:
|
|
# ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION
|
|
# FIXED_TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION
|
|
# TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION
|
|
|
|
# Counts of functions in each detection state. These are used to assert
|
|
# that the parser doesn't bit-rot. Change the values as needed when you add,
|
|
# remove or change runtime functions, but make sure we don't lose our ability
|
|
# to parse them!
|
|
EXPECTED_FUNCTION_COUNT = 358
|
|
EXPECTED_FUZZABLE_COUNT = 326
|
|
EXPECTED_CCTEST_COUNT = 6
|
|
EXPECTED_UNKNOWN_COUNT = 4
|
|
EXPECTED_BUILTINS_COUNT = 800
|
|
|
|
|
|
# Don't call these at all.
|
|
BLACKLISTED = [
|
|
"Abort", # Kills the process.
|
|
"AbortJS", # Kills the process.
|
|
"CompileForOnStackReplacement", # Riddled with ASSERTs.
|
|
"IS_VAR", # Not implemented in the runtime.
|
|
"ListNatives", # Not available in Release mode.
|
|
"SetAllocationTimeout", # Too slow for fuzzing.
|
|
"SystemBreak", # Kills (int3) the process.
|
|
|
|
# These are weird. They violate some invariants when called after
|
|
# bootstrapping.
|
|
"DisableAccessChecks",
|
|
"EnableAccessChecks",
|
|
|
|
# The current LiveEdit implementation relies on and messes with internals
|
|
# in ways that makes it fundamentally unfuzzable :-(
|
|
"DebugGetLoadedScripts",
|
|
"DebugSetScriptSource",
|
|
"LiveEditFindSharedFunctionInfosForScript",
|
|
"LiveEditFunctionSourceUpdated",
|
|
"LiveEditGatherCompileInfo",
|
|
"LiveEditPatchFunctionPositions",
|
|
"LiveEditReplaceFunctionCode",
|
|
"LiveEditReplaceRefToNestedFunction",
|
|
"LiveEditReplaceScript",
|
|
"LiveEditRestartFrame",
|
|
"SetScriptBreakPoint",
|
|
|
|
# TODO(jkummerow): Fix these and un-blacklist them!
|
|
"CreateDateTimeFormat",
|
|
"CreateNumberFormat",
|
|
]
|
|
|
|
|
|
# These will always throw.
|
|
THROWS = [
|
|
"CheckExecutionState", # Needs to hit a break point.
|
|
"CheckIsBootstrapping", # Needs to be bootstrapping.
|
|
"DebugEvaluate", # Needs to hit a break point.
|
|
"DebugEvaluateGlobal", # Needs to hit a break point.
|
|
"DebugIndexedInterceptorElementValue", # Needs an indexed interceptor.
|
|
"DebugNamedInterceptorPropertyValue", # Needs a named interceptor.
|
|
"DebugSetScriptSource", # Checks compilation state of script.
|
|
"GetAllScopesDetails", # Needs to hit a break point.
|
|
"GetFrameCount", # Needs to hit a break point.
|
|
"GetFrameDetails", # Needs to hit a break point.
|
|
"GetRootNaN", # Needs to be bootstrapping.
|
|
"GetScopeCount", # Needs to hit a break point.
|
|
"GetScopeDetails", # Needs to hit a break point.
|
|
"GetStepInPositions", # Needs to hit a break point.
|
|
"GetTemplateField", # Needs a {Function,Object}TemplateInfo.
|
|
"GetThreadCount", # Needs to hit a break point.
|
|
"GetThreadDetails", # Needs to hit a break point.
|
|
"IsAccessAllowedForObserver", # Needs access-check-required object.
|
|
"UnblockConcurrentRecompilation" # Needs --block-concurrent-recompilation.
|
|
]
|
|
|
|
|
|
# Definitions used in CUSTOM_KNOWN_GOOD_INPUT below.
|
|
_BREAK_ITERATOR = (
|
|
"%GetImplFromInitializedIntlObject(new Intl.v8BreakIterator())")
|
|
_COLLATOR = "%GetImplFromInitializedIntlObject(new Intl.Collator('en-US'))"
|
|
_DATETIME_FORMAT = (
|
|
"%GetImplFromInitializedIntlObject(new Intl.DateTimeFormat('en-US'))")
|
|
_NUMBER_FORMAT = (
|
|
"%GetImplFromInitializedIntlObject(new Intl.NumberFormat('en-US'))")
|
|
|
|
|
|
# Custom definitions for function input that does not throw.
|
|
# Format: "FunctionName": ["arg0", "arg1", ..., argslength].
|
|
# None means "fall back to autodetected value".
|
|
CUSTOM_KNOWN_GOOD_INPUT = {
|
|
"Apply": ["function() {}", None, None, None, None, None],
|
|
"ArrayBufferSliceImpl": [None, None, 0, None],
|
|
"ArrayConcat": ["[1, 'a']", None],
|
|
"BreakIteratorAdoptText": [_BREAK_ITERATOR, None, None],
|
|
"BreakIteratorBreakType": [_BREAK_ITERATOR, None],
|
|
"BreakIteratorCurrent": [_BREAK_ITERATOR, None],
|
|
"BreakIteratorFirst": [_BREAK_ITERATOR, None],
|
|
"BreakIteratorNext": [_BREAK_ITERATOR, None],
|
|
"CompileString": [None, "false", None],
|
|
"CreateBreakIterator": ["'en-US'", "{type: 'string'}", None, None],
|
|
"CreateJSFunctionProxy": [None, "function() {}", None, None, None],
|
|
"CreatePrivateSymbol": ["\"foo\"", None],
|
|
"CreateSymbol": ["\"foo\"", None],
|
|
"DateParseString": [None, "new Array(8)", None],
|
|
"DefineOrRedefineAccessorProperty": [None, None, "function() {}",
|
|
"function() {}", 2, None],
|
|
"FunctionBindArguments": [None, None, "undefined", None, None],
|
|
"GetBreakLocations": [None, 0, None],
|
|
"GetDefaultReceiver": ["function() {}", None],
|
|
"GetImplFromInitializedIntlObject": ["new Intl.NumberFormat('en-US')", None],
|
|
"InternalCompare": [_COLLATOR, None, None, None],
|
|
"InternalDateFormat": [_DATETIME_FORMAT, None, None],
|
|
"InternalDateParse": [_DATETIME_FORMAT, None, None],
|
|
"InternalNumberFormat": [_NUMBER_FORMAT, None, None],
|
|
"InternalNumberParse": [_NUMBER_FORMAT, None, None],
|
|
"IsSloppyModeFunction": ["function() {}", None],
|
|
"LoadMutableDouble": ["{foo: 1.2}", None, None],
|
|
"NewObjectFromBound": ["(function() {}).bind({})", None],
|
|
"NumberToRadixString": [None, "2", None],
|
|
"ParseJson": ["\"{}\"", 1],
|
|
"RegExpExecMultiple": [None, None, "['a']", "['a']", None],
|
|
"SetAccessorProperty": [None, None, "undefined", "undefined", None, None],
|
|
"SetIteratorInitialize": [None, None, "2", None],
|
|
"SetDebugEventListener": ["undefined", None, None],
|
|
"SetFunctionBreakPoint": [None, 200, None, None],
|
|
"StringBuilderConcat": ["[1, 2, 3]", 3, None, None],
|
|
"StringBuilderJoin": ["['a', 'b']", 4, None, None],
|
|
"StringMatch": [None, None, "['a', 'b']", None],
|
|
"StringNormalize": [None, 2, None],
|
|
"StringReplaceGlobalRegExpWithString": [None, None, None, "['a']", None],
|
|
"TypedArrayInitialize": [None, 6, "new ArrayBuffer(8)", None, 4, None],
|
|
"TypedArrayInitializeFromArrayLike": [None, 6, None, None, None],
|
|
"TypedArraySetFastCases": [None, None, "0", None],
|
|
}
|
|
|
|
|
|
# Types of arguments that cannot be generated in a JavaScript testcase.
|
|
NON_JS_TYPES = [
|
|
"Code", "Context", "FixedArray", "FunctionTemplateInfo",
|
|
"JSFunctionResultCache", "JSMessageObject", "Map", "ScopeInfo",
|
|
"SharedFunctionInfo"]
|
|
|
|
|
|
class Generator(object):
|
|
|
|
def RandomVariable(self, varname, vartype, simple):
|
|
if simple:
|
|
return self._Variable(varname, self.GENERATORS[vartype][0])
|
|
return self.GENERATORS[vartype][1](self, varname,
|
|
self.DEFAULT_RECURSION_BUDGET)
|
|
|
|
@staticmethod
|
|
def IsTypeSupported(typename):
|
|
return typename in Generator.GENERATORS
|
|
|
|
USUAL_SUSPECT_PROPERTIES = ["size", "length", "byteLength", "__proto__",
|
|
"prototype", "0", "1", "-1"]
|
|
DEFAULT_RECURSION_BUDGET = 2
|
|
PROXY_TRAPS = """{
|
|
getOwnPropertyDescriptor: function(name) {
|
|
return {value: function() {}, configurable: true, writable: true,
|
|
enumerable: true};
|
|
},
|
|
getPropertyDescriptor: function(name) {
|
|
return {value: function() {}, configurable: true, writable: true,
|
|
enumerable: true};
|
|
},
|
|
getOwnPropertyNames: function() { return []; },
|
|
getPropertyNames: function() { return []; },
|
|
defineProperty: function(name, descriptor) {},
|
|
delete: function(name) { return true; },
|
|
fix: function() {}
|
|
}"""
|
|
|
|
def _Variable(self, name, value, fallback=None):
|
|
args = { "name": name, "value": value, "fallback": fallback }
|
|
if fallback:
|
|
wrapper = "try { %%s } catch(e) { var %(name)s = %(fallback)s; }" % args
|
|
else:
|
|
wrapper = "%s"
|
|
return [wrapper % ("var %(name)s = %(value)s;" % args)]
|
|
|
|
def _Boolean(self, name, recursion_budget):
|
|
return self._Variable(name, random.choice(["true", "false"]))
|
|
|
|
def _Oddball(self, name, recursion_budget):
|
|
return self._Variable(name,
|
|
random.choice(["true", "false", "undefined", "null"]))
|
|
|
|
def _StrictMode(self, name, recursion_budget):
|
|
return self._Variable(name, random.choice([0, 1]))
|
|
|
|
def _Int32(self, name, recursion_budget=0):
|
|
die = random.random()
|
|
if die < 0.5:
|
|
value = random.choice([-3, -1, 0, 1, 2, 10, 515, 0x3fffffff, 0x7fffffff,
|
|
0x40000000, -0x40000000, -0x80000000])
|
|
elif die < 0.75:
|
|
value = random.randint(-1000, 1000)
|
|
else:
|
|
value = random.randint(-0x80000000, 0x7fffffff)
|
|
return self._Variable(name, value)
|
|
|
|
def _Uint32(self, name, recursion_budget=0):
|
|
die = random.random()
|
|
if die < 0.5:
|
|
value = random.choice([0, 1, 2, 3, 4, 8, 0x3fffffff, 0x40000000,
|
|
0x7fffffff, 0xffffffff])
|
|
elif die < 0.75:
|
|
value = random.randint(0, 1000)
|
|
else:
|
|
value = random.randint(0, 0xffffffff)
|
|
return self._Variable(name, value)
|
|
|
|
def _Smi(self, name, recursion_budget):
|
|
die = random.random()
|
|
if die < 0.5:
|
|
value = random.choice([-5, -1, 0, 1, 2, 3, 0x3fffffff, -0x40000000])
|
|
elif die < 0.75:
|
|
value = random.randint(-1000, 1000)
|
|
else:
|
|
value = random.randint(-0x40000000, 0x3fffffff)
|
|
return self._Variable(name, value)
|
|
|
|
def _Number(self, name, recursion_budget):
|
|
die = random.random()
|
|
if die < 0.5:
|
|
return self._Smi(name, recursion_budget)
|
|
elif die < 0.6:
|
|
value = random.choice(["Infinity", "-Infinity", "NaN", "-0",
|
|
"1.7976931348623157e+308", # Max value.
|
|
"2.2250738585072014e-308", # Min value.
|
|
"4.9406564584124654e-324"]) # Min subnormal.
|
|
else:
|
|
value = random.lognormvariate(0, 15)
|
|
return self._Variable(name, value)
|
|
|
|
def _RawRandomString(self, minlength=0, maxlength=100,
|
|
alphabet=string.ascii_letters):
|
|
length = random.randint(minlength, maxlength)
|
|
result = ""
|
|
for i in xrange(length):
|
|
result += random.choice(alphabet)
|
|
return result
|
|
|
|
def _SeqString(self, name, recursion_budget):
|
|
s1 = self._RawRandomString(1, 5)
|
|
s2 = self._RawRandomString(1, 5)
|
|
# 'foo' + 'bar'
|
|
return self._Variable(name, "\"%s\" + \"%s\"" % (s1, s2))
|
|
|
|
def _SeqTwoByteString(self, name):
|
|
s1 = self._RawRandomString(1, 5)
|
|
s2 = self._RawRandomString(1, 5)
|
|
# 'foo' + unicode + 'bar'
|
|
return self._Variable(name, "\"%s\" + \"\\2082\" + \"%s\"" % (s1, s2))
|
|
|
|
def _SlicedString(self, name):
|
|
s = self._RawRandomString(20, 30)
|
|
# 'ffoo12345678901234567890'.substr(1)
|
|
return self._Variable(name, "\"%s\".substr(1)" % s)
|
|
|
|
def _ConsString(self, name):
|
|
s1 = self._RawRandomString(8, 15)
|
|
s2 = self._RawRandomString(8, 15)
|
|
# 'foo12345' + (function() { return 'bar12345';})()
|
|
return self._Variable(name,
|
|
"\"%s\" + (function() { return \"%s\";})()" % (s1, s2))
|
|
|
|
def _InternalizedString(self, name):
|
|
return self._Variable(name, "\"%s\"" % self._RawRandomString(0, 20))
|
|
|
|
def _String(self, name, recursion_budget):
|
|
die = random.random()
|
|
if die < 0.5:
|
|
string = random.choice(self.USUAL_SUSPECT_PROPERTIES)
|
|
return self._Variable(name, "\"%s\"" % string)
|
|
elif die < 0.6:
|
|
number_name = name + "_number"
|
|
result = self._Number(number_name, recursion_budget)
|
|
return result + self._Variable(name, "\"\" + %s" % number_name)
|
|
elif die < 0.7:
|
|
return self._SeqString(name, recursion_budget)
|
|
elif die < 0.8:
|
|
return self._ConsString(name)
|
|
elif die < 0.9:
|
|
return self._InternalizedString(name)
|
|
else:
|
|
return self._SlicedString(name)
|
|
|
|
def _Symbol(self, name, recursion_budget):
|
|
raw_string_name = name + "_1"
|
|
result = self._String(raw_string_name, recursion_budget)
|
|
return result + self._Variable(name, "Symbol(%s)" % raw_string_name)
|
|
|
|
def _Name(self, name, recursion_budget):
|
|
if random.random() < 0.2:
|
|
return self._Symbol(name, recursion_budget)
|
|
return self._String(name, recursion_budget)
|
|
|
|
def _JSValue(self, name, recursion_budget):
|
|
die = random.random()
|
|
raw_name = name + "_1"
|
|
if die < 0.33:
|
|
result = self._String(raw_name, recursion_budget)
|
|
return result + self._Variable(name, "new String(%s)" % raw_name)
|
|
elif die < 0.66:
|
|
result = self._Boolean(raw_name, recursion_budget)
|
|
return result + self._Variable(name, "new Boolean(%s)" % raw_name)
|
|
else:
|
|
result = self._Number(raw_name, recursion_budget)
|
|
return result + self._Variable(name, "new Number(%s)" % raw_name)
|
|
|
|
def _RawRandomPropertyName(self):
|
|
if random.random() < 0.5:
|
|
return random.choice(self.USUAL_SUSPECT_PROPERTIES)
|
|
return self._RawRandomString(0, 10)
|
|
|
|
def _AddProperties(self, name, result, recursion_budget):
|
|
propcount = random.randint(0, 3)
|
|
propname = None
|
|
for i in range(propcount):
|
|
die = random.random()
|
|
if die < 0.5:
|
|
propname = "%s_prop%d" % (name, i)
|
|
result += self._Name(propname, recursion_budget - 1)
|
|
else:
|
|
propname = "\"%s\"" % self._RawRandomPropertyName()
|
|
propvalue_name = "%s_val%d" % (name, i)
|
|
result += self._Object(propvalue_name, recursion_budget - 1)
|
|
result.append("try { %s[%s] = %s; } catch (e) {}" %
|
|
(name, propname, propvalue_name))
|
|
if random.random() < 0.2 and propname:
|
|
# Force the object to slow mode.
|
|
result.append("delete %s[%s];" % (name, propname))
|
|
|
|
def _RandomElementIndex(self, element_name, result):
|
|
if random.random() < 0.5:
|
|
return random.randint(-1000, 1000)
|
|
result += self._Smi(element_name, 0)
|
|
return element_name
|
|
|
|
def _AddElements(self, name, result, recursion_budget):
|
|
elementcount = random.randint(0, 3)
|
|
for i in range(elementcount):
|
|
element_name = "%s_idx%d" % (name, i)
|
|
index = self._RandomElementIndex(element_name, result)
|
|
value_name = "%s_elt%d" % (name, i)
|
|
result += self._Object(value_name, recursion_budget - 1)
|
|
result.append("try { %s[%s] = %s; } catch(e) {}" %
|
|
(name, index, value_name))
|
|
|
|
def _AddAccessors(self, name, result, recursion_budget):
|
|
accessorcount = random.randint(0, 3)
|
|
for i in range(accessorcount):
|
|
propname = self._RawRandomPropertyName()
|
|
what = random.choice(["get", "set"])
|
|
function_name = "%s_access%d" % (name, i)
|
|
result += self._PlainFunction(function_name, recursion_budget - 1)
|
|
result.append("try { Object.defineProperty(%s, \"%s\", {%s: %s}); } "
|
|
"catch (e) {}" % (name, propname, what, function_name))
|
|
|
|
def _PlainArray(self, name, recursion_budget):
|
|
die = random.random()
|
|
if die < 0.5:
|
|
literal = random.choice(["[]", "[1, 2]", "[1.5, 2.5]",
|
|
"['a', 'b', 1, true]"])
|
|
return self._Variable(name, literal)
|
|
else:
|
|
new = random.choice(["", "new "])
|
|
length = random.randint(0, 101000)
|
|
return self._Variable(name, "%sArray(%d)" % (new, length))
|
|
|
|
def _PlainObject(self, name, recursion_budget):
|
|
die = random.random()
|
|
if die < 0.67:
|
|
literal_propcount = random.randint(0, 3)
|
|
properties = []
|
|
result = []
|
|
for i in range(literal_propcount):
|
|
propname = self._RawRandomPropertyName()
|
|
propvalue_name = "%s_lit%d" % (name, i)
|
|
result += self._Object(propvalue_name, recursion_budget - 1)
|
|
properties.append("\"%s\": %s" % (propname, propvalue_name))
|
|
return result + self._Variable(name, "{%s}" % ", ".join(properties))
|
|
else:
|
|
return self._Variable(name, "new Object()")
|
|
|
|
def _JSArray(self, name, recursion_budget):
|
|
result = self._PlainArray(name, recursion_budget)
|
|
self._AddAccessors(name, result, recursion_budget)
|
|
self._AddProperties(name, result, recursion_budget)
|
|
self._AddElements(name, result, recursion_budget)
|
|
return result
|
|
|
|
def _RawRandomBufferLength(self):
|
|
if random.random() < 0.2:
|
|
return random.choice([0, 1, 8, 0x40000000, 0x80000000])
|
|
return random.randint(0, 1000)
|
|
|
|
def _JSArrayBuffer(self, name, recursion_budget):
|
|
length = self._RawRandomBufferLength()
|
|
return self._Variable(name, "new ArrayBuffer(%d)" % length)
|
|
|
|
def _JSDataView(self, name, recursion_budget):
|
|
buffer_name = name + "_buffer"
|
|
result = self._JSArrayBuffer(buffer_name, recursion_budget)
|
|
args = [buffer_name]
|
|
die = random.random()
|
|
if die < 0.67:
|
|
offset = self._RawRandomBufferLength()
|
|
args.append("%d" % offset)
|
|
if die < 0.33:
|
|
length = self._RawRandomBufferLength()
|
|
args.append("%d" % length)
|
|
result += self._Variable(name, "new DataView(%s)" % ", ".join(args),
|
|
fallback="new DataView(new ArrayBuffer(8))")
|
|
return result
|
|
|
|
def _JSDate(self, name, recursion_budget):
|
|
die = random.random()
|
|
if die < 0.25:
|
|
return self._Variable(name, "new Date()")
|
|
elif die < 0.5:
|
|
ms_name = name + "_ms"
|
|
result = self._Number(ms_name, recursion_budget)
|
|
return result + self._Variable(name, "new Date(%s)" % ms_name)
|
|
elif die < 0.75:
|
|
str_name = name + "_str"
|
|
month = random.choice(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
|
|
"Aug", "Sep", "Oct", "Nov", "Dec"])
|
|
day = random.randint(1, 28)
|
|
year = random.randint(1900, 2100)
|
|
hour = random.randint(0, 23)
|
|
minute = random.randint(0, 59)
|
|
second = random.randint(0, 59)
|
|
str_value = ("\"%s %s, %s %s:%s:%s\"" %
|
|
(month, day, year, hour, minute, second))
|
|
result = self._Variable(str_name, str_value)
|
|
return result + self._Variable(name, "new Date(%s)" % str_name)
|
|
else:
|
|
components = tuple(map(lambda x: "%s_%s" % (name, x),
|
|
["y", "m", "d", "h", "min", "s", "ms"]))
|
|
return ([j for i in map(self._Int32, components) for j in i] +
|
|
self._Variable(name, "new Date(%s)" % ", ".join(components)))
|
|
|
|
def _PlainFunction(self, name, recursion_budget):
|
|
result_name = "result"
|
|
body = ["function() {"]
|
|
body += self._Object(result_name, recursion_budget - 1)
|
|
body.append("return result;\n}")
|
|
return self._Variable(name, "%s" % "\n".join(body))
|
|
|
|
def _JSFunction(self, name, recursion_budget):
|
|
result = self._PlainFunction(name, recursion_budget)
|
|
self._AddAccessors(name, result, recursion_budget)
|
|
self._AddProperties(name, result, recursion_budget)
|
|
self._AddElements(name, result, recursion_budget)
|
|
return result
|
|
|
|
def _JSFunctionProxy(self, name, recursion_budget):
|
|
# TODO(jkummerow): Revisit this as the Proxy implementation evolves.
|
|
return self._Variable(name, "Proxy.createFunction(%s, function() {})" %
|
|
self.PROXY_TRAPS)
|
|
|
|
def _JSGeneratorObject(self, name, recursion_budget):
|
|
# TODO(jkummerow): Be more creative here?
|
|
return self._Variable(name, "(function*() { yield 1; })()")
|
|
|
|
def _JSMap(self, name, recursion_budget, weak=""):
|
|
result = self._Variable(name, "new %sMap()" % weak)
|
|
num_entries = random.randint(0, 3)
|
|
for i in range(num_entries):
|
|
key_name = "%s_k%d" % (name, i)
|
|
value_name = "%s_v%d" % (name, i)
|
|
if weak:
|
|
result += self._JSObject(key_name, recursion_budget - 1)
|
|
else:
|
|
result += self._Object(key_name, recursion_budget - 1)
|
|
result += self._Object(value_name, recursion_budget - 1)
|
|
result.append("%s.set(%s, %s)" % (name, key_name, value_name))
|
|
return result
|
|
|
|
def _JSMapIterator(self, name, recursion_budget):
|
|
map_name = name + "_map"
|
|
result = self._JSMap(map_name, recursion_budget)
|
|
iterator_type = random.choice(['keys', 'values', 'entries'])
|
|
return (result + self._Variable(name, "%s.%s()" %
|
|
(map_name, iterator_type)))
|
|
|
|
def _JSProxy(self, name, recursion_budget):
|
|
# TODO(jkummerow): Revisit this as the Proxy implementation evolves.
|
|
return self._Variable(name, "Proxy.create(%s)" % self.PROXY_TRAPS)
|
|
|
|
def _JSRegExp(self, name, recursion_budget):
|
|
flags = random.choice(["", "g", "i", "m", "gi"])
|
|
string = "a(b|c)*a" # TODO(jkummerow): Be more creative here?
|
|
ctor = random.choice(["/%s/%s", "new RegExp(\"%s\", \"%s\")"])
|
|
return self._Variable(name, ctor % (string, flags))
|
|
|
|
def _JSSet(self, name, recursion_budget, weak=""):
|
|
result = self._Variable(name, "new %sSet()" % weak)
|
|
num_entries = random.randint(0, 3)
|
|
for i in range(num_entries):
|
|
element_name = "%s_e%d" % (name, i)
|
|
if weak:
|
|
result += self._JSObject(element_name, recursion_budget - 1)
|
|
else:
|
|
result += self._Object(element_name, recursion_budget - 1)
|
|
result.append("%s.add(%s)" % (name, element_name))
|
|
return result
|
|
|
|
def _JSSetIterator(self, name, recursion_budget):
|
|
set_name = name + "_set"
|
|
result = self._JSSet(set_name, recursion_budget)
|
|
iterator_type = random.choice(['values', 'entries'])
|
|
return (result + self._Variable(name, "%s.%s()" %
|
|
(set_name, iterator_type)))
|
|
|
|
def _JSTypedArray(self, name, recursion_budget):
|
|
arraytype = random.choice(["Int8", "Int16", "Int32", "Uint8", "Uint16",
|
|
"Uint32", "Float32", "Float64", "Uint8Clamped"])
|
|
ctor_type = random.randint(0, 3)
|
|
if ctor_type == 0:
|
|
length = random.randint(0, 1000)
|
|
return self._Variable(name, "new %sArray(%d)" % (arraytype, length),
|
|
fallback="new %sArray(8)" % arraytype)
|
|
elif ctor_type == 1:
|
|
input_name = name + "_typedarray"
|
|
result = self._JSTypedArray(input_name, recursion_budget - 1)
|
|
return (result +
|
|
self._Variable(name, "new %sArray(%s)" % (arraytype, input_name),
|
|
fallback="new %sArray(8)" % arraytype))
|
|
elif ctor_type == 2:
|
|
arraylike_name = name + "_arraylike"
|
|
result = self._JSObject(arraylike_name, recursion_budget - 1)
|
|
length = random.randint(0, 1000)
|
|
result.append("try { %s.length = %d; } catch(e) {}" %
|
|
(arraylike_name, length))
|
|
return (result +
|
|
self._Variable(name,
|
|
"new %sArray(%s)" % (arraytype, arraylike_name),
|
|
fallback="new %sArray(8)" % arraytype))
|
|
else:
|
|
die = random.random()
|
|
buffer_name = name + "_buffer"
|
|
args = [buffer_name]
|
|
result = self._JSArrayBuffer(buffer_name, recursion_budget)
|
|
if die < 0.67:
|
|
offset_name = name + "_offset"
|
|
args.append(offset_name)
|
|
result += self._Int32(offset_name)
|
|
if die < 0.33:
|
|
length_name = name + "_length"
|
|
args.append(length_name)
|
|
result += self._Int32(length_name)
|
|
return (result +
|
|
self._Variable(name,
|
|
"new %sArray(%s)" % (arraytype, ", ".join(args)),
|
|
fallback="new %sArray(8)" % arraytype))
|
|
|
|
def _JSArrayBufferView(self, name, recursion_budget):
|
|
if random.random() < 0.4:
|
|
return self._JSDataView(name, recursion_budget)
|
|
else:
|
|
return self._JSTypedArray(name, recursion_budget)
|
|
|
|
def _JSWeakCollection(self, name, recursion_budget):
|
|
ctor = random.choice([self._JSMap, self._JSSet])
|
|
return ctor(name, recursion_budget, weak="Weak")
|
|
|
|
def _PropertyDetails(self, name, recursion_budget):
|
|
# TODO(jkummerow): Be more clever here?
|
|
return self._Int32(name)
|
|
|
|
def _JSObject(self, name, recursion_budget):
|
|
die = random.random()
|
|
if die < 0.4:
|
|
function = random.choice([self._PlainObject, self._PlainArray,
|
|
self._PlainFunction])
|
|
elif die < 0.5:
|
|
return self._Variable(name, "this") # Global object.
|
|
else:
|
|
function = random.choice([self._JSArrayBuffer, self._JSDataView,
|
|
self._JSDate, self._JSFunctionProxy,
|
|
self._JSGeneratorObject, self._JSMap,
|
|
self._JSMapIterator, self._JSRegExp,
|
|
self._JSSet, self._JSSetIterator,
|
|
self._JSTypedArray, self._JSValue,
|
|
self._JSWeakCollection])
|
|
result = function(name, recursion_budget)
|
|
self._AddAccessors(name, result, recursion_budget)
|
|
self._AddProperties(name, result, recursion_budget)
|
|
self._AddElements(name, result, recursion_budget)
|
|
return result
|
|
|
|
def _JSReceiver(self, name, recursion_budget):
|
|
if random.random() < 0.9: return self._JSObject(name, recursion_budget)
|
|
return self._JSProxy(name, recursion_budget)
|
|
|
|
def _HeapObject(self, name, recursion_budget):
|
|
die = random.random()
|
|
if die < 0.9: return self._JSReceiver(name, recursion_budget)
|
|
elif die < 0.95: return self._Oddball(name, recursion_budget)
|
|
else: return self._Name(name, recursion_budget)
|
|
|
|
def _Object(self, name, recursion_budget):
|
|
if recursion_budget <= 0:
|
|
function = random.choice([self._Oddball, self._Number, self._Name,
|
|
self._JSValue, self._JSRegExp])
|
|
return function(name, recursion_budget)
|
|
if random.random() < 0.2:
|
|
return self._Smi(name, recursion_budget)
|
|
return self._HeapObject(name, recursion_budget)
|
|
|
|
GENERATORS = {
|
|
"Boolean": ["true", _Boolean],
|
|
"HeapObject": ["new Object()", _HeapObject],
|
|
"Int32": ["32", _Int32],
|
|
"JSArray": ["new Array()", _JSArray],
|
|
"JSArrayBuffer": ["new ArrayBuffer(8)", _JSArrayBuffer],
|
|
"JSArrayBufferView": ["new Int32Array(2)", _JSArrayBufferView],
|
|
"JSDataView": ["new DataView(new ArrayBuffer(24))", _JSDataView],
|
|
"JSDate": ["new Date()", _JSDate],
|
|
"JSFunction": ["function() {}", _JSFunction],
|
|
"JSFunctionProxy": ["Proxy.createFunction({}, function() {})",
|
|
_JSFunctionProxy],
|
|
"JSGeneratorObject": ["(function*(){ yield 1; })()", _JSGeneratorObject],
|
|
"JSMap": ["new Map()", _JSMap],
|
|
"JSMapIterator": ["new Map().entries()", _JSMapIterator],
|
|
"JSObject": ["new Object()", _JSObject],
|
|
"JSProxy": ["Proxy.create({})", _JSProxy],
|
|
"JSReceiver": ["new Object()", _JSReceiver],
|
|
"JSRegExp": ["/ab/g", _JSRegExp],
|
|
"JSSet": ["new Set()", _JSSet],
|
|
"JSSetIterator": ["new Set().values()", _JSSetIterator],
|
|
"JSTypedArray": ["new Int32Array(2)", _JSTypedArray],
|
|
"JSValue": ["new String('foo')", _JSValue],
|
|
"JSWeakCollection": ["new WeakMap()", _JSWeakCollection],
|
|
"Name": ["\"name\"", _Name],
|
|
"Number": ["1.5", _Number],
|
|
"Object": ["new Object()", _Object],
|
|
"PropertyDetails": ["513", _PropertyDetails],
|
|
"SeqOneByteString": ["\"seq 1-byte\"", _SeqString],
|
|
"SeqString": ["\"seqstring\"", _SeqString],
|
|
"SeqTwoByteString": ["\"seq \\u2082-byte\"", _SeqTwoByteString],
|
|
"Smi": ["1", _Smi],
|
|
"StrictMode": ["1", _StrictMode],
|
|
"String": ["\"foo\"", _String],
|
|
"Symbol": ["Symbol(\"symbol\")", _Symbol],
|
|
"Uint32": ["32", _Uint32],
|
|
}
|
|
|
|
|
|
class ArgParser(object):
|
|
def __init__(self, regex, ctor):
|
|
self.regex = regex
|
|
self.ArgCtor = ctor
|
|
|
|
|
|
class Arg(object):
|
|
def __init__(self, typename, varname, index):
|
|
self.type = typename
|
|
self.name = "_%s" % varname
|
|
self.index = index
|
|
|
|
|
|
class Function(object):
|
|
def __init__(self, match):
|
|
self.name = match.group(1)
|
|
self.argslength = -1
|
|
self.args = {}
|
|
self.inline = ""
|
|
|
|
handle_arg_parser = ArgParser(
|
|
re.compile("^\s*CONVERT_ARG_HANDLE_CHECKED\((\w+), (\w+), (\d+)\)"),
|
|
lambda match: Arg(match.group(1), match.group(2), int(match.group(3))))
|
|
|
|
plain_arg_parser = ArgParser(
|
|
re.compile("^\s*CONVERT_ARG_CHECKED\((\w+), (\w+), (\d+)\)"),
|
|
lambda match: Arg(match.group(1), match.group(2), int(match.group(3))))
|
|
|
|
number_handle_arg_parser = ArgParser(
|
|
re.compile("^\s*CONVERT_NUMBER_ARG_HANDLE_CHECKED\((\w+), (\d+)\)"),
|
|
lambda match: Arg("Number", match.group(1), int(match.group(2))))
|
|
|
|
smi_arg_parser = ArgParser(
|
|
re.compile("^\s*CONVERT_SMI_ARG_CHECKED\((\w+), (\d+)\)"),
|
|
lambda match: Arg("Smi", match.group(1), int(match.group(2))))
|
|
|
|
double_arg_parser = ArgParser(
|
|
re.compile("^\s*CONVERT_DOUBLE_ARG_CHECKED\((\w+), (\d+)\)"),
|
|
lambda match: Arg("Number", match.group(1), int(match.group(2))))
|
|
|
|
number_arg_parser = ArgParser(
|
|
re.compile(
|
|
"^\s*CONVERT_NUMBER_CHECKED\(\w+, (\w+), (\w+), args\[(\d+)\]\)"),
|
|
lambda match: Arg(match.group(2), match.group(1), int(match.group(3))))
|
|
|
|
strict_mode_arg_parser = ArgParser(
|
|
re.compile("^\s*CONVERT_STRICT_MODE_ARG_CHECKED\((\w+), (\d+)\)"),
|
|
lambda match: Arg("StrictMode", match.group(1), int(match.group(2))))
|
|
|
|
boolean_arg_parser = ArgParser(
|
|
re.compile("^\s*CONVERT_BOOLEAN_ARG_CHECKED\((\w+), (\d+)\)"),
|
|
lambda match: Arg("Boolean", match.group(1), int(match.group(2))))
|
|
|
|
property_details_parser = ArgParser(
|
|
re.compile("^\s*CONVERT_PROPERTY_DETAILS_CHECKED\((\w+), (\d+)\)"),
|
|
lambda match: Arg("PropertyDetails", match.group(1), int(match.group(2))))
|
|
|
|
arg_parsers = [handle_arg_parser, plain_arg_parser, number_handle_arg_parser,
|
|
smi_arg_parser,
|
|
double_arg_parser, number_arg_parser, strict_mode_arg_parser,
|
|
boolean_arg_parser, property_details_parser]
|
|
|
|
def SetArgsLength(self, match):
|
|
self.argslength = int(match.group(1))
|
|
|
|
def TryParseArg(self, line):
|
|
for parser in Function.arg_parsers:
|
|
match = parser.regex.match(line)
|
|
if match:
|
|
arg = parser.ArgCtor(match)
|
|
self.args[arg.index] = arg
|
|
return True
|
|
return False
|
|
|
|
def Filename(self):
|
|
return "%s.js" % self.name.lower()
|
|
|
|
def __str__(self):
|
|
s = [self.name, "("]
|
|
argcount = self.argslength
|
|
if argcount < 0:
|
|
print("WARNING: unknown argslength for function %s" % self.name)
|
|
if self.args:
|
|
argcount = max([self.args[i].index + 1 for i in self.args])
|
|
else:
|
|
argcount = 0
|
|
for i in range(argcount):
|
|
if i > 0: s.append(", ")
|
|
s.append(self.args[i].type if i in self.args else "<unknown>")
|
|
s.append(")")
|
|
return "".join(s)
|
|
|
|
|
|
class Macro(object):
|
|
def __init__(self, match):
|
|
self.name = match.group(1)
|
|
self.args = [s.strip() for s in match.group(2).split(",")]
|
|
self.lines = []
|
|
self.indentation = 0
|
|
self.AddLine(match.group(3))
|
|
|
|
def AddLine(self, line):
|
|
if not line: return
|
|
if not self.lines:
|
|
# This is the first line, detect indentation.
|
|
self.indentation = len(line) - len(line.lstrip())
|
|
line = line.rstrip("\\\n ")
|
|
if not line: return
|
|
assert len(line[:self.indentation].strip()) == 0, \
|
|
("expected whitespace: '%s', full line: '%s'" %
|
|
(line[:self.indentation], line))
|
|
line = line[self.indentation:]
|
|
if not line: return
|
|
self.lines.append(line + "\n")
|
|
|
|
def Finalize(self):
|
|
for arg in self.args:
|
|
pattern = re.compile(r"(##|\b)%s(##|\b)" % arg)
|
|
for i in range(len(self.lines)):
|
|
self.lines[i] = re.sub(pattern, "%%(%s)s" % arg, self.lines[i])
|
|
|
|
def FillIn(self, arg_values):
|
|
filler = {}
|
|
assert len(arg_values) == len(self.args)
|
|
for i in range(len(self.args)):
|
|
filler[self.args[i]] = arg_values[i]
|
|
result = []
|
|
for line in self.lines:
|
|
result.append(line % filler)
|
|
return result
|
|
|
|
|
|
# Parses HEADERFILENAME to find out which runtime functions are "inline".
|
|
def FindInlineRuntimeFunctions():
|
|
inline_functions = []
|
|
with open(HEADERFILENAME, "r") as f:
|
|
inline_list = "#define INLINE_FUNCTION_LIST(F) \\\n"
|
|
inline_function = re.compile(r"^\s*F\((\w+), \d+, \d+\)\s*\\?")
|
|
mode = "SEARCHING"
|
|
for line in f:
|
|
if mode == "ACTIVE":
|
|
match = inline_function.match(line)
|
|
if match:
|
|
inline_functions.append(match.group(1))
|
|
if not line.endswith("\\\n"):
|
|
mode = "SEARCHING"
|
|
elif mode == "SEARCHING":
|
|
if line == inline_list:
|
|
mode = "ACTIVE"
|
|
return inline_functions
|
|
|
|
|
|
def ReadFileAndExpandMacros(filename):
|
|
found_macros = {}
|
|
expanded_lines = []
|
|
with open(filename, "r") as f:
|
|
found_macro = None
|
|
for line in f:
|
|
if found_macro is not None:
|
|
found_macro.AddLine(line)
|
|
if not line.endswith("\\\n"):
|
|
found_macro.Finalize()
|
|
found_macro = None
|
|
continue
|
|
|
|
match = MACRO.match(line)
|
|
if match:
|
|
found_macro = Macro(match)
|
|
if found_macro.name in EXPAND_MACROS:
|
|
found_macros[found_macro.name] = found_macro
|
|
else:
|
|
found_macro = None
|
|
continue
|
|
|
|
match = FIRST_WORD.match(line)
|
|
if match:
|
|
first_word = match.group(1)
|
|
if first_word in found_macros:
|
|
MACRO_CALL = re.compile("%s\(([^)]*)\)" % first_word)
|
|
match = MACRO_CALL.match(line)
|
|
assert match
|
|
args = [s.strip() for s in match.group(1).split(",")]
|
|
expanded_lines += found_macros[first_word].FillIn(args)
|
|
continue
|
|
|
|
expanded_lines.append(line)
|
|
return expanded_lines
|
|
|
|
|
|
# Detects runtime functions by parsing FILENAME.
|
|
def FindRuntimeFunctions():
|
|
inline_functions = FindInlineRuntimeFunctions()
|
|
functions = []
|
|
expanded_lines = ReadFileAndExpandMacros(FILENAME)
|
|
function = None
|
|
partial_line = ""
|
|
for line in expanded_lines:
|
|
# Multi-line definition support, ignoring macros.
|
|
if line.startswith("RUNTIME_FUNCTION") and not line.endswith("{\n"):
|
|
if line.endswith("\\\n"): continue
|
|
partial_line = line.rstrip()
|
|
continue
|
|
if partial_line:
|
|
partial_line += " " + line.strip()
|
|
if partial_line.endswith("{"):
|
|
line = partial_line
|
|
partial_line = ""
|
|
else:
|
|
continue
|
|
|
|
match = FUNCTION.match(line)
|
|
if match:
|
|
function = Function(match)
|
|
if function.name in inline_functions:
|
|
function.inline = "_"
|
|
continue
|
|
if function is None: continue
|
|
|
|
match = ARGSLENGTH.match(line)
|
|
if match:
|
|
function.SetArgsLength(match)
|
|
continue
|
|
|
|
if function.TryParseArg(line):
|
|
continue
|
|
|
|
if line == FUNCTIONEND:
|
|
if function is not None:
|
|
functions.append(function)
|
|
function = None
|
|
return functions
|
|
|
|
|
|
# Hack: This must have the same fields as class Function above, because the
|
|
# two are used polymorphically in RunFuzzer(). We could use inheritance...
|
|
class Builtin(object):
|
|
def __init__(self, match):
|
|
self.name = match.group(1)
|
|
args = match.group(2)
|
|
self.argslength = 0 if args == "" else args.count(",") + 1
|
|
self.inline = ""
|
|
self.args = {}
|
|
if self.argslength > 0:
|
|
args = args.split(",")
|
|
for i in range(len(args)):
|
|
# a = args[i].strip() # TODO: filter out /* comments */ first.
|
|
a = ""
|
|
self.args[i] = Arg("Object", a, i)
|
|
|
|
def __str__(self):
|
|
return "%s(%d)" % (self.name, self.argslength)
|
|
|
|
|
|
def FindJSBuiltins():
|
|
PATH = "src"
|
|
fileslist = []
|
|
for (root, dirs, files) in os.walk(PATH):
|
|
for f in files:
|
|
if f.endswith(".js"):
|
|
fileslist.append(os.path.join(root, f))
|
|
builtins = []
|
|
regexp = re.compile("^function (\w+)\s*\((.*?)\) {")
|
|
matches = 0
|
|
for filename in fileslist:
|
|
with open(filename, "r") as f:
|
|
file_contents = f.read()
|
|
file_contents = js2c.ExpandInlineMacros(file_contents)
|
|
lines = file_contents.split("\n")
|
|
partial_line = ""
|
|
for line in lines:
|
|
if line.startswith("function") and not '{' in line:
|
|
partial_line += line.rstrip()
|
|
continue
|
|
if partial_line:
|
|
partial_line += " " + line.strip()
|
|
if '{' in line:
|
|
line = partial_line
|
|
partial_line = ""
|
|
else:
|
|
continue
|
|
match = regexp.match(line)
|
|
if match:
|
|
builtins.append(Builtin(match))
|
|
return builtins
|
|
|
|
|
|
# Classifies runtime functions.
|
|
def ClassifyFunctions(functions):
|
|
# Can be fuzzed with a JavaScript testcase.
|
|
js_fuzzable_functions = []
|
|
# We have enough information to fuzz these, but they need inputs that
|
|
# cannot be created or passed around in JavaScript.
|
|
cctest_fuzzable_functions = []
|
|
# This script does not have enough information about these.
|
|
unknown_functions = []
|
|
|
|
types = {}
|
|
for f in functions:
|
|
if f.name in BLACKLISTED:
|
|
continue
|
|
decision = js_fuzzable_functions
|
|
custom = CUSTOM_KNOWN_GOOD_INPUT.get(f.name, None)
|
|
if f.argslength < 0:
|
|
# Unknown length -> give up unless there's a custom definition.
|
|
if custom and custom[-1] is not None:
|
|
f.argslength = custom[-1]
|
|
assert len(custom) == f.argslength + 1, \
|
|
("%s: last custom definition must be argslength" % f.name)
|
|
else:
|
|
decision = unknown_functions
|
|
else:
|
|
if custom:
|
|
# Any custom definitions must match the known argslength.
|
|
assert len(custom) == f.argslength + 1, \
|
|
("%s should have %d custom definitions but has %d" %
|
|
(f.name, f.argslength + 1, len(custom)))
|
|
for i in range(f.argslength):
|
|
if custom and custom[i] is not None:
|
|
# All good, there's a custom definition.
|
|
pass
|
|
elif not i in f.args:
|
|
# No custom definition and no parse result -> give up.
|
|
decision = unknown_functions
|
|
else:
|
|
t = f.args[i].type
|
|
if t in NON_JS_TYPES:
|
|
decision = cctest_fuzzable_functions
|
|
else:
|
|
assert Generator.IsTypeSupported(t), \
|
|
("type generator not found for %s, function: %s" % (t, f))
|
|
decision.append(f)
|
|
return (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions)
|
|
|
|
|
|
def _GetKnownGoodArgs(function, generator):
|
|
custom_input = CUSTOM_KNOWN_GOOD_INPUT.get(function.name, None)
|
|
definitions = []
|
|
argslist = []
|
|
for i in range(function.argslength):
|
|
if custom_input and custom_input[i] is not None:
|
|
name = "arg%d" % i
|
|
definitions.append("var %s = %s;" % (name, custom_input[i]))
|
|
else:
|
|
arg = function.args[i]
|
|
name = arg.name
|
|
definitions += generator.RandomVariable(name, arg.type, simple=True)
|
|
argslist.append(name)
|
|
return (definitions, argslist)
|
|
|
|
|
|
def _GenerateTestcase(function, definitions, argslist, throws):
|
|
s = ["// Copyright 2014 the V8 project authors. All rights reserved.",
|
|
"// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY",
|
|
"// Flags: --allow-natives-syntax --harmony"] + definitions
|
|
call = "%%%s%s(%s);" % (function.inline, function.name, ", ".join(argslist))
|
|
if throws:
|
|
s.append("try {")
|
|
s.append(call);
|
|
s.append("} catch(e) {}")
|
|
else:
|
|
s.append(call)
|
|
testcase = "\n".join(s)
|
|
return testcase
|
|
|
|
|
|
def GenerateJSTestcaseForFunction(function):
|
|
gen = Generator()
|
|
(definitions, argslist) = _GetKnownGoodArgs(function, gen)
|
|
testcase = _GenerateTestcase(function, definitions, argslist,
|
|
function.name in THROWS)
|
|
path = os.path.join(BASEPATH, function.Filename())
|
|
with open(path, "w") as f:
|
|
f.write("%s\n" % testcase)
|
|
|
|
|
|
def GenerateTestcases(functions):
|
|
shutil.rmtree(BASEPATH) # Re-generate everything.
|
|
os.makedirs(BASEPATH)
|
|
for f in functions:
|
|
GenerateJSTestcaseForFunction(f)
|
|
|
|
|
|
def _SaveFileName(save_path, process_id, save_file_index):
|
|
return "%s/fuzz_%d_%d.js" % (save_path, process_id, save_file_index)
|
|
|
|
|
|
def _GetFuzzableRuntimeFunctions():
|
|
functions = FindRuntimeFunctions()
|
|
(js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \
|
|
ClassifyFunctions(functions)
|
|
return js_fuzzable_functions
|
|
|
|
|
|
FUZZ_TARGET_LISTS = {
|
|
"runtime": _GetFuzzableRuntimeFunctions,
|
|
"builtins": FindJSBuiltins,
|
|
}
|
|
|
|
|
|
def RunFuzzer(process_id, options, stop_running):
|
|
MAX_SLEEP_TIME = 0.1
|
|
INITIAL_SLEEP_TIME = 0.001
|
|
SLEEP_TIME_FACTOR = 1.25
|
|
base_file_name = "/dev/shm/runtime_fuzz_%d" % process_id
|
|
test_file_name = "%s.js" % base_file_name
|
|
stderr_file_name = "%s.out" % base_file_name
|
|
save_file_index = 0
|
|
while os.path.exists(_SaveFileName(options.save_path, process_id,
|
|
save_file_index)):
|
|
save_file_index += 1
|
|
|
|
targets = FUZZ_TARGET_LISTS[options.fuzz_target]()
|
|
try:
|
|
for i in range(options.num_tests):
|
|
if stop_running.is_set(): break
|
|
function = None
|
|
while function is None or function.argslength == 0:
|
|
function = random.choice(targets)
|
|
args = []
|
|
definitions = []
|
|
gen = Generator()
|
|
for i in range(function.argslength):
|
|
arg = function.args[i]
|
|
argname = "arg%d%s" % (i, arg.name)
|
|
args.append(argname)
|
|
definitions += gen.RandomVariable(argname, arg.type, simple=False)
|
|
testcase = _GenerateTestcase(function, definitions, args, True)
|
|
with open(test_file_name, "w") as f:
|
|
f.write("%s\n" % testcase)
|
|
with open("/dev/null", "w") as devnull:
|
|
with open(stderr_file_name, "w") as stderr:
|
|
process = subprocess.Popen(
|
|
[options.binary, "--allow-natives-syntax", "--harmony",
|
|
"--enable-slow-asserts", test_file_name],
|
|
stdout=devnull, stderr=stderr)
|
|
end_time = time.time() + options.timeout
|
|
timed_out = False
|
|
exit_code = None
|
|
sleep_time = INITIAL_SLEEP_TIME
|
|
while exit_code is None:
|
|
if time.time() >= end_time:
|
|
# Kill the process and wait for it to exit.
|
|
os.kill(process.pid, signal.SIGTERM)
|
|
exit_code = process.wait()
|
|
timed_out = True
|
|
else:
|
|
exit_code = process.poll()
|
|
time.sleep(sleep_time)
|
|
sleep_time = sleep_time * SLEEP_TIME_FACTOR
|
|
if sleep_time > MAX_SLEEP_TIME:
|
|
sleep_time = MAX_SLEEP_TIME
|
|
if exit_code != 0 and not timed_out:
|
|
oom = False
|
|
with open(stderr_file_name, "r") as stderr:
|
|
for line in stderr:
|
|
if line.strip() == "# Allocation failed - process out of memory":
|
|
oom = True
|
|
break
|
|
if oom: continue
|
|
save_name = _SaveFileName(options.save_path, process_id,
|
|
save_file_index)
|
|
shutil.copyfile(test_file_name, save_name)
|
|
save_file_index += 1
|
|
except KeyboardInterrupt:
|
|
stop_running.set()
|
|
finally:
|
|
if os.path.exists(test_file_name):
|
|
os.remove(test_file_name)
|
|
if os.path.exists(stderr_file_name):
|
|
os.remove(stderr_file_name)
|
|
|
|
|
|
def BuildOptionParser():
|
|
usage = """Usage: %%prog [options] ACTION
|
|
|
|
where ACTION can be:
|
|
|
|
info Print diagnostic info.
|
|
check Check that runtime functions can be parsed as expected, and that
|
|
test cases exist.
|
|
generate Parse source code for runtime functions, and auto-generate
|
|
test cases for them. Warning: this will nuke and re-create
|
|
%(path)s.
|
|
fuzz Generate fuzz tests, run them, save those that crashed (see options).
|
|
""" % {"path": os.path.relpath(BASEPATH)}
|
|
|
|
o = optparse.OptionParser(usage=usage)
|
|
o.add_option("--binary", default="out/x64.debug/d8",
|
|
help="d8 binary used for running fuzz tests (default: %default)")
|
|
o.add_option("--fuzz-target", default="runtime",
|
|
help="Set of functions targeted by fuzzing. Allowed values: "
|
|
"%s (default: %%default)" % ", ".join(FUZZ_TARGET_LISTS))
|
|
o.add_option("-n", "--num-tests", default=1000, type="int",
|
|
help="Number of fuzz tests to generate per worker process"
|
|
" (default: %default)")
|
|
o.add_option("--save-path", default="~/runtime_fuzz_output",
|
|
help="Path to directory where failing tests will be stored"
|
|
" (default: %default)")
|
|
o.add_option("--timeout", default=20, type="int",
|
|
help="Timeout for each fuzz test (in seconds, default:"
|
|
"%default)")
|
|
return o
|
|
|
|
|
|
def ProcessOptions(options, args):
|
|
options.save_path = os.path.expanduser(options.save_path)
|
|
if options.fuzz_target not in FUZZ_TARGET_LISTS:
|
|
print("Invalid fuzz target: %s" % options.fuzz_target)
|
|
return False
|
|
if len(args) != 1 or args[0] == "help":
|
|
return False
|
|
return True
|
|
|
|
|
|
def Main():
|
|
parser = BuildOptionParser()
|
|
(options, args) = parser.parse_args()
|
|
|
|
if not ProcessOptions(options, args):
|
|
parser.print_help()
|
|
return 1
|
|
action = args[0]
|
|
|
|
functions = FindRuntimeFunctions()
|
|
(js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \
|
|
ClassifyFunctions(functions)
|
|
builtins = FindJSBuiltins()
|
|
|
|
if action == "test":
|
|
print("put your temporary debugging code here")
|
|
return 0
|
|
|
|
if action == "info":
|
|
print("%d functions total; js_fuzzable_functions: %d, "
|
|
"cctest_fuzzable_functions: %d, unknown_functions: %d"
|
|
% (len(functions), len(js_fuzzable_functions),
|
|
len(cctest_fuzzable_functions), len(unknown_functions)))
|
|
print("%d JavaScript builtins" % len(builtins))
|
|
print("unknown functions:")
|
|
for f in unknown_functions:
|
|
print(f)
|
|
return 0
|
|
|
|
if action == "check":
|
|
errors = 0
|
|
|
|
def CheckCount(actual, expected, description):
|
|
if len(actual) != expected:
|
|
print("Expected to detect %d %s, but found %d." % (
|
|
expected, description, len(actual)))
|
|
print("If this change is intentional, please update the expectations"
|
|
" at the top of %s." % THIS_SCRIPT)
|
|
return 1
|
|
return 0
|
|
|
|
errors += CheckCount(functions, EXPECTED_FUNCTION_COUNT,
|
|
"functions in total")
|
|
errors += CheckCount(js_fuzzable_functions, EXPECTED_FUZZABLE_COUNT,
|
|
"JavaScript-fuzzable functions")
|
|
errors += CheckCount(cctest_fuzzable_functions, EXPECTED_CCTEST_COUNT,
|
|
"cctest-fuzzable functions")
|
|
errors += CheckCount(unknown_functions, EXPECTED_UNKNOWN_COUNT,
|
|
"functions with incomplete type information")
|
|
errors += CheckCount(builtins, EXPECTED_BUILTINS_COUNT,
|
|
"JavaScript builtins")
|
|
|
|
def CheckTestcasesExisting(functions):
|
|
errors = 0
|
|
for f in functions:
|
|
if not os.path.isfile(os.path.join(BASEPATH, f.Filename())):
|
|
print("Missing testcase for %s, please run '%s generate'" %
|
|
(f.name, THIS_SCRIPT))
|
|
errors += 1
|
|
files = filter(lambda filename: not filename.startswith("."),
|
|
os.listdir(BASEPATH))
|
|
if (len(files) != len(functions)):
|
|
unexpected_files = set(files) - set([f.Filename() for f in functions])
|
|
for f in unexpected_files:
|
|
print("Unexpected testcase: %s" % os.path.join(BASEPATH, f))
|
|
errors += 1
|
|
print("Run '%s generate' to automatically clean these up."
|
|
% THIS_SCRIPT)
|
|
return errors
|
|
|
|
errors += CheckTestcasesExisting(js_fuzzable_functions)
|
|
|
|
def CheckNameClashes(runtime_functions, builtins):
|
|
errors = 0
|
|
runtime_map = {}
|
|
for f in runtime_functions:
|
|
runtime_map[f.name] = 1
|
|
for b in builtins:
|
|
if b.name in runtime_map:
|
|
print("Builtin/Runtime_Function name clash: %s" % b.name)
|
|
errors += 1
|
|
return errors
|
|
|
|
errors += CheckNameClashes(functions, builtins)
|
|
|
|
if errors > 0:
|
|
return 1
|
|
print("Generated runtime tests: all good.")
|
|
return 0
|
|
|
|
if action == "generate":
|
|
GenerateTestcases(js_fuzzable_functions)
|
|
return 0
|
|
|
|
if action == "fuzz":
|
|
processes = []
|
|
if not os.path.isdir(options.save_path):
|
|
os.makedirs(options.save_path)
|
|
stop_running = multiprocessing.Event()
|
|
for i in range(multiprocessing.cpu_count()):
|
|
args = (i, options, stop_running)
|
|
p = multiprocessing.Process(target=RunFuzzer, args=args)
|
|
p.start()
|
|
processes.append(p)
|
|
try:
|
|
for i in range(len(processes)):
|
|
processes[i].join()
|
|
except KeyboardInterrupt:
|
|
stop_running.set()
|
|
for i in range(len(processes)):
|
|
processes[i].join()
|
|
return 0
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(Main())
|