1#!/usr/local/bin/python3.8 2# Copyright 2013 The Emscripten Authors. All rights reserved. 3# Emscripten is available under two separate licenses, the MIT license and the 4# University of Illinois/NCSA Open Source License. Both these licenses can be 5# found in the LICENSE file. 6 7"""Runs csmith, a C fuzzer, and looks for bugs. 8 9CSMITH_PATH should be set to something like /usr/local/include/csmith 10""" 11 12import os 13import sys 14import shutil 15import random 16from distutils.spawn import find_executable 17from subprocess import check_call, Popen, PIPE, CalledProcessError 18 19script_dir = os.path.dirname(os.path.abspath(__file__)) 20sys.path.append(os.path.join(os.path.dirname(os.path.dirname(script_dir)))) 21 22from tools import shared 23 24# can add flags like --no-threads --ion-offthread-compile=off 25engine = eval('shared.' + sys.argv[1]) if len(sys.argv) > 1 else shared.JS_ENGINES[0] 26 27print('testing js engine', engine) 28 29TEST_BINARYEN = 1 30 31CSMITH = os.environ.get('CSMITH', find_executable('csmith')) 32assert CSMITH, 'Could not find CSmith on your PATH. Please set the environment variable CSMITH.' 33CSMITH_PATH = os.environ.get('CSMITH_PATH', '/usr/include/csmith') 34assert os.path.exists(CSMITH_PATH), 'Please set the environment variable CSMITH_PATH.' 35CSMITH_CFLAGS = ['-I', CSMITH_PATH] 36 37filename = os.path.join(os.getcwd(), 'temp_fuzzcode' + str(os.getpid()) + '_') 38 39shared.DEFAULT_TIMEOUT = 5 40 41tried = 0 42 43notes = {'invalid': 0, 'embug': 0} 44 45fails = 0 46 47while 1: 48 if random.random() < 0.666: 49 opts = '-O' + str(random.randint(0, 3)) 50 else: 51 if random.random() < 0.5: 52 opts = '-Os' 53 else: 54 opts = '-Oz' 55 print('opt level:', opts) 56 57 llvm_opts = [] 58 if random.random() < 0.5: 59 llvm_opts = ['--llvm-opts', str(random.randint(0, 3))] 60 61 print('Tried %d, notes: %s' % (tried, notes)) 62 print('1) Generate source') 63 extra_args = [] 64 if random.random() < 0.5: 65 extra_args += ['--no-math64'] 66 extra_args += ['--no-bitfields'] # due to pnacl bug 4027, "LLVM ERROR: can't convert calls with illegal types" 67 # if random.random() < 0.5: extra_args += ['--float'] # XXX hits undefined behavior on float=>int conversions (too big to fit) 68 if random.random() < 0.5: 69 extra_args += ['--max-funcs', str(random.randint(10, 30))] 70 suffix = '.c' 71 COMP = shared.CLANG_CC 72 fullname = filename + suffix 73 check_call([CSMITH, '--no-volatiles', '--no-packed-struct'] + extra_args, 74 # ['--max-block-depth', '2', '--max-block-size', '2', '--max-expr-complexity', '2', '--max-funcs', '2'], 75 stdout=open(fullname, 'w')) 76 print('1) Generate source... %.2f K' % (len(open(fullname).read()) / 1024.)) 77 78 tried += 1 79 80 print('2) Compile natively') 81 shared.try_delete(filename) 82 try: 83 shared.run_process([COMP, '-m32', opts, fullname, '-o', filename + '1'] + CSMITH_CFLAGS + ['-w']) # + shared.get_cflags() 84 except CalledProcessError: 85 print('Failed to compile natively using clang') 86 notes['invalid'] += 1 87 continue 88 89 shared.run_process([COMP, '-m32', opts, '-emit-llvm', '-c', fullname, '-o', filename + '.bc'] + CSMITH_CFLAGS + shared.get_cflags() + ['-w']) 90 shared.run_process([shared.path_from_root('tools', 'nativize_llvm.py'), filename + '.bc'], stderr=PIPE) 91 shutil.move(filename + '.bc.run', filename + '2') 92 shared.run_process([COMP, fullname, '-o', filename + '3'] + CSMITH_CFLAGS + ['-w']) 93 print('3) Run natively') 94 try: 95 correct1 = shared.timeout_run(Popen([filename + '1'], stdout=PIPE, stderr=PIPE), 3) 96 if 'Segmentation fault' in correct1 or len(correct1) < 10: 97 raise Exception('segfault') 98 correct2 = shared.timeout_run(Popen([filename + '2'], stdout=PIPE, stderr=PIPE), 3) 99 if 'Segmentation fault' in correct2 or len(correct2) < 10: 100 raise Exception('segfault') 101 correct3 = shared.timeout_run(Popen([filename + '3'], stdout=PIPE, stderr=PIPE), 3) 102 if 'Segmentation fault' in correct3 or len(correct3) < 10: 103 raise Exception('segfault') 104 if correct1 != correct3: 105 raise Exception('clang opts change result') 106 except Exception as e: 107 print('Failed or infinite looping in native, skipping', e) 108 notes['invalid'] += 1 109 continue 110 111 fail_output_name = 'newfail_%d_%d%s' % (os.getpid(), fails, suffix) 112 113 print('4) Compile JS-ly and compare') 114 115 def try_js(args=[]): 116 shared.try_delete(filename + '.js') 117 js_args = [shared.EMCC, fullname, '-o', filename + '.js'] + [opts] + llvm_opts + CSMITH_CFLAGS + args + ['-w'] 118 if TEST_BINARYEN: 119 if random.random() < 0.5: 120 js_args += ['-g'] 121 if random.random() < 0.5: 122 # pick random passes 123 BINARYEN_EXTRA_PASSES = [ 124 "code-pushing", 125 "duplicate-function-elimination", 126 "dce", 127 "remove-unused-brs", 128 "remove-unused-names", 129 "local-cse", 130 "optimize-instructions", 131 "post-emscripten", 132 "precompute", 133 "simplify-locals", 134 "simplify-locals-nostructure", 135 "vacuum", 136 "coalesce-locals", 137 "reorder-locals", 138 "merge-blocks", 139 "remove-unused-module-elements", 140 "memory-packing", 141 ] 142 passes = [] 143 while 1: 144 passes.append(random.choice(BINARYEN_EXTRA_PASSES)) 145 if random.random() < 0.1: 146 break 147 js_args += ['-s', 'BINARYEN_EXTRA_PASSES="' + ','.join(passes) + '"'] 148 if random.random() < 0.5: 149 js_args += ['-s', 'ALLOW_MEMORY_GROWTH=1'] 150 if random.random() < 0.5 and 'ALLOW_MEMORY_GROWTH=1' not in js_args and 'BINARYEN=1' not in js_args: 151 js_args += ['-s', 'MAIN_MODULE=1'] 152 if random.random() < 0.25: 153 js_args += ['-s', 'INLINING_LIMIT=1'] # inline nothing, for more call interaction 154 if random.random() < 0.5: 155 js_args += ["--memory-init-file", "0", "-s", "MEM_INIT_METHOD=2"] 156 if random.random() < 0.5: 157 js_args += ['-s', 'ASSERTIONS=1'] 158 print('(compile)', ' '.join(js_args)) 159 short_args = [shared.EMCC, fail_output_name] + js_args[5:] 160 escaped_short_args = map(lambda x: ("'" + x + "'") if '"' in x else x, short_args) 161 open(fullname, 'a').write('\n// ' + ' '.join(escaped_short_args) + '\n\n') 162 try: 163 shared.run_process(js_args) 164 assert os.path.exists(filename + '.js') 165 return js_args 166 except Exception: 167 return False 168 169 def execute_js(engine): 170 print('(run in %s)' % engine) 171 try: 172 js = shared.timeout_run(Popen(shared.NODE_JS + [filename + '.js'], stdout=PIPE, stderr=PIPE), 15 * 60) 173 except Exception: 174 print('failed to run in primary') 175 return False 176 js = js.split('\n')[0] + '\n' # remove any extra printed stuff (node workarounds) 177 return correct1 == js or correct2 == js 178 179 def fail(): 180 global fails 181 print("EMSCRIPTEN BUG") 182 notes['embug'] += 1 183 fails += 1 184 shutil.copyfile(fullname, fail_output_name) 185 186 js_args = try_js() 187 if not js_args: 188 fail() 189 continue 190 if not execute_js(engine): 191 fail() 192 continue 193