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