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(".*DCHECK\(.*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 = 428
51 EXPECTED_FUZZABLE_COUNT = 331
52 EXPECTED_CCTEST_COUNT = 7
53 EXPECTED_UNKNOWN_COUNT = 16
54 EXPECTED_BUILTINS_COUNT = 809
57 # Don't call these at all.
59 "Abort", # Kills the process.
60 "AbortJS", # Kills the process.
61 "CompileForOnStackReplacement", # Riddled with DCHECK.
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",
90 # TODO(danno): Fix these internal function that are only callable form stubs
91 # and un-blacklist them!
93 "RxegExpConstructResult",
102 "CompileUnoptimized",
104 "TryInstallOptimizedCode",
109 "AllocateInNewSpace",
110 "AllocateInTargetSpace",
111 "AllocateHeapNumber",
113 "NumberToStringSkipCache",
115 "NewSloppyArguments",
116 "NewStrictArguments",
119 "CreateJSGeneratorObject",
120 "SuspendJSGeneratorObject",
121 "ResumeJSGeneratorObject",
122 "ThrowGeneratorStateError",
126 "InternalArrayConstructor",
130 "MaterializeRegExpLiteral",
131 "CreateObjectLiteral",
132 "CreateArrayLiteral",
133 "CreateArrayLiteralStubBailout",
137 "NewClosureFromStubFailure",
139 "NewObjectWithAllocationSite",
140 "FinalizeInstanceSize",
143 "ThrowReferenceError",
147 "PromoteScheduledException",
151 "NewFunctionContext",
158 "LoadLookupSlotNoReferenceError",
164 "DeclareContextSlot",
165 "InitializeConstGlobal",
166 "InitializeConstContextSlot",
169 "ResolvePossiblyDirectEval",
177 # These will always throw.
179 "CheckExecutionState", # Needs to hit a break point.
180 "CheckIsBootstrapping", # Needs to be bootstrapping.
181 "DebugEvaluate", # Needs to hit a break point.
182 "DebugEvaluateGlobal", # Needs to hit a break point.
183 "DebugIndexedInterceptorElementValue", # Needs an indexed interceptor.
184 "DebugNamedInterceptorPropertyValue", # Needs a named interceptor.
185 "DebugSetScriptSource", # Checks compilation state of script.
186 "GetAllScopesDetails", # Needs to hit a break point.
187 "GetFrameCount", # Needs to hit a break point.
188 "GetFrameDetails", # Needs to hit a break point.
189 "GetRootNaN", # Needs to be bootstrapping.
190 "GetScopeCount", # Needs to hit a break point.
191 "GetScopeDetails", # Needs to hit a break point.
192 "GetStepInPositions", # Needs to hit a break point.
193 "GetTemplateField", # Needs a {Function,Object}TemplateInfo.
194 "GetThreadCount", # Needs to hit a break point.
195 "GetThreadDetails", # Needs to hit a break point.
196 "IsAccessAllowedForObserver", # Needs access-check-required object.
197 "UnblockConcurrentRecompilation" # Needs --block-concurrent-recompilation.
201 # Definitions used in CUSTOM_KNOWN_GOOD_INPUT below.
203 "%GetImplFromInitializedIntlObject(new Intl.v8BreakIterator())")
204 _COLLATOR = "%GetImplFromInitializedIntlObject(new Intl.Collator('en-US'))"
206 "%GetImplFromInitializedIntlObject(new Intl.DateTimeFormat('en-US'))")
208 "%GetImplFromInitializedIntlObject(new Intl.NumberFormat('en-US'))")
211 # Custom definitions for function input that does not throw.
212 # Format: "FunctionName": ["arg0", "arg1", ..., argslength].
213 # None means "fall back to autodetected value".
214 CUSTOM_KNOWN_GOOD_INPUT = {
215 "AddNamedProperty": [None, "\"bla\"", None, None, None],
216 "AddPropertyForTemplate": [None, 10, None, None, None],
217 "Apply": ["function() {}", None, None, None, None, None],
218 "ArrayBufferSliceImpl": [None, None, 0, None],
219 "ArrayConcat": ["[1, 'a']", None],
220 "BreakIteratorAdoptText": [_BREAK_ITERATOR, None, None],
221 "BreakIteratorBreakType": [_BREAK_ITERATOR, None],
222 "BreakIteratorCurrent": [_BREAK_ITERATOR, None],
223 "BreakIteratorFirst": [_BREAK_ITERATOR, None],
224 "BreakIteratorNext": [_BREAK_ITERATOR, None],
225 "CompileString": [None, "false", None],
226 "CreateBreakIterator": ["'en-US'", "{type: 'string'}", None, None],
227 "CreateJSFunctionProxy": [None, "function() {}", None, None, None],
228 "CreatePrivateSymbol": ["\"foo\"", None],
229 "CreateSymbol": ["\"foo\"", None],
230 "DateParseString": [None, "new Array(8)", None],
231 "DefineAccessorPropertyUnchecked": [None, None, "function() {}",
232 "function() {}", 2, None],
233 "FunctionBindArguments": [None, None, "undefined", None, None],
234 "GetBreakLocations": [None, 0, None],
235 "GetDefaultReceiver": ["function() {}", None],
236 "GetImplFromInitializedIntlObject": ["new Intl.NumberFormat('en-US')", None],
237 "InternalCompare": [_COLLATOR, None, None, None],
238 "InternalDateFormat": [_DATETIME_FORMAT, None, None],
239 "InternalDateParse": [_DATETIME_FORMAT, None, None],
240 "InternalNumberFormat": [_NUMBER_FORMAT, None, None],
241 "InternalNumberParse": [_NUMBER_FORMAT, None, None],
242 "IsSloppyModeFunction": ["function() {}", None],
243 "LoadMutableDouble": ["{foo: 1.2}", None, None],
244 "NewObjectFromBound": ["(function() {}).bind({})", None],
245 "NumberToRadixString": [None, "2", None],
246 "ParseJson": ["\"{}\"", 1],
247 "RegExpExecMultiple": [None, None, "['a']", "['a']", None],
248 "DefineApiAccessorProperty": [None, None, "undefined", "undefined", None, None],
249 "SetIteratorInitialize": [None, None, "2", None],
250 "SetDebugEventListener": ["undefined", None, None],
251 "SetFunctionBreakPoint": [None, 218, None, None],
252 "StringBuilderConcat": ["[1, 2, 3]", 3, None, None],
253 "StringBuilderJoin": ["['a', 'b']", 4, None, None],
254 "StringMatch": [None, None, "['a', 'b']", None],
255 "StringNormalize": [None, 2, None],
256 "StringReplaceGlobalRegExpWithString": [None, None, None, "['a']", None],
257 "TypedArrayInitialize": [None, 6, "new ArrayBuffer(8)", None, 4, None],
258 "TypedArrayInitializeFromArrayLike": [None, 6, None, None, None],
259 "TypedArraySetFastCases": [None, None, "0", None],
260 "FunctionIsArrow": ["() => null", None],
264 # Types of arguments that cannot be generated in a JavaScript testcase.
266 "Code", "Context", "FixedArray", "FunctionTemplateInfo",
267 "JSFunctionResultCache", "JSMessageObject", "Map", "ScopeInfo",
268 "SharedFunctionInfo"]
271 class Generator(object):
273 def RandomVariable(self, varname, vartype, simple):
275 return self._Variable(varname, self.GENERATORS[vartype][0])
276 return self.GENERATORS[vartype][1](self, varname,
277 self.DEFAULT_RECURSION_BUDGET)
280 def IsTypeSupported(typename):
281 return typename in Generator.GENERATORS
283 USUAL_SUSPECT_PROPERTIES = ["size", "length", "byteLength", "__proto__",
284 "prototype", "0", "1", "-1"]
285 DEFAULT_RECURSION_BUDGET = 2
287 getOwnPropertyDescriptor: function(name) {
288 return {value: function() {}, configurable: true, writable: true,
291 getPropertyDescriptor: function(name) {
292 return {value: function() {}, configurable: true, writable: true,
295 getOwnPropertyNames: function() { return []; },
296 getPropertyNames: function() { return []; },
297 defineProperty: function(name, descriptor) {},
298 delete: function(name) { return true; },
302 def _Variable(self, name, value, fallback=None):
303 args = { "name": name, "value": value, "fallback": fallback }
305 wrapper = "try { %%s } catch(e) { var %(name)s = %(fallback)s; }" % args
308 return [wrapper % ("var %(name)s = %(value)s;" % args)]
310 def _Boolean(self, name, recursion_budget):
311 return self._Variable(name, random.choice(["true", "false"]))
313 def _Oddball(self, name, recursion_budget):
314 return self._Variable(name,
315 random.choice(["true", "false", "undefined", "null"]))
317 def _StrictMode(self, name, recursion_budget):
318 return self._Variable(name, random.choice([0, 1]))
320 def _Int32(self, name, recursion_budget=0):
321 die = random.random()
323 value = random.choice([-3, -1, 0, 1, 2, 10, 515, 0x3fffffff, 0x7fffffff,
324 0x40000000, -0x40000000, -0x80000000])
326 value = random.randint(-1000, 1000)
328 value = random.randint(-0x80000000, 0x7fffffff)
329 return self._Variable(name, value)
331 def _Uint32(self, name, recursion_budget=0):
332 die = random.random()
334 value = random.choice([0, 1, 2, 3, 4, 8, 0x3fffffff, 0x40000000,
335 0x7fffffff, 0xffffffff])
337 value = random.randint(0, 1000)
339 value = random.randint(0, 0xffffffff)
340 return self._Variable(name, value)
342 def _Smi(self, name, recursion_budget):
343 die = random.random()
345 value = random.choice([-5, -1, 0, 1, 2, 3, 0x3fffffff, -0x40000000])
347 value = random.randint(-1000, 1000)
349 value = random.randint(-0x40000000, 0x3fffffff)
350 return self._Variable(name, value)
352 def _Number(self, name, recursion_budget):
353 die = random.random()
355 return self._Smi(name, recursion_budget)
357 value = random.choice(["Infinity", "-Infinity", "NaN", "-0",
358 "1.7976931348623157e+308", # Max value.
359 "2.2250738585072014e-308", # Min value.
360 "4.9406564584124654e-324"]) # Min subnormal.
362 value = random.lognormvariate(0, 15)
363 return self._Variable(name, value)
365 def _RawRandomString(self, minlength=0, maxlength=100,
366 alphabet=string.ascii_letters):
367 length = random.randint(minlength, maxlength)
369 for i in xrange(length):
370 result += random.choice(alphabet)
373 def _SeqString(self, name, recursion_budget):
374 s1 = self._RawRandomString(1, 5)
375 s2 = self._RawRandomString(1, 5)
377 return self._Variable(name, "\"%s\" + \"%s\"" % (s1, s2))
379 def _SeqTwoByteString(self, name):
380 s1 = self._RawRandomString(1, 5)
381 s2 = self._RawRandomString(1, 5)
382 # 'foo' + unicode + 'bar'
383 return self._Variable(name, "\"%s\" + \"\\2082\" + \"%s\"" % (s1, s2))
385 def _SlicedString(self, name):
386 s = self._RawRandomString(20, 30)
387 # 'ffoo12345678901234567890'.substr(1)
388 return self._Variable(name, "\"%s\".substr(1)" % s)
390 def _ConsString(self, name):
391 s1 = self._RawRandomString(8, 15)
392 s2 = self._RawRandomString(8, 15)
393 # 'foo12345' + (function() { return 'bar12345';})()
394 return self._Variable(name,
395 "\"%s\" + (function() { return \"%s\";})()" % (s1, s2))
397 def _InternalizedString(self, name):
398 return self._Variable(name, "\"%s\"" % self._RawRandomString(0, 20))
400 def _String(self, name, recursion_budget):
401 die = random.random()
403 string = random.choice(self.USUAL_SUSPECT_PROPERTIES)
404 return self._Variable(name, "\"%s\"" % string)
406 number_name = name + "_number"
407 result = self._Number(number_name, recursion_budget)
408 return result + self._Variable(name, "\"\" + %s" % number_name)
410 return self._SeqString(name, recursion_budget)
412 return self._ConsString(name)
414 return self._InternalizedString(name)
416 return self._SlicedString(name)
418 def _Symbol(self, name, recursion_budget):
419 raw_string_name = name + "_1"
420 result = self._String(raw_string_name, recursion_budget)
421 return result + self._Variable(name, "Symbol(%s)" % raw_string_name)
423 def _Name(self, name, recursion_budget):
424 if random.random() < 0.2:
425 return self._Symbol(name, recursion_budget)
426 return self._String(name, recursion_budget)
428 def _JSValue(self, name, recursion_budget):
429 die = random.random()
430 raw_name = name + "_1"
432 result = self._String(raw_name, recursion_budget)
433 return result + self._Variable(name, "new String(%s)" % raw_name)
435 result = self._Boolean(raw_name, recursion_budget)
436 return result + self._Variable(name, "new Boolean(%s)" % raw_name)
438 result = self._Number(raw_name, recursion_budget)
439 return result + self._Variable(name, "new Number(%s)" % raw_name)
441 def _RawRandomPropertyName(self):
442 if random.random() < 0.5:
443 return random.choice(self.USUAL_SUSPECT_PROPERTIES)
444 return self._RawRandomString(0, 10)
446 def _AddProperties(self, name, result, recursion_budget):
447 propcount = random.randint(0, 3)
449 for i in range(propcount):
450 die = random.random()
452 propname = "%s_prop%d" % (name, i)
453 result += self._Name(propname, recursion_budget - 1)
455 propname = "\"%s\"" % self._RawRandomPropertyName()
456 propvalue_name = "%s_val%d" % (name, i)
457 result += self._Object(propvalue_name, recursion_budget - 1)
458 result.append("try { %s[%s] = %s; } catch (e) {}" %
459 (name, propname, propvalue_name))
460 if random.random() < 0.2 and propname:
461 # Force the object to slow mode.
462 result.append("delete %s[%s];" % (name, propname))
464 def _RandomElementIndex(self, element_name, result):
465 if random.random() < 0.5:
466 return random.randint(-1000, 1000)
467 result += self._Smi(element_name, 0)
470 def _AddElements(self, name, result, recursion_budget):
471 elementcount = random.randint(0, 3)
472 for i in range(elementcount):
473 element_name = "%s_idx%d" % (name, i)
474 index = self._RandomElementIndex(element_name, result)
475 value_name = "%s_elt%d" % (name, i)
476 result += self._Object(value_name, recursion_budget - 1)
477 result.append("try { %s[%s] = %s; } catch(e) {}" %
478 (name, index, value_name))
480 def _AddAccessors(self, name, result, recursion_budget):
481 accessorcount = random.randint(0, 3)
482 for i in range(accessorcount):
483 propname = self._RawRandomPropertyName()
484 what = random.choice(["get", "set"])
485 function_name = "%s_access%d" % (name, i)
486 result += self._PlainFunction(function_name, recursion_budget - 1)
487 result.append("try { Object.defineProperty(%s, \"%s\", {%s: %s}); } "
488 "catch (e) {}" % (name, propname, what, function_name))
490 def _PlainArray(self, name, recursion_budget):
491 die = random.random()
493 literal = random.choice(["[]", "[1, 2]", "[1.5, 2.5]",
494 "['a', 'b', 1, true]"])
495 return self._Variable(name, literal)
497 new = random.choice(["", "new "])
498 length = random.randint(0, 101000)
499 return self._Variable(name, "%sArray(%d)" % (new, length))
501 def _PlainObject(self, name, recursion_budget):
502 die = random.random()
504 literal_propcount = random.randint(0, 3)
507 for i in range(literal_propcount):
508 propname = self._RawRandomPropertyName()
509 propvalue_name = "%s_lit%d" % (name, i)
510 result += self._Object(propvalue_name, recursion_budget - 1)
511 properties.append("\"%s\": %s" % (propname, propvalue_name))
512 return result + self._Variable(name, "{%s}" % ", ".join(properties))
514 return self._Variable(name, "new Object()")
516 def _JSArray(self, name, recursion_budget):
517 result = self._PlainArray(name, recursion_budget)
518 self._AddAccessors(name, result, recursion_budget)
519 self._AddProperties(name, result, recursion_budget)
520 self._AddElements(name, result, recursion_budget)
523 def _RawRandomBufferLength(self):
524 if random.random() < 0.2:
525 return random.choice([0, 1, 8, 0x40000000, 0x80000000])
526 return random.randint(0, 1000)
528 def _JSArrayBuffer(self, name, recursion_budget):
529 length = self._RawRandomBufferLength()
530 return self._Variable(name, "new ArrayBuffer(%d)" % length)
532 def _JSDataView(self, name, recursion_budget):
533 buffer_name = name + "_buffer"
534 result = self._JSArrayBuffer(buffer_name, recursion_budget)
536 die = random.random()
538 offset = self._RawRandomBufferLength()
539 args.append("%d" % offset)
541 length = self._RawRandomBufferLength()
542 args.append("%d" % length)
543 result += self._Variable(name, "new DataView(%s)" % ", ".join(args),
544 fallback="new DataView(new ArrayBuffer(8))")
547 def _JSDate(self, name, recursion_budget):
548 die = random.random()
550 return self._Variable(name, "new Date()")
552 ms_name = name + "_ms"
553 result = self._Number(ms_name, recursion_budget)
554 return result + self._Variable(name, "new Date(%s)" % ms_name)
556 str_name = name + "_str"
557 month = random.choice(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
558 "Aug", "Sep", "Oct", "Nov", "Dec"])
559 day = random.randint(1, 28)
560 year = random.randint(1900, 2100)
561 hour = random.randint(0, 23)
562 minute = random.randint(0, 59)
563 second = random.randint(0, 59)
564 str_value = ("\"%s %s, %s %s:%s:%s\"" %
565 (month, day, year, hour, minute, second))
566 result = self._Variable(str_name, str_value)
567 return result + self._Variable(name, "new Date(%s)" % str_name)
569 components = tuple(map(lambda x: "%s_%s" % (name, x),
570 ["y", "m", "d", "h", "min", "s", "ms"]))
571 return ([j for i in map(self._Int32, components) for j in i] +
572 self._Variable(name, "new Date(%s)" % ", ".join(components)))
574 def _PlainFunction(self, name, recursion_budget):
575 result_name = "result"
576 body = ["function() {"]
577 body += self._Object(result_name, recursion_budget - 1)
578 body.append("return result;\n}")
579 return self._Variable(name, "%s" % "\n".join(body))
581 def _JSFunction(self, name, recursion_budget):
582 result = self._PlainFunction(name, recursion_budget)
583 self._AddAccessors(name, result, recursion_budget)
584 self._AddProperties(name, result, recursion_budget)
585 self._AddElements(name, result, recursion_budget)
588 def _JSFunctionProxy(self, name, recursion_budget):
589 # TODO(jkummerow): Revisit this as the Proxy implementation evolves.
590 return self._Variable(name, "Proxy.createFunction(%s, function() {})" %
593 def _JSGeneratorObject(self, name, recursion_budget):
594 # TODO(jkummerow): Be more creative here?
595 return self._Variable(name, "(function*() { yield 1; })()")
597 def _JSMap(self, name, recursion_budget, weak=""):
598 result = self._Variable(name, "new %sMap()" % weak)
599 num_entries = random.randint(0, 3)
600 for i in range(num_entries):
601 key_name = "%s_k%d" % (name, i)
602 value_name = "%s_v%d" % (name, i)
604 result += self._JSObject(key_name, recursion_budget - 1)
606 result += self._Object(key_name, recursion_budget - 1)
607 result += self._Object(value_name, recursion_budget - 1)
608 result.append("%s.set(%s, %s)" % (name, key_name, value_name))
611 def _JSMapIterator(self, name, recursion_budget):
612 map_name = name + "_map"
613 result = self._JSMap(map_name, recursion_budget)
614 iterator_type = random.choice(['keys', 'values', 'entries'])
615 return (result + self._Variable(name, "%s.%s()" %
616 (map_name, iterator_type)))
618 def _JSProxy(self, name, recursion_budget):
619 # TODO(jkummerow): Revisit this as the Proxy implementation evolves.
620 return self._Variable(name, "Proxy.create(%s)" % self.PROXY_TRAPS)
622 def _JSRegExp(self, name, recursion_budget):
623 flags = random.choice(["", "g", "i", "m", "gi"])
624 string = "a(b|c)*a" # TODO(jkummerow): Be more creative here?
625 ctor = random.choice(["/%s/%s", "new RegExp(\"%s\", \"%s\")"])
626 return self._Variable(name, ctor % (string, flags))
628 def _JSSet(self, name, recursion_budget, weak=""):
629 result = self._Variable(name, "new %sSet()" % weak)
630 num_entries = random.randint(0, 3)
631 for i in range(num_entries):
632 element_name = "%s_e%d" % (name, i)
634 result += self._JSObject(element_name, recursion_budget - 1)
636 result += self._Object(element_name, recursion_budget - 1)
637 result.append("%s.add(%s)" % (name, element_name))
640 def _JSSetIterator(self, name, recursion_budget):
641 set_name = name + "_set"
642 result = self._JSSet(set_name, recursion_budget)
643 iterator_type = random.choice(['values', 'entries'])
644 return (result + self._Variable(name, "%s.%s()" %
645 (set_name, iterator_type)))
647 def _JSTypedArray(self, name, recursion_budget):
648 arraytype = random.choice(["Int8", "Int16", "Int32", "Uint8", "Uint16",
649 "Uint32", "Float32", "Float64", "Uint8Clamped"])
650 ctor_type = random.randint(0, 3)
652 length = random.randint(0, 1000)
653 return self._Variable(name, "new %sArray(%d)" % (arraytype, length),
654 fallback="new %sArray(8)" % arraytype)
656 input_name = name + "_typedarray"
657 result = self._JSTypedArray(input_name, recursion_budget - 1)
659 self._Variable(name, "new %sArray(%s)" % (arraytype, input_name),
660 fallback="new %sArray(8)" % arraytype))
662 arraylike_name = name + "_arraylike"
663 result = self._JSObject(arraylike_name, recursion_budget - 1)
664 length = random.randint(0, 1000)
665 result.append("try { %s.length = %d; } catch(e) {}" %
666 (arraylike_name, length))
669 "new %sArray(%s)" % (arraytype, arraylike_name),
670 fallback="new %sArray(8)" % arraytype))
672 die = random.random()
673 buffer_name = name + "_buffer"
675 result = self._JSArrayBuffer(buffer_name, recursion_budget)
677 offset_name = name + "_offset"
678 args.append(offset_name)
679 result += self._Int32(offset_name)
681 length_name = name + "_length"
682 args.append(length_name)
683 result += self._Int32(length_name)
686 "new %sArray(%s)" % (arraytype, ", ".join(args)),
687 fallback="new %sArray(8)" % arraytype))
689 def _JSArrayBufferView(self, name, recursion_budget):
690 if random.random() < 0.4:
691 return self._JSDataView(name, recursion_budget)
693 return self._JSTypedArray(name, recursion_budget)
695 def _JSWeakCollection(self, name, recursion_budget):
696 ctor = random.choice([self._JSMap, self._JSSet])
697 return ctor(name, recursion_budget, weak="Weak")
699 def _PropertyDetails(self, name, recursion_budget):
700 # TODO(jkummerow): Be more clever here?
701 return self._Int32(name)
703 def _JSObject(self, name, recursion_budget):
704 die = random.random()
706 function = random.choice([self._PlainObject, self._PlainArray,
707 self._PlainFunction])
709 return self._Variable(name, "this") # Global object.
711 function = random.choice([self._JSArrayBuffer, self._JSDataView,
712 self._JSDate, self._JSFunctionProxy,
713 self._JSGeneratorObject, self._JSMap,
714 self._JSMapIterator, self._JSRegExp,
715 self._JSSet, self._JSSetIterator,
716 self._JSTypedArray, self._JSValue,
717 self._JSWeakCollection])
718 result = function(name, recursion_budget)
719 self._AddAccessors(name, result, recursion_budget)
720 self._AddProperties(name, result, recursion_budget)
721 self._AddElements(name, result, recursion_budget)
724 def _JSReceiver(self, name, recursion_budget):
725 if random.random() < 0.9: return self._JSObject(name, recursion_budget)
726 return self._JSProxy(name, recursion_budget)
728 def _HeapObject(self, name, recursion_budget):
729 die = random.random()
730 if die < 0.9: return self._JSReceiver(name, recursion_budget)
731 elif die < 0.95: return self._Oddball(name, recursion_budget)
732 else: return self._Name(name, recursion_budget)
734 def _Object(self, name, recursion_budget):
735 if recursion_budget <= 0:
736 function = random.choice([self._Oddball, self._Number, self._Name,
737 self._JSValue, self._JSRegExp])
738 return function(name, recursion_budget)
739 if random.random() < 0.2:
740 return self._Smi(name, recursion_budget)
741 return self._HeapObject(name, recursion_budget)
744 "Boolean": ["true", _Boolean],
745 "HeapObject": ["new Object()", _HeapObject],
746 "Int32": ["32", _Int32],
747 "JSArray": ["new Array()", _JSArray],
748 "JSArrayBuffer": ["new ArrayBuffer(8)", _JSArrayBuffer],
749 "JSArrayBufferView": ["new Int32Array(2)", _JSArrayBufferView],
750 "JSDataView": ["new DataView(new ArrayBuffer(24))", _JSDataView],
751 "JSDate": ["new Date()", _JSDate],
752 "JSFunction": ["function() {}", _JSFunction],
753 "JSFunctionProxy": ["Proxy.createFunction({}, function() {})",
755 "JSGeneratorObject": ["(function*(){ yield 1; })()", _JSGeneratorObject],
756 "JSMap": ["new Map()", _JSMap],
757 "JSMapIterator": ["new Map().entries()", _JSMapIterator],
758 "JSObject": ["new Object()", _JSObject],
759 "JSProxy": ["Proxy.create({})", _JSProxy],
760 "JSReceiver": ["new Object()", _JSReceiver],
761 "JSRegExp": ["/ab/g", _JSRegExp],
762 "JSSet": ["new Set()", _JSSet],
763 "JSSetIterator": ["new Set().values()", _JSSetIterator],
764 "JSTypedArray": ["new Int32Array(2)", _JSTypedArray],
765 "JSValue": ["new String('foo')", _JSValue],
766 "JSWeakCollection": ["new WeakMap()", _JSWeakCollection],
767 "Name": ["\"name\"", _Name],
768 "Number": ["1.5", _Number],
769 "Object": ["new Object()", _Object],
770 "PropertyDetails": ["513", _PropertyDetails],
771 "SeqOneByteString": ["\"seq 1-byte\"", _SeqString],
772 "SeqString": ["\"seqstring\"", _SeqString],
773 "SeqTwoByteString": ["\"seq \\u2082-byte\"", _SeqTwoByteString],
775 "StrictMode": ["1", _StrictMode],
776 "String": ["\"foo\"", _String],
777 "Symbol": ["Symbol(\"symbol\")", _Symbol],
778 "Uint32": ["32", _Uint32],
782 class ArgParser(object):
783 def __init__(self, regex, ctor):
789 def __init__(self, typename, varname, index):
791 self.name = "_%s" % varname
795 class Function(object):
796 def __init__(self, match):
797 self.name = match.group(1)
802 handle_arg_parser = ArgParser(
803 re.compile("^\s*CONVERT_ARG_HANDLE_CHECKED\((\w+), (\w+), (\d+)\)"),
804 lambda match: Arg(match.group(1), match.group(2), int(match.group(3))))
806 plain_arg_parser = ArgParser(
807 re.compile("^\s*CONVERT_ARG_CHECKED\((\w+), (\w+), (\d+)\)"),
808 lambda match: Arg(match.group(1), match.group(2), int(match.group(3))))
810 number_handle_arg_parser = ArgParser(
811 re.compile("^\s*CONVERT_NUMBER_ARG_HANDLE_CHECKED\((\w+), (\d+)\)"),
812 lambda match: Arg("Number", match.group(1), int(match.group(2))))
814 smi_arg_parser = ArgParser(
815 re.compile("^\s*CONVERT_SMI_ARG_CHECKED\((\w+), (\d+)\)"),
816 lambda match: Arg("Smi", match.group(1), int(match.group(2))))
818 double_arg_parser = ArgParser(
819 re.compile("^\s*CONVERT_DOUBLE_ARG_CHECKED\((\w+), (\d+)\)"),
820 lambda match: Arg("Number", match.group(1), int(match.group(2))))
822 number_arg_parser = ArgParser(
824 "^\s*CONVERT_NUMBER_CHECKED\(\w+, (\w+), (\w+), args\[(\d+)\]\)"),
825 lambda match: Arg(match.group(2), match.group(1), int(match.group(3))))
827 strict_mode_arg_parser = ArgParser(
828 re.compile("^\s*CONVERT_STRICT_MODE_ARG_CHECKED\((\w+), (\d+)\)"),
829 lambda match: Arg("StrictMode", match.group(1), int(match.group(2))))
831 boolean_arg_parser = ArgParser(
832 re.compile("^\s*CONVERT_BOOLEAN_ARG_CHECKED\((\w+), (\d+)\)"),
833 lambda match: Arg("Boolean", match.group(1), int(match.group(2))))
835 property_details_parser = ArgParser(
836 re.compile("^\s*CONVERT_PROPERTY_DETAILS_CHECKED\((\w+), (\d+)\)"),
837 lambda match: Arg("PropertyDetails", match.group(1), int(match.group(2))))
839 arg_parsers = [handle_arg_parser, plain_arg_parser, number_handle_arg_parser,
841 double_arg_parser, number_arg_parser, strict_mode_arg_parser,
842 boolean_arg_parser, property_details_parser]
844 def SetArgsLength(self, match):
845 self.argslength = int(match.group(1))
847 def TryParseArg(self, line):
848 for parser in Function.arg_parsers:
849 match = parser.regex.match(line)
851 arg = parser.ArgCtor(match)
852 self.args[arg.index] = arg
857 return "%s.js" % self.name.lower()
861 argcount = self.argslength
863 print("WARNING: unknown argslength for function %s" % self.name)
865 argcount = max([self.args[i].index + 1 for i in self.args])
868 for i in range(argcount):
869 if i > 0: s.append(", ")
870 s.append(self.args[i].type if i in self.args else "<unknown>")
876 def __init__(self, match):
877 self.name = match.group(1)
878 self.args = [s.strip() for s in match.group(2).split(",")]
881 self.AddLine(match.group(3))
883 def AddLine(self, line):
886 # This is the first line, detect indentation.
887 self.indentation = len(line) - len(line.lstrip())
888 line = line.rstrip("\\\n ")
890 assert len(line[:self.indentation].strip()) == 0, \
891 ("expected whitespace: '%s', full line: '%s'" %
892 (line[:self.indentation], line))
893 line = line[self.indentation:]
895 self.lines.append(line + "\n")
898 for arg in self.args:
899 pattern = re.compile(r"(##|\b)%s(##|\b)" % arg)
900 for i in range(len(self.lines)):
901 self.lines[i] = re.sub(pattern, "%%(%s)s" % arg, self.lines[i])
903 def FillIn(self, arg_values):
905 assert len(arg_values) == len(self.args)
906 for i in range(len(self.args)):
907 filler[self.args[i]] = arg_values[i]
909 for line in self.lines:
910 result.append(line % filler)
914 # Parses HEADERFILENAME to find out which runtime functions are "inline".
915 def FindInlineRuntimeFunctions():
916 inline_functions = []
917 with open(HEADERFILENAME, "r") as f:
918 inline_list = "#define INLINE_FUNCTION_LIST(F) \\\n"
919 inline_function = re.compile(r"^\s*F\((\w+), \d+, \d+\)\s*\\?")
923 match = inline_function.match(line)
925 inline_functions.append(match.group(1))
926 if not line.endswith("\\\n"):
928 elif mode == "SEARCHING":
929 if line == inline_list:
931 return inline_functions
934 def ReadFileAndExpandMacros(filename):
937 with open(filename, "r") as f:
940 if found_macro is not None:
941 found_macro.AddLine(line)
942 if not line.endswith("\\\n"):
943 found_macro.Finalize()
947 match = MACRO.match(line)
949 found_macro = Macro(match)
950 if found_macro.name in EXPAND_MACROS:
951 found_macros[found_macro.name] = found_macro
956 match = FIRST_WORD.match(line)
958 first_word = match.group(1)
959 if first_word in found_macros:
960 MACRO_CALL = re.compile("%s\(([^)]*)\)" % first_word)
961 match = MACRO_CALL.match(line)
963 args = [s.strip() for s in match.group(1).split(",")]
964 expanded_lines += found_macros[first_word].FillIn(args)
967 expanded_lines.append(line)
968 return expanded_lines
971 # Detects runtime functions by parsing FILENAME.
972 def FindRuntimeFunctions():
973 inline_functions = FindInlineRuntimeFunctions()
975 expanded_lines = ReadFileAndExpandMacros(FILENAME)
978 for line in expanded_lines:
979 # Multi-line definition support, ignoring macros.
980 if line.startswith("RUNTIME_FUNCTION") and not line.endswith("{\n"):
981 if line.endswith("\\\n"): continue
982 partial_line = line.rstrip()
985 partial_line += " " + line.strip()
986 if partial_line.endswith("{"):
992 match = FUNCTION.match(line)
994 function = Function(match)
995 if function.name in inline_functions:
996 function.inline = "_"
998 if function is None: continue
1000 match = ARGSLENGTH.match(line)
1002 function.SetArgsLength(match)
1005 if function.TryParseArg(line):
1008 if line == FUNCTIONEND:
1009 if function is not None:
1010 functions.append(function)
1015 # Hack: This must have the same fields as class Function above, because the
1016 # two are used polymorphically in RunFuzzer(). We could use inheritance...
1017 class Builtin(object):
1018 def __init__(self, match):
1019 self.name = match.group(1)
1020 args = match.group(2)
1021 self.argslength = 0 if args == "" else args.count(",") + 1
1024 if self.argslength > 0:
1025 args = args.split(",")
1026 for i in range(len(args)):
1027 # a = args[i].strip() # TODO: filter out /* comments */ first.
1029 self.args[i] = Arg("Object", a, i)
1032 return "%s(%d)" % (self.name, self.argslength)
1035 def FindJSBuiltins():
1038 for (root, dirs, files) in os.walk(PATH):
1040 if f.endswith(".js"):
1041 fileslist.append(os.path.join(root, f))
1043 regexp = re.compile("^function (\w+)\s*\((.*?)\) {")
1045 for filename in fileslist:
1046 with open(filename, "r") as f:
1047 file_contents = f.read()
1048 file_contents = js2c.ExpandInlineMacros(file_contents)
1049 lines = file_contents.split("\n")
1052 if line.startswith("function") and not '{' in line:
1053 partial_line += line.rstrip()
1056 partial_line += " " + line.strip()
1062 match = regexp.match(line)
1064 builtins.append(Builtin(match))
1068 # Classifies runtime functions.
1069 def ClassifyFunctions(functions):
1070 # Can be fuzzed with a JavaScript testcase.
1071 js_fuzzable_functions = []
1072 # We have enough information to fuzz these, but they need inputs that
1073 # cannot be created or passed around in JavaScript.
1074 cctest_fuzzable_functions = []
1075 # This script does not have enough information about these.
1076 unknown_functions = []
1080 if f.name in BLACKLISTED:
1082 decision = js_fuzzable_functions
1083 custom = CUSTOM_KNOWN_GOOD_INPUT.get(f.name, None)
1084 if f.argslength < 0:
1085 # Unknown length -> give up unless there's a custom definition.
1086 if custom and custom[-1] is not None:
1087 f.argslength = custom[-1]
1088 assert len(custom) == f.argslength + 1, \
1089 ("%s: last custom definition must be argslength" % f.name)
1091 decision = unknown_functions
1094 # Any custom definitions must match the known argslength.
1095 assert len(custom) == f.argslength + 1, \
1096 ("%s should have %d custom definitions but has %d" %
1097 (f.name, f.argslength + 1, len(custom)))
1098 for i in range(f.argslength):
1099 if custom and custom[i] is not None:
1100 # All good, there's a custom definition.
1102 elif not i in f.args:
1103 # No custom definition and no parse result -> give up.
1104 decision = unknown_functions
1107 if t in NON_JS_TYPES:
1108 decision = cctest_fuzzable_functions
1110 assert Generator.IsTypeSupported(t), \
1111 ("type generator not found for %s, function: %s" % (t, f))
1113 return (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions)
1116 def _GetKnownGoodArgs(function, generator):
1117 custom_input = CUSTOM_KNOWN_GOOD_INPUT.get(function.name, None)
1120 for i in range(function.argslength):
1121 if custom_input and custom_input[i] is not None:
1123 definitions.append("var %s = %s;" % (name, custom_input[i]))
1125 arg = function.args[i]
1127 definitions += generator.RandomVariable(name, arg.type, simple=True)
1128 argslist.append(name)
1129 return (definitions, argslist)
1132 def _GenerateTestcase(function, definitions, argslist, throws):
1133 s = ["// Copyright 2014 the V8 project authors. All rights reserved.",
1134 "// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY",
1135 "// Flags: --allow-natives-syntax --harmony --harmony-proxies"
1137 call = "%%%s%s(%s);" % (function.inline, function.name, ", ".join(argslist))
1141 s.append("} catch(e) {}")
1144 testcase = "\n".join(s)
1148 def GenerateJSTestcaseForFunction(function):
1150 (definitions, argslist) = _GetKnownGoodArgs(function, gen)
1151 testcase = _GenerateTestcase(function, definitions, argslist,
1152 function.name in THROWS)
1153 path = os.path.join(BASEPATH, function.Filename())
1154 with open(path, "w") as f:
1155 f.write("%s\n" % testcase)
1158 def GenerateTestcases(functions):
1159 shutil.rmtree(BASEPATH) # Re-generate everything.
1160 os.makedirs(BASEPATH)
1162 GenerateJSTestcaseForFunction(f)
1165 def _SaveFileName(save_path, process_id, save_file_index):
1166 return "%s/fuzz_%d_%d.js" % (save_path, process_id, save_file_index)
1169 def _GetFuzzableRuntimeFunctions():
1170 functions = FindRuntimeFunctions()
1171 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \
1172 ClassifyFunctions(functions)
1173 return js_fuzzable_functions
1176 FUZZ_TARGET_LISTS = {
1177 "runtime": _GetFuzzableRuntimeFunctions,
1178 "builtins": FindJSBuiltins,
1182 def RunFuzzer(process_id, options, stop_running):
1183 MAX_SLEEP_TIME = 0.1
1184 INITIAL_SLEEP_TIME = 0.001
1185 SLEEP_TIME_FACTOR = 1.25
1186 base_file_name = "/dev/shm/runtime_fuzz_%d" % process_id
1187 test_file_name = "%s.js" % base_file_name
1188 stderr_file_name = "%s.out" % base_file_name
1190 while os.path.exists(_SaveFileName(options.save_path, process_id,
1192 save_file_index += 1
1194 targets = FUZZ_TARGET_LISTS[options.fuzz_target]()
1196 for i in range(options.num_tests):
1197 if stop_running.is_set(): break
1199 while function is None or function.argslength == 0:
1200 function = random.choice(targets)
1204 for i in range(function.argslength):
1205 arg = function.args[i]
1206 argname = "arg%d%s" % (i, arg.name)
1207 args.append(argname)
1208 definitions += gen.RandomVariable(argname, arg.type, simple=False)
1209 testcase = _GenerateTestcase(function, definitions, args, True)
1210 with open(test_file_name, "w") as f:
1211 f.write("%s\n" % testcase)
1212 with open("/dev/null", "w") as devnull:
1213 with open(stderr_file_name, "w") as stderr:
1214 process = subprocess.Popen(
1215 [options.binary, "--allow-natives-syntax", "--harmony",
1216 "--harmony-proxies", "--enable-slow-asserts", test_file_name],
1217 stdout=devnull, stderr=stderr)
1218 end_time = time.time() + options.timeout
1221 sleep_time = INITIAL_SLEEP_TIME
1222 while exit_code is None:
1223 if time.time() >= end_time:
1224 # Kill the process and wait for it to exit.
1225 os.kill(process.pid, signal.SIGTERM)
1226 exit_code = process.wait()
1229 exit_code = process.poll()
1230 time.sleep(sleep_time)
1231 sleep_time = sleep_time * SLEEP_TIME_FACTOR
1232 if sleep_time > MAX_SLEEP_TIME:
1233 sleep_time = MAX_SLEEP_TIME
1234 if exit_code != 0 and not timed_out:
1236 with open(stderr_file_name, "r") as stderr:
1238 if line.strip() == "# Allocation failed - process out of memory":
1242 save_name = _SaveFileName(options.save_path, process_id,
1244 shutil.copyfile(test_file_name, save_name)
1245 save_file_index += 1
1246 except KeyboardInterrupt:
1249 if os.path.exists(test_file_name):
1250 os.remove(test_file_name)
1251 if os.path.exists(stderr_file_name):
1252 os.remove(stderr_file_name)
1255 def BuildOptionParser():
1256 usage = """Usage: %%prog [options] ACTION
1258 where ACTION can be:
1260 info Print diagnostic info.
1261 check Check that runtime functions can be parsed as expected, and that
1263 generate Parse source code for runtime functions, and auto-generate
1264 test cases for them. Warning: this will nuke and re-create
1266 fuzz Generate fuzz tests, run them, save those that crashed (see options).
1267 """ % {"path": os.path.relpath(BASEPATH)}
1269 o = optparse.OptionParser(usage=usage)
1270 o.add_option("--binary", default="out/x64.debug/d8",
1271 help="d8 binary used for running fuzz tests (default: %default)")
1272 o.add_option("--fuzz-target", default="runtime",
1273 help="Set of functions targeted by fuzzing. Allowed values: "
1274 "%s (default: %%default)" % ", ".join(FUZZ_TARGET_LISTS))
1275 o.add_option("-n", "--num-tests", default=1000, type="int",
1276 help="Number of fuzz tests to generate per worker process"
1277 " (default: %default)")
1278 o.add_option("--save-path", default="~/runtime_fuzz_output",
1279 help="Path to directory where failing tests will be stored"
1280 " (default: %default)")
1281 o.add_option("--timeout", default=20, type="int",
1282 help="Timeout for each fuzz test (in seconds, default:"
1287 def ProcessOptions(options, args):
1288 options.save_path = os.path.expanduser(options.save_path)
1289 if options.fuzz_target not in FUZZ_TARGET_LISTS:
1290 print("Invalid fuzz target: %s" % options.fuzz_target)
1292 if len(args) != 1 or args[0] == "help":
1298 parser = BuildOptionParser()
1299 (options, args) = parser.parse_args()
1301 if not ProcessOptions(options, args):
1306 functions = FindRuntimeFunctions()
1307 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \
1308 ClassifyFunctions(functions)
1309 builtins = FindJSBuiltins()
1311 if action == "test":
1312 print("put your temporary debugging code here")
1315 if action == "info":
1316 print("%d functions total; js_fuzzable_functions: %d, "
1317 "cctest_fuzzable_functions: %d, unknown_functions: %d"
1318 % (len(functions), len(js_fuzzable_functions),
1319 len(cctest_fuzzable_functions), len(unknown_functions)))
1320 print("%d JavaScript builtins" % len(builtins))
1321 print("unknown functions:")
1322 for f in unknown_functions:
1326 if action == "check":
1329 def CheckCount(actual, expected, description):
1330 if len(actual) != expected:
1331 print("Expected to detect %d %s, but found %d." % (
1332 expected, description, len(actual)))
1333 print("If this change is intentional, please update the expectations"
1334 " at the top of %s." % THIS_SCRIPT)
1338 errors += CheckCount(functions, EXPECTED_FUNCTION_COUNT,
1339 "functions in total")
1340 errors += CheckCount(js_fuzzable_functions, EXPECTED_FUZZABLE_COUNT,
1341 "JavaScript-fuzzable functions")
1342 errors += CheckCount(cctest_fuzzable_functions, EXPECTED_CCTEST_COUNT,
1343 "cctest-fuzzable functions")
1344 errors += CheckCount(unknown_functions, EXPECTED_UNKNOWN_COUNT,
1345 "functions with incomplete type information")
1346 errors += CheckCount(builtins, EXPECTED_BUILTINS_COUNT,
1347 "JavaScript builtins")
1349 def CheckTestcasesExisting(functions):
1352 if not os.path.isfile(os.path.join(BASEPATH, f.Filename())):
1353 print("Missing testcase for %s, please run '%s generate'" %
1354 (f.name, THIS_SCRIPT))
1356 files = filter(lambda filename: not filename.startswith("."),
1357 os.listdir(BASEPATH))
1358 if (len(files) != len(functions)):
1359 unexpected_files = set(files) - set([f.Filename() for f in functions])
1360 for f in unexpected_files:
1361 print("Unexpected testcase: %s" % os.path.join(BASEPATH, f))
1363 print("Run '%s generate' to automatically clean these up."
1367 errors += CheckTestcasesExisting(js_fuzzable_functions)
1369 def CheckNameClashes(runtime_functions, builtins):
1372 for f in runtime_functions:
1373 runtime_map[f.name] = 1
1375 if b.name in runtime_map:
1376 print("Builtin/Runtime_Function name clash: %s" % b.name)
1380 errors += CheckNameClashes(functions, builtins)
1384 print("Generated runtime tests: all good.")
1387 if action == "generate":
1388 GenerateTestcases(js_fuzzable_functions)
1391 if action == "fuzz":
1393 if not os.path.isdir(options.save_path):
1394 os.makedirs(options.save_path)
1395 stop_running = multiprocessing.Event()
1396 for i in range(multiprocessing.cpu_count()):
1397 args = (i, options, stop_running)
1398 p = multiprocessing.Process(target=RunFuzzer, args=args)
1402 for i in range(len(processes)):
1404 except KeyboardInterrupt:
1406 for i in range(len(processes)):
1410 if __name__ == "__main__":