Cranelift CLIF-level differential fuzzer (#3038)
* cranelift: Initial fuzzer implementation * cranelift: Generate multiple test cases in fuzzer * cranelift: Separate function generator in fuzzer * cranelift: Insert random instructions in fuzzer * cranelift: Rename gen_testcase * cranelift: Implement div for unsigned values in interpreter * cranelift: Run all test cases in fuzzer * cranelift: Comment options in function_runner * cranelift: Improve fuzzgen README.md * cranelift: Fuzzgen remove unused variable * cranelift: Fuzzer code style fixes Thanks! @bjorn3 * cranelift: Fix nits in CLIF fuzzer Thanks @cfallin! * cranelift: Implement Arbitrary for TestCase * cranelift: Remove gen_testcase * cranelift: Move fuzzers to wasmtime fuzz directory * cranelift: CLIF-Fuzzer ignore tests that produce traps * cranelift: CLIF-Fuzzer create new fuzz target to validate generated testcases * cranelift: Store clif-fuzzer config in a separate struct * cranelift: Generate variables upfront per function * cranelift: Prevent publishing of fuzzgen crate
This commit is contained in:
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -640,6 +640,16 @@ dependencies = [
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-fuzzgen"
|
||||
version = "0.75.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arbitrary",
|
||||
"cranelift",
|
||||
"rand 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-interpreter"
|
||||
version = "0.75.0"
|
||||
@@ -747,6 +757,7 @@ dependencies = [
|
||||
"cranelift-entity",
|
||||
"cranelift-filetests",
|
||||
"cranelift-frontend",
|
||||
"cranelift-fuzzgen",
|
||||
"cranelift-interpreter",
|
||||
"cranelift-jit",
|
||||
"cranelift-module",
|
||||
@@ -3676,6 +3687,9 @@ name = "wasmtime-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"cranelift-filetests",
|
||||
"cranelift-fuzzgen",
|
||||
"cranelift-interpreter",
|
||||
"cranelift-reader",
|
||||
"cranelift-wasm",
|
||||
"libfuzzer-sys",
|
||||
|
||||
@@ -29,6 +29,7 @@ cranelift-object = { path = "object", version = "0.75.0" }
|
||||
cranelift-jit = { path = "jit", version = "0.75.0" }
|
||||
cranelift-preopt = { path = "preopt", version = "0.75.0" }
|
||||
cranelift = { path = "umbrella", version = "0.75.0" }
|
||||
cranelift-fuzzgen = { path = "fuzzgen", version = "0.75.0" }
|
||||
filecheck = "0.5.0"
|
||||
log = "0.4.8"
|
||||
termcolor = "1.1.2"
|
||||
|
||||
@@ -91,13 +91,17 @@ impl SingleFunctionCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Compilation Error when compiling a function.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CompilationError {
|
||||
/// This Target ISA is invalid for the current host.
|
||||
#[error("Cross-compilation not currently supported; use the host's default calling convention \
|
||||
or remove the specified calling convention in the function signature to use the host's default.")]
|
||||
InvalidTargetIsa,
|
||||
/// Cranelift codegen error.
|
||||
#[error("Cranelift codegen error")]
|
||||
CodegenError(#[from] CodegenError),
|
||||
/// Memory mapping error.
|
||||
#[error("Memory mapping error")]
|
||||
IoError(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ use std::path::Path;
|
||||
use std::time;
|
||||
|
||||
mod concurrent;
|
||||
mod function_runner;
|
||||
pub mod function_runner;
|
||||
mod match_directive;
|
||||
mod runner;
|
||||
mod runone;
|
||||
|
||||
18
cranelift/fuzzgen/Cargo.toml
Normal file
18
cranelift/fuzzgen/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "cranelift-fuzzgen"
|
||||
version = "0.75.0"
|
||||
authors = ["The Wasmtime Project Developers"]
|
||||
description = "Cranelift module generator"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
repository = "https://github.com/bytecodealliance/wasmtime"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
|
||||
[dependencies]
|
||||
cranelift = { path = "../umbrella", version = "0.75.0" }
|
||||
|
||||
anyhow = "1.0.19"
|
||||
arbitrary = "1.0.0"
|
||||
rand = "0.8.0"
|
||||
220
cranelift/fuzzgen/LICENSE
Normal file
220
cranelift/fuzzgen/LICENSE
Normal file
@@ -0,0 +1,220 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
--- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
3
cranelift/fuzzgen/README.md
Normal file
3
cranelift/fuzzgen/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# `cranelift-fuzzgen`
|
||||
|
||||
This crate implements a generator to create random Cranelift modules.
|
||||
24
cranelift/fuzzgen/src/config.rs
Normal file
24
cranelift/fuzzgen/src/config.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// Holds the range of acceptable values to use during the generation of testcases
|
||||
pub struct Config {
|
||||
pub test_case_inputs: RangeInclusive<usize>,
|
||||
pub signature_params: RangeInclusive<usize>,
|
||||
pub signature_rets: RangeInclusive<usize>,
|
||||
pub instructions_per_block: RangeInclusive<usize>,
|
||||
/// Number of variables that we allocate per function
|
||||
/// This value does not include the signature params
|
||||
pub vars_per_function: RangeInclusive<usize>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
test_case_inputs: 1..=10,
|
||||
signature_params: 0..=16,
|
||||
signature_rets: 0..=16,
|
||||
instructions_per_block: 0..=64,
|
||||
vars_per_function: 0..=16,
|
||||
}
|
||||
}
|
||||
}
|
||||
253
cranelift/fuzzgen/src/function_generator.rs
Normal file
253
cranelift/fuzzgen/src/function_generator.rs
Normal file
@@ -0,0 +1,253 @@
|
||||
use crate::config::Config;
|
||||
use anyhow::Result;
|
||||
use arbitrary::Unstructured;
|
||||
use cranelift::codegen::ir::types::*;
|
||||
use cranelift::codegen::ir::{AbiParam, ExternalName, Function, Opcode, Signature, Type, Value};
|
||||
use cranelift::codegen::isa::CallConv;
|
||||
use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
|
||||
use cranelift::prelude::{EntityRef, InstBuilder};
|
||||
|
||||
fn insert_opcode_arity_0(
|
||||
_fgen: &mut FunctionGenerator,
|
||||
builder: &mut FunctionBuilder,
|
||||
opcode: Opcode,
|
||||
_args: &'static [Type],
|
||||
_rets: &'static [Type],
|
||||
) -> Result<()> {
|
||||
builder.ins().NullAry(opcode, INVALID);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert_opcode_arity_2(
|
||||
fgen: &mut FunctionGenerator,
|
||||
builder: &mut FunctionBuilder,
|
||||
opcode: Opcode,
|
||||
args: &'static [Type],
|
||||
rets: &'static [Type],
|
||||
) -> Result<()> {
|
||||
let arg0 = fgen.get_variable_of_type(args[0])?;
|
||||
let arg0 = builder.use_var(arg0);
|
||||
|
||||
let arg1 = fgen.get_variable_of_type(args[1])?;
|
||||
let arg1 = builder.use_var(arg1);
|
||||
|
||||
let typevar = rets[0];
|
||||
let (inst, dfg) = builder.ins().Binary(opcode, typevar, arg0, arg1);
|
||||
let results = dfg.inst_results(inst).to_vec();
|
||||
|
||||
for (val, ty) in results.into_iter().zip(rets) {
|
||||
let var = fgen.get_variable_of_type(*ty)?;
|
||||
builder.def_var(var, val);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
type OpcodeInserter = fn(
|
||||
fgen: &mut FunctionGenerator,
|
||||
builder: &mut FunctionBuilder,
|
||||
Opcode,
|
||||
&'static [Type],
|
||||
&'static [Type],
|
||||
) -> Result<()>;
|
||||
|
||||
// TODO: Derive this from the `cranelift-meta` generator.
|
||||
const OPCODE_SIGNATURES: &'static [(
|
||||
Opcode,
|
||||
&'static [Type], // Args
|
||||
&'static [Type], // Rets
|
||||
OpcodeInserter,
|
||||
)] = &[
|
||||
(Opcode::Nop, &[], &[], insert_opcode_arity_0),
|
||||
// Iadd
|
||||
(Opcode::Iadd, &[I8, I8], &[I8], insert_opcode_arity_2),
|
||||
(Opcode::Iadd, &[I16, I16], &[I16], insert_opcode_arity_2),
|
||||
(Opcode::Iadd, &[I32, I32], &[I32], insert_opcode_arity_2),
|
||||
(Opcode::Iadd, &[I64, I64], &[I64], insert_opcode_arity_2),
|
||||
// Isub
|
||||
(Opcode::Isub, &[I8, I8], &[I8], insert_opcode_arity_2),
|
||||
(Opcode::Isub, &[I16, I16], &[I16], insert_opcode_arity_2),
|
||||
(Opcode::Isub, &[I32, I32], &[I32], insert_opcode_arity_2),
|
||||
(Opcode::Isub, &[I64, I64], &[I64], insert_opcode_arity_2),
|
||||
// Imul
|
||||
(Opcode::Imul, &[I8, I8], &[I8], insert_opcode_arity_2),
|
||||
(Opcode::Imul, &[I16, I16], &[I16], insert_opcode_arity_2),
|
||||
(Opcode::Imul, &[I32, I32], &[I32], insert_opcode_arity_2),
|
||||
(Opcode::Imul, &[I64, I64], &[I64], insert_opcode_arity_2),
|
||||
// Udiv
|
||||
(Opcode::Udiv, &[I8, I8], &[I8], insert_opcode_arity_2),
|
||||
(Opcode::Udiv, &[I16, I16], &[I16], insert_opcode_arity_2),
|
||||
(Opcode::Udiv, &[I32, I32], &[I32], insert_opcode_arity_2),
|
||||
(Opcode::Udiv, &[I64, I64], &[I64], insert_opcode_arity_2),
|
||||
// Sdiv
|
||||
(Opcode::Sdiv, &[I8, I8], &[I8], insert_opcode_arity_2),
|
||||
(Opcode::Sdiv, &[I16, I16], &[I16], insert_opcode_arity_2),
|
||||
(Opcode::Sdiv, &[I32, I32], &[I32], insert_opcode_arity_2),
|
||||
(Opcode::Sdiv, &[I64, I64], &[I64], insert_opcode_arity_2),
|
||||
];
|
||||
|
||||
pub struct FunctionGenerator<'r, 'data>
|
||||
where
|
||||
'data: 'r,
|
||||
{
|
||||
u: &'r mut Unstructured<'data>,
|
||||
config: &'r Config,
|
||||
vars: Vec<(Type, Variable)>,
|
||||
}
|
||||
|
||||
impl<'r, 'data> FunctionGenerator<'r, 'data>
|
||||
where
|
||||
'data: 'r,
|
||||
{
|
||||
pub fn new(u: &'r mut Unstructured<'data>, config: &'r Config) -> Self {
|
||||
Self {
|
||||
u,
|
||||
config,
|
||||
vars: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_callconv(&mut self) -> Result<CallConv> {
|
||||
// TODO: Generate random CallConvs per target
|
||||
Ok(CallConv::SystemV)
|
||||
}
|
||||
|
||||
fn generate_type(&mut self) -> Result<Type> {
|
||||
// TODO: It would be nice if we could get these directly from cranelift
|
||||
let scalars = [
|
||||
// IFLAGS, FFLAGS,
|
||||
// B1, B8, B16, B32, B64, B128,
|
||||
I8, I16, I32, I64,
|
||||
// I128,
|
||||
// F32, F64,
|
||||
// R32, R64,
|
||||
];
|
||||
// TODO: vector types
|
||||
|
||||
let ty = self.u.choose(&scalars[..])?;
|
||||
Ok(*ty)
|
||||
}
|
||||
|
||||
fn generate_abi_param(&mut self) -> Result<AbiParam> {
|
||||
// TODO: Generate more advanced abi params (structs/purposes/extensions/etc...)
|
||||
let ty = self.generate_type()?;
|
||||
Ok(AbiParam::new(ty))
|
||||
}
|
||||
|
||||
fn generate_signature(&mut self) -> Result<Signature> {
|
||||
let callconv = self.generate_callconv()?;
|
||||
let mut sig = Signature::new(callconv);
|
||||
|
||||
for _ in 0..self.u.int_in_range(self.config.signature_params.clone())? {
|
||||
sig.params.push(self.generate_abi_param()?);
|
||||
}
|
||||
|
||||
for _ in 0..self.u.int_in_range(self.config.signature_rets.clone())? {
|
||||
sig.returns.push(self.generate_abi_param()?);
|
||||
}
|
||||
|
||||
Ok(sig)
|
||||
}
|
||||
|
||||
/// Creates a new var
|
||||
fn create_var(&mut self, builder: &mut FunctionBuilder, ty: Type) -> Result<Variable> {
|
||||
let id = self.vars.len();
|
||||
let var = Variable::new(id);
|
||||
builder.declare_var(var, ty);
|
||||
self.vars.push((ty, var));
|
||||
Ok(var)
|
||||
}
|
||||
|
||||
fn vars_of_type(&self, ty: Type) -> Vec<Variable> {
|
||||
self.vars
|
||||
.iter()
|
||||
.filter(|(var_ty, _)| *var_ty == ty)
|
||||
.map(|(_, v)| *v)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get a variable of type `ty` from the current function
|
||||
fn get_variable_of_type(&mut self, ty: Type) -> Result<Variable> {
|
||||
let opts = self.vars_of_type(ty);
|
||||
let var = self.u.choose(&opts[..])?;
|
||||
Ok(*var)
|
||||
}
|
||||
|
||||
/// Generates an instruction(`iconst`/`fconst`/etc...) to introduce a constant value
|
||||
fn generate_const(&mut self, builder: &mut FunctionBuilder, ty: Type) -> Result<Value> {
|
||||
let imm64 = match ty {
|
||||
I8 => self.u.arbitrary::<i8>()? as i64,
|
||||
I16 => self.u.arbitrary::<i16>()? as i64,
|
||||
I32 => self.u.arbitrary::<i32>()? as i64,
|
||||
I64 => self.u.arbitrary::<i64>()?,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let val = builder.ins().iconst(ty, imm64);
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
fn generate_return(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
|
||||
let ret_params = builder.func.signature.returns.clone();
|
||||
|
||||
let vars = ret_params
|
||||
.iter()
|
||||
.map(|p| self.get_variable_of_type(p.value_type))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let vals = vars
|
||||
.into_iter()
|
||||
.map(|v| builder.use_var(v))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
builder.ins().return_(&vals[..]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Inserts a random instruction into the block
|
||||
fn generate_instruction(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
|
||||
let (op, args, rets, inserter) = *self.u.choose(OPCODE_SIGNATURES)?;
|
||||
inserter(self, builder, op, args, rets)
|
||||
}
|
||||
|
||||
pub fn generate(mut self) -> Result<Function> {
|
||||
let sig = self.generate_signature()?;
|
||||
|
||||
let mut fn_builder_ctx = FunctionBuilderContext::new();
|
||||
let mut func = Function::with_name_signature(ExternalName::user(0, 0), sig.clone());
|
||||
|
||||
let mut builder = FunctionBuilder::new(&mut func, &mut fn_builder_ctx);
|
||||
let block0 = builder.create_block();
|
||||
builder.append_block_params_for_function_params(block0);
|
||||
builder.switch_to_block(block0);
|
||||
builder.seal_block(block0);
|
||||
|
||||
// Define variables for the function signature
|
||||
for (i, param) in sig.params.iter().enumerate() {
|
||||
let var = self.create_var(&mut builder, param.value_type)?;
|
||||
let block_param = builder.block_params(block0)[i];
|
||||
builder.def_var(var, block_param);
|
||||
}
|
||||
|
||||
// Create a pool of vars that are going to be used in this function
|
||||
for _ in 0..self.u.int_in_range(self.config.vars_per_function.clone())? {
|
||||
let ty = self.generate_type()?;
|
||||
let var = self.create_var(&mut builder, ty)?;
|
||||
let value = self.generate_const(&mut builder, ty)?;
|
||||
builder.def_var(var, value);
|
||||
}
|
||||
|
||||
for _ in 0..self
|
||||
.u
|
||||
.int_in_range(self.config.instructions_per_block.clone())?
|
||||
{
|
||||
self.generate_instruction(&mut builder)?;
|
||||
}
|
||||
|
||||
// TODO: We should make this part of the regular instruction selection
|
||||
self.generate_return(&mut builder)?;
|
||||
|
||||
builder.finalize();
|
||||
|
||||
Ok(func)
|
||||
}
|
||||
}
|
||||
82
cranelift/fuzzgen/src/lib.rs
Normal file
82
cranelift/fuzzgen/src/lib.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use crate::config::Config;
|
||||
use crate::function_generator::FunctionGenerator;
|
||||
use anyhow::Result;
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
use cranelift::codegen::data_value::DataValue;
|
||||
use cranelift::codegen::ir::types::*;
|
||||
use cranelift::codegen::ir::Function;
|
||||
use cranelift::prelude::*;
|
||||
|
||||
mod config;
|
||||
mod function_generator;
|
||||
|
||||
pub type TestCaseInput = Vec<DataValue>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestCase {
|
||||
pub func: Function,
|
||||
/// Generate multiple test inputs for each test case.
|
||||
/// This allows us to get more coverage per compilation, which may be somewhat expensive.
|
||||
pub inputs: Vec<TestCaseInput>,
|
||||
}
|
||||
|
||||
impl<'a> Arbitrary<'a> for TestCase {
|
||||
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
FuzzGen::new(u)
|
||||
.generate_test()
|
||||
.map_err(|_| arbitrary::Error::IncorrectFormat)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FuzzGen<'r, 'data>
|
||||
where
|
||||
'data: 'r,
|
||||
{
|
||||
u: &'r mut Unstructured<'data>,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl<'r, 'data> FuzzGen<'r, 'data>
|
||||
where
|
||||
'data: 'r,
|
||||
{
|
||||
pub fn new(u: &'r mut Unstructured<'data>) -> Self {
|
||||
Self {
|
||||
u,
|
||||
config: Config::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_test_inputs(&mut self, signature: &Signature) -> Result<Vec<TestCaseInput>> {
|
||||
let num_tests = self.u.int_in_range(self.config.test_case_inputs.clone())?;
|
||||
let mut inputs = Vec::with_capacity(num_tests);
|
||||
|
||||
for _ in 0..num_tests {
|
||||
let test_args = signature
|
||||
.params
|
||||
.iter()
|
||||
.map(|p| {
|
||||
let imm64 = match p.value_type {
|
||||
I8 => self.u.arbitrary::<i8>()? as i64,
|
||||
I16 => self.u.arbitrary::<i16>()? as i64,
|
||||
I32 => self.u.arbitrary::<i32>()? as i64,
|
||||
I64 => self.u.arbitrary::<i64>()?,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Ok(DataValue::from_integer(imm64, p.value_type)?)
|
||||
})
|
||||
.collect::<Result<TestCaseInput>>()?;
|
||||
|
||||
inputs.push(test_args);
|
||||
}
|
||||
|
||||
Ok(inputs)
|
||||
}
|
||||
|
||||
pub fn generate_test(mut self) -> Result<TestCase> {
|
||||
let func = FunctionGenerator::new(&mut self.u, &self.config).generate()?;
|
||||
let inputs = self.generate_test_inputs(&func.signature)?;
|
||||
|
||||
Ok(TestCase { func, inputs })
|
||||
}
|
||||
}
|
||||
@@ -664,7 +664,7 @@ impl<'a, V> ControlFlow<'a, V> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
pub enum CraneliftTrap {
|
||||
#[error("user code: {0}")]
|
||||
User(TrapCode),
|
||||
|
||||
@@ -12,6 +12,9 @@ cargo-fuzz = true
|
||||
cranelift-codegen = { path = "../cranelift/codegen" }
|
||||
cranelift-reader = { path = "../cranelift/reader" }
|
||||
cranelift-wasm = { path = "../cranelift/wasm" }
|
||||
cranelift-filetests = { path = "../cranelift/filetests" }
|
||||
cranelift-interpreter = { path = "../cranelift/interpreter" }
|
||||
cranelift-fuzzgen = { path = "../cranelift/fuzzgen" }
|
||||
libfuzzer-sys = "0.4.0"
|
||||
target-lexicon = "0.12"
|
||||
peepmatic-fuzzing = { path = "../cranelift/peepmatic/crates/fuzzing", optional = true }
|
||||
@@ -84,3 +87,15 @@ name = "instantiate-maybe-invalid"
|
||||
path = "fuzz_targets/instantiate-maybe-invalid.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "cranelift-fuzzgen"
|
||||
path = "fuzz_targets/cranelift-fuzzgen.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "cranelift-fuzzgen-verify"
|
||||
path = "fuzz_targets/cranelift-fuzzgen-verify.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
11
fuzz/fuzz_targets/cranelift-fuzzgen-verify.rs
Normal file
11
fuzz/fuzz_targets/cranelift-fuzzgen-verify.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
use cranelift_codegen::{settings, verify_function};
|
||||
use cranelift_fuzzgen::TestCase;
|
||||
|
||||
fuzz_target!(|testcase: TestCase| {
|
||||
let flags = settings::Flags::new(settings::builder());
|
||||
verify_function(&testcase.func, &flags).unwrap();
|
||||
});
|
||||
85
fuzz/fuzz_targets/cranelift-fuzzgen.rs
Normal file
85
fuzz/fuzz_targets/cranelift-fuzzgen.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
use cranelift_codegen::data_value::DataValue;
|
||||
use cranelift_filetests::function_runner::{CompiledFunction, SingleFunctionCompiler};
|
||||
use cranelift_fuzzgen::*;
|
||||
use cranelift_interpreter::environment::FuncIndex;
|
||||
use cranelift_interpreter::environment::FunctionStore;
|
||||
use cranelift_interpreter::interpreter::{Interpreter, InterpreterState};
|
||||
use cranelift_interpreter::step::ControlFlow;
|
||||
use cranelift_interpreter::step::CraneliftTrap;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum RunResult {
|
||||
Success(Vec<DataValue>),
|
||||
Trap(CraneliftTrap),
|
||||
Error(Box<dyn std::error::Error>),
|
||||
}
|
||||
|
||||
impl RunResult {
|
||||
pub fn unwrap(self) -> Vec<DataValue> {
|
||||
match self {
|
||||
RunResult::Success(d) => d,
|
||||
_ => panic!("Expected RunResult::Success in unwrap but got: {:?}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_in_interpreter(interpreter: &mut Interpreter, args: &[DataValue]) -> RunResult {
|
||||
// The entrypoint function is always 0
|
||||
let index = FuncIndex::from_u32(0);
|
||||
let res = interpreter.call_by_index(index, args);
|
||||
|
||||
match res {
|
||||
Ok(ControlFlow::Return(results)) => RunResult::Success(results.to_vec()),
|
||||
Ok(ControlFlow::Trap(trap)) => RunResult::Trap(trap),
|
||||
Ok(cf) => RunResult::Error(format!("Unrecognized exit ControlFlow: {:?}", cf).into()),
|
||||
Err(e) => RunResult::Error(format!("InterpreterError: {:?}", e).into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_in_host(compiled_fn: &CompiledFunction, args: &[DataValue]) -> RunResult {
|
||||
let res = compiled_fn.call(args);
|
||||
RunResult::Success(res)
|
||||
}
|
||||
|
||||
fuzz_target!(|testcase: TestCase| {
|
||||
let mut interpreter = {
|
||||
let mut env = FunctionStore::default();
|
||||
env.add(testcase.func.name.to_string(), &testcase.func);
|
||||
|
||||
let state = InterpreterState::default().with_function_store(env);
|
||||
let interpreter = Interpreter::new(state);
|
||||
interpreter
|
||||
};
|
||||
|
||||
// Native fn
|
||||
let mut host_compiler = SingleFunctionCompiler::with_default_host_isa();
|
||||
let compiled_fn = host_compiler.compile(testcase.func.clone()).unwrap();
|
||||
|
||||
for args in &testcase.inputs {
|
||||
let int_res = run_in_interpreter(&mut interpreter, args);
|
||||
match int_res {
|
||||
RunResult::Success(_) => {}
|
||||
RunResult::Trap(_) => {
|
||||
// We currently ignore inputs that trap the interpreter
|
||||
// We could catch traps in the host run and compare them to the
|
||||
// interpreter traps, but since we already test trap cases with
|
||||
// wasm tests and wasm-level fuzzing, the amount of effort does
|
||||
// not justify implementing it again here.
|
||||
return;
|
||||
}
|
||||
RunResult::Error(_) => panic!("interpreter failed: {:?}", int_res),
|
||||
}
|
||||
|
||||
let host_res = run_in_host(&compiled_fn, args);
|
||||
match host_res {
|
||||
RunResult::Success(_) => {}
|
||||
_ => panic!("host failed: {:?}", host_res),
|
||||
}
|
||||
|
||||
assert_eq!(int_res.unwrap(), host_res.unwrap());
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user