forked from rust-lang/rust-bindgen
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathpredicate.py
executable file
·272 lines (221 loc) · 9.22 KB
/
predicate.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#!/usr/bin/env python3
import argparse
import os
import re
import shlex
import subprocess
import sys
from tempfile import NamedTemporaryFile
DESC = """
Determine whether `bindgen` can successfully process a C or C++ input header.
First, `bindgen` is run on the input header. Then the emitted bindings are
compiled with `rustc`. Finally, the compiled bindings' layout tests are run.
By default, this script will exit zero if all of the above steps are successful,
and non-zero if any of them fail. This is appropriate for determining if some
test case (perhaps generated with `csmith` or another fuzzer) uncovers any bugs
in `bindgen`.
However, this script can also be used when reducing (perhaps with `creduce`) a
known-bad test case into a new, smaller test case that exhibits the same bad
behavior. In this mode, you might expect that the emitted bindings fail to
compile with `rustc`, and want to exit non-zero early if that is not the
case. See the "reducing arguments" for details and what knobs are available.
"""
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=DESC.strip())
BINDGEN_ARGS = "--with-derive-partialeq \
--with-derive-eq \
--with-derive-partialord \
--with-derive-ord \
--with-derive-hash \
--with-derive-default"
parser.add_argument(
"--bindgen-args",
type=str,
default=BINDGEN_ARGS,
help="An argument string that `bindgen` should be invoked with. By default, all traits are derived. Note that the input header and output bindings file will automatically be provided by this script, and you should not manually specify them.")
parser.add_argument(
"--save-temp-files",
action="store_true",
help="Do not delete temporary files.")
parser.add_argument(
"input",
type=str,
default="input.h",
help="The input header file. Defaults to 'input.h'.")
REDUCING_DESC = """
Arguments that are useful when reducing known-bad test cases into
equivalent-but-smaller test cases that exhibit the same bug with `creduce`.
"""
reducing = parser.add_argument_group("reducing arguments", REDUCING_DESC.strip())
reducing.add_argument(
"--release",
action="store_true",
help="Use a release instead of a debug build.")
reducing.add_argument(
"--expect-bindgen-fail",
action="store_true",
help="Exit non-zero if `bindgen` successfully emits bindings.")
reducing.add_argument(
"--bindgen-grep",
type=str,
help="Exit non-zero if the given regexp pattern is not found in `bindgen`'s output.")
reducing.add_argument(
"--bindings-grep",
type=str,
help="Exit non-zero if the given regexp pattern is not found in the emitted bindings.")
reducing.add_argument(
"--no-compile-bindings",
action="store_false",
dest="rustc",
help="Do not attempt to compile the emitted bindings with `rustc`.")
reducing.add_argument(
"--extra-compile-file",
type=str,
help="Append the content of this extra file to the end of the emitted bindings just before compiling it.")
reducing.add_argument(
"--expect-compile-fail",
action="store_true",
help="Exit non-zero if `rustc` successfully compiles the emitted bindings.")
reducing.add_argument(
"--rustc-grep",
type=str,
help="Exit non-zero if the output from compiling the bindings with `rustc` does not contain the given regexp pattern")
reducing.add_argument(
"--no-layout-tests",
action="store_false",
dest="layout_tests",
help="Do not run the compiled bindings' layout tests.")
reducing.add_argument(
"--expect-layout-tests-fail",
action="store_true",
help="Exit non-zero if the compiled bindings' layout tests pass.")
reducing.add_argument(
"--layout-tests-grep",
type=str,
help="Exit non-zero if the output of running the compiled bindings' layout tests does not contain the given regexp pattern.")
################################################################################
class ExitOne(Exception):
pass
def exit_1(msg, child=None):
print(msg)
if child:
stdout = decode(child.stdout)
for line in stdout.splitlines():
sys.stdout.write("+")
sys.stdout.write(line)
sys.stdout.write("\n")
stderr = decode(child.stderr)
for line in stderr.splitlines():
sys.stderr.write("+")
sys.stderr.write(line)
sys.stderr.write("\n")
raise ExitOne()
def main():
args = parser.parse_args()
os.environ["RUST_BACKTRACE"] = "full"
exit_code = 0
try:
bindings = new_temp_file(prefix="bindings-", suffix=".rs")
run_bindgen(args, bindings)
if args.rustc and not args.expect_bindgen_fail:
test_exe = new_temp_file(prefix="layout-tests-")
run_rustc(args, bindings, test_exe)
if args.layout_tests and not args.expect_compile_fail:
run_layout_tests(args, test_exe)
except ExitOne:
exit_code = 1
except Exception as e:
exit_code = 2
print("Unexpected exception:", e)
if not args.save_temp_files:
for path in TEMP_FILES:
try:
os.remove(path)
except Exception as e:
print("Unexpected exception:", e)
sys.exit(exit_code)
def run(cmd, **kwargs):
print("Running:", cmd)
return subprocess.run(cmd, **kwargs)
def decode(f):
return f.decode(encoding="utf-8", errors="ignore")
TEMP_FILES = []
def new_temp_file(prefix, suffix=None):
temp = NamedTemporaryFile(delete=False, prefix=prefix, suffix=suffix)
temp.close()
TEMP_FILES.append(temp.name)
return temp.name
def contains(pattern, lines):
for line in lines:
if re.match(pattern, line):
return True
return False
def regexp(pattern):
if not pattern.startswith("^"):
pattern = ".*" + pattern
if not pattern.endswith("$"):
pattern = pattern + ".*"
return re.compile(pattern)
def run_bindgen(args, bindings):
manifest_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),
"..",
"Cargo.toml"))
command = ["cargo", "run", "--manifest-path", manifest_path]
if args.release:
command += ["--release"]
command += ["--", args.input, "-o", bindings]
command += shlex.split(args.bindgen_args)
child = run(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
if args.bindgen_grep:
pattern = regexp(args.bindgen_grep)
if not (contains(pattern, decode(child.stdout).splitlines()) or
contains(pattern, decode(child.stderr).splitlines())):
exit_1("Error: did not find '{}' in `bindgen`'s output".format(args.bindgen_grep), child)
if args.expect_bindgen_fail and child.returncode == 0:
exit_1("Error: expected running `bindgen` to fail, but it didn't", child)
if not args.expect_bindgen_fail and child.returncode != 0:
exit_1("Error: running `bindgen` failed", child)
if args.bindings_grep:
pattern = regexp(args.bindings_grep)
with open(bindings, mode="r") as f:
if not contains(pattern, f):
print("Error: expected the emitted bindings to contain '{}', but they didn't".format(args.bindings_grep))
print("---------- {} ----------------------------------------------".format(bindings))
f.seek(0)
print(f.read())
raise ExitOne()
def run_rustc(args, bindings, test_exe):
if args.extra_compile_file:
with open(bindings, mode="a") as outfile:
with open(args.extra_compile_file, mode="r") as infile:
outfile.write(infile.read())
child = run(
["rustc", "--crate-type", "lib", "--test", "-o", test_exe, bindings],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
if args.rustc_grep:
pattern = regexp(args.rustc_grep)
if not (contains(pattern, decode(child.stdout).splitlines()) or
contains(pattern, decode(child.stderr).splitlines())):
exit_1("Error: did not find '{}' in `rustc`'s output".format(args.rustc_grep), child)
if args.expect_compile_fail and child.returncode == 0:
exit_1("Error: expected running `rustc` on the emitted bindings to fail, but it didn't", child)
if not args.expect_compile_fail and child.returncode != 0:
exit_1("Error: running `rustc` on the emitted bindings failed", child)
def run_layout_tests(args, test_exe):
child = run(
[test_exe],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
if args.layout_tests_grep:
pattern = regexp(args.layout_tests_grep)
if not (contains(pattern, decode(child.stdout).splitlines()) or
contains(pattern, decode(child.stderr).splitlines())):
exit_1("Error: did not find '{}' in the compiled bindings' layout tests' output".format(args.layout_tests_grep), child)
if args.expect_layout_tests_fail and child.returncode == 0:
exit_1("Error: expected running the compiled bindings' layout tests to fail, but it didn't", child)
if not args.expect_layout_tests_fail and child.returncode != 0:
exit_1("Error: running the compiled bindings' layout tests failed", child)
if __name__ == "__main__":
main()