Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(736)

Side by Side Diff: tools/generate-runtime-tests.py

Issue 250923002: Add test case generator for runtime functions (Closed) Base URL: https://v8.googlecode.com/svn/branches/bleeding_edge
Patch Set: rebased Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « test/mjsunit/runtime-gen/weakcollectionset.js ('k') | tools/run-tests.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2014 the V8 project authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 import os
7 import re
8 import shutil
9 import sys
10
11 # TODO(jkummerow): Support DATA_VIEW_{G,S}ETTER in runtime.cc
12
13 FILENAME = "src/runtime.cc"
14 HEADERFILENAME = "src/runtime.h"
15 FUNCTION = re.compile("^RUNTIME_FUNCTION\(Runtime_(\w+)")
16 ARGSLENGTH = re.compile(".*ASSERT\(.*args\.length\(\) == (\d+)\);")
17 FUNCTIONEND = "}\n"
18
19 WORKSPACE = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), ".."))
20 BASEPATH = os.path.join(WORKSPACE, "test", "mjsunit", "runtime-gen")
Michael Achenbach 2014/05/08 08:03:12 Wouldn't this fit better into the fuzz-natives tes
Jakob Kummerow 2014/05/08 09:45:38 Semantically yes, but the distinction between mjsu
21 THIS_SCRIPT = os.path.relpath(sys.argv[0])
22
23 # Counts of functions in each detection state. These are used to assert
24 # that the parser doesn't bit-rot. Change the values as needed when you add,
25 # remove or change runtime functions, but make sure we don't lose our ability
26 # to parse them!
27 EXPECTED_FUNCTION_COUNT = 339
28 EXPECTED_FUZZABLE_COUNT = 317
29 EXPECTED_CCTEST_COUNT = 6
30 EXPECTED_UNKNOWN_COUNT = 5
31
32
33 # Don't call these at all.
34 BLACKLISTED = [
35 "Abort", # Kills the process.
36 "AbortJS", # Kills the process.
37 "CompileForOnStackReplacement", # Riddled with ASSERTs.
38 "IS_VAR", # Not implemented in the runtime.
39 "ListNatives", # Not available in Release mode.
40 "SetAllocationTimeout", # Too slow for fuzzing.
41 "SystemBreak", # Kills (int3) the process.
42
43 # These are weird. They violate some invariants when called after
44 # bootstrapping.
45 "DisableAccessChecks",
46 "EnableAccessChecks",
47
48 # Seems to be incompatible with --stress-runs.
49 "LiveEditReplaceScript",
50
51 # TODO(jkummerow): Fix these and un-blacklist them!
52 "CreateDateTimeFormat",
53 "CreateNumberFormat",
54 ]
55
56
57 # These will always throw.
58 THROWS = [
59 "CheckExecutionState", # Needs to hit a break point.
60 "CheckIsBootstrapping", # Needs to be bootstrapping.
61 "DebugEvaluate", # Needs to hit a break point.
62 "DebugEvaluateGlobal", # Needs to hit a break point.
63 "DebugIndexedInterceptorElementValue", # Needs an indexed interceptor.
64 "DebugNamedInterceptorPropertyValue", # Needs a named interceptor.
65 "DebugSetScriptSource", # Checks compilation state of script.
66 "GetAllScopesDetails", # Needs to hit a break point.
67 "GetFrameCount", # Needs to hit a break point.
68 "GetFrameDetails", # Needs to hit a break point.
69 "GetRootNaN", # Needs to be bootstrapping.
70 "GetScopeCount", # Needs to hit a break point.
71 "GetScopeDetails", # Needs to hit a break point.
72 "GetStepInPositions", # Needs to hit a break point.
73 "GetTemplateField", # Needs a {Function,Object}TemplateInfo.
74 "GetThreadCount", # Needs to hit a break point.
75 "GetThreadDetails", # Needs to hit a break point.
76 "IsAccessAllowedForObserver", # Needs access-check-required object.
77 "LiveEditFunctionSourceUpdated", # Needs a SharedFunctionInfo.
78 "LiveEditPatchFunctionPositions", # Needs a SharedFunctionInfo.
79 "LiveEditReplaceFunctionCode", # Needs a SharedFunctionInfo.
80 "LiveEditReplaceRefToNestedFunction", # Needs a SharedFunctionInfo.
81 "LiveEditRestartFrame", # Needs to hit a break point.
82 "UnblockConcurrentRecompilation" # Needs --block-concurrent-recompilation.
83 ]
84
85
86 # Definitions used in CUSTOM_KNOWN_GOOD_INPUT below.
87 _BREAK_ITERATOR = (
88 "%GetImplFromInitializedIntlObject(new Intl.v8BreakIterator())")
89 _COLLATOR = "%GetImplFromInitializedIntlObject(new Intl.Collator('en-US'))"
90 _DATETIME_FORMAT = (
91 "%GetImplFromInitializedIntlObject(new Intl.DateTimeFormat('en-US'))")
92 _NUMBER_FORMAT = (
93 "%GetImplFromInitializedIntlObject(new Intl.NumberFormat('en-US'))")
94 _SCRIPT = "%DebugGetLoadedScripts()[1]"
95
96
97 # Custom definitions for function input that does not throw.
98 # Format: "FunctionName": ["arg0", "arg1", ..., argslength].
99 # None means "fall back to autodetected value".
100 CUSTOM_KNOWN_GOOD_INPUT = {
101 "Apply": ["function() {}", None, None, None, None, None],
102 "ArrayBufferSliceImpl": [None, None, 0, None],
103 "ArrayConcat": ["[1, 'a']", None],
104 "BreakIteratorAdoptText": [_BREAK_ITERATOR, None, None],
105 "BreakIteratorBreakType": [_BREAK_ITERATOR, None],
106 "BreakIteratorCurrent": [_BREAK_ITERATOR, None],
107 "BreakIteratorFirst": [_BREAK_ITERATOR, None],
108 "BreakIteratorNext": [_BREAK_ITERATOR, None],
109 "CompileString": [None, "false", None],
110 "CreateBreakIterator": ["'en-US'", "{type: 'string'}", None, None],
111 "CreateJSFunctionProxy": [None, "function() {}", None, None, None],
112 "CreatePrivateSymbol": ["\"foo\"", None],
113 "CreateSymbol": ["\"foo\"", None],
114 "DateParseString": [None, "new Array(8)", None],
115 "DebugSetScriptSource": [_SCRIPT, None, None],
116 "DefineOrRedefineAccessorProperty": [None, None, "function() {}",
117 "function() {}", 2, None],
118 "GetBreakLocations": [None, 0, None],
119 "GetDefaultReceiver": ["function() {}", None],
120 "GetImplFromInitializedIntlObject": ["new Intl.NumberFormat('en-US')", None],
121 "InternalCompare": [_COLLATOR, None, None, None],
122 "InternalDateFormat": [_DATETIME_FORMAT, None, None],
123 "InternalDateParse": [_DATETIME_FORMAT, None, None],
124 "InternalNumberFormat": [_NUMBER_FORMAT, None, None],
125 "InternalNumberParse": [_NUMBER_FORMAT, None, None],
126 "IsSloppyModeFunction": ["function() {}", None],
127 "LiveEditFindSharedFunctionInfosForScript": [_SCRIPT, None],
128 "LiveEditGatherCompileInfo": [_SCRIPT, None, None],
129 "LoadMutableDouble": ["{foo: 1.2}", None, None],
130 "NewObjectFromBound": ["(function() {}).bind({})", None],
131 "NumberToRadixString": [None, "2", None],
132 "ParseJson": ["\"{}\"", 1],
133 "SetAccessorProperty": [None, None, "undefined", "undefined", None, None,
134 None],
135 "SetCreateIterator": [None, "2", None],
136 "SetDebugEventListener": ["undefined", None, None],
137 "SetScriptBreakPoint": [_SCRIPT, None, 0, None, None],
138 "StringBuilderConcat": ["[1, 2, 3]", 3, None, None],
139 "StringBuilderJoin": ["['a', 'b']", 4, None, None],
140 "StringMatch": [None, None, "['a', 'b']", None],
141 "StringNormalize": [None, 2, None],
142 "TypedArrayInitialize": [None, None, "new ArrayBuffer(8)", None, None, None],
143 "TypedArraySetFastCases": [None, None, "0", None],
144 }
145
146
147 # Types of arguments that cannot be generated in a JavaScript testcase.
148 NON_JS_TYPES = [
149 "Code", "Context", "FixedArray", "FunctionTemplateInfo",
150 "JSFunctionResultCache", "JSMessageObject", "Map", "ScopeInfo",
151 "SharedFunctionInfo"]
152
153
154 # Maps argument types to concrete example inputs of that type.
155 JS_TYPE_GENERATORS = {
156 "Boolean": "true",
157 "HeapObject": "new Object()",
158 "Int32": "32",
159 "JSArray": "new Array()",
160 "JSArrayBuffer": "new ArrayBuffer(8)",
161 "JSDataView": "new DataView(new ArrayBuffer(8))",
162 "JSDate": "new Date()",
163 "JSFunction": "function() {}",
164 "JSFunctionProxy": "Proxy.createFunction({}, function() {})",
165 "JSGeneratorObject": "(function*(){ yield 1; })()",
166 "JSMap": "new Map()",
167 "JSMapIterator": "%MapCreateIterator(new Map(), 3)",
168 "JSObject": "new Object()",
169 "JSProxy": "Proxy.create({})",
170 "JSReceiver": "new Object()",
171 "JSRegExp": "/ab/g",
172 "JSSet": "new Set()",
173 "JSSetIterator": "%SetCreateIterator(new Set(), 2)",
174 "JSTypedArray": "new Int32Array(2)",
175 "JSValue": "new String('foo')",
176 "JSWeakCollection": "new WeakMap()",
177 "Name": "\"name\"",
178 "Number": "1.5",
179 "Object": "new Object()",
180 "PropertyDetails": "513",
181 "SeqString": "\"seqstring\"",
182 "Smi": 1,
183 "StrictMode": "1",
184 "String": "\"foo\"",
185 "Symbol": "Symbol(\"symbol\")",
186 "Uint32": "32",
187 }
188
189
190 class ArgParser(object):
191 def __init__(self, regex, ctor):
192 self.regex = regex
193 self.ArgCtor = ctor
Michael Achenbach 2014/05/08 08:03:12 nit: lower case?
Jakob Kummerow 2014/05/08 09:45:38 Chose upper case since this isn't just a field, it
194
195
196 class Arg(object):
197 def __init__(self, typename, varname, index):
198 self.type = typename
199 self.name = "_%s" % varname
200 self.index = index
201
202
203 class Function(object):
204 def __init__(self, match):
205 self.name = match.group(1)
206 self.argslength = -1
207 self.args = {}
208 self.inline = ""
209
210 handle_arg_parser = ArgParser(
211 re.compile("^\s*CONVERT_ARG_HANDLE_CHECKED\((\w+), (\w+), (\d+)\)"),
212 lambda match: Arg(match.group(1), match.group(2), int(match.group(3))))
213
214 plain_arg_parser = ArgParser(
215 re.compile("^\s*CONVERT_ARG_CHECKED\((\w+), (\w+), (\d+)\)"),
216 lambda match: Arg(match.group(1), match.group(2), int(match.group(3))))
217
218 number_handle_arg_parser = ArgParser(
219 re.compile("^\s*CONVERT_NUMBER_ARG_HANDLE_CHECKED\((\w+), (\d+)\)"),
220 lambda match: Arg("Number", match.group(1), int(match.group(2))))
221
222 smi_arg_parser = ArgParser(
223 re.compile("^\s*CONVERT_SMI_ARG_CHECKED\((\w+), (\d+)\)"),
224 lambda match: Arg("Smi", match.group(1), int(match.group(2))))
225
226 double_arg_parser = ArgParser(
227 re.compile("^\s*CONVERT_DOUBLE_ARG_CHECKED\((\w+), (\d+)\)"),
228 lambda match: Arg("Number", match.group(1), int(match.group(2))))
229
230 number_arg_parser = ArgParser(
231 re.compile(
232 "^\s*CONVERT_NUMBER_CHECKED\(\w+, (\w+), (\w+), args\[(\d+)\]\)"),
233 lambda match: Arg(match.group(2), match.group(1), int(match.group(3))))
234
235 strict_mode_arg_parser = ArgParser(
236 re.compile("^\s*CONVERT_STRICT_MODE_ARG_CHECKED\((\w+), (\d+)\)"),
237 lambda match: Arg("StrictMode", match.group(1), int(match.group(2))))
238
239 boolean_arg_parser = ArgParser(
240 re.compile("^\s*CONVERT_BOOLEAN_ARG_CHECKED\((\w+), (\d+)\)"),
241 lambda match: Arg("Boolean", match.group(1), int(match.group(2))))
242
243 property_details_parser = ArgParser(
244 re.compile("^\s*CONVERT_PROPERTY_DETAILS_CHECKED\((\w+), (\d+)\)"),
245 lambda match: Arg("PropertyDetails", match.group(1), int(match.group(2))))
246
247 arg_parsers = [handle_arg_parser, plain_arg_parser, number_handle_arg_parser,
248 smi_arg_parser,
249 double_arg_parser, number_arg_parser, strict_mode_arg_parser,
250 boolean_arg_parser, property_details_parser]
251
252
253 def SetArgsLength(self, match):
254 self.argslength = int(match.group(1))
255
256 def TryParseArg(self, line):
257 for parser in Function.arg_parsers:
258 match = parser.regex.match(line)
259 if match:
260 arg = parser.ArgCtor(match)
261 self.args[arg.index] = arg
262 return True
263 return False
264
265 def Filename(self):
266 return "%s.js" % self.name.lower()
267
268 def __str__(self):
269 s = [self.name, "("]
270 argcount = self.argslength
271 if argcount < 0:
272 print("WARNING: unknown argslength for function %s" % self.name)
273 if self.args:
274 argcount = max([self.args[i].index + 1 for i in self.args])
275 else:
276 argcount = 0
277 for i in range(argcount):
278 if i > 0: s.append(", ")
279 s.append(self.args[i].type if i in self.args else "<unknown>")
280 s.append(")")
281 return "".join(s)
282
283 # Parses HEADERFILENAME to find out which runtime functions are "inline".
284 def FindInlineRuntimeFunctions():
285 inline_functions = []
286 with open(HEADERFILENAME, "r") as f:
287 inline_list = "#define INLINE_FUNCTION_LIST(F) \\\n"
288 inline_opt_list = "#define INLINE_OPTIMIZED_FUNCTION_LIST(F) \\\n"
289 inline_function = re.compile(r"^\s*F\((\w+), \d+, \d+\)\s*\\?")
290 mode = "SEARCHING"
291 for line in f:
292 if mode == "ACTIVE":
293 match = inline_function.match(line)
294 if match:
295 inline_functions.append(match.group(1))
296 if not line.endswith("\\\n"):
297 mode = "SEARCHING"
298 elif mode == "SEARCHING":
299 if line == inline_list or line == inline_opt_list:
300 mode = "ACTIVE"
301 return inline_functions
302
303
304 # Detects runtime functions by parsing FILENAME.
305 def FindRuntimeFunctions():
306 inline_functions = FindInlineRuntimeFunctions()
307 functions = []
308 with open(FILENAME, "r") as f:
309 function = None
310 partial_line = ""
311 for line in f:
312 # Multi-line definition support, ignoring macros.
313 if line.startswith("RUNTIME_FUNCTION") and not line.endswith("{\n"):
314 if line.endswith("\\\n"): continue
315 partial_line = line.rstrip()
316 continue
317 if partial_line:
318 partial_line += " " + line.strip()
319 if partial_line.endswith("{"):
320 line = partial_line
321 partial_line = ""
322 else:
323 continue
324
325 match = FUNCTION.match(line)
326 if match:
327 function = Function(match)
328 if function.name in inline_functions:
329 function.inline = "_"
330 continue
331 if function is None: continue
332
333 match = ARGSLENGTH.match(line)
334 if match:
335 function.SetArgsLength(match)
336 continue
337
338 if function.TryParseArg(line):
339 continue
340
341 if line == FUNCTIONEND:
342 if function is not None:
343 functions.append(function)
344 function = None
345 return functions
346
347 # Classifies runtime functions.
348 def ClassifyFunctions(functions):
349 # Can be fuzzed with a JavaScript testcase.
350 js_fuzzable_functions = []
351 # We have enough information to fuzz these, but they need inputs that
352 # cannot be created or passed around in JavaScript.
353 cctest_fuzzable_functions = []
354 # This script does not have enough information about these.
355 unknown_functions = []
356
357 types = {}
358 for f in functions:
359 if f.name in BLACKLISTED:
360 continue
361 decision = js_fuzzable_functions
362 custom = CUSTOM_KNOWN_GOOD_INPUT.get(f.name, None)
363 if f.argslength < 0:
364 # Unknown length -> give up unless there's a custom definition.
365 if custom and custom[-1] is not None:
366 f.argslength = custom[-1]
367 assert len(custom) == f.argslength + 1, \
368 ("%s: last custom definition must be argslength" % f.name)
369 else:
370 decision = unknown_functions
371 else:
372 if custom:
373 # Any custom definitions must match the known argslength.
374 assert len(custom) == f.argslength + 1, \
375 ("%s should have %d custom definitions but has %d" %
376 (f.name, f.argslength + 1, len(custom)))
377 for i in range(f.argslength):
378 if custom and custom[i] is not None:
379 # All good, there's a custom definition.
380 pass
381 elif not i in f.args:
382 # No custom definition and no parse result -> give up.
383 decision = unknown_functions
384 else:
385 t = f.args[i].type
386 if t in NON_JS_TYPES:
387 decision = cctest_fuzzable_functions
388 else:
389 assert t in JS_TYPE_GENERATORS, \
390 ("type generator not found for %s, function: %s" % (t, f))
391 decision.append(f)
392 return (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions)
393
394
395 def GenerateJSTestcaseForFunction(f):
396 s = ["// Copyright 2014 the V8 project authors. All rights reserved.",
397 "// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY",
398 "// Flags: --allow-natives-syntax --harmony"]
399 call = "%%%s%s(" % (f.inline, f.name)
400 custom = CUSTOM_KNOWN_GOOD_INPUT.get(f.name, None)
401 for i in range(f.argslength):
402 if custom and custom[i] is not None:
403 (name, value) = ("arg%d" % i, custom[i])
404 else:
405 arg = f.args[i]
406 (name, value) = (arg.name, JS_TYPE_GENERATORS[arg.type])
407 s.append("var %s = %s;" % (name, value))
408 if i > 0: call += ", "
Michael Achenbach 2014/05/08 08:03:12 how about collecting the names in a list and then
Jakob Kummerow 2014/05/08 09:45:38 I've already refactored this in a follow-up CL tha
409 call += name
410 call += ");"
411 if f.name in THROWS:
412 s.append("try {")
413 s.append(call);
414 s.append("} catch(e) {}")
415 else:
416 s.append(call)
417 testcase = "\n".join(s)
418 path = os.path.join(BASEPATH, f.Filename())
419 with open(path, "w") as f:
420 f.write("%s\n" % testcase)
421
422 def GenerateTestcases(functions):
423 shutil.rmtree(BASEPATH) # Re-generate everything.
424 os.makedirs(BASEPATH)
425 for f in functions:
426 GenerateJSTestcaseForFunction(f)
427
428 def PrintUsage():
429 print """Usage: %(this_script)s ACTION
430
431 where ACTION can be:
432
433 info Print diagnostic info.
434 check Check that runtime functions can be parsed as expected, and that
435 test cases exist.
436 generate Parse source code for runtime functions, and auto-generate
437 test cases for them. Warning: this will nuke and re-create
438 %(path)s.
439 """ % {"path": os.path.relpath(BASEPATH), "this_script": THIS_SCRIPT}
440
441 if __name__ == "__main__":
442 if len(sys.argv) != 2:
443 PrintUsage()
444 sys.exit(1)
445 action = sys.argv[1]
446 if action in ["-h", "--help", "help"]:
447 PrintUsage()
448 sys.exit(0)
449
450 functions = FindRuntimeFunctions()
Michael Achenbach 2014/05/08 08:03:12 Would it be useful if it found also functions defi
Jakob Kummerow 2014/05/08 09:45:38 Potentially. This is just a first step. There is p
451 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \
452 ClassifyFunctions(functions)
453
454 if action == "info":
455 print("%d functions total; js_fuzzable_functions: %d, "
456 "cctest_fuzzable_functions: %d, unknown_functions: %d"
457 % (len(functions), len(js_fuzzable_functions),
458 len(cctest_fuzzable_functions), len(unknown_functions)))
459 print("unknown functions:")
460 for f in unknown_functions:
461 print(f)
462 sys.exit(0)
463
464 if action == "check":
465 error = False
466 def CheckCount(actual, expected, description):
467 global error
468 if len(actual) != expected:
469 print("Expected to detect %d %s, but found %d." % (
470 expected, description, len(actual)))
471 error = True
472 CheckCount(functions, EXPECTED_FUNCTION_COUNT, "functions in total")
473 CheckCount(js_fuzzable_functions, EXPECTED_FUZZABLE_COUNT,
474 "JavaScript-fuzzable functions")
475 CheckCount(cctest_fuzzable_functions, EXPECTED_CCTEST_COUNT,
476 "cctest-fuzzable functions")
477 CheckCount(unknown_functions, EXPECTED_UNKNOWN_COUNT,
478 "functions with incomplete type information")
479
480 def CheckTestcasesExisting(functions):
481 global error
482 for f in functions:
483 if not os.path.isfile(os.path.join(BASEPATH, f.Filename())):
484 print("Missing testcase for %s, please run '%s generate'" %
485 (f.name, THIS_SCRIPT))
486 error = True
487 files = os.listdir(BASEPATH)
Michael Achenbach 2014/05/08 08:03:12 Wonder if there appears a .svn folder on the bots
Jakob Kummerow 2014/05/08 09:45:38 Good point. Let's filter out dotfiles.
488 if (len(files) != len(functions)):
489 unexpected_files = set(files) - set([f.Filename() for f in functions])
490 for f in unexpected_files:
491 print("Unexpected testcase: %s" % os.path.join(BASEPATH, f))
492 error = True
493 CheckTestcasesExisting(js_fuzzable_functions)
494
495 if error:
496 sys.exit(1)
497 print("All good.")
498 sys.exit(0)
499
500 if action == "generate":
501 GenerateTestcases(js_fuzzable_functions)
502 sys.exit(0)
OLDNEW
« no previous file with comments | « test/mjsunit/runtime-gen/weakcollectionset.js ('k') | tools/run-tests.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698