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