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

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