1 #!/usr/bin/env python 2 # Copyright 2014 the V8 project authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 import itertools 7 import js2c 8 import multiprocessing 9 import optparse 10 import os 11 import random 12 import re 13 import shutil 14 import signal 15 import string 16 import subprocess 17 import sys 18 import time 19 20 FILENAME = "src/runtime.cc" 21 HEADERFILENAME = "src/runtime.h" 22 FUNCTION = re.compile("^RUNTIME_FUNCTION\(Runtime_(\w+)") 23 ARGSLENGTH = re.compile(".*ASSERT\(.*args\.length\(\) == (\d+)\);") 24 FUNCTIONEND = "}\n" 25 MACRO = re.compile(r"^#define ([^ ]+)\(([^)]*)\) *([^\\]*)\\?\n$") 26 FIRST_WORD = re.compile("^\s*(.*?)[\s({\[]") 27 28 WORKSPACE = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "..")) 29 BASEPATH = os.path.join(WORKSPACE, "test", "mjsunit", "runtime-gen") 30 THIS_SCRIPT = os.path.relpath(sys.argv[0]) 31 32 # Expand these macros, they define further runtime functions. 33 EXPAND_MACROS = [ 34 "BUFFER_VIEW_GETTER", 35 "DATA_VIEW_GETTER", 36 "DATA_VIEW_SETTER", 37 "RUNTIME_UNARY_MATH", 38 ] 39 # TODO(jkummerow): We could also whitelist the following macros, but the 40 # functions they define are so trivial that it's unclear how much benefit 41 # that would provide: 42 # ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION 43 # FIXED_TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION 44 # TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION 45 46 # Counts of functions in each detection state. These are used to assert 47 # that the parser doesn't bit-rot. Change the values as needed when you add, 48 # remove or change runtime functions, but make sure we don't lose our ability 49 # to parse them! 50 EXPECTED_FUNCTION_COUNT = 358 51 EXPECTED_FUZZABLE_COUNT = 326 52 EXPECTED_CCTEST_COUNT = 6 53 EXPECTED_UNKNOWN_COUNT = 4 54 EXPECTED_BUILTINS_COUNT = 798 55 56 57 # Don't call these at all. 58 BLACKLISTED = [ 59 "Abort", # Kills the process. 60 "AbortJS", # Kills the process. 61 "CompileForOnStackReplacement", # Riddled with ASSERTs. 62 "IS_VAR", # Not implemented in the runtime. 63 "ListNatives", # Not available in Release mode. 64 "SetAllocationTimeout", # Too slow for fuzzing. 65 "SystemBreak", # Kills (int3) the process. 66 67 # These are weird. They violate some invariants when called after 68 # bootstrapping. 69 "DisableAccessChecks", 70 "EnableAccessChecks", 71 72 # The current LiveEdit implementation relies on and messes with internals 73 # in ways that makes it fundamentally unfuzzable :-( 74 "DebugGetLoadedScripts", 75 "DebugSetScriptSource", 76 "LiveEditFindSharedFunctionInfosForScript", 77 "LiveEditFunctionSourceUpdated", 78 "LiveEditGatherCompileInfo", 79 "LiveEditPatchFunctionPositions", 80 "LiveEditReplaceFunctionCode", 81 "LiveEditReplaceRefToNestedFunction", 82 "LiveEditReplaceScript", 83 "LiveEditRestartFrame", 84 "SetScriptBreakPoint", 85 86 # TODO(jkummerow): Fix these and un-blacklist them! 87 "CreateDateTimeFormat", 88 "CreateNumberFormat", 89 ] 90 91 92 # These will always throw. 93 THROWS = [ 94 "CheckExecutionState", # Needs to hit a break point. 95 "CheckIsBootstrapping", # Needs to be bootstrapping. 96 "DebugEvaluate", # Needs to hit a break point. 97 "DebugEvaluateGlobal", # Needs to hit a break point. 98 "DebugIndexedInterceptorElementValue", # Needs an indexed interceptor. 99 "DebugNamedInterceptorPropertyValue", # Needs a named interceptor. 100 "DebugSetScriptSource", # Checks compilation state of script. 101 "GetAllScopesDetails", # Needs to hit a break point. 102 "GetFrameCount", # Needs to hit a break point. 103 "GetFrameDetails", # Needs to hit a break point. 104 "GetRootNaN", # Needs to be bootstrapping. 105 "GetScopeCount", # Needs to hit a break point. 106 "GetScopeDetails", # Needs to hit a break point. 107 "GetStepInPositions", # Needs to hit a break point. 108 "GetTemplateField", # Needs a {Function,Object}TemplateInfo. 109 "GetThreadCount", # Needs to hit a break point. 110 "GetThreadDetails", # Needs to hit a break point. 111 "IsAccessAllowedForObserver", # Needs access-check-required object. 112 "UnblockConcurrentRecompilation" # Needs --block-concurrent-recompilation. 113 ] 114 115 116 # Definitions used in CUSTOM_KNOWN_GOOD_INPUT below. 117 _BREAK_ITERATOR = ( 118 "%GetImplFromInitializedIntlObject(new Intl.v8BreakIterator())") 119 _COLLATOR = "%GetImplFromInitializedIntlObject(new Intl.Collator('en-US'))" 120 _DATETIME_FORMAT = ( 121 "%GetImplFromInitializedIntlObject(new Intl.DateTimeFormat('en-US'))") 122 _NUMBER_FORMAT = ( 123 "%GetImplFromInitializedIntlObject(new Intl.NumberFormat('en-US'))") 124 125 126 # Custom definitions for function input that does not throw. 127 # Format: "FunctionName": ["arg0", "arg1", ..., argslength]. 128 # None means "fall back to autodetected value". 129 CUSTOM_KNOWN_GOOD_INPUT = { 130 "Apply": ["function() {}", None, None, None, None, None], 131 "ArrayBufferSliceImpl": [None, None, 0, None], 132 "ArrayConcat": ["[1, 'a']", None], 133 "BreakIteratorAdoptText": [_BREAK_ITERATOR, None, None], 134 "BreakIteratorBreakType": [_BREAK_ITERATOR, None], 135 "BreakIteratorCurrent": [_BREAK_ITERATOR, None], 136 "BreakIteratorFirst": [_BREAK_ITERATOR, None], 137 "BreakIteratorNext": [_BREAK_ITERATOR, None], 138 "CompileString": [None, "false", None], 139 "CreateBreakIterator": ["'en-US'", "{type: 'string'}", None, None], 140 "CreateJSFunctionProxy": [None, "function() {}", None, None, None], 141 "CreatePrivateSymbol": ["\"foo\"", None], 142 "CreateSymbol": ["\"foo\"", None], 143 "DateParseString": [None, "new Array(8)", None], 144 "DefineOrRedefineAccessorProperty": [None, None, "function() {}", 145 "function() {}", 2, None], 146 "FunctionBindArguments": [None, None, "undefined", None, None], 147 "GetBreakLocations": [None, 0, None], 148 "GetDefaultReceiver": ["function() {}", None], 149 "GetImplFromInitializedIntlObject": ["new Intl.NumberFormat('en-US')", None], 150 "InternalCompare": [_COLLATOR, None, None, None], 151 "InternalDateFormat": [_DATETIME_FORMAT, None, None], 152 "InternalDateParse": [_DATETIME_FORMAT, None, None], 153 "InternalNumberFormat": [_NUMBER_FORMAT, None, None], 154 "InternalNumberParse": [_NUMBER_FORMAT, None, None], 155 "IsSloppyModeFunction": ["function() {}", None], 156 "LoadMutableDouble": ["{foo: 1.2}", None, None], 157 "NewObjectFromBound": ["(function() {}).bind({})", None], 158 "NumberToRadixString": [None, "2", None], 159 "ParseJson": ["\"{}\"", 1], 160 "RegExpExecMultiple": [None, None, "['a']", "['a']", None], 161 "SetAccessorProperty": [None, None, "undefined", "undefined", None, None, 162 None], 163 "SetIteratorInitialize": [None, None, "2", None], 164 "SetDebugEventListener": ["undefined", None, None], 165 "SetFunctionBreakPoint": [None, 200, None, None], 166 "StringBuilderConcat": ["[1, 2, 3]", 3, None, None], 167 "StringBuilderJoin": ["['a', 'b']", 4, None, None], 168 "StringMatch": [None, None, "['a', 'b']", None], 169 "StringNormalize": [None, 2, None], 170 "StringReplaceGlobalRegExpWithString": [None, None, None, "['a']", None], 171 "TypedArrayInitialize": [None, 6, "new ArrayBuffer(8)", None, 4, None], 172 "TypedArrayInitializeFromArrayLike": [None, 6, None, None, None], 173 "TypedArraySetFastCases": [None, None, "0", None], 174 } 175 176 177 # Types of arguments that cannot be generated in a JavaScript testcase. 178 NON_JS_TYPES = [ 179 "Code", "Context", "FixedArray", "FunctionTemplateInfo", 180 "JSFunctionResultCache", "JSMessageObject", "Map", "ScopeInfo", 181 "SharedFunctionInfo"] 182 183 184 class Generator(object): 185 186 def RandomVariable(self, varname, vartype, simple): 187 if simple: 188 return self._Variable(varname, self.GENERATORS[vartype][0]) 189 return self.GENERATORS[vartype][1](self, varname, 190 self.DEFAULT_RECURSION_BUDGET) 191 192 @staticmethod 193 def IsTypeSupported(typename): 194 return typename in Generator.GENERATORS 195 196 USUAL_SUSPECT_PROPERTIES = ["size", "length", "byteLength", "__proto__", 197 "prototype", "0", "1", "-1"] 198 DEFAULT_RECURSION_BUDGET = 2 199 PROXY_TRAPS = """{ 200 getOwnPropertyDescriptor: function(name) { 201 return {value: function() {}, configurable: true, writable: true, 202 enumerable: true}; 203 }, 204 getPropertyDescriptor: function(name) { 205 return {value: function() {}, configurable: true, writable: true, 206 enumerable: true}; 207 }, 208 getOwnPropertyNames: function() { return []; }, 209 getPropertyNames: function() { return []; }, 210 defineProperty: function(name, descriptor) {}, 211 delete: function(name) { return true; }, 212 fix: function() {} 213 }""" 214 215 def _Variable(self, name, value, fallback=None): 216 args = { "name": name, "value": value, "fallback": fallback } 217 if fallback: 218 wrapper = "try { %%s } catch(e) { var %(name)s = %(fallback)s; }" % args 219 else: 220 wrapper = "%s" 221 return [wrapper % ("var %(name)s = %(value)s;" % args)] 222 223 def _Boolean(self, name, recursion_budget): 224 return self._Variable(name, random.choice(["true", "false"])) 225 226 def _Oddball(self, name, recursion_budget): 227 return self._Variable(name, 228 random.choice(["true", "false", "undefined", "null"])) 229 230 def _StrictMode(self, name, recursion_budget): 231 return self._Variable(name, random.choice([0, 1])) 232 233 def _Int32(self, name, recursion_budget=0): 234 die = random.random() 235 if die < 0.5: 236 value = random.choice([-3, -1, 0, 1, 2, 10, 515, 0x3fffffff, 0x7fffffff, 237 0x40000000, -0x40000000, -0x80000000]) 238 elif die < 0.75: 239 value = random.randint(-1000, 1000) 240 else: 241 value = random.randint(-0x80000000, 0x7fffffff) 242 return self._Variable(name, value) 243 244 def _Uint32(self, name, recursion_budget=0): 245 die = random.random() 246 if die < 0.5: 247 value = random.choice([0, 1, 2, 3, 4, 8, 0x3fffffff, 0x40000000, 248 0x7fffffff, 0xffffffff]) 249 elif die < 0.75: 250 value = random.randint(0, 1000) 251 else: 252 value = random.randint(0, 0xffffffff) 253 return self._Variable(name, value) 254 255 def _Smi(self, name, recursion_budget): 256 die = random.random() 257 if die < 0.5: 258 value = random.choice([-5, -1, 0, 1, 2, 3, 0x3fffffff, -0x40000000]) 259 elif die < 0.75: 260 value = random.randint(-1000, 1000) 261 else: 262 value = random.randint(-0x40000000, 0x3fffffff) 263 return self._Variable(name, value) 264 265 def _Number(self, name, recursion_budget): 266 die = random.random() 267 if die < 0.5: 268 return self._Smi(name, recursion_budget) 269 elif die < 0.6: 270 value = random.choice(["Infinity", "-Infinity", "NaN", "-0", 271 "1.7976931348623157e+308", # Max value. 272 "2.2250738585072014e-308", # Min value. 273 "4.9406564584124654e-324"]) # Min subnormal. 274 else: 275 value = random.lognormvariate(0, 15) 276 return self._Variable(name, value) 277 278 def _RawRandomString(self, minlength=0, maxlength=100, 279 alphabet=string.ascii_letters): 280 length = random.randint(minlength, maxlength) 281 result = "" 282 for i in xrange(length): 283 result += random.choice(alphabet) 284 return result 285 286 def _SeqString(self, name, recursion_budget): 287 s1 = self._RawRandomString(1, 5) 288 s2 = self._RawRandomString(1, 5) 289 # 'foo' + 'bar' 290 return self._Variable(name, "\"%s\" + \"%s\"" % (s1, s2)) 291 292 def _SeqTwoByteString(self, name): 293 s1 = self._RawRandomString(1, 5) 294 s2 = self._RawRandomString(1, 5) 295 # 'foo' + unicode + 'bar' 296 return self._Variable(name, "\"%s\" + \"\\2082\" + \"%s\"" % (s1, s2)) 297 298 def _SlicedString(self, name): 299 s = self._RawRandomString(20, 30) 300 # 'ffoo12345678901234567890'.substr(1) 301 return self._Variable(name, "\"%s\".substr(1)" % s) 302 303 def _ConsString(self, name): 304 s1 = self._RawRandomString(8, 15) 305 s2 = self._RawRandomString(8, 15) 306 # 'foo12345' + (function() { return 'bar12345';})() 307 return self._Variable(name, 308 "\"%s\" + (function() { return \"%s\";})()" % (s1, s2)) 309 310 def _InternalizedString(self, name): 311 return self._Variable(name, "\"%s\"" % self._RawRandomString(0, 20)) 312 313 def _String(self, name, recursion_budget): 314 die = random.random() 315 if die < 0.5: 316 string = random.choice(self.USUAL_SUSPECT_PROPERTIES) 317 return self._Variable(name, "\"%s\"" % string) 318 elif die < 0.6: 319 number_name = name + "_number" 320 result = self._Number(number_name, recursion_budget) 321 return result + self._Variable(name, "\"\" + %s" % number_name) 322 elif die < 0.7: 323 return self._SeqString(name, recursion_budget) 324 elif die < 0.8: 325 return self._ConsString(name) 326 elif die < 0.9: 327 return self._InternalizedString(name) 328 else: 329 return self._SlicedString(name) 330 331 def _Symbol(self, name, recursion_budget): 332 raw_string_name = name + "_1" 333 result = self._String(raw_string_name, recursion_budget) 334 return result + self._Variable(name, "Symbol(%s)" % raw_string_name) 335 336 def _Name(self, name, recursion_budget): 337 if random.random() < 0.2: 338 return self._Symbol(name, recursion_budget) 339 return self._String(name, recursion_budget) 340 341 def _JSValue(self, name, recursion_budget): 342 die = random.random() 343 raw_name = name + "_1" 344 if die < 0.33: 345 result = self._String(raw_name, recursion_budget) 346 return result + self._Variable(name, "new String(%s)" % raw_name) 347 elif die < 0.66: 348 result = self._Boolean(raw_name, recursion_budget) 349 return result + self._Variable(name, "new Boolean(%s)" % raw_name) 350 else: 351 result = self._Number(raw_name, recursion_budget) 352 return result + self._Variable(name, "new Number(%s)" % raw_name) 353 354 def _RawRandomPropertyName(self): 355 if random.random() < 0.5: 356 return random.choice(self.USUAL_SUSPECT_PROPERTIES) 357 return self._RawRandomString(0, 10) 358 359 def _AddProperties(self, name, result, recursion_budget): 360 propcount = random.randint(0, 3) 361 propname = None 362 for i in range(propcount): 363 die = random.random() 364 if die < 0.5: 365 propname = "%s_prop%d" % (name, i) 366 result += self._Name(propname, recursion_budget - 1) 367 else: 368 propname = "\"%s\"" % self._RawRandomPropertyName() 369 propvalue_name = "%s_val%d" % (name, i) 370 result += self._Object(propvalue_name, recursion_budget - 1) 371 result.append("try { %s[%s] = %s; } catch (e) {}" % 372 (name, propname, propvalue_name)) 373 if random.random() < 0.2 and propname: 374 # Force the object to slow mode. 375 result.append("delete %s[%s];" % (name, propname)) 376 377 def _RandomElementIndex(self, element_name, result): 378 if random.random() < 0.5: 379 return random.randint(-1000, 1000) 380 result += self._Smi(element_name, 0) 381 return element_name 382 383 def _AddElements(self, name, result, recursion_budget): 384 elementcount = random.randint(0, 3) 385 for i in range(elementcount): 386 element_name = "%s_idx%d" % (name, i) 387 index = self._RandomElementIndex(element_name, result) 388 value_name = "%s_elt%d" % (name, i) 389 result += self._Object(value_name, recursion_budget - 1) 390 result.append("try { %s[%s] = %s; } catch(e) {}" % 391 (name, index, value_name)) 392 393 def _AddAccessors(self, name, result, recursion_budget): 394 accessorcount = random.randint(0, 3) 395 for i in range(accessorcount): 396 propname = self._RawRandomPropertyName() 397 what = random.choice(["get", "set"]) 398 function_name = "%s_access%d" % (name, i) 399 result += self._PlainFunction(function_name, recursion_budget - 1) 400 result.append("try { Object.defineProperty(%s, \"%s\", {%s: %s}); } " 401 "catch (e) {}" % (name, propname, what, function_name)) 402 403 def _PlainArray(self, name, recursion_budget): 404 die = random.random() 405 if die < 0.5: 406 literal = random.choice(["[]", "[1, 2]", "[1.5, 2.5]", 407 "['a', 'b', 1, true]"]) 408 return self._Variable(name, literal) 409 else: 410 new = random.choice(["", "new "]) 411 length = random.randint(0, 101000) 412 return self._Variable(name, "%sArray(%d)" % (new, length)) 413 414 def _PlainObject(self, name, recursion_budget): 415 die = random.random() 416 if die < 0.67: 417 literal_propcount = random.randint(0, 3) 418 properties = [] 419 result = [] 420 for i in range(literal_propcount): 421 propname = self._RawRandomPropertyName() 422 propvalue_name = "%s_lit%d" % (name, i) 423 result += self._Object(propvalue_name, recursion_budget - 1) 424 properties.append("\"%s\": %s" % (propname, propvalue_name)) 425 return result + self._Variable(name, "{%s}" % ", ".join(properties)) 426 else: 427 return self._Variable(name, "new Object()") 428 429 def _JSArray(self, name, recursion_budget): 430 result = self._PlainArray(name, recursion_budget) 431 self._AddAccessors(name, result, recursion_budget) 432 self._AddProperties(name, result, recursion_budget) 433 self._AddElements(name, result, recursion_budget) 434 return result 435 436 def _RawRandomBufferLength(self): 437 if random.random() < 0.2: 438 return random.choice([0, 1, 8, 0x40000000, 0x80000000]) 439 return random.randint(0, 1000) 440 441 def _JSArrayBuffer(self, name, recursion_budget): 442 length = self._RawRandomBufferLength() 443 return self._Variable(name, "new ArrayBuffer(%d)" % length) 444 445 def _JSDataView(self, name, recursion_budget): 446 buffer_name = name + "_buffer" 447 result = self._JSArrayBuffer(buffer_name, recursion_budget) 448 args = [buffer_name] 449 die = random.random() 450 if die < 0.67: 451 offset = self._RawRandomBufferLength() 452 args.append("%d" % offset) 453 if die < 0.33: 454 length = self._RawRandomBufferLength() 455 args.append("%d" % length) 456 result += self._Variable(name, "new DataView(%s)" % ", ".join(args), 457 fallback="new DataView(new ArrayBuffer(8))") 458 return result 459 460 def _JSDate(self, name, recursion_budget): 461 die = random.random() 462 if die < 0.25: 463 return self._Variable(name, "new Date()") 464 elif die < 0.5: 465 ms_name = name + "_ms" 466 result = self._Number(ms_name, recursion_budget) 467 return result + self._Variable(name, "new Date(%s)" % ms_name) 468 elif die < 0.75: 469 str_name = name + "_str" 470 month = random.choice(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", 471 "Aug", "Sep", "Oct", "Nov", "Dec"]) 472 day = random.randint(1, 28) 473 year = random.randint(1900, 2100) 474 hour = random.randint(0, 23) 475 minute = random.randint(0, 59) 476 second = random.randint(0, 59) 477 str_value = ("\"%s %s, %s %s:%s:%s\"" % 478 (month, day, year, hour, minute, second)) 479 result = self._Variable(str_name, str_value) 480 return result + self._Variable(name, "new Date(%s)" % str_name) 481 else: 482 components = tuple(map(lambda x: "%s_%s" % (name, x), 483 ["y", "m", "d", "h", "min", "s", "ms"])) 484 return ([j for i in map(self._Int32, components) for j in i] + 485 self._Variable(name, "new Date(%s)" % ", ".join(components))) 486 487 def _PlainFunction(self, name, recursion_budget): 488 result_name = "result" 489 body = ["function() {"] 490 body += self._Object(result_name, recursion_budget - 1) 491 body.append("return result;\n}") 492 return self._Variable(name, "%s" % "\n".join(body)) 493 494 def _JSFunction(self, name, recursion_budget): 495 result = self._PlainFunction(name, recursion_budget) 496 self._AddAccessors(name, result, recursion_budget) 497 self._AddProperties(name, result, recursion_budget) 498 self._AddElements(name, result, recursion_budget) 499 return result 500 501 def _JSFunctionProxy(self, name, recursion_budget): 502 # TODO(jkummerow): Revisit this as the Proxy implementation evolves. 503 return self._Variable(name, "Proxy.createFunction(%s, function() {})" % 504 self.PROXY_TRAPS) 505 506 def _JSGeneratorObject(self, name, recursion_budget): 507 # TODO(jkummerow): Be more creative here? 508 return self._Variable(name, "(function*() { yield 1; })()") 509 510 def _JSMap(self, name, recursion_budget, weak=""): 511 result = self._Variable(name, "new %sMap()" % weak) 512 num_entries = random.randint(0, 3) 513 for i in range(num_entries): 514 key_name = "%s_k%d" % (name, i) 515 value_name = "%s_v%d" % (name, i) 516 if weak: 517 result += self._JSObject(key_name, recursion_budget - 1) 518 else: 519 result += self._Object(key_name, recursion_budget - 1) 520 result += self._Object(value_name, recursion_budget - 1) 521 result.append("%s.set(%s, %s)" % (name, key_name, value_name)) 522 return result 523 524 def _JSMapIterator(self, name, recursion_budget): 525 map_name = name + "_map" 526 result = self._JSMap(map_name, recursion_budget) 527 iterator_type = random.choice(['keys', 'values', 'entries']) 528 return (result + self._Variable(name, "%s.%s()" % 529 (map_name, iterator_type))) 530 531 def _JSProxy(self, name, recursion_budget): 532 # TODO(jkummerow): Revisit this as the Proxy implementation evolves. 533 return self._Variable(name, "Proxy.create(%s)" % self.PROXY_TRAPS) 534 535 def _JSRegExp(self, name, recursion_budget): 536 flags = random.choice(["", "g", "i", "m", "gi"]) 537 string = "a(b|c)*a" # TODO(jkummerow): Be more creative here? 538 ctor = random.choice(["/%s/%s", "new RegExp(\"%s\", \"%s\")"]) 539 return self._Variable(name, ctor % (string, flags)) 540 541 def _JSSet(self, name, recursion_budget, weak=""): 542 result = self._Variable(name, "new %sSet()" % weak) 543 num_entries = random.randint(0, 3) 544 for i in range(num_entries): 545 element_name = "%s_e%d" % (name, i) 546 if weak: 547 result += self._JSObject(element_name, recursion_budget - 1) 548 else: 549 result += self._Object(element_name, recursion_budget - 1) 550 result.append("%s.add(%s)" % (name, element_name)) 551 return result 552 553 def _JSSetIterator(self, name, recursion_budget): 554 set_name = name + "_set" 555 result = self._JSSet(set_name, recursion_budget) 556 iterator_type = random.choice(['values', 'entries']) 557 return (result + self._Variable(name, "%s.%s()" % 558 (set_name, iterator_type))) 559 560 def _JSTypedArray(self, name, recursion_budget): 561 arraytype = random.choice(["Int8", "Int16", "Int32", "Uint8", "Uint16", 562 "Uint32", "Float32", "Float64", "Uint8Clamped"]) 563 ctor_type = random.randint(0, 3) 564 if ctor_type == 0: 565 length = random.randint(0, 1000) 566 return self._Variable(name, "new %sArray(%d)" % (arraytype, length), 567 fallback="new %sArray(8)" % arraytype) 568 elif ctor_type == 1: 569 input_name = name + "_typedarray" 570 result = self._JSTypedArray(input_name, recursion_budget - 1) 571 return (result + 572 self._Variable(name, "new %sArray(%s)" % (arraytype, input_name), 573 fallback="new %sArray(8)" % arraytype)) 574 elif ctor_type == 2: 575 arraylike_name = name + "_arraylike" 576 result = self._JSObject(arraylike_name, recursion_budget - 1) 577 length = random.randint(0, 1000) 578 result.append("try { %s.length = %d; } catch(e) {}" % 579 (arraylike_name, length)) 580 return (result + 581 self._Variable(name, 582 "new %sArray(%s)" % (arraytype, arraylike_name), 583 fallback="new %sArray(8)" % arraytype)) 584 else: 585 die = random.random() 586 buffer_name = name + "_buffer" 587 args = [buffer_name] 588 result = self._JSArrayBuffer(buffer_name, recursion_budget) 589 if die < 0.67: 590 offset_name = name + "_offset" 591 args.append(offset_name) 592 result += self._Int32(offset_name) 593 if die < 0.33: 594 length_name = name + "_length" 595 args.append(length_name) 596 result += self._Int32(length_name) 597 return (result + 598 self._Variable(name, 599 "new %sArray(%s)" % (arraytype, ", ".join(args)), 600 fallback="new %sArray(8)" % arraytype)) 601 602 def _JSArrayBufferView(self, name, recursion_budget): 603 if random.random() < 0.4: 604 return self._JSDataView(name, recursion_budget) 605 else: 606 return self._JSTypedArray(name, recursion_budget) 607 608 def _JSWeakCollection(self, name, recursion_budget): 609 ctor = random.choice([self._JSMap, self._JSSet]) 610 return ctor(name, recursion_budget, weak="Weak") 611 612 def _PropertyDetails(self, name, recursion_budget): 613 # TODO(jkummerow): Be more clever here? 614 return self._Int32(name) 615 616 def _JSObject(self, name, recursion_budget): 617 die = random.random() 618 if die < 0.4: 619 function = random.choice([self._PlainObject, self._PlainArray, 620 self._PlainFunction]) 621 elif die < 0.5: 622 return self._Variable(name, "this") # Global object. 623 else: 624 function = random.choice([self._JSArrayBuffer, self._JSDataView, 625 self._JSDate, self._JSFunctionProxy, 626 self._JSGeneratorObject, self._JSMap, 627 self._JSMapIterator, self._JSRegExp, 628 self._JSSet, self._JSSetIterator, 629 self._JSTypedArray, self._JSValue, 630 self._JSWeakCollection]) 631 result = function(name, recursion_budget) 632 self._AddAccessors(name, result, recursion_budget) 633 self._AddProperties(name, result, recursion_budget) 634 self._AddElements(name, result, recursion_budget) 635 return result 636 637 def _JSReceiver(self, name, recursion_budget): 638 if random.random() < 0.9: return self._JSObject(name, recursion_budget) 639 return self._JSProxy(name, recursion_budget) 640 641 def _HeapObject(self, name, recursion_budget): 642 die = random.random() 643 if die < 0.9: return self._JSReceiver(name, recursion_budget) 644 elif die < 0.95: return self._Oddball(name, recursion_budget) 645 else: return self._Name(name, recursion_budget) 646 647 def _Object(self, name, recursion_budget): 648 if recursion_budget <= 0: 649 function = random.choice([self._Oddball, self._Number, self._Name, 650 self._JSValue, self._JSRegExp]) 651 return function(name, recursion_budget) 652 if random.random() < 0.2: 653 return self._Smi(name, recursion_budget) 654 return self._HeapObject(name, recursion_budget) 655 656 GENERATORS = { 657 "Boolean": ["true", _Boolean], 658 "HeapObject": ["new Object()", _HeapObject], 659 "Int32": ["32", _Int32], 660 "JSArray": ["new Array()", _JSArray], 661 "JSArrayBuffer": ["new ArrayBuffer(8)", _JSArrayBuffer], 662 "JSArrayBufferView": ["new Int32Array(2)", _JSArrayBufferView], 663 "JSDataView": ["new DataView(new ArrayBuffer(24))", _JSDataView], 664 "JSDate": ["new Date()", _JSDate], 665 "JSFunction": ["function() {}", _JSFunction], 666 "JSFunctionProxy": ["Proxy.createFunction({}, function() {})", 667 _JSFunctionProxy], 668 "JSGeneratorObject": ["(function*(){ yield 1; })()", _JSGeneratorObject], 669 "JSMap": ["new Map()", _JSMap], 670 "JSMapIterator": ["new Map().entries()", _JSMapIterator], 671 "JSObject": ["new Object()", _JSObject], 672 "JSProxy": ["Proxy.create({})", _JSProxy], 673 "JSReceiver": ["new Object()", _JSReceiver], 674 "JSRegExp": ["/ab/g", _JSRegExp], 675 "JSSet": ["new Set()", _JSSet], 676 "JSSetIterator": ["new Set().values()", _JSSetIterator], 677 "JSTypedArray": ["new Int32Array(2)", _JSTypedArray], 678 "JSValue": ["new String('foo')", _JSValue], 679 "JSWeakCollection": ["new WeakMap()", _JSWeakCollection], 680 "Name": ["\"name\"", _Name], 681 "Number": ["1.5", _Number], 682 "Object": ["new Object()", _Object], 683 "PropertyDetails": ["513", _PropertyDetails], 684 "SeqOneByteString": ["\"seq 1-byte\"", _SeqString], 685 "SeqString": ["\"seqstring\"", _SeqString], 686 "SeqTwoByteString": ["\"seq \\u2082-byte\"", _SeqTwoByteString], 687 "Smi": ["1", _Smi], 688 "StrictMode": ["1", _StrictMode], 689 "String": ["\"foo\"", _String], 690 "Symbol": ["Symbol(\"symbol\")", _Symbol], 691 "Uint32": ["32", _Uint32], 692 } 693 694 695 class ArgParser(object): 696 def __init__(self, regex, ctor): 697 self.regex = regex 698 self.ArgCtor = ctor 699 700 701 class Arg(object): 702 def __init__(self, typename, varname, index): 703 self.type = typename 704 self.name = "_%s" % varname 705 self.index = index 706 707 708 class Function(object): 709 def __init__(self, match): 710 self.name = match.group(1) 711 self.argslength = -1 712 self.args = {} 713 self.inline = "" 714 715 handle_arg_parser = ArgParser( 716 re.compile("^\s*CONVERT_ARG_HANDLE_CHECKED\((\w+), (\w+), (\d+)\)"), 717 lambda match: Arg(match.group(1), match.group(2), int(match.group(3)))) 718 719 plain_arg_parser = ArgParser( 720 re.compile("^\s*CONVERT_ARG_CHECKED\((\w+), (\w+), (\d+)\)"), 721 lambda match: Arg(match.group(1), match.group(2), int(match.group(3)))) 722 723 number_handle_arg_parser = ArgParser( 724 re.compile("^\s*CONVERT_NUMBER_ARG_HANDLE_CHECKED\((\w+), (\d+)\)"), 725 lambda match: Arg("Number", match.group(1), int(match.group(2)))) 726 727 smi_arg_parser = ArgParser( 728 re.compile("^\s*CONVERT_SMI_ARG_CHECKED\((\w+), (\d+)\)"), 729 lambda match: Arg("Smi", match.group(1), int(match.group(2)))) 730 731 double_arg_parser = ArgParser( 732 re.compile("^\s*CONVERT_DOUBLE_ARG_CHECKED\((\w+), (\d+)\)"), 733 lambda match: Arg("Number", match.group(1), int(match.group(2)))) 734 735 number_arg_parser = ArgParser( 736 re.compile( 737 "^\s*CONVERT_NUMBER_CHECKED\(\w+, (\w+), (\w+), args\[(\d+)\]\)"), 738 lambda match: Arg(match.group(2), match.group(1), int(match.group(3)))) 739 740 strict_mode_arg_parser = ArgParser( 741 re.compile("^\s*CONVERT_STRICT_MODE_ARG_CHECKED\((\w+), (\d+)\)"), 742 lambda match: Arg("StrictMode", match.group(1), int(match.group(2)))) 743 744 boolean_arg_parser = ArgParser( 745 re.compile("^\s*CONVERT_BOOLEAN_ARG_CHECKED\((\w+), (\d+)\)"), 746 lambda match: Arg("Boolean", match.group(1), int(match.group(2)))) 747 748 property_details_parser = ArgParser( 749 re.compile("^\s*CONVERT_PROPERTY_DETAILS_CHECKED\((\w+), (\d+)\)"), 750 lambda match: Arg("PropertyDetails", match.group(1), int(match.group(2)))) 751 752 arg_parsers = [handle_arg_parser, plain_arg_parser, number_handle_arg_parser, 753 smi_arg_parser, 754 double_arg_parser, number_arg_parser, strict_mode_arg_parser, 755 boolean_arg_parser, property_details_parser] 756 757 def SetArgsLength(self, match): 758 self.argslength = int(match.group(1)) 759 760 def TryParseArg(self, line): 761 for parser in Function.arg_parsers: 762 match = parser.regex.match(line) 763 if match: 764 arg = parser.ArgCtor(match) 765 self.args[arg.index] = arg 766 return True 767 return False 768 769 def Filename(self): 770 return "%s.js" % self.name.lower() 771 772 def __str__(self): 773 s = [self.name, "("] 774 argcount = self.argslength 775 if argcount < 0: 776 print("WARNING: unknown argslength for function %s" % self.name) 777 if self.args: 778 argcount = max([self.args[i].index + 1 for i in self.args]) 779 else: 780 argcount = 0 781 for i in range(argcount): 782 if i > 0: s.append(", ") 783 s.append(self.args[i].type if i in self.args else "<unknown>") 784 s.append(")") 785 return "".join(s) 786 787 788 class Macro(object): 789 def __init__(self, match): 790 self.name = match.group(1) 791 self.args = [s.strip() for s in match.group(2).split(",")] 792 self.lines = [] 793 self.indentation = 0 794 self.AddLine(match.group(3)) 795 796 def AddLine(self, line): 797 if not line: return 798 if not self.lines: 799 # This is the first line, detect indentation. 800 self.indentation = len(line) - len(line.lstrip()) 801 line = line.rstrip("\\\n ") 802 if not line: return 803 assert len(line[:self.indentation].strip()) == 0, \ 804 ("expected whitespace: '%s', full line: '%s'" % 805 (line[:self.indentation], line)) 806 line = line[self.indentation:] 807 if not line: return 808 self.lines.append(line + "\n") 809 810 def Finalize(self): 811 for arg in self.args: 812 pattern = re.compile(r"(##|\b)%s(##|\b)" % arg) 813 for i in range(len(self.lines)): 814 self.lines[i] = re.sub(pattern, "%%(%s)s" % arg, self.lines[i]) 815 816 def FillIn(self, arg_values): 817 filler = {} 818 assert len(arg_values) == len(self.args) 819 for i in range(len(self.args)): 820 filler[self.args[i]] = arg_values[i] 821 result = [] 822 for line in self.lines: 823 result.append(line % filler) 824 return result 825 826 827 # Parses HEADERFILENAME to find out which runtime functions are "inline". 828 def FindInlineRuntimeFunctions(): 829 inline_functions = [] 830 with open(HEADERFILENAME, "r") as f: 831 inline_list = "#define INLINE_FUNCTION_LIST(F) \\\n" 832 inline_function = re.compile(r"^\s*F\((\w+), \d+, \d+\)\s*\\?") 833 mode = "SEARCHING" 834 for line in f: 835 if mode == "ACTIVE": 836 match = inline_function.match(line) 837 if match: 838 inline_functions.append(match.group(1)) 839 if not line.endswith("\\\n"): 840 mode = "SEARCHING" 841 elif mode == "SEARCHING": 842 if line == inline_list: 843 mode = "ACTIVE" 844 return inline_functions 845 846 847 def ReadFileAndExpandMacros(filename): 848 found_macros = {} 849 expanded_lines = [] 850 with open(filename, "r") as f: 851 found_macro = None 852 for line in f: 853 if found_macro is not None: 854 found_macro.AddLine(line) 855 if not line.endswith("\\\n"): 856 found_macro.Finalize() 857 found_macro = None 858 continue 859 860 match = MACRO.match(line) 861 if match: 862 found_macro = Macro(match) 863 if found_macro.name in EXPAND_MACROS: 864 found_macros[found_macro.name] = found_macro 865 else: 866 found_macro = None 867 continue 868 869 match = FIRST_WORD.match(line) 870 if match: 871 first_word = match.group(1) 872 if first_word in found_macros: 873 MACRO_CALL = re.compile("%s\(([^)]*)\)" % first_word) 874 match = MACRO_CALL.match(line) 875 assert match 876 args = [s.strip() for s in match.group(1).split(",")] 877 expanded_lines += found_macros[first_word].FillIn(args) 878 continue 879 880 expanded_lines.append(line) 881 return expanded_lines 882 883 884 # Detects runtime functions by parsing FILENAME. 885 def FindRuntimeFunctions(): 886 inline_functions = FindInlineRuntimeFunctions() 887 functions = [] 888 expanded_lines = ReadFileAndExpandMacros(FILENAME) 889 function = None 890 partial_line = "" 891 for line in expanded_lines: 892 # Multi-line definition support, ignoring macros. 893 if line.startswith("RUNTIME_FUNCTION") and not line.endswith("{\n"): 894 if line.endswith("\\\n"): continue 895 partial_line = line.rstrip() 896 continue 897 if partial_line: 898 partial_line += " " + line.strip() 899 if partial_line.endswith("{"): 900 line = partial_line 901 partial_line = "" 902 else: 903 continue 904 905 match = FUNCTION.match(line) 906 if match: 907 function = Function(match) 908 if function.name in inline_functions: 909 function.inline = "_" 910 continue 911 if function is None: continue 912 913 match = ARGSLENGTH.match(line) 914 if match: 915 function.SetArgsLength(match) 916 continue 917 918 if function.TryParseArg(line): 919 continue 920 921 if line == FUNCTIONEND: 922 if function is not None: 923 functions.append(function) 924 function = None 925 return functions 926 927 928 # Hack: This must have the same fields as class Function above, because the 929 # two are used polymorphically in RunFuzzer(). We could use inheritance... 930 class Builtin(object): 931 def __init__(self, match): 932 self.name = match.group(1) 933 args = match.group(2) 934 self.argslength = 0 if args == "" else args.count(",") + 1 935 self.inline = "" 936 self.args = {} 937 if self.argslength > 0: 938 args = args.split(",") 939 for i in range(len(args)): 940 # a = args[i].strip() # TODO: filter out /* comments */ first. 941 a = "" 942 self.args[i] = Arg("Object", a, i) 943 944 def __str__(self): 945 return "%s(%d)" % (self.name, self.argslength) 946 947 948 def FindJSBuiltins(): 949 PATH = "src" 950 fileslist = [] 951 for (root, dirs, files) in os.walk(PATH): 952 for f in files: 953 if f.endswith(".js"): 954 fileslist.append(os.path.join(root, f)) 955 builtins = [] 956 regexp = re.compile("^function (\w+)\s*\((.*?)\) {") 957 matches = 0 958 for filename in fileslist: 959 with open(filename, "r") as f: 960 file_contents = f.read() 961 file_contents = js2c.ExpandInlineMacros(file_contents) 962 lines = file_contents.split("\n") 963 partial_line = "" 964 for line in lines: 965 if line.startswith("function") and not '{' in line: 966 partial_line += line.rstrip() 967 continue 968 if partial_line: 969 partial_line += " " + line.strip() 970 if '{' in line: 971 line = partial_line 972 partial_line = "" 973 else: 974 continue 975 match = regexp.match(line) 976 if match: 977 builtins.append(Builtin(match)) 978 return builtins 979 980 981 # Classifies runtime functions. 982 def ClassifyFunctions(functions): 983 # Can be fuzzed with a JavaScript testcase. 984 js_fuzzable_functions = [] 985 # We have enough information to fuzz these, but they need inputs that 986 # cannot be created or passed around in JavaScript. 987 cctest_fuzzable_functions = [] 988 # This script does not have enough information about these. 989 unknown_functions = [] 990 991 types = {} 992 for f in functions: 993 if f.name in BLACKLISTED: 994 continue 995 decision = js_fuzzable_functions 996 custom = CUSTOM_KNOWN_GOOD_INPUT.get(f.name, None) 997 if f.argslength < 0: 998 # Unknown length -> give up unless there's a custom definition. 999 if custom and custom[-1] is not None: 1000 f.argslength = custom[-1] 1001 assert len(custom) == f.argslength + 1, \ 1002 ("%s: last custom definition must be argslength" % f.name) 1003 else: 1004 decision = unknown_functions 1005 else: 1006 if custom: 1007 # Any custom definitions must match the known argslength. 1008 assert len(custom) == f.argslength + 1, \ 1009 ("%s should have %d custom definitions but has %d" % 1010 (f.name, f.argslength + 1, len(custom))) 1011 for i in range(f.argslength): 1012 if custom and custom[i] is not None: 1013 # All good, there's a custom definition. 1014 pass 1015 elif not i in f.args: 1016 # No custom definition and no parse result -> give up. 1017 decision = unknown_functions 1018 else: 1019 t = f.args[i].type 1020 if t in NON_JS_TYPES: 1021 decision = cctest_fuzzable_functions 1022 else: 1023 assert Generator.IsTypeSupported(t), \ 1024 ("type generator not found for %s, function: %s" % (t, f)) 1025 decision.append(f) 1026 return (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) 1027 1028 1029 def _GetKnownGoodArgs(function, generator): 1030 custom_input = CUSTOM_KNOWN_GOOD_INPUT.get(function.name, None) 1031 definitions = [] 1032 argslist = [] 1033 for i in range(function.argslength): 1034 if custom_input and custom_input[i] is not None: 1035 name = "arg%d" % i 1036 definitions.append("var %s = %s;" % (name, custom_input[i])) 1037 else: 1038 arg = function.args[i] 1039 name = arg.name 1040 definitions += generator.RandomVariable(name, arg.type, simple=True) 1041 argslist.append(name) 1042 return (definitions, argslist) 1043 1044 1045 def _GenerateTestcase(function, definitions, argslist, throws): 1046 s = ["// Copyright 2014 the V8 project authors. All rights reserved.", 1047 "// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY", 1048 "// Flags: --allow-natives-syntax --harmony"] + definitions 1049 call = "%%%s%s(%s);" % (function.inline, function.name, ", ".join(argslist)) 1050 if throws: 1051 s.append("try {") 1052 s.append(call); 1053 s.append("} catch(e) {}") 1054 else: 1055 s.append(call) 1056 testcase = "\n".join(s) 1057 return testcase 1058 1059 1060 def GenerateJSTestcaseForFunction(function): 1061 gen = Generator() 1062 (definitions, argslist) = _GetKnownGoodArgs(function, gen) 1063 testcase = _GenerateTestcase(function, definitions, argslist, 1064 function.name in THROWS) 1065 path = os.path.join(BASEPATH, function.Filename()) 1066 with open(path, "w") as f: 1067 f.write("%s\n" % testcase) 1068 1069 1070 def GenerateTestcases(functions): 1071 shutil.rmtree(BASEPATH) # Re-generate everything. 1072 os.makedirs(BASEPATH) 1073 for f in functions: 1074 GenerateJSTestcaseForFunction(f) 1075 1076 1077 def _SaveFileName(save_path, process_id, save_file_index): 1078 return "%s/fuzz_%d_%d.js" % (save_path, process_id, save_file_index) 1079 1080 1081 def _GetFuzzableRuntimeFunctions(): 1082 functions = FindRuntimeFunctions() 1083 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \ 1084 ClassifyFunctions(functions) 1085 return js_fuzzable_functions 1086 1087 1088 FUZZ_TARGET_LISTS = { 1089 "runtime": _GetFuzzableRuntimeFunctions, 1090 "builtins": FindJSBuiltins, 1091 } 1092 1093 1094 def RunFuzzer(process_id, options, stop_running): 1095 MAX_SLEEP_TIME = 0.1 1096 INITIAL_SLEEP_TIME = 0.001 1097 SLEEP_TIME_FACTOR = 1.25 1098 base_file_name = "/dev/shm/runtime_fuzz_%d" % process_id 1099 test_file_name = "%s.js" % base_file_name 1100 stderr_file_name = "%s.out" % base_file_name 1101 save_file_index = 0 1102 while os.path.exists(_SaveFileName(options.save_path, process_id, 1103 save_file_index)): 1104 save_file_index += 1 1105 1106 targets = FUZZ_TARGET_LISTS[options.fuzz_target]() 1107 try: 1108 for i in range(options.num_tests): 1109 if stop_running.is_set(): break 1110 function = None 1111 while function is None or function.argslength == 0: 1112 function = random.choice(targets) 1113 args = [] 1114 definitions = [] 1115 gen = Generator() 1116 for i in range(function.argslength): 1117 arg = function.args[i] 1118 argname = "arg%d%s" % (i, arg.name) 1119 args.append(argname) 1120 definitions += gen.RandomVariable(argname, arg.type, simple=False) 1121 testcase = _GenerateTestcase(function, definitions, args, True) 1122 with open(test_file_name, "w") as f: 1123 f.write("%s\n" % testcase) 1124 with open("/dev/null", "w") as devnull: 1125 with open(stderr_file_name, "w") as stderr: 1126 process = subprocess.Popen( 1127 [options.binary, "--allow-natives-syntax", "--harmony", 1128 "--enable-slow-asserts", test_file_name], 1129 stdout=devnull, stderr=stderr) 1130 end_time = time.time() + options.timeout 1131 timed_out = False 1132 exit_code = None 1133 sleep_time = INITIAL_SLEEP_TIME 1134 while exit_code is None: 1135 if time.time() >= end_time: 1136 # Kill the process and wait for it to exit. 1137 os.kill(process.pid, signal.SIGTERM) 1138 exit_code = process.wait() 1139 timed_out = True 1140 else: 1141 exit_code = process.poll() 1142 time.sleep(sleep_time) 1143 sleep_time = sleep_time * SLEEP_TIME_FACTOR 1144 if sleep_time > MAX_SLEEP_TIME: 1145 sleep_time = MAX_SLEEP_TIME 1146 if exit_code != 0 and not timed_out: 1147 oom = False 1148 with open(stderr_file_name, "r") as stderr: 1149 for line in stderr: 1150 if line.strip() == "# Allocation failed - process out of memory": 1151 oom = True 1152 break 1153 if oom: continue 1154 save_name = _SaveFileName(options.save_path, process_id, 1155 save_file_index) 1156 shutil.copyfile(test_file_name, save_name) 1157 save_file_index += 1 1158 except KeyboardInterrupt: 1159 stop_running.set() 1160 finally: 1161 if os.path.exists(test_file_name): 1162 os.remove(test_file_name) 1163 if os.path.exists(stderr_file_name): 1164 os.remove(stderr_file_name) 1165 1166 1167 def BuildOptionParser(): 1168 usage = """Usage: %%prog [options] ACTION 1169 1170 where ACTION can be: 1171 1172 info Print diagnostic info. 1173 check Check that runtime functions can be parsed as expected, and that 1174 test cases exist. 1175 generate Parse source code for runtime functions, and auto-generate 1176 test cases for them. Warning: this will nuke and re-create 1177 %(path)s. 1178 fuzz Generate fuzz tests, run them, save those that crashed (see options). 1179 """ % {"path": os.path.relpath(BASEPATH)} 1180 1181 o = optparse.OptionParser(usage=usage) 1182 o.add_option("--binary", default="out/x64.debug/d8", 1183 help="d8 binary used for running fuzz tests (default: %default)") 1184 o.add_option("--fuzz-target", default="runtime", 1185 help="Set of functions targeted by fuzzing. Allowed values: " 1186 "%s (default: %%default)" % ", ".join(FUZZ_TARGET_LISTS)) 1187 o.add_option("-n", "--num-tests", default=1000, type="int", 1188 help="Number of fuzz tests to generate per worker process" 1189 " (default: %default)") 1190 o.add_option("--save-path", default="~/runtime_fuzz_output", 1191 help="Path to directory where failing tests will be stored" 1192 " (default: %default)") 1193 o.add_option("--timeout", default=20, type="int", 1194 help="Timeout for each fuzz test (in seconds, default:" 1195 "%default)") 1196 return o 1197 1198 1199 def ProcessOptions(options, args): 1200 options.save_path = os.path.expanduser(options.save_path) 1201 if options.fuzz_target not in FUZZ_TARGET_LISTS: 1202 print("Invalid fuzz target: %s" % options.fuzz_target) 1203 return False 1204 if len(args) != 1 or args[0] == "help": 1205 return False 1206 return True 1207 1208 1209 def Main(): 1210 parser = BuildOptionParser() 1211 (options, args) = parser.parse_args() 1212 1213 if not ProcessOptions(options, args): 1214 parser.print_help() 1215 return 1 1216 action = args[0] 1217 1218 functions = FindRuntimeFunctions() 1219 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \ 1220 ClassifyFunctions(functions) 1221 builtins = FindJSBuiltins() 1222 1223 if action == "test": 1224 print("put your temporary debugging code here") 1225 return 0 1226 1227 if action == "info": 1228 print("%d functions total; js_fuzzable_functions: %d, " 1229 "cctest_fuzzable_functions: %d, unknown_functions: %d" 1230 % (len(functions), len(js_fuzzable_functions), 1231 len(cctest_fuzzable_functions), len(unknown_functions))) 1232 print("%d JavaScript builtins" % len(builtins)) 1233 print("unknown functions:") 1234 for f in unknown_functions: 1235 print(f) 1236 return 0 1237 1238 if action == "check": 1239 errors = 0 1240 1241 def CheckCount(actual, expected, description): 1242 if len(actual) != expected: 1243 print("Expected to detect %d %s, but found %d." % ( 1244 expected, description, len(actual))) 1245 print("If this change is intentional, please update the expectations" 1246 " at the top of %s." % THIS_SCRIPT) 1247 return 1 1248 return 0 1249 1250 errors += CheckCount(functions, EXPECTED_FUNCTION_COUNT, 1251 "functions in total") 1252 errors += CheckCount(js_fuzzable_functions, EXPECTED_FUZZABLE_COUNT, 1253 "JavaScript-fuzzable functions") 1254 errors += CheckCount(cctest_fuzzable_functions, EXPECTED_CCTEST_COUNT, 1255 "cctest-fuzzable functions") 1256 errors += CheckCount(unknown_functions, EXPECTED_UNKNOWN_COUNT, 1257 "functions with incomplete type information") 1258 errors += CheckCount(builtins, EXPECTED_BUILTINS_COUNT, 1259 "JavaScript builtins") 1260 1261 def CheckTestcasesExisting(functions): 1262 errors = 0 1263 for f in functions: 1264 if not os.path.isfile(os.path.join(BASEPATH, f.Filename())): 1265 print("Missing testcase for %s, please run '%s generate'" % 1266 (f.name, THIS_SCRIPT)) 1267 errors += 1 1268 files = filter(lambda filename: not filename.startswith("."), 1269 os.listdir(BASEPATH)) 1270 if (len(files) != len(functions)): 1271 unexpected_files = set(files) - set([f.Filename() for f in functions]) 1272 for f in unexpected_files: 1273 print("Unexpected testcase: %s" % os.path.join(BASEPATH, f)) 1274 errors += 1 1275 print("Run '%s generate' to automatically clean these up." 1276 % THIS_SCRIPT) 1277 return errors 1278 1279 errors += CheckTestcasesExisting(js_fuzzable_functions) 1280 1281 def CheckNameClashes(runtime_functions, builtins): 1282 errors = 0 1283 runtime_map = {} 1284 for f in runtime_functions: 1285 runtime_map[f.name] = 1 1286 for b in builtins: 1287 if b.name in runtime_map: 1288 print("Builtin/Runtime_Function name clash: %s" % b.name) 1289 errors += 1 1290 return errors 1291 1292 errors += CheckNameClashes(functions, builtins) 1293 1294 if errors > 0: 1295 return 1 1296 print("Generated runtime tests: all good.") 1297 return 0 1298 1299 if action == "generate": 1300 GenerateTestcases(js_fuzzable_functions) 1301 return 0 1302 1303 if action == "fuzz": 1304 processes = [] 1305 if not os.path.isdir(options.save_path): 1306 os.makedirs(options.save_path) 1307 stop_running = multiprocessing.Event() 1308 for i in range(multiprocessing.cpu_count()): 1309 args = (i, options, stop_running) 1310 p = multiprocessing.Process(target=RunFuzzer, args=args) 1311 p.start() 1312 processes.append(p) 1313 try: 1314 for i in range(len(processes)): 1315 processes[i].join() 1316 except KeyboardInterrupt: 1317 stop_running.set() 1318 for i in range(len(processes)): 1319 processes[i].join() 1320 return 0 1321 1322 if __name__ == "__main__": 1323 sys.exit(Main()) 1324