| Index: tools/generate-runtime-tests.py
|
| diff --git a/tools/generate-runtime-tests.py b/tools/generate-runtime-tests.py
|
| index 6cda22277b8924391f2d2e94c4b98e4290244236..e4489303270dbae94d66d075e9dc2dc9f5d759e9 100755
|
| --- a/tools/generate-runtime-tests.py
|
| +++ b/tools/generate-runtime-tests.py
|
| @@ -3,879 +3,32 @@
|
| # 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(".*DCHECK\(.*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",
|
| + "ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION",
|
| + "FIXED_TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION",
|
| "RUNTIME_UNARY_MATH",
|
| + "TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION",
|
| ]
|
| -# 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 = 431
|
| -EXPECTED_FUZZABLE_COUNT = 330
|
| -EXPECTED_CCTEST_COUNT = 7
|
| -EXPECTED_UNKNOWN_COUNT = 17
|
| -EXPECTED_BUILTINS_COUNT = 806
|
| -
|
| -
|
| -# Don't call these at all.
|
| -BLACKLISTED = [
|
| - "Abort", # Kills the process.
|
| - "AbortJS", # Kills the process.
|
| - "CompileForOnStackReplacement", # Riddled with DCHECK.
|
| - "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",
|
| -
|
| - # TODO(danno): Fix these internal function that are only callable form stubs
|
| - # and un-blacklist them!
|
| - "NumberToString",
|
| - "RxegExpConstructResult",
|
| - "RegExpExec",
|
| - "StringAdd",
|
| - "SubString",
|
| - "StringCompare",
|
| - "StringCharCodeAt",
|
| - "GetFromCache",
|
| -
|
| - # Compilation
|
| - "CompileUnoptimized",
|
| - "CompileOptimized",
|
| - "TryInstallOptimizedCode",
|
| - "NotifyDeoptimized",
|
| - "NotifyStubFailure",
|
| -
|
| - # Utilities
|
| - "AllocateInNewSpace",
|
| - "AllocateInTargetSpace",
|
| - "AllocateHeapNumber",
|
| - "LoadMutableDouble",
|
| - "NumberToSmi",
|
| - "NumberToStringSkipCache",
|
| -
|
| - "FunctionBindArguments",
|
| - "NewSloppyArguments",
|
| - "NewStrictArguments",
|
| -
|
| - # Harmony
|
| - "CreateJSGeneratorObject",
|
| - "SuspendJSGeneratorObject",
|
| - "ResumeJSGeneratorObject",
|
| - "ThrowGeneratorStateError",
|
| -
|
| - # Arrays
|
| - "ArrayConstructor",
|
| - "InternalArrayConstructor",
|
| - "NormalizeElements",
|
| -
|
| - # Literals
|
| - "MaterializeRegExpLiteral",
|
| - "CreateObjectLiteral",
|
| - "CreateArrayLiteral",
|
| - "CreateArrayLiteralStubBailout",
|
| -
|
| - # Statements
|
| - "NewClosure",
|
| - "NewClosureFromStubFailure",
|
| - "NewObject",
|
| - "NewObjectWithAllocationSite",
|
| - "FinalizeInstanceSize",
|
| - "Throw",
|
| - "ReThrow",
|
| - "ThrowReferenceError",
|
| - "ThrowNotDateError",
|
| - "StackGuard",
|
| - "Interrupt",
|
| - "PromoteScheduledException",
|
| -
|
| - # Contexts
|
| - "NewGlobalContext",
|
| - "NewFunctionContext",
|
| - "PushWithContext",
|
| - "PushCatchContext",
|
| - "PushBlockContext",
|
| - "PushModuleContext",
|
| - "DeleteLookupSlot",
|
| - "LoadLookupSlot",
|
| - "LoadLookupSlotNoReferenceError",
|
| - "StoreLookupSlot",
|
| -
|
| - # Declarations
|
| - "DeclareGlobals",
|
| - "DeclareModules",
|
| - "DeclareContextSlot",
|
| - "InitializeConstGlobal",
|
| - "InitializeConstContextSlot",
|
| -
|
| - # Eval
|
| - "ResolvePossiblyDirectEval",
|
| -
|
| - # Maths
|
| - "MathPowSlow",
|
| - "MathPowRT",
|
| -
|
| - # Internal
|
| - "InternalSetPrototype",
|
| -]
|
| -
|
| -
|
| -# 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 = {
|
| - "AddNamedProperty": [None, "\"bla\"", None, None, None],
|
| - "AddPropertyForTemplate": [None, 10, None, None, None],
|
| - "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],
|
| - "CreatePrivateOwnSymbol": ["\"foo\"", None],
|
| - "CreateSymbol": ["\"foo\"", None],
|
| - "DateParseString": [None, "new Array(8)", None],
|
| - "DefineAccessorPropertyUnchecked": [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],
|
| - "DefineApiAccessorProperty": [None, None, "undefined", "undefined", None, None],
|
| - "SetIteratorInitialize": [None, None, "2", None],
|
| - "SetDebugEventListener": ["undefined", None, None],
|
| - "SetFunctionBreakPoint": [None, 218, 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],
|
| - "FunctionIsArrow": ["() => null", 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):
|
| @@ -917,26 +70,6 @@ class Macro(object):
|
| 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 = []
|
| @@ -976,7 +109,6 @@ def ReadFileAndExpandMacros(filename):
|
|
|
| # Detects runtime functions by parsing FILENAME.
|
| def FindRuntimeFunctions():
|
| - inline_functions = FindInlineRuntimeFunctions()
|
| functions = []
|
| expanded_lines = ReadFileAndExpandMacros(FILENAME)
|
| function = None
|
| @@ -998,19 +130,9 @@ def FindRuntimeFunctions():
|
| 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)
|
| @@ -1018,34 +140,19 @@ def FindRuntimeFunctions():
|
| 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():
|
| +def FindJSNatives():
|
| 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 = []
|
| + natives = []
|
| regexp = re.compile("^function (\w+)\s*\((.*?)\) {")
|
| matches = 0
|
| for filename in fileslist:
|
| @@ -1067,351 +174,28 @@ def FindJSBuiltins():
|
| 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 --harmony-proxies"
|
| - ] + 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",
|
| - "--harmony-proxies", "--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
|
| + natives.append(Builtin(match))
|
| + return natives
|
|
|
|
|
| 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
|
| + natives = FindJSNatives()
|
| + errors = 0
|
| + runtime_map = {}
|
| + for f in functions:
|
| + runtime_map[f.name] = 1
|
| + for b in natives:
|
| + if b.name in runtime_map:
|
| + print("JS_Native/Runtime_Function name clash: %s" % b.name)
|
| + errors += 1
|
|
|
| - if action == "generate":
|
| - GenerateTestcases(js_fuzzable_functions)
|
| - return 0
|
| + if errors > 0:
|
| + return 1
|
| + print("Runtime/Natives name clashes: checked %d/%d functions, all good." %
|
| + (len(functions), len(natives)))
|
| + 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())
|
|
|