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

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: more rebasing 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")
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 = 316
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 "RegExpExecMultiple": [None, None, "['a']", "['a']", None],
134 "SetAccessorProperty": [None, None, "undefined", "undefined", None, None,
135 None],
136 "SetCreateIterator": [None, "2", None],
137 "SetDebugEventListener": ["undefined", None, None],
138 "SetFunctionBreakPoint": [None, 200, None, None],
139 "SetScriptBreakPoint": [_SCRIPT, None, 0, None, None],
140 "StringBuilderConcat": ["[1, 2, 3]", 3, None, None],
141 "StringBuilderJoin": ["['a', 'b']", 4, None, None],
142 "StringMatch": [None, None, "['a', 'b']", None],
143 "StringNormalize": [None, 2, None],
144 "StringReplaceGlobalRegExpWithString": [None, None, None, "['a']", None],
145 "TypedArrayInitialize": [None, 6, "new ArrayBuffer(8)", None, 4, None],
146 "TypedArrayInitializeFromArrayLike": [None, 6, None, None, None],
147 "TypedArraySetFastCases": [None, None, "0", None],
148 }
149
150
151 # Types of arguments that cannot be generated in a JavaScript testcase.
152 NON_JS_TYPES = [
153 "Code", "Context", "FixedArray", "FunctionTemplateInfo",
154 "JSFunctionResultCache", "JSMessageObject", "Map", "ScopeInfo",
155 "SharedFunctionInfo"]
156
157
158 # Maps argument types to concrete example inputs of that type.
159 JS_TYPE_GENERATORS = {
160 "Boolean": "true",
161 "HeapObject": "new Object()",
162 "Int32": "32",
163 "JSArray": "new Array()",
164 "JSArrayBuffer": "new ArrayBuffer(8)",
165 "JSDataView": "new DataView(new ArrayBuffer(8))",
166 "JSDate": "new Date()",
167 "JSFunction": "function() {}",
168 "JSFunctionProxy": "Proxy.createFunction({}, function() {})",
169 "JSGeneratorObject": "(function*(){ yield 1; })()",
170 "JSMap": "new Map()",
171 "JSMapIterator": "%MapCreateIterator(new Map(), 3)",
172 "JSObject": "new Object()",
173 "JSProxy": "Proxy.create({})",
174 "JSReceiver": "new Object()",
175 "JSRegExp": "/ab/g",
176 "JSSet": "new Set()",
177 "JSSetIterator": "%SetCreateIterator(new Set(), 2)",
178 "JSTypedArray": "new Int32Array(2)",
179 "JSValue": "new String('foo')",
180 "JSWeakCollection": "new WeakMap()",
181 "Name": "\"name\"",
182 "Number": "1.5",
183 "Object": "new Object()",
184 "PropertyDetails": "513",
185 "SeqString": "\"seqstring\"",
186 "Smi": 1,
187 "StrictMode": "1",
188 "String": "\"foo\"",
189 "Symbol": "Symbol(\"symbol\")",
190 "Uint32": "32",
191 }
192
193
194 class ArgParser(object):
195 def __init__(self, regex, ctor):
196 self.regex = regex
197 self.ArgCtor = ctor
198
199
200 class Arg(object):
201 def __init__(self, typename, varname, index):
202 self.type = typename
203 self.name = "_%s" % varname
204 self.index = index
205
206
207 class Function(object):
208 def __init__(self, match):
209 self.name = match.group(1)
210 self.argslength = -1
211 self.args = {}
212 self.inline = ""
213
214 handle_arg_parser = ArgParser(
215 re.compile("^\s*CONVERT_ARG_HANDLE_CHECKED\((\w+), (\w+), (\d+)\)"),
216 lambda match: Arg(match.group(1), match.group(2), int(match.group(3))))
217
218 plain_arg_parser = ArgParser(
219 re.compile("^\s*CONVERT_ARG_CHECKED\((\w+), (\w+), (\d+)\)"),
220 lambda match: Arg(match.group(1), match.group(2), int(match.group(3))))
221
222 number_handle_arg_parser = ArgParser(
223 re.compile("^\s*CONVERT_NUMBER_ARG_HANDLE_CHECKED\((\w+), (\d+)\)"),
224 lambda match: Arg("Number", match.group(1), int(match.group(2))))
225
226 smi_arg_parser = ArgParser(
227 re.compile("^\s*CONVERT_SMI_ARG_CHECKED\((\w+), (\d+)\)"),
228 lambda match: Arg("Smi", match.group(1), int(match.group(2))))
229
230 double_arg_parser = ArgParser(
231 re.compile("^\s*CONVERT_DOUBLE_ARG_CHECKED\((\w+), (\d+)\)"),
232 lambda match: Arg("Number", match.group(1), int(match.group(2))))
233
234 number_arg_parser = ArgParser(
235 re.compile(
236 "^\s*CONVERT_NUMBER_CHECKED\(\w+, (\w+), (\w+), args\[(\d+)\]\)"),
237 lambda match: Arg(match.group(2), match.group(1), int(match.group(3))))
238
239 strict_mode_arg_parser = ArgParser(
240 re.compile("^\s*CONVERT_STRICT_MODE_ARG_CHECKED\((\w+), (\d+)\)"),
241 lambda match: Arg("StrictMode", match.group(1), int(match.group(2))))
242
243 boolean_arg_parser = ArgParser(
244 re.compile("^\s*CONVERT_BOOLEAN_ARG_CHECKED\((\w+), (\d+)\)"),
245 lambda match: Arg("Boolean", match.group(1), int(match.group(2))))
246
247 property_details_parser = ArgParser(
248 re.compile("^\s*CONVERT_PROPERTY_DETAILS_CHECKED\((\w+), (\d+)\)"),
249 lambda match: Arg("PropertyDetails", match.group(1), int(match.group(2))))
250
251 arg_parsers = [handle_arg_parser, plain_arg_parser, number_handle_arg_parser,
252 smi_arg_parser,
253 double_arg_parser, number_arg_parser, strict_mode_arg_parser,
254 boolean_arg_parser, property_details_parser]
255
256
257 def SetArgsLength(self, match):
258 self.argslength = int(match.group(1))
259
260 def TryParseArg(self, line):
261 for parser in Function.arg_parsers:
262 match = parser.regex.match(line)
263 if match:
264 arg = parser.ArgCtor(match)
265 self.args[arg.index] = arg
266 return True
267 return False
268
269 def Filename(self):
270 return "%s.js" % self.name.lower()
271
272 def __str__(self):
273 s = [self.name, "("]
274 argcount = self.argslength
275 if argcount < 0:
276 print("WARNING: unknown argslength for function %s" % self.name)
277 if self.args:
278 argcount = max([self.args[i].index + 1 for i in self.args])
279 else:
280 argcount = 0
281 for i in range(argcount):
282 if i > 0: s.append(", ")
283 s.append(self.args[i].type if i in self.args else "<unknown>")
284 s.append(")")
285 return "".join(s)
286
287 # Parses HEADERFILENAME to find out which runtime functions are "inline".
288 def FindInlineRuntimeFunctions():
289 inline_functions = []
290 with open(HEADERFILENAME, "r") as f:
291 inline_list = "#define INLINE_FUNCTION_LIST(F) \\\n"
292 inline_opt_list = "#define INLINE_OPTIMIZED_FUNCTION_LIST(F) \\\n"
293 inline_function = re.compile(r"^\s*F\((\w+), \d+, \d+\)\s*\\?")
294 mode = "SEARCHING"
295 for line in f:
296 if mode == "ACTIVE":
297 match = inline_function.match(line)
298 if match:
299 inline_functions.append(match.group(1))
300 if not line.endswith("\\\n"):
301 mode = "SEARCHING"
302 elif mode == "SEARCHING":
303 if line == inline_list or line == inline_opt_list:
304 mode = "ACTIVE"
305 return inline_functions
306
307
308 # Detects runtime functions by parsing FILENAME.
309 def FindRuntimeFunctions():
310 inline_functions = FindInlineRuntimeFunctions()
311 functions = []
312 with open(FILENAME, "r") as f:
313 function = None
314 partial_line = ""
315 for line in f:
316 # Multi-line definition support, ignoring macros.
317 if line.startswith("RUNTIME_FUNCTION") and not line.endswith("{\n"):
318 if line.endswith("\\\n"): continue
319 partial_line = line.rstrip()
320 continue
321 if partial_line:
322 partial_line += " " + line.strip()
323 if partial_line.endswith("{"):
324 line = partial_line
325 partial_line = ""
326 else:
327 continue
328
329 match = FUNCTION.match(line)
330 if match:
331 function = Function(match)
332 if function.name in inline_functions:
333 function.inline = "_"
334 continue
335 if function is None: continue
336
337 match = ARGSLENGTH.match(line)
338 if match:
339 function.SetArgsLength(match)
340 continue
341
342 if function.TryParseArg(line):
343 continue
344
345 if line == FUNCTIONEND:
346 if function is not None:
347 functions.append(function)
348 function = None
349 return functions
350
351 # Classifies runtime functions.
352 def ClassifyFunctions(functions):
353 # Can be fuzzed with a JavaScript testcase.
354 js_fuzzable_functions = []
355 # We have enough information to fuzz these, but they need inputs that
356 # cannot be created or passed around in JavaScript.
357 cctest_fuzzable_functions = []
358 # This script does not have enough information about these.
359 unknown_functions = []
360
361 types = {}
362 for f in functions:
363 if f.name in BLACKLISTED:
364 continue
365 decision = js_fuzzable_functions
366 custom = CUSTOM_KNOWN_GOOD_INPUT.get(f.name, None)
367 if f.argslength < 0:
368 # Unknown length -> give up unless there's a custom definition.
369 if custom and custom[-1] is not None:
370 f.argslength = custom[-1]
371 assert len(custom) == f.argslength + 1, \
372 ("%s: last custom definition must be argslength" % f.name)
373 else:
374 decision = unknown_functions
375 else:
376 if custom:
377 # Any custom definitions must match the known argslength.
378 assert len(custom) == f.argslength + 1, \
379 ("%s should have %d custom definitions but has %d" %
380 (f.name, f.argslength + 1, len(custom)))
381 for i in range(f.argslength):
382 if custom and custom[i] is not None:
383 # All good, there's a custom definition.
384 pass
385 elif not i in f.args:
386 # No custom definition and no parse result -> give up.
387 decision = unknown_functions
388 else:
389 t = f.args[i].type
390 if t in NON_JS_TYPES:
391 decision = cctest_fuzzable_functions
392 else:
393 assert t in JS_TYPE_GENERATORS, \
394 ("type generator not found for %s, function: %s" % (t, f))
395 decision.append(f)
396 return (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions)
397
398
399 def GenerateJSTestcaseForFunction(f):
400 s = ["// Copyright 2014 the V8 project authors. All rights reserved.",
401 "// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY",
402 "// Flags: --allow-natives-syntax --harmony"]
403 call = "%%%s%s(" % (f.inline, f.name)
404 custom = CUSTOM_KNOWN_GOOD_INPUT.get(f.name, None)
405 for i in range(f.argslength):
406 if custom and custom[i] is not None:
407 (name, value) = ("arg%d" % i, custom[i])
408 else:
409 arg = f.args[i]
410 (name, value) = (arg.name, JS_TYPE_GENERATORS[arg.type])
411 s.append("var %s = %s;" % (name, value))
412 if i > 0: call += ", "
413 call += name
414 call += ");"
415 if f.name in THROWS:
416 s.append("try {")
417 s.append(call);
418 s.append("} catch(e) {}")
419 else:
420 s.append(call)
421 testcase = "\n".join(s)
422 path = os.path.join(BASEPATH, f.Filename())
423 with open(path, "w") as f:
424 f.write("%s\n" % testcase)
425
426 def GenerateTestcases(functions):
427 shutil.rmtree(BASEPATH) # Re-generate everything.
428 os.makedirs(BASEPATH)
429 for f in functions:
430 GenerateJSTestcaseForFunction(f)
431
432 def PrintUsage():
433 print """Usage: %(this_script)s ACTION
434
435 where ACTION can be:
436
437 info Print diagnostic info.
438 check Check that runtime functions can be parsed as expected, and that
439 test cases exist.
440 generate Parse source code for runtime functions, and auto-generate
441 test cases for them. Warning: this will nuke and re-create
442 %(path)s.
443 """ % {"path": os.path.relpath(BASEPATH), "this_script": THIS_SCRIPT}
444
445 if __name__ == "__main__":
446 if len(sys.argv) != 2:
447 PrintUsage()
448 sys.exit(1)
449 action = sys.argv[1]
450 if action in ["-h", "--help", "help"]:
451 PrintUsage()
452 sys.exit(0)
453
454 functions = FindRuntimeFunctions()
455 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \
456 ClassifyFunctions(functions)
457
458 if action == "info":
459 print("%d functions total; js_fuzzable_functions: %d, "
460 "cctest_fuzzable_functions: %d, unknown_functions: %d"
461 % (len(functions), len(js_fuzzable_functions),
462 len(cctest_fuzzable_functions), len(unknown_functions)))
463 print("unknown functions:")
464 for f in unknown_functions:
465 print(f)
466 sys.exit(0)
467
468 if action == "check":
469 error = False
470 def CheckCount(actual, expected, description):
471 global error
472 if len(actual) != expected:
473 print("Expected to detect %d %s, but found %d." % (
474 expected, description, len(actual)))
475 print("If this change is intentional, please update the expectations"
476 " at the top of %s." % THIS_SCRIPT)
477 error = True
478 CheckCount(functions, EXPECTED_FUNCTION_COUNT, "functions in total")
479 CheckCount(js_fuzzable_functions, EXPECTED_FUZZABLE_COUNT,
480 "JavaScript-fuzzable functions")
481 CheckCount(cctest_fuzzable_functions, EXPECTED_CCTEST_COUNT,
482 "cctest-fuzzable functions")
483 CheckCount(unknown_functions, EXPECTED_UNKNOWN_COUNT,
484 "functions with incomplete type information")
485
486 def CheckTestcasesExisting(functions):
487 global error
488 for f in functions:
489 if not os.path.isfile(os.path.join(BASEPATH, f.Filename())):
490 print("Missing testcase for %s, please run '%s generate'" %
491 (f.name, THIS_SCRIPT))
492 error = True
493 files = filter(lambda filename: not filename.startswith("."),
494 os.listdir(BASEPATH))
495 if (len(files) != len(functions)):
496 unexpected_files = set(files) - set([f.Filename() for f in functions])
497 for f in unexpected_files:
498 print("Unexpected testcase: %s" % os.path.join(BASEPATH, f))
499 error = True
500 CheckTestcasesExisting(js_fuzzable_functions)
501
502 if error:
503 sys.exit(1)
504 print("Generated runtime tests: all good.")
505 sys.exit(0)
506
507 if action == "generate":
508 GenerateTestcases(js_fuzzable_functions)
509 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