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 6import itertools 7import js2c 8import multiprocessing 9import optparse 10import os 11import random 12import re 13import shutil 14import signal 15import string 16import subprocess 17import sys 18import time 19 20FILENAME = "src/runtime.cc" 21HEADERFILENAME = "src/runtime.h" 22FUNCTION = re.compile("^RUNTIME_FUNCTION\(Runtime_(\w+)") 23ARGSLENGTH = re.compile(".*ASSERT\(.*args\.length\(\) == (\d+)\);") 24FUNCTIONEND = "}\n" 25MACRO = re.compile(r"^#define ([^ ]+)\(([^)]*)\) *([^\\]*)\\?\n$") 26FIRST_WORD = re.compile("^\s*(.*?)[\s({\[]") 27 28WORKSPACE = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "..")) 29BASEPATH = os.path.join(WORKSPACE, "test", "mjsunit", "runtime-gen") 30THIS_SCRIPT = os.path.relpath(sys.argv[0]) 31 32# Expand these macros, they define further runtime functions. 33EXPAND_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! 50EXPECTED_FUNCTION_COUNT = 358 51EXPECTED_FUZZABLE_COUNT = 326 52EXPECTED_CCTEST_COUNT = 6 53EXPECTED_UNKNOWN_COUNT = 4 54EXPECTED_BUILTINS_COUNT = 798 55 56 57# Don't call these at all. 58BLACKLISTED = [ 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. 93THROWS = [ 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". 129CUSTOM_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. 178NON_JS_TYPES = [ 179 "Code", "Context", "FixedArray", "FunctionTemplateInfo", 180 "JSFunctionResultCache", "JSMessageObject", "Map", "ScopeInfo", 181 "SharedFunctionInfo"] 182 183 184class 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 695class ArgParser(object): 696 def __init__(self, regex, ctor): 697 self.regex = regex 698 self.ArgCtor = ctor 699 700 701class Arg(object): 702 def __init__(self, typename, varname, index): 703 self.type = typename 704 self.name = "_%s" % varname 705 self.index = index 706 707 708class 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 788class 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". 828def 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 847def 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. 885def 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... 930class 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 948def 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. 982def 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 1029def _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 1045def _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 1060def 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 1070def GenerateTestcases(functions): 1071 shutil.rmtree(BASEPATH) # Re-generate everything. 1072 os.makedirs(BASEPATH) 1073 for f in functions: 1074 GenerateJSTestcaseForFunction(f) 1075 1076 1077def _SaveFileName(save_path, process_id, save_file_index): 1078 return "%s/fuzz_%d_%d.js" % (save_path, process_id, save_file_index) 1079 1080 1081def _GetFuzzableRuntimeFunctions(): 1082 functions = FindRuntimeFunctions() 1083 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \ 1084 ClassifyFunctions(functions) 1085 return js_fuzzable_functions 1086 1087 1088FUZZ_TARGET_LISTS = { 1089 "runtime": _GetFuzzableRuntimeFunctions, 1090 "builtins": FindJSBuiltins, 1091} 1092 1093 1094def 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 1167def BuildOptionParser(): 1168 usage = """Usage: %%prog [options] ACTION 1169 1170where ACTION can be: 1171 1172info Print diagnostic info. 1173check Check that runtime functions can be parsed as expected, and that 1174 test cases exist. 1175generate Parse source code for runtime functions, and auto-generate 1176 test cases for them. Warning: this will nuke and re-create 1177 %(path)s. 1178fuzz 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 1199def 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 1209def 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 1322if __name__ == "__main__": 1323 sys.exit(Main()) 1324