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 | 6 import itertools |
| 7 import js2c |
7 import multiprocessing | 8 import multiprocessing |
8 import optparse | 9 import optparse |
9 import os | 10 import os |
10 import random | 11 import random |
11 import re | 12 import re |
12 import shutil | 13 import shutil |
13 import signal | 14 import signal |
14 import string | 15 import string |
15 import subprocess | 16 import subprocess |
16 import sys | 17 import sys |
(...skipping 26 matching lines...) Expand all Loading... |
43 # TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION | 44 # TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION |
44 | 45 |
45 # Counts of functions in each detection state. These are used to assert | 46 # Counts of functions in each detection state. These are used to assert |
46 # that the parser doesn't bit-rot. Change the values as needed when you add, | 47 # that the parser doesn't bit-rot. Change the values as needed when you add, |
47 # remove or change runtime functions, but make sure we don't lose our ability | 48 # remove or change runtime functions, but make sure we don't lose our ability |
48 # to parse them! | 49 # to parse them! |
49 EXPECTED_FUNCTION_COUNT = 362 | 50 EXPECTED_FUNCTION_COUNT = 362 |
50 EXPECTED_FUZZABLE_COUNT = 329 | 51 EXPECTED_FUZZABLE_COUNT = 329 |
51 EXPECTED_CCTEST_COUNT = 6 | 52 EXPECTED_CCTEST_COUNT = 6 |
52 EXPECTED_UNKNOWN_COUNT = 5 | 53 EXPECTED_UNKNOWN_COUNT = 5 |
| 54 EXPECTED_BUILTINS_COUNT = 827 |
53 | 55 |
54 | 56 |
55 # Don't call these at all. | 57 # Don't call these at all. |
56 BLACKLISTED = [ | 58 BLACKLISTED = [ |
57 "Abort", # Kills the process. | 59 "Abort", # Kills the process. |
58 "AbortJS", # Kills the process. | 60 "AbortJS", # Kills the process. |
59 "CompileForOnStackReplacement", # Riddled with ASSERTs. | 61 "CompileForOnStackReplacement", # Riddled with ASSERTs. |
60 "IS_VAR", # Not implemented in the runtime. | 62 "IS_VAR", # Not implemented in the runtime. |
61 "ListNatives", # Not available in Release mode. | 63 "ListNatives", # Not available in Release mode. |
62 "SetAllocationTimeout", # Too slow for fuzzing. | 64 "SetAllocationTimeout", # Too slow for fuzzing. |
(...skipping 228 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
291 # 'ffoo12345678901234567890'.substr(1) | 293 # 'ffoo12345678901234567890'.substr(1) |
292 return self._Variable(name, "\"%s\".substr(1)" % s) | 294 return self._Variable(name, "\"%s\".substr(1)" % s) |
293 | 295 |
294 def _ConsString(self, name): | 296 def _ConsString(self, name): |
295 s1 = self._RawRandomString(8, 15) | 297 s1 = self._RawRandomString(8, 15) |
296 s2 = self._RawRandomString(8, 15) | 298 s2 = self._RawRandomString(8, 15) |
297 # 'foo12345' + (function() { return 'bar12345';})() | 299 # 'foo12345' + (function() { return 'bar12345';})() |
298 return self._Variable(name, | 300 return self._Variable(name, |
299 "\"%s\" + (function() { return \"%s\";})()" % (s1, s2)) | 301 "\"%s\" + (function() { return \"%s\";})()" % (s1, s2)) |
300 | 302 |
301 def _ExternalString(self, name): | |
302 # Needs --expose-externalize-string. | |
303 return None | |
304 | |
305 def _InternalizedString(self, name): | 303 def _InternalizedString(self, name): |
306 return self._Variable(name, "\"%s\"" % self._RawRandomString(0, 20)) | 304 return self._Variable(name, "\"%s\"" % self._RawRandomString(0, 20)) |
307 | 305 |
308 def _String(self, name, recursion_budget): | 306 def _String(self, name, recursion_budget): |
309 die = random.random() | 307 die = random.random() |
310 if die < 0.5: | 308 if die < 0.5: |
311 string = random.choice(self.USUAL_SUSPECT_PROPERTIES) | 309 string = random.choice(self.USUAL_SUSPECT_PROPERTIES) |
312 return self._Variable(name, "\"%s\"" % string) | 310 return self._Variable(name, "\"%s\"" % string) |
313 elif die < 0.6: | 311 elif die < 0.6: |
314 number_name = name + "_number" | 312 number_name = name + "_number" |
(...skipping 596 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
911 | 909 |
912 if function.TryParseArg(line): | 910 if function.TryParseArg(line): |
913 continue | 911 continue |
914 | 912 |
915 if line == FUNCTIONEND: | 913 if line == FUNCTIONEND: |
916 if function is not None: | 914 if function is not None: |
917 functions.append(function) | 915 functions.append(function) |
918 function = None | 916 function = None |
919 return functions | 917 return functions |
920 | 918 |
| 919 |
| 920 # Hack: This must have the same fields as class Function above, because the |
| 921 # two are used polymorphically in RunFuzzer(). We could use inheritance... |
| 922 class Builtin(object): |
| 923 def __init__(self, match): |
| 924 self.name = match.group(1) |
| 925 args = match.group(2) |
| 926 self.argslength = 0 if args == "" else args.count(",") + 1 |
| 927 self.inline = "" |
| 928 self.args = {} |
| 929 if self.argslength > 0: |
| 930 args = args.split(",") |
| 931 for i in range(len(args)): |
| 932 # a = args[i].strip() # TODO: filter out /* comments */ first. |
| 933 a = "" |
| 934 self.args[i] = Arg("Object", a, i) |
| 935 |
| 936 def __str__(self): |
| 937 return "%s(%d)" % (self.name, self.argslength) |
| 938 |
| 939 |
| 940 def FindJSBuiltins(): |
| 941 PATH = "src" |
| 942 fileslist = [] |
| 943 for (root, dirs, files) in os.walk(PATH): |
| 944 for f in files: |
| 945 if f.endswith(".js"): |
| 946 fileslist.append(os.path.join(root, f)) |
| 947 builtins = [] |
| 948 regexp = re.compile("^function (\w+)\s*\((.*?)\) {") |
| 949 matches = 0 |
| 950 for filename in fileslist: |
| 951 with open(filename, "r") as f: |
| 952 file_contents = f.read() |
| 953 file_contents = js2c.ExpandInlineMacros(file_contents) |
| 954 lines = file_contents.split("\n") |
| 955 partial_line = "" |
| 956 for line in lines: |
| 957 if line.startswith("function") and not '{' in line: |
| 958 partial_line += line.rstrip() |
| 959 continue |
| 960 if partial_line: |
| 961 partial_line += " " + line.strip() |
| 962 if '{' in line: |
| 963 line = partial_line |
| 964 partial_line = "" |
| 965 else: |
| 966 continue |
| 967 match = regexp.match(line) |
| 968 if match: |
| 969 builtins.append(Builtin(match)) |
| 970 return builtins |
| 971 |
| 972 |
921 # Classifies runtime functions. | 973 # Classifies runtime functions. |
922 def ClassifyFunctions(functions): | 974 def ClassifyFunctions(functions): |
923 # Can be fuzzed with a JavaScript testcase. | 975 # Can be fuzzed with a JavaScript testcase. |
924 js_fuzzable_functions = [] | 976 js_fuzzable_functions = [] |
925 # We have enough information to fuzz these, but they need inputs that | 977 # We have enough information to fuzz these, but they need inputs that |
926 # cannot be created or passed around in JavaScript. | 978 # cannot be created or passed around in JavaScript. |
927 cctest_fuzzable_functions = [] | 979 cctest_fuzzable_functions = [] |
928 # This script does not have enough information about these. | 980 # This script does not have enough information about these. |
929 unknown_functions = [] | 981 unknown_functions = [] |
930 | 982 |
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1011 shutil.rmtree(BASEPATH) # Re-generate everything. | 1063 shutil.rmtree(BASEPATH) # Re-generate everything. |
1012 os.makedirs(BASEPATH) | 1064 os.makedirs(BASEPATH) |
1013 for f in functions: | 1065 for f in functions: |
1014 GenerateJSTestcaseForFunction(f) | 1066 GenerateJSTestcaseForFunction(f) |
1015 | 1067 |
1016 | 1068 |
1017 def _SaveFileName(save_path, process_id, save_file_index): | 1069 def _SaveFileName(save_path, process_id, save_file_index): |
1018 return "%s/fuzz_%d_%d.js" % (save_path, process_id, save_file_index) | 1070 return "%s/fuzz_%d_%d.js" % (save_path, process_id, save_file_index) |
1019 | 1071 |
1020 | 1072 |
| 1073 def _GetFuzzableRuntimeFunctions(): |
| 1074 functions = FindRuntimeFunctions() |
| 1075 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \ |
| 1076 ClassifyFunctions(functions) |
| 1077 return js_fuzzable_functions |
| 1078 |
| 1079 |
| 1080 FUZZ_TARGET_LISTS = { |
| 1081 "runtime": _GetFuzzableRuntimeFunctions, |
| 1082 "builtins": FindJSBuiltins, |
| 1083 } |
| 1084 |
| 1085 |
1021 def RunFuzzer(process_id, options, stop_running): | 1086 def RunFuzzer(process_id, options, stop_running): |
| 1087 MAX_SLEEP_TIME = 0.1 |
| 1088 INITIAL_SLEEP_TIME = 0.001 |
| 1089 SLEEP_TIME_FACTOR = 1.25 |
1022 base_file_name = "/dev/shm/runtime_fuzz_%d" % process_id | 1090 base_file_name = "/dev/shm/runtime_fuzz_%d" % process_id |
1023 test_file_name = "%s.js" % base_file_name | 1091 test_file_name = "%s.js" % base_file_name |
1024 stderr_file_name = "%s.out" % base_file_name | 1092 stderr_file_name = "%s.out" % base_file_name |
1025 save_file_index = 0 | 1093 save_file_index = 0 |
1026 while os.path.exists(_SaveFileName(options.save_path, process_id, | 1094 while os.path.exists(_SaveFileName(options.save_path, process_id, |
1027 save_file_index)): | 1095 save_file_index)): |
1028 save_file_index += 1 | 1096 save_file_index += 1 |
1029 MAX_SLEEP_TIME = 0.1 | |
1030 INITIAL_SLEEP_TIME = 0.001 | |
1031 SLEEP_TIME_FACTOR = 1.5 | |
1032 | 1097 |
1033 functions = FindRuntimeFunctions() | 1098 targets = FUZZ_TARGET_LISTS[options.fuzz_target]() |
1034 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \ | |
1035 ClassifyFunctions(functions) | |
1036 | |
1037 try: | 1099 try: |
1038 for i in range(options.num_tests): | 1100 for i in range(options.num_tests): |
1039 if stop_running.is_set(): break | 1101 if stop_running.is_set(): break |
1040 function = random.choice(js_fuzzable_functions) # TODO: others too | 1102 function = None |
1041 if function.argslength == 0: continue | 1103 while function is None or function.argslength == 0: |
| 1104 function = random.choice(targets) |
1042 args = [] | 1105 args = [] |
1043 definitions = [] | 1106 definitions = [] |
1044 gen = Generator() | 1107 gen = Generator() |
1045 for i in range(function.argslength): | 1108 for i in range(function.argslength): |
1046 arg = function.args[i] | 1109 arg = function.args[i] |
1047 argname = "arg%d%s" % (i, arg.name) | 1110 argname = "arg%d%s" % (i, arg.name) |
1048 args.append(argname) | 1111 args.append(argname) |
1049 definitions += gen.RandomVariable(argname, arg.type, simple=False) | 1112 definitions += gen.RandomVariable(argname, arg.type, simple=False) |
1050 testcase = _GenerateTestcase(function, definitions, args, True) | 1113 testcase = _GenerateTestcase(function, definitions, args, True) |
1051 with open(test_file_name, "w") as f: | 1114 with open(test_file_name, "w") as f: |
(...skipping 27 matching lines...) Expand all Loading... |
1079 if line.strip() == "# Allocation failed - process out of memory": | 1142 if line.strip() == "# Allocation failed - process out of memory": |
1080 oom = True | 1143 oom = True |
1081 break | 1144 break |
1082 if oom: continue | 1145 if oom: continue |
1083 save_name = _SaveFileName(options.save_path, process_id, | 1146 save_name = _SaveFileName(options.save_path, process_id, |
1084 save_file_index) | 1147 save_file_index) |
1085 shutil.copyfile(test_file_name, save_name) | 1148 shutil.copyfile(test_file_name, save_name) |
1086 save_file_index += 1 | 1149 save_file_index += 1 |
1087 except KeyboardInterrupt: | 1150 except KeyboardInterrupt: |
1088 stop_running.set() | 1151 stop_running.set() |
1089 except Exception, e: | |
1090 print e | |
1091 finally: | 1152 finally: |
1092 os.remove(test_file_name) | 1153 if os.path.exists(test_file_name): |
1093 os.remove(stderr_file_name) | 1154 os.remove(test_file_name) |
| 1155 if os.path.exists(stderr_file_name): |
| 1156 os.remove(stderr_file_name) |
1094 | 1157 |
1095 | 1158 |
1096 def BuildOptionParser(): | 1159 def BuildOptionParser(): |
1097 usage = """Usage: %%prog [options] ACTION | 1160 usage = """Usage: %%prog [options] ACTION |
1098 | 1161 |
1099 where ACTION can be: | 1162 where ACTION can be: |
1100 | 1163 |
1101 info Print diagnostic info. | 1164 info Print diagnostic info. |
1102 check Check that runtime functions can be parsed as expected, and that | 1165 check Check that runtime functions can be parsed as expected, and that |
1103 test cases exist. | 1166 test cases exist. |
1104 generate Parse source code for runtime functions, and auto-generate | 1167 generate Parse source code for runtime functions, and auto-generate |
1105 test cases for them. Warning: this will nuke and re-create | 1168 test cases for them. Warning: this will nuke and re-create |
1106 %(path)s. | 1169 %(path)s. |
1107 fuzz Generate fuzz tests, run them, save those that crashed (see options). | 1170 fuzz Generate fuzz tests, run them, save those that crashed (see options). |
1108 """ % {"path": os.path.relpath(BASEPATH)} | 1171 """ % {"path": os.path.relpath(BASEPATH)} |
1109 | 1172 |
1110 o = optparse.OptionParser(usage=usage) | 1173 o = optparse.OptionParser(usage=usage) |
1111 o.add_option("--binary", default="out/x64.debug/d8", | 1174 o.add_option("--binary", default="out/x64.debug/d8", |
1112 help="d8 binary used for running fuzz tests (default: %default)") | 1175 help="d8 binary used for running fuzz tests (default: %default)") |
| 1176 o.add_option("--fuzz-target", default="runtime", |
| 1177 help="Set of functions targeted by fuzzing. Allowed values: " |
| 1178 "%s (default: %%default)" % ", ".join(FUZZ_TARGET_LISTS)) |
1113 o.add_option("-n", "--num-tests", default=1000, type="int", | 1179 o.add_option("-n", "--num-tests", default=1000, type="int", |
1114 help="Number of fuzz tests to generate per worker process" | 1180 help="Number of fuzz tests to generate per worker process" |
1115 " (default: %default)") | 1181 " (default: %default)") |
1116 o.add_option("--save-path", default="~/runtime_fuzz_output", | 1182 o.add_option("--save-path", default="~/runtime_fuzz_output", |
1117 help="Path to directory where failing tests will be stored" | 1183 help="Path to directory where failing tests will be stored" |
1118 " (default: %default)") | 1184 " (default: %default)") |
1119 o.add_option("--timeout", default=20, type="int", | 1185 o.add_option("--timeout", default=20, type="int", |
1120 help="Timeout for each fuzz test (in seconds, default:" | 1186 help="Timeout for each fuzz test (in seconds, default:" |
1121 "%default)") | 1187 "%default)") |
1122 return o | 1188 return o |
1123 | 1189 |
1124 | 1190 |
| 1191 def ProcessOptions(options, args): |
| 1192 options.save_path = os.path.expanduser(options.save_path) |
| 1193 if options.fuzz_target not in FUZZ_TARGET_LISTS: |
| 1194 print("Invalid fuzz target: %s" % options.fuzz_target) |
| 1195 return False |
| 1196 if len(args) != 1 or args[0] == "help": |
| 1197 return False |
| 1198 return True |
| 1199 |
| 1200 |
1125 def Main(): | 1201 def Main(): |
1126 parser = BuildOptionParser() | 1202 parser = BuildOptionParser() |
1127 (options, args) = parser.parse_args() | 1203 (options, args) = parser.parse_args() |
1128 options.save_path = os.path.expanduser(options.save_path) | |
1129 | 1204 |
1130 if len(args) != 1 or args[0] == "help": | 1205 if not ProcessOptions(options, args): |
1131 parser.print_help() | 1206 parser.print_help() |
1132 return 1 | 1207 return 1 |
1133 action = args[0] | 1208 action = args[0] |
1134 | 1209 |
1135 functions = FindRuntimeFunctions() | 1210 functions = FindRuntimeFunctions() |
1136 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \ | 1211 (js_fuzzable_functions, cctest_fuzzable_functions, unknown_functions) = \ |
1137 ClassifyFunctions(functions) | 1212 ClassifyFunctions(functions) |
| 1213 builtins = FindJSBuiltins() |
1138 | 1214 |
1139 if action == "test": | 1215 if action == "test": |
1140 gen = Generator() | 1216 print("put your temporary debugging code here") |
1141 vartype = "JSTypedArray" | |
1142 print("simple: %s" % gen.RandomVariable("x", vartype, True)) | |
1143 for i in range(10): | |
1144 print("----") | |
1145 print("%s" % "\n".join(gen.RandomVariable("x", vartype, False))) | |
1146 return 0 | 1217 return 0 |
1147 | 1218 |
1148 if action == "info": | 1219 if action == "info": |
1149 print("%d functions total; js_fuzzable_functions: %d, " | 1220 print("%d functions total; js_fuzzable_functions: %d, " |
1150 "cctest_fuzzable_functions: %d, unknown_functions: %d" | 1221 "cctest_fuzzable_functions: %d, unknown_functions: %d" |
1151 % (len(functions), len(js_fuzzable_functions), | 1222 % (len(functions), len(js_fuzzable_functions), |
1152 len(cctest_fuzzable_functions), len(unknown_functions))) | 1223 len(cctest_fuzzable_functions), len(unknown_functions))) |
| 1224 print("%d JavaScript builtins" % len(builtins)) |
1153 print("unknown functions:") | 1225 print("unknown functions:") |
1154 for f in unknown_functions: | 1226 for f in unknown_functions: |
1155 print(f) | 1227 print(f) |
1156 return 0 | 1228 return 0 |
1157 | 1229 |
1158 if action == "check": | 1230 if action == "check": |
1159 errors = 0 | 1231 errors = 0 |
1160 | 1232 |
1161 def CheckCount(actual, expected, description): | 1233 def CheckCount(actual, expected, description): |
1162 if len(actual) != expected: | 1234 if len(actual) != expected: |
1163 print("Expected to detect %d %s, but found %d." % ( | 1235 print("Expected to detect %d %s, but found %d." % ( |
1164 expected, description, len(actual))) | 1236 expected, description, len(actual))) |
1165 print("If this change is intentional, please update the expectations" | 1237 print("If this change is intentional, please update the expectations" |
1166 " at the top of %s." % THIS_SCRIPT) | 1238 " at the top of %s." % THIS_SCRIPT) |
1167 return 1 | 1239 return 1 |
1168 return 0 | 1240 return 0 |
1169 | 1241 |
1170 errors += CheckCount(functions, EXPECTED_FUNCTION_COUNT, | 1242 errors += CheckCount(functions, EXPECTED_FUNCTION_COUNT, |
1171 "functions in total") | 1243 "functions in total") |
1172 errors += CheckCount(js_fuzzable_functions, EXPECTED_FUZZABLE_COUNT, | 1244 errors += CheckCount(js_fuzzable_functions, EXPECTED_FUZZABLE_COUNT, |
1173 "JavaScript-fuzzable functions") | 1245 "JavaScript-fuzzable functions") |
1174 errors += CheckCount(cctest_fuzzable_functions, EXPECTED_CCTEST_COUNT, | 1246 errors += CheckCount(cctest_fuzzable_functions, EXPECTED_CCTEST_COUNT, |
1175 "cctest-fuzzable functions") | 1247 "cctest-fuzzable functions") |
1176 errors += CheckCount(unknown_functions, EXPECTED_UNKNOWN_COUNT, | 1248 errors += CheckCount(unknown_functions, EXPECTED_UNKNOWN_COUNT, |
1177 "functions with incomplete type information") | 1249 "functions with incomplete type information") |
| 1250 errors += CheckCount(builtins, EXPECTED_BUILTINS_COUNT, |
| 1251 "JavaScript builtins") |
1178 | 1252 |
1179 def CheckTestcasesExisting(functions): | 1253 def CheckTestcasesExisting(functions): |
1180 errors = 0 | 1254 errors = 0 |
1181 for f in functions: | 1255 for f in functions: |
1182 if not os.path.isfile(os.path.join(BASEPATH, f.Filename())): | 1256 if not os.path.isfile(os.path.join(BASEPATH, f.Filename())): |
1183 print("Missing testcase for %s, please run '%s generate'" % | 1257 print("Missing testcase for %s, please run '%s generate'" % |
1184 (f.name, THIS_SCRIPT)) | 1258 (f.name, THIS_SCRIPT)) |
1185 errors += 1 | 1259 errors += 1 |
1186 files = filter(lambda filename: not filename.startswith("."), | 1260 files = filter(lambda filename: not filename.startswith("."), |
1187 os.listdir(BASEPATH)) | 1261 os.listdir(BASEPATH)) |
1188 if (len(files) != len(functions)): | 1262 if (len(files) != len(functions)): |
1189 unexpected_files = set(files) - set([f.Filename() for f in functions]) | 1263 unexpected_files = set(files) - set([f.Filename() for f in functions]) |
1190 for f in unexpected_files: | 1264 for f in unexpected_files: |
1191 print("Unexpected testcase: %s" % os.path.join(BASEPATH, f)) | 1265 print("Unexpected testcase: %s" % os.path.join(BASEPATH, f)) |
1192 errors += 1 | 1266 errors += 1 |
1193 print("Run '%s generate' to automatically clean these up." | 1267 print("Run '%s generate' to automatically clean these up." |
1194 % THIS_SCRIPT) | 1268 % THIS_SCRIPT) |
1195 return errors | 1269 return errors |
1196 | 1270 |
1197 errors += CheckTestcasesExisting(js_fuzzable_functions) | 1271 errors += CheckTestcasesExisting(js_fuzzable_functions) |
1198 | 1272 |
| 1273 def CheckNameClashes(runtime_functions, builtins): |
| 1274 errors = 0 |
| 1275 runtime_map = {} |
| 1276 for f in runtime_functions: |
| 1277 runtime_map[f.name] = 1 |
| 1278 for b in builtins: |
| 1279 if b.name in runtime_map: |
| 1280 print("Builtin/Runtime_Function name clash: %s" % b.name) |
| 1281 errors += 1 |
| 1282 return errors |
| 1283 |
| 1284 errors += CheckNameClashes(functions, builtins) |
| 1285 |
1199 if errors > 0: | 1286 if errors > 0: |
1200 return 1 | 1287 return 1 |
1201 print("Generated runtime tests: all good.") | 1288 print("Generated runtime tests: all good.") |
1202 return 0 | 1289 return 0 |
1203 | 1290 |
1204 if action == "generate": | 1291 if action == "generate": |
1205 GenerateTestcases(js_fuzzable_functions) | 1292 GenerateTestcases(js_fuzzable_functions) |
1206 return 0 | 1293 return 0 |
1207 | 1294 |
1208 if action == "fuzz": | 1295 if action == "fuzz": |
(...skipping 10 matching lines...) Expand all Loading... |
1219 for i in range(len(processes)): | 1306 for i in range(len(processes)): |
1220 processes[i].join() | 1307 processes[i].join() |
1221 except KeyboardInterrupt: | 1308 except KeyboardInterrupt: |
1222 stop_running.set() | 1309 stop_running.set() |
1223 for i in range(len(processes)): | 1310 for i in range(len(processes)): |
1224 processes[i].join() | 1311 processes[i].join() |
1225 return 0 | 1312 return 0 |
1226 | 1313 |
1227 if __name__ == "__main__": | 1314 if __name__ == "__main__": |
1228 sys.exit(Main()) | 1315 sys.exit(Main()) |
OLD | NEW |