2 # Copyright 2014 the V8 project authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
20 FILENAME = "src/runtime.cc"
21 HEADERFILENAME = "src/runtime.h"
22 FUNCTION = re.compile("^RUNTIME_FUNCTION\(Runtime_(\w+)")
23 ARGSLENGTH = re.compile(".*ASSERT\(.*args\.length\(\) == (\d+)\);")
25 MACRO = re.compile(r"^#define ([^ ]+)\(([^)]*)\) *([^\\]*)\\?\n$")
26 FIRST_WORD = re.compile("^\s*(.*?)[\s({\[]")
28 WORKSPACE = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), ".."))
29 BASEPATH = os.path.join(WORKSPACE, "test", "mjsunit", "runtime-gen")
30 THIS_SCRIPT = os.path.relpath(sys.argv[0])
32 # Expand these macros, they define further runtime functions.
39 # TODO(jkummerow): We could also whitelist the following macros, but the
40 # functions they define are so trivial that it's unclear how much benefit
42 # ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION
43 # FIXED_TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION
44 # TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION
46 # Counts of functions in each detection state. These are used to assert
47 # that the parser doesn't bit-rot. Change the values as needed when you add,
48 # remove or change runtime functions, but make sure we don't lose our ability
50 EXPECTED_FUNCTION_COUNT = 373
51 EXPECTED_FUZZABLE_COUNT = 335
52 EXPECTED_CCTEST_COUNT = 6
53 EXPECTED_UNKNOWN_COUNT = 10
54 EXPECTED_BUILTINS_COUNT = 918
57 # Don't call these at all.
59 "Abort", # Kills the process.
60 "AbortJS", # Kills the process.
61 "CompileForOnStackReplacement", # Riddled with ASSERTs.
62 "IS_VAR", # Not implemented in the runtime.
63 "ListNatives", # Not available in Release mode.
64 "SetAllocationTimeout", # Too slow for fuzzing.
65 "SystemBreak", # Kills (int3) the process.
67 # These are weird. They violate some invariants when called after
69 "DisableAccessChecks",
72 # The current LiveEdit implementation relies on and messes with internals
73 # in ways that makes it fundamentally unfuzzable :-(
74 "DebugGetLoadedScripts",
75 "DebugSetScriptSource",
76 "LiveEditFindSharedFunctionInfosForScript",
77 "LiveEditFunctionSourceUpdated",
78 "LiveEditGatherCompileInfo",
79 "LiveEditPatchFunctionPositions",
80 "LiveEditReplaceFunctionCode",
81 "LiveEditReplaceRefToNestedFunction",
82 "LiveEditReplaceScript",
83 "LiveEditRestartFrame",
84 "SetScriptBreakPoint",
86 # TODO(jkummerow): Fix these and un-blacklist them!
87 "CreateDateTimeFormat",
92 # These will always throw.
94 "CheckExecutionState", # Needs to hit a break point.
95 "CheckIsBootstrapping", # Needs to be bootstrapping.
96 "DebugEvaluate", # Needs to hit a break point.
97 "DebugEvaluateGlobal", # Needs to hit a break point.
98 "DebugIndexedInterceptorElementValue", # Needs an indexed interceptor.
99 "DebugNamedInterceptorPropertyValue", # Needs a named interceptor.
100 "DebugSetScriptSource", # Checks compilation state of script.
101 "GetAllScopesDetails", # Needs to hit a break point.
102 "GetFrameCount", # Needs to hit a break point.
103 "GetFrameDetails", # Needs to hit a break point.
104 "GetRootNaN", # Needs to be bootstrapping.
105 "GetScopeCount", # Needs to hit a break point.
106 "GetScopeDetails", # Needs to hit a break point.
107 "GetStepInPositions", # Needs to hit a break point.
108 "GetTemplateField", # Needs a {Function,Object}TemplateInfo.
109 "GetThreadCount", # Needs to hit a break point.
110 "GetThreadDetails", # Needs to hit a break point.
111 "IsAccessAllowedForObserver", # Needs access-check-required object.
112 "UnblockConcurrentRecompilation" # Needs --block-concurrent-recompilation.
116 # Definitions used in CUSTOM_KNOWN_GOOD_INPUT below.
118 "%GetImplFromInitializedIntlObject(new Intl.v8BreakIterator())")
119 _COLLATOR = "%GetImplFromInitializedIntlObject(new Intl.Collator('en-US'))"
121 "%GetImplFromInitializedIntlObject(new Intl.DateTimeFormat('en-US'))")
123 "%GetImplFromInitializedIntlObject(new Intl.NumberFormat('en-US'))")
126 # Custom definitions for function input that does not throw.
127 # Format: "FunctionName": ["arg0", "arg1", ..., argslength].
128 # None means "fall back to autodetected value".
129 CUSTOM_KNOWN_GOOD_INPUT = {
130 "Apply": ["function() {}", None, None, None, None, None],
131 "ArrayBufferSliceImpl": [None, None, 0, None],
132 "ArrayConcat": ["[1, 'a']", None],
133 "BreakIteratorAdoptText": [_BREAK_ITERATOR, None, None],
134 "BreakIteratorBreakType": [_BREAK_ITERATOR, None],
135 "BreakIteratorCurrent": [_BREAK_ITERATOR, None],
136 "BreakIteratorFirst": [_BREAK_ITERATOR, None],
137 "BreakIteratorNext": [_BREAK_ITERATOR, None],
138 "CompileString": [None, "false", None],
139 "CreateBreakIterator": ["'en-US'", "{type: 'string'}", None, None],
140 "CreateJSFunctionProxy": [None, "function() {}", None, None, None],
141 "CreatePrivateSymbol": ["\"foo\"", None],
142 "CreateSymbol": ["\"foo\"", None],
143 "DateParseString": [None, "new Array(8)", None],
144 "DefineOrRedefineAccessorProperty": [None, None, "function() {}",
145 "function() {}", 2, None],
146 "FunctionBindArguments": [None, None, "undefined", None, None],
147 "GetBreakLocations": [None, 0, None],
148 "GetDefaultReceiver": ["function() {}", None],
149 "GetImplFromInitializedIntlObject": ["new Intl.NumberFormat('en-US')", None],
150 "InternalCompare": [_COLLATOR, None, None, None],
151 "InternalDateFormat": [_DATETIME_FORMAT, None, None],
152 "InternalDateParse": [_DATETIME_FORMAT, None, None],
153 "InternalNumberFormat": [_NUMBER_FORMAT, None, None],
154 "InternalNumberParse": [_NUMBER_FORMAT, None, None],
155 "IsSloppyModeFunction": ["function() {}", None],
156 "LoadMutableDouble": ["{foo: 1.2}", None, None],
157 "NewObjectFromBound": ["(function() {}).bind({})", None],
158 "NumberToRadixString": [None, "2", None],
159 "ParseJson": ["\"{}\"", 1],
160 "RegExpExecMultiple": [None, None, "['a']", "['a']", None],
161 "SetAccessorProperty": [None, None, "undefined", "undefined", None, None,
163 "SetIteratorInitialize": [None, None, "2", None],
164 "SetDebugEventListener": ["undefined", None, None],
165 "SetFunctionBreakPoint": [None, 200, None, None],
166 "StringBuilderConcat": ["[1, 2, 3]", 3, None, None],
167 "StringBuilderJoin": ["['a', 'b']", 4, None, None],
168 "StringMatch": [None, None, "['a', 'b']", None],
169 "StringNormalize": [None, 2, None],
170 "StringReplaceGlobalRegExpWithString": [None, None, None, "['a']", None],
171 "TypedArrayInitialize": [None, 6, "new ArrayBuffer(8)", None, 4, None],
172 "TypedArrayInitializeFromArrayLike": [None, 6, None, None, None],
173 "TypedArraySetFastCases": [None, None, "0", None],
177 # Types of arguments that cannot be generated in a JavaScript testcase.
179 "Code", "Context", "FixedArray", "FunctionTemplateInfo",
180 "JSFunctionResultCache", "JSMessageObject", "Map", "ScopeInfo",
181 "SharedFunctionInfo"]
184 class Generator(object):
186 def RandomVariable(self, varname, vartype, simple):
188 return self._Variable(varname, self.GENERATORS[vartype][0])
189 return self.GENERATORS[vartype][1](self, varname,
190 self.DEFAULT_RECURSION_BUDGET)
193 def IsTypeSupported(typename):
194 return typename in Generator.GENERATORS
196 USUAL_SUSPECT_PROPERTIES = ["size", "length", "byteLength", "__proto__",
197 "prototype", "0", "1", "-1"]
198 DEFAULT_RECURSION_BUDGET = 2
200 getOwnPropertyDescriptor: function(name) {
201 return {value: function() {}, configurable: true, writable: true,
204 getPropertyDescriptor: function(name) {
205 return {value: function() {}, configurable: true, writable: true,
208 getOwnPropertyNames: function() { return []; },
209 getPropertyNames: function() { return []; },
210 defineProperty: function(name, descriptor) {},
211 delete: function(name) { return true; },
215 def _Variable(self, name, value, fallback=None):
216 args = { "name": name, "value": value, "fallback": fallback }
218 wrapper = "try { %%s } catch(e) { var %(name)s = %(fallback)s; }" % args
221 return [wrapper % ("var %(name)s = %(value)s;" % args)]
223 def _Boolean(self, name, recursion_budget):
224 return self._Variable(name, random.choice(["true", "false"]))
226 def _Oddball(self, name, recursion_budget):
227 return self._Variable(name,
228 random.choice(["true", "false", "undefined", "null"]))
230 def _StrictMode(self, name, recursion_budget):
231 return self._Variable(name, random.choice([0, 1]))
233 def _Int32(self, name, recursion_budget=0):
234 die = random.random()
236 value = random.choice([-3, -1, 0, 1, 2, 10, 515, 0x3fffffff, 0x7fffffff,
237 0x40000000, -0x40000000, -0x80000000])
239 value = random.randint(-1000, 1000)
241 value = random.randint(-0x80000000, 0x7fffffff)
242 return self._Variable(name, value)
244 def _Uint32(self, name, recursion_budget=0):
245 die = random.random()
247 value = random.choice([0, 1, 2, 3, 4, 8, 0x3fffffff, 0x40000000,
248 0x7fffffff, 0xffffffff])
250 value = random.randint(0, 1000)
252 value = random.randint(0, 0xffffffff)
253 return self._Variable(name, value)
255 def _Smi(self, name, recursion_budget):
256 die = random.random()
258 value = random.choice([-5, -1, 0, 1, 2, 3, 0x3fffffff, -0x40000000])
260 value = random.randint(-1000, 1000)
262 value = random.randint(-0x40000000, 0x3fffffff)
263 return self._Variable(name, value)
265 def _Number(self, name, recursion_budget):
266 die = random.random()
268 return self._Smi(name, recursion_budget)
270 value = random.choice(["Infinity", "-Infinity", "NaN", "-0",
271 "1.7976931348623157e+308", # Max value.
272 "2.2250738585072014e-308", # Min value.
273 "4.9406564584124654e-324"]) # Min subnormal.
275 value = random.lognormvariate(0, 15)
276 return self._Variable(name, value)
278 def _RawRandomString(self, minlength=0, maxlength=100,
279 alphabet=string.ascii_letters):
280 length = random.randint(minlength, maxlength)
282 for i in xrange(length):
283 result += random.choice(alphabet)
286 def _SeqString(self, name, recursion_budget):
287 s1 = self._RawRandomString(1, 5)
288 s2 = self._RawRandomString(1, 5)
290 return self._Variable(name, "\"%s\" + \"%s\"" % (s1, s2))
292 def _SeqTwoByteString(self, name):
293 s1 = self._RawRandomString(1, 5)
294 s2 = self._RawRandomString(1, 5)
295 # 'foo' + unicode + 'bar'
296 return self._Variable(name, "\"%s\" + \"\\2082\" + \"%s\"" % (s1, s2))
298 def _SlicedString(self, name):
299 s = self._RawRandomString(20, 30)
300 # 'ffoo12345678901234567890'.substr(1)
301 return self._Variable(name, "\"%s\".substr(1)" % s)
303 def _ConsString(self, name):
304 s1 = self._RawRandomString(8, 15)
305 s2 = self._RawRandomString(8, 15)
306 # 'foo12345' + (function() { return 'bar12345';})()
307 return self._Variable(name,
308 "\"%s\" + (function() { return \"%s\";})()" % (s1, s2))
310 def _InternalizedString(self, name):
311 return self._Variable(name, "\"%s\"" % self._RawRandomString(0, 20))
313 def _String(self, name, recursion_budget):
314 die = random.random()
316 string = random.choice(self.USUAL_SUSPECT_PROPERTIES)
317 return self._Variable(name, "\"%s\"" % string)
319 number_name = name + "_number"
320 result = self._Number(number_name, recursion_budget)
321 return result + self._Variable(name, "\"\" + %s" % number_name)
323 return self._SeqString(name, recursion_budget)
325 return self._ConsString(name)
327 return self._InternalizedString(name)
329 return self._SlicedString(name)
331 def _Symbol(self, name, recursion_budget):
332 raw_string_name = name + "_1"
333 result = self._String(raw_string_name, recursion_budget)
334 return result + self._Variable(name, "Symbol(%s)" % raw_string_name)
336 def _Name(self, name, recursion_budget):
337 if random.random() < 0.2:
338 return self._Symbol(name, recursion_budget)
339 return self._String(name, recursion_budget)
341 def _JSValue(self, name, recursion_budget):
342 die = random.random()
343 raw_name = name + "_1"
345 result = self._String(raw_name, recursion_budget)
346 return result + self._Variable(name, "new String(%s)" % raw_name)
348 result = self._Boolean(raw_name, recursion_budget)
349 return result + self._Variable(name, "new Boolean(%s)" % raw_name)
351 result = self._Number(raw_name, recursion_budget)
352 return result + self._Variable(name, "new Number(%s)" % raw_name)
354 def _RawRandomPropertyName(self):
355 if random.random() < 0.5:
356 return random.choice(self.USUAL_SUSPECT_PROPERTIES)
357 return self._RawRandomString(0, 10)
359 def _AddProperties(self, name, result, recursion_budget):
360 propcount = random.randint(0, 3)
362 for i in range(propcount):
363 die = random.random()
365 propname = "%s_prop%d" % (name, i)
366 result += self._Name(propname, recursion_budget - 1)
368 propname = "\"%s\"" % self._RawRandomPropertyName()
369 propvalue_name = "%s_val%d" % (name, i)
370 result += self._Object(propvalue_name, recursion_budget - 1)
371 result.append("try { %s[%s] = %s; } catch (e) {}" %
372 (name, propname, propvalue_name))
373 if random.random() < 0.2 and propname:
374 # Force the object to slow mode.
375 result.append("delete %s[%s];" % (name, propname))
377 def _RandomElementIndex(self, element_name, result):
378 if random.random() < 0.5:
379 return random.randint(-1000, 1000)
380 result += self._Smi(element_name, 0)
383 def _AddElements(self, name, result, recursion_budget):
384 elementcount = random.randint(0, 3)
385 for i in range(elementcount):
386 element_name = "%s_idx%d" % (name, i)
387 index = self._RandomElementIndex(element_name, result)
388 value_name = "%s_elt%d" % (name, i)
389 result += self._Object(value_name, recursion_budget - 1)
390 result.append("try { %s[%s] = %s; } catch(e) {}" %
391 (name, index, value_name))
393 def _AddAccessors(self, name, result, recursion_budget):
394 accessorcount = random.randint(0, 3)
395 for i in range(accessorcount):
396 propname = self._RawRandomPropertyName()
397 what = random.choice(["get", "set"])
398 function_name = "%s_access%d" % (name, i)
399 result += self._PlainFunction(function_name, recursion_budget - 1)
400 result.append("try { Object.defineProperty(%s, \"%s\", {%s: %s}); } "
401 "catch (e) {}" % (name, propname, what, function_name))
403 def _PlainArray(self, name, recursion_budget):
404 die = random.random()
406 literal = random.choice(["[]", "[1, 2]", "[1.5, 2.5]",
407 "['a', 'b', 1, true]"])
408 return self._Variable(name, literal)
410 new = random.choice(["", "new "])
411 length = random.randint(0, 101000)
412 return self._Variable(name, "%sArray(%d)" % (new, length))
414 def _PlainObject(self, name, recursion_budget):
415 die = random.random()
417 literal_propcount = random.randint(0, 3)
420 for i in range(literal_propcount):
421 propname = self._RawRandomPropertyName()
422 propvalue_name = "%s_lit%d" % (name, i)
423 result += self._Object(propvalue_name, recursion_budget - 1)
424 properties.append("\"%s\": %s" % (propname, propvalue_name))
425 return result + self._Variable(name, "{%s}" % ", ".join(properties))
427 return self._Variable(name, "new Object()")
429 def _JSArray(self, name, recursion_budget):
430 result = self._PlainArray(name, recursion_budget)
431 self._AddAccessors(name, result, recursion_budget)
432 self._AddProperties(name, result, recursion_budget)
433 self._AddElements(name, result, recursion_budget)
436 def _RawRandomBufferLength(self):
437 if random.random() < 0.2:
438 return random.choice([0, 1, 8, 0x40000000, 0x80000000])
439 return random.randint(0, 1000)
441 def _JSArrayBuffer(self, name, recursion_budget):
442 length = self._RawRandomBufferLength()
443 return self._Variable(name, "new ArrayBuffer(%d)" % length)
445 def _JSDataView(self, name, recursion_budget):
446 buffer_name = name + "_buffer"
447 result = self._JSArrayBuffer(buffer_name, recursion_budget)
449 die = random.random()
451 offset = self._RawRandomBufferLength()
452 args.append("%d" % offset)
454 length = self._RawRandomBufferLength()
455 args.append("%d" % length)
456 result += self._Variable(name, "new DataView(%s)" % ", ".join(args),
457 fallback="new DataView(new ArrayBuffer(8))")
460 def _Float32x4(self, name, recursion_budget):
465 return self._Variable(name, "SIMD.float32x4(%s, %s, %s, %s)" %(x, y, z, w))
467 def _Float64x2(self, name, recursion_budget):
470 return self._Variable(name, "SIMD.float64x2(%s, %s)" %(x, y))
472 def _Int32x4(self, name, recursion_budget):
473 x = random.randint(-0x40000000, 0x3fffffff)
474 y = random.randint(-0x40000000, 0x3fffffff)
475 z = random.randint(-0x40000000, 0x3fffffff)
476 w = random.randint(-0x40000000, 0x3fffffff)
477 return self._Variable(name, "SIMD.int32x4(%s, %s, %s, %s)" %(x, y, z, w))
479 def _JSDate(self, name, recursion_budget):
480 die = random.random()
482 return self._Variable(name, "new Date()")
484 ms_name = name + "_ms"
485 result = self._Number(ms_name, recursion_budget)
486 return result + self._Variable(name, "new Date(%s)" % ms_name)
488 str_name = name + "_str"
489 month = random.choice(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
490 "Aug", "Sep", "Oct", "Nov", "Dec"])
491 day = random.randint(1, 28)
492 year = random.randint(1900, 2100)
493 hour = random.randint(0, 23)
494 minute = random.randint(0, 59)
495 second = random.randint(0, 59)
496 str_value = ("\"%s %s, %s %s:%s:%s\"" %
497 (month, day, year, hour, minute, second))
498 result = self._Variable(str_name, str_value)
499 return result + self._Variable(name, "new Date(%s)" % str_name)
501 components = tuple(map(lambda x: "%s_%s" % (name, x),
502 ["y", "m", "d", "h", "min", "s", "ms"]))
503 return ([j for i in map(self._Int32, components) for j in i] +
504 self._Variable(name, "new Date(%s)" % ", ".join(components)))
506 def _PlainFunction(self, name, recursion_budget):
507 result_name = "result"
508 body = ["function() {"]
509 body += self._Object(result_name, recursion_budget - 1)
510 body.append("return result;\n}")
511 return self._Variable(name, "%s" % "\n".join(body))
513 def _JSFunction(self, name, recursion_budget):
514 result = self._PlainFunction(name, recursion_budget)
515 self._AddAccessors(name, result, recursion_budget)
516 self._AddProperties(name, result, recursion_budget)
517 self._AddElements(name, result, recursion_budget)
520 def _JSFunctionProxy(self, name, recursion_budget):
521 # TODO(jkummerow): Revisit this as the Proxy implementation evolves.
522 return self._Variable(name, "Proxy.createFunction(%s, function() {})" %
525 def _JSGeneratorObject(self, name, recursion_budget):
526 # TODO(jkummerow): Be more creative here?
527 return self._Variable(name, "(function*() { yield 1; })()")
529 def _JSMap(self, name, recursion_budget, weak=""):
530 result = self._Variable(name, "new %sMap()" % weak)
531 num_entries = random.randint(0, 3)
532 for i in range(num_entries):
533 key_name = "%s_k%d" % (name, i)
534 value_name = "%s_v%d" % (name, i)
536 result += self._JSObject(key_name, recursion_budget - 1)
538 result += self._Object(key_name, recursion_budget - 1)
539 result += self._Object(value_name, recursion_budget - 1)
540 result.append("%s.set(%s, %s)" % (name, key_name, value_name))
543 def _JSMapIterator(self, name, recursion_budget):
544 map_name = name + "_map"
545 result = self._JSMap(map_name, recursion_budget)
546 iterator_type = random.choice(['keys', 'values', 'entries'])
547 return (result + self._Variable(name, "%s.%s()" %
548 (map_name, iterator_type)))
550 def _JSProxy(self, name, recursion_budget):
551 # TODO(jkummerow): Revisit this as the Proxy implementation evolves.
552 return self._Variable(name, "Proxy.create(%s)" % self.PROXY_TRAPS)
554 def _JSRegExp(self, name, recursion_budget):
555 flags = random.choice(["", "g", "i", "m", "gi"])
556 string = "a(b|c)*a" # TODO(jkummerow): Be more creative here?
557 ctor = random.choice(["/%s/%s", "new RegExp(\"%s\", \"%s\")"])
558 return self._Variable(name, ctor % (string, flags))
560 def _JSSet(self, name, recursion_budget, weak=""):
561 result = self._Variable(name, "new %sSet()" % weak)
562 num_entries = random.randint(0, 3)
563 for i in range(num_entries):
564 element_name = "%s_e%d" % (name, i)
566 result += self._JSObject(element_name, recursion_budget - 1)
568 result += self._Object(element_name, recursion_budget - 1)
569 result.append("%s.add(%s)" % (name, element_name))
572 def _JSSetIterator(self, name, recursion_budget):
573 set_name = name + "_set"
574 result = self._JSSet(set_name, recursion_budget)
575 iterator_type = random.choice(['values', 'entries'])
576 return (result + self._Variable(name, "%s.%s()" %
577 (set_name, iterator_type)))
579 def _JSTypedArray(self, name, recursion_budget):
580 arraytype = random.choice(["Int8", "Int16", "Int32", "Uint8", "Uint16",
581 "Uint32", "Float32", "Float64", "Uint8Clamped"])
582 ctor_type = random.randint(0, 3)
584 length = random.randint(0, 1000)
585 return self._Variable(name, "new %sArray(%d)" % (arraytype, length),
586 fallback="new %sArray(8)" % arraytype)
588 input_name = name + "_typedarray"
589 result = self._JSTypedArray(input_name, recursion_budget - 1)
591 self._Variable(name, "new %sArray(%s)" % (arraytype, input_name),
592 fallback="new %sArray(8)" % arraytype))
594 arraylike_name = name + "_arraylike"
595 result = self._JSObject(arraylike_name, recursion_budget - 1)
596 length = random.randint(0, 1000)
597 result.append("try { %s.length = %d; } catch(e) {}" %
598 (arraylike_name, length))
601 "new %sArray(%s)" % (arraytype, arraylike_name),
602 fallback="new %sArray(8)" % arraytype))
604 die = random.random()
605 buffer_name = name + "_buffer"
607 result = self._JSArrayBuffer(buffer_name, recursion_budget)
609 offset_name = name + "_offset"
610 args.append(offset_name)
611 result += self._Int32(offset_name)
613 length_name = name + "_length"
614 args.append(length_name)
615 result += self._Int32(length_name)
618 "new %sArray(%s)" % (arraytype, ", ".join(args)),
619 fallback="new %sArray(8)" % arraytype))
621 def _JSArrayBufferView(self, name, recursion_budget):
622 if random.random() < 0.4:
623 return self._JSDataView(name, recursion_budget)
625 return self._JSTypedArray(name, recursion_budget)
627 def _JSWeakCollection(self, name, recursion_budget):
628 ctor = random.choice([self._JSMap, self._JSSet])
629 return ctor(name, recursion_budget, weak="Weak")
631 def _PropertyDetails(self, name, recursion_budget):
632 # TODO(jkummerow): Be more clever here?
633 return self._Int32(name)
635 def _JSObject(self, name, recursion_budget):
636 die = random.random()
638 function = random.choice([self._PlainObject, self._PlainArray,
639 self._PlainFunction])
641 return self._Variable(name, "this") # Global object.
643 function = random.choice([self._JSArrayBuffer, self._JSDataView,
644 self._JSDate, self._JSFunctionProxy,
645 self._JSGeneratorObject, self._JSMap,
646 self._JSMapIterator, self._JSRegExp,
647 self._JSSet, self._JSSetIterator,
648 self._JSTypedArray, self._JSValue,
649 self._JSWeakCollection])
650 result = function(name, recursion_budget)
651 self._AddAccessors(name, result, recursion_budget)
652 self._AddProperties(name, result, recursion_budget)
653 self._AddElements(name, result, recursion_budget)
656 def _JSReceiver(self, name, recursion_budget):
657 if random.random() < 0.9: return self._JSObject(name, recursion_budget)
658 return self._JSProxy(name, recursion_budget)
660 def _HeapObject(self, name, recursion_budget):
661 die = random.random()
662 if die < 0.9: return self._JSReceiver(name, recursion_budget)
663 elif die < 0.95: return self._Oddball(name, recursion_budget)
664 else: return self._Name(name, recursion_budget)
666 def _Object(self, name, recursion_budget):
667 if recursion_budget <= 0:
668 function = random.choice([self._Oddball, self._Number, self._Name,
669 self._JSValue, self._JSRegExp])
670 return function(name, recursion_budget)
671 if random.random() < 0.2:
672 return self._Smi(name, recursion_budget)
673 return self._HeapObject(name, recursion_budget)
676 "Boolean": ["true", _Boolean],
677 "HeapObject": ["new Object()", _HeapObject],
678 "Int32": ["32", _Int32],
679 "JSArray": ["new Array()", _JSArray],
680 "JSArrayBuffer": ["new ArrayBuffer(8)", _JSArrayBuffer],
681 "JSArrayBufferView": ["new Int32Array(2)", _JSArrayBufferView],
682 "JSDataView": ["new DataView(new ArrayBuffer(24))", _JSDataView],
683 "JSDate": ["new Date()", _JSDate],
684 "JSFunction": ["function() {}", _JSFunction],
685 "JSFunctionProxy": ["Proxy.createFunction({}, function() {})",
687 "JSGeneratorObject": ["(function*(){ yield 1; })()", _JSGeneratorObject],
688 "JSMap": ["new Map()", _JSMap],
689 "JSMapIterator": ["new Map().entries()", _JSMapIterator],
690 "JSObject": ["new Object()", _JSObject],
691 "JSProxy": ["Proxy.create({})", _JSProxy],
692 "JSReceiver": ["new Object()", _JSReceiver],
693 "JSRegExp": ["/ab/g", _JSRegExp],
694 "JSSet": ["new Set()", _JSSet],
695 "JSSetIterator": ["new Set().values()", _JSSetIterator],
696 "JSTypedArray": ["new Int32Array(2)", _JSTypedArray],
697 "JSValue": ["new String('foo')", _JSValue],
698 "JSWeakCollection": ["new WeakMap()", _JSWeakCollection],
699 "Name": ["\"name\"", _Name],
700 "Number": ["1.5", _Number],
701 "Object": ["new Object()", _Object],
702 "PropertyDetails": ["513", _PropertyDetails],
703 "SeqOneByteString": ["\"seq 1-byte\"", _SeqString],
704 "SeqString": ["\"seqstring\"", _SeqString],
705 "SeqTwoByteString": ["\"seq \\u2082-byte\"", _SeqTwoByteString],
707 "StrictMode": ["1", _StrictMode],
708 "String": ["\"foo\"", _String],
709 "Symbol": ["Symbol(\"symbol\")", _Symbol],
710 "Uint32": ["32", _Uint32],
711 "Float32x4": ["SIMD.float32x4(0.0, 0.0, 0.0, 0.0)", _Float32x4],
712 "Float64x2": ["SIMD.float64x2(0.0, 0.0)", _Float64x2],
713 "Int32x4": ["SIMD.int32x4(0, 0, 0, 0)", _Int32x4],
717 class ArgParser(object):
718 def __init__(self, regex, ctor):
724 def __init__(self, typename, varname, index):
726 self.name = "_%s" % varname
730 class Function(object):
731 def __init__(self, match):
732 self.name = match.group(1)
737 handle_arg_parser = ArgParser(
738 re.compile("^\s*CONVERT_ARG_HANDLE_CHECKED\((\w+), (\w+), (\d+)\)"),
739 lambda match: Arg(match.group(1), match.group(2), int(match.group(3))))
741 plain_arg_parser = ArgParser(
742 re.compile("^\s*CONVERT_ARG_CHECKED\((\w+), (\w+), (\d+)\)"),
743 lambda match: Arg(match.group(1), match.group(2), int(match.group(3))))
745 number_handle_arg_parser = ArgParser(
746 re.compile("^\s*CONVERT_NUMBER_ARG_HANDLE_CHECKED\((\w+), (\d+)\)"),
747 lambda match: Arg("Number", match.group(1), int(match.group(2))))
749 smi_arg_parser = ArgParser(
750 re.compile("^\s*CONVERT_SMI_ARG_CHECKED\((\w+), (\d+)\)"),
751 lambda match: Arg("Smi", match.group(1), int(match.group(2))))
753 double_arg_parser = ArgParser(
754 re.compile("^\s*CONVERT_DOUBLE_ARG_CHECKED\((\w+), (\d+)\)"),
755 lambda match: Arg("Number", match.group(1), int(match.group(2))))
757 number_arg_parser = ArgParser(
759 "^\s*CONVERT_NUMBER_CHECKED\(\w+, (\w+), (\w+), args\[(\d+)\]\)"),
760 lambda match: Arg(match.group(2), match.group(1), int(match.group(3))))
762 strict_mode_arg_parser = ArgParser(
763 re.compile("^\s*CONVERT_STRICT_MODE_ARG_CHECKED\((\w+), (\d+)\)"),
764 lambda match: Arg("StrictMode", match.group(1), int(match.group(2))))
766 boolean_arg_parser = ArgParser(
767 re.compile("^\s*CONVERT_BOOLEAN_ARG_CHECKED\((\w+), (\d+)\)"),
768 lambda match: Arg("Boolean", match.group(1), int(match.group(2))))
770 property_details_parser = ArgParser(
771 re.compile("^\s*CONVERT_PROPERTY_DETAILS_CHECKED\((\w+), (\d+)\)"),
772 lambda match: Arg("PropertyDetails", match.group(1), int(match.group(2))))
774 arg_parsers = [handle_arg_parser, plain_arg_parser, number_handle_arg_parser,
776 double_arg_parser, number_arg_parser, strict_mode_arg_parser,
777 boolean_arg_parser, property_details_parser]
779 def SetArgsLength(self, match):
780 self.argslength = int(match.group(1))
782 def TryParseArg(self, line):
783 for parser in Function.arg_parsers:
784 match = parser.regex.match(line)
786 arg = parser.ArgCtor(match)
787 self.args[arg.index] = arg
792 return "%s.js" % self.name.lower()
796 argcount = self.argslength
798 print("WARNING: unknown argslength for function %s" % self.name)
800 argcount = max([self.args[i].index + 1 for i in self.args])
803 for i in range(argcount):
804 if i > 0: s.append(", ")
805 s.append(self.args[i].type if i in self.args else "<unknown>")
811 def __init__(self, match):
812 self.name = match.group(1)
813 self.args = [s.strip() for s in match.group(2).split(",")]
816 self.AddLine(match.group(3))
818 def AddLine(self, line):
821 # This is the first line, detect indentation.
822 self.indentation = len(line) - len(line.lstrip())
823 line = line.rstrip("\\\n ")
825 assert len(line[:self.indentation].strip()) == 0, \
826 ("expected whitespace: '%s', full line: '%s'" %
827 (line[:self.indentation], line))
828 line = line[self.indentation:]
830 self.lines.append(line + "\n")
833 for arg in self.args:
834 pattern = re.compile(r"(##|\b)%s(##|\b)" % arg)
835 for i in range(len(self.lines)):
836 self.lines[i] = re.sub(pattern, "%%(%s)s" % arg, self.lines[i])
838 def FillIn(self, arg_values):
840 assert len(arg_values) == len(self.args)
841 for i in range(len(self.args)):
842 filler[self.args[i]] = arg_values[i]
844 for line in self.lines:
845 result.append(line % filler)
849 # Parses HEADERFILENAME to find out which runtime functions are "inline".
850 def FindInlineRuntimeFunctions():
851 inline_functions = []
852 with open(HEADERFILENAME, "r") as f:
853 inline_list = "#define INLINE_FUNCTION_LIST(F) \\\n"
854 inline_function = re.compile(r"^\s*F\((\w+), \d+, \d+\)\s*\\?")
858 match = inline_function.match(line)
860 inline_functions.append(match.group(1))
861 if not line.endswith("\\\n"):
863 elif mode == "SEARCHING":
864 if line == inline_list:
866 return inline_functions
869 def ReadFileAndExpandMacros(filename):
872 with open(filename, "r") as f:
875 if found_macro is not None:
876 found_macro.AddLine(line)
877 if not line.endswith("\\\n"):
878 found_macro.Finalize()
882 match = MACRO.match(line)
884 found_macro = Macro(match)
885 if found_macro.name in EXPAND_MACROS:
886 found_macros[found_macro.name] = found_macro
891 match = FIRST_WORD.match(line)
893 first_word = match.group(1)
894 if first_word in found_macros:
895 MACRO_CALL = re.compile("%s\(([^)]*)\)" % first_word)
896 match = MACRO_CALL.match(line)
898 args = [s.strip() for s in match.group(1).split(",")]
899 expanded_lines += found_macros[first_word].FillIn(args)
902 expanded_lines.append(line)
903 return expanded_lines
906 # Detects runtime functions by parsing FILENAME.
907 def FindRuntimeFunctions():
908 inline_functions = FindInlineRuntimeFunctions()
910 expanded_lines = ReadFileAndExpandMacros(FILENAME)
913 for line in expanded_lines:
914 # Multi-line definition support, ignoring macros.
915 if line.startswith("RUNTIME_FUNCTION") and not line.endswith("{\n"):
916 if line.endswith("\\\n"): continue
917 partial_line = line.rstrip()
920 partial_line += " " + line.strip()
921 if partial_line.endswith("{"):
927 match = FUNCTION.match(line)
929 function = Function(match)
930 if function.name in inline_functions:
931 function.inline = "_"
933 if function is None: continue
935 match = ARGSLENGTH.match(line)
937 function.SetArgsLength(match)
940 if function.TryParseArg(line):
943 if line == FUNCTIONEND:
944 if function is not None:
945 functions.append(function)
950 # Hack: This must have the same fields as class Function above, because the
951 # two are used polymorphically in RunFuzzer(). We could use inheritance...
952 class Builtin(object):
953 def __init__(self, match):
954 self.name = match.group(1)
955 args = match.group(2)
956 self.argslength = 0 if args == "" else args.count(",") + 1
959 if self.argslength > 0:
960 args = args.split(",")
961 for i in range(len(args)):
962 # a = args[i].strip() # TODO: filter out /* comments */ first.
964 self.args[i] = Arg("Object", a, i)
967 return "%s(%d)" % (self.name, self.argslength)
970 def FindJSBuiltins():
973 for (root, dirs, files) in os.walk(PATH):
975 if f.endswith(".js"):
976 fileslist.append(os.path.join(root, f))
978 regexp = re.compile("^function (\w+)\s*\((.*?)\) {")
980 for filename in fileslist:
981 with open(filename, "r") as f:
982 file_contents = f.read()
983 file_contents = js2c.ExpandInlineMacros(file_contents)
984 lines = file_contents.split("\n")
987 if line.startswith("function") and not '{' in line:
988 partial_line += line.rstrip()
991 partial_line += " " + line.strip()
997 match = regexp.match(line)
999 builtins.append(Builtin(match))
1003 # Classifies runtime functions.
1004 def ClassifyFunctions(functions):
1005 # Can be fuzzed with a JavaScript testcase.
1006 js_fuzzable_functions = []
1007 # We have enough information to fuzz these, but they need inputs that
1008 # cannot be created or passed around in JavaScript.
1009 cctest_fuzzable_functions = []
1010 # This script does not have enough information about these.
1011 unknown_functions = []
1015 if f.name in BLACKLISTED:
1017 decision = js_fuzzable_functions
1018 custom = CUSTOM_KNOWN_GOOD_INPUT.get(f.name, None)
1019 if f.argslength < 0:
1020 # Unknown length -> give up unless there's a custom definition.
1021 if custom and custom[-1] is not None:
1022 f.argslength = custom[-1]
1023 assert len(custom) == f.argslength + 1, \
1024 ("%s: last custom definition must be argslength" % f.name)
1026 decision = unknown_functions
1029 # Any custom definitions must match the known argslength.
1030 assert len(custom) == f.argslength + 1, \
1031 ("%s should have %d custom definitions but has %d" %
1032 (f.name, f.argslength + 1, len(custom)))
1033 for i in range(f.argslength):
1034 if custom and custom[i] is not None:
1035 # All good, there's a custom definition.
1037 elif not i in f.args:
1038 # No custom definition and no parse result -> give up.
1039 decision = unknown_functions
1042 if t in NON_JS_TYPES:
1043 decision = cctest_fuzzable_functions
1045 assert Generator.IsTypeSupported(t), \
1046 ("type generator not found for %s, function: %s" % (t, f))
1048 return (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions)
1051 def _GetKnownGoodArgs(function, generator):
1052 custom_input = CUSTOM_KNOWN_GOOD_INPUT.get(function.name, None)
1055 for i in range(function.argslength):
1056 if custom_input and custom_input[i] is not None:
1058 definitions.append("var %s = %s;" % (name, custom_input[i]))
1060 arg = function.args[i]
1062 definitions += generator.RandomVariable(name, arg.type, simple=True)
1063 argslist.append(name)
1064 return (definitions, argslist)
1067 def _GenerateTestcase(function, definitions, argslist, throws):
1068 s = ["// Copyright 2014 the V8 project authors. All rights reserved.",
1069 "// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY",
1070 "// Flags: --allow-natives-syntax --harmony"] + definitions
1071 if function.name.find("Float32x4") != -1 or function.name.find("Float64x2") != -1 or function.name.find("Int32x4") != -1:
1072 s = ["// Copyright 2014 the V8 project authors. All rights reserved.",
1073 "// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY",
1074 "// Flags: --allow-natives-syntax --harmony --simd-object"] + definitions
1075 call = "%%%s%s(%s);" % (function.inline, function.name, ", ".join(argslist))
1079 s.append("} catch(e) {}")
1082 testcase = "\n".join(s)
1086 def GenerateJSTestcaseForFunction(function):
1088 (definitions, argslist) = _GetKnownGoodArgs(function, gen)
1089 testcase = _GenerateTestcase(function, definitions, argslist,
1090 function.name in THROWS)
1091 path = os.path.join(BASEPATH, function.Filename())
1092 with open(path, "w") as f:
1093 f.write("%s\n" % testcase)
1096 def GenerateTestcases(functions):
1097 shutil.rmtree(BASEPATH) # Re-generate everything.
1098 os.makedirs(BASEPATH)
1100 GenerateJSTestcaseForFunction(f)
1103 def _SaveFileName(save_path, process_id, save_file_index):
1104 return "%s/fuzz_%d_%d.js" % (save_path, process_id, save_file_index)
1107 def _GetFuzzableRuntimeFunctions():
1108 functions = FindRuntimeFunctions()
1109 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \
1110 ClassifyFunctions(functions)
1111 return js_fuzzable_functions
1114 FUZZ_TARGET_LISTS = {
1115 "runtime": _GetFuzzableRuntimeFunctions,
1116 "builtins": FindJSBuiltins,
1120 def RunFuzzer(process_id, options, stop_running):
1121 MAX_SLEEP_TIME = 0.1
1122 INITIAL_SLEEP_TIME = 0.001
1123 SLEEP_TIME_FACTOR = 1.25
1124 base_file_name = "/dev/shm/runtime_fuzz_%d" % process_id
1125 test_file_name = "%s.js" % base_file_name
1126 stderr_file_name = "%s.out" % base_file_name
1128 while os.path.exists(_SaveFileName(options.save_path, process_id,
1130 save_file_index += 1
1132 targets = FUZZ_TARGET_LISTS[options.fuzz_target]()
1134 for i in range(options.num_tests):
1135 if stop_running.is_set(): break
1137 while function is None or function.argslength == 0:
1138 function = random.choice(targets)
1142 for i in range(function.argslength):
1143 arg = function.args[i]
1144 argname = "arg%d%s" % (i, arg.name)
1145 args.append(argname)
1146 definitions += gen.RandomVariable(argname, arg.type, simple=False)
1147 testcase = _GenerateTestcase(function, definitions, args, True)
1148 with open(test_file_name, "w") as f:
1149 f.write("%s\n" % testcase)
1150 with open("/dev/null", "w") as devnull:
1151 with open(stderr_file_name, "w") as stderr:
1152 process = subprocess.Popen(
1153 [options.binary, "--allow-natives-syntax", "--harmony",
1154 "--enable-slow-asserts", test_file_name],
1155 stdout=devnull, stderr=stderr)
1156 end_time = time.time() + options.timeout
1159 sleep_time = INITIAL_SLEEP_TIME
1160 while exit_code is None:
1161 if time.time() >= end_time:
1162 # Kill the process and wait for it to exit.
1163 os.kill(process.pid, signal.SIGTERM)
1164 exit_code = process.wait()
1167 exit_code = process.poll()
1168 time.sleep(sleep_time)
1169 sleep_time = sleep_time * SLEEP_TIME_FACTOR
1170 if sleep_time > MAX_SLEEP_TIME:
1171 sleep_time = MAX_SLEEP_TIME
1172 if exit_code != 0 and not timed_out:
1174 with open(stderr_file_name, "r") as stderr:
1176 if line.strip() == "# Allocation failed - process out of memory":
1180 save_name = _SaveFileName(options.save_path, process_id,
1182 shutil.copyfile(test_file_name, save_name)
1183 save_file_index += 1
1184 except KeyboardInterrupt:
1187 if os.path.exists(test_file_name):
1188 os.remove(test_file_name)
1189 if os.path.exists(stderr_file_name):
1190 os.remove(stderr_file_name)
1193 def BuildOptionParser():
1194 usage = """Usage: %%prog [options] ACTION
1196 where ACTION can be:
1198 info Print diagnostic info.
1199 check Check that runtime functions can be parsed as expected, and that
1201 generate Parse source code for runtime functions, and auto-generate
1202 test cases for them. Warning: this will nuke and re-create
1204 fuzz Generate fuzz tests, run them, save those that crashed (see options).
1205 """ % {"path": os.path.relpath(BASEPATH)}
1207 o = optparse.OptionParser(usage=usage)
1208 o.add_option("--binary", default="out/x64.debug/d8",
1209 help="d8 binary used for running fuzz tests (default: %default)")
1210 o.add_option("--fuzz-target", default="runtime",
1211 help="Set of functions targeted by fuzzing. Allowed values: "
1212 "%s (default: %%default)" % ", ".join(FUZZ_TARGET_LISTS))
1213 o.add_option("-n", "--num-tests", default=1000, type="int",
1214 help="Number of fuzz tests to generate per worker process"
1215 " (default: %default)")
1216 o.add_option("--save-path", default="~/runtime_fuzz_output",
1217 help="Path to directory where failing tests will be stored"
1218 " (default: %default)")
1219 o.add_option("--timeout", default=20, type="int",
1220 help="Timeout for each fuzz test (in seconds, default:"
1225 def ProcessOptions(options, args):
1226 options.save_path = os.path.expanduser(options.save_path)
1227 if options.fuzz_target not in FUZZ_TARGET_LISTS:
1228 print("Invalid fuzz target: %s" % options.fuzz_target)
1230 if len(args) != 1 or args[0] == "help":
1236 parser = BuildOptionParser()
1237 (options, args) = parser.parse_args()
1239 if not ProcessOptions(options, args):
1244 functions = FindRuntimeFunctions()
1245 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \
1246 ClassifyFunctions(functions)
1247 builtins = FindJSBuiltins()
1249 if action == "test":
1250 print("put your temporary debugging code here")
1253 if action == "info":
1254 print("%d functions total; js_fuzzable_functions: %d, "
1255 "cctest_fuzzable_functions: %d, unknown_functions: %d"
1256 % (len(functions), len(js_fuzzable_functions),
1257 len(cctest_fuzzable_functions), len(unknown_functions)))
1258 print("%d JavaScript builtins" % len(builtins))
1259 print("unknown functions:")
1260 for f in unknown_functions:
1264 if action == "check":
1267 def CheckCount(actual, expected, description):
1268 if len(actual) != expected:
1269 print("Expected to detect %d %s, but found %d." % (
1270 expected, description, len(actual)))
1271 print("If this change is intentional, please update the expectations"
1272 " at the top of %s." % THIS_SCRIPT)
1276 errors += CheckCount(functions, EXPECTED_FUNCTION_COUNT,
1277 "functions in total")
1278 errors += CheckCount(js_fuzzable_functions, EXPECTED_FUZZABLE_COUNT,
1279 "JavaScript-fuzzable functions")
1280 errors += CheckCount(cctest_fuzzable_functions, EXPECTED_CCTEST_COUNT,
1281 "cctest-fuzzable functions")
1282 errors += CheckCount(unknown_functions, EXPECTED_UNKNOWN_COUNT,
1283 "functions with incomplete type information")
1284 errors += CheckCount(builtins, EXPECTED_BUILTINS_COUNT,
1285 "JavaScript builtins")
1287 def CheckTestcasesExisting(functions):
1290 if not os.path.isfile(os.path.join(BASEPATH, f.Filename())):
1291 print("Missing testcase for %s, please run '%s generate'" %
1292 (f.name, THIS_SCRIPT))
1294 files = filter(lambda filename: not filename.startswith("."),
1295 os.listdir(BASEPATH))
1296 if (len(files) != len(functions)):
1297 unexpected_files = set(files) - set([f.Filename() for f in functions])
1298 for f in unexpected_files:
1299 print("Unexpected testcase: %s" % os.path.join(BASEPATH, f))
1301 print("Run '%s generate' to automatically clean these up."
1305 errors += CheckTestcasesExisting(js_fuzzable_functions)
1307 def CheckNameClashes(runtime_functions, builtins):
1310 for f in runtime_functions:
1311 runtime_map[f.name] = 1
1313 if b.name in runtime_map:
1314 print("Builtin/Runtime_Function name clash: %s" % b.name)
1318 errors += CheckNameClashes(functions, builtins)
1322 print("Generated runtime tests: all good.")
1325 if action == "generate":
1326 GenerateTestcases(js_fuzzable_functions)
1329 if action == "fuzz":
1331 if not os.path.isdir(options.save_path):
1332 os.makedirs(options.save_path)
1333 stop_running = multiprocessing.Event()
1334 for i in range(multiprocessing.cpu_count()):
1335 args = (i, options, stop_running)
1336 p = multiprocessing.Process(target=RunFuzzer, args=args)
1340 for i in range(len(processes)):
1342 except KeyboardInterrupt:
1344 for i in range(len(processes)):
1348 if __name__ == "__main__":