| #!/usr/bin/python |
| # Copyright 2013 The Emscripten Authors. All rights reserved. |
| # Emscripten is available under two separate licenses, the MIT license and the |
| # University of Illinois/NCSA Open Source License. Both these licenses can be |
| # found in the LICENSE file. |
| |
| """Runs csmith, a C fuzzer, and looks for bugs. |
| |
| CSMITH_PATH should be set to something like /usr/local/include/csmith |
| """ |
| |
| import os |
| import sys |
| import shutil |
| import random |
| from distutils.spawn import find_executable |
| from subprocess import check_call, Popen, PIPE, CalledProcessError |
| |
| script_dir = os.path.dirname(os.path.abspath(__file__)) |
| sys.path.append(os.path.join(os.path.dirname(os.path.dirname(script_dir)))) |
| |
| from tools import shared |
| |
| # can add flags like --no-threads --ion-offthread-compile=off |
| engine = eval('shared.' + sys.argv[1]) if len(sys.argv) > 1 else shared.JS_ENGINES[0] |
| |
| print('testing js engine', engine) |
| |
| TEST_BINARYEN = 1 |
| |
| CSMITH = os.environ.get('CSMITH', find_executable('csmith')) |
| assert CSMITH, 'Could not find CSmith on your PATH. Please set the environment variable CSMITH.' |
| CSMITH_PATH = os.environ.get('CSMITH_PATH', '/usr/include/csmith') |
| assert os.path.exists(CSMITH_PATH), 'Please set the environment variable CSMITH_PATH.' |
| CSMITH_CFLAGS = ['-I', CSMITH_PATH] |
| |
| filename = os.path.join(os.getcwd(), 'temp_fuzzcode' + str(os.getpid()) + '_') |
| |
| shared.DEFAULT_TIMEOUT = 5 |
| |
| tried = 0 |
| |
| notes = {'invalid': 0, 'embug': 0} |
| |
| fails = 0 |
| |
| while 1: |
| if random.random() < 0.666: |
| opts = '-O' + str(random.randint(0, 3)) |
| else: |
| if random.random() < 0.5: |
| opts = '-Os' |
| else: |
| opts = '-Oz' |
| print('opt level:', opts) |
| |
| llvm_opts = [] |
| if random.random() < 0.5: |
| llvm_opts = ['--llvm-opts', str(random.randint(0, 3))] |
| |
| print('Tried %d, notes: %s' % (tried, notes)) |
| print('1) Generate source') |
| extra_args = [] |
| if random.random() < 0.5: |
| extra_args += ['--no-math64'] |
| extra_args += ['--no-bitfields'] # due to pnacl bug 4027, "LLVM ERROR: can't convert calls with illegal types" |
| # if random.random() < 0.5: extra_args += ['--float'] # XXX hits undefined behavior on float=>int conversions (too big to fit) |
| if random.random() < 0.5: |
| extra_args += ['--max-funcs', str(random.randint(10, 30))] |
| suffix = '.c' |
| COMP = shared.CLANG_CC |
| fullname = filename + suffix |
| check_call([CSMITH, '--no-volatiles', '--no-packed-struct'] + extra_args, |
| # ['--max-block-depth', '2', '--max-block-size', '2', '--max-expr-complexity', '2', '--max-funcs', '2'], |
| stdout=open(fullname, 'w')) |
| print('1) Generate source... %.2f K' % (len(open(fullname).read()) / 1024.)) |
| |
| tried += 1 |
| |
| print('2) Compile natively') |
| shared.try_delete(filename) |
| try: |
| shared.run_process([COMP, '-m32', opts, fullname, '-o', filename + '1'] + CSMITH_CFLAGS + ['-w']) # + shared.get_cflags() |
| except CalledProcessError: |
| print('Failed to compile natively using clang') |
| notes['invalid'] += 1 |
| continue |
| |
| shared.run_process([COMP, '-m32', opts, '-emit-llvm', '-c', fullname, '-o', filename + '.bc'] + CSMITH_CFLAGS + shared.get_cflags() + ['-w']) |
| shared.run_process([shared.path_from_root('tools', 'nativize_llvm.py'), filename + '.bc'], stderr=PIPE) |
| shutil.move(filename + '.bc.run', filename + '2') |
| shared.run_process([COMP, fullname, '-o', filename + '3'] + CSMITH_CFLAGS + ['-w']) |
| print('3) Run natively') |
| try: |
| correct1 = shared.timeout_run(Popen([filename + '1'], stdout=PIPE, stderr=PIPE), 3) |
| if 'Segmentation fault' in correct1 or len(correct1) < 10: |
| raise Exception('segfault') |
| correct2 = shared.timeout_run(Popen([filename + '2'], stdout=PIPE, stderr=PIPE), 3) |
| if 'Segmentation fault' in correct2 or len(correct2) < 10: |
| raise Exception('segfault') |
| correct3 = shared.timeout_run(Popen([filename + '3'], stdout=PIPE, stderr=PIPE), 3) |
| if 'Segmentation fault' in correct3 or len(correct3) < 10: |
| raise Exception('segfault') |
| if correct1 != correct3: |
| raise Exception('clang opts change result') |
| except Exception as e: |
| print('Failed or infinite looping in native, skipping', e) |
| notes['invalid'] += 1 |
| continue |
| |
| fail_output_name = 'newfail_%d_%d%s' % (os.getpid(), fails, suffix) |
| |
| print('4) Compile JS-ly and compare') |
| |
| def try_js(args=[]): |
| shared.try_delete(filename + '.js') |
| js_args = [shared.EMCC, fullname, '-o', filename + '.js'] + [opts] + llvm_opts + CSMITH_CFLAGS + args + ['-w'] |
| if TEST_BINARYEN: |
| if random.random() < 0.5: |
| js_args += ['-g'] |
| if random.random() < 0.5: |
| # pick random passes |
| BINARYEN_EXTRA_PASSES = [ |
| "code-pushing", |
| "duplicate-function-elimination", |
| "dce", |
| "remove-unused-brs", |
| "remove-unused-names", |
| "local-cse", |
| "optimize-instructions", |
| "post-emscripten", |
| "precompute", |
| "simplify-locals", |
| "simplify-locals-nostructure", |
| "vacuum", |
| "coalesce-locals", |
| "reorder-locals", |
| "merge-blocks", |
| "remove-unused-module-elements", |
| "memory-packing", |
| ] |
| passes = [] |
| while 1: |
| passes.append(random.choice(BINARYEN_EXTRA_PASSES)) |
| if random.random() < 0.1: |
| break |
| js_args += ['-s', 'BINARYEN_EXTRA_PASSES="' + ','.join(passes) + '"'] |
| if random.random() < 0.5: |
| js_args += ['-s', 'ALLOW_MEMORY_GROWTH=1'] |
| if random.random() < 0.5 and 'ALLOW_MEMORY_GROWTH=1' not in js_args and 'BINARYEN=1' not in js_args: |
| js_args += ['-s', 'MAIN_MODULE=1'] |
| if random.random() < 0.25: |
| js_args += ['-s', 'INLINING_LIMIT=1'] # inline nothing, for more call interaction |
| if random.random() < 0.5: |
| js_args += ['-s', 'ASSERTIONS=1'] |
| print('(compile)', ' '.join(js_args)) |
| short_args = [shared.EMCC, fail_output_name] + js_args[5:] |
| escaped_short_args = map(lambda x: ("'" + x + "'") if '"' in x else x, short_args) |
| open(fullname, 'a').write('\n// ' + ' '.join(escaped_short_args) + '\n\n') |
| try: |
| shared.run_process(js_args) |
| assert os.path.exists(filename + '.js') |
| return js_args |
| except Exception: |
| return False |
| |
| def execute_js(engine): |
| print('(run in %s)' % engine) |
| try: |
| js = shared.timeout_run(Popen(shared.NODE_JS + [filename + '.js'], stdout=PIPE, stderr=PIPE), 15 * 60) |
| except Exception: |
| print('failed to run in primary') |
| return False |
| js = js.split('\n')[0] + '\n' # remove any extra printed stuff (node workarounds) |
| return correct1 == js or correct2 == js |
| |
| def fail(): |
| global fails |
| print("EMSCRIPTEN BUG") |
| notes['embug'] += 1 |
| fails += 1 |
| shutil.copyfile(fullname, fail_output_name) |
| |
| js_args = try_js() |
| if not js_args: |
| fail() |
| continue |
| if not execute_js(engine): |
| fail() |
| continue |